Repository navigation
Sync 89 upstream commits and retry dropped WebSocket turns - #34
Conversation
The x86_64-apple-darwin legs in continuous.yml and release.yml move from the macos-15-intel pool to the macos-15 Apple-silicon pool, cross-compiling for x86_64 (the target stays x86_64-apple-darwin). The M1 links this binary ~2.2x faster than the intel pool with far tighter variance (484 s vs 1046 s same-commit link tail), killing the 29-minute outliers; the livecheck runs the x64 artifact under Rosetta 2, the runtime install-rust.sh gives an x86_64 payload on an Apple-Silicon Mac.
…ct-ai#3223) The hillclimb era's measurement cluster dies whole: the TS-side faux-provider driver (scripts/ts_faux_extension.js) and the perf battery it drove - scripts/battery/ in its entirety (stream_throughput.py, streaming_render.py-era survivors, sustained_cpu.py, perf_wave.py, perf_gate.py, ci_perf_wave.sh, run_perf_wave.sh, the recorded perf-baseline.json, and the support libs that served nothing else: batterylib.py, mock_provider.py, perf.py, scale_corpus.py, ts_identity.py) - plus the Makefile perf-wave target and its .PHONY entry. Every member had zero live references: no workflow invocation (benchmark.yml is long gone, only the dead Makefile mirror remained), no test shells out to them, the crontab and the fleet tooling never touch them. The benchmark campaign harness on the bench node is the modern performance tooling. Folded onto main after the scripts purge (PrimeIntellect-ai#3178) merged: that purge already removed the parity harnesses this lane's map had found importing the faux (acp_compaction_parity.py, compact_parity.py, mcp_hint_parity.py, paste_stream_parity.py, spinner_cadence.py, streaming_render.py and the visual-parity cluster), so those deletions fold to no-ops; the purge's merged form kept the wave serving only itself - the faux header named exactly the two wave dimensions as its consumers - so the whole battery directory goes here instead. The golden regeneration harness (crates/pa-core/tests/golden/harness.mjs) is not needed post-port either: the replay tests assert against the frozen corpus at runtime, and the TS checkout remains the reference if a golden ever needs re-derivation. The corpus stays, frozen provenance noted in the replay module doc. Doc/comment repairs where kept files named a removed script: the Makefile drops the perf-wave mirror, golden_replay.rs's harness.mjs cites become the corpus's provenance notes, and differential_cli.rs's TMPDIR comment no longer cites batterylib's scrubbed_env.
* add the serper key flow to mcp The /mcp view gains the api-key credential class: the daemon's get_mcp_connections serves a credentials section (the api-key catalog), the view renders 'Serper (web search)' alongside the MCP connections, and Enter runs the paste-the-key prompt that stores AuthCredential::ApiKey at the 'serper' slot - the exact contract the websearch runtime reads. The /login-is-providers-only removal left no home for the key; /mcp is it. The SKILL.md (repo + installed) and the runtime's missing-key message now point at the new flow; the existing auth.json reading path is unchanged (a stored key keeps working, and the /logout row pairs with the new entry). File-size compliance: the view's row model moved to mcp_view/rows.rs and provider_login.rs's tests moved beside it (the additions pushed both files over the 1,000-line cap). * reload the auth store at the mcp view open The daemon's get_mcp_connections serves the api-key credential rows (and the roster) from the manager's cached AuthStorage copy; the interactive client's /mcp key flow stores through its own storage instance in its own process, so the row kept reading 'Not configured' until a daemon restart. The open now reloads the shared store first (TS buildServiceCatalogViews re-reads the store on every open) - the Macroscope finding on the first push. * retrigger ci: the synchronize push fired no ci run --------- Co-authored-by: kt <kt@users.noreply.github.com>
…llect-ai#3228) * pa-tui: the Anthropic subscription warning fires once per session lifecycle - not on every open The warning's shown-flag lived on the TUI instance (session_ui.rs's anthropic_subscription_warning_shown, reset at every SessionUi construction), so every session open re-drew the ban-risk warning. Operator directive 2026-09-29: the shown-state persists in the session's own durable store - once per session LIFECYCLE. The persisted state (the thread_goal_state pattern): - A custom row, customType "anthropic_subscription_warning_shown", data {"shown": true}, rides the session JSONL (the one durable store): branch-faithful, survives daemon restarts and worker replacements. - pa-core's windowed walk parses the marker on the active chain (the same sparse-parse condition as the goal row) and serves WindowedSessionStore::anthropic_warning_shown(); the snapshot carries it with a serde-default false (v7 sidecars stay servable; the default is the fail-open direction) and live appends fold it. - The daemon store hydrates on BOTH opens (full scan + the windowed snapshot, so the marker hydrates from the discarded prefix) and both fork arms re-hydrate from their own adopted rows (the fork's live flag agrees with its own file; a fork cut above the marker is a genuinely new session and answers closed). The wire: - New Rust-native client command mark_anthropic_warning_shown (the TS gate is per interactive-mode instance; the TS protocol has no counterpart): the worker persists the marker row idempotently and get_state serves SessionSummary::anthropic_warning_shown (skip-when-absent, so older workers never emit it). - pa-tui: the credential-detection arm (startup / model-select / api-key-save) gains the lifecycle gate as the LAST check before warning - the settings toggle (warnings.anthropicExtraUsage) stays first, the per-instance flag stays as the view's dedup, and the extra get_state round trip only happens when a warning is actually pending. The read fails OPEN (an absent field from an older daemon, or an unreadable state, draws the warning rather than suppressing it). The mark is fire-and-forget bounded 10s (a failed mark costs one repeated warning on the next open, never a suppressed one). - The login-completed arm (a fresh auth landing) RE-WARNS - each completed subscription login is its own event - and marks too. Tests (the five directive behaviors): - pa-tui/tests/anthropic_warning_lifecycle_headless.rs (the scripted socket harness): new-session warns once + marks exactly once; marked-session reattach never re-warns; the toggle off never warns; a fresh login re-warns on a marked session; the absent-field degrade fails open. - pa-cli e2e over the REAL supervisor: run one warns once (the fake subscription credential + the scripted anthropic model - the harness engine gains the opt-in "model" script knob for it), the marker row is durable in the session file, get_state serves the gate over the wire; a FRESH process attaching renders no warning. The supervisor routes the new frame through the generic active-session selector. - pa-daemon: the store mark/dual-reopen hydration, the worker dispatch/get_state/idempotence, the fork arms' own-rows hydration, the window-scan hydration (the marker beyond the retained boundary, cold + sidecar; a data.shown=false row never hydrates); command_breadth extends the Rust-native accept list + fixture. * pa-tui: the Anthropic warning's bots round - the gate rides the active branch and the harness settle waits the mark * pa-cli: the warning e2e's missing-file panic names the file, not the session id (CodeQL) * pa-cli: the missing-file panic's format argument count (the CI round's compile fix)
…only, and one shard dep cache (PrimeIntellect-ai#3233) - continuous.yml + release.yml: swap dtolnay/rust-toolchain to the action pin that honors the toolchain input; the old pin has no toolchain input, so every release binary built with rustc 1.95.0 while ci.yml tests on 1.98.1. - continuous.yml + release.yml: build -p pa-cli --bin prime-agent — the jobs package only prime-agent, and pa-daemon's [[bin]] was a second thin-LTO link with no consumer (~26% of the build step, measured). - ci.yml: one shared rust-cache key for the 8 test shards — per-shard keys held 8x ~500 MB per ref and evicted the release-build caches (the repo sits at the 10 GB quota; the shards were 83% of it). Co-authored-by: Sebastian <sebastian@primeintellect.ai>
…rimeIntellect-ai#3229) The owner-watchdog daemon thread wakes once a second for the whole life of every kernel just to re-probe the owner. On macOS/BSD register the owner's exit (kqueue NOTE_EXIT) and on Linux a pidfd, then block on the single exit event: zero wakes until the owner dies. A 30 s poll remains only as the fallback when exit notification cannot be registered (old kernels, seccomp, CPython without os.pidfd_open). Owner resolution, reparent semantics, the exit path, and Windows are unchanged.
…rkers (PrimeIntellect-ai#3242) The adopted-worker monitor polled liveness at 1 Hz forever (a `ps` subprocess per tick on macOS). It now parks on the kernel's exit notification (Linux pidfd, macOS kqueue EVFILT_PROC/NOTE_EXIT, both awaited through tokio's AsyncFd), with a clearly marked 30 s liveness poll only where the watch cannot register. pa-core's open_pidfd now returns io::Result so the wait can tell ESRCH (already exited) from an unwatchable pid. The adopted arm matches the Child arm's shape: it waits for the exit, then honors the stop flags.
…ai#3047) * feat(pa-tui): the scoped agents view's ctrl+n starts the session under the scope root Operator-directed divergence (snimu, 2026-09-28): the unscoped view's ctrl+n keeps starting a depth-0 root session (TS `createNewSession`), but a scoped view's ctrl+n starts the new session UNDER the scope root - one level below it and in its directory - so it lists in that same scoped view and the agents-back return lands on it, selected. The create stays the plain daemon `create` carrying two config keys the daemon already accepts for RLM children (`parentSessionPath`, `rlmDepth`): no new wire op, no runtime metadata, no ledger edge - the new session is a user-owned top-level runtime whose file lands in the scanned sessions dir. - `scope_depth` becomes `pub(crate) scope_root`/`ScopeRoot`: the depth label, the parent file (via the forest's own `parent_record_file` so the create binds exactly what the roster links), and the root's cwd, resolved once. - The new action rides `OpenedRow` (TS `finish({ type: "open" })`); `new_session: bool` is deleted instead of widened (#10/#6). - `SessionSelection::NewChild { parent_session_file, rlm_depth }` carries the binding through `create_config`; the pre-attach startup paint and the fork match arms stay explicit; `/new` keeps its root semantics. - Adoption telemetry: `tui agents new scoped` { depth } (schema v1, primitives only) via the new `InteractionTelemetry::scoped_agent_created` seam, emitted in `SessionUi::open` once the daemon accepted the create; the unscoped path emits nothing. Regression tests: the scoped ctrl+n binds under the scope root (whole-object `OpenedRow`), the unscoped override pins plain `New`, and `create_config` carries the parent binding (whole-object). * feat(pa-tui): the dock counts a user-created child session as a child Operator ruling (snimu, 2026-09-28): "agents and users should be seen as equal collaborators working on the same project" - the parent chat's dock counters include sessions a user created under the parent (the scoped agents view's ctrl+n), not only spawned subagent runtimes. The one TUI parentage rule moves to `subagents.rs`, already the shared subagent-family data layer: `is_subagent_summary` relocates unchanged and `depth_consistent_binding` extracts the forest's depth-consistent parent test. The dock's walk links a live top-level row whose binding names the linking owner's file one level up, so a user child counts direct (or nested under a subagent) exactly like a spawned one; a same-depth fork stays a sibling and a message-less draft stays out of the walk, exactly as the agents view hides it. The forest keeps its record-shaped wrapper; forest behavior is unchanged. Side effect: a resumed subagent file reopened as a top-level runtime now counts in the dock too, ending the dock/agents-view mismatch. Regression: a_bound_session_counts_as_a_child_and_a_fork_or_draft_does_not (whole-object SubagentCounts); the redundant non_subagent_rows_never_link folds its fork case into it.
* perf(startup): the probe-grid fast-poll cut - both connect-probe grids tighten 5ms -> 1ms (timing-only) * docs: the probe-interval comments state current behavior, not the poll-interval history (bugbot df2563c5 fix) * docs: drop the predecessor-record clause from the daemon probe comment (bugbot df2563c5 follow-up) --------- Co-authored-by: hillclimb-lane <lane@hillclimb.local>
…eIntellect-ai#3286) Two unit tests instantiated the real panel login UIs and invoked on_auth, whose production implementation calls pa_core::platform::browser::open_in_browser - so every local cargo test run opened real OAuth pages in the developer browser: github.com/login/device (GitHub Copilot device flow), claude.ai/oauth/authorize (Anthropic), and auth.example/authorize (Codex). Delete the browser-opening tests and the now-unused Duration import; production code is untouched (the opener behavior is correct at runtime).
…meIntellect-ai#3275) Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
…ai#3253) * show installer progress and add a --force daemon stop * fix the printed rollback after a typescript takeover * only signal a pid that is a prime agent daemon under --force * report each --force outcome with its own reason * give the installer the splash banner and a minimal step list * keep the download path out of the renderer's shell command * retrigger ci * retrigger ci --------- Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
…t-ai#3276) Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
* never set git identity - the ambient auth owns the attribution * extend the identity rule: no co-authored-by trailers ever * scope the identity rule to commits that land in this repo * AGENTS.md: the identity comes from the ambient git config, not auth --------- Co-authored-by: Sebastian <sebastian@primeintellect.ai> Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
…-ai#3156) * pa-daemon: supervisor_roster.rs split - under 1,000 lines The 1,799-row roster-serving module becomes its 30-row facade plus two test-family children at the same tree position (supervisor_roster::{passivation,delta_push}): - The facade keeps the product whole (the zero-move cut): the `WorkerRosterDelta` record, the entire `impl Supervisor` (the subscribe handling, the delta merge, the stop-path passivation, the roster pushes, the `write_roster_summary` arms), and the free fns (`passivated_summary`, `normalize_model_to_durable_pair`). - passivation.rs (650): the passivation + snapshot family - the stop-path passivation (the anchored-child preservation, the live-field stripping, the family pruning), the rewrite/diff-baseline snapshots, the registration-mark + re-registration rules, with the seed-tests fixtures the family drives; the shared drain fixture resolves through the facade. - delta_push.rs (514): the roster-delta push family - the supervisor fixture, the sequence-drop, the flip/push batching, and the push benchmark. - The shared `drain_roster_pushes` fixture (both families drive it, 23 passivation call sites) rides in the facade under `#[cfg(test)]` per the agents_view shared-fixture precedent; the children reach it through `use super::*`. ZERO product moves: the facade's crate::supervisor_roster::X paths (WorkerRosterDelta and every impl member) resolve unchanged; the test families reach the product through `use super::*` with the supervisor_roster_seed::tests fixture block riding in the passivation child (its sole consumer). Byte-proof: each family child is byte-identical to the tip block modulo the enumerated headers, the uniform dedent, one fmt-canonical signature wrap, and the seed use block (verify_supervisor_roster.py); the facade is the tip minus the test mass plus the two cfg(test) decls; the tip blob reassembles ZERO-DELTA. External-path audit: the only external reference (supervisor/clients.rs's crate::supervisor_roster::WorkerRosterDelta) resolves unchanged. * ci: the empty-commit retrigger (the shard 4/8 tui_dispatches_slash_commands_menu_and_suggestions red is the adjudicated view-render/data-arrival ordering flake, red-slashmenu-tui-dispatch-parallel-flake-20260930-1 - solo x3 GREEN at the exact head cf8f86f plus the pristine-tip control x2 GREEN in the lane sandbox; this diff is the pa-daemon supervisor_roster pure-structure split, zero pa-cli/tui surface; the rerun-failed route is banned by the fleet retrigger policy) * pa-daemon: drop the split-narration doc leftovers --------- Co-authored-by: Sebastian <sebastian@primeintellect.ai> Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
…ync (write_file_atomic gains the per-call-site sync option) (PrimeIntellect-ai#3283) * perf(pa-daemon): the supervisor-config persist drops the TS-absent fsync (L1 prebuild) write_file_atomic made TS writeFileAtomicSync's per-call-site OPT-IN fsync unconditional at every call site. The supervisor-config persist is the only pre-bind durable write on the daemon boot path (Supervisor::new, before bind_transport) and its TS counterpart (persistSupervisorConfig, daemon-supervisor.ts) passes no fsync option: the unsynced rename is the TS daemon's own posture for this write. One trapped fresh+sync_all+rename fsync is the ~39-40ms class the daemon-init bind-span decompose measures on the warm-create path. Scope: TempSync enum (the per-site posture, the worker journal's Finalize-enum shape) with the synced default preserved for every other caller (worker descriptors, identity-pending, update_prepare); only persist_supervisor_config moves to write_file_atomic_unsynced. The boot error contract is unchanged (a failed write still fails the boot; only the sync is dropped). The persisted config has no in-tree readers (load_supervisor_config has zero callers) - a write-only parity mirror. Tests: the unsynced arm replaces an existing destination with temp cleanup, and persist_supervisor_config round-trips through load_supervisor_config (parseability through the unsynced write). * test(pa-daemon): the config round-trip compares fields, not the non-PartialEq struct The prebuild test used assert_eq! on PersistedSupervisorConfig, which derives no PartialEq - a test-compile E0369 under clippy --all-targets. Compare the three fields instead (same oracle: the unsynced persist stays parseable and the loaded record belongs to its socket). * style(pa-daemon): rustfmt shape for the round-trip test's field tuple cargo fmt --all --check wants the 3-tuple broken across lines (it exceeds the width on one line).
* make release-prepare use the pinned toolchain and drop the ignored toolchain inputs - release-prepare.yml: swap dtolnay/rust-toolchain to the 1.98.1 action pin (same fix as PrimeIntellect-ai#3233); the old pin ignored the toolchain input and installed 1.95.0. - ci.yml: delete the ignored toolchain inputs (the action SHA selects 1.98.1). - ci.yml: fix the shared cache comment; the shards' dev-dependencies differ, so the first leg to save defines the entry and other legs get a partial hit. * drop the ignored toolchain input in the windows triage workflow * retrigger ci * retrigger ci
…llect-ai#3293) * let the down arrow always reach the menus under the prompt * keep the escape test off the tray and wait on what the tray tests check
…ai#3289) * select the top search hit when the agents view query changes Typing a query kept the previous row selected by identity, or clamped its index onto the last hit when that row was filtered out, so the best match sat above the cursor. Every query edit now selects the first row (the top-ranked hit); clearing the query returns to the session selected before the search, or the top if it is gone. A move while searching keeps the user's pick through the clear. * select the topmost row on every agents view query change Drop the pre-search restore: the clear is a query change like any other and selects the top row too. * Revert "select the topmost row on every agents view query change" This reverts commit b530f41.
…TS fsyncs neither install-id site) (PrimeIntellect-ai#3295)
…ect-ai#3281) The e2e socket helpers poll the daemon socket with a 100 ms read timeout in a loop, clearing the line buffer at the top of every iteration. BufRead::read_line keeps the bytes that arrived before the timeout fires and returns Err, so the next iteration discarded the first half of the line and parsed only the tail: parse response line: Error("expected value", line: 1, column: 1) A long line straddling the timeout, like a chunked attach snapshot, then failed the suite: attach::chunked_snapshot_attach_streams_begin_chunk_end flaked three times in CI (PR PrimeIntellect-ai#3258 run 36817673817, PR PrimeIntellect-ai#3249 run 36898991027, and once more on PrimeIntellect-ai#3258). Clear the buffer only after a complete line was consumed, so partial bytes survive the timeout; the deadline is unchanged. The pa-cli e2e helpers use the same loop and get the same fix: continue_guard_e2e.rs re-created the buffer inside the loop, so the buffer moves out of the loop instead.
…lect-ai#3245) * ci(test): run the idle passivation e2e and the release-dir kernel suites The merged bootstrap step readies the product's kernel venv, but the idle passivation e2e still self-skipped: its private PA_TEST_KERNEL_PYTHON lookup had no HOME fallback and CI set nothing. It now uses the crate's standard PA_E2E_KERNEL_PYTHON-else-default-path lookup, so the venv the bootstrap builds reaches it (PA_TEST_KERNEL_PYTHON goes away). The kernel suites that read an installed release dir run too: pa-core's abort_kernel_cell and turn_boundary_kernel already honored PI_PACKAGE_DIR, and the pa-daemon abort_kernel/steer_boundary e2es and the agent-engine abort test now take the same branch. The shard step exports PI_PACKAGE_DIR at the checkout (the runtime dir the bootstrap itself resolves), and the Install uv step names its real consumer. * fix two racy e2e tests and drop the release-dir gates Both tests failed on this branch and on other branches that never set PI_PACKAGE_DIR; the races are in the tests: - read_goldens threw away events that arrive before the prompt reply. The turn can finish first, so its turn_end was lost and the test waited out its 15s deadline. It now keeps those events, like lifecycle.rs does. - worker_fd_count_stable_across_prompts counted the worker's fds at the moment the prompt returned, while the turn's short-lived roster-push sockets were still open. Each sample now waits for the count to drop back to the baseline plus slack; a leaked fd never closes, so a leak still fails. wait_until's panic now prints the deadline and points at the caller. The five live-kernel suites (pa-daemon abort_kernel_e2e, steer_boundary_batch_e2e and the lib abort test; pa-core abort_kernel_cell and turn_boundary_kernel) do not need a release dir: a debug build finds the checkout's runtime and skills on its own. They now run whenever the kernel venv exists. * ci: stop setting PI_PACKAGE_DIR for the test step The live-kernel suites no longer read a release dir, so the job-wide variable has no reader that needs it.
…meIntellect-ai#3282) The pa-daemon e2e helpers poll the daemon socket with a short read timeout in a retry loop and either clear the line buffer at the top of every iteration or re-create it inside the loop. BufRead::read_line keeps the bytes that arrived before the timeout fires and returns Err, so the next iteration discarded the first half of the line and parsed only the tail. Same fix as PrimeIntellect-ai#3281, swept over the remaining helpers: clear the buffer only after a complete line was consumed, so the fresh-per-iteration buffer in the drain loops moves out of the loop instead. Deadlines and messages are unchanged.
…s transport deletion, event parity (PrimeIntellect-ai#3264) * fix(acp): the daemon-attached ACP session follows the CLI session flags and lifecycle The daemon-attached ACP mode created a throwaway client-owned worker on every session/new, ignored --resume/--continue/--fork/--session-dir and the telemetry posture, and killed the worker on session/close and at EOF. Startup now creates and attaches the daemon session the flags select (TS main.ts): pa-cli resolves the session with the same selector the print and rpc modes use (lifted out of build_session_manager_with_lease, with the stored-cwd rule shared through stored_session_cwd), sends the resolved sessionPath, cwd and sessionDir, sets telemetryDisabled, and marks only --no-session sessions client_owned. A failure after the daemon is up (bad selector, busy session, create or attach refusal) prints Error: and exits 1 before any ACP frame instead of falling back to in-process mode. session/new binds that session instead of creating one. session/close runs the shared stop sequence, releases MCP and fences the producer, but keeps the worker, so a later session/new binds it again. A session/close with the wrong id no longer drops the live session. EOF releases MCP and ends only a client-owned session (complete_owned_session); a resident session survives and is detached when the link drops; its running prompt is cancelled, as TS's EOF abort does (cancel_prompt_admission with cancelOwned). Worker::new built the prompt-admission registry twice: the turn runner committed admissions in one copy while cancel_prompt_admission read the other, so a cancelOwned cancel of a running prompt answered cancelled and never aborted the turn. The runner now shares the worker's registry. * fix(acp): await session/new before the next frame and resolve relative resume paths A session/new that was still running when stdin closed installed its session after teardown had already run, so nothing ever released it: the producer's sink kept the writer open and the process never exited, and the MCP servers it had admitted stayed registered. The reader loop now awaits the session/new handler before it reads the next frame (the same rule the in-process ACP loop applies to every handler), so EOF is only seen after the session is installed and the existing teardown releases it. Every other handler still runs concurrently: session/close and set_config_option can wait behind session/cancel. A path-like --resume selector went to the daemon unchanged and the worker resolved it against the stored session cwd, so a relative path from the CLI's working directory opened the wrong file or created a new one and started a second worker. TS resolves the file before sending it (SessionManager.setSessionFile), so select_headless_session now makes the opened resume path absolute where the session is selected. * fix(acp): --continue sends an absolute session path like --resume A relative --session-dir makes find_most_recent_session_for_cwd return a path joined under it, so --continue sent the daemon a relative session path. The supervisor resolves create paths against its own cwd, so the reuse lookup missed the live resident and launched a second worker the lease refused. The continue arm now absolutizes the selected file exactly like the resume arm; sessionDir itself stays as given, matching TS runtimeConfigFromArgs. * test(acp): run the ACP e2e suites on the daemon-attached path Both ACP e2e suites rode the in-process fallback, but the product's ACP mode prefers the daemon-attached transport, so the coverage now spawns the sandboxed supervisor and drives the same wire the installed binary speaks. - acp_mode_e2e: one daemon-attached constructor (the in-process spawn and the dead PRIME_AGENT_AGENT_DIR env are gone); the supervisor socket lives on the harness struct. The 20 migrated tests keep their assertions; the 8 turn-running scripts gain engine: "faux" (the worker runs the real agent engine only with the key), and the threshold script pins maxTokens: 4096 -- the worker seam defaults to 16384, which turned the test's reserve-shaped threshold degenerate (a non-positive threshold disables the trigger, so the arm never fired). The redundant mcp-capabilities twin and the three daemon subsets are deleted (the goal test absorbs the tokenBudget check), and the overflow re-probe loop is gone: the daemon releases the prompt slot before it answers, so the probe cannot hit the running-turn guard. - acp_config_option_e2e: the in-process harness and both its tests are gone. The locked-provider refusal is not TS daemon behavior (the SDK counts authHeader as configured auth), and the faux worker does not follow set_model yet, so the switch flow stays unit-covered (PR-8b owns that seam). The transport-neutral assertions -- fixture discovery, the effort level list, the unknown-session error -- moved into the daemon picker test. - acp/daemon.rs: initialize now runs the same protocolVersion schema check the in-process arm runs (the TS SDK zod-validates initialize on every connection); the string-protocol-version test is red without it. - README: the offline e2e sentence names the daemon-attached transport. * changelog: acp daemon initialize validation * test(acp): the faux worker follows set_model The faux branch of AgentSessionEngine::resolve_model returned the script model unconditionally, so a set_model switch on the daemon-path e2e harness succeeded without switching: the pickers kept reporting the script model. Return the script model while the selection is unset or names it; any other selection resolves through the registry (the faux provider is registered under the `faux` api, so a models.json faux-api model streams through the same script). Port the deleted in-process suite's model-switch assertions into the daemon picker test: the plain model drops the effort picker and refuses `high`, the turn after a switch settles, switching back restores `high`, and the map model accepts `xhigh`. * fix(daemon): derive the engine model selection from the create config only AgentSessionEngine::new fell back to PRIME_AGENT_MODEL_PROVIDER / PRIME_AGENT_MODEL when the create config carried no provider or model. The faux worker branch always passes none, so with the env pair exported a never-switched scripted session started with an env-derived selection, which resolve_model treats like an explicit set_model switch - the session left the script model and resolved through the registry. The non-faux worker branch already reads that env pair and passes it in the create config, so the fallback only duplicated the read. Delete it: the worker is now the single owner of the env read, and only an explicit switch moves a scripted session off the script model. * refactor(acp): the composition root serves ACP only over the daemon `acp_mode_main` now starts like every other daemon-attached mode: it ensures a supervisor is listening, creates the daemon session the CLI session flags select, and serves the ACP surface over it. When the daemon cannot start it fails with `Error:` and exit 1, matching the TypeScript CLI, which has no in-process ACP path on this branch. The `try_daemon_attached_acp` fallback (and its "using in-process mode" stderr line) go away with the short-circuit. The scripted-session seam renames `PRIME_AGENT_ACP_DAEMON_SCRIPT` to `PRIME_AGENT_FAUX_SCRIPT`, the interactive mode's env: the value is a script file path forwarded through the create config, so the ACP and interactive modes now share one contract instead of two env names for the same mechanism. The two e2e helpers follow the rename. * refactor(acp): delete the in-process ACP transport The daemon-attached transport is now the only ACP transport, so the in-process engine and its session machinery go: prompt.rs, session.rs, in_process_config.rs, compaction_arms.rs (and its tests), autorefine.rs, goal_continuation.rs, and the events.rs loop-event projection (wire_events.rs already maps the daemon worker's session events). mod.rs shrinks to the shared helpers and the module tree; handle_initialize/validate_initialize and the prompt-block error move into daemon.rs next to their only callers, and AdmittedPrompt moves into types.rs next to parse_prompt_blocks. pa-agent's clear_all_queues goes with them (the deleted handlers were its only callers). The pa-daemon scope README and the ACP capture notes drop their in-process clauses. * fix(acp): a session/cancel request answers method not found like TS The TS SDK registers session/cancel only as a notification; a request framing falls through to the generic method-not-found answer. The Rust daemon-attached transport had grown a special case that answered `{}` instead, so a client could not tell an unhandled method from a handled one. The reader loop now only arms the stop for the notification form. * fix(acp): the settle waits for RLM quiescence, the stop cancels the children An ACP prompt answered end_turn while the subagents it started were still running: both quiescence frames reported a hard-coded outstandingSubagents of 0, the terminal frame published right after the completion, and cancel/close stopped the parent's turn only, leaving the RLM children running. EOF, conversely, waited behind an in-flight settle. - The settle now reads the live roster (get_rlm_children) for the completion envelope, then loops on the headless-completion barrier with waitForRlmQuiescence (TS PrimeIntellect-ai#1612 finalizePendingTerminal): the barrier is the event with no timer, it answers once every tracked child run settles, and the loop re-reads the roster before publishing the terminal frame with a zero count. A failed barrier or read fails the prompt with "ACP lifecycle reconciliation failed: ..." instead of degrading, since a degraded status would spin the loop. - The stop sequence behind session/cancel and session/close cancels every roster row after WaitForIdle (TS cancelOutstandingRlmChildren in stopSessionWork); the cancels run one after another and their failures are ignored like every other stop step (declared divergence from Promise.allSettled and from the stopFailure latch). - Only the prompt handlers are owned by the JoinSet EOF shuts down before the teardown (TS aborts the controller and exits), so the process never waits on a prompt still settling; the aborted prompt handler leaves its turn slot set, so the teardown still sends the owned-admission cancel. The other request handlers stay detached spawns, so a session/close in flight at EOF runs to completion — its tx clone holds writer.await — and its session still stops and releases its servers. session/new stays awaited inline. - The terminal frame reports the settlement's own status observation, and the stop reason reads the abort flag after the settlement (TS terminalStatus / terminalSettlementCancelled). The CLI's ACP create also forwards PRIME_AGENT_FAUX_CHILD_SCRIPT (a script file) as the create-config childScript, extending the PRIME_AGENT_FAUX_SCRIPT verification seam (the worker has consumed the key since PrimeIntellect-ai#3171), so e2e lanes can script the spawned children. The new kernel-gated e2e lanes prove: the completion reports the live child and the terminal reports zero after the settled child's notice turn drained inside the barrier; a cancel during the settle cancels the held child and answers cancelled; EOF during the settle exits while the resident session's child keeps running. The lanes run resident sessions because the RLM spawn ledger needs the parent's session file: a --no-session parent cannot spawn children at all. * test(acp): spawn lanes stop writing receipts into a dropped temp dir The receipts TempDir was dropped when spawn_lane returned, before the cell ran on the prompt: open(spawn.json) raised FileNotFoundError after the successful spawn, the except arm raised again, and every lane's tool call ended as an error no test read. The cell now only spawns and prints the child id; a failed spawn already surfaces through the tool error. * fix(acp): a failed MCP admission clears the applied server list The replace wire command can fail after the worker already applied the list: the 30 s request timeout drops a lost acknowledgement. The worker rolls back only when it answers with a failure, so the daemon path left the servers owned by the abandoned session owner. Clear them best-effort, an owner-scoped empty replace like close and teardown, mirroring TS `clearAcpMcpServers(serverNames).catch()` in the `replaceAcpMcpServers` catch. * fix(acp): report heartbeat changes and user bash runs like TS The daemon-attached link dropped the supervisor's daemon-global heartbeats_changed broadcast and mapped the worker's user-level bash events to nothing, so a client never learned that the session's heartbeat schedule changed or saw a shell command another client ran. An empty tool result also published an empty content block. All three diverged from the TypeScript adapter (08ff1b2): - the reader classifies heartbeats_changed as its own frame kind, and the consumer publishes session_info_update with heartbeatsChanged at origin turn 0, connection-scoped like TS rather than an event of the active prompt turn; - wire_events maps bash_start/bash_output/bash_end to a synthetic tool call keyed by run id (prime-agent-bash-<runId>), so the started run, its streamed chunks and the settled status are addressable; - tool_result_text drops empty text blocks before the join and returns None for an empty result, so no update carries empty content. * fix(supervisor): keep the worker's event-before-response order on the client hop The worker guarantees that every session event a turn emitted reaches the socket before the command response that ends the turn (the fan-out flush barrier in write_response_frame). The supervisor's per-client writer broke that guarantee: its select polled the response and event arms at random, so a response could be written ahead of events that were published - and already queued - before it. ACP compensated for the reorder with a 30 s wait for a post-turn marker. The writer now polls biased, with the session-event arm ahead of the response arm, so a queued event is always written before a queued reply - the order the worker itself sends, restored at the hop. The ACP marker machinery (the turn_emitted oneshot, the agent run counters, the 30 s timeout and its failed-before-run special case) is deleted; the settle reads the stream directly. A slow-reader e2e drives a large streamed turn through a client that reads nothing until a second connection's wait_for_idle proves the turn settled (the probe attaches first and waits for the turn's agent_start, so its dispatch cannot beat the prompt), then asserts the reply arrived behind the queued events - counting only events whose wire offset starts past any kernel socket buffer, >= 8 of them, the test's own precondition - and after the turn's turn_end and agent_end. * fix(daemon): long turn routes get the TS 24 h worker budget instead of a 10-minute cap * fix(acp): the daemon session create sends only an env telemetry opt-out, like the interactive client * fix(acp): each bound session starts its own event mapping state, like TS
…Intellect-ai#3290) * factory: port the state-machine harness entry kind, validator, dag compiler, and executor (PrimeIntellect-ai#2397 + PrimeIntellect-ai#2401) * factory: address review findings (join-pause skip, exact guard equality, cancellation claim, harness spec isolation, refinement update path, digest resume hint) * factory: second review round (unhashable join entries, validation never raises, JSON-null machine form is absent) * factory: a terminal entry no longer queues an un-admittable retry A foreach sibling failing with retries left AFTER its entry already went terminal (a sibling failed it permanently first) was reset to pending -- _next_pending_instance serves running entries only, while _run_complete and the stall detector both counted the pending instance as in-flight, so the control loop never finished the run. The retry is now queued only when the entry can re-admit it (entry.status == running); a terminal entry's failed sibling settles as an error instance, and the policy call is a no-op on a terminal entry (Cursor Bugbot finding on PrimeIntellect-ai#3199). * factory: a resident-saturated cap fails the run instead of wedging With every max_parallel slot held by resident instances, queued work can never be admitted -- a resident never settles, so no slot ever frees, _run_complete stays false (a pending instance blocks completion), and the residents keep children in flight, so the no-in-flight stall detector never fires: the control loop polled forever (two resident entry states with max_parallel 1, or eight residents plus a task at the default cap). The loop now detects the un-admittable dead end (_resident_cap_starved: cap saturated and every running instance a resident) and fails the run with a deterministic executor error; stop() still tears the resident children down (Cursor Bugbot finding on PrimeIntellect-ai#3199). * factory: a resident in flight cannot mask the pending-entry stall Bugbot review finding on the resident stall series: the dead-end stall check ran only with nothing in flight, but an admitted resident never settles, so a pending entry waiting on an input source that never settled kept the control loop polling collect forever -- in_flight true, _run_complete false (the pending entry blocks quiescence), _resident_cap_starved false (it sees queued instances, not unprepared entries). The run wedged in "running"; only stop() ended it. The stall detection now also fires when the only in-flight work is resident instances: residents never settle and can never unblock a pending entry (they declare no outputs and no outgoing transitions), so a pending entry with no instances behind them is the same dead end. stop() still tears the resident children down. Regression: test_resident_in_flight_stall_fails_the_run_instead_of_wedging fails without the resident_only_in_flight gate ("never left the running state") and passes with it; the resident completion tests are unchanged. * factory: an unsettled optional foreach.over expands to zero items instead of failing * factory: machine validation rejects a required self-input that can never bind A required self-input needs the state's own prior settle on its first entry, which cannot exist, so the entry stays pending until the stall detector fails the run. Optional self-inputs are the designed loop form and stay valid; this mirrors the dag compiler's cannot-depend-on-itself rule (cursor review finding on PrimeIntellect-ai#3199). * feat(tui): /factory live view with machine diagram highlighting The /factory live view over the kernel factory executor, for both users and agents: - kernel (rlm/factory.py): factory.graph fuses the machine structure (states, transitions, guards, entry states, declared order) with the live runtime state (status() node data, active nodes, last-fired edges, events tail, usage, budget consumed); a stored spec id answers the static structure, no ref answers every live run. A bounded factory.watch blocks until the run state/instance shape changes or the timeout elapses (change detection over a per-run revision the ledger-touching seam bumps), then returns the same snapshot plus changed. - host bridge: the out-of-band factory_activity kernel frame (the bash_activity precedent: a running cell never delays the live view) carries graph/status/watch/run/stop/resume. The session_engine factory_host follows the system_router_host pattern from PrimeIntellect-ai#3184: a config of session facts captured in create_session, and a run preflight that resolves every declared model selector through the credential-backed catalog (exact form, TS short form, the stale-provider session-model gate), enforces the daemon allowedModels pin, and requires request auth before any child spawns. - daemon: the factory_activity command (session-addressed, advertised by the factory_activity capability) reaches the session kernel through the bridge; unknown actions and malformed targets fail loudly on both sides of the wire. - TUI: the /factory view - one panel per live run: the machine diagram (status-glyphed state rows in declared order, connector rows with guard labels, joins once, back edges marked, active nodes bright, pending dim, last-fired edges marked), the run state, instances running and queued, budget consumed, and the milestone tail. Orchestration keys stop/resume the selected run; m copies the same graph model as genuine Mermaid source (flowchart TD with classDef active styling and linkStyle marks on the fired edges). The refresh rides the run collect cycle (a bounded watch on the selected run, then the graph list) with signature hysteresis: only a notice-worthy run-shape change lights the changed marker, never a per-transition repaint. * fix(tui): the factory review round — wire parity, page entry, hysteresis, and the preflight seam The kernel<->TUI field-name seam (live probe: the TUI read camelCase `runId`/`specId` while the kernel graph emitted snake_case, so every run row dropped and the view was always empty) is fixed on BOTH sides: the activity lane now emits the protocol's camelCase wire keys end to end (`_wire_payload` re-keys the reply's rows; the in-kernel conversation API stays snake_case), and the TUI parser tolerates both spellings — each side pinned independently (a Python battery test asserts the reply's wire keys; the TUI fixture is the real reply shape captured from a live activity probe, with a mechanical snake_case mirror asserting both spellings parse to the identical struct). The entry surface reworks to the page family: the activity dock's new factory group (`⚙ N factory`, arrow-traversable like subagents/ heartbeats/shells) opens the factory page from the dock's Enter and the click; the `/factory` builtin is dropped (the shells/subagents pages carry no command either); the always-on 2s poll (the bash poll's cadence and capability gate) keeps the dock's live-run count current while the page is closed, and a reply without the runs list reports on the page's error line instead of painting a fake empty state. Review-thread fixes: the hysteresis signature drops `elapsed_ms` (the clock never trips the changed marker); the render budget window keeps the trailing chrome and the selected run's panel visible (never the leading-rows truncation); the Mermaid emitter paints pending nodes with a dim `pending` class (never bright active); the preflight's unresolvable subagent reference skips only its own state (the other states' declared models still preflight); the refresh cycle serializes like the heartbeat refresh (one in flight with a queued tick — the 2s tick never mints an epoch the in-flight reply can't match) and the whole cycle sits inside the request bound; a same-session rebind keeps the page mounted; `graph()` deep-copies each transition's `from` list (the snapshot owns its rows — a consumer mutating a join can't corrupt the active run). Also two platform-hygiene fixes for pre-existing macOS-local merge-gate blockers (byte-identical failures on the pristine branch head): the pidfd stubs' `#[must_use]` parity with the linux implementation, and the boot_reap unix helpers' dead-code marks for their linux-only callers. * fix(tui): the second factory review round — scoped preflight, guard identity, and the page seams Nine review threads on the reworked head, all valid, each with a mutation-checked pin: - Scoped harness ids (`local:`/`global:`) now preflight their own store (`split_harness_scope` mirrors the kernel harness's `_strip_scope_prefix`): a scoped run resolves the exact factory entry and subagent references it will execute, where raw map lookups missed the prefixed ids and skipped the whole model preflight. - The fired-edge identity includes the guard: the kernel's fired events and last_fired edges carry `when`, and the TUI edge match requires it — two guarded transitions sharing one from+to pair never cross-mark. - The Escape key closes the page (`key_event_to_id` reports "escape"; the match accepted only "esc"). - The render honors degenerate viewports: the `.max(6)` floor is gone, a 1-row viewport renders exactly one row, and the tail-clip keeps the hint. - The Mermaid source neutralizes label-breaking text (the `|` delimiter, the node-label quote, newlines) in guards, state ids, and run names. - watch() rejects NaN timeouts by identity (NaN passes every comparison; the deadline would reach asyncio.sleep and raise). - The activity error reply rides the same wire cap as the success reply: a huge validation reason fails loudly under the cap instead of exceeding the transport bound. - The factory cache dies with its session: the attach fold and the different-session rebind reset `factory_graph` (the bash registry's pattern), and the fold asks for the first frame's refresh — the dock count and a reopened page never show the previous session's runs. * fix(tui): the third factory review round — bounded graph history, live-activity painting, and guard fidelity Six review threads on the round-2 head, all valid, each with a mutation-checked pin: - The unscoped graph reply bounds its terminal history: every live run reports, the newest GRAPH_RUNS_WINDOW terminal runs follow, and the registry's unbounded completed-run history no longer feeds one polling reply's snapshot construction (by-ref snapshots stay available for all of them). - The wire conversion treats guard dicts as verbatim: a guard's comparison value mirrors the executor's declared condition, so its nested keys never re-key (the structure keys are single words already). - The diagram paints by the node's live activity, not the aggregate status: a multi-entry state whose latest entry settled while an earlier one still runs stays bright in both the terminal diagram and the Mermaid active class. - The valueless guard label renders `output.path op` (verdict exists, not verdictexists) — the path and the separator join the valued form's shape. - The malformed-reply guard requires the runs value to be an array: {"runs": {}} is a malformed lane, not zero runs. - The rebind fold keeps the factory cache when the page was never mounted under another session: a same-session rebind no longer wipes the dock's live-run count (every rebind path attaches first, so the attach fold owns the cache's cross-session reset). * fix(kernel): the fourth factory review round — non-finite guard values never ride the wire A non-finite float in a guard comparison value (when.value = NaN or Infinity, directly or nested in a contains needle list) passed every validation (a NaN float is a scalar) and rode the reply frames as the non-JSON NaN/Infinity tokens — every strict consumer of the activity lane (the host bridge's serde_json parser included) would fail to parse the reply. Fixed at the source and belted at the wire: - _validate_guard rejects non-finite guard values deeply (_value_is_finite walks lists and objects): a machine declaring one is invalid at create/validate time, so no run or spec graph can ever emit one (pinned in test_guard_values_must_be_finite — NaN, +inf, -inf, and a nested dict needle). - _cap_factory_frame fails loudly on a non-finite frame instead of emitting the token (json.dumps(..., allow_nan=False) belt; the loud error names the non-finite values) — pinned in test_a_non_finite_frame_fails_loudly. Both pins mutation-verified: removing the validation check fails the source pin, removing the belt fails the wire pin. * fix(tui): the factory view reads newest-first, the newest run selected on open * fix(tui): factory strings scrub terminal control bytes at the parse seam * fix(tui): the RLM child status notices render in the subagent-message class The operator's 2026-10-01 directive on the factory UX: "the factory child status notices should be like subagent messages not user messages". Factory children are kernel children (the executor admits them with rlm.spawn), and a child that settles without an explicit reply lands the daemon's terminal notice row in the parent transcript - the classic subagent-completion injection. The TUI dispatch classed those rows (rlm_child_terminal_notice / rlm_child_failure) as injected-prompt rows: the class of turn prompts (heartbeat fires, goal contexts), so every factory child's exit read as another user-style turn prompt between the user's own turns. The seam is the render dispatch (crates/pa-tui/custom_message/mod.rs), not the kernel: the factory executor emits no transcript rows of its own (factory.progress stays the deliberately-unported ledger-only milestone lane), so the Python runtime is untouched. The two notice types now decode to the agent_message component's class - the same quiet, counterpart-attributed row the children's own agent_message mail renders - with the child's session name as the counterpart and the full notice text as the expandable body, so nothing is lost. The notices still drive their followUp wake turn (the daemon's notice action is unchanged); only the transcript row's class moves. The 2026-09-23 diamond-row divergence is superseded and its render code retired. Tests: rlm_child_notice_rows_decode_to_the_agent_message_class pins the class for all three outcome rows (the failure, the no-reply exit, the cancelled exit) plus the pre-details content-token name fallback - mutation-checked by reverting the dispatch to the injected-prompt arm (the pin fails with "must decode to the agent-message class"). The old diamond-row decode/render pins are retired with the code. * fix(tui): the factory page mount holds the malformed-reply contract * docs(skills): agent-facing factory SKILL.md — authoring, running, and watching machine workflows The factory surface shipped with only the module docstring, a one-line harness digest hint, and the refinement planner bullet; an agent working in the RLM had nothing callable to learn the machine form from. This adds skills/factory/SKILL.md, the missing agent-facing reference: - what the factory is and the harness entry kind (create_factory validates either form at write time; an invalid spec is never stored); - a complete worked machine spec derived from the seed pr-manager shape (entry state, guarded branching, optional-input loop binding, bounded max_entries, resident lifecycle) and a dag-form example derived from review-sweep (one-join compilation, foreach expansion) — both validated against the branch's own validator; - the authoring reference: states/ports/transitions/guards, the required self-input rejection, joins, legal cycles, foreach, residents, the run block (max_parallel as a global budget, max_transitions pause/resume), failure policies, budgets, and the stall detectors; - the rlm.factory run/status/stop/resume/graph/watch API with a run-and-monitor snippet, the camelCase wire note, and the kernel-restart and preview-cap operating limits; - discovery pointers: the machine library (arriving on the stacked PR) and the TUI factory page (dock group, keys, Mermaid export). * docs(skills): trim the factory SKILL.md to a discovery stub pointing at rlm.factory.help() The user ruled that agents read the factory docs via rlm.factory.help(): the full authoring reference now lives in the kernel (embedded from this skill into prime-agent-runtime/src/rlm/factory.py on the factory-core branch), so the SKILL.md keeps a thin discovery stub to prevent two drifting copies: what the factory is and its harness entry kind, the machine-library and TUI-page pointers, and the prominent instruction to call rlm.factory.help() for the full authoring reference and API guide. * factory: rlm.factory.help() returns the embedded authoring reference The user design call for the factory surface: agents must get the factory documentation from rlm.factory.help() - no file-path reading. The fact-checked 282-line agent-facing reference (authoring rules, guards/joins/cycles, foreach, budgets and policies, stall detectors, and the API with worked examples) is embedded as the module-level constant FACTORY_HELP in rlm/factory.py, and the _RLMFactoryNamespace gains a sync help() -> str returning it - no filesystem resolution, so packaged kernels (where the repo layout is not adjacent) see the same guide. The factory module docstring and the namespace docstring both point at help() (help(rlm.factory) shows the API plus the pointer), and factory.py's __all__ exports the constant. The reference content is the skills/factory SKILL.md guide (frontmatter stripped) - the discovery stub on the stacked view branch points agents at this call. The worked examples bound their emitted payloads (a Macroscope review finding on the embedded guide): the pr-manager verdict caps its findings at three one-line findings and the review-sweep file list caps at the eight most relevant paths, because captured answers are capped previews (~160-200 chars) - an unbounded payload truncates at the cap and fails to bind. The example commentary states the rule; the Safety section keeps the compact-outputs guidance. Tests: test_factory_help_returns_the_full_reference pins the call-level surface (rlm.factory.help()): the section structure, the guard op set, foreach expansion, max_parallel as the run's global budget, the stall detectors, the stop/resume contract, the run/status/stop/resume/graph/watch snippet, and the bounded example payloads - mutation-checked by emptying the constant (the pin fails on '# Factory'). * feat(tui): per-stage agent occupancy on the factory diagrams The factory page reads as a page of machine diagrams with per-stage agent counts. The kernel's graph/status node rows carry `running`/ `queued` (admitted children in flight / prepared instances waiting for a parallel slot) in `_state_report` - the exact `status()` node shape, single-word keys riding the wire's camelCase conversion unchanged. The ASCII diagram's node labels render the counts (`reviewing (3 run · 2 queued)`, a stage at rest keeps its plain label, row colors unchanged), and the Mermaid export's node labels carry the same fragment so a pasted diagram shows stage occupancy; the TUI reads the kernel's counts with the instance rows as the older-kernel fallback. * fix(kernel): a done run with children in flight stays live in the unscoped graph Regression (Macroscope): the unscoped graph classified liveness by run state alone, so a `done` run whose resident child is still in flight (admitted residents never block completion - the finished milestone tells the operator to `rlm.factory.stop()` them) dropped out of the reply once it left the GRAPH_RUNS_WINDOW terminal history: the page lost the run and its stop control while the child kept running. Liveness is children-shaped: a run with any instance still in flight reports regardless of the window, and the window holds only runs with no child in flight. Pinned in test_a_done_run_with_children_in_flight_reports_live (reverting the children clause drops the run from the reply; a stopped run outside the window still leaves it). * fix(tui): the factory page rides the picker keyset, and the scale battery The copy-mermaid action is gone (the m key, the CopyMermaid action, the clipboard arm, the mermaid emitter): the kernel graph data is unchanged, so agents may still build a Mermaid diagram from rlm.factory.graph() - only the TUI button dies. The page rides the activity pages' picker keyset (arrows + Enter + Esc, the heartbeats/bash pages' family): the arrows move the run selection, Enter opens the selected run's in-page action rows (the offered set reads the run's live state, tracked by action identity so a fold clamps it - never a stale target), Enter runs the tracked action, Esc backs out or closes. The letters are not page keys. One defect found by the scale battery and fixed: the dock's count filtered on state alone, dropping a done run whose resident children still run - live by the kernel's own unscoped-list rule, still carrying its stop control. The count now reads the same one liveness rule (FactoryRunSnapshot::is_live). The scale battery: the seven-state pr-manager-shaped machine (the review/fix loop on guarded branches, the fan-in join, the resident watchdog) renders its whole shape; the fifteen-run world (live, stopping, paused, done-with-residents, failed, stopped; many instances at a stage) pins the liveness count, the selection stability across folds and trims, the render window slide at fifteen panels, the refresh tick's notice-worthy markers at scale (the occupancy drain, the fired-marker move, the active-node transition), the focused-run orchestration, and the rapid-churn coherence (interleaved keys, no dropped keypress, no stale-index action). * factory: an optional input binds the sentinel over every no-value source Review finding (PR PrimeIntellect-ai#3199, cursor Medium): _prepare_entry bound the optional null sentinel only while the source had NO settle. Once the source settled with an error, a missing port, or a failed JSON capture, the optional input still failed the dependent entry -- one failed fixer iteration or unparseable fix report killed the review/fix loop that optional inputs exist to enable. Binding now classifies the source first: a settled source either offers a captured value or a no-value failure; the optional form binds the null sentinel (and an optional foreach.over expands to zero items) in every no-value case, and only the required form fails over a settled-but- valueless source -- the required form still just waits while the source never settled. The authoring reference documents the decided errored- source semantics. Regression coverage: optional input over an errored source re-enters with the sentinel; an optional foreach.over over an errored source expands empty; an optional input over a settled source with no captured value proceeds while the required form fails ('has no captured output'); an optional input over a failed JSON capture proceeds while the required form fails ('no JSON object containing output'); a required input over an errored source still fails the dependent entry. * factory: run.max_children bounds total admissions; whitespace-only inline subagent fields are write-time invalid * factory: an opt-in gate -- `factory.enabled` (default off) refuses the namespace The factory ships disabled: the user turns it on with `/factory on` (the persisted `factory.enabled` setting in the agent dir's settings.json, the same nested-camelCase document the daemon and TUI settings surface write, shaped like compaction/agentTraces). While it is off, every `rlm.factory` call except `help()` -- run, status, stop, resume (and the later graph/watch) -- and every factory harness write (`create_factory`, `update_factory`, and the generic create/update/upsert paths, so a refinement edit cannot author a factory either) refuses with ONE exact clean message: "the factory is disabled; run /factory on to enable it". `help()` stays available so the embedded authoring reference is readable before opting in. The settings seam (`factory_enabled()` in factory.py) resolves the agent dir the way the rest of the runtime does and reads the file leniently like the Rust loader: a missing file or key, a wrong-typed value, or a corrupt document all read as the disabled default -- fail-closed, a clean refusal, never a crash. The refusal precedes spec resolution (run) and spec validation (creates/updates), so the disabled message is unconditional while off. Deleting factory entries stays available: cleanup is not authoring or execution. FACTORY_HELP's opening documents the enabled/disabled contract with the /factory on|off|status pointer and the exact refusal. Pins (mutation-checked) in test_factory.py: FactorySettingReadTest (the real settings-document round-trip; missing/wrong-typed/corrupt reads stay disabled; removing the fail-closed default fails it), FactoryOptInGateTest (empty settings refuse run with the exact message and start nothing; status/stop/resume refuse; create_factory refuses before spec validation and persists nothing; generic updates refuse; delete stays available; help() answers while disabled; enabled settings -> run proceeds through the scripted kernel; removing the run gate or the harness gates fails the refusals), and FactoryHelpTest's opt-in assertions (emptying the guide's contract fails them). The shared executor setUp isolates the agent dir and writes the enabled setting in the real shape, so every executor test runs through the live gate; test_harness's module setup installs the same enabled setting for its factory CRUD tests. * factory: configured subagent names label children; the guide matches the namespace Two review threads on the port, both real: - The inline subagent `name` was accepted at write time and then dropped: _resolve_subagents discarded it and _admit always built the generated _child_name, so a state configured with {"prompt": ..., "name": "reviewer"} spawned sw-reviewing-<run>-i0 instead of reviewer - the label agents message the child by. The name now rides through run creation to the spawn call: the state's first instance is named exactly the configured name, and later instances (max_entries re-entry, foreach fan-out) and retries keep the generated label's -i<n>/-a<n> suffixes, because one state's settled children stay registered for the run's life and the supervisor rejects duplicate sibling names. Write-time checks keep a persistable factory spawnable: the name is capped at the host's 64-character subagent-name limit (SUBAGENT_NAME_MAX_LENGTH), and names duplicated across states - which would collide at spawn time - are rejected like duplicate state ids. - FACTORY_HELP advertised `rlm.factory.watch(...)` and `rlm.factory.graph()` examples, but the namespace ships run/status/stop/resume/help only, so an agent following the guide hit AttributeError. Those views are the stacked live-view PR's kernel surface (a 675-line implementation), so the smallest correct fix here is the exact invariant: the guide now documents only what the namespace has (the call examples are dropped, the gate sentence and the safety bullet match, and the views are named as roadmap prose), pinned by a test that extracts every dotted rlm.factory.<call>(...) the guide teaches and asserts it exists on the namespace. Mutation-checked: reverting the spawn-side or resolve-side name use fails the executor pins; removing each write-time check fails its validation pin; re-adding a watch() example fails the guide/namespace pin. Tests: cd prime-agent-runtime && uv run python -m unittest test.test_factory (174 green); the full unittest discover run differs from HEAD only in the pre-existing test_bash journal and agent-message failures (verified at 8151b72). * factory: the guide's activity-lane bullet names the stacked view PR's wire The bullet documented the camelCase factory_activity wire (runId, specId, timeoutMs) in the present tense, but that lane is the stacked live-view PR's surface - this branch's milestone notices ride factory.progress with snake_case keys. Same accuracy class as the watch/graph trim in the parent commit: the guide describes what this branch ships, and names the stacked surface as roadmap. Tests stay green (174). * factory: suffixed spawn labels fit the host cap; shadowed names rejected at write time Second review round on the configured-subagent-name fix, two findings (both real): - A configured name at the 64-character cap fits its first instance, but the -i<n>/-a<n> suffixes for re-entry, foreach fan-out, and retries can push the label past the host's spawn-name cap, so every later admission of an otherwise valid state failed. _spawn_label now builds the suffix first and shrinks an overflowing base with a digest of the full name - exactly the generated label's state-id token trick - because a bare truncation could collide for two long names sharing the truncated prefix; distinct names stay distinct and every admission fits. - A state configured as foo-i1 passes validation but collides at spawn time with foo's later instances. The write-time duplicate check now rejects a name another configured name can suffix onto, in either order (the never-generated -i0/-a1 do not shadow and stay valid). Pins (mutation-checked, each fails when its check is reverted): SpawnLabelTest.test_suffixed_labels_stay_within_the_host_cap (cap fit, digest distinctness, retry and combined suffixes) and the shadowing cases of test_inline_subagent_name_length_and_uniqueness. The guide's name contract states both rules. Tests: cd prime-agent-runtime && uv run python -m unittest test.test_factory (175 green). * factory: the shadowing error names the state that generates the labels Cursor review finding (Low, real): the write-time shadowing check matched both collision directions but always attributed the -i<n>/-a<n> labels to the earlier state's name. When the shorter name is declared second, the current state's name generates the suffixed labels and the earlier state's name is one of them - the message claimed the reverse. The check now resolves the direction first: the earlier-name-base case keeps its message, and the current-name-base case says this state's name suffixed by re-entry, foreach, and retries collides with the earlier state. Pin (mutation-checked: swapping the messages fails it): the reversed declaration-order case of test_inline_subagent_name_length_and_uniqueness asserts the exact attribution, and the forward case pins its own. * factory: the opt-in gate's client surface — /factory on|off|status, the shared settings key, the lane advertisement gate The factory's client surface for its opt-in gate: the /factory [on|off|status] client command (the /nightly family) persists factory.enabled through the pa-core settings manager — FactorySettings shaped like agentTraces in the agent dir's settings.json, the same nested-camelCase key the kernel's factory gate reads — plus the ClientSettings trait pair and the CLI setting macro. The daemon's factory_activity lane advertisement reads the same setting fresh per connection (advertised_server_capabilities filters the lane while the factory stays disabled, on both hello surfaces — the supervisor's and the worker's), and the dock's factory group mounts exactly while the daemon advertises the lane (ActivityDock::factory_group, driven by the hello's serverCapabilities). * factory: complete the opt-in gate — the merged-tree reconciliation, the pins, and the docs The merged tree's gate completion: graph_factory and watch_factory join the namespace's require_factory_enabled() gates and the out-of-band activity seam gates at its top (the lane's run action would otherwise bypass the namespace's gate), so every gated surface refuses with the one message while factory.enabled is off. FACTORY_HELP teaches the graph()/watch() call examples this branch's namespace ships (the guide-matches-namespace invariant stays: every dotted call the guide teaches exists on the namespace), and the guide's TUI bullet matches the shipped keyset (arrows + Enter + Esc; the old j/k/s/r/m letters never were page keys). The kernel usage block unifies behind _usage_report (the merged status snapshot carried the core's max_children field while the graph's shared report lacked it — the graph-equals-status fusion pin caught the drift). Pins: the daemon lane advertisement (no settings -> no lane; enabled -> advertised; disabled again -> gone), the dock's unadvertised-lane contract (no row segment, no traversal, no click region), the headless /factory e2e (the status arm's disabled default, the on/off round trip through the settings seam, the usage line, and no factory group while the lane is unadvertised), the pa-cli seam round-trip's settings-file content assertions (factory.enabled true/false beside the other keys), and the kernel gate's graph/watch/activity refusals. Mutation-checked: removing the advertisement filter, the dock group condition, the panels wiring, the settings write, or each kernel gate fails its named pin. The skills/factory SKILL.md stub documents the opt-in contract (default off, /factory on to enable, help() answers while disabled). * factory: the review round — the gate's global-scope read, the graph's run block, and the view's fold/caret/dead-flag fixes Macroscope and Cursor findings on the opt-in head, all real: - get_factory_enabled reads the GLOBAL scope only (Macroscope): the merged global-and-project read could diverge from the kernel's agent-dir-file gate — a project .prime/agent/settings.json with factory.enabled could advertise the lane or flip /factory status while the kernel refused (or the reverse). The getter now reads the exact document set_factory_enabled writes and the kernel gate reads; pinned by factory_enabled_reads_the_global_scope_only (a project override never enables; the write lands in the global file). - Both hello surfaces read the gate behind spawn_blocking (Macroscope): the settings file's metadata and locked read never blocks an executor thread on the handshake path (the daemon's settings-read posture); the read stays fresh per connection. - The graph's machine block carries run.max_children (Macroscope): _machine_structure omitted the total-admission limit, so a consumer could not reconstruct the validated configuration; pinned by the fuses test's default and the declared-value graph test. - The malformed fold keeps the dock's cached reply (Cursor): a reply without the runs list used to clobber factory_graph anyway, zeroing the dock's live-run count and a remount's cache; the fold now takes only well-formed batches (fold_factory_reply), pinned by the fold test (mutation-checked). - The factory page suppresses the stale editor caret (Macroscope): frame_cursor now treats the open factory view as an input-less overlay (the settings menu's guard family), pinned by the_factory_page_suppresses_the_stale_editor_cursor. - The dead factory_view_open_flag is gone (Cursor): written at four sites, read nowhere — the close arm and the rebind fold key off factory_view_session and view.factory_view, and the key dispatch reads view.factory_view. * factory: /factory off refuses while runs are live — the lifecycle guard kevinjosethomas's finding on the retargeted head: /factory off while a run is active writes factory.enabled=false and strands the run — the kernel control loop is not gated by the setting (it keeps admitting and collecting children), but every factory surface (rlm.factory.stop/ status/graph, the activity lane, and the page after the next client start) refuses, so a running factory loses its stop and visibility path until the factory is enabled again. The guard: /factory off refuses while the session's kernel reports live runs, naming the count and the stop path (the page's stop action or rlm.factory.stop); the runs stop first, then the off write lands. The count rides live_factory_runs (session_ui/factory.rs): one bounded factory_activity graph request through the lane — the same lane and the same is_live liveness rule the dock's count reads (a live state, or children still in flight, so a done run with resident children counts exactly like its panel and its stop control). The unadvertised-lane and query-failure classes (an older daemon, no kernel booted) cannot carry live runs, so those proceed unchanged. Pins (factory_command_headless.rs, the mock daemon advertising the lane and answering the graph request a real kernel would): the refusal names the live count (a running run and a done-with-residents run both count; a fully terminal run never blocks), the gate stays enabled after the refused off, and the off proceeds once no run is live — mutation-checked: removing the guard fails the refusal pin. Riding along: the base-flip merge's composition repairs (the factory arm's match closers, the shared-import wrap) and the /telemetry command's ClientSettings stub seam (telemetry_command_headless.rs). * factory: the macroscope round — the guard-value key rule and the preflight's null machine Two review findings, fixed at the branch's own seams: - _value_is_finite (rlm/factory.py) checked a guard comparison value's floats recursively but never its object KEYS: a dict with a non-finite float key or a non-string key passed validation, then broke the reply frames at the wire — json.dumps(..., allow_nan=False) raises on the NaN key (the lane answers the wire-cap error instead of the graph), and a non-string key is either coerced (the wire object no longer matches the declared machine) or rejected outright. The dict branch now requires string keys, the same source-invalid rule as the non-finite floats. - FactoryHost::spec_model_selectors read machine.or(dag) without the null-is-absent filter every settled seam makes: a stored {"dag": ..., "machine": null} (a dag-form edit the planner accepts and only the /refine writer can store) read as Some(Value::Null), the fall-through to the dag never ran, and the preflight answered no declared models — exempting the dag's inline model selections from the catalog, allowlist, and auth checks before any child spawned. Both reads now filter null exactly like refinement::planner. Pins: test_guard_value_object_keys_must_be_strings (a NaN float key, an int key, and a tuple key reject; string keys stay valid) and a_null_machine_falls_through_to_the_dag_spec (the dag form's declared models preflight; the null-dag mirror; the allowlist gate refuses the run end to end) — both mutation-checked: reverting either fix fails its pin. * factory: the second review round — the off guard fails closed on an unreadable count, the preflight leaves the executor - /factory off's guard now fails closed when the lane-advertised client cannot read the live-run count: a timed-out or malformed graph reply cannot prove zero live runs, and an unknown liveness must not open the gate — the refusal names the unreadable count and the retry, and the gate stays enabled. A client whose hello never advertised the lane keeps the fail-open read (an older daemon has no executor to protect; a client started before /factory on already sees the frozen hello the settled surfaces rule describes), and the count query itself still rides regardless of the advertisement — the hello-predates-the-gate client counts live runs exactly like the advertised one. - The factory run preflight (engine.rs) reads the harness states, the model catalog, and the auth caches from disk — that blocking work now rides tokio::task::spawn_blocking instead of the async lane, the daemon's established settings-read posture (the advertisement gate's hello reads), so a stalled filesystem can never stall the worker's other activity. FactoryHost is Clone for the move; the semantics are unchanged (an executor-topology fix, so no behavioral pin — the preflight batteries still pin the resolution, allowlist, and auth outcomes). Pins: factory_off_refuses_when_the_advertised_lane_cannot_count (the malformed-lane refusal, the gate stays enabled; mutation-checked: removing the fail-closed arm fails the pin), the two None-path pins split by advertisement (the unadvertised client proceeds on an unreadable count; the hello-predates client still counts), and the existing live-count pins unchanged. * factory: the guard-value traversal stops at a cycle A self-referential list or dict passed as a `contains` guard value made `_value_is_finite` chase the cycle to a RecursionError — the write path crashed instead of answering the validation error. The traversal now threads the per-branch ancestry and rejects a container it is already inside: the encoder refuses circular references outright, so a cycle can never be a valid comparison value. A shared-but-acyclic reference (the same object appearing twice, a diamond) stays valid — each branch checks it independently, so only a true ancestor repeat reads as a cycle. Pinned in test_guard_value_cycles_reject_without_exhausting_the_stack: a self-referential list and a self-referential dict each answer the finite validation error, and a diamond stays valid. Mutation-checked: disabling the ancestry check fails the pin with the RecursionError. * factory: the preflight resolves a subagent reference the kernel's way — tier by tier, id then title The preflight's reference lookup swept the id across both harness stores before any title scan, so a global ID answered before a local TITLE of the same name — while the kernel's own resolution (verified against the kernel: its harness get answers the id before the title scan, within the tier it reads) spawns the local titled subagent. The preflight then validated, allowlisted, and authenticated the GLOBAL id's model while the run spawned the local titled subagent's model — the allowlist pin judged the wrong model, and a blocked model could spawn. The lookup now walks the kernel's order: for each tier (local, then global, filtered by the reference's scope prefix) the id, then the title, before the next tier — so a local title wins over a global id of the same name, a local id still shadows a global id, and a scoped reference still resolves its own store exactly as before. Pinned in a_local_title_wins_over_a_global_id_of_the_same_name (factory_host/tests.rs): a local TITLE "researcher" against a global ID "researcher" reads the local titled subagent's model, a gate allowing only the global id's model refuses the run, and a gate allowing the actual model passes it. Mutation-checked: restoring the id-first sweep fails the pin (the selectors read the global id's model). * factory: the preflight's title match takes the kernel's list order, and guard-value leaves stay JSON Two more review findings at the branch's own seams: - Among duplicate titles within a tier, the preflight's title scan now picks the (path, id)-least entry — the kernel's own title scan reads `harness.list`, sorted by (kind, path, title, id), so among same-title entries it picks the (path, id)-least; the preflight's old map-iteration find offered whichever entry the store's iteration happened to surface first, validating (and allowlisting) one metadata.model while the kernel executed another. - `_value_is_finite` now rejects leaves outside JSON's scalar set: a tuple (or a set, bytes, any container the JSON grammar has no spelling for) used to pass validation and serialize as something other than the declared shape — an array — carrying any non-finite float inside it past the finiteness traversal to the saved spec and the wire frames, where the strict consumers reject the whole frame. The validation message now names the JSON-shape rule (the finiteness wording covered only the float class). Pins: a_duplicate_title_resolves_the_kernel_list_order (same-title locals resolve the (path, id)-least entry's model; mutation-checked: max_by instead of min_by fails the pin), and the object-keys pin extends to the non-JSON leaf shapes — (float('nan'),), a plain tuple, a set, and bytes each reject (mutation-checked: restoring the True fall-through fails the pin). * factory: the guard-value depth bound and the local-title test's local spec Two macroscope threads on the retargeted head (both follow-ons to the just-closed guard-value/preflight rounds): - _value_is_finite descended one recursion per nesting level with no bound: a deep-but-ACYCLIC container (the cycle rule's other half) chased the nesting to the interpreter's stack -- RecursionError on the write path instead of the validation answer (measured crash floor: ~499 levels at the default recursion limit, ~2 frames per level). The traversal now bounds nesting at MAX_GUARD_VALUE_DEPTH (256, half the crash floor, generous past any real guard value): every downstream seam recurses per level the same way (the snapshot's deepcopy, the wire conversion, the reply frames' encoder), so a value past the bound cannot ride any of them. The rejection joins the one finite-JSON-data message (which now enumerates the depth rule). Pinned: test_guard_value_depth_rejects_without_exhausting_the_stack (a 306-deep value answers the validation error, a shallow nested value stays valid; mutation-checked: without the bound the deep value passes validation and the pin fails). - a_local_title_wins_over_a_global_id_of_the_same_name stored its spec only in the GLOBAL harness while the titled subagent lives locally: the kernel's unprefixed spec lookup reads one store (its harness get has no local-then-global fall-through -- verified against harness.py's get: _global_target(global_=False) is None, so the unprefixed lookup answers the local entries alone), so the run the end-to-end allowlist pins judge would reject as an unknown spec before any spawn. The spec entry now also lives in the local harness the test configures -- the bypass scenario is a run the kernel actually accepts. The host's spec_model_selectors doc line claimed the harness's unprefixed get shares its local-then-global tier order; corrected (the host is deliberately broader; the kernel's run() stays the authority). * factory: the no-kernel off read is a definitive zero, and the wire cap never drops a live run * factory: the page's picker keys paint on the key — the input loop's render contract, pinned end to end * factory: the overlay's activity counts the instance layer - a failed foreach entry's running siblings keep the stage active, pinned at both seams --------- Co-authored-by: Sebastian <sebastian@primeintellect.ai>
* fix: detach daemon and update restart from client terminal session * test: pin the launcher client env and bound the detached-daemon cleanup the launcher e2e now runs ensure_daemon_running_with in a re-exec'd client process with PRIME_AGENT_CODING_AGENT_DIR, TMPDIR, and the worker scrub list pinned on its spawn, so the launched supervisor writes into the fixture instead of the ambient agent dir (verified: the old shape leaked auth.json, logs, and daemon-workers into a poison dir); the detached-daemon guard now waits bounded for the socket to stop accepting after the shutdown command and SIGKILLs the supervisor's process group when it never exits. * retrigger ci after a registered shard-4 flake
* input goes live at first paint — the session open runs beside the run loop The operator's input-latency report (2026-10-03): the first frame lands fast, but time-to-type waits on the whole open pipeline — warm it measured ~174ms past first paint (isolated probe), and on cold launches the create/attach/dock folds stretched that to 0.6-1.6s. Root cause: the port gated the ENTIRE run loop on the session open; TS `init()` runs `ui.start()` (the input surface is live) and awaits `rebindCurrentSession()` on an already-serving event loop. - pa-tui: `run_interactive_surface` starts at PAINT (a NEW chat's chrome mounts before the open, as today); `SessionUi::open` runs as an in-flight background task; an OPENING loop serves editor-level input (editor.handle_input, paste, resize) at the run loop's own MIN_RENDER_INTERVAL cadence and queues everything the session owes (submits, commands, overlay keys, mouse) in the existing pending VecDeque, in order — the first queue parks the echo path so the editor always holds a prefix of the input stream. On open completion ONE fold runs today's post-open block unchanged (stats/rebuild/notices/dock folds — the zero-shift dock ruling kept) and the run falls into today's main loop, draining the queue first: a typed-ahead submit is held and delivered at the fold, never dropped. app.exit (ctrl+d) on the empty editor still exits immediately. One carve-out: a first-run onboarding launch keeps the base input ordering — the login/trace panes own the input channel after the open (the phase needs the session, so it cannot run during the opening phase), and the opening loop leaves the channel to them instead of serving the editor. - pa-core (operator ruling): after the eager kernel build completes, the prewarm kicks the kernel's generic-MCP open in the background (the bounded mcp_tool_listing the connections view uses). The join point stays at first use: an early tool call joins the in-flight open and succeeds, bounded, never an error. - pa-daemon: the passive scheduled-jobs snapshot warms at daemon boot (the scan spawns beside the other boot passes, before the accept loop serves), so the first client heartbeats_list serves the warm snapshot instead of scanning the session-artifacts tree inline inside the client's open. Invalidation semantics unchanged; a client that connects before the scan lands joins it exactly as today. Red-first verifiers: a real-pty e2e (crates/pa-cli/tests/ input_live_open_pty_e2e.rs) against a mock supervisor whose attach stalls 2.5s — keystrokes must echo while the open is stalled (an ordering oracle: the echo frame precedes the attach's content frame in the byte stream) and the typed-ahead text + queued Enter must submit once the session lands; kernel_mcp_settle.rs pins the kernel-boot join (a mid-boot tool call waits the in-flight build and succeeds) and the MCP settle (the configured server spawns with no user use anywhere; the first call, issued mid-handshake, waits and succeeds); the scheduling_catalog unit proves the boot warmup serves the first read without an inline scan. Measurement (the conductor's input-live probe, renderer-grid echo, matched pair, send_at=0): before (base 2b962da) 411ms first client on a fresh daemon / 226-227ms warm-scanned; after 61-64ms on every trial (the daemon warmup covers the first client; echo-after-frame 193ms -> 28ms). * the opening phase honors the keymap and the stash; the eager MCP settle leaves the cell queue Pre-bar review wave (three findings on the pushed head): - pa-tui (keymap-blind opening dispatch): the opening loop's editor echo now consults the effective keybindings — a key the post-open dispatch would claim for an app/session action queues behind the open and dispatches through the full keymap-aware ladder at the fold, instead of echoing into the editor as a stray motion (default `left` on the empty editor is `app.agents.back`, not a cursor move; any user-bound single char or space runs its action). The claim set is the fresh-state projection of SessionUi::handle_key's ladder (`opening_echo_key_claimed`), pinned by unit tests for the default and the user-bound shapes. - pa-tui (stash/early-input ordering): a session-reopen launch never accepts typed-ahead into the editor during the open. The fold's `restore_prompt_stash_on_open` gates on the editor being empty, so pre-restore input would silently consume a stashed draft's restore — and the opening exit arm would exit the session on Ctrl+D against the pre-restore empty editor. Reopen typed-ahead queues and dispatches after the fold's restore (base ordering exactly); new-session launches (no stash can exist) keep the echo path. Pinned by a two-run headless e2e: draft -> agents-view auto-stash -> stalled reopen with Ctrl+D during the open -> the draft restores before the queued exit key evaluates and the run stays open. - pa-core + the kernel runtime (eager MCP status on the cell queue): the eager settle's mcp_status request rode the manager's single execution queue, so a slow or unreachable server parked the user's first python cell behind its handshake for up to the per-server timeout. Both halves now have a dedicated lane: the kernel runtime's serve loop dispatches mcp_status to a concurrent task (the registry's per-server locks still join an early user call onto the same in-flight open), and the host's mcp_tool_listing resolves its done event by request id without taking the execution slot (the factory-activity lane's shape), bounded by the per-server timeout plus a settle margin. Pinned red-first: with the settle's listing provably in flight on a hanging server, the first python cell completes without waiting (the queued shape waited 8.5s out the handshake). * the boot warmup waits out the adopt pass; the kernel tests explain their lock allow Two bot-thread clears from the review of bd21410: - pa-daemon: the passive-catalog warmup now waits for the boot's adopt pass (a watch signal; fail-open when the sender is gone) before scanning. The scan's live-worker filter consults the registry, so a scan racing adoption would cache the just-adopted worker's artifacts as a passive row and serve the stale row for the snapshot's refresh window — adoption never invalidates the catalog. A plain startup's adopt pass is ms-scale, so the snapshot still lands well before the first client read. Pinned: with the adopt signal unfired the snapshot never lands; once fired, it does. - pa-core: the kernel tests' `clippy::await_holding_lock` allows now carry the reason next to the attribute (LIVE_KERNEL_LOCK is process-global env serialization — the lock's own doc), matching the crate's explain-exceptions-next-to-the-allow rule. * the adopt-order pin asserts the waiter, not the scan's absence The cursor bot's methodology finding on the new ordering test: a fixed 150ms sleep followed by an empty-snapshot assertion was the pass condition, not a failure bound — a buggy helper that returned early could still pass on a slow runner while the empty-tree scan had not stored its snapshot yet. The negative pin now sits on the waiter itself: with the adopt signal unfired the helper must stay PENDING through the whole window (an early return finishes in microseconds and fails the assert deterministically; a correct helper can only return on the signal or the sender's death, neither of which happens). The positive pin keeps the poll-with-deadline scan assertion and adds a bounded waiter completion. --------- Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
* send the json content type on provider requests * keep the content-type comments to current behavior
…i#3347) * pa-daemon: free the ACP prompt slot before the response leaves The ACP turn settle sends its response through the async FrameSink and only then clears the connection's prompt slot: the writer task can put the response on the wire while the turn task still waits on the connection mutex, so a client that prompts again the instant it reads the response races the slot clear and gets the still-set slot's 'A prompt turn is already running' refusal — the CI-wave flake shape behind acp_threshold_auto_compaction_publishes_the_compaction_meta's second prompt (registered red-acp-auto-compaction-20261002-1, stopReason null at acp_mode_e2e.rs:1729). Clear the slot before every settle-path send, matching the daemon-attached path's release_turn_slot ordering. The turn-failure text now also echoes to stderr (a stop-reason assert loses the internal_error message), the failing test prints the whole response envelope on mismatch, and a new test opens the admission window on twenty sequential prompts. * state the admission contract in the settled-prompt test's doc comment
* fix misleading clipboard success when tmux blocks osc52 * fix clipboard copy routing through local tools and tmux * fix unconfirmed clipboard reporting and e2e expectations * skip remote clipboard helpers before terminal forwarding * retain oversized remote clipboard helper fallback * report the oversized remote clipboard fallback honestly * pin the remote helper outcome with a regression test and trim the duplicated comment * drop the history-narrating comment from the remote helper regression test * drain tmux probe pipes, forward to the pane's client, and stop blaming tmux for oversized copies * pin the probe drain with a pipe-filling regression test * trim the probe drain doc to its current contract * gate the clipboard cap by length and encode only for the osc52 write * satisfy the workspace clippy gate: backtick pipe_to, div_ceil, use before statements * read set-clipboard from its server scope so forwarding survives legacy tmux * retrigger ci after a shard-4 timing flake * retrigger ci after a registered shard-4 flake * pa-cli: settle the slash-menu dispatch race on the catalog refresh landing (flake fix) * state the catalog barrier's two cases * retrigger ci after a registered wave-b10 flake
… gaps (PrimeIntellect-ai#3303) * fix(kernel): share in-flight startup across first touch and teardown * fix(kernel): close the panic wedge and the stop-gate gap in the shared startup Review follow-ups on PR PrimeIntellect-ai#3257 (both blockers independently verified by both adversarial reviewers at 21da7aa): - F1 panic wedge: a panic in run_startup or a progress listener skipped the memo clear and the done send, which closed the watch and wedged every later ensure() on the dead memo. The boot now runs on its own JoinHandle, so the publisher always clears the memo and sends the result, converts a panicked boot into a "kernel startup task failed" failure, and clears the listener state so the next ensure() boots fresh (self-heal; the memo re-arms on close). The watch now carries the startup result itself: joined callers keep the manager even when a concurrent stop takes the provisioner's live owner (TS managerPromise), and a dispose-raced boot rejects with the honest disposed-startup cause instead of handing callers a dead manager. - F2 stop-gate gap: stop_kernel took the manager and installed the pending_stop gate in two separate lock passes; a revival ensure in the gap captured the previous gate and could restore against a snapshot still being flushed. The gate is now installed in the same lock block that takes the manager. - F3 dispose fixture vacuity: the dispose leg pins the absolute interpreter count at dispose-return (exactly one spawn; the aborted retry never ran) in addition to the stasis assert. - Fixtures: a panicking progress handler must not poison the next boot (the re-arm regression leg), and a revival boot must wait for the in-flight stop's held host request before its first progress stage (exact ordering barrier, not flush timing). * fix(kernel): close the provisioner's four residual mid-boot lifecycle gaps Staged hardening set for the PrimeIntellect-ai#3257 shared-startup head (the design consult's unresolved-risk list + the Macroscope 07:10/07:15 R3 residuals, both fresh reviewers dispositioned pre-existing and recommended this follow-up): - Atomic settle/publish: the publisher's disposed check, memo-generation check, listener teardown, and manager park now share ONE lock scope (TS ensure()'s `managerPromise === startup` guard). A stale publisher can no longer clear a NEWER memo armed underneath it - a defunct clear inside the park->publish->clear window used to wipe the newer memo and arm a transient duplicate kernel - and a boot can no longer park a live kernel into a provisioner that reported itself torn down between the old separate check and publish scopes (the dispose TOCTOU). - kill() clears the startup memo (TS kill() clears `managerPromise`): a boot doomed mid-flight is killed by its own settle instead of parking a resident kernel nobody owns; the next ensure() boots fresh. - Concurrent stops of one in-flight boot share the first stop's gate (`pending_stop_for_startup`): the take-losing stop task used to open a fresh gate before the winner's final snapshot flush, un-gating a revival to race that flush over the same on-disk file. The take-loser that still exists (against a direct-arm stop) now waits the currently armed gate before opening its own. Fixtures: kernel_startup_memo.rs (stale-publisher generation survival; kill-during-boot; dispose-during-boot end-state pin; kill vs PrimeIntellect-ai#3257's panic-cleanup re-arm) + the concurrent-stops fixture in kernel_stop_revive.rs. All drain-boundary observables on the single-threaded test runtime (the settle chain runs in one scheduler drain; no sleeps, no interposition into wake chains). Failing-first verified on the pre-fix head: the kill/stale fixtures delivered a LIVE resident kernel; the concurrent-stops fixture caught the loser's early gate open in the settle drain. * test(kernel): the two bot-finding fixtures (fail-first shapes) The Macroscope/Bugbot findings on the stopgate-hardening head, as drain-boundary fixtures BEFORE the fix (RED by construction; the fail-first receipts are the ffix-branch gates cited in the fix commit): - superseding_stop_after_kill_cannot_deadlock_the_revival_gate: a stop armed for one boot, kill(), a revival gated on that stop's gate, and a second stop of the revived boot that supersedes it; the take-losing first stop waiting the currently-installed gate forms the wait cycle (first stop -> second stop's gate -> revival boot -> first stop's gate) - the oracle is the bounded settle, so the cycle shape wedges against the 30s timeout. Absolute spawn count pinned. - doomed_settle_keeps_the_newer_boot_listener_state: a doomed boot settling against a newer memo generation must not wipe the newer boot's progress listeners/replay stage - a late joiner of the newer boot replays that boot's current stage to a fresh handler; the gated counting wrapper holds each boot's handshake at a file barrier (the wrapper reads its ordinal from the count file, so the count file is pre-created; a Drop guard opens every gate on any exit so a wrapper never outlives the test as an orphan), making the settle ordering a test-controlled barrier, never a timing race. * fix(kernel): chain superseding stop gates and scope the settle's listener teardown The five Macroscope/Bugbot findings on the stopgate-hardening head reduce to two root causes; both fixed, with the drain-boundary fixtures that failed first on the unfixed tree (RED receipt: gate_1790910402 on the fixtures-only tree e3b92c4 - fmt rc0, clippy rc0, test rc101 with EXACTLY the two new fixtures failing at their designed oracles: "a late joiner must replay the active boot's stage" and "the first stop settled (no wait cycle)", both bounded-settle timeouts; everything else green; the compile-caught authoring attempts gate_1790908504/ gate_1790908744/gate_1790909006/gate_1790909694 are the fixture authoring cycle, incl. the counting-wrapper ordinal-read-before-create bug that made one early RED vacuous): - The stop-gate CHAINING (main's PrimeIntellect-ai#3257 shape) dropped by the JOIN: arm_stop now captures the gate the arm REPLACED and the stop task waits THAT before opening its own - never the currently-installed gate. The installed-gate design missed the supersede window (the take-losing second stop's loser saw its OWN gate installed and opened before the first direct stop's final snapshot flush settled, un-gating a revival to race that flush) and admitted a three-task wait cycle (a stop waiting a superseding gate that waits a revival that waits the first stop's gate). The chain is strictly-older-only, so acyclic by construction; the same-boot JOIN is unchanged (the second stop of the same boot still never arms). - The settle's listener teardown is generation-scoped: the doomed boot clearing startup_listeners/last_startup_message unconditionally wiped a NEWER memo generation's progress listeners and replayed stage. The clear now runs only when the settling memo is the active generation (or no memo is armed at all - the stale-entries case). * fix(kernel): drop the redundant gate clone; relax the fixture's replay assert - arm_stop installs the arm's own receiver directly (clippy redundant_clone: the clone was the value's last use after the Armed struct stopped carrying stop_rx). - the listener fixture's replay oracle asserts the replay FIRES with the active boot's current stage; pinning the exact stage string made the oracle flaky against legitimate later stages the boot emits around its held handshake ("Preparing Python runtime..."). * fix(kernel): generation-scope the startup progress emit The 4880ab3 settle fix scoped the LISTENER TEARDOWN to the active memo generation but left the EMIT unscoped, and its own fixture proved it: doomed_settle_keeps_the_newer_boot_listener_state failed 3/3 on the fixed tree (replayed "Preparing Python runtime..." instead of "Starting Python kernel...") - a boot kill() invalidates still emitted its later stages, overwriting the newer generation's last_startup_message and firing the newer boot's listeners with the wrong boot's stage. The emit now threads the boot's memo generation through run_startup/start_kernel/start_kernel_impl and writes the shared progress state (last_startup_message + the listener fan-out) only when the emitting memo is still the ACTIVE generation; the boot's own on_progress handler keeps firing unconditionally (it is its caller's, not shared state). Also drops the redundant stop_rx.clone() that red-inked CI's clippy job on the swarm push (redundant_clone at the arm_stop install). Gates (keeper worktree, CARGO_TARGET_DIR=.cargo-target-keeper-3303): fmt rc0; clippy --workspace --all-targets -D warnings rc0; kernel fixtures kernel_startup_memo 5/5 + kernel_stop_revive 5/5 (the previously-red doomed_settle fixture green); pa-core lib 1043 passed, 1 failed - workspace_snapshot::head_tree_baseline_excludes_only_absent _skip_worktree_paths, a git-2.34.1 sparse-checkout artifact reproduced outside the lane (git 2.34.1 marks root files S after `sparse-checkout set --cone`; CI's newer git passes it; the PR does not touch workspace_snapshot). * fix(kernel): clear the killed generation's shared progress state at kill() FINDINGS-1 from the pre-push review (gpt-6-sol cross-model reviewer, report /home/ubuntu/handoffs/REVIEW-3303.md): kill() invalidates the startup memo but retained the killed boot's last_startup_message and startup_listeners, so the next ensure() replayed the killed boot's stale stage to a fresh handler and the newer boot's emits fanned out to the killed generation's listeners - the 5e3037a emit gate stops the doomed boot's own emissions but not the B-to-A delivery or the stale replay. kill() now clears both shared fields in the same lock scope that invalidates the memo (the same stale-entries reasoning as the settle's generation-scoped teardown), and the listener fixture grows the transition oracles the reviewer asked for: A's joiner's collector must show nothing after the kill, and the newer boot's handler must show exactly its own stage twice (fan-out + boot-local, the pre-existing main shape) with no stale replay first. RED receipt: with only the kill-clear reverted, the fixture fails at "the killed generation's listener must not hear the newer boot" (kernel_startup_memo.rs:531); restored, all 10 kernel fixtures pass. Gates: fmt rc0; clippy --workspace --all-targets -D warnings rc0; pa-core lib 1043 passed, 1 git-2.34.1 sparse-checkout artifact (workspace_snapshot, untouched by this PR, green on CI's newer git). * fix(kernel): dead generations do not retry and do not surface restores Macroscope's follow-up finding on 5901a45 (thread r4162712975): a run_startup task whose memo kill() invalidated still consumed its retry - each attempt spawns an interpreter and runs restore/bootstrap, and the stale attempt's settle only killed its kernel, leaving duplicate kernel work and stale restore notifications/state against the fresher generation. - The retry loop now checks boot_generation_is_live (memo still the armed startup generation AND provisioner not disposed) before each retry AND again after the backoff, so a kill() during the backoff window cannot resurrect a dead generation's boot. The disposed arm of the old guard folds into the same check (dispose() does not clear the memo, so both conditions are needed). - The restore surface (on_restore + last_restore) is now generation-gated with the same predicate: a boot kill() invalidated restores its snapshot only for the settle to kill its kernel, so its restore must not fire the notification nor pollute last_restore. Fixture: kill_during_the_retry_backoff_pins_the_spawn_count - a fail-first flaky wrapper (first invocation exits 1 before the handshake; later invocations exec the real kernel python), kill() during the backoff window, and the absolute spawn count pinned at 2. RED receipt: with the memo check removed the fixture fails 3 vs 2 (the doomed boot consumed its retry); restored, all 11 kernel fixtures pass. Gates: fmt rc0; clippy --workspace --all-targets -D warnings rc0; pa-core lib 1043 passed / 1 git-2.34.1 sparse-checkout artifact (workspace_snapshot, untouched by this PR, green on CI's newer git). * fix(kernel): a dead generation does not report unavailable skills Cursor Bugbot's finding on 155a876 (thread r4162867816): a boot that kill() invalidated still fired on_unavailable_skills after its bootstrap - the callback shares the restore notice's mailbox, so a discarded kernel could append stale skills-unavailable rows the next turn showed the model, even though on_restore/last_restore are generation-gated in the same function. The skills report now uses the restore gate's exact contract: the generation-liveness check and the callback share ONE lock scope (the callbacks must not re-enter the provisioner, same as the startup-progress listeners), so a kill() can no longer slip between the check and the report. Fixture: doomed_boot_does_not_report_unavailable_skills - the gated counting wrapper holds the doomed boot's interpreter, kill() invalidates its generation, the newer boot arms with the SAME broken skill, and the mailbox must carry exactly the live boot's report. RED receipt: with the gate reverted the fixture fails 2 vs 1 (the dead generation's report lands first); restored, all 12 kernel fixtures pass. Gates: fmt rc0; clippy --workspace --all-targets -D warnings rc0; pa-core lib 1043 passed / 1 git-2.34.1 sparse-checkout artifact (workspace_snapshot, untouched by this PR, green on CI's newer git). * chore: empty retrigger of CI (test shard 3's acp_threshold_auto_compaction_publishes_the_compaction_meta is a known flake: same test green at 155a876 and flaky on main's own cf285dc runs) * fix(daemon): gate the interleave race harness to unix Main's PrimeIntellect-ai#3004 promoted the windows cross-check (clippy for x86_64-pc-windows-gnu), and the new job is red on main's own tip: PrimeIntellect-ai#3195's interleave harness binds a unix-socket probe, but tokio gates UnixListener behind all(unix) - the module fails to cross-compile for windows (E0433, the single red in pa-daemon's lib-test target). The module declaration now carries #[cfg(unix)] (the same contract as feed's #[cfg(unix)] tests - every race test in the module rides the unix-socket probe, so the whole module is unix-only). This is an upstream break the lane folds and fixes so its head can be green; main carries the same fix independently. Gates: cargo check -p pa-daemon --all-targets --target x86_64-pc-windows-gnu rc 0; fmt rc 0; clippy --workspace --all-targets -D warnings rc 0; pa-daemon turn_stream_tests 42 passed on unix. * fix(kernel): spent stop arms release and doomed teardowns never flush The two Cursor Bugbot findings on 4c74a09, plus the cross-model reviewer's TOCTOU finding on the first fix shape: 1. "Stop-arm memo never released" (Medium): pending_stop_for_startup is written when a stop arms against an in-flight boot and never cleared when that boot settles or kill() invalidates it. The arm's memo receiver is a strong handle to the settled StartupResult, so a spent arm keeps a parked-or-failed manager alive past stop_kernel - a later failed shutdown's process would leak with the stale receiver as its sole owner. Both spenders now release the arm (the boot's settle and the kill() that invalidates it), through one shared release_spent_stop_arm. 2. "Doomed boot can clobber snapshots" (Medium): a boot kill() invalidated still runs start_kernel_impl to completion, and its failure teardowns shut down with the dispose snapshot policy (default true) against the same snapshot_dir the replacement boot restores from. The four failure teardown sites now route through hold_snapshot_flush_gate: the policy (dispose policy while the boot's memo is still armed, never a flush for a kill()-invalidated boot; dispose() does not clear the memo so a disposed boot keeps the dispose's own policy) AND, when the policy still flushes, a pending-stop revival gate for the teardown's own flush - the decision and the gate install share ONE lock scope, so a kill() landing between the decision and the flush still leaves the replacement boot gated on this teardown (the same revival gate stop_kernel arms against its own flush; a stop ACTIVELY armed for this boot is the one exemption - its memo wait settles only after this teardown, so its own gate already covers the flush - while a merely installed older gate, including one long settled and left in place, covers nothing and is replaced by the teardown gate). Also gates the two unix-only kernel test targets (kernel_startup_memo.rs, kernel_stop_revive.rs) with #![cfg(unix)] - main's PrimeIntellect-ai#3004 promoted the windows cross-check and the whole-target /bin/sh wrapper fixtures never cross-compiled (the sibling kernel targets and lock_compat already carry the gate). RED receipts (provisioner unit tests): release no-op -> both arm tests fail; flush gate install removed -> the gate assert fails; restored -> 12/12. Gates: fmt rc0; clippy --workspace --all-targets -D warnings rc0; the 12 kernel fixtures pass; pa-core lib 1046 passed / 1 git-2.34.1 sparse-checkout artifact (workspace_snapshot, untouched by this PR, green on CI's newer git); clippy --workspace --all-targets --target x86_64-pc-windows-gnu -D warnings rc0. * fix(kernel): dispose waits kill-doomed boots and stops chain superseded gates snimu's five remaining review items (2026-10-02T20:36:31Z comment): 1. StopArm::Joined deleted. The join made a second stop of one in-flight boot wait the first stop's gate after arm_stop had already written the second caller's dispose_snapshot, splitting the two snapshot policies. Concurrent stops of one boot now supersede: each stop arms its own gate and its task waits the strictly older gate it superseded before opening its own, so a revival cannot cross before the final flush settles; pending_stop_for_startup stays (the flush-gate exemption is load-bearing), now consulted by the failed-boot teardown instead of the join. 2. kill() parks the startup memo it invalidates (doomed_startups) instead of clearing it. The doomed boot's kernel exists until its settle, and a later dispose() must wait that settle the same way it waits an armed memo - without the park, a kill followed by a dispose skipped the doomed boot entirely and could orphan its kernel at worker exit. Each parked memo is spent by its own boot's settle. 3. The settle path checks the generation before the disposed flag (settle_decision): a killed+disposed boot reaching Ok tears down with kill() semantics and never flushes a snapshot, and kill->ensure->dispose cannot overlap two flushes on one snapshot_dir. The order is pinned by a unit test. 4. The four memo fixtures hold their boots at file gates instead of spinning on yield_now; the flaky fixture holds before its failure on the same schedule, so a kill lands mid-boot by construction. Both gated wrappers self-release when their fixture directory vanishes (the counting loop falls through to the real interpreter, whose stdin pipe is gone; the flaky loop exits 1), so a cancelled test cannot leave a polling shell behind. 5. The main merge folds PrimeIntellect-ai#3306's #![cfg(unix)] inside interleave.rs, dropping c9de47a's duplicate mod gate from the resolution (the duplicated_attributes clippy risk). Red-first regressions, both demonstrated failing on the pre-change behavior: dispose_after_a_kill_waits_the_doomed_boot and a_killed_then_disposed_boot_never_flushes_the_snapshot; the settle-order unit pin fails with the old disposed-first order. The joined-gate drain test is deleted with its arm. Cross-family review (bugbot, gpt-6-sol): READY-CLEAN, no findings. * fix(kernel): a kill while the boot waits gates it before the spawn cursor[bot] finding on 8d29f37 ("Killed boot still spawns interpreter", Medium): the boot permit gate checked only the dispose signal, so a kill() landing while a boot was still waiting - queued behind an in-flight stop's gate or the boot permit itself - let the doomed generation spawn an interpreter its own settle then had to kill (no flush, no park, but a real boot racing the replacement generation on the same snapshot_dir and kernel-stderr.log). The permit-gate closure now rechecks boot_generation_is_live as the last point before the interpreter spawn and fails fast with "Kernel provisioner killed before start" - non-retryable (FATAL_MARKERS "provisioner killed", unit-pinned). The settle stays the correctness backstop for the post-check race. Red-first regression a_kill_while_the_boot_waits_the_stop_gate_spawns_no_interpreter: boot A holds mid-handshake at a file gate; a stop arms against it; kill #1 invalidates A; a fresh ensure arms memo B (polled once on the test thread so the arm is guaranteed, not yield-assumed); kill #2 invalidates B while it still waits; releasing A settles the chain and B fails the liveness check before the spawn. Red on the unfixed tree (B spawned a 183ms handshake and settled "killed during startup"); green after (the spawn count stays at one). Also folds pi/main c24ac22 (factory/machine lib, acp single daemon path, semantic-edge ledger, telemetry first-frame fix, factory e2e, subagent relisting, factory state-machine port, vouches). Cross-family review (bugbot, gpt-6-sol): READY-CLEAN on the fix and on the test-hardening re-review.
…t-ai#3338) * test(tui): pin accepted-then-killed session error render * test(tui): wait for worker-admitted user row before kill * test(tui): kill after worker reports streaming admission * test(tui): trace killed-close delivery across client pump * test(tui): trace close apply-to-frame visibility * test(tui): attribute killed-turn probe output to its own case * fix(tui): surface killed session as persistent TS error row * test(tui): assert close error survives cancelled-turn status
…-ai#3278) * make update --rollback and --archive work on installer installs * say the current install was kept when the installer fails * keep the bundled installer and the prefix check private * recheck the rollback source under the lock and test archive and rollback end to end * retrigger ci * release the install lock when the rollback source vanished * recheck the rollback source before the sweep * clean the stage on a bad archive and refuse a channel flag on a local update * run the local installer modes handed off on windows so the payload can be replaced * sweep the extraction stage on the refused and failed installs * split the trusted shell resolution from the installer child command * refuse a windows handoff up front when nothing is kept or the archive cannot install * retrigger ci * answer the windows rollback pre-flight from the installer's own scan * retrigger ci * retrigger ci * remove a stray clippy output file committed by mistake * refuse an archive whose payload disagrees with its name * gate the unix-socket interleave harness off the windows cross-check * answer the local modes' preflight from the installer's own archive check * own the check knobs, bound the version probe, and sweep the check stage * retrigger ci * initialize the check trap's stage so an inherited variable cannot become its target * bound the version probe with a reaping runner and refuse an unresponsive payload * retrigger ci * retrigger ci * move the probe files out of the payload stage and take the continuous version stamp * scope the version probe to the archive mode * require a working version probe and sweep the interrupted install's staging * bound the version probe's output writes * keep the version probe on the staged tree's own package manifest * retrigger ci * guard the version probe's output limit against platform failure * drop the interleave gate now that the harness gates itself * spawn the windows handoff behind the detached process wall * read the unmarked ps1 rollback slot when the generations record is silent * normalize the ps1-written marker on the rollback read and refuse a prefix the record cannot hold * carry the resolved prefix past the substitution trim and read every recorded marker through the normalizer * retrigger ci * read the ps1-encoded markers through the ownership gate and strip the probe report's carriage return * accept the v-prefixed archive name and wait out the handoff parent before the payload touch * run the installer from its held handle and ask liveness of a trusted tasklist * restore the parked tree through the publish window and let the windows local modes run without python
…ort of TS PrimeIntellect-ai#2529) (PrimeIntellect-ai#3344) * port TS PrimeIntellect-ai#2529: rlm.rename across runtime, daemon, supervisor, and agents view - Python runtime: rlm.rename(new_name, session_id=...) + rlm.rlm.rename + __all__; _collect_target_selector gains the what label - pa-core: rlm.rename host handler (spawn-name rules), RlmSubagentHost::rename, NoRlmChildren local self-rename; RUNTIME_READY_CHECK requires rlm.rename - pa-types: DaemonCommand::Rename gains renamedBy (parent marker) + RENAMED_BY_PARENT - pa-daemon: SupervisorChildSessions rename routing (self by supervisor, children by id only), supervisor name-reservation ladder shared by the saved-session and live rename routes, ledger rename for Rename + SetSessionName, worker session_renamed transcript notice - pa-tui agents view: ctrl+r / /name renames subagent rows too - prompts/core.md documents rlm.rename; .changes fragment * fmt: apply rustfmt to the rlm.rename port * fix clippy: hoist the session Arc out of the boxed rename future; rename the test reply binding * fix clippy/rustc: ladder match arm types, or-pattern parens, str compare, guard drop, test Arc double-wrap * fmt: or-pattern continuation indent * fix routing: the ladder passes the inner route result through (drop the double wrap) * fix e2e: RlmSpawnRequest initializer gains spawned_by_request_id * review 1: set_session_name takes the typed path (ledger lands), rename wakes passivated targets, one summary->scope projection, one id predicate, one renamed-by spelling, drop dead roundtrip test, trim duplicated test cases * fmt: review-1 worker notice compare shape * test fix: the notice test creates its worker without a name (the named fixture made the first rename a real rename) * golden: regenerate the system-prompt snapshot for the core.md rlm.rename line * fix(rlm): the parent rename wakes a twice-passivated child through the durable selector The host rename addressed the child by the registry record's spawn-time active id with no retry, while prompt_child already retried a miss by the child's durable selector: after a passivation, a revival re-keys the supervisor roster's child row to the fresh worker's id (the registration's roster write drops the old id's index), and once that worker passivates again the spawn-time id resolves nowhere - no resident, no roster row, no ledger edge - so the parent rename failed with `Unknown active session` while a prompt to the same child survived. One shared durable-wake retry now backs both the prompt and the rename (extracted from prompt_child, not copied): same `Unknown active session:` predicate, same durable_child_selector resolution, same failure contexts. The red test drives the whole chain: passivate, revive by the durable id, passivate again, then the parent rename - which failed on the previous head with `rename session "<spawn-time id>": Unknown active session`. * fix(worker): the session_renamed notice persists under the rename's own core lock handle_rename persisted the session_info name write under the core lock, dropped it, and emit_custom_row re-locked core to persist the session_renamed notice - two lock scopes while worker commands run concurrently, so a racing command could interleave its writes between a rename's name and its notice and order the notice against a contradicted name history (TS serializes both in one synchronous step). emit_custom_row now splits into a persist half (persist_custom_row, called with the caller's guard) and a broadcast half (broadcast_custom_row): the rename persists the notice inside the same lock scope as the name write and broadcasts after the lock drops, and the notice's JSON shape stays defined once. The persist-error policy is unchanged (the display row stays advisory, never failing the rename). * test: keep the child session id out of the rename-wake assert message * test(supervisor): property-test the live rename ladder's family-name reservation * fix(rlm): serialize parent rename and delete of a child --------- Co-authored-by: Kevin Thomas <kevin.jt2007@gmail.com>
…rimeIntellect-ai#3258) * fix(daemon): stop client-owned workers 30s after their owner's last connection closes A worker created as client-owned (ACP sessions, TUI --no-session) kept running when its owner died without a clean close - an editor SIGTERM, a crash, an EOF during session/new - because nothing in the supervisor tracked whether the owning client was still connected. Owned workers are hidden from other clients, so the session stayed stuck running until the daemon stopped; adopted owned workers survived daemon restarts the same way. Port the TS supervisor's scheduleOwnedWorkerCleanup: - Track live connections (connection id -> the shared effective-client-id cell dispatch already writes), so "owner connected" scans the live set like TS's this.clients: one process can hold several connections with the same id. - Split handle_client into a registration wrapper plus the read loop, so every exit path (EOF, failed writes, even the hello write) deregisters and considers the owner-disconnect cleanup; the old write-failure returns skipped the disconnect tail entirely. - Arm a 30s grace timer per owned resident (ResidentWorker.owner_cleanup) when its owner's last connection closes, and after the boot adoption pass. The expiry re-checks the owner id, the live connections, and that the same resident is still registered before stopping the worker through the graceful per-session stop. A later arm replaces a pending timer, so the stop is measured from the owner's last disconnect, and a reconnect inside the grace (or any live connection of the owner at expiry) makes the expiry a no-op. * fix(daemon): let each owner-disconnect timer take only its own handle When the owner-disconnect timer woke, it took whatever handle sat in the resident's `owner_cleanup` slot. If the owner reconnected and disconnected again at exactly the grace boundary, the re-arm could store a newer timer's handle there while the old timer was still mid-poll: the old timer then took the new timer's handle, and since the re-arm had aborted it, it was cancelled at its first await inside `stop_worker`, halfway through the stop. The expiry now clears the slot only when it holds its own handle (AbortHandle id == task id, under the slot mutex), so a replaced timer returns without stopping the worker and a timer that wins the race is never aborted mid-stop. The phase-4 leg of the test now also asserts the expiry cleared the slot, instead of trusting the yields as proof that the expiry ran. * fix(daemon): measure the owned-worker grace from the timer's arm The spawned cleanup task computed its sleep deadline at its first poll, not when schedule_owned_worker_cleanup armed it, so the grace effectively started whenever the task was first polled. Compute the deadline at the arm and sleep_until it, so the grace always runs from the owner's disconnect. * fix(daemon): arm the owner cleanup when a client-owned worker is registered A connection that closed while its create was still in flight scanned the registry before the new worker was inserted, so that worker was never armed and ran on until the next daemon start. launch_worker now arms the cleanup right after the insert; the arm skips owners that are still connected, so a normal create is unaffected. * fix(daemon): veto expired cleanup after owner reconnects --------- Co-authored-by: Kevin Thomas <kevin.jt2007@gmail.com>
The transcript click target only accepted tool cards, bash cards, agent messages and shell completions. Compaction summaries, refinement notices, skill invocations and injected prompts are clickable in v0.9.8 and their renderers already follow the per-row expansion, so add them to the click target.
…ort (PrimeIntellect-ai#3323) The flow tests keep failing when a runner holds port 53692: the probe-and-skip guards pass the probe, then the flow's own bind loses the race a few milliseconds later (the CI failure in run 37061119942). The flow tests never needed the registered port, so they now run on a free loopback port through a login_with_server seam; the pa-cli test exercises the store step directly through store_anthropic_login. Production still binds the registered port. The probe, the skip guards, and CALLBACK_PORT_LOCK are deleted.
…t-ai#3361) * pa-ai: end the openai-completions stream at the [DONE] marker The SSE read loop terminated only at body EOF; a server that keeps the response open past `data: [DONE]` (a persistent SSE channel with no content length, no chunked terminator, no half-close) left the stream reading forever — the turn stalled mid-stream with the tool call parsed but never executed, no kernel spawned, and the ACP prompt waiting on an uncapped request. The TS SDK stops at the marker; the port now breaks the read loop there and skips the post-marker decoder flush. * trim the done-marker comments to the non-obvious fact --------- Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
…refine/agent-message updates, stop failures, MCP owner, input pause) (PrimeIntellect-ai#3348) * acp e2e: exercise the daemon-attached MCP clear path and the unreachable-daemon exit The worker release is observed through the real replace_acp_mcp_servers command: a foreign-owner probe fails the ownership fence while the session is open and succeeds after session/close and after the EOF teardown, proving both clear sites ran on the daemon session. With the socket path blocked by a non-socket file the supervisor never comes up, and --mode acp must print one Error line, exit 1, and write nothing to stdout: no in-process fallback serves ACP frames. * acp: stop-failure fence, input-pause lease, per-connection MCP owner (TS parity) * pa-daemon: emit subagent, refine and agent-message session events and map them to ACP _meta (TS parity) * fix(acp): retain mcp owner names until clear acknowledged * fix: emit and fence ACP refinement lifecycle events * test: run refinement event fence under a Tokio runtime * fix: fence ACP close and wake paused idle waiters * style: remove merge marker padding in child records * test: exclude buffered wakes in ACP race regressions --------- Co-authored-by: Kevin Thomas <kevin.jt2007@gmail.com> Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
* red: the 755 output-budget wire pins * honor an explicitly configured max tokens * cargo fmt * silence the pass-by-ref lint on the serde skip predicates * saturate the thinking fold and tighten the max tokens comments * drop the deleted-mechanism clause from the overflow test comment
… at the copy that serves (PrimeIntellect-ai#3364) * publish install.ps1 on every beta cut and point the windows one-liner at the copy that serves * review: qualify the no-github claims for the windows entry route and trim the publish narration * retrigger ci: the test-summary job died in the runner 15m starvation window (cancelled, no log; all 8 shards green) * retrigger ci again: the wave died in the same org-wide runner 15m starvation window (cancelled/skipped legs, zero red units; every failing leg passed on the prior head) * retrigger ci after the runner-pool recovery (the canary wave completed green; the prior waves died in the famine) --------- Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
…tstrap (PrimeIntellect-ai#3346) * fix(kernel): refuse to snapshot live bash handles and log dropped protocol frames * fix(kernel): refuse to reopen pickled raw fd numbers during restore * fix(kernel): bound the provisioner's runtime bootstrap execute * fix(kernel): prove the wedge tests red on behavior (off-cell handle, bare wakeup await) * fix(kernel): pass the bootstrap bound through the shared execute path * fix(kernel): bound blocked protocol writes during abort and shutdown * fix(kernel): retire stdin when an aborted request write tears its frame A timed-out oversized request force-aborts while its JSON-lines frame is still mid-pipe. Cancelling the writer leaves the request unsent or torn: the runtime never sees it, its execution never settles, and the kernel stays busy forever — every later request fails as busy after the reuse window, and any frame written next glues onto the prefix and drops as invalid JSON. Retire the kernel stdin when the early-settle cancel actually joined a cancelled writer: later writes fail fast, the runtime reads EOF and shuts down, and the provisioner replaces the defunct kernel. The abort watcher's interrupt needs no such handling: its ~60-byte frame is a single write on a non-blocking pipe, all-or-nothing below PIPE_BUF, so the grace deadline can drop it without tearing. --------- Co-authored-by: Kevin Thomas <kevin.jt2007@gmail.com>
Integrate the 89 upstream commits through 672085e while preserving the Oneiron WebSocket transport, delta continuation, compaction, model catalog fallback and local model compatibility patch. Retry dropped provider turns from committed state with the shared bounded 2s/4s/8s transport ladder. Preserve committed tool results and supersede failed partial output. Retain WebSockets and recover the connection cap. Keep the combined daemon protocol at 7 with schema 32. Update native test fixtures and side-by-side packaging for the merged upstream APIs.
|
ⓘ Qodo reviews are paused because your workspace is out of credits. Ask your workspace admin to add credits to resume reviews. Manage billing |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
PR SummaryHigh Risk Overview ACP, daemon, and RLM gain broad TypeScript parity: daemon-only ACP (no in-process fallback), session flags, single-turn ownership, 24h long routes, client-owned Release and CI shift the train to Smaller fixes span TUI (code wrap, expandable rows, CSI panic), Reviewed by Cursor Bugbot for commit f64ee8f. Bugbot is set up for automated code reviews on this repo. Configure here. |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: df8195b7-63d2-440e-af45-1b02b78b2bb0) |
Prime Agent performance — failedPR Benchmark execution did not complete successfully. Missing measurements are not performance wins. Failure diagnostics:
See the saved per-trial logs and terminal transcripts for details. Overall: 0 regressed · 0 improved · 0 no clear change · 42 unavailable.
Python runtime
Session transport
UI interactions
Cost pending or unavailable; sandbox usage has not been collected. Methodology and samplesMain resolved at 2026-10-07T10:29:51.158671+00:00. Harness
Failures:
|
Summary
Merge all 89 upstream commits from
672085e883c25a8a5f10c77ad8e5d3fb38dd6584into the Oneiron Rust fork, preserving the 212 fork-only commits and local open PrimeIntellect-ai#3201 compatibility patch. Ordered merge parents:925ee93ab0fa50eb47067285aade7fcc3722ed59,672085e883c25a8a5f10c77ad8e5d3fb38dd6584.Includes PrimeIntellect-ai#3197 stream-drop classification/guard, PrimeIntellect-ai#3361
[DONE]termination, PrimeIntellect-ai#3362 configured max tokens, and upstream lifecycle/kernel/factory changes. All 55 conflicts have recorded resolutions (ledger below); upstream structure is retained while fork capabilities stay available.WebSocket retry and continuation
websocket_connection_limit_reached: one bounded immediate same-provider refresh outside the ordinary retry budget; repeated caps terminate. Cached sockets rotate at acquisition after 55 minutes, never mid-flight.X-ACP-Model-Request-ID/Idempotency-Keychurn. Only these two correlation keys are excluded from connection identity; URL/auth/routing remain identity-sensitive. They still travel on fresh handshakes. Reused WebSocket handshakes cannot refresh per-call headers; no per-message metadata or SSE fallback was invented.Ownership and public API
Provider transport/classification/cache stay in
pa-ai; retry policy/committed-state rollback stay inpa-core; hosted execution/ACP/admission stay inpa-daemon; wire vocabulary stays inpa-types; rendering stays inpa-tui; delivery/aggregate events stay inpa-telemetry. Crate README scopes/non-goals/public APIs and cycle-free dependency direction are retained. The CLI remains the composition root.Classification: internal retry/cache changes plus documented structured public failure/retry variants and provider SDK-name/borrowed error-name API. No new fork-specific external dependency or cross-crate re-export; upstream dependency changes are accompanied by Cargo.lock. Strict workspace Clippy and deny pass. Retry adoption/error telemetry uses the existing fire-and-forget seam, including transport aggregate
network_error, without prompt/session/file contents or render-path network waits.Compatibility and preserved wire names
Protocol 7 / combined schema 32 (
protocol-7-schema-32-20261006c0a1), capability-gated optional metadata, tool selection, models.json fallback, compaction, affinity and memory behavior are preserved. Wire identifiers such asPI_PACKAGE_DIR,ai.primeintellect.prime-agent, provider IDs, settings keys,_meta, compatibility keys and lockfile names remain byte-compatible. These preserved identifiers are not user-visible branding changes.Verification
17c18cc092c2ee7452e86ad036ef9b3f204cdf9a951880bc8ef40f8cd1226928; 1,672 files; treed84164932bf8e2af1d7f2ab0d1e9e9a7d437651a.64182502d94a6a74515ea5d9c6358b5a24dbf8797182744e852e420420ea8683. Original real-cap failures remain preserved and excluded..Parity-diff evidence
All seven final same-binary steps passed: genericWS8, Codex7, peer-close2, nativehello, fullTTY2 and six retained frame diffs. Candidate SHA
64182502d94a6a74515ea5d9c6358b5a24dbf8797182744e852e420420ea8683; source-bound result/Users/olety/Desktop/temp/prime-agent-sync-20261006/resume14-parity/runs/20261007T055704365773Z-pid33030/result.json. Behavioral proof with intentional differences, not pixel-identical output.. The verifier keeps all original assertions: generic WS 8 cases, Codex 7 cases, TS/Rust peer-close 2 targets, real native daemon hello, full interactive tmux TTY on both binaries, and six retained frame diffs. It binds one immutable final candidate and every source hash. Wire stdout is byte-compared and input/timing behavior is exercised. Intentional fork differences are reported, not claimed equal: Rust replaces failed partial text while canonical TS retains it; existing layout/dock/mode differences and combined schema32 vs TS schema30 remain explicit.Release safety
Release
0.9.8-oneiron.20261006.1: signed commitf64ee8f74e8cc15ca7dd7a84126065599011e644, treed84164932bf8e2af1d7f2ab0d1e9e9a7d437651a; native Mac+mel builds and no-activate installs verified. Mac/Users/olety/.local/share/prime-agent-oneiron-rs/0.9.8-oneiron.20261006.1; Arch/home/lexi/.local/share/prime-agent-oneiron-rs/0.9.8-oneiron.20261006.1. Manifest SHA6f12d52f9a63bb202e5b696028d0381f5f105abb987625020a124c31fcb684d5; SHA256SUMS SHAa69a96ad84ed68f970b8e8f131be5720061a223198a5c1a3709207951b8e868a; controller SHA079ff4f137ab90cb1e3e214df5ce109655d12cb72b302a93dc3e2b23215759f2. Pinned real catalogs:1199 models/42 transport tuples/68 services.. Mac build/stage is native and sequential; Linux build goes through fleet on mel; selyse/Arch only verifies and installs. Both installers use--no-activate. Current.20261003.2, launchers, live feed pointers and daemons remain untouched. Activation and rollback are deferred to an owner-approved quiet window and recorded in the report.Conflict ledger
The table below records the initial merge resolutions. Subsequent retry/integration fixes are described above; an initial resolution is not a claim that its file received no later changes.
Conflict resolution ledger — 55 resolved paths
crates/pa-ai/src/providers/anthropic/mod.rscrates/pa-ai/src/providers/anthropic/stream_tests.rscrates/pa-ai/src/providers/openai_completions/stream.rs[DONE]termination and decoder guard while preserving fork response-model attribution.crates/pa-ai/src/providers/openai_completions/stream_drop.rs[DONE]regression.crates/pa-ai/src/utils_inner/stream_failure.rscrates/pa-ai/src/utils_inner/stream_failure/tests.rscrates/pa-cli/src/command_registry.rscrates/pa-cli/src/global_flags.rscrates/pa-cli/src/mode.rscrates/pa-cli/src/print_runtime.rscrates/pa-core/README.mdcrates/pa-core/src/kernel/bootstrap/mod.rscrates/pa-core/src/kernel/bootstrap/venv/probe.rsrlm.renamereadiness with fork installed-runtime snapshot safety checks.crates/pa-core/src/kernel/manager/mod.rscrates/pa-core/src/kernel/manager/requests.rscrates/pa-core/src/kernel/provisioner.rscrates/pa-core/src/kernel/shared.rsKernelCellLineand upstreamKernelBashCommands.crates/pa-core/src/models/custom.rscrates/pa-core/src/session_engine/auto_retry/tests.rscrates/pa-core/src/session_engine/compact_session.rscrates/pa-core/src/session_engine/compaction_arms.rscrates/pa-core/src/session_engine/engine.rscrates/pa-core/src/session_engine/error_classify.rscrates/pa-core/src/session_engine/mod.rscrates/pa-core/src/session_engine/provider_retry.rscrates/pa-core/src/session_engine/runtime_wiring.rscrates/pa-core/src/session_engine/wiring.rscrates/pa-core/src/tools/golden_replay.rscrates/pa-core/src/tools/ipython.rscrates/pa-core/src/update/installer.rscrates/pa-daemon/README.mdcrates/pa-daemon/src/acp/autorefine.rscrates/pa-daemon/src/acp/compaction_arms/tests.rscrates/pa-daemon/src/acp/daemon.rsDaemonLink; retained tool-selection refusal and explicit hosted-link waits.crates/pa-daemon/src/acp/goal_continuation.rscrates/pa-daemon/src/acp/in_process_config.rsSetModel, which retains the session transport.crates/pa-daemon/src/acp/wire_config.rsDaemonLink, with explicit waits for the new calls.crates/pa-core/src/session_engine/telemetry.rscrates/pa-daemon/src/agent_engine/config.rscrates/pa-daemon/src/lib.rscrates/pa-daemon/src/prompt_admission.rscancelOwnedbehavior.crates/pa-daemon/src/supervisor.rscrates/pa-daemon/src/supervisor/clients.rscrates/pa-daemon/src/supervisor/supervision.rscrates/pa-daemon/src/supervisor/worker_lifecycle.rscrates/pa-daemon/src/types.rscrates/pa-daemon/src/worker/summary.rscrates/pa-daemon/src/worker/turn.rscrates/pa-telemetry/src/catalog.rscrates/pa-telemetry/src/events.rscrates/pa-telemetry/src/lib.rscrates/pa-telemetry/src/platform.rscrates/pa-tui/src/daemon_client.rscrates/pa-tui/src/interactive.rsprime-agent-runtime/src/rlm/repl.pySupplemental integration resolution
crates/pa-daemon/src/daemon_link.rs: Shared transport supports upstreamrequest_okwhile retaining optional pending slots, close-fast behavior, and fork hosted-headless budgets.