Build pi-rs into a production-quality Rust implementation of the current Pi coding-agent
product. Match observable Pi behavior where it is intentional, while keeping the Rust design
plugin-first, generation-based, and strongly typed. The target is a usable CLI/TUI product, not
only a library port.
Treat legacy/pi as the local behavior oracle. Verify the relevant current TypeScript source before
claiming Pi compatibility; do not implement from memory or from an older Pi shape.
- A generation-based runtime registers unified Agent/Session plugins and independent provider/catalog plugins and rebuilds factory-backed plugins on reload.
- Version-locked native Plugin/ProviderPlugin implementations are exported through
pi-plugin, loaded from manifests or explicit paths, and adapted into those same generation factories. - Skills,
models.json, project resources, OpenAI-compatible routing, production filesystem/shell tools, and deterministic scripted-provider test support are integrated. - Pi-compatible project trust persists nearest-ancestor decisions, prompts interactively, and
gates project
.piresources and project skill roots before generation construction. - Pi v4 sessions support resume, queues, branching, compaction, context repair, recovery reduction,
and lazy first-response persistence through
AgentSessionRuntime. - The Ratatui CLI provides interactive, print, and NDJSON modes with fullscreen operation, product events, slash-command selectors, Markdown rendering, history, scrolling, selection, and IME input.
- Architecture changes: read
docs/architecture.mdbefore changing cross-crate boundaries, plugin lifecycles, runtime generations, prompt assembly, event ordering, model selection, or session persistence. - Pi conformance: inspect the corresponding implementation under
legacy/pibefore changing a user-visible behavior, wire format, hook contract, command, session rule, or resource precedence. - CLI/TUI changes: read
apps/pi-cli/README.mdand the focused tests inapps/pi-cli/src/tui.rsbefore changing terminal modes, input behavior, commands, transcript rendering, scrolling, selection, or status presentation. - Native plugins: read
crates/pi-plugin/docs/native.mdbefore changing export macros, descriptors, manifests, compatibility checks, discovery, or library lifetime. - Historical status: treat
docs/incomplete-handoff.mdas a historical snapshot. Derive current status from code and tests instead of copying its milestone or test counts.
- Dependencies point inward.
pi-coreowns foundational values;pi-pluginowns executable contracts, not product policy, filesystem discovery, vendor routing, session storage, or terminal rendering. - Terminal ownership stays in
apps/pi-cli. Reusable crates may expose semantic data and product events, but terminal setup, alternate-screen control, Ratatui widgets, input decoding, and visual styling remain in the app layer. - Put policy in the module that owns the concept. Keep app wiring thin; avoid command-name, provider-name, tool-name, or skill-name switches in generic runtimes.
- Use one
pi-plugin::Plugininstance for Agent/tool/command and Session lifecycle callbacks. Keep provider/catalog plugins (ProviderPlugin) independent. The samePluginDriverdirectly dispatches both callback families, with Session metadata supplied as context parameters. - Registration happens while building a generation. Registries are immutable after publication, duplicate identities fail construction, and hooks run in registration order.
- Every product plugin must be reloadable through a factory-backed next generation. Prepare and validate the complete generation before swapping it; a failed reload keeps the previous generation intact. Never mutate live registries in place.
- Native dynamic-library loading stays behind the existing fallible generation factory seams. A dynamic library exports exactly one unified Plugin or ProviderPlugin; package metadata and pinned-library lifetime remain loader concerns rather than a fourth lifecycle.
- Provider plugins own provider implementations, routing overlays, and their model catalog entries.
Keep the frozen
ModelRuntimeas the generation-local query surface; do not reintroduce a separate model-plugin lifecycle for catalog registration alone. SkillsPluginowns skill roots, discovery, parsing, collisions, catalog prompt contribution, and/skill:<name>commands. Generic resources and prompt assembly remain skill-agnostic.
models.jsonowns registered model/provider catalog and request routing. Keep credentials and environment expansion request-time only.- Initial model priority is explicit request, restorable session model, catalog default, then
runtime fallback. Keep this policy in
AgentSessionOptions/InitialModelRequest, outside the catalog loader. - Generation-time prompt changes flow through plugin hooks. Prompt contributions are run-local and must not mutate the reusable base prompt.
- Preserve the Pi v4 JSONL schema, shared mutation sequence, tree/lane semantics, and provider projection rules. Unknown agent-message wire extensions must survive replay.
- A new
AgentSessionexists in memory immediately, but its JSONL file materializes only after the first assistantmessage_end. Quit-before-response and shell-only use leave no resume entry. Reloading an unsaved session reuses its in-memory log; an unsaved session cannot be forked. - Persist every assistant tool call with its matching tool result before the next provider request. Recovery and context projection must never emit a dangling tool call.
- Session plugins receive lifecycle events; executable plugin code and resources are rebuilt, not deserialized from session data.
- Drive every frontend mode from the same
AgentSessionRuntimeand semantic product-event stream. Provider errors, tool failures, queues, reloads, and resumed history must remain visible without writing application output outside the TUI renderer. - Fullscreen alternate-screen mode is the default. Preserve main-screen opt-out, Unicode/IME input,
copy selection, transcript scrolling, registered
/model,/resume, and/skill:selectors, and shell shorthand. - Keep user, assistant, tool, error, and working entries semantically distinct while using shared spacing and alignment rules. Prefer tests of layout relationships over terminal-specific pixel assumptions.
- Project native plugins use the existing project trust service before loading; future project
settings and packages must consume that same decision. Match Pi by leaving
AGENTS.md/CLAUDE.mdcontext discovery independent of trust; trust gates project.piresources and project skills, not tool execution. - Filesystem tools follow Pi path semantics: resolve relative paths from cwd, expand
~, and allow absolute or parent-relative paths outside cwd. Operating-system permissions are the boundary; do not introduce areadable_rootssandbox as part of project trust. - The reducer reconstructs interrupted session state, but full operation replay/recovery execution orchestration is still separate from the live runtime.
- Native plugin packaging is local and version-locked; signed content-addressed installation and a
remote registry are not implemented. Inspect Farm's loader and lifetime model under
legacy/farm/crates/node/src/plugin_adapters/rust_plugin_adapterand its build/distribution conventions underlegacy/farm/packages/plugin-toolsbefore changing the ABI, manifest/version checks, or unload seam.
-
Locate the owning Rust module and the matching Pi source. State whether the change is conformance or a deliberate Rust/product divergence.
-
Write or identify a focused regression at the owning seam. For cross-layer behavior, add the smallest integration test that proves the handoff between layers.
-
Implement in the deepest owning module, then adapt outward. Preserve unrelated work in the worktree and keep protocol/storage compatibility explicit.
-
Update
docs/architecture.mdwhen an architectural invariant, lifecycle, persistence rule, or deliberate Pi divergence changes. -
Finish only when all affected tests pass and the full quality gates are green:
cargo fmt --all -- --check cargo test --workspace cargo clippy --workspace --all-targets -- -D warnings git diff --check
For real-provider checks, keep credentials out of source, logs, fixtures, and distribution artifacts. Deterministic scripted-provider tests remain the default validation path.