# AGENTS.md Development rules for Prime Agent (Rust) on PrimeIntellect-ai/prime-agent, branch `main`. Every contributor (human or agent) must read this before working on this repo. ## Repository - **Never set git identity for commits to this repo's history.** Agents must not set `user.name`/`user.email` (config or `-c`) or `GIT_AUTHOR_NAME`/`GIT_AUTHOR_EMAIL`/ `GIT_COMMITTER_NAME`/`GIT_COMMITTER_EMAIL` for commits that land in this repository. Those commits carry the identity of the ambient git config. If such a commit fails for a missing identity, report it instead of adding config. Allowed: the `*_DATE` variables (the golden corpus pins them for reproducibility), throwaway temp repos created by tests, and the release automation's bot identity. - **Never add `Co-authored-by` trailers** — not in commit messages, not in squash suggestions, not as attribution for work merged from branches. A PR's commits carry one identity: the ambient git config's. Fabricated or hand-written trailers (including noreply forms) misattribute commits to unrelated GitHub accounts. - The repo is PrimeIntellect-ai/prime-agent; the Rust implementation lives on the `main` branch. - PRs go to the org repo with base `main`: `gh pr create --repo PrimeIntellect-ai/prime-agent --base main`. - CI runs on the org's billing: `.github/workflows/continuous.yml` on `main` pushes and `release.yml` on version tags. - Parity ground truth is unchanged: a read-only checkout of the TS implementation (this repo at tag `v0.9.8`, the last TS release before the Rust port merged to `main`). Clone it to `~/prime-agent`, the conventional location the parity scripts and code fall back to; tooling that honors an override reads `PA_TS_REFERENCE`. ## Style and structure - Workspace crates are prefixed `pa-`. The hard ownership rules: one owned area per crate, pa-types is the only shared crate, cycle-free dependency direction, minimal public APIs, no god-modules. The dependency direction is pinned in the Crates table below; each crate's README.md states its scope, non-goals, and public API surface. - Prefer private modules with an explicitly exported public crate API. Internals are `pub(crate)`. - Aim for files under 2,000 lines, tests included. The 2,000-2,100 range is a soft review signal, not a merge limit: when adding to an already-large file, consider splitting by responsibility rather than growing it further. Move related tests and docs with extracted code. - Name modules for what they own, not `utils` or `common`. Follow existing structure: in `crates/pa-core/src/kernel/manager/`, `mod.rs` connects `execution.rs`, `requests.rs`, and `teardown.rs`; put a new kernel-manager concern beside them. In `crates/pa-core/src/cron/store/`, `mod.rs` connects `heartbeat.rs`, `jobs.rs`, and `session_artifacts.rs`; keep storage work there rather than growing the scheduler. - Inline format args: always prefer `format!("{x}")` over positional. - Collapse if statements per clippy::collapsible_if. - Prefer method references over closures per clippy::redundant_closure_for_method_calls. - Make `match` statements exhaustive; avoid wildcard arms. - New traits need doc comments explaining their role and how implementations are expected to behave. - No opaque positional `bool`/`Option` parameters (`foo(false)` is unreadable). Prefer enums, named methods, or newtypes. If you must pass an opaque literal by position, use an exact `/*param_name*/` comment matching the callee signature. - Prefer native RPITIT trait methods with explicit `Send` bounds (`fn foo(&self) -> impl Future + Send;`) over `#[async_trait]` or `#[allow(async_fn_in_trait)]`. Implementations may use `async fn` when they satisfy the contract. - No single-use helper methods. Do not create a helper referenced only once. - Instrument async work at the definition (`#[tracing::instrument(...)]`), not with `.instrument(...)` at call sites. Check whether the callee is already instrumented first. ## Change hygiene - One logical change per PR. Fixes ride with the change that introduced them when possible; a PR that mixes a feature with unrelated lint fixes or refactors hides both from review. - If you change dependencies (`Cargo.toml`), regenerate/commit `Cargo.lock` in the same change. A new dependency is an architectural change: state in the PR why the existing crates cannot host the functionality, and `make deny` must stay clean for it. - If a change starts forcing edits across many crate internals, stop and fix the boundary instead. ## Tests - Prefer whole-object equality comparisons over field-by-field checks. - Do not add tests for statically defined values. - Do not add negative tests for logic that was removed. - Bug fixes need a regression test that fails without the fix. - Verifiers over self-assessment: tmux user-level tests, differential tests against the TS binary on PATH, golden corpora replayed against real captured data. No lane merges without its verifier passing, rerun by the reviewer where feasible. - Tests must wait for observable readiness, not fixed sleeps or retry-to-green loops. If a test must be disabled, give a reason at the test. Use isolated ports and temporary paths, and restore shared state. Run focused tests when changing their behavior. ## Lint discipline - Zero-warning posture: the merge gates run fmt + clippy + tests at `-D warnings`, so a `warn` lint is a merge blocker. Keep the enabled set deliberate and selective; enable a new lint family only with every current site fixed in the same change. - Explain lint exceptions next to the `#[allow(...)]` or workspace configuration; keep exceptions narrow. Advisory ignores in `deny.toml` need a reason and review date. ## Performance and the critical path The UI critical path is paint-latency-critical. First paint, and every frame the user waits on, must never block on network, disk sync, or background bookkeeping. The launch benchmarks measure this surface (cold/warm ready, first paint); a merge that regresses them undoes the product's core claim. Rules: - Nothing user-visible awaits the network. Any HTTP/socket call on a render path (telemetry, catalog fetch, update check) runs on a background worker; the paint path hands the work off and returns immediately. Delivery semantics (timeout, retry, ordering) live with the worker, not the caller. - Telemetry is fire-and-forget from user-facing code. `pa-telemetry`'s background worker owns delivery end to end; no startup, session create, tab open, or first-render path may hold a flush or await a sink. - Fire-and-forget boundaries need a red-first test: a hanging sink (one that never replies) must not delay paint. The test fails if any await on the critical path reaches the worker's delivery. - Tests that mock a sink or run offline cannot prove the above (the benchmark's mock hides the POST). The regression test with a hanging sink is the required verifier. Example (the #3288 regression this rule exists to prevent): the telemetry startup flush was awaited on the interactive launch path, so the analytics POST — a real HTTPS round-trip, ~157 ms — ran before the first frame. Every CI check passed because tests set DO_NOT_TRACK=1 and the benchmark mocked the provider offline; only real-network launches paid the cost. The fix moved the flush to the background worker and added `the_startup_flush_never_blocks_the_first_frame` (a hanging sink must not delay paint) as the permanent guard. Any change touching the launch path must keep that test red-first. ## Merge gates - `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace` must pass before every merge. Run `make check` — the local mirror of the same gates; CI runs on the org's billing (`.github/workflows/continuous.yml` on `main` pushes; `release.yml` on version tags). The PR-only codebase-health check reports growth past 2,000 lines as review guidance; it never blocks a merge. - **Parity-diff evidence is a merge gate** (the port's definition, not optional polish): every PR that touches a user-visible surface must include a "parity-diff evidence" section in its description showing the TS-binary comparison for what it changed: (1) rendered output — frame-diff vs the TS binary; (2) interactive behavior — the same input handled identically (keys, mouse, timing); (3) wire parity — byte-compare the TS daemon's traffic for protocol changes; (4) user-visible invariants — every user action produces the same visible reaction as TS (`/compact` shows started+completed; `/model` shows the selector; a refinement shows its decoration). A feature that "works" but was never diffed against the TS binary does not pass review. If TS shows it, Rust shows it identically; if Rust shows something TS does not, that is also a parity bug. - PRs must state ownership compliance (crate README scope/non-goals/public API, dependency direction) and classify the change: internals, new `pub` surface, or a new dependency/cross-crate re-export. The latter two are architectural changes — an innocent-looking `pub use` is a very simple way to break encapsulation. - The compiled fallback model catalog `crates/pa-ai/src/models_generated.rs` is hand-maintained Rust mirroring the TS `packages/ai/src/models.generated.ts` object literal — edit it by hand, exactly like its TS source (there is no generator). The parity fixture `crates/pa-models/tests/fixtures/catalog.v1.json` refreshes with `scripts/generate-catalog-fixture.py`. Bulk data files are exempt from the size guidance. ## Adoption telemetry Every user-visible feature ships its adoption telemetry event in the same PR as the feature: the event name + properties join the versioned schema (`pa-telemetry`), and a seam emits it from day one. Telemetry properties never carry prompt, session, or file content (primitives only; see `pa-telemetry`); delivery is fire-and-forget from user-facing code (see Performance and the critical path). ## Branding The product is Prime Agent - we are not a pi fork. Scrub "pi"/"pi-mono"/"pi-ai"/ "Prime Intellect"-style naming from all user-visible surfaces (docs, READMEs, CLI help text, error messages, splash/onboarding strings, keybinding hints, TUI labels, and code comments that quote user-facing strings); brand everything Prime Agent. Audit with a repo-wide grep and classify every hit (user-visible vs wire-internal vs comment) before scrubbing, and list the preserved wire identifiers in the PR body so the reviewer can verify none were wrongly scrubbed. EXPLICIT EXCEPTION: wire-protocol identifiers that must stay byte-compatible with the TS product (e.g. the PI_PACKAGE_DIR env var, settings keys, provider IDs like prime-inference, harness _meta namespaces like ai.primeintellect.prime-agent, lockfile names) stay until/unless the TS side renames them — PARITY BEATS BRANDING ON THE WIRE. ## Surface contract (must not change) - Tools exposed to the model: `bash`, `edit`, `python_repl` (internal helpers: `rename`, `stdout`). - RLM kernel API in the persistent Python REPL: `rlm.spawn/find_models/collect/list_subagents/delete_subagent/create_session/progress_note`, `rlm.harness` CRUD, `agent_message.send`, `agent_observe`, `compact`, `goal`, `refine`, `attach_image`, skills (markdown + Python) per the skill contract in the base system prompt. - System prompt structure: layered — cache-stable static layer files (core harness description with the full API surface, mandatory usage rules, opinionated guidelines, per-model additions from the model-prompts TOML rule map) followed by one dynamic tail (packages, project context, skills inventory, MCP servers, environment, session role); the harness digest stays a separate `[harness-digest]` user message. `prime-agent prompt` dumps the assembled prompt with its layer breakdown. - CLI shape: `prime-agent` with the same commands/flags as the TS product; headless modes (RPC/daemon/session-worker) with identical behavior. ## Crates One owned area per crate; `pa-types` is the only shared vocabulary crate. Dependencies flow from the composition root toward lower crates, never back upward. The table summarizes each crate's current direct workspace dependencies: | Crate | Purpose | Direct workspace dependencies | | --- | --- | --- | | `pa-types` | Shared wire and domain types | none | | `pa-telemetry` | Events and sinks | none | | `pa-agent` | Provider-independent agent loop | none | | `pa-ai` | Providers, model registry, streaming | `pa-types` | | `pa-models` | Live model catalog and transport | `pa-ai`, `pa-types` | | `pa-core` | Session engine, tools, skills, kernel, settings | `pa-types`, `pa-ai`, `pa-models`, `pa-agent`, `pa-telemetry` | | `pa-daemon` | Supervisor, workers, wire protocol | `pa-types`, `pa-core`, `pa-agent`, `pa-ai`, `pa-telemetry`, `pa-models` | | `pa-tui` | Terminal UI, daemon-wire client | `pa-types` | | `pa-cli` | `prime-agent` binary, composition root | `pa-types`, `pa-core`, `pa-ai`, `pa-agent`, `pa-daemon`, `pa-tui`, `pa-telemetry` | | `pa-perf` | Performance corpora + pty bench harness (dev/CI tooling, never shipped) | `pa-types`, `pa-core`, `pa-daemon` | Layering rules: - Dependencies only point from higher crates to lower crates shown above. No cycles or reverse edges. The leaf crates are peers; add only the dependencies needed for their work. Shared wire/domain vocabulary belongs in `pa-types`, not in a higher crate or a duplicate. - `pa-tui` renders from wire types and events; it does not link the session engine. - `pa-cli` is a composition root: it wires crates together and contains no business logic. ## Reliability model - The daemon is a supervisor: it spawns one worker process per active session instead of hosting sessions in-process. Workers are supervised, restarted with backoff, and sessions persist on disk (append-only JSONL, same layout as `~/.prime/agent/sessions`) so reattach works even if the supervisor restarts. - No stubs, no `todo!()`, no swallowed errors (`anyhow` bubbling to UI is fine). ## References - Cursor Bugbot's PR review rules (`.cursor/BUGBOT.md`) mirror the standards in this file - update both whenever either changes.