feat(agents): durable managed-agent runtime (configurable orchestration workflows) - #5954
feat(agents): durable managed-agent runtime (configurable orchestration workflows)#5954jlwainwright wants to merge 17 commits into
Conversation
Turn managed Buzz agents into persistent colleagues with a single identity, serialized inbox, resumable channel sessions, durable assignment ownership, detached authenticated jobs that outlive an ACP turn and a Desktop restart, signed relay progress (kinds 43001-43006), Desktop reattachment, and persistent assignment state. Adds: durable inbox + session store; privileged job supervisor with identity verification, crash reconciliation, cancel/stop process-tree cleanup; loopback runtime-control server with generation-scoped capabilities; relay job protocol validation; managed no-shell capability profile; schema-v2 Desktop adoption; agent-to-agent collaboration across restarts; legacy schema-v1 pair-lock safety. Baseline: block/buzz ac4fa13 (2026-08-01). Feature-flagged rollout per the persistent managed-agent runtime plan (Phases 0-6). Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
…rror Governed lh job requests collapsed every runtime failure into 'managed runtime request failed' with no data and no log, so an agent denied by an empty BUZZ_ACP_JOB_WORKSPACE_ROOTS could not tell the operator why and could only retry blindly. Adopt the assignment path's typed mapping: surface the runtime's own code and message for caller-correctable rejections, keep transport failures opaque but typed, and log their cause. Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
Keep the first 512 bytes of the upstream error body in the mapped HTTP error so callers can distinguish provider 4xx payloads from transport failures. Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
…t channel tag Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
…locks Adds rustdoc to all 118 pub items across store/artifacts/logs/protocol (AGENTS.md: new public API must have doc comments) and documents the safety invariants for the macOS proc_listpids and Windows Job Object FFI sites exempted from deny(unsafe_code). Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
…stants, blocking log tail - Client reconcile() accepted only Ack while the server answers Jobs; accept Jobs and return the reconciled job list. - Late Progress(Running) after a cancel flipped cancelling back to running; mirror the Accepted-branch cancel_requested guard and add a Postgres-gated regression test. - Use MAX_PENDING_PER_CHANNEL / MAX_INBOX_RETRIES instead of drifting 500 / 10 literals in enqueue, requeue, and recover. - Wrap tail_rotating_log in spawn_blocking so the async control handler no longer blocks a tokio worker on sync file reads. Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
… canonical job states - Add live-server client round-trip tests (controller hello/status/ reconcile/shutdown, model capability denial, dead-pid receipt rejection) covering the previously-untested RuntimeClient surface. - Add artifact contract tests: spec write/read round-trip with tamper/zero-attempt/relative-driver rejection, runner-receipt terminal invariants, attempt-dir path normalization, workspace containment. buzz-runtime line coverage 59% -> 66%. - buzz-runtime gains the repo-standard crate doc and #![warn(missing_docs)]; all 372 flagged pub items documented. - Single-source the eight agent job states as buzz_core::agent_job::AGENT_JOB_STATES; relay and CLI filters now consume it instead of hand-maintained copies. - Register buzz-runtime in the ARCHITECTURE.md and AGENTS.md crate maps. Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
themiguelamador
left a comment
There was a problem hiding this comment.
Requesting changes after a full pass over the durable job protocol, relay projection, runtime control/store/recovery paths, ACP integration, CLI, SDK, and migration.
I pushed the fixes that were safe to make without redesigning the runtime as commit 82a7bdfa2. That commit:
- renumbers the colliding
0028_agent_jobs.sqlmigration to0051(0028–0050 are already allocated across this repository's branches); - makes target-side pre-accept rejection durable and publishable, and lets the relay accept the corresponding attempt-1 terminal event;
- tightens request/result/error/artifact validation, bounds artifact reads and control errors, and aligns CLI/SDK/desktop URI behavior;
- bounds authenticated control connections, pending inbox admission, local job listings, and capacity rejection storage;
- preserves relay write acknowledgements in CLI output and removes new production
expectpaths.
Two merge blockers remain:
-
The runtime adds hand-written
unsafeoutside the sole allowed generated mobile FFI exception.crates/buzz-runtime/src/artifacts.rs,crates/buzz-runtime/src/windows_job.rs,crates/buzz-acp/src/job_runner.rs, andcrates/buzz-acp/src/job_windows.rscall platform APIs through manyunsafeblocks (some with localallow(unsafe_code)overrides). This directly conflicts with the repository-wide policy. Please replace these paths with safe abstractions, or obtain and document the required project-level security decision rather than locally bypassing the lint. -
The durable runtime has no retention/compaction policy. Completed/dead-letter inbox rows, terminal jobs/assignments, and published/rejected/superseded outbox rows are never removed. The active-admission caps therefore do not bound total disk growth. In addition, cancel-before-request tombstones have a global 4,096-row cap but no pruning, so a long-lived runtime eventually rejects every new tombstone permanently. Add a transactional retention/compaction policy with tests proving active work, replay/idempotency fences, and necessary terminal history are preserved.
Minor documentation issue: the PR description says the relay adds POST /jobs; the implementation adds GET /jobs and GET /jobs/{id}, while writes continue through POST /events. Please correct the API summary.
Verification completed on the fix commit:
- affected-package
cargo clippy --all-targets -- -D warnings: pass; buzz-runtimelibrary: 29 passed;- ACP job-supervisor tests: 15 passed;
buzz-dev-mcplibrary: 123 passed, 1 ignored;- CLI jobs tests: 5 passed;
- SDK agent-job tests: 8 passed;
- database migration lint: 7 passed, 3 Postgres-only tests ignored.
The focused relay test binary could not finish codegen because the host volume reached ENOSPC; this was an environment failure, not a test assertion. The relay code did pass the affected-package clippy build. The new Postgres regression remains infrastructure-gated and was not run because no local database container was available.
Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com> # Conflicts: # CHANGELOG.md # Cargo.lock # Cargo.toml # crates/buzz-acp/src/pool.rs # crates/buzz-acp/src/queue.rs # crates/buzz-agent/src/config.rs # crates/buzz-agent/src/llm.rs # crates/buzz-cli/src/lib.rs # crates/buzz-db/src/migration.rs # crates/buzz-relay/src/handlers/ingest.rs # desktop/src-tauri/Cargo.lock # desktop/src-tauri/src/managed_agents/env_vars.rs # desktop/src-tauri/src/managed_agents/mod.rs # desktop/src-tauri/src/managed_agents/runtime.rs # desktop/src-tauri/src/managed_agents/runtime/tests.rs # desktop/src/features/channels/ui/ChannelPane.tsx # desktop/src/features/messages/ui/MessageThreadPanel.tsx # schema/schema.sql
…ceptions Blocker fixes for the block#5954 review: - Retention/compaction: transactional compact() removes terminal inbox rows, terminal jobs (no linked active assignment) and assignments, and settled outbox rows past 7-day cutoffs; prunes cancel tombstones for jobs without live state back under the global cap so a long-lived runtime no longer permanently rejects new tombstones. One automatic pass per store-thread hour; compact_at() exposed for tests. Tests prove active work, replay fences and live jobs survive, terminal history is pruned, and the tombstone cap re-admits after compaction (store::tests:: compaction_preserves_active_work_and_fences). - unsafe scoping: Windows/macOS process-identity and Job Object FFI consolidated behind fn-scoped #[allow(unsafe_code)] citing the security decision issue block#6047, matching the existing buzz-dev-mcp shell.rs precedent; stray stmt-level allows removed. Gates: cargo clippy -p buzz-runtime -p buzz-acp --all-targets -D warnings clean; buzz-runtime 30+3+3+2+2 lib/integration tests pass; buzz-acp lib 839 pass. Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
|
Both review blockers addressed in 18f5c8f:
Verification: |
|
@themiguelamador ready for re-review when you have a moment |
Conflict resolutions: - agent_models.rs: adopt upstream's agent_models_update.rs submodule split; port durable-runtime swap (clear_legacy_runtime_pids instead of sync_managed_agent_processes) into the new submodule. - restore.rs: keep durable-runtime start_pair delegation; upstream's legacy-path boot profile reconciliation (kind:0 republish after restore) does not apply — restore no longer spawns via the legacy runtime map. - runtime tests/fixtures: keep our durable-runtime test suite (local fixture + provider_policy_pending field from upstream's record). - acp.rs: keep session_resume/session_load; take upstream's reworded goose system-prompt doc. - CHANGELOG: Unreleased (durable managed agents) above v0.5.16/v0.5.15. Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
Second wave (24 upstream commits, desktop v0.5.17): - pool.rs: keep durable recover_or_create_channel_session AND upstream's ModelSwitchOutcome enum + agent_supports_mode tests; adapt upstream test callsites to the agent_core param; SwitchModel is a struct variant. - runtime.rs: keep our env ordering (user env then configure_managed_acp_environment reasserts runtime-owned controls) and graft upstream's apply_effort_env / apply_claude_model_env on top. - record fixture: add upstream fields provider_policy_pending, effort_level. - commands imports: drop legacy sync_managed_agent_processes references. - CHANGELOG: Unreleased (durable managed agents) above v0.5.17. Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
Third wave (15 upstream commits): - restore.rs/runtime.rs/tests.rs: keep durable-runtime delegation and restart-eligibility; drop re-added legacy orphan_sweep/instance_reaper/ lifecycle/spawn_key module tree (deleted in this branch — durable runtime owns those lifecycles). - MessageThreadPanel.tsx: union props (our onCancelJob + upstream header title-click / column resize callbacks). - ChannelPane.tsx: restore channelTimelineEmpty* imports dropped by the helpers merge. Signed-off-by: Jacques Wainwright <jlwainwright@gmail.com>
Closes #3871
References #5523 (docs-only spec for reliable job lifecycle — this PR supplies the working implementation of both proposals)
What
Adds a durable managed-agent runtime to the ACP harness: jobs survive runner crashes and runtime restarts, with bounded state, enforced capability separation, and a Nostr-first job event surface.
New crate:
buzz-runtimeJobsCancel/Reconcile/ShutdownMAX_*) enforced at protocol, supervisor, and store layersRelay surface (Nostr-first)
43001–43006pre-registered inbuzz-core/src/agent_job.rswith doc commentsGET /jobs+GET /jobs/{job_id}indexed-reads endpoints (NIP-98 auth; job writes go through the existingPOST /events) — the HTTP exception AGENTS.md reserves for genuinely HTTP-only surfacesexpecton this path)0028_agent_jobs.sql(idempotent, count test updated)Desktop
https?://|nostr:), strict sha256 validation, no arbitrary key acceptancetypes.tskept under the 1000-line file-size capCLI
buzz jobs stopsignsKIND_JOB_CANCELviabuzz_sdk(Nostr-first, no new HTTP)Security
A full security audit of the new surfaces found no critical or high findings. Cheap fixes already applied in this PR:
Enforced surfaces verified: loopback-only control server with constant-time token; non-loopback
control_addrrefused; Model capability cannot cancel/reconcile/shutdown; cwd confinement;argv_sha256cross-check (no shell invocation — argv array passed directly); env scrubbing; relay state machine row-locked; bounds enforced at all three layers.The crate keeps
#![deny(unsafe_code)]; the two exempted FFI sites (macOSproc_listpids, Windows Job Objects) carry documented safety invariants (// SAFETY:comments / module-level safety doc).Conventions
buzz-runtimehave rustdoc commentsunwrap()/expect()in production paths (three known crate allowances unchanged)Testing
just checkgreen (fmt, clippy root+tauri with-D warnings, biome desktop/web, mobile analyze, file-size caps, px-text guard, pubkey-truncation guard)Companion spec: #5523. Related prior art discussions: #4161 (FileFailurePersistence), #4638 (buzz-dev-mcp ZeroClaw ACP), #2426 (JOB_DECLINED).
Post-draft quality pass
RuntimeClientround-trips (controller hello/status/reconcile/shutdown, model-capability denial, dead-pid receipt rejection) and artifact contract tests (spec round-trip + tamper rejection, runner-receipt terminal invariants, attempt-dir normalization, workspace containment).store.rs36% of file — recovery scans, inbox requeue, outbox drain paths are exercised only indirectly by integration suites;protocol.rs40% — wire-structDebug/Fromimpls (test-only cost);artifacts.rs47% — canonicalize edge paths. Highest-risk uncovered = crash-recovery requeue loop; covered indirectly by buzz-acpjob_supervisor(69%) + relayagent_jobshandler (91%) suites.#![warn(missing_docs)]; all 372 flagged items documented.buzz_core::agent_job::AGENT_JOB_STATES; relay + CLI--statefilters consume it (drops two hand-maintained copies that could drift from the0028_agent_jobs.sqlCHECK).