SF is built on top of pi-mono, an open-source agent framework maintained by Mario Zechner at [github.com/badlogic/pi-mono](https://github.com/badlogic/pi-mono). SF takes pi-mono as vendored source copies — four packages (`pi-agent-core`, `pi-ai`, `pi-tui`, `pi-coding-agent`) are copied directly into `/packages/` and modified in-place.
This worked as a starting point but has created a structural problem: **SF-authored code lives inside the pi packages**. The 98KB `agent-session.ts`, the compaction system, three run modes (interactive, RPC, print), the CLI utilities, and the `createAgentSession()` factory are all authored by SF but stored inside `pi-coding-agent`. Approximately 79 SF-authored TypeScript files are mixed in with pi's upstream source.
The consequence is that every pi-mono update requires manually diffing SF's modifications against the incoming upstream changes file-by-file. There is no reliable way to tell which files are SF's and which are pi's without reading them. Updates that should take hours become multi-day archaeology projects. Pi-mono is currently 10 versions behind upstream (0.57.1 vs 0.67.2 as of April 2026), with a blocking API change (`session_switch`/`session_fork` removal in v0.65.0) unresolved.
Beyond update pain, there is a project risk: if pi-mono stops being maintained or changes direction, SF's business logic is entangled with a dependency it no longer controls.
SF's code is clearly separated from pi's code at the module system level. The vendored pi packages contain only upstream code (plus the extension system, which is intentionally pi-typed). SF's agent logic lives in SF-owned packages that **depend on** pi but do not live inside it. When a new pi release comes out, a maintainer updates the vendored pi packages, runs the TypeScript compiler, and fixes the errors that surface in the SF packages — without ever needing to diff individual files to find what's ours vs. theirs.
| Zero SF business logic in vendored pi packages | `pi-coding-agent/src/` contains no files that import from `@sf/` packages (except the extension system's bundled module map) |
| Applying a pi-mono update is scoped | Updating pi packages produces type errors only in `@sf/agent-core` and `@sf/agent-modes` — no changes required in pi package source files |
A new workspace package at `packages/sf-agent-core/` that owns all SF session orchestration logic. It depends on `@sf/pi-coding-agent`, `@sf/pi-agent-core`, and `@sf/pi-ai`. Nothing in the vendored pi packages depends on it.
A new workspace package at `packages/sf-agent-modes/` that owns all run-mode and CLI code. It depends on `@sf/agent-core`, `@sf/pi-coding-agent`, and `@sf/pi-tui`. It is the layer the top-level `sf-run` binary entry point assembles.
The extension system (`src/core/extensions/`) remains in `pi-coding-agent` because it is legitimately pi-typed: extension authors write against pi's `AgentMessage`, `Model`, and `TUI` types. The virtual module map in `extensions/loader.ts` must be updated to include `@sf/agent-core` and `@sf/agent-modes` so extensions can import from them.
Each new package must have an `index.ts` that declares its public API. Internal files must not be imported by path from outside the package. Specifically:
-`web/bridge-service.ts` currently imports `AgentSessionEvent` from an internal path in `pi-coding-agent` — this must be fixed to use the public export from `@sf/agent-core`
Extensions are loaded by `pi-coding-agent`'s jiti-based loader. The virtual module map (`STATIC_BUNDLED_MODULES`) must be updated to resolve `@sf/agent-core` and `@sf/agent-modes` alongside the existing pi package mappings. This requires both a map entry and a top-level bundle import in `loader.ts` (see ADR-009 for the exact diff). Extension authors must not need to change their import paths.
1. Does `clearQueue()` on `AgentSession` need to be added to a public type export, or is it already accessible to the auto-mode extension that uses it?