111 lines
7.4 KiB
Markdown
111 lines
7.4 KiB
Markdown
# PRD: Pi Clean Seam Refactor
|
|
|
|
**Author:** Tom Boucher
|
|
**Date:** 2026-04-14
|
|
**ADR:** [ADR-010-pi-clean-seam-architecture.md](./ADR-010-pi-clean-seam-architecture.md)
|
|
**Priority:** High — blocks safe pi-mono upstream updates
|
|
|
|
---
|
|
|
|
## Problem Statement
|
|
|
|
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.
|
|
|
|
## Vision
|
|
|
|
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.
|
|
|
|
## Success Criteria
|
|
|
|
| Criterion | Measurement |
|
|
|-----------|-------------|
|
|
| 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) |
|
|
| Module boundary is compiler-enforced | TypeScript `paths` config or package `exports` prevents pi packages from importing SF packages |
|
|
| 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 |
|
|
| Install experience is unchanged | `npm install -g sf-run@latest` produces an identical binary from the user's perspective |
|
|
| Existing extensions continue to work | All built-in SF extensions load and execute without modification |
|
|
| Build time does not regress significantly | Full build completes within 120% of current baseline |
|
|
|
|
## Non-Goals
|
|
|
|
- **Not** moving pi packages from vendored source to npm dependencies (that is a potential Phase 2)
|
|
- **Not** creating an abstraction layer that hides pi types from SF code — SF packages may freely use pi's `AgentMessage`, `Model`, `TUI`, etc.
|
|
- **Not** upstreaming SF's modifications to pi-mono (desirable long-term but out of scope)
|
|
- **Not** changing the published npm package name, install command, or any user-facing CLI behavior
|
|
- **Not** removing or replacing the extension system — it stays in `pi-coding-agent` and remains typed against pi's types
|
|
|
|
## Stakeholders
|
|
|
|
- **Maintainers applying pi updates** — primary beneficiary; this work directly reduces their update burden
|
|
- **Extension authors** — must not be broken; the extension API surface stays in `@sf/pi-coding-agent`
|
|
- **End users** — not impacted; the refactor is entirely internal
|
|
|
|
## Requirements
|
|
|
|
### R1 — New package: `@sf/agent-core`
|
|
|
|
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.
|
|
|
|
Must contain:
|
|
- `agent-session.ts` and all `AgentSession` types
|
|
- `compaction/` (orchestrator, branch summarization, utilities)
|
|
- `system-prompt.ts`
|
|
- `bash-executor.ts`
|
|
- `fallback-resolver.ts`
|
|
- `lifecycle-hooks.ts`
|
|
- `image-overflow-recovery.ts`
|
|
- `contextual-tips.ts`
|
|
- `keybindings.ts`
|
|
- `sdk.ts` (the `createAgentSession()` factory — the primary public API of this package)
|
|
- `artifact-manager.ts`, `blob-store.ts`
|
|
- `export-html/`
|
|
|
|
### R2 — New package: `@sf/agent-modes`
|
|
|
|
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.
|
|
|
|
Must contain:
|
|
- `modes/interactive/` (full TUI interactive mode and all components)
|
|
- `modes/rpc/` (RPC server, RPC client, JSON protocol)
|
|
- `modes/print/` (print and machine-surface output)
|
|
- `cli/` (arg parsing, config selector, session picker, model lister, file processor)
|
|
- `main.ts` entry point logic
|
|
|
|
### R3 — `pi-coding-agent` contains only upstream code and the extension system
|
|
|
|
After the migration, the vendored `pi-coding-agent` source must not contain files that:
|
|
- Import from `@sf/agent-core` or `@sf/agent-modes`
|
|
- Contain SF business logic (compaction, session management, run modes, CLI)
|
|
|
|
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.
|
|
|
|
### R4 — Public API surfaces are explicit
|
|
|
|
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`
|
|
- Any other internal-path imports identified during migration must be fixed
|
|
|
|
### R5 — Build order is updated
|
|
|
|
The workspace build script must be updated to build packages in dependency order:
|
|
1. `@sf/pi-agent-core`, `@sf/pi-ai`, `@sf/pi-tui` (parallel, no dependencies between them)
|
|
2. `@sf/pi-coding-agent`
|
|
3. `@sf/agent-core`
|
|
4. `@sf/agent-modes`
|
|
5. `sf-run` (top-level binary)
|
|
|
|
### R6 — No change to the extension loader's public interface
|
|
|
|
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.
|
|
|
|
## Open Questions
|
|
|
|
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?
|
|
2. Does `buildSessionContext()` on `SessionManager` need a public re-export from `@sf/agent-core`?
|
|
3. Should `@sf/agent-modes` re-export `createAgentSession()` as a convenience, or should consumers always import it from `@sf/agent-core` directly?
|