Technical Documentation: PrimeIntellect-ai/prime-agent
βΉοΈ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference
Overview (`README.md`)
Prime Agent: A Self-Improving RLM Harness
Prime Agent is an open-source coding and research agent for general and long-running work. It is designed around two core abstractions:
- The Recursive Language Model (RLM) treats context as variables (prompt-as-a-variable) and tools like recursive subagents as function calls (programmatic tool /sub-agent calling) inside a persistent REPL.
- The Continual Harness stores supplemental prompts, memories, skill descriptions, and reusable subagent specifications as durable state that Prime Agent can refine through small, evidence-backed updates, local to the session by default.
Install
Install the latest stable version with the one-command installer:
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | shOn Windows, install from PowerShell:
irm https://app.primeintellect.ai/prime-agent/install.ps1 | iexThe Windows installer downloads the latest stable Windows x64 build and verifies its checksum before installation.
Why Prime Agent
Prime Agent combines a persistent Python control environment with durable harness state, so useful working context and reusable operating patterns can outlive a single chat window.
- Everything is programmatic: a persistent Python REPL is the built-in model tool; file operations, shell commands, tool use, subagents, and context management happen through code.
- Subagents are built in:
rlm.spawn(...)spawns real child agents for parallel or background work and returns their results programmatically. - The harness can improve:
/refinereviews the current trajectory and can apply small, evidence-backed updates to supplemental harness state. It never rewrites the immutable base system prompt, and recorded snapshots support rollback. - Skills are executable: skills are importable Python packages, and the built-in skill creator can turn recurring workflows into project or personal skills.
- Sessions run in the background: daemon-backed agents keep running when the terminal disconnects and can be reattached later.
- Agents communicate directly: running agents can exchange messages and orchestrate one another without routing everything through the user.
- Long tasks keep moving: automatic compaction, persistent goals, heartbeats, schedules, autonomous mode, and retained subagents preserve progress across turns and terminal sessions.
Slack
Connect Slack from /mcp or run /mcp login slack inside Prime Agent to search
your workspace and use Slack tools. See the Slack connector guide
for OAuth setup and troubleshooting.
Clipboard over SSH and tmux
Prime Agent uses the local clipboard tool (such as pbcopy) when available. In tmux it asks the attached tmux client to forward a clipboard buffer if that client advertises the Ms capability. This works with tmux's default set-clipboard external, unlike application-origin OSC 52. Without tmux, remote copies request delivery using OSC 52. Terminal requests cannot confirm that your local clipboard changed, so Prime Agent reports them as unconfirmed.
Nested tmux servers and terminals may still block delivery. An outer tmux with set-clipboard external can reject the inner server's OSC 52. In that case, run tmux set -s set-clipboard on on the outer server, or use native terminal selection (Shift-drag in most terminals; Option-drag in iTerm2) followed by Cmd-C. macOS Terminal.app does not support OSC 52; iTerm2 requires its βApplications in terminal may access clipboardβ setting.
Custom certificate authorities
HTTPS requests trust your operating system's trust store together with the bundled Mozilla roots. For servers behind a custom CA, point NODE_EXTRA_CA_CERTS at a PEM bundle of your CA certificates β the same variable the Node/TypeScript build honors:
export NODE_EXTRA_CA_CERTS=/path/to/your-ca.pemEach transport reads the variable once when its first client is initialized. The background daemon keeps the environment it started with, so run prime-agent shutdown after changing it.
Getting Started
Start Prime Agent from the repository or directory you want it to work in:
cd /path/to/project
prime-agentOn first launch, run /login to choose a subscription or API-key provider. Prime Agent works in the current directory and can run commands and modify files there. Use a disposable clone, clean worktree, or another checkpoint you can inspect and restore.
[!WARNING]
Prime Agent executes model-generated Python and project commands with your user permissions. Its worker and kernel processes improve lifecycle isolation and recovery; they are not a security sandbox. Review changes and use trusted repositories, instructions, and skills only. Run untrusted code or instructions in an external sandbox or restricted environment.
Useful commands:
prime-agent agents # Browse running, idle, and saved sessions
prime-agent attach <agent> # Reattach to a running session
prime-agent --resume [path|id] # Browse sessions or resume one directly
prime-agent status # Inspect background service state
prime-agent doctor [--fix] # Inspect or repair background services
prime-agent update [--force] # Update Prime Agent
prime-agent shutdown [--force] # Stop every agent, worker, and background serviceBuilt for Long-Running Work
Prime Agent is built for long-running work, especially for evaluations in research. These features are available in the TUI, and when run autonomously.
- Continual Harness:
/refinecan persist focused, reviewable lessons as supplemental prompts, memories, reusable skill descriptions, or subagent specifications, with recorded refinement history. It does not replace packaging and reviewing new executable skills. - Direct agent-to-agent communication: running agents and retained subagents can discover one another, exchange messages, and steer active work.
- Daemon-backed continuity: active sessions, Python REPL state, schedules, and subagents keep running when the terminal detaches and can be reattached later.
- Heartbeats and schedules:
/heartbeat,rlm_heartbeat, andprime-agent schedulecan re-enter a session periodically or at a specific time. - Persistent goals:
/goalkeeps an objective and its progress active across turns until it is completed, paused, or cleared. - Bounded autonomous mode:
/autonomouscontinues within configured turn, token, and time budgets and can run user-defined quality gates. A passed gate checks only what that gate verifies; reaching a limit does not imply task success.
Contributing
Start with a GitHub Discussion for general questions, bug reports, and feature requests. Maintainers promote accepted work into Issues, and pull requests are reviewed from maintainers and vouched contributors.
License
Prime Agent is fully open source and released under the MIT License.
Citation
If you use this codebase in your research, please cite Prime Agent:
@article{karten2026prime,
title={Prime Agent: A Self-Improving RLM Harness},
author={Karten, Seth and Zhang, Alex L. and Thomas, Kevin and MΓΌller, Sebastian and Bakouch, Elie and Auras, Daniel and Senghaas, Mika and Obeid, Fares and Dunas, Konstantin and Hagemann, Johannes and Jaghouar, Sami},
journal={arXiv preprint arXiv:2608.23552},
year={2026}
}Available at https://arxiv.org/abs/2608.23552.
Automatic compaction settings
/settings exposes the global automatic compaction percentage in five-point steps,
with a 95% default. A saved off-grid percentage stays selectable. The effective
trigger also reserves room for retained history and summaries; the model's input
and requested-output ceiling always takes priority. Manual compaction budgets are
unchanged. The underlying compaction.thresholdPercent setting accepts 1β100,
including fractional values; project settings can override the global setting.
For per-model early compaction, use the existing provider-scopedmodelOverrides..contextWindow option in models.json, for example:
{
"providers": {
"prime-inference": {
"modelOverrides": {
"your-model-id": { "contextWindow": 524288 }
}
}
}
}Use the actual model ID for your provider. This sets that model's effective context
window; it does not change every model's global compaction percentage.
Overview (`crates/pa-agent/README.md`)
pa-agent
The agent loop.
Scope
Turn loop: drives a provider stream, dispatches tool calls, enforces max-turn and sequencing semantics, handles partial streams, retries on stream failure, propagates aborts.
Non-goals
No tool implementations (pa-core), no providers (pa-ai), no session persistence, no UI. The loop is tool-agnostic: it receives a ToolDispatcher boundary.
Public API
run_loop (session inputs -> stream of loop events), ToolDispatcher trait. Loop internals pub(crate).
Depends on
No workspace dependencies.
Overview (`crates/pa-ai/README.md`)
pa-ai
Provider APIs and model registry.
Scope
Provider trait + per-provider streaming clients (anthropic, openai-completions/responses, google, bedrock, mistral, azure, prime-inference), model registry/resolution, usage accounting, stream-failure retry, provider-error shapes (per-SDK user-facing texts, diagnostic error names, connection-error profiles), bedrock transport selection (h2c prior-knowledge HTTP/2 cleartext, h2-preferred TLS ALPN, the http1 AWS_BEDROCK_FORCE_HTTP1/proxy mode), overflow handling, JSON repair parsing, faux provider for tests.
Non-goals
No agent loop, no tool execution, no session state, no UI. Receives/returns pa-types messages.
Public API
Provider trait, ProviderRegistry, model lookup/resolution, faux provider. utils::tls::http_client_builder lets caller-owned HTTP transports apply the shared extra-CA trust configuration. Per-provider internals are pub(crate).
Depends on
pa-types (one-way).
Overview (`crates/pa-cli/README.md`)
pa-cli
The prime-agent binary.
Scope
Argument parsing matching the TS CLI exactly, mode selection (interactive/headless/json/daemon subcommands), binary wiring of crates into processes, exit codes and user-facing errors. The daemon-forensics CLI (incident.rs, TS src/cli/incident.ts): the prime-agent incident timeline command β log-source discovery (agent.jsonl plus its .old rotation, falling back to the newest per-daemon log), --since/--until window and --session filter parsing, and the report rendering; the shared classifier lives in pa-types (pa_types::incident) and the agents-view notice in pa-tui. Client-side MCP auth flows (mcp_login.rs): the /mcp login//mcp logout hook the TUI calls β the TS interactive client's placement β resolving a server (settings mcpServers + the builtin catalog), running the pa-core OAuth login on the suspended terminal, and persisting through the shared auth store. The same placement serves the TUI's client-command hooks: provider auth (provider_login.rs β the /login//logout catalog, the API-key store, the MCP device flow, the Prime Inference terminal login (prime_inference_login.rs β the API-key flow: prime-cli credential reuse, the paste prompt with the whoami access check, the numbered team selector); the subscription OAuth and the Prime browser challenge stay unported and report it), trace-sharing state (client_traces.rs β the settings flag writes and the credential resolution /traces shows; the upload subsystem stays unported), and the /update runner (client_update.rs β the out-of-band installer funnel with the output captured, the same body prime-agent update runs; installer_update.rs is that CLI command's own driver, --check included), plus the TS-parity staged update flow (update_flow/, self_update.rs β the package update self target's body, kept for the TS-parity package corpus).
Non-goals
No business logic; it composes pa-daemon, pa-core, pa-tui, pa-ai via their public APIs only.
Public API
The prime-agent executable surface (flags, subcommands, exit codes). Internal lib types pub(crate), plus the daemon wiring (interactive_mode::{ensure_daemon_running, ensure_daemon_running_with, resolve_socket_path}) exported for the integration verifier.
Depends on
pa-types, pa-ai, pa-agent, pa-core, pa-daemon, pa-tui (one-way, composition root).
Print runtime
The headless print/json modes (print_runtime.rs) drive the in-process
session engine directly: prompt admission, the turn-boundary compaction
checks (print_boundary.rs β the overflow compact-and-retry arm, the
model-requested compaction/refinement consumption, and the threshold arm,
the TS _checkCompaction flow), the json event stream, and the headless
terminal selection (stdout/stderr/exit code) mirroring modes/print-mode.ts.
Autonomous continuation (print_autonomous.rs, the #254 follow-up): the
composed in-run continuation hook (the pa-agent loop'sgetContinuationMessages seam) runs the natural continuations inside
the one agent run (the TS shape: turn_end -> turn_start with the
continuation user row between them, no run boundary); the goal arm runs
first (exclusive priority), the boundary gates (queued input, a
requested compaction, the threshold arm) are shared, and a threshold
compaction holds its minted continuation for the boundary's queuedfollowUp admission (TurnBoundary::admit_continuation). The stop
surfaces only through the headless exit contract (TS: no row, no frame).
Goal continuation (print_goal.rs, the #252 residue): the --goal seed rides
the first turn; the in-loop continuation hook runs an active goal's natural
continuations inside the one agent run; the budget-limit steer and the
threshold-held continuation drain as this invocation's follow-up runs
with the TS session-action phase frames.
Inline subagents (print_rlm.rs): the daemonless print session hostsrlm.spawn children in-process over a tree of session nodes β theRlmSubagentHost impl (depth gate, name uniqueness, model resolution,
the child session/engine build, the kickoff row), theAgentMessageController (family roster, one steer-lane delivery path),
the quiescence drive that holds the run open until every admitted
descendant settles and its replies and notices drain (the #2270 fix),
the goal/autonomous deferral behind unsettled work, and the exit
disposal (kernels snapshotted, drivers joined; no child outlives the
process).
Session slash commands (print_session_command.rs, the #253 residue): print
prompts that are /compact, /refine, /goal, or /autonomous execute
through the pa-core session-command executor (the daemon worker's seam β no
parallel implementation), streaming the TS shapes: thesession_action_update phase frames around the durable echo row, the
per-command events (compaction_start/compaction_end for /compact, the
refinement rows plus refine_complete/refine_failed for /refine, the
unconditional goal_update publish for /goal), the result rows, and the
settled queue frame. A /goal start (or resume) drains its scheduled
continuation inside the same prompt wait; a failed command rejects the
prompt wait (the raw error to stderr, exit 1, no later prompts). Client
commands (/model, /tree, ...) stay model prompts β TS_normalizeSubmission classifies only the four session commands before
admission, so they never cross the turn boundary.
The json stream emits the TS session-event surface byte-for-shape: the
session header row (version 3), the message_update streaming deltas with
the slim assistantMessageEvent (the daemon wire drops the nestedpartial), tool_execution_update, the harness digest's message pair at
the first-turn boundary, the compaction compaction_start/compaction_end
pairs with their durable outcome rows, the goal_update/session_action_update
frames of the goal loop, and the refinement rows withrefine_complete/refine_failed. Verifier: the print_runtime_e2e rows.
Daemon client
The daemon-backed public commands (list, stop, rename, send, schedule) talk to the
pa-daemon supervisor over its JSONL Unix socket through the crate-private client module
(daemon_client.rs). The client never spawns a daemon - the TS CLI only auto-starts one for
the internal daemon start/open commands, which are not reachable from the public surface.
Packaged layout / kernel packaging
The release artifact is exe-adjacent (TS install.sh native layout): the binary
plus package.json (the version manifest --version reads at runtime, with
the compiled-in version as the fallback), prime-agent-runtime/ (the kernel
sidecar), skills/, and docs/. scripts/package_release.py (make package)
assembles, validates, version-pins, hashes, and tars it; the hidden--prime-agent-bootstrap flag is the installer handoff that pre-bootstraps the
kernel venv. Verifier: tests/packaged_layout_e2e.rs.
Daemon discovery
The discovery commands (status, doctor [--fix], shutdown [--force], TScli/daemon-ps.ts) live in the crate-private daemon_discovery module: an OS census of
listening unix sockets owned by product processes, a socket-dir sweep, probing/classifying
each discovered daemon, and the reap/shutdown planners and executors. Containment is part of
the contract: every scan, probe, and stop is scoped to an explicit DaemonStateRoot
(the env-resolved current root for the CLI), with a hard never-touch exclusion list for this
sandbox's ambient mission daemons. All e2e daemons live in
test-created fixture directories only.
Update flow (staged activation)
prime-agent update (the update_flow module): the invoking CLI plans, downloads, and
stages the candidate (Acquire..Staged), then spawns the detached
coordinator - the new binary running from its release dir - which adopts the
status file and owns the FSM to a terminal state (Preparing..Complete,Rollback first-class). intent.json is the coordinator lock (Join on a
live holder, dead-holder steal), the status file keeps the TS schema plusupdateId/state/epoch, and the activation owns the bin-symlink swap
(bin/previous rollback pointer, .activation-state recovery record).
Mechanism support (version/channel policy, install-root layout, manifest
fetch, archive staging) lives in pa-core's update module; the daemon-side
prepare/commit/stop drivers and the boot restore pass are pa-daemon
(slices 2-3 and 5): the coordinator's Restoring phase polls the
successor's update_restore_status RPC for the real per-session counts.
The legacy restart adapter accepts the fresh status path supplied by shipped
TypeScript updaters after they activate a release. It reports the originalrequestId/phase status schema, translates their session checkpoint to a
Rust roster, and restarts the daemon without repeating activation. Recovery
rosters remain on disk when restoration fails. Socket-scoped checkpoints resume
after an interrupted handoff when no daemon is running, and reject a different
installed version. The adapter holds the shipped TypeScript shutdown-admission
lease in both historic supervisor registries until Rust owns the replacement
socket, preventing old clients from launching a competing daemon. Admission
records are refreshed and removed only while their ownership token matches.
Overview (`crates/pa-core/README.md`)
pa-core
The session engine.
Scope
MCP host side (mcp/): auth gating for built-in integrations (the
disabled-skill overrides and /mcp list status), the mcpServers settings
seam, and the mcp.* host-request handlers the kernel's generic MCP
client reaches through (mcp.config, mcp.refresh, optionalmcp.begin_login). The OAuth flow lives behind it (oauth* submodules:
RFC 9728 discovery, dynamic registration, PKCE, the local callback
server, token refresh; McpOAuthConfig::callback_port selects an exact registered
desktop callback port, while dynamic clients retain the candidate range.
Slack's endpoint selects its documented public desktop client; login.rs: login execution that persists the
endpoint-bound credential into the shared auth store and thewire_begin_login/McpOAuth refresh integration hosts wire). The MCP
protocol itself runs kernel-side (Python
stdio/HTTP); the engine never spawns MCP servers. Tools (bash, edit, python_repl + internal rename/stdout), file mutation queue, truncation and rendering rules, RLM kernel lifecycle (IPython spawn/execute/revive), skills loading, system prompt assembly, compaction, harness refinement, settings/config, package manager (npm/git/local source install/remove/list/update against settings), session manager (persist/resume). Autonomous mode
(autonomous): runtime state with limit normalization, continuation and
gate-failure texts, shell quality gates with retry windows and git
worktree snapshotting, and the AutonomousDriver policy trait a turn
loop consults after every settled turn (the engine holds no autonomous
logic of its own). The RLM recursion host seam
(session_engine::rlm_host): the trait the kernel's rlm.spawn/rlm.create_session/rlm.list_subagents/rlm.collect/rlm.delete_subagent host requests call into, with the roster/collect/
selector-error vocabulary the daemon implements over the supervisor link.
Platform wall (platform): process control (signals/process groups, kernel exit waits), file locking, file permissions, and shell selection - every OS-specific behavior in the engine lives there behind cfg-gated implementations (the durable-write rename primitive is pa-telemetry's rename_onto, re-exported as platform::rename_onto). Scheduled jobs (cron, the AgentCronJobStore port of core/cron-jobs.ts): file-backed job state under session artifacts (scheduled-jobs.json partitions) with cross-process locking, plus the public read-only scan (cron::store::read_scheduled_jobs_artifact) the update flow's roster projection and boot re-arm read. Tools (bash, edit, python_repl + internal rename/stdout), file mutation queue, truncation and rendering rules, RLM kernel lifecycle (IPython spawn/execute/revive), skills loading, system prompt assembly, compaction, harness refinement, settings/config, package manager (npm/git/local source install/remove/list/update against settings, plus resolve(): precedence-ranked session resource resolution over configured packages, settings arrays, auto-discovery, and bundled skills), session manager (persist/resume). origin/main
Provider resilience policies in session_engine: the shared quick-retry
policy (TS provider-retry.ts: permanent-kind classification,
Retry-After-aware capped delays), the interactive auto-retry loop, and the
provider-failover driver (when a provider exhausts its quick retries and
another configured provider serves the same model, the failed turn
re-routes to the next provider in catalog order; the TSauto_retry_start/reason: "backup" event vocabulary surfaces the
progression). The drivers are pure decision logic: the host (pa-daemon)
owns the attempt, the wait, and the model switch.
Session HTML export (export_html): the standalone viewer file for/export and session export - the embedded product template
(assets/export-html, vendored marked/highlight.js attributed in its
NOTICE.md) plus the theme-to-CSS resolution over the bundled theme data
(pa-types) and custom themes under <agent-dir>/themes/, the tools
section mapping (tools_section: the session's tool registry to the
template's name/description/parameters list), and the custom-tool
pre-render pipeline (ToolHtmlRenderer/pre_render_custom_tools +ansi_to_html: the exporter walks the entries, the session layer's
renderer produces line-oriented output, ANSI converts to inline-styled
HTML at the export step), and the two entry points
(export_session_to_html for the daemon worker's export_html
command, export_from_file for the CLI). Terminal theme rendering stays
pa-tui's; tool renderers live with the session's tool surface;
the share-viewer flow (gist creation) is the caller's.
Cloud workspace snapshots (workspace_snapshot): the portable, verifiable
capture of a git worktree's working state - tracked modifications, staged
paths, deletions, and nonignored untracked files - staged into an
owner-private directory as content-addressed blobs plus a deterministic
manifest that hashes them, with credential-shaped, symlink-ancestor,
escaping-symlink, nested-repo, and submodule exclusions recorded in the
manifest, hard entry/size bounds, race-free openat(O_NOFOLLOW) leaf reads
(Unix), and offline verification of a staged snapshot before upload. Capture
refuses on non-Unix targets: owner-only 0700 staging and 0600 files cannot be
enforced there. The baseline is an explicit caller choice (BaselineMode):
HEAD's tree content staged through the same filters as the delta (each entry
verified against HEAD's recorded object id and mode, no git history ships),
or an explicit external reference the consumer must reach itself. The secret
skip is a filename-shaped denylist only; ordinary-named files containing
secrets stage verbatim. Snapshot directories are secret-bearing: every
consumer must treat them as credentials. Capture does synchronous file I/O
inside its async entry point; callers must offload the entire operation withtokio::task::spawn_blocking and Handle::block_on. Foundation plumbing
only: nothing wires it to a user-facing cloud toggle yet.
Non-goals
No provider HTTP (pa-ai), no loop policy (pa-agent), no daemon supervision (pa-daemon), no TUI (pa-tui). No update coordination (the pa-cli coordinator owns the FSM; the update-flow seam here is the daemon-free support layer: update::version (semver/channel policy), update::install (the managed install-root layout), update::release (the channel manifest fetch), update::download (sha256-verified archive download + staging)). The package manager installs sources and resolves resource paths only. No workspace snapshot upload, transport, or remote materialization (staging and verification live here; the cloud attach surface owns the transport when it ships).
Public API
SessionEngine (message in -> events out), export_html (the HTML exporter plus its renderer seam: tools_section, pre_render_custom_tools + ToolHtmlRenderer/RenderedToolHtml/RenderedToolResult, and ansi_to_html::ansi_lines_to_html/tool_render::trim_rendered_result_lines for renderer implementers), ToolRegistry, kernel manager, settings (SettingsManager), packages (packages::PackageManager + source types), platform (process control, file locking, permissions, shell selection - usable by higher crates, e.g. pa-cli's detached spawn), session_engine::rlm_host::RlmSubagentHost (implemented by pa-daemon for supervisor-backed children and by pa-cli's print mode for in-process children; the default is no children; the trait crate also owns the shared spawn/depth facts β DEFAULT_RLM_MAX_DEPTH, the depth-limit and name errors, the daemonless create_session refusal, rlm_child_session_dir, and the persisted_rlm_max_depth/resolve_rlm_max_depth precedence chain), session_engine::semantic_edges (the TS semantic-edges.ts WRITER: SemanticEdgeRecorder/SemanticEdgeIdentity, semantic_edge_ledger_path + last_committed_request_id, and the X-ACP-Model-Request-ID/Idempotency-Key constants β one durable per-session ledger of model-request events, byte-compatible with the TS lines; the fold and any publishing stay downstream), mcp::McpManager (the session's host-side MCP manager, exposed as SessionEngine.mcp_manager: auth gating for built-in integrations, the mcpServers setting seam, and the mcp.* host-request handlers - mcp.config/mcp.refresh/optional mcp.begin_login - the kernel's generic MCP client resolves through), mcp::McpManager::connection_roster + mcp::McpConnectionEntry (the /mcp view's roster: every resolved integration with its connected state, display kind, transport, and generic-listable flag), mcp::McpManager::service_catalog_views + service_catalog_diagnostics (the /mcp view's resolved service-catalog cards and their diagnostics, answered from local state - the TS buildServiceCatalogViews parity; the picker never round-trips the kernel), mcp::McpManager::api_key_credential_views + mcp::API_KEY_CREDENTIALS/McpCredentialView (the /mcp view's api-key credential rows - the stored keys the surface manages alongside the connections, e.g. the web-search key - configured from the shared auth store), models (the registry and resolver, plus models::order_for_picker: the picker-catalog order the composition root applies to its snapshot), session_engine::provider_adapter (switchable_stream_fn/ProviderTarget: the live provider-target slot a host swaps on set_model; the adapter bridges the loop's abort into pa-ai's StreamOptions::signal cancellation token, so a turn abort cancels the in-flight HTTP fetch at the transport β closed by ModelStream::close and the stream's drop), session_engine::ipython_state (the post-compaction ipython_state kernel-persistence notice: the CompactionKernelProbe view of the kernel provisioner the engine wiring binds via AgentSession::set_kernel_state_probe, and the notice row that rides CompactRun for the surfaces to broadcast). session_engine::session_commands (the /compact//refine//goal//autonomous executor: the durable echo/result rows with their persistence order - the echo ahead of the command so its own work sees it - the compaction/refinement/goal/autonomous arms, and the post-execution live-context rebuild that keeps the rows in the agent state; hosts own the wire surface), session_engine::auto_refine_trigger (the compact-trigger auto-refine machine on AgentSession: the arm/discard/increment accessors and consume_compact_auto_refine, the shared gate/review/stamp sequence every scheduling surface applies - TS _compactAutoRefinePending/_lastAutoRefineReviewAt/_assistantTurnsSinceAutoRefine plus _maybeAutoRefine's compact arm, the serialized checkpoint's compact step, and dispose's serialized drain, with the surface parameter carrying the one behavioral difference between a boundary and a disposal). session_engine::goal_boundary (the goal arms of the natural turn end on SessionEngine: the --goal construction seed with its next-turn context row - TS _pendingNextTurnMessages on AgentSession - the usage accounting, the budget-limit steer mint, the natural continuation mint with its slot rollback, the RLM deferral behind unsettled descendant work (mark/drop/take owed, the wake-up ownership probe, and the latest settled turn the post-settle deliveries decide from), and the terminal-error finish; the transports - the print driver's in-loop hook, the ACP settle loop, the daemon worker's queue - drive the one goal driver through these methods). SessionEngine::expand_skill_submission + AgentSession's prompt-path expansion (the /skill:<name> submission seam, TS _expandSkillCommand: skills::expand_skill_command builds the <skill ...> block over the shared pa_types::skill_blocks parse, the accepted-turn row and the admitted user message agree on the expanded text, and the skill_use_count session counter counts at the admission). workspace_snapshot (create_workspace_snapshot: bounded, secret/nested-repo/submodule/symlink-ancestor-excluding capture of a git worktree into an owner-private staging directory, with the caller-chosen BaselineMode (HEAD-tree content verified against HEAD's object ids, or explicit external) and a ConcurrentMutation error when the worktree changes under the capture; verify_workspace_snapshot: offline manifest/blob/baseline integrity check; the SnapshotManifest/WorkspaceSnapshot/SnapshotLimits/BaselineMode/SnapshotError types). All subsystem internals pub(crate); the engine is the only facade - subsystems must not import each other directly (the package manager consumes the public settings API only).
Depends on
pa-types, pa-ai, pa-agent (one-way).
The Unix platform process API exposes current_user_id() for the daemon
endpoint namespace to match the TypeScript process.getuid() contract on
macOS as well as Linux. This reads the OS identity without filesystem or
subprocess work.
Automatic trace delivery (agent_traces::ContinuousTraceUpload) owns consent-gated
persist scheduling and startup outbox recovery alongside the existing manual uploader.
Hosts reuse load_settings to capture consent before their existing settings parse,
attach persisted after successful disk writes, and own the returned installation
for the session lifetime. Replacements use rebind with the effective cwd; forks
use forked. The installation never drains on drop. A detached process-wide runtime
owns all transcript reads, scans, settings reloads, networking and retries; a maximum
of 256 registered controllers and four deliveries bounds resident work. Admission
returns None at capacity and logs rejection; hosts attach no hook in that case.
Retired descriptors free capacity only after background recovery acknowledges them.
Session replacement transfers its existing slot and cancels the predecessor; forks
require a distinct slot. Replacement retains a bounded retirement flag until the
background recovery snapshot acknowledges its exact controller identity. The synchronous
persist seam checks the cached consent snapshot and performs an exists/create
pending-marker write; consent metadata reads and reloads stay in the worker after
the host captures its initial settings generation; it does not fsync the marker (matching TS v0.9.8 process-crash
recovery rather than promising power-loss durability). A short nonblocking mutation lease prevents cursor/prune races; contention
records one stable fallback marker per session, which is retained conservatively
until a future sender has delivered it. Its cost must be measured
separately; startup/paint parity is not established by scheduler unit tests.
Active requests monitor consent changes every second on the worker and cancel on
revocation/errors; bytes already sent cannot be recalled. Recovery belongs to all
live hosts sharing the agent directory and cancels when the last host disappears.
Catch-up uses at most three delivery cycles per pending entry, honors Retry-After,
and releases upload capacity during retry waits; exhaustion retains durable intent.
Overview (`crates/pa-daemon/README.md`)
pa-daemon
Session supervision and wire serving.
Scope
ACP stdio transport (acp): the JSON-RPC serve surface for Agent
Client Protocol clients, served over a daemon session (initialize, session/new, session/prompt, session/close,
session/cancel, and the outgoing session/update notification with
namespaced _meta correlation), owned by this crate because
wire-protocol serving is its area; the worker turn loop owns compaction
arms, auto-refine, and goal continuation. The compact-trigger
auto-refine scheduling owns its surface here (compact_autorefine.rs
for the daemon worker: every compaction arm plus the manual compact
command arm the pa-core trigger, and the quiescent turn boundaries /
the command path consume the gated review - TS_scheduleAutoRefineAfterCompaction plus the background_maybeAutoRefine("compact")).
Supervisor process (one worker process per active session), restart/backoff
supervision, session registry/roster + worker self-registration (session
identity survives supervisor restarts: workers re-register with backoff and
the roster rebuilds), parent-death child cleanup (supervisor_parent_death.rs:
an unexpectedly exited worker's resident RLM children close with it - the
same plain-stop semantics as the worker-side #246 close, which SIGKILL
bypasses - via the durable create's parentActiveSessionId join), append-only session store ownership (including the
compaction-entry fold for compacted message reads), client attach/detach
(full-snapshot and chunked session_snapshot_begin/chunk/end
streaming), direct-attach transport (supervisor-issued single-use tickets
with a 10s TTL, worker-side peer grants burned on first use, session-plane
command gating on peer links), kernel agent_message/agent_observe bridge (agent_messaging.rs: the worker-side controllers over the supervisor link; the family view joins the supervisor roster with the session's own RLM children registry - the same registry rlm.list_subagents reads - so registry children are Child members addressable by name, RLM child id, and persisted session id, the spawning session is the Parent member for a subagent worker, and everything else stays a sibling; delivery tries the direct peer transport then the supervisor-routed send_message), worker-to-worker peer messaging (stage 3:worker-purpose single-use grants minted by get_worker_peer_transport
for a source worker's kernel agent_message.send, directworker_deliver_message on the target worker's socket with the supervisor
routed send_message as the never-retried fallback),
saved-session wake
for non-resident send_message targets (session_catalog.rs: catalog
resolve by session-id prefix or exact name, cwd-scoped first; a live miss
lazily restores a matching file from sessions-archive/ without
overwriting an existing live file; no new sessions are physically archived;
a resident worker hosting the file is reused, otherwise one spawns over it; RLM
children fall back to the spawn ledger's live child edges when the catalog
misses - children persist outside the sessions dir, under the parent's
session-artifacts tree - and wake over their own session file), wire protocol serve/negotiation (including thecompact/abort_compaction/set_auto_compaction commands and theircompaction_start/compaction_end events, and the liveset_model/set_thinking_level commands β model_switch.rs: registry
resolution, the durable model_change/thinking_level_change rows, the
settings defaults, and the engine's live agent/provider-target/level
switch), session export commands (session_export.rs: the worker-sideexport_html/export_jsonl handlers; the HTML data carries the engine's
tools section and custom-tool pre-render through theSessionEngine::export_tools/export_rendered_tools seams, built
lazily when an export precedes the first turn - the TS state exists from
create), the get_model_catalog command (the TS refreshModelCatalog:
registry refresh β live Prime Inference catalog fetch, disk cache at<agent-dir>/prime-inference-models-cache.json, bundled fallback β then
the full catalog minus unauthorized private models plus the configured
providers; the worker create path fires the same refresh at startup), the
agent-roster arms
(roster_subscribe/roster_unsubscribe with the full snapshot, liveroster_update pushes keyed by the TS roster agentId = session id, and
authenticated worker_roster_delta self-reports so live status reaches
subscribers without polling) plus the live-roster ledger seed (TSseedRosterLedger, supervisor_roster.rs: passivated ledger descendants
of resident workers seed into the live roster - roots are the resident
session files, descent is membership at any parent-walk step, and
present rows by agent id or session file are never overwritten - at
subscribe, spawn-admission, and worker-stop moments, so subscribers see
the full family, not just resident rows), cloud sandbox attach, session
leases (core/session-lease.ts port). Daemon-owned RLM spawn ledger (rlm_ledger.rs):
one append-only JSONL per sessions dir (spawn/rename/delete admissions with
the TS rlm-ledger.ts record grammar, bounds, stat-guarded replay, and
legacy-registry seeding) plus the per-child display files; family topology
is read from the ledger, never re-derived from session files. Passive-RLM
roster walk (rlm_roster.rs): list --all and the saved-session catalog
merge non-resident ledger children (walk roots = saved + resident session
files), so passivated children stay roster-visible (TSwalkPassiveRlmSubagents / withPassiveRlmDescendantInfos). The saved
session scan (session_store.rs) folds each file once into the durable
catalog row.
Supervisor-backed RLM child sessions
(rlm_children.rs, the daemon side of the pa-core RlmSubagentHost seam):rlm.spawn/rlm.create_session create real daemon sessions through the
worker's supervisor link - one supervised worker process per child - and the
parent-side registry serves rlm.list_subagents/rlm.collect/rlm.delete_subagent with TS-parity selector errors; child model resolution
and thinking-level validation live in rlm_child_model.rs; the create
command carries the RLM recursion identity (rlmDepth/rlmMaxDepth/parentSessionPath/thinking) so respawned children keep their depth. Per-session model binding: the
create-config provider/model/apiKey are authoritative for worker model
resolution (explicit CLI flags reach the worker; env remains the no-flag
fallback). Worker session files carry the TS creation prefix
(model_change/thinking_level_change/service_tier_change), and queue
snapshots persist to the worker recovery journal, not the session file.
Queue-lane command surface (queue_commands.rs): the full TSDAEMON_COMMAND_TYPES accept list with exhaustive router tables inprotocol.rs, plus the worker's mutate_queued_message/resume_queue arms
(AgentSession.mutateQueuedMessage/resumeQueuedWork: preview-addressed
delete/move/replace over the two lanes with the TS status vocabulary, and
the empty-queue resume refusal). The sessionActions queue projection
(SessionActionSnapshot) carries one Rust-native typed rider the TS wire
has no counterpart for: rlmChildStatus, the parked RLM child status
notices' lane indices, derived per item from the injected custom row (therlm_child_terminal_notice/rlm_child_failure kinds) at projection time β
so journal recovery re-derives it, the lane strings stay the TSqueuedAgentMessagePreview projection verbatim, and the condensed queue
strip folds exactly these rows (a user-typed lookalike never flags); the
rider serializes only when a notice is parked. The reserved kinds are
daemon provenance, never client data (child_status_notices.rs): the
prompt/steer/follow-up parse and the restore_actions validation answer a
caller-supplied custom row claiming one loudly β it never parks, never
folds β and the daemon's own notice injection rides the follow-up route
with a one-shot capability minted in the worker process (the wire's
optional rlmNoticeNonce), so the queue's classification only ever sees
daemon-authentic rows.
Prompt attachments: the prompt/steer/follow_up wire images array
(base64 payload + mime type) rides the queue item into the session engine
as multimodal user content (images on a queued prompt do not survive a
worker respawn - the recovery journal keeps the text lanes only, the TS
command-recovery shape).
Eager turn abort (TS requestAbort's closing agent.abort()): every
worker abort surface β abort/abort_and_clear_queue, the compaction and
branch-navigation interrupt-and-settle waits, shutdown, kill, and thecancel_prompt_admission cancel-owned arm β funnels throughSessionEngine::abort_in_flight_turn (the engine's build-time agent
mirror), so an abort landing mid-provider-wait cancels the in-flight fetch
immediately and the aborted turn settles on its zero-usage aborted message
(the usage-accounting parity of the #238 adjacent gap 3 fix; the transport
half is pa-core's provider-adapter cancellation token).
Queued-input suspension (TS _sessionInputPumpSuspended, the #227/#233
ruling): abort/abort_and_clear_queue/manualcompact suspend queued-input admission indefinitely; while suspended a
plain prompt/prompt_and_wait is rejected with the TS admission error
and the lanes park. Resume sites: a prompt carrying streamingBehavior,steer/follow_up, resume_queue (even on the empty queue), an appliedmutate_queued_message, a cron/heartbeat fire, and a successful compact
with an active goal. Agent-message delivery is rejected while suspended
and idle (TS acceptAgentMessagePrompt runs with resumeIfIdle: false).
The compact-with-active-goal resume is also the TS post-compaction
continue (agent-session.ts compact()'s didCompact branch): with no
queued work parked, the worker mints the owed goal continuation through
the engine (SessionEngine::mint_post_compaction_goal_continuation, the_maybeResumeGoalContinuationAfterRlmWork mint β the follow-up lane item
with the durable goal-context row), emits the mint's goal_update, then
resumes: the parked continuation crosses the suspension gate through the
resume site and the turn runner drives it (the _schedulePostCompactionContinue
schedule). The compact-trigger auto-refine defers behind that continuation
(TS _compactAutoRefinePending), servicing at its turn boundary.
Thread-goal durability (TS one-store parity): every goal_update
announcement mirrors the new state as a thread_goal_state custom row in
the worker session file (the turn emit closure; the compact mint persists
its row at the mint site, outside a turn), and the engine's session-build
adoption rehydrates the fresh goal driver from the moved branch's or the
session file's latest valid thread_goal_state entry
(goal_state_persist.rs) β status, objective, usage counters, and
continuation counts continue across a worker-recovery rebuild, and the
seeded published baseline keeps the rehydrated state from announcing
itself (TS loads at construction without emitting). Recovery branch
rebuild (TS one-store parity): the owned-session worker respawns with--resume <sessionFile> (createRpcRecoveryArgs), so the rebuilt
session's branch is the durable history β while the Rust worker owns the
file writes and the engine keeps an in-memory manager, the engine's
session-build adoption rebuilds the branch from the worker session file
(the pending_branch seam's recovery twin), so a post-recovery compaction
walk (and the live loop context) reads the full durable history instead
of a fresh engine's empty branch skipping "Session is too short to
compact". Platform wall
(platform): per-OS endpoint naming and socket identity; the transport itself
is the shared trait in pa_types::platform::transport, and the private-frame
codec plus command planes are the shared wire contract inpa_types::daemon (clients in pa-tui/pa-cli speak them).
Live token-stream rendering
(streaming): turn events forward from the hosted engine to the worker's
emit path as they arrive (one message_update per provider delta, the
full partial message per frame - never buffered until turn settle); the
worker coalesces them for broadcast in a single-slot coalescer with a
50ms flusher, while message_start/message_end/tool frames flush the
parked update first and go out directly, so wire order and event-sequence
order match uncoalesced streaming. The supervisor stays payload-free:
deltas ride the worker -> client session-event stream (direct-attach or
supervisor-routed).
Autonomous
continuation driving in the worker's engine: per-message usage accounting
runs in the agent-loop subscription, and the in-run continuation hook
(autonomous_continuation.rs) consults the pa-core AutonomousDriver
policy at the agent loop's natural turn end (product default: shell
quality gates in the session cwd; deterministic drivers injectable for
eval harnesses) β continuations mint as durable user rows churned
inside the one prompt wait (the TS getContinuationMessages shape), a
threshold compaction holds its minted continuation for the worker's
queued followUp admission, and gate pass/fail or limit stops surface no
row (the headless status request and the print exit contract carry
them).
Live MCP product-path verifier
(tests/mcp_product_path_e2e.rs): a settings-declared stdio MCP server
round-trips through a real worker session and kernel - the kernel cell'smcp.list_tools/mcp.call_tool resolve mcp.config through the
session's host handlers, spawn the fixture
(tests/fixtures/mcp_echo_server.py), and echo back.
Update-prepare transaction (update_prepare.rs): the supervisor-side FSMDraining -> Fenced -> Snapshotted -> Prepared -> Stopping with the
mutation-drain latch + admission gate (mutating commands refused with
"Daemon is preparing an update restart" while active; reads/attach and the
TS abort-family drain commands stay served), the hard prepare deadline
(90 s default) and the durable 45 s marker self-expiry as watchdogs β both
re-checked on a timer and on any later command β idempotentprepare_update_restart on updateId (a different id is a typed refusal
the coordinator maps to Join), and Aborted -> Serving with prepared-dir
cleanup as the failure default. The prepare RPC drives accept
through the mutation drain to Fenced, snapshots every resident worker
(update_snapshot, worker.rs), assembles the roster (update_roster.rs:
sessions/workers/subagents from the ledger + displays, heartbeats as a
read-only scheduled-jobs.json projection), writesprepared//{roster,marker}.json durably, and acks Prepared β all
inside the prepare budget. commit_update_restart consumes the
transaction (update_stop.rs): the acked worker shutdown (its handler is
the flush barrier) with the 30s + 30s budgets, abandon-on-refusal (the
supervisor resumes Serving, refused sessions untouched, stopped workers
relaunched β never a kill), or the update exit when all workers stopped
(descriptors survive for the new supervisor's create-or-adopt restore).
The new supervisor's boot owns the update's restore side
(update_restore.rs, spec Β§6): it consumes PRIME_AGENT_UPDATE_ROSTER
from its spawn env before the unconditional scratch-dir sweep (this
socket's update-restarts/ subtree plus the TS-era legacy names β no
liveness checks, no exceptions), then restores the roster rows bottom-up
(create-or-adopt; per-session failure records, restore never fails the
boot), re-arms scheduled work (a boot scan of scheduled-jobs.json;
due active jobs of sessionless files are woken once, never archived), and
reports the pass over update_restore_status while hello.update_resume
carries the settle state; client attaches to a not-yet-restored roster row
queue behind the pass instead of failing.
For historical TypeScript update manifests, the restore pass recreates parents
before their children, remaps active parent IDs, and restores pending context
and action queues explicitly because those manifests predate Rust's journals.
Attaches remain gated until that replay completes.
The heartbeat-catalog change
broadcast owns its surface here too (the worker's cron-storeon_heartbeat_change listener emits a heartbeats_changed outbound frame
β TS daemon-mode's broadcastGlobal β and the supervisor re-broadcasts it
to every client, so any heartbeat mutation anywhere in the daemon reaches
attached UIs daemon-wide).
Non-goals
No agent behavior inside workers beyond hosting a pa-core engine; no UI.
Public API
Supervisor entrypoint, worker entrypoint, mcp_login::{WorkerMcpLoginUi, wire_worker_mcp_login} (the worker's browser+callback login behind mcp.begin_login; wired by the agent engine before sessions register host handlers), client connection API for pa-tui/pa-cli, acp::daemon::{run_daemon_attached_acp_mode, DaemonAcpOptions} (pa-cli dispatches --mode acp through it), agent_messaging::LinkAgentMessageController + rlm_children::{SupervisorChildSessions, ParentIdentity, RlmChildIdentity} (e2e verifiers construct the worker-side family controller and the children registry; the engine wires the same types), agent_engine::AgentSessionEngine::dispose_kernel (the session-end kernel teardown the worker invokes at kill/shutdown/orphan exit β the engine outlives the session, so the pa-core engine-drop teardown cannot run there), the worker's get_mcp_connections command (mcp_connections.rs: the /mcp view's roster from the session's MCP manager overlaid with the session kernel's per-server tool listing, plus the api-key credential rows the same response serves β Rust-native session-plane extension; the TS daemon has no counterpart). Supervision internals pub(crate).
Depends on
pa-types, pa-core (one-way).
The composition root initializes the packaged product version withprotocol::configure_app_version before entering daemon or worker mode.protocol::app_version supplies that identity to both hello frames and update
checkpoints; library embedders fall back to the compiled version. Release
manifest restamping therefore remains consistent with CLI --version.
Overview (`crates/pa-models/README.md`)
pa-models
The live model catalog subsystem: fetch layer, no-cold-start chain, strict
schemas, transport pinning, and the credentialed Prime Inference catalog.
Port of the TS catalog client (branch feat/catalog-client:model-catalog-cache.ts, model-catalog.ts, model-compat-schema.ts,provider-model-catalog.ts, bundled-model-catalog.ts,prime-inference-model-catalog.ts).
Scope
- Fetch layer: unauthenticated GET on the two catalog URLs; 5 s timeout,
8 MiB cap (content-length AND streaming), redirects refused, ETagIf-None-Matchwith 304 snapshot reuse;PI_OFFLINEdisables the network. - No-cold-start chain: validated last-good disk cache -> build-time bundled
snapshot -> compiled fallback (42 transport tuples + 110 offline Prime
Inference entries). - Strict schemas: serde
deny_unknown_fields, version gates (modelsschemaVersion == 1), skip-invalid for remote model refresh, compat
validation perapi. Unsupported future versions keep last-good
silently, forever. - Transport pinning: every remote model must match a compiled
(provider, api, baseUrl)tuple; catalog data never carries request
headers. - Prime Inference: live credentialed fetch, scope-keyed disk cache
(HMAC-SHA256 of key over team), 401/403 clears only that scope. - Refresh: hourly + startup + picker open + auth change; fire-and-forget;
coalesced per source; a mid-session refresh never retargets the active
model.
Non-goals
- The plugins/MCP catalog serde + resolution (the plugins lane; this crate
hands it the fetcher, the genericCatalogCache, and the bundled asset
bytes). - The build-time asset generator + release packer (the build lane).
- Session auth storage,
models.jsonparsing, and registry merges
(pa-core): the user's localmodels.jsontakes precedence over this
catalog and is merged by its owner. - The catalog repo's sync/export tooling (lives with the data).
Public API
ModelCatalog(chain resolution + refresh cadence),RefreshTrigger,PrimeCredentialscache::CatalogCache<T>(generic last-good cache; the plugins lane
instantiates it with its own fail-closed parser),cache::{RefreshOptions, PUBLIC_SCOPE}fetch::{CatalogFetcher, FetchOutcome, FetchError, MODEL_CATALOG_URL, MCP_SERVICE_CATALOG_URL}schema::{parse_model_catalog, InvalidEntries, ModelCatalogV1}compat::is_model_compatpinning::{parse_provider_model_catalog, pin_catalog_models, PinnedTemplates}transports::{compiled_models, compiled_transport_tuples, prime_inference_offline_entries}bundled::{BundledAssets, load_bundled_models, PACKAGED_MODEL_CATALOG_FILE, PACKAGED_MCP_CATALOG_FILE}prime_inference::{PrimeInferenceCatalog, PrimeInferenceCredentials, build_prime_inference_models, merge_prime_inference_models, parse_prime_inference_model_catalog, is_private_prime_inference_model_id, scope_key, PRIME_INFERENCE_BASE_URL}offline::is_catalog_offline
Dependencies (direction compliance)
Depends on pa-ai (compiled transport templates: models_generated) andpa-types (shared Model). Nothing else in the workspace; no crate belowpa-ai is touched. Consumers live at or above pa-agent (registry wiring
in pa-core). The plugins lane must not place its catalog code in pa-ai
(pa-models already depends on it).
Overview (`crates/pa-perf/README.md`)
pa-perf
The prime-agent performance tooling: a deterministic corpus generator over
the product's real on-disk layout, and a pty bench harness that drives the
REAL release binary (target/release/prime-agent) headlessly. A dev/CI
crate at the top of the workspace dependency graph β nothing depends on it,
and it never ships (the release lanes build -p pa-cli --bin prime-agent
only).
Scope and non-goals
- Scope: faking realistic agent-dir state at scale (sessions, subagent
trees, artifacts, the spawn ledger, the cwd project tree), and measuring
the interactive surfaces (start, agents view, open/switch session,
@ completion) through the real binary. - Non-goals: benchmarks of provider/LLM paths (the harness stays offline),
and any product-code changes. The newvt100screen-model dependency
is described below; clap and nix were already in the tree.
The corpus generator
pa-perf corpus --profile medium --out DIR [--seed N] [knob overrides]DIR must be absent or empty. A populated output is rejected before any writes,
so stale sessions cannot enter the new corpus or its restore baseline. Choose a
fresh directory for another standalone generation. The suite command can be run
again in the same output: it replaces only its own corpus directories after
verifying their manifests, and refuses unrelated populated directories.
Writes the product's real layout under DIR/:
DIR/
corpus.json # manifest: counts, needle markers, the file-set baseline
project/ # the sessions' cwd + @-completion tree
agent/ # point PRIME_AGENT_CODING_AGENT_DIR here
settings.json # onboarding pre-answered, traces off
sessions/<uuid>.jsonl # current-format session files (flat)
sessions-archive/<uuid>.jsonl
rlm-ledger/<hash16>.jsonl # written through pa-daemon's RlmSpawnLedger
session-artifacts/... # kernel-state stubs, harness/, sub-<id>/ treesRow payloads are serialized from the product's own types (pa-types message
shapes inside pa-daemon's SessionEntry envelope and session_header_line
header) β no hand-copied JSON. Entry ids are counter-based unique 8-hex: the
product's own ids are uuid4 prefixes with a 100-try collision check, which
collide around ~22K rows and silently break branch reconstruction.
Knobs
--profile small|medium|large|xl picks the defaults; every knob overrides:
| flag | meaning | small | medium | large | xl |
|---|---|---|---|---|---|
--sessions |
top-level sessions | 10 | 100 | 1,000 | 2,500 |
--rows |
rows per session | 100 | 2,000 | 10,000 | 400 |
--huge-rows |
rows of the huge marker session | 2,000 | 4,000 | 20,000 | 2,000 |
--child-rows |
rows per subagent child | 40 | 100 | 150 | 80 |
--fanout |
children per parent per level | 2 | 3 | 3 | 2 |
--depth |
subagent tree depth | 2 | 2 | 3 | 2 |
--tree-parents |
sessions growing a tree | 2 | 5 | 8 | 10 |
--wide-children |
one parent's direct children (the operator's ~110-wide real parent) | 0 | 0 | 110 | 0 |
--archive |
sessions in sessions-archive/ | 3 | 20 | 200 | 500 |
--cwd-files |
files in the project tree | 200 | 2,000 | 4,000 | 2,000 |
--artifacts |
per-session artifact partitions (on/off) | on | on | on | on |
--cache |
cold/warm (see below) | cold | cold | cold | cold |
--seed |
the deterministic seed | 7 | 7 | 7 | 7 |
Total rows: small ~3.5K, medium ~250K, large ~10.1M, xl ~1.2M (medium is
~210 MB on disk; large ~3.5 GB; xl ~1 GB, generated in ~5 s).
Determinism
Same seed + same canonical path => a byte-identical tree, file mtimes
included (roster order is mtime-desc). Two stamps are the product writer's
own wall clock and are excluded from that contract: the ledger records'at fields, and the corpus-root path embedded in child headers/ledger
records (parentSession/child are canonical paths).
Caches (cold vs warm)
--cache warmruns the product's ownpa_core::session::window::WindowedSessionStore::open
over every openable session file, writing real.window-cache.json
sidecars. On a real store warm window-cache sidecars are the common state
(the operator's live store: 71 of 111 sessions carry one).- The
.info-cache.jsonsidecar is written only at worker lease
release (pa-daemon, no public writer), so generated corpora carry none β
matching real stores (9 of 111). Theagents-view-warmscenario measures
the warm scan via the daemon's in-process cache instead. pa-perf rungenerates BOTH corpora (cold for the cold scenarios, warm
for warm-start/warm-ready) β every scenario runs on the state it needs.
The bench harness
# the whole suite (CI + make perf): generates the corpora, routes every
# scenario to the corpus state it needs, writes one results/<scenario>.json
pa-perf run --binary target/release/prime-agent --profile medium --out DIR [--runs N]
# one scenario over one corpus (investigation)
pa-perf bench --binary target/release/prime-agent --corpus DIR --scenario S --runs 10 --json OUTScenarios (timed from a triggering action to an observable needle β literal
paints as byte needles, cell-diffed content through a vt100 screen model;
the liveness deadline only fails broken runs):
| scenario | corpus | trigger -> needle |
|---|---|---|
cold-start |
cold | exec -> the chat first paint (the splash paints before any daemon work β the product's own paint contract) |
cold-ready |
cold | the same launch, exec -> the tray's session-usage pill, the first attach-only content (daemon spawn + session create + attach included) |
warm-start |
warm | exec -> first paint, daemon pre-started |
warm-ready |
warm | the same launch, exec -> the attach pill |
agents-view |
cold | left-arrow -> the settled first catalog page (Inactive (N) plus the M older sessions not loaded footer account for every session) in the screen model |
agents-view-warm |
cold | the same daemon's second populate (its in-process scan cache warm) |
open-session |
cold | --resume <huge session> -> the huge session's tail marker (the final user row; its reply is one line) |
session-switch |
cold | enter on the uniquely-filtered switch-b row -> B's transcript marker (the final user row; its reply is one line) |
at-completion |
cold | the @ keystroke -> the first completion menu row |
Mechanics (the repo's pty-e2e discipline): the TUI child runs in its own
session with NO controlling tty (setsid pre-exec; vendored crossterm
falls back to the stdio pty), 120x36, kitty probe answered identically every
run, PRIME_AGENT_CODING_AGENT_DIR + --daemon-socket isolate it, and the
env is rebuilt from scratch (no ambient credentials leak in). Between runs
the harness restores the corpus to the manifest's file set (the product's
new session files, sidecars, logs, and leases are deleted) so every run
measures identical state. A discarded warm-up run precedes the samples.
Teardown signals only the harness's own child pids; the daemon stops by the
shutdown protocol and the run FAILS if it keeps accepting β never a
pid/name-based kill.
report + baseline
pa-perf report --baseline crates/pa-perf/baseline.json --results results/*.json [--markdown OUT]Prints the aggregate table (scenario/profile, median, p95, baseline, delta)
and exits nonzero ONLY when a non-report-only median exceeds twice the
baseline. Noisy scenarios (>20% spread across 3 CI runs) are markedreport_only in the baseline: their deltas always print, the gate never
fails. baseline.json is committed from CI numbers, not laptop/sandbox
ones.
Local run
make perf builds the release binaries and runs the whole suite
(pa-perf run --profile small) with the report table printed at the end.
Run it on a quiet machine; the numbers are wall-clock latencies.
Dependencies
pa-perf adds ONE new third-party dependency: vt100 (MIT), the screen
model behind the content needles (the agents view's heading counts repaint
as terminal cell diffs, which the raw byte stream cannot see). The crate
never ships β the release lanes build -p pa-cli --bin prime-agent only β
and make deny stays clean.
CI
The bench matrix runs all four profiles at 10 runs each; the xl
(2500-session) agents-view populate measures ~2.7 s on an 8-core sandbox.
.github/workflows/perf.yml: nightly schedule + workflow_dispatch +
label-gated pull requests (perf-test label, [labeled, synchronize] β
labeling is the explicit opt-in). One release build, a bench matrix over
the four profiles, and a report job writing the $GITHUB_STEP_SUMMARY
table with the >2x gate.
Overview (`crates/pa-telemetry/README.md`)
pa-telemetry
Modular telemetry: the event schema, queueing/batching, sinks, and the
pseudonymous installation identity for Prime Agent product analytics.
Scope
Properties: primitive-only property maps (the privacy boundary β strings,
numbers, booleans, null; structured values are rejected at insertion).TelemetryEvent:{id, name, timestamp, properties}records.TelemetryClient: non-blockingtrack(event, properties),flush,shutdown. One background worker, capped queue (drop-oldest), batched
flush (size + interval + explicit), fan-out to every sink. Best-effort by
contract: never blocks, never panics, never fails the agent.TelemetrySinktrait + shipped sinks:AnalyticsSink(the one product
destination: the TS endpointANALYTICS_ENDPOINTand wire format{installation_id, events: [{id, name, timestamp, properties}]}, no
credentials, 1.5s timeout; the platform backend forwards to PostHog),FileSink(local JSONL transparency mirror at<agentDir>/telemetry.jsonl),NoopSink(opt-out fast path),MockSink(tests, also re-exported for
downstream crate tests).install_id:<agentDir>/telemetry.json{version, installationId},
exclusive 0600 create, validated on load (TS parity).rename_onto: the shared rename-onto-destination primitive for durable
persist writes (TSrenameOntoSync: bounded win32 destination-busy
retry,10ms * attemptbackoff, every failure immediate off Windows).
Lives in this crate because every persist owner (pa-core's settings and
session writes, pa-daemon's descriptors/journals/session store, this
crate's install id) already depends on it while it depends on no other
workspace crate; pa-core re-exports it asplatform::rename_ontoso its
platform wall stays the engine's single platform entry.- The TS boolean env parsing (
parse_bool_override); the opt-out
precedence (PI_OFFLINE,DO_NOT_TRACK,PRIME_AGENT_TELEMETRY,
settings) lives in pa-core'stelemetry_switch.
Non-goals
- No settings reading: the enabled/disclosure flow is owned by pa-core
settings + pa-cli wiring (this crate only answers "does the environment
override?"). - No event emission from this crate: seams live in pa-core/pa-daemon/pa-cli,
which calltrackwith schema-documented events. - No prompt/session content, tool arguments, file paths, or user identity β
by construction (primitives-only properties). - No retries or delivery guarantees: batches are best-effort and offline-safe
(dropped on failure), matching the TS product.
Public API
TelemetryClient/TelemetryClientConfig(track,flush,shutdown,dropped_count,install_id,spawn)TelemetrySinktrait,SinkOutcomeProperties,TelemetryEventinstall_id(agent_dir)rename_onto(from, to)parse_bool_override(value),existing_install_id(agent_dir)- Sinks:
AnalyticsSink(+ANALYTICS_ENDPOINT),FileSink,NoopSink,MockSink
(+RecordedBatch) AnalyticsSink::new(endpoint)andwith_timeout(endpoint, duration)retain
their default HTTP clients.with_http_builder(endpoint, builder)lets the
caller supply its trust configuration while retaining the product timeout;
pa-core composition supplies the shared extra-CA builder without adding a
workspace dependency to this leaf crate.
Placement
Depends on no workspace crate (sits beside pa-types at the bottom of the
dependency graph). Consumers: pa-core, pa-daemon, pa-cli.
Overview (`crates/pa-tui/README.md`)
pa-tui
The terminal UI.
Scope
Rendering (markdown, themes, layout, tool panels, custom-message decorated rows: the agent-message summary rows render the viewer-relative notice (the shared Agent message label, the β sent/queued and β received arrows from the row's actual direction, the counterpart agent's name only, no collapsed body preview β the operator's 2026-09-25 directive), and the ipython cell renders the sent/queued receipt rows below the code, and the RLM child rows render the outcome diamond rows β accent diamond, semantic label colors, the failure error and the cancellation reason as the expandable body; the skill-invocation card β TS SkillInvocationMessageComponent: a user message whose text is one <skill ...> block renders the [skill] <name> card collapsed, the **<name>** + content markdown expanded over the shared pa_types::skill_blocks parse, and the trailing invocation args as their own user block), the working loader row (activity label, TS formatWorkingElapsed, and the tool-owned working note during python-kernel boot, sourced from starting tool partials), image support (the terminal-image library: capability detection, Kitty/iTerm2 protocol encoders, dimension parsing; the image component with the fullscreen compose fallback; terminal.showImages gating of tool-result image rows; clipboard image paste behind app.clipboard.pasteImage with [image #N] editor markers, byte-budgeted registry, and wire-image attach), editor (cursor/kill-ring/undo/history/word ops, with the live prompt highlight: argument tokens in their TS colors on every line and the first line's slash-command token in accent for any recognized builtin command (sanctioned divergence, operator ruling 2026-09-29: TS accents argument-taking commands only) β suppressed while the cursor sits inside it, TS CustomEditor.styleDisplayText), the transcript prompt-highlight surfaces (TS PromptTokenMask: the user-message block masks its accent command segment and argument tokens to same-width private-use placeholders before the markdown layout and restores them after, TS UserMessageComponent; the session-command echo row styles the typed command the TS SlashCommandMessageComponent way β accent command segment, colored tokens, styled wrap), and the suspend-to-background cycle (app.suspend, default ctrl+z, TS handleCtrlZ: SIGINT ignored for the stopped window, the TUI stopped with the terminal handed to the shell, the process group SIGTSTP'd; the SIGCONT resume re-applies raw mode, the alternate screen, and SGR mouse tracking β the terminal modes a suspend cycle can lose), keybindings (user-editable via ~/.prime/agent/keybindings.json: TS parse/migration/merge semantics β legacy id migration on load and at startup, malformed values dropped, empty-array disables, same-scope default eviction, conflicts, case/order-insensitive matching β the effective set wired into every hint, handler, and picker), autocomplete (slash commands, path completion, and the @ fuzzy file search: a parallel ignore walk with fd's semantics, off the UI thread), fullscreen/scrollback (the wheel-scroll surface: SGR mouse-report decoding, ?1002+?1006 tracking enabled blind at the fullscreen surface's entry and released on exit/suspend β tmux never answers DECRQM, unsupporting terminals ignore the mode-sets β wheel turns scroll the transcript three lines, consumed without scrolling while a picker owns the frame or when the terminal.fullscreenMouse setting disabled tracking; in-frame click/drag selection stays unported), input handling, and the double-Ctrl+C force-quit guard (the exit contract: two in-window Ctrl+C presses end the process within 1.5s of the second press regardless of daemon or loop state; cleanup inside that window is best-effort). Interactive sessions attached through the daemon: the prompt stash (TS prompt-stash-state.ts: the client-owned per-session draft store shared across a TUI process's chat views β a draft left behind on a session switch returns to the editor when the session's chat reopens, its pasted images rehydrated behind their markers; the capture on the agents-view handoffs (/resume, the rebound app.session.resume key, the scoped view, /resume <selector>) and the in-place /switch, the restore on reopen/switch-back, and the queued-stash semantics for multi-switch chains; the manual app.prompt.stash capture (default ctrl+s, TS handlePromptStash): the key stashes the live draft and clears the editor, restores it on the next press, and a manually stashed draft returns on that key or after the next sent prompt is admitted β never on a reopen or a switch landing), the JSONL client socket (hello handshake, command envelopes, session-event loop), slim-attach snapshot reconstruction, the direct worker link loss path (a killed/crashed worker tears the link; the UI re-attaches through the supervisor with the TS reconnect surface - the Daemon connection lost; reconnectingβ¦ warning, the resync, Daemon reconnected, the 60s TS window with TS backoff, and the terminal β Error: Daemon reconnection failed: <last> arm with dispatch blocked so the UI never rides a silent spinner), prompt submission with streamed assistant output, live session list and switch, the inline pickers (/model: the TS inline menu-panel - the bordered Search models field, >-marker rows with the effort-square cluster and current Β· require sign in Β· provider trailing, the price detail block, and the model/effort key hint - over the catalog the daemon's get_model_catalog refresh supplies (bundled + models.json fallback), with the TS sort chain, the scored fuzzy filter, left/right effort adjustment, and apply through the daemon set_model command (plus set_thinking_level for a user-edited effort) with the Model: status row; /effort: the session's thinking levels from get_state, the unsupported-model note, and apply through set_thinking_level; /mcp: the inline connections view over the daemon's get_mcp_connections roster - the TS MCP-Connections-tab panel (bordered Search MCP connections field, βΊ rows with the status flush right on connected entries, the selected connection's detail block with its tool listing, the navigate/select/close hint), Enter resolving to the selected connection's login command through the interactive loop's client-command seam, plus the api-key credential rows the same response serves (the stored keys, e.g. the web-search one; Enter runs the paste-the-key prompt, a rust-only surface - the operator's /login-is-providers-only ruling moved the key here, TS still serves it on its /login MCP tab); /heartbeats: the inline management view (TS HeartbeatManagerComponent, inline-picker style per the lane directive): the session-scoped user and agent heartbeat catalog from the selector-less supervisor heartbeats_list β scoped like TS scopeHeartbeatsToSession to this session and its roster descendants β with the TS count label, βΊ rows with status-colored trailing, the selection's detail line, the actions pane (pause/resume/stop through heartbeat_manage), the fetch/action error rows, the heartbeats_changed broadcast refresh through the update channel, and the tray heartbeat label N heartbeats Β· M paused (Ctrl+R); ctrl+r (app.heartbeats.open) opens it from anywhere), the TS showStatus back-to-back rewrite (a status emitted with nothing after the previous one rewrites it in place), the session export and share commands (/export [path]: the path-argument parse, the .jsonl branch switch, the daemon-side export_html/export_jsonl requests, and the Session exported to: row; /share: the gh auth gate, the temp export, the background gh gist create run behind the bordered Creating gist... loader with Esc cancel, and the Share URL:/Gist: rows - the gist provider is the GitHub CLI exactly like TS), the visible follow-up queue (TS queuedMessagesContainer): prompts parked behind a running turn render as dim preview rows above the prompt dock with the TS prompt-highlight styling (accent on a leading slash command's /name segment, success/mdLink on @path/--flag argument tokens β TS styleQueuedMessagePreview) β Steering:/Follow-up: labels with the β°β <key> to browse and edit queued messages hint, re-synced from state.sessionActions on attach and kept live by session_action_update; the queued internal prompts (heartbeat fires, agent messages, goal contexts, background-command notices) condense into one counted row below the human previews β "1 agent message, 1 heartbeat, and 1 other internal prompt queued": each origin's own count, singular/plural-correct, zero-count origins omitted (sanctioned divergence, operator requests 2026-09-24 + 2026-09-25: TS renders every internal prompt as its own preview row; the browse/edit affordances still walk every queued item, so only the strip rows condense β the four TS-internal labels classify by preview string, TS isLabeledQueuedPreview, the queue wire carries no provenance for them, so a human-typed prompt that begins with one of the internal labels classifies internal for the strip too: it still delivers, and the browse/edit affordance walks and shows it; the parked RLM child status notices β the [child-exited: ...]/[child-failed ...] lifecycle rows the queue-fold operator bug 2026-09-25 flooded the strip with β classify by WIRE-TYPED provenance instead, never by their text: the worker's sessionActions.rlmChildStatus rider carries the parked injected rows' lane indices, they count as their own origin ("N child status notices"), and a user-typed lookalike stays a human row); the collapsed conversation view (operator directive 2026-09-28, the undo of the 2026-09-25 condensed-run summary block): the collapse mode renders every activity item exactly as details does - every tool card, every notice, every agent-message row - with ONLY the thinking blocks hidden (the TS fork's shape), a chat starts at that collapsed level, and the Ctrl+O cycle from there is the thinking-visibility toggle (overview -> details -> all -> overview unchanged; a card click toggles only the clicked card's own expansion - TS's per-component expanded - and the Ctrl+O cycle resets every card to the new level) - purely render-time over the unchanged transcript model (no session jsonl schema change, no stored-event change); the follow-up key (app.message.followUp, default alt+enter) parks a submission on the follow-up lane (streamingBehavior: "followUp" + queueIfBusy), Enter on the steering lane; the browse affordances TS gives the rows (TS QueueSelection port): alt+up/alt+down walk the parked messages newest-first with the editor draft stashed and restored, the dim browse header (lane, index, key hints) renders as the editor box header row, Enter steers the edit (replace/move to steering, empty deletes), the follow-up key re-parks it, ctrl+alt+arrows reorder through mutate_queued_message (mirrored locally like TS β the event can land after the response), the clear key restores the draft, and queue-change statuses surface the TS status rows verbatim; and the utility slash commands (TS dispatch_client_command arms): /btw+/side (the side-question pane over the daemon start_side_question/abort_side_question flow β the popup-surface turns with the /btw header, follow-up bubbles, the slash/image rejection notices, the running/return hint rows, Esc cancel; replies while the pane is open seed follow-ups from the answered turns), /fast (the priority-tier toggle with the TS eligibility check and unavailable status), /rlm-max-depth (view/set with the --global arm and the TS status rows), /name+/rename (the daemon set_session_name rename whose persisted session_info entry and session_info_changed broadcast the daemon owns), /settings (the TS SettingsList grouped into tabs β the spaced tab strip (white-bold active tab) over label/value rows with descriptions, β/β/Enter/Space value cycling (the arrows step a row's options in place; Tab/Shift+Tab and the number keys move the tabs), the detail block's separator rule and description-matched hint padding, the thinking/theme/warnings/service-tier submenus with the theme's live preview and restore, type-to-search; daemon switches for autocompact/steering/follow-up/transport/thinking, the client-settings seam for the persisted preferences), and /reload (the TS guards, the bordered reload box, the daemon reload request, keybinding/theme/catalog re-reads, and the outcome row); /thinking rides the registry alias into /effort. The client-settings seam (client_settings::ClientSettings, implemented by the composition root) is how pa-tui persists user preferences without a pa-core dependency; the autocomplete listing drops /fast when the current model is not fast-mode-eligible (the pa-types supports_fast_mode helper), the subagent summary line under the tray (TS SubagentSummaryLine: live descendant counts from the roster subscription, the dock renders in every session, all-zero included (the operator's 2026-09-28 directive; TS renders nothing at zero, a sanctioned divergence), and its Subagents group opens the scoped agents view even with no children, which shows the view's No sessions yet. empty state, the focus hand-off from the editor β Down at the end of the prompt or app.subagents.focus β the focused key contract β confirm/open into the focused group's own view, left/right stepping every rendered group (an empty one included, wrapping at the row's ends β the operator's 2026-09-26 muscle-memory directive), up/cancel/back to the editor, expand keeping the focus, other keys releasing it into the editor path β and the scoped-view hand-off; every dock panel's exit (ESC/left from the heartbeats view, the bash view, the goal panel, or the scoped agents view's back keys) restores the dock's focus on the panel's own group instead of the prompt bar β the operator's 2026-09-26 panel-exit ruling; TS has no multi-group dock, its panels close to the editor, a sanctioned divergence); and the agents view (the unified live-roster +saved-catalog session list: Running/Idle/Inactive sections, inline search(TS query language: case-insensitive substring tokens, "quoted phrases",re: regex, capped fuzzy fallback; the corpus is deliberately restricted to
the session name, the durable session id, and the cwd β the TS corpus fields
(first/last messages, the capped saved transcript, and
file paths) never match; Escape clears the query and the view restores the roster;
the query survives opening a chat and returning), open-to-attach/resume,n-via-ctrl+n new session, live roster_subscribe pushes; the
selection is session-keyed, not index-keyed (TSresolveAgentsViewSelectionState: row identity, then active-session id,
then session id re-find the same session across roster rebuilds β spawn
churn, activity re-sorts, and identical no-op re-pushes never move it;
a session that left the roster keeps the bounded position, TS parity),
and the list window follows the selection (TS renderSessionRows: the
slice centers on the selected row and clips the overflow behind
leading/trailing ellipses, so arrowing below the fold keeps the
selection on-screen instead of the view snapping back to the top of the
list; rows carry their session's β· N heartbeat count from theheartbeats_list catalog, refreshed on heartbeats_changed), ctrl+o's spawn-program surface (app.agents.program: the selected row's target β the row itself for a top-level agent, its parent otherwise β expands its list and shows the program rows, one code block per spawn cell above the children it launched, capped at 10 lines with the remainder marker, on the tool-panel background, skipped by the selection and the click surface, a second press hides it, and collapsing the list clears it), ctrl+r's rename mode (app.agents.rename: the prompt becomes the "Rename agent session" header over the name buffer β the search grammar, Enter submits the trimmed name through the daemon rename/rename_saved_session, Esc cancels back to the untouched query β a saved rename patches the catalog row in place), and the tui agents action adoption events (program_shown, renamed) that ride the view outcome out of the run.
Non-goals
No session logic, no providers, no loop policy. The interactive UI renders daemon events and sends user intents (prompts, abort, switch) as daemon commands; the session loop itself lives in the pa-daemon worker. It never computes agent behavior and never spawns the supervisor (launch semantics live in pa-cli).
The client commands of the clipboard/auth/update group: /copy (the last assistant text through the platform clipboard tool chain with the OSC 52 fallback β clipboard.rs + osc52.rs, the same emitter the selection-copy work can reuse; the daemon get_last_assistant_text supplies the text), /import (the path argument parse, the TS confirm, the typed error surfaces β the missing file, the missing session cwd with its fallback-cwd retry β and the post-import transcript rebuild through the daemon's import_jsonl), /login + /logout (the TS providers selector β provider_auth.rs, the panel showConfigurationMenu("providers") mounts: the tab bar, the search, the status indicators, the in-panel API-key prompt β over the composition root's auth flows), /traces (the TS status block and settings writes; the upload subsystem stays unported, its arms keep the TS state shapes and report the unavailability), and /update (the TS busy guard, the split-CLI child mapping β packages then the binary β and the self-update relaunch through the composition root's hook).
Public API
custom_message::{custom_message_entries, AGENT_MESSAGE_CUSTOM_TYPE, skill_invocation::{SkillInvocationRow, skill_invocation_entries}, ...} (the custom-type render dispatch shared by the live and replay paths; the user-message skill-invocation card decode; component row renderers are pub(crate)), app::run_app (replay), interactive::{run_interactive, InteractiveOptions, ModelSelection, SessionSelection, UiMode, HeadlessPlan, InteractiveOutcome} (daemon-attached), daemon_client::{DaemonClient, DaemonClientEvent} (wire client for the UI and the composition root), session::SessionStream, agents_view::{run_agents_view, AgentsViewOptions, AgentsViewUiMode, AgentsViewOutcome} (the view and its headless verifier seam), interactive::{OnboardingTask, OnboardingSink} (first-run flow surface; the composition root implements the sink against the settings manager), keybindings::{KeybindingsManager, migrate_keybindings_file, KEYBINDING_NAME_MIGRATIONS, format_key_text} (the effective-binding surface: the composition root loads the user config and passes the manager in; migrate_keybindings_file is the startup rewrite), queued::{QueuedMessages, QueueSelection, QueueSelectionItem, QueueLane, QueueBrowseKeys, render_queue, browse_header_text, mirror_lane_move} (the strip and browse state; the session UI owns the selection), prompt_stash::PromptStashStore (the per-process draft store the composition root owns; entry semantics are crate-internal), client_settings::ClientSettings (the client-process settings seam the interactive commands persist through β the composition root implements it over the real store), client_auth::{ClientAuthCommands, ClientAuthCommandsHandle, run_mcp_auth_command} (the /mcp login//mcp logout client surface: the TUI owns the command UX and mounts the inline auth panel β TS usage wording β while the composition root runs the login against it). mcp_view::{McpView, McpViewAction, McpConnection, McpToolInfo} (the /mcp inline connections view: the daemon get_mcp_connections roster rendered in the TS MCP-Connections-tab panel shape, with the per-connection status and tool detail; Enter resolves to the selected connection's login command, dispatched through the interactive loop's client-command seam). traces::{TracesCommands, TracesCommandsHandle, traces_base_url} (the /traces settings + credential seam), provider_auth::{ProviderAuthCommands, ProviderAuthCommandsHandle, ProviderRow, AuthType, AuthCategory, AuthFlow, AuthStatusIndicator, AuthStatusStyle, AuthSelectorKind, PRIME_INFERENCE_PROVIDER_ID} (the /login+/logout provider-auth seam: rows, status indicators, and the flow hook), auth_panel::{AuthPanel, AuthPanelHandle, AuthPanelRequest, PrimeTeamOption, PrimeTeamPick, PasteStyle} (the inline auth panel the login flows render through β TS LoginDialogComponent + PrimeTeamSelectorComponent: progress lines, the browser URL block, the paste prompts, and the Prime team picker, all over the run loop's request/reply channel), update_command::{UpdateCommands, UpdateCommandsHandle, UpdateOutcome, UpdateRunFuture} (the /update out-of-band runner seam: the TUI confirms, then spawns the composition root's funnel task and folds the landed outcome row), confirm::{ConfirmPanel, ConfirmAction} (the Yes/No confirm panel), and InteractiveOutcome::clipboard_emissions (the headless OSC 52 capture the verifiers assert with). Component internals pub(crate). altscreen::{enter, leave} is the process-wide alternate-screen ownership shared by the terminal surfaces: the first surface enters the screen, a real exit leaves it, and in-process view switches adopt the same buffer (no primary-screen flash between the agents view and a chat).
Depends on
pa-types only (the daemon wire protocol types live there). The headless UiMode is the verifier seam: it drives the identical attach/submit/stream/render path without a TTY and captures rendered frames.
Transcript caching
Fullscreen transcript frames render the visible entry window through a persistent entry cursor. Per-entry, per-detail line caches retain visited rows; unseen entries stay unrendered. Selection highlighting restyles only the rows the selection change touched (cached rows, never a rebuild from raw entries), and appends, entry growth, and tail re-anchors fold into the sparse window's bookkeeping instead of a geometry pass. Absolute-row operations, including paused detail changes, use separately cached count-only geometry and paint only the requested window. Cold geometry still scans uncached content; the exceptional copy fallbacks walk only the selected range from the viewport cursor. The frame's end sections β the splash and the streaming tail (the shortcut guide, pending bash output, the loaders) β render only when the visible window reaches them, and frames without a selection skip the restyle pass (its styled rows are the cached window verbatim). These caches and viewport coordinates are private to this crate; wire types and dependency direction are unchanged.
Overview (`crates/pa-types/README.md`)
pa-types
The single shared vocabulary crate. Nothing else is shared between crates.
Scope
compaction::is_valid_threshold_percent is the shared percentage-domain validator
used by settings deserialization, persistence, runtime policy, and the TUI.
Wire and domain types + serde only: AI messages/content blocks/tool calls/usage/stream events, session JSONL entry schema, daemon wire protocol messages, worker frames.
Daemon wire mechanics shared by the serving side (pa-daemon) and clients (pa-tui/pa-cli) live here because pa-tui depends on pa-types alone:
daemon::framing: the private-frame codec of the worker socket (direct-attach clients speak it too).daemon::plane: the session/control command-plane table (worker-side peer gating and client-side socket routing both read it).daemon::{DaemonPeerTransportTicket, DaemonWorkerPeerGrant, DaemonPeerCommand}: the direct-transport ticket and grant wire shapes.goal: the thread-goal wire state (GoalState/GoalStatus) shared by the
goal engine (pa-core), the daemon wire (goal_updatesession events and
the attach snapshot'sstate.goal), and the TUI (announcement rows, tray
label). The goal engine (validation, accounting, continuation prompts)
is pa-core's.slash_commands: the builtin slash-command table every surface shares
(the TUI dispatch + autocomplete, the session engine's command admission,
CLI suggestion help) plus its pure parse/suggestion helpers β the TS
product keeps the same single table in core and imports it from its TUI.incident: the daemon incident classifier shared by the incident CLI
(pa-cli'sprime-agent incident, TSsrc/cli/incident.ts) and the
agents-view incident notice (pa-tui'sincident_notices, TSsrc/modes/agents-view/incident-notices.ts) β pa-tui depends on
pa-types alone, and the TS product keeps the same single classifier incli/incident.tswith both halves importing it. Pure log-line parsing
(agent.jsonlrecords, per-daemon plain-text lines), worker pid
attribution, event classification, and the stall/burst/gap anomaly
computation β the message shapes are the TS regexes kept 1:1. The CLI's
log-file discovery,--since/--untilwindow parsing, and report
rendering belong to pa-cli; the notice polling, rotation-safe
incremental reads, and dismissal horizons belong to pa-tui.themes: the bundled theme definition files (prime/dark/light) as
pure data, shared by the TUI's theme loader (pa-tui renders terminal
colors from them) and the session HTML exporter (pa-core resolves them
into CSS variables). Everything built on top of the files β terminal
color rendering, export CSS generation, custom-theme discovery β belongs
to the consuming crates.daemon::update_flow: the update-flow state machine's shared vocabulary:
the coordinator FSM states + legal
transition table,UpdateId, the on-disk artifact schemas (intent.json,status.jsonβ TS status-file shape,prepared//{roster,marker}.json),
the roster-snapshot projection (sessions/workers/subagents/heartbeats,
durable session ids, heartbeat re-arm fields only), the timeout-budget
table withPRIME_AGENT_UPDATE_*_MSoverrides, and the artifact path
layout under<agent-dir>/update-restarts/. Pure serde + pure data
helpers; the FSM drivers and watchdogs are owned by pa-daemon/pa-cli. The
prepare/commit wire commands (prepare_update_restart/commit_update_restart) and the TS mutation classification
(is_daemon_mutating_command,is_update_drain_commandβ the
update-flow admission gate's read tables) live indaemon::{command,plane}.
Platform contracts (platform): the cross-crate transport, process-identity, socket-identity, and home-dir helpers, plus the suspend-to-background signal control (process::{stop_own_process_group, ignore_sigint_for_suspend, restore_default_sigint}: the TUI's TS-handleCtrlZcycle β SIGTSTP to the own process group, SIGINT ignored for the stopped window and restored on the SIGCONT resume; unsafe lives behind this wall because pa-tui opts into the workspaceunsafe_codeforbid) (platform::dirs::home_dir:HOME, then on WindowsUSERPROFILE/HOMEDRIVE+HOMEPATH- Nodeos.homedir()parity, returningNoneso each caller owns its fallback). pa-types is the only crate every transport consumer can depend on (pa-tui depends on pa-types alone), so the shared trait vocabulary and its cfg-gated platform implementations live here: AF_UNIX on Unix, named pipes (\.\pipe\,platform/windows_pipe.rs) on Windows. Remaining platform areas (process control, perms, ...) are implemented per platform behind the same traits, not re-plumbed in callers.
Non-goals
No provider logic, no session logic, no UI. Beyond pure data helpers and the platform contracts, no behavior: a domain type that wants a method belongs in the owning crate.
Public API
Everything in this crate is deliberately pub - it is the cross-crate contract. Unknown fields survive round-trips via catch-all maps so schema revisions stay compatible.
Depends on
serde, serde_json, thiserror, anyhow, tokio, regex (the incident classifier's TS log-message patterns; see the incident scope entry). No workspace crates.
Overview (`scripts/release/README.md`)
Release publication
Eligible main commits still produce unique beta versions and tags throughnightly.yml. release.yml retains the five-platform build/reuse routes and
publishes versioned installer payloads to R2. Only stable tags create individual
GitHub releases; beta tags refresh the single prerelease named nightly.
For future beta versions, the public R2 prefixreleases/v<version>/ retains the entire published payload: platform tarballs,
Linux debug decoders, the npm migration bridge, SHA256SUMS, merged manifest,
beta.json, and per-target SPDX SBOMs. Audit files upload before channel pointers
advance, even when a newer main commit has superseded the build. The existing
provenance attestation step continues to cover tarballs and decoders. The rolling
nightly release also carries the full current payload and checksums.
After a successful nightly asset publication, the same serialized refresh movesrefs/tags/nightly to manifest.json's verified build commit and updates the
release title/notes with the version, source commit, and refresh time. It never
uses a newly read main HEAD. Failed uploads leave the existing tag unchanged;
a failed tag/metadata update fails the job and can be repaired by rerunning the
same version. GitHub's original release publication date is not rewritten.
Installers and native/npm updaters keep using the R2 channel manifests and
versioned payload paths anonymously; they do not require per-beta GitHub release
objects. Consumers of /releases/download/v/... must use the public R2
versioned prefix for future beta builds, or nightly for its current payload.
Stable GitHub asset URLs retain their existing behavior.
No historical releases or assets are removed. Versioned nightly asset names
continue to accumulate; retention and historical cleanup require a separate
decision. The R2 channel comparator repair remains in PR #3452; this change only
hardens the independent rolling GitHub newest-wins guard.
python3 scripts/release/test_release_publication.py executes the workflow shell
against local GitHub/R2 fakes, including stale reruns, failed reads, complete
payloads, interrupted bootstrap, and superseded beta archival. It performs no
network requests or real publication.
Overview (`vendor/crossterm/README.md`)
Cross-platform Terminal Manipulation Library
Crossterm is a pure-rust, terminal manipulation library that makes it possible to write cross-platform text-based interfaces (see features). It supports all UNIX and Windows terminals down to Windows 7 (not all terminals are tested,
see Tested Terminals for more info).
Table of Contents
Features
- Cross-platform
- Multi-threaded (send, sync)
- Detailed documentation
- Few dependencies
- Full control over writing and flushing output buffer
- Is tty
- Cursor
- Move the cursor N times (up, down, left, right)
- Move to previous / next line
- Move to column
- Set/get the cursor position
- Store the cursor position and restore to it later
- Hide/show the cursor
- Enable/disable cursor blinking (not all terminals do support this feature)
- Styled output
- Foreground color (16 base colors)
- Background color (16 base colors)
- 256 (ANSI) color support (Windows 10 and UNIX only)
- RGB color support (Windows 10 and UNIX only)
- Text attributes like bold, italic, underscore, crossed, etc
- Terminal
- Clear (all lines, current line, from cursor down and up, until new line)
- Scroll up, down
- Set/get the terminal size
- Exit current process
- Alternate screen
- Raw screen
- Set terminal title
- Enable/disable line wrapping
- Event
- Input Events
- Mouse Events (press, release, position, button, drag)
- Terminal Resize Events
- Advanced modifier (SHIFT | ALT | CTRL) support for both mouse and key events and
- futures Stream (feature 'event-stream')
- Poll/read API
Tested Terminals
- Console Host
- Windows 10 (Pro)
- Windows 8.1 (N)
- Windows Terminal
- Windows 10 x86_64 (Enterprise)
- Windows 11 arm64 (Enterprise)
- Ubuntu Desktop Terminal
- Ubuntu 23.04 64-bit
- Ubuntu 17.10
- Pop!_OS ( Ubuntu ) 20.04
- (Arch, Manjaro) KDE Konsole
- (Arch, NixOS) Kitty
- Linux Mint
- (OpenSuse) Alacritty
- (Chrome OS) Crostini
- Apple
- macOS Monterey 12.7.1 (Intel-Chip)
- macOS Sonama 14.4 (M1 Max, Apple Silicon-Chip)
This crate supports all UNIX terminals and Windows terminals down to Windows 7; however, not all of the
terminals have been tested. If you have used this library for a terminal other than the above list without
issues, then feel free to add it to the above list - I really would appreciate it!
Getting Started
see the examples directory and documentation for more advanced examples.
Click to show Cargo.toml.
[dependencies]
crossterm = "0.27"use std::io::{stdout, Write};
use crossterm::{
execute,
style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor},
ExecutableCommand,
event,
};
fn main() -> std::io::Result<()> {
// using the macro
execute!(
stdout(),
SetForegroundColor(Color::Blue),
SetBackgroundColor(Color::Red),
Print("Styled text here."),
ResetColor
)?;
// or using functions
stdout()
.execute(SetForegroundColor(Color::Blue))?
.execute(SetBackgroundColor(Color::Red))?
.execute(Print("Styled text here."))?
.execute(ResetColor)?;
Ok(())
}Checkout this list with all possible commands.
Feature Flags
[dependencies.crossterm]
version = "0.27"
features = ["event-stream"]| Feature | Description |
|---|---|
event-stream |
futures::Stream producing Result<Event>. |
serde |
(De)serializing of events. |
events |
Reading input/system events (enabled by default) |
filedescriptor |
Use raw filedescriptor for all events rather then mio dependency |
To use crossterm as a very thin layer you can disable the events feature or use filedescriptor feature.
This can disable mio / signal-hook / signal-hook-mio dependencies.
Dependency Justification
| Dependency | Used for | Included |
|---|---|---|
bitflags |
KeyModifiers, those are differ based on input. |
always |
parking_lot |
locking RwLocks with a timeout, const mutexes. |
always |
libc |
UNIX terminal_size/raw modes/set_title and several other low level functionality. | optional (events feature), UNIX only |
Mio |
event readiness polling, waking up poller | optional (events feature), UNIX only |
signal-hook |
signal-hook is used to handle terminal resize SIGNAL with Mio. | optional (events feature),UNIX only |
winapi |
Used for low-level windows system calls which ANSI codes can't replace | windows only |
futures-core |
For async stream of events | only with event-stream feature flag |
serde |
serializing and deserializing of events | only with serde feature flag |
Other Resources
Used By
Contributing
We highly appreciate when anyone contributes to this crate. Before you do, please,
read the Contributing guidelines.
Authors
- Timon Post - Project Owner & creator
License
This project, crossterm and all its sub-crates: crossterm_screen, crossterm_cursor, crossterm_style,crossterm_input, crossterm_terminal, crossterm_winapi, crossterm_utils are licensed under the MIT
License - see the LICENSE file for details.
Overview (`scripts/release/native-compat/README.md`)
Native installer compatibility assets
Shipped TypeScript native installers validate these paths before activating a
release and whenever validating an installed release for rollback. Rust ships
the real historical assets to preserve that contract. They are not loaded by
Rust's renderer or image implementation.
theme/prime.jsonandexport-html/template.htmlare unmodified from
Prime Agent v0.9.8, commit7d442aafa985f9342134fac16c2ef41f03fb45c1, pathspackages/coding-agent/src/modes/interactive/theme/prime.jsonandpackages/coding-agent/src/core/export-html/template.html.photon_rs_bg.wasmandPHOTON-LICENSE.mdare unmodified from@silvia-odwyer/photon-node0.3.4, the dependency used by that release.
Source: https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz
Tarball SHA-256:5a23015c1cd2c38e3c492dd96929985247b92f52d2ff0fb948d29edca52bc50a.
Wasm SHA-256:10468181565c56004c867f3a4af96f89a0ef5a63a72f2b5fb12c1f1992a3615c.
The assembler also supplies package.json for every release and copies the
maintained install-rust.sh to install.sh. The old updater keeps executing
its existing installer; the downloaded installer is not a migration hook.
The bundled installer is available for manual Rust installation/repair and
uses the Rust installer's arguments and environment contract.
Keep these wire-level file names unchanged while supporting those installers.
Overview (`crates/pa-daemon/testdata/acp/README.md`)
ACP differential corpus (slice 1)
Captures of the installed TS binary (prime-agent --mode acp --no-session --provider prime-inference --model z-ai/glm-5.3-flash) speaking ACP over
stdio, one file per scenario. Every file is line-delimited JSON:
{"scenario": ..., "direction": "in"|"out", "frame": {jsonrpc frame}}out lines are what the scenario client sent; in lines are what the
binary answered, in order. The last line of each file is a meta row with
the process exit code and stderr.
Scenarios
happy_path: initialize, session/new (matching cwd), one text prompt,
session/close. Shows the full completion envelope: chunks, then theresponseBoundaryinfo update (terminalQuiescenceExpected: true,outcome), the quiescence event, theterminalQuiescenceenvelope, then
the{stopReason}response.cwd_mismatch: session/new with cwd/tmpwhile the process runs in
another directory. The result carries_meta.cwd {requested, actual}.errors: unknown-session prompt/close, a second session/new on a live
connection, an unknown method (-32601with the observed message
shape), close. Also demonstrates the one-initialize-per-connection
behavior of the served surface.second_initialize: a secondinitializeon the same connection is
served normally.cancel:session/cancelmid-turn. The prompt resolves{stopReason: "cancelled"}with no boundary frames after the streamed
chunks.tool_call: a turn that calls the ipython tool (6*7). Shows thetool_call/tool_call_updateshapes including thecontentwrapper
and the ipython rich-output_metaon results that carry attachments.compact_command: two turns, then/compact. The session is too short
to compact, so the observable parity is the namespacedcompaction: {}
update (the TScompaction_endwith an undefined result) followed by the
normal completion envelope andend_turn.goal_command:/goal <objective>then/goal status. Shows the
goal-start_meta.goalframe, mid-turn usage updates after each settled
message, the completion observation, and the status-prompt frame.autonomous_gate:--autonomous --autonomous-gate true. The gate passes
after the first turn: the completion and terminal envelopes carry the_meta.autonomousaccounting andremainingAutonomousContinuations
reflects the configured budget; the stop reason isend_turn.autonomous_limit:--autonomous --autonomous-max-tokens 1. The token
limit stops the run: the envelopes carry the final accounting and the
stop reason ismax_tokens.mcp_stdio:session/newwith two valid servers (one stdio with a
literal environment entry, one http with a header) plus one
schema-invalid entry (missing the requiredenvarray): the ACP SDK
zod filter (vecSkipError(zMcpServer)) silently drops it, admission
succeeds with the survivors, and close releases them.mcp_replace: admission, close, then a replacement admission with a
different server list β the owner-scoped clear/re-release cycle.mcp_errors: the full resolve-stage rejection matrix, every entry
schema-valid: bad name pattern, duplicate name, NUL in the stdio
command, duplicate environment entry (case-sensitive), duplicate HTTP
header (case-insensitive, the reason reports the second casing),
unsupported sse transport, embedded URL credentials (all-32602with
the exact TSreasonpayloads), then a name that passes the 64-char
admission pattern but fails the 48-char tool-name pattern
(-32603withInvalid ACP MCP server name: ...details), then a
valid admission and close. Failed admissions keep the connection
serving.
The Rust captures in this directory
The rust-*.jsonl files are the evidence run for this slice: the same
scenario client against the Rust build with the same provider/model
(z-ai/glm-5.3-flash) and the kernel sidecar resolved throughPI_PACKAGE_DIR (a cargo-built binary has no sidecar next to the exe).
Since slice 4 the Rust ACP mode serves over the daemon-attached transport
(the TS shouldUseDaemonClient behavior): the binary spawns a supervisor
on the sandboxed --daemon-socket, hosts a client-owned daemon session,
and streams its session events as ACP updates. The daemon-attached
captures are: happy_path, cwd_mismatch, errors, second_initialize,
tool_call, compact_command, mcp_stdio, mcp_replace, mcp_errors β all
MATCH in full mode. cancel, goal_command, autonomous_limit, andautonomous_gate were captured on the since-removed in-process
transport; recapture is pending (the files stay):
cancel: the capture matched in full mode; the daemon-attached run
resolves the same{stopReason: "cancelled"}shape with no boundary
frames, but a fresh worker's first-token latency exceeds the
scenario's 2s cancel window, so the pre-cancel chunk count differs
per run. The daemon-attached cancel semantics are locked by the
deterministic e2e (acp_daemon_attached_cancels_mid_turn).goal_command/autonomous_limit/autonomous_gate: the captures
predate the daemon'sgoal_update/ autonomous-accounting producers;
recapture is pending.
Comparing a Rust run
compare_differential.py normalizes both captures (volatile ids, version,
model-generated text, sequence numbers) and compares the frame shape
sequences field by field. mcpCapabilities is compared exactly: both
binaries advertise { "http": true }:
python3 compare_differential.py ts-happy_path.jsonl rust-happy_path.jsonlBoth captures must come from the same scenario client with the same
request order. crates/pa-cli/tests/acp_mode_e2e.rs locks the deterministic
scenarios offline over the daemon-attached transport (the scripted faux
worker); the network-dependent scenarios (tool_call, cancel mid-turn) are
verified by running the scenario client against both binaries on a
networked box.
--landmarks drops model-behavior-dependent work frames (tool calls,
chunk streams, and mid-turn goal-usage updates) and compares the protocol
envelopes only. Use it for scenarios where the two model runs legally
diverge in their tool usage (goal_command: one run answered directly,
the other ran several ipython cells; autonomous_limit: one run wrote
the essay with tool calls, the other without β both sequences are
correct protocol behavior). The goal and autonomous frames themselves are
locked field-by-field by the offline e2e tests. All other scenarios
(happy_path, cwd_mismatch, errors, second_initialize, tool_call,
compact_command, cancel, autonomous_gate, mcp_stdio, mcp_replace,
mcp_errors) match in full mode.
Daily Stats (`docs/daily-stats.md`)
Daily Prime Agent stats
The GitHub Actions workflow Prime Agent daily stats prepares a report at 9 AM
America/Los_Angeles, including daylight saving changes. GitHub may start scheduled
runs late. The report covers the previous completed UTC day, matching the
Prime Agent Overview dashboard.
It reports DAU, WAU, MAU, token usage, completed runs, and run success rate. Daily
values compare with yesterday and the same weekday last week. WAU compares with
the preceding seven-day window, and MAU with the preceding 30-day window. Success
rate changes use percentage points. A zero baseline shows new (baseline 0);
no completed runs gives an unavailable success rate.
Active users count anonymous installations that emitted agent run completed,
including successful, failed, and aborted runs. They do not count verified people.
Tokens sum total_tokens, including recorded cache usage; they do not measure
spend. Success rate divides runs with outcome=success by all completed runs.
The query excludes PostHog test accounts and version 0.0.0-benchmark.
Setup and activation
- Add repository secret
POSTHOG_PERSONAL_API_KEY: a
PostHog personal API key with
read access to queries and insights, limited to project 22174. A project capture
token cannot read analytics. The script uses the EU
query endpoint. - Set repository secret
SLACK_STATS_WEBHOOK_URLto the engineering bot's incoming
webhook for#research-19-prime-agent. The webhook chooses the channel and sender. - Merge the workflow to
main. Run it manually with publish unchecked to
validate access without sending a message. Logs show validation status only. - After reviewing a local report, set repository variable
PRIME_AGENT_STATS_ENABLED=trueto enable daily delivery. Keep this variable
unset orfalseto stop delivery. Manual sends also require this variable,
publish checked, and themainbranch.
Neither Codex nor n8n runs the scheduled report. An Actions preview does not receive
the Slack secret. Metric values never enter job logs, summaries, or uploaded artifacts.
For a local preview, supply the PostHog key through the environment and run:
python3 scripts/daily_stats.py prepare --output /tmp/prime-agent-stats.jsonRead the JSON file locally. Do not commit or upload it: it contains business metrics.
An existing query response can be replayed without network access:
python3 scripts/daily_stats.py prepare --date YYYY-MM-DD \
--response-file /tmp/posthog-response.json --output /tmp/prime-agent-stats.jsonDelivery and recovery
The workflow serializes delivery attempts and saves an artifact namedprime-agent-stats-YYYY-MM-DD before contacting Slack. It contains only the
report date and workflow run ID, retained for 90 days. Manual report dates are
limited to completed UTC days within the last 60 days. A rerun with an existing
artifact skips delivery, including a timeout whose delivery outcome is unknown.
Failed preparation creates no delivery artifact and can be rerun safely.
An interruption after saving the artifact can leave a report unsent. Check the
channel and run first. If the report was never delivered, delete that date's
artifact in GitHub Actions before rerunning with publish enabled. Do not remove
the marker for a delivered report. The script never automatically retries Slack.
Incoming webhooks cannot delete messages; deletion requires the owning bot or a
Slack administrator.
Run the synthetic tests without credentials or network access:
python3 -m unittest discover -s scripts -p test_daily_stats.py -vLegacy Release Inventory.Json (`docs/legacy-release-inventory.json`)
{
"retrieved_at": "2026-10-09",
"github_release_pages": 2,
"github_release_count": 107,
"stable_count": 18,
"releases": [
{
"tag": "v0.9.8",
"target_commitish": "7d442aafa985f9342134fac16c2ef41f03fb45c1",
"published_at": "2026-09-29T22:06:42Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.8",
"npm_assets": [
{
"name": "prime-agent-0.9.8.tgz",
"digest": "sha256:d7b72785119efc28bfbca8ec4a7f47a1fcdcf47fcd8cebdb60bffaa79e3e1274",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.8/prime-agent-0.9.8.tgz"
}
]
},
{
"tag": "beta",
"target_commitish": "7d442aafa985f9342134fac16c2ef41f03fb45c1",
"published_at": "2026-07-17T23:04:57Z",
"prerelease": true,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/beta",
"npm_assets": [
{
"name": "prime-agent-0.9.8-beta.2286.1.7d442aa.tgz",
"digest": "sha256:7a311e2d7fd45c0b9f134197bb3101ebc11233c6c2bf649c4d574a0d98d3473b",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/beta/prime-agent-0.9.8-beta.2286.1.7d442aa.tgz"
}
]
},
{
"tag": "v0.9.7",
"target_commitish": "08ff1b2e2794ea9e8f4a08d12bc95408a66e1074",
"published_at": "2026-09-28T23:13:49Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.7",
"npm_assets": [
{
"name": "prime-agent-0.9.7.tgz",
"digest": "sha256:d696f2636cd7780d2d67296142bc1d13d72960c8be4e5cfae0bbba82f9d8707d",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.7/prime-agent-0.9.7.tgz"
}
]
},
{
"tag": "v0.9.6",
"target_commitish": "e260085dd8f742e0def3d871860c9a888b114851",
"published_at": "2026-09-24T01:01:53Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.6",
"npm_assets": [
{
"name": "prime-agent-0.9.6.tgz",
"digest": "sha256:e5bf0e349e55b3f75c79e66006c993b10c51ed1c9bf15863b06658fcaf0232b2",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.6/prime-agent-0.9.6.tgz"
}
]
},
{
"tag": "releases/v0.9.1-fhcache1",
"target_commitish": "da819d32f6a3f57e71f5d5fb784a4958929dbdae",
"published_at": "2026-09-20T03:45:42Z",
"prerelease": true,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/releases/v0.9.1-fhcache1",
"npm_assets": [
{
"name": "prime-agent-0.9.1-fhcache1.tgz",
"digest": "sha256:4a461bf7fc9d8e6e4e61471b66341aff05ba94e02c6c25f678851d26d1b27d2c",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/releases/v0.9.1-fhcache1/prime-agent-0.9.1-fhcache1.tgz"
}
]
},
{
"tag": "fhcache-candidate-1",
"target_commitish": "da819d32f6a3f57e71f5d5fb784a4958929dbdae",
"published_at": "2026-09-20T03:33:40Z",
"prerelease": true,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/fhcache-candidate-1",
"npm_assets": [
{
"name": "prime-agent-0.9.1-fhcache1.tgz",
"digest": "sha256:4a461bf7fc9d8e6e4e61471b66341aff05ba94e02c6c25f678851d26d1b27d2c",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/fhcache-candidate-1/prime-agent-0.9.1-fhcache1.tgz"
}
]
},
{
"tag": "v0.9.5",
"target_commitish": "a7d791bc1be09793ed5f3ec05bf4cccbc60679ea",
"published_at": "2026-09-16T00:13:49Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.5",
"npm_assets": [
{
"name": "prime-agent-0.9.5.tgz",
"digest": "sha256:349f1682c7909550842f1b04a71ba95814341b136474ade736df93f8ec006876",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.5/prime-agent-0.9.5.tgz"
}
]
},
{
"tag": "v0.9.4",
"target_commitish": "f771dfcedd684d1afff84ca2c6fa95c7a21efbc2",
"published_at": "2026-09-08T23:18:05Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.4",
"npm_assets": [
{
"name": "prime-agent-0.9.4.tgz",
"digest": "sha256:b8d752a53d11a8c9a7580e1fb5fc24f7ce74ccad979c7e6e6aa8880fc3ad90b0",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.4/prime-agent-0.9.4.tgz"
}
]
},
{
"tag": "v0.9.3",
"target_commitish": "915c78f42c248b08238dd27fcd4bcab32c60beab",
"published_at": "2026-09-06T10:44:04Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.3",
"npm_assets": [
{
"name": "prime-agent-0.9.3.tgz",
"digest": "sha256:ce71049389877770aa31b9be64c473685a86159adbbd2466bd43e4a9113242f1",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.3/prime-agent-0.9.3.tgz"
}
]
},
{
"tag": "v0.9.2",
"target_commitish": "9c54a35dac3a2ad17910074d66664859ea175666",
"published_at": "2026-09-05T19:14:39Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.2",
"npm_assets": [
{
"name": "prime-agent-0.9.2.tgz",
"digest": "sha256:d649b9f0258c77de7d02aba33d786925ce41a36b601e8e24eef0f00bdc2f1f49",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.2/prime-agent-0.9.2.tgz"
}
]
},
{
"tag": "v0.9.1",
"target_commitish": "81ae3cb34d27d38ee37f9e205a1e73694993b344",
"published_at": "2026-09-01T18:54:36Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.1",
"npm_assets": [
{
"name": "prime-agent-0.9.1.tgz",
"digest": "sha256:573bce0cd004fc62052e9a924089941b7f39266ab71e66a94c85a1f9d35835ba",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.1/prime-agent-0.9.1.tgz"
}
]
},
{
"tag": "v0.9.0",
"target_commitish": "c394506e2f0dd887b3f94908da9f2910b43c846b",
"published_at": "2026-09-01T18:18:51Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.0",
"npm_assets": [
{
"name": "prime-agent-0.9.0.tgz",
"digest": "sha256:fc560ec74195297999405d4fbfd0d308598cf502e0fccef7f02eb60032213108",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.0/prime-agent-0.9.0.tgz"
}
]
},
{
"tag": "v0.8.1",
"target_commitish": "514633727bf26d74f39f3119c2b0e31a5ceb2a9d",
"published_at": "2026-08-26T05:14:48Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.8.1",
"npm_assets": [
{
"name": "prime-agent-0.8.1.tgz",
"digest": "sha256:46c24db1782dd31adc35d5c6cbcc75564faba6ced3bf2ccf03d836ee77134475",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.8.1/prime-agent-0.8.1.tgz"
}
]
},
{
"tag": "v0.8.0",
"target_commitish": "8d7deeab5861bf9d77bde3d8511046a5c799818d",
"published_at": "2026-08-21T21:18:10Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.8.0",
"npm_assets": [
{
"name": "prime-agent-0.8.0.tgz",
"digest": "sha256:f5b0093c7e0fddb73f94773d74383585456adfa84f12a4082d3098f23bb8fab6",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.8.0/prime-agent-0.8.0.tgz"
}
]
},
{
"tag": "v0.7.4",
"target_commitish": "af0b8e00b9f704e834787fd321065ca78281f2aa",
"published_at": "2026-08-19T23:44:42Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.7.4",
"npm_assets": [
{
"name": "prime-agent-0.7.4.tgz",
"digest": "sha256:dea045d7c170466a102e030e940f459cc13f2c113465f40be1c7505d80405d2e",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.7.4/prime-agent-0.7.4.tgz"
}
]
},
{
"tag": "v0.7.3",
"target_commitish": "61131b2d195ba7a67a4ce8ac60bb10cecae07b67",
"published_at": "2026-08-17T22:38:17Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.7.3",
"npm_assets": [
{
"name": "prime-agent-0.7.3.tgz",
"digest": "sha256:2a188738318b91ff91ea77b8a0b215c41543cf0483fcb18a53475dc4e7032784",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.7.3/prime-agent-0.7.3.tgz"
}
]
},
{
"tag": "v0.7.2",
"target_commitish": "83a0f9f9566219551fcb6ffaf7f519a815749a58",
"published_at": "2026-08-11T19:00:48Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.7.2",
"npm_assets": [
{
"name": "prime-agent-0.7.2.tgz",
"digest": "sha256:bc5471f2a626d727b88a45eb745fff93b10c554a3c4fc5912f25d8c64b987f5e",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.7.2/prime-agent-0.7.2.tgz"
}
]
},
{
"tag": "v0.7.1",
"target_commitish": "95afd319a78ae017a41241d50b013d656a0685ce",
"published_at": "2026-08-07T18:39:08Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.7.1",
"npm_assets": [
{
"name": "prime-agent-0.7.1.tgz",
"digest": "sha256:d68612c83239caafab72cc76c55ac572bfd07a059ea8fbd2a3ddbe1f2b55dcdb",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.7.1/prime-agent-0.7.1.tgz"
}
]
},
{
"tag": "v0.7.0",
"target_commitish": "be9e2fa0714e7cd1c6bd9bdb1b554d2cc6550387",
"published_at": "2026-08-05T18:41:37Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.7.0",
"npm_assets": [
{
"name": "prime-agent-0.7.0.tgz",
"digest": "sha256:88b6578518c72cd51a825bc80f28e0fef9a64c67de4a7d6fd7afd7ca1b34da0b",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.7.0/prime-agent-0.7.0.tgz"
}
]
},
{
"tag": "v0.6.1",
"target_commitish": "8bd7c18f16bfdc356c1cd20fb9fcf01119147cda",
"published_at": "2026-08-05T14:55:26Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.6.1",
"npm_assets": [
{
"name": "prime-agent-0.6.1.tgz",
"digest": "sha256:4c7150dbfe5807c8480676d20c3104e63045b8ca2463abf38f021062e9fcacef",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.6.1/prime-agent-0.6.1.tgz"
}
]
},
{
"tag": "v0.6.0",
"target_commitish": "7db7b69c60be0f7b271faf948864891813b27182",
"published_at": "2026-08-05T04:36:20Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.6.0",
"npm_assets": [
{
"name": "prime-agent-0.6.0.tgz",
"digest": "sha256:8ef229a0422398174ba8f0ba4f7101adfc614b023a8f819f6ef3920e5d2350c6",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.6.0/prime-agent-0.6.0.tgz"
}
]
}
],
"support_scope": "Prime Agent 0.6.0 onward",
"rust_beta_tags": [
"v0.9.9-beta.10",
"v0.9.9-beta.4",
"v0.9.9-beta.6",
"v0.9.9-beta.7",
"v0.9.9-beta.8",
"v0.9.9-beta.9",
"v0.9.9-beta.11",
"v0.9.9-beta.17",
"v0.9.9-beta.18",
"v0.9.9-beta.21",
"v0.9.9-beta.22",
"v0.9.9-beta.23",
"v0.9.9-beta.24",
"v0.9.9-beta.25",
"v0.9.9-beta.26",
"v0.9.9-beta.27",
"v0.9.9-beta.28",
"v0.9.9-beta.29",
"v0.9.9-beta.30",
"v0.9.9-beta.31",
"v0.9.9-beta.32",
"v0.9.9-beta.33",
"v0.9.9-beta.34",
"v0.9.9-beta.35",
"v0.9.9-beta.36",
"v0.9.9-beta.37",
"v0.9.9-beta.38",
"v0.9.9-beta.39",
"v0.9.9-beta.40",
"v0.9.9-beta.41",
"v0.9.9-beta.42",
"v0.9.9-beta.43",
"v0.9.9-beta.44",
"v0.9.9-beta.45",
"v0.9.9-beta.46",
"v0.9.9-beta.47",
"v0.9.9-beta.48",
"v0.9.9-beta.49",
"v0.9.9-beta.50",
"v0.9.9-beta.51",
"v0.9.9-beta.52",
"v0.9.9-beta.53",
"v0.9.9-beta.54",
"v0.9.9-beta.55",
"v0.9.9-beta.56",
"v0.9.9-beta.57",
"v0.9.9-beta.58",
"v0.9.9-beta.59",
"v0.9.9-beta.60"
]
}
Legacy Upgrade Contract (`docs/legacy-upgrade-contract.md`)
Legacy TypeScript upgrade contract
This audit covers published Prime Agent releases from v0.6.0 onward, including
18 stable TypeScript releases through v0.9.8 and relevant candidate and beta
artifacts. Earlier versions are excluded from the requested support scope.
Inventory and source comparison are not evidence that an installed-artifact
upgrade or every supported platform has passed testing.
Published inventory in scope
A full two-page GitHub Releases API query on 2026-10-09 returned 107 releases.
The compact release inventory records the 18
stable releases in scope, independent GitHub npm-asset digests, source targets,
and relevant TS prereleases. The stable versions are 0.6.0β0.6.1,
0.7.0β0.7.4, 0.8.0β0.8.1, and 0.9.0β0.9.8.
All 18 have genuine published prime-agent-VERSION.tgz assets; this inventory
does not count inherited upstream Git tags as Prime releases. The unscoped
public npm registry endpoint for prime-agent returned 404 during this audit;
these npm packages are published as tarballs through GitHub and the release bucket.
Additional published TS artifacts are 0.9.1-fhcache1 (the same digest hosted by
both fhcache-candidate-1 and releases/v0.9.1-fhcache1) and the rolling beta
release, currently 0.9.8-beta.2286.1.7d442aa. The latter has npm and native
archives. The fhcache package digest, independently supplied by the GitHub API,
is 4a461bf7fc9d8e6e4e61471b66341aff05ba94e02c6c25f678851d26d1b27d2c.
It has no published SHA256SUMS file. A locally computed digest alone is not
independent download verification.
The 49 versioned Rust 0.9.9-beta.* releases are distinct from TS migration
fixtures and require Rust-to-Rust update coverage. Their exact published tags
are recorded in the inventory. The nightly release is a rolling Rust asset host.
Historical rolling TS beta assets are overwritten on GitHub; this inventory
cannot establish an exhaustive list of every beta ever distributed through the
bucket. Do not equate an absent GitHub beta tag with an unpublished build.
Authoritative release sources
Use the GitHub release target_commitish below for source comparisons of the
audited versions, then verify behavior against the actual published artifacts.
Several local version tags resolve to upstream history with different package
names and no Prime Agent updater. Those tags are not suitable fixtures for these
releases. A recorded source target alone does not prove the bytes shipped; release
assets remain the authoritative end-to-end test inputs.
| Release | GitHub release target commit |
|---|---|
| v0.6.0 | 7db7b69c60be0f7b271faf948864891813b27182 |
| v0.6.1 | 8bd7c18f16bfdc356c1cd20fb9fcf01119147cda |
| v0.7.0 | be9e2fa0714e7cd1c6bd9bdb1b554d2cc6550387 |
| v0.7.1 | 95afd319a78ae017a41241d50b013d656a0685ce |
| v0.7.2 | 83a0f9f9566219551fcb6ffaf7f519a815749a58 |
| v0.7.3 | 61131b2d195ba7a67a4ce8ac60bb10cecae07b67 |
| v0.7.4 | af0b8e00b9f704e834787fd321065ca78281f2aa |
| v0.8.0 | 8d7deeab5861bf9d77bde3d8511046a5c799818d |
| v0.8.1 | 514633727bf26d74f39f3119c2b0e31a5ceb2a9d |
| v0.9.0 | c394506e2f0dd887b3f94908da9f2910b43c846b |
| v0.9.1 | 81ae3cb34d27d38ee37f9e205a1e73694993b344 |
| v0.9.2 | 9c54a35dac3a2ad17910074d66664859ea175666 |
| v0.9.3 | 915c78f42c248b08238dd27fcd4bcab32c60beab |
| v0.9.4 | f771dfcedd684d1afff84ca2c6fa95c7a21efbc2 |
| v0.9.5 | a7d791bc1be09793ed5f3ec05bf4cccbc60679ea |
| v0.9.6 | e260085dd8f742e0def3d871860c9a888b114851 |
| v0.9.7 | 08ff1b2e2794ea9e8f4a08d12bc95408a66e1074 |
| v0.9.8 | 7d442aafa985f9342134fac16c2ef41f03fb45c1 |
Update families
- v0.6.0βv0.9.4 ship npm tarballs. Their
packages/coding-agent/src/utils/version-check.tsfiles are byte-identical:
SHA-2565a87851fc4f6473452607cb2816945e82f37e35f3aa2ecc846b3d41f1932aa29. - v0.9.5βv0.9.8 additionally ship managed native archives. Their manifest parsers
are byte-identical: SHA-2562a37f5116a6d855da564bb3efe4a6b62eb0d0a35523c369796b5529379dacff0.
Theircli/native-update.tsfiles are also byte-identical: SHA-256707cdc796f035f01c08647c026a1f1a5aaeafbb3b4158a22e741b367a8d10f03.
All stable clients read latest.json under PRIME_AGENT_DOWNLOAD_BASE_URL, or
the default release bucket. Beta clients read beta.json. The parser acceptsversion, package (or packageName), and tarball; relative tarball URLs
resolve against the release bucket. Keep the npm tarball in the manifest even
when native artifacts are present.
npm bridge requirements
The published package builder rewrites the source package to prime-agent
and the command to prime-agent, with entrypoint dist/bundle/cli.js.
Do not infer the published package identity from the source package.json.
The existing updater runs the detected global package manager against the
manifest tarball. Supported command builders include npm, pnpm, yarn, and Bun;
that source support is not a claim that each has been integration-tested.
Keep the package name and dist/bundle/cli.js anddist/bundle/cli-node.js paths stable. The bridge also retains a thindist/cli.js alias; that compatibility alias does not expand the tested version
scope. In published v0.9.5βv0.9.8 npm
artifacts, the packer moves the TS bundle to cli-node.js and puts the old
native-migration shim at cli.js. When that shim falls back to Node, the
updater's original entrypoint is cli-node.js. After package replacement the
old CLI and TUI execute their original Node executable with that original
absolute JavaScript entrypoint path. Both paths must run the new Rust bridge;
a new command shim elsewhere does not repair this relaunch. The bridge must forward arguments, signals, terminal
I/O, and exit codes and remain usable when package-manager install scripts are
disabled. It must not remove its own package while the old process still needs
to relaunch through it.
Failed migration and retry
The bridge includes the checksum-pinned, genuine v0.9.8 TypeScript package as
recovery. A host that cannot execute Rust must retain a usable public command,
daemon, worker, and saved sessions. Explicit updates report failure; ordinary
launches use recovery without repeatedly attempting the same failed install.
For bridge-driven channel installs, the native executable must pass the bounded
staged version probe before the installer replaces the public launcher. A valid
archive checksum alone does not establish that the host can execute its payload.
An explicit retry after failure must consult the current release through the
preserved updater, including downloading its updated bridge and installer.
Retrying only the failed bridge's pinned version would prevent a later release
from adding support for that host. The retry may report success only after a
working native version and its corresponding migration receipt are verified.
Managed native requirements
The v0.9.5βv0.9.8 updater executes the installer bundled in the old release.
Replacing only the new archive's install.sh cannot affect that installation.
The old installer downloadsreleases/vVERSION/prime-agent-VERSION-PLATFORM.tar.gz, checks its checksum,
and requires these regular files at the archive root:
prime-agentpackage.jsonwith the target versioninstall.shprime-agent-runtime/pyproject.tomlprime-agent-runtime/src/rlm/repl.pytheme/prime.jsonexport-html/template.htmlphoton_rs_bg.wasm
It requires prime-agent --version to print the bare target version andprime-agent --help to succeed. It then installs toreleases/VERSION-PLATFORM-SHA256[.SUFFIX]/, writes .archive-sha256 and.install-source, and atomically changes bin/prime-agent. The managed-root
marker is prime-agent-native-v1.
These same legacy files are revalidated by the still-running TS client before
it can find the new launcher for restart. They must remain present after
activation, including the two metadata files written by the old installer.
The next Rust update must recognize this managed installation layout.
The manifest parser prefers binariesV2 over binaries; each accepted entry
must have an exact platform-specific filename and lowercase 64-digit SHA-256.
Retain the legacy platforms in the manifest only when the corresponding
artifacts can actually run on those hosts.
Daemon and TUI handoff
All audited versions launch the newly installed entrypoint with:
update --internal-update-restart-coordinator --daemon-socket SOCKET
--internal-update-restart-status STATUS_PATH
[--internal-update-restart-origin SESSION_ID]The TS parent creates the status directory but does not create a staged
status file. The coordinator must initialize it. TS polls a version-1 JSON
record with requestId, socketPath, phase, coordinator.pid,counts: {total, restored, resumed, failed}, startedAt, and updatedAt.
Terminal phases are complete, skipped, and failed; other phases includestarting, preparing, stopping, starting_daemon, and restoring.heartbeatAt, predecessor/successor identities, failures, and message are
optional. Keep progress live during a long restore.
This differs from the newer Rust staged-update FSM and its status schema.
Recognizing the flag alone is insufficient: a legacy adapter must initialize
and maintain TS-compatible status and use the TS daemon'sprepare_update_restart / shutdown / session restoration contract. The parent
otherwise reports a restart warning even if installation succeeded.
For /update, the old TUI runs the update subprocess withPRIME_AGENT_INTERACTIVE_SELF_UPDATE=1, disposes its old connection, launches
the coordinator itself, and relaunches with its saved session file. Do not
restart the daemon prematurely from the installer or an executable probe.
The TS daemon persists its restart manifest before replying to preparation.
A lost reply therefore does not prove that preparation failed. Record a durable
binding to the predecessor, socket, target release, and attempt before sending
the request; recover a fresh scoped TS manifest only through that binding.
Retain pending source manifests through ordinary startup cleanup until their
state has been adopted. A definitive preparation refusal with no new manifest
must allow another attempt. Queued actions must remain paused while the resumed
continuation is admitted, so follow-up input cannot run ahead of that turn.
Platform limits and release claims
The old native distribution supports Darwin arm64/x64 and Linux arm64/x64,
including linux-arm64-musl, linux-x64-baseline, linux-x64-musl, andlinux-x64-musl-baseline. The Rust release matrix currently ships GNU Linux
builds with a GLIBC 2.35 ceiling and no musl target. A GNU archive must never be
renamed to a musl suffix: that would turn a controlled refusal into a broken
installation. Supporting Alpine/musl requires real musl artifacts and runtime
verification. Older glibc hosts likewise need a compatible build or a clear
unsupported-platform result before activation.
The TS baseline suffix selects x64 CPUs without AVX2. Rust can reuse its
ordinary x64 artifact for that alias only after verifying its target CPU and
all bundled native dependencies work without AVX2; the absence of a separate
Rust baseline target is not by itself proof.
βAll versions since 0.6β describes the source-version range, not an exemption
from platform support. Do not claim every prior platform migrates until those
platform gates pass. Test all 18 published npm releases, all four native source
releases, CLI and TUI entrypoints, session preservation, daemon handoff, and a
second Rust update using assembled artifacts.
Legacy Upgrade Evidence (`docs/legacy-upgrade-evidence.md`)
Legacy update compatibility evidence
This is unreleased-candidate evidence, not approval to publish v1. All builds
and runtime tests after the remote-only instruction ran in Prime sandboxes.
Support scope starts at 0.6.0; no earlier versions are required.
Current verification status
The recovery-hardening snapshot passed the full repository gate, but its real
TS /update rerun exposed a request-ID collision after reconnecting: TS replayed
the preparation response instead of executing shutdown. The follow-up uses
distinct preparation and shutdown envelope IDs. A deliberate collision fails
the regression; the fix passes all six coordinator tests. Full gates and actual
migrations for this follow-up remain pending. Earlier results below are not
attributed to the latest source.
The consolidated full run encountered ETXTBSY (text file busy) in the existingupdate::download::tests::stages_a_local_payload_directory test. A diagnostic
single-test run and the complete core library suite then passed (1,160 passed,
3 ignored), without product changes or retry logic. The cause is unproven; the
original failure is retained in make-check-hardened-etxtbsy-red.log. One fresh
full run passed; this does not by itself establish that the failure is fixed.
| Check | Verified result |
|---|---|
| npm CLI and TUI migration, all 18 stable TS versions | 36/36 on optimized candidate before final recovery hardening |
| Native CLI and TUI migration, TS 0.9.5β0.9.8 | 8/8 on that optimized candidate |
| Subsequent Rust 1.0.0 β 1.0.1 update | Passed through the original custom public command |
| Fresh curl installation and reinstallation | Passed with final staged-probe installer |
| Unusable native executable | Red reproduced; fixed installer retained usable TS recovery |
| Failed migration followed by a newer release | Original update command reached Rust 1.0.1 |
| Lost prepare response and retained queued work | Six focused coordinator tests passed; red-first regression reproduced |
| Startup cleanup preserves pending recovery manifests | Two focused tests passed |
| Busy-session continuation before follow-up input | Real worker regression reproduced red; 19 focused restore tests passed |
| npm bridge unit suite | 25 passed, including IPC, fd3, signals, retry, and prefix behavior |
| Packaging suites | 39 passed: native compatibility, restamping, decoder, catalog |
| Consolidated full repository gate | fmt, clippy, 5,206 tests passed (19 ignored), release build passed |
Published-version matrix
The npm runs use unchanged, checksum-verified published packages and their real
public CLI/TUI entrypoints. Every version below passed both commands.
| Versions | CLI cases | TUI cases |
|---|---|---|
| 0.6.0, 0.6.1 | 2 | 2 |
| 0.7.0β0.7.4 | 5 | 5 |
| 0.8.0, 0.8.1 | 2 | 2 |
| 0.9.0β0.9.8 | 9 | 9 |
Native installs additionally cover every stable native TS release,
0.9.5β0.9.8, using each archive's unchanged old installer.
The TUI runs require completed restoration, the original saved session attached
to a Rust worker, a preserved transcript prefix, responsive input, and no
restart warnings. CLI checks include version/help, checksum refusal, state
preservation, and paths with spaces.
The optimized Linux x64 snapshot tested before final recovery hardening:
- Rust 1.0.0 archive:
7a5e663b535d1755504f8315d4f13de0857ed746ac5c6fdc6c5e55db84ec11b3. - Rust 1.0.1 archive:
f46a36e1456dc6d7ddfefaf85bad68b29a0b06d863cb1577ce5e3445d4cad97a. - Shipped executable:
0dbfb29af17614870f613017de2c6a11b25aa80572390a61dbe4d42c36335227.
Independent source archive digests and the exact version inventory live in
legacy-release-inventory.json.
The compatibility contract describes the layouts,
wire fields, and required recovery behavior.
Failure and usability evidence
Unusable executable: the actual TS 0.6.0 updater installed a checksum-valid
archive whose native executable named a nonexistent interpreter. With the
old probe condition, the npm launcher was replaced and the command exited 127.
With the fix, the staged probe refused activation, retained the npm symlink,
wrote no success receipt, and left a genuine TS 0.9.8 daemon/worker able to
resume the original session and accept input. The npm/native prefixes overlapped.
Future retry: after an unsupported architecture caused migration to fail,
the feed advanced from 1.0.0 to 1.0.1 and host support was restored. The original
public update command fetched the new bridge and installer, activated Rust
1.0.1, and resumed the saved session. An explicit retry that remains unsupported
must still fail, despite the old TS updater's warning-only success exit.
Lost response: a Unix-socket protocol fixture writes a TS-shaped manifest
containing queued work, then drops the prepare reply. The Rust coordinator
recovers it through its durable predecessor/socket/attempt binding before
shutdown. Disabling recovery makes the regression fail. Tests reject foreign
owners and stale data and permit retry after a definitive preparation refusal.
Startup cleanup preserves pending source manifests until adoption.
Busy queue: a real scheduled Rust worker with a scripted engine proves that
restored follow-up input stays parked until continuation admission. Disabling
the input pause consumes the queue too early; restoring it passes.
Usability: separate real TS 0.6.0 npm and TS 0.9.8 native handoffs execute
and persist a controlled Bash command after restoration, preserving prior
transcript bytes. No provider inference or IPython execution was tested.
The genuine fallback is the complete published TS 0.9.8 package, pinned to
SHA-256 d7b72785119efc28bfbca8ec4a7f47a1fcdcf47fcd8cebdb60bffaa79e3e1274.
The final staged-probe installer tested here is323328cea0ede4d60aedfef486ba383ea081457e65ccedb1782ca6a0575a7f09.
Reproduction and retained reports
Run these only in an isolated test host, with temporary homes and no real
credentials. Harnesses accept candidate archives and report paths; use their--help for the published source archive/checksum/prefix arguments.
| Harness | Purpose |
|---|---|
scripts/release/verify_legacy_release.py |
Download and verify a genuine native TS release, then exercise its updater |
scripts/release/test_legacy_upgrade.py |
Native CLI, checksum refusal, custom paths, second Rust update |
scripts/release/test_legacy_npm_upgrade.py |
Genuine npm CLI migration |
scripts/release/test_legacy_tui_upgrade.py |
Actual npm/native TUI update with saved-session restoration |
scripts/release/test_legacy_npm_fallback.py |
Unsupported host, unusable payload, and later-release recovery |
scripts/release/test_legacy_runtime_restart.py |
Real post-migration worker command execution |
scripts/release/test_channel_install.py |
Current curl installer, reinstall, and user-file preservation |
Reports, hashes, TUI captures, and logs were downloaded without agent binaries
to /tmp/prime-legacy-artifacts/ in the maintainer workspace. Key reports:native-upgrade-evidence-optimized.tar.gz, native-tui-optimized-0.9.{5,6,7,8}.json,channel-install-probe-final.json, probe-red.json, probe-green.json,future-release-recovery.json, legacy-recovery-red-green.tar.gz, andmake-check-hardened.log. Matrix bundles contain each case's exact archive and
bridge hashes. Intermediate evidence remains in Git history andevidence-history-before-condensing.md beside the raw reports.
Remaining release gates and limits
- Complete full gates and the migration matrix against the corrected optimized artifact.
- Verify production artifacts on all supported hosts. The Linux candidate was
built on Debian 12 with fixture catalog data. Its highest required GLIBC
symbol is 2.34, but that is not an Ubuntu 22.04 runtime test. - Stable release CI builds and livechecks Linux x64/arm64 in pinned Ubuntu 22.04,
and gates fresh installation plus TS migration on Linux and macOS arm64.
macOS x64 currently has a Rosetta version probe, not a full migration gate. - Older macOS arm64 CLI runs, two Rust beta CLI runs, and retained TS prerelease
runs passed earlier snapshots; they do not establish final platform coverage
or coverage of every historical beta. - GNU binaries do not establish musl, older-glibc, or unsupported-CPU support.
A working TS fallback is not a successful Rust migration. - npm was integration-tested; pnpm, Yarn, and Bun were not. Beta artifact reuse
does not execute the new fresh-install gate. - No final live-catalog, public stable endpoint, release-tag, or publication
verification is claimed. Full rendered-frame and exhaustive wire-byte parity
were not measured; the unchanged TS clients exercise the actual handoff.
Slack Mcp (`docs/slack-mcp.md`)
Slack MCP connector
Open /mcp (or /plugins), search for Slack, and connect. You can also
run /mcp login slack inside Prime Agent. Complete Slack's consent screen in
your browser. The connection becomes available to the current session through
the generic mcp Python API:
tools = await mcp.list_tools("slack")
# Read the returned inputSchema before calling a tool.Prime Agent connects to Slack's hosted Streamable HTTP server athttps://mcp.slack.com/mcp. It uses the public desktop OAuth client published
in Slack's agent harness guide:
client ID 1601185624273.8899143856786 and callbackhttp://localhost:3118/callback. This is the client used by Slack's Claude Code
plugin, so Slack's consent screen may identify the app as Claude. Prime Agent
uses PKCE and state validation, then stores the grant in its own auth store;
no Claude installation or credentials are needed.
Slack does not offer dynamic client registration. Workspace administrators
still control app approval and granted scopes. The desktop client registration
is controlled by Slack and its partner; changes there can require an update.
If port 3118 is occupied, finish or close the other login attempt and retry.
Prime Agent reports the conflict instead of redirecting to an unregistered
port. For SSH sessions, forward local port 3118 to port 3118 on the host
running Prime Agent, or paste the final redirect URL into the auth panel.
Disconnect with /mcp logout slack. Connector usage contributes to the existingmcp_connector_use_count session telemetry counter; server names and Slack
content are not recorded in that counter.
--- METRICS ---
- Files Extracted: 20
- Estimated Token Budget: ~39459 tokens
- Canonical Reference: https://codewiki.google/github.com/PrimeIntellect-ai/prime-agent