Skip to content

feat: add durable capability-service managed runs - #510

Merged
anconina merged 841 commits into
mainfrom
release/1.0.64-prep
Aug 25, 2026
Merged

feat: add durable capability-service managed runs#510
anconina merged 841 commits into
mainfrom
release/1.0.64-prep

Conversation

@anconina

@anconina anconina commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Description

Ship the release/1.0.64-prep branch into main: 840 commits carrying durable capability-service managed runs, the capability-service protocol bundle and fixtures, dead-letter delivery resilience, durable terminal attachment relays across worker replacement, and the 1.0.64 version bump across all 17 workspace packages.

Also included (merged into this branch via #511): the MCP tool bridge no longer drops image result blocks, so a server returning a screenshot reaches the model sanitized and bounded instead of collapsing to "Tool returned no text content".

The three review findings listed under Risk Assessment below are closed by commits pushed after that section was written:

Finding Closed by
capability-service-protocol-fixture-host-entry.ts:81 — omits managed_run_group + approval_receipt scopes ea4b6d0a fix(daemon): align executable fixture scopes
capability-service-protocol-fixture-server.test.ts:692 — asserts raw package-script strings 5fd56604 test(protocol): exercise public fixture commands
test/architecture/capability-service-protocol-bundle.test.ts:135 — matches command strings without executing 5fd56604 test(protocol): exercise public fixture commands

Related Issue

N/A: release-integration PR. Each constituent change carries its own rationale in its commit; no single tracking issue covers the branch.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactor

Checklist

  • Targeted checks for the changed area pass
  • Full repository validation passes (pnpm validate — green in CI on this head, and as the local pre-push hook)
  • New or changed behavior has a test that demonstrated the RED state before the production change (per constituent commit; see RED Test Proof)
  • Documentation updated (capability-service docs, managed-run event families, MCP guide "Image results")
  • No secrets or credentials committed
  • Security implications considered (0600 socket/credential modes, no readiness-exposed credential, scope-gated handshake, sanitized + untrusted-labelled tool images)
  • The change is focused on one concern (shipping the 1.0.64 release branch) and states the allowed N/A reason above

RED Test Proof

This is a release-integration PR: the RED proof for each behavior lives in its own test-first commit on the branch (the test(...) commit preceding each fix(...) commit — e.g. b8fcc9e8/bb1db89a, 3eeac39d/e8c9daac, c9ab419b/a21c058e).

Captured directly for the most recent addition, run at the test-only commit before its production patch:

 FAIL  src/skills/bridge/mcp-tool-bridge.test.ts > mcpToolsToAgentTools > execute() replaces the silent image drop with a disabled-images notice when no image policy is configured
AssertionError: expected 'Tool returned no text content' to be '1 image block not attached: 1 image t…' // Object.is equality
 FAIL  src/skills/bridge/mcp-tool-bridge.test.ts > mcpToolsToAgentTools image results > execute() attaches a sanitized image block after the notice when the server returns only an image
AssertionError: expected [ { type: 'text', …(1) } ] to deeply equal [ { type: 'text', …(1) }, …(1) ]
 FAIL  src/skills/bridge/mcp-tool-bridge.test.ts > mcpToolsToAgentTools image results > execute() keeps wrapped text first, then the notice, then the image block for mixed results
AssertionError: expected [ 'text' ] to deeply equal [ 'text', 'text', 'image' ]

 Test Files  1 failed (1)
      Tests  5 failed | 75 passed (80)

After the production patch: Test Files 2 passed (2), Tests 88 passed (88).


Intent

Ship release/1.0.64-prep after the completed full unit, integration, E2E, clean-build, and local review passes. Preserve the atomic managed-run admission fixes, corrected capability-service contracts and fixtures, dead-letter resilience already merged into the release branch, and durable terminal attachment relays across worker replacement. Create a PR targeting the repository default branch, drive all findings and CI to green, merge the PR, then clean up the merged release branch and only other refs proven disposable. Do not add Co-Authored-By trailers.

What Changed

  • Add configurable capability-service integrations with authenticated Unix-socket control, a generated and release-packaged JSON protocol bundle, and operator CLI/RPC health views.
  • Implement atomic, durable admission and lifecycle management for individual and grouped managed runs, including approvals, verified evidence, attention, continuations, recovery, workspace leases, and terminal attachment relays that survive worker replacement.
  • Harden sub-agent and background completion delivery with governed reservations, idempotent receipts, bounded dead-letter retry and quarantine, and restart-safe chunk and attachment recovery.

Risk Assessment

🚨 High: A required cross-language fixture path remains unreachable through the standalone host, and two newly added tests rely on prohibited source-content assertions rather than the public commands.

Testing

The supplied broad validation baseline was supplemented with focused protocol, admission, dead-letter, relay, and retirement tests plus four executable/persisted-state artifacts; the only local integration retry limitation was absent clean-build dist output, covered by equivalent source-level execution and the supplied completed integration baseline, and all observed behavior passed. This is backend/protocol work, so visual UI evidence is not applicable.

Evidence: Full-scope capability-service handshake
{
  "interface": "capability-service fixture host executable",
  "request": {
    "method": "capabilityServices.handshake",
    "requestedScopes": [
      "health",
      "attention_response",
      "evidence",
      "report",
      "workspace_lease",
      "terminal_events",
      "execution_attachment",
      "managed_run_group",
      "approval_receipt"
    ]
  },
  "response": {
    "jsonrpc": "2.0",
    "id": "operation_manual_full_scope_handshake",
    "protocolId": "comis.capability-service/1",
    "bundleDigest": "1e7f3838bcc08bf9b580375e8afe2a11f5edd14bc944c9968724845e4516ef6d",
    "serviceInstanceId": "service-instance_a",
    "activeScopes": [
      "health",
      "attention_response",
      "evidence",
      "report",
      "workspace_lease",
      "terminal_events",
      "execution_attachment",
      "managed_run_group",
      "approval_receipt"
    ]
  },
  "security": {
    "socketMode": "600",
    "credentialMode": "600",
    "readinessExposedCredential": false
  },
  "shutdown": {
    "exit": {
      "code": 0,
      "signal": null
    },
    "socketRemoved": true,
    "credentialRemoved": true
  }
}
Evidence: Atomic managed-run admission state
{
  "interface": "SQLite managed-run authority stores",
  "serviceCapacity": 1,
  "concurrentAdmissions": {
    "singleRunAdmission": "created",
    "groupedRunAdmission": "capacity_exceeded"
  },
  "persistedState": {
    "managedRuns": [
      {
        "managedRunId": "managed-run_single",
        "managedRunGroupId": null
      }
    ],
    "managedRunGroupCount": 0
  }
}
Evidence: Dead-letter retry and recovery state
{
  "interface": "JSONL-backed announcement dead-letter queue",
  "afterEnqueue": {
    "queued": 1,
    "persisted": true
  },
  "afterRejectedRetry": {
    "queued": 1,
    "persisted": true,
    "attemptCount": null,
    "lastError": null
  },
  "afterAcceptedRetry": {
    "queued": 0,
    "persisted": false,
    "quarantineRecords": 0
  },
  "receiptAwareSendAttempts": 2,
  "events": [
    {
      "name": "announcement:dead_lettered",
      "runId": "run-manual-recovery"
    },
    {
      "name": "announcement:dead_letter_delivered",
      "runId": "run-manual-recovery",
      "attemptCount": 2
    }
  ]
}
Evidence: Durable attachment relay across worker replacement
{
  "interface": "durable terminal execution-attachment relay executable",
  "originalWorker": {
    "role": "original-worker",
    "separateProcess": true,
    "response": "attachment-service:before-replacement"
  },
  "relayAliveAfterOriginalWorkerExit": true,
  "replacementWorker": {
    "role": "replacement-worker",
    "separateProcess": true,
    "response": "attachment-service:after-replacement"
  },
  "relayExit": {
    "code": 0,
    "signal": null
  },
  "relaySocketRemovedAfterRetirement": true,
  "relayDirectoryRemovedAfterRetirement": true
}
- Outcome: 🔧 1 issue found → auto-fixed ✅ across 2 runs (35m8s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 3 errors
  • 🚨 packages/capability-service-sdk/src/methods.ts:555 - The required criterion “Preserve … corrected capability-service contracts and fixtures” is not met: the method catalog and production senders advertise managedRunGroups.activate and managedRunGroups.abandon, but this aggregate omits both request schemas. The cross-language fixture server therefore rejects valid group-control frames as invalid_params. Add both schemas to the aggregate and enforce catalog-to-union parity.
  • 🚨 packages/daemon/src/__tests__/capability-service-protocol-fixture-server.ts:144 - The same fixture criterion is contradicted here: managedRuns.heartbeat and managedRunGroups.getHostRollup have no dispatch or response-validation cases. A valid request falls through dispatch, which returns the request itself, while this default returns the nonempty method string as a truthy validation result, causing a malformed request-shaped response. Implement both response paths with their schemas and add their semantic fixture targets.
  • ⚠️ packages/orchestrator/src/cross-session/announcement-dead-letter-terminal-decision.ts:340 - The persisted retirement validator accepts a tool_result producer without its required nonempty operationId, unlike the shared validator. Because JSON hashing omits the absent field, a malformed record can still have a matching ID; recovery then cannot match or remove its handoff, leaving an uncommitted producer active indefinitely or stale state after commit. Reuse isAnnouncementRetirementProducer() at this deserialization boundary.

🔧 Fix: Align capability protocol parity and retirement validation
3 errors still open:

  • 🚨 packages/daemon/src/__tests__/capability-service-protocol-fixture-host-entry.ts:81 - The required criterion “Preserve … corrected capability-service contracts and fixtures” remains contradicted by the standalone fixture entry: it omits managed_run_group and approval_receipt, causing those canonical requests to return precondition_failed. Its preparation sets also omit external-run_group-member-a, so the published valid group activation returns invalid_params. Enable the canonical scopes/references and exercise those requests through the standalone process.
  • 🚨 packages/daemon/src/__tests__/capability-service-protocol-fixture-server.test.ts:692 - This added process test asserts raw package-script strings, then bypasses both scripts by spawning the TypeScript entry directly. It can pass while the public pnpm capability-service-fixture-host command is broken. Invoke the real package-script entry and assert its readiness and request behavior instead.
  • 🚨 test/architecture/capability-service-protocol-bundle.test.ts:135 - This added test only reads package.json and matches raw command strings/substrings, contrary to the test-quality rule; it does not prove either command executes. Exercise the real drift-check command and the generator against an isolated output instead.
🔧 **Test** - 1 issue found → auto-fixed ✅
  • 🚨 packages/daemon/src/__tests__/capability-service-protocol-fixture-host-entry.ts:81 - The standalone cross-language fixture host is out of parity with the committed valid protocol fixture. A real handshake requesting all nine valid scopes returns precondition_failed because activeScopes omits managed_run_group and approval_receipt. Add those scopes and extend the process-level test to exercise the complete valid handshake.
  • pnpm exec vitest run src/managed-run-store.test.ts src/managed-run-group-store.test.ts --testNamePattern='admits one concurrent run when the service capacity is one|shares atomic service capacity with single-run admission' from packages/memory
  • pnpm exec vitest run packages/capability-service-sdk/src/contract-revision.test.ts
  • pnpm exec vitest run src/__tests__/capability-service-protocol-fixture-host.test.ts src/__tests__/capability-service-protocol-fixture-server.test.ts from packages/daemon
  • pnpm exec vitest run src/tools/builtin/terminal-driver/terminal-durable-attachment-relay.test.ts from packages/skills
  • pnpm exec vitest run src/cross-session/announcement-dead-letter.test.ts src/cross-session/announcement-dead-letter-terminal-decision.test.ts --testNamePattern='enqueue persists entry to JSONL file|drain retries delivery via sendToChannel|drain emits announcement:dead_letter_delivered on success|loads the durable count before the first health observation after restart|retires replay guards only when their producer completion retires|rejects malformed terminal decision and retirement record shapes' from packages/orchestrator
  • Daemon coordinator tests initially encountered missing ignored @comis/memory/dist; retried successfully with pnpm exec vitest run --config /var/folders/w_/w1lk_fns7nqcjkp2n3512g8c0000gn/T/no-mistakes-evidence/01M0VRAYQ3316K74DZ91J83THA/daemon-source-alias.vitest.mjs src/wiring/managed-run-activation-coordinator.test.ts src/wiring/managed-run-group-activation-coordinator.test.ts src/wiring/managed-run-report-bridge.test.ts --testNamePattern='admits a run below the concurrency cap and refuses one at the cap|rejects a group whose members exceed service capacity|admits only one of two concurrent reports at a one-report ceiling|returns the original sequence when a rate-limited client retries an accepted report'
  • Executable fixture-host Unix-socket check covering handshake, managedRuns.receiveAttentionResponse, managedRuns.release, and cleanup
  • Executable full valid-scope handshake against capability-service-protocol-fixture-host-entry.ts
  • Real detached relay check: worker exit → helper survival → socket round trip → helper retirement
  • Real SQLite/JSONL check: capacity-one managed-run admission and dead-letter recovery through queue replacement
  • git log --format='%B' 56c47590d3e25e148a28a4b0009d09ef96e0195f..97034295ae05ea965b54a28ce6250830d008b54d | rg -n '^Co-Authored-By:' found no forbidden trailers

🔧 Fix: Align executable fixture scopes with protocol
✅ Re-checked - no issues remain.

  • Accepted the supplied baseline of completed full unit, integration, E2E, clean-build, and local-review passes.
  • pnpm exec vitest run packages/daemon/src/__tests__/capability-service-protocol-fixture-server.test.ts packages/capability-service-sdk/src/contract-revision.test.ts packages/memory/src/managed-run-store.test.ts packages/memory/src/managed-run-group-store.test.ts packages/skills/src/tools/builtin/terminal-driver/terminal-durable-attachment-relay.test.ts -t 'runs as a test-only process|accepts one canonical request for every advertised method|classifies report replay before applying atomic rate admission|admits one concurrent run when the service capacity is one|shares atomic service capacity with single-run admission|launches a detached session-bound helper and transfers attachment authority over stdin'
  • pnpm exec vitest run packages/orchestrator/src/cross-session/announcement-dead-letter.test.ts packages/daemon/src/__tests__/capability-service-protocol-fixture-server.test.ts -t 'drain retries delivery via sendToChannel|drain emits announcement:dead_letter_delivered on success|binds a 0600 Unix socket and strictly dispatches the closed pinned methods'
  • pnpm exec vitest run packages/orchestrator/src/cross-session/announcement-dead-letter-terminal-decision.test.ts -t 'rejects malformed terminal decision and retirement record shapes'
  • pnpm exec vitest run --config test/vitest.config.ts test/integration/resilience-e2e-dead-letter.test.ts -t 'failed delivery -> enqueue -> drain retry -> successful delivery -> dead_letter_delivered event' (setup-only import failure because this isolated worktree lacks daemon/orchestrator dist; covered by the supplied integration baseline plus source-mapped checks).
  • Manual executable fixture-host JSON-RPC handshake requesting all nine scopes, including socket/credential permissions, secret non-disclosure, and shutdown cleanup.
  • Manual SQLite concurrent single/group admission at capacity one, inspecting the resulting durable authority rows.
  • Manual JSONL dead-letter lifecycle through persistence, rejected retry retention, accepted recovery, cleanup, and emitted events.
  • Manual detached attachment-relay lifecycle using separate original and replacement worker processes, followed by clean retirement.
  • Parsed all evidence artifacts, checked testing-process cleanup and worktree state, and ran git log --format='%B' 56c47590d3e25e148a28a4b0009d09ef96e0195f..HEAD | rg -i '^Co-Authored-By:'.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

`pnpm lint:security` failed with three errors, which blocks the push gate
and every downstream CI tier.

- sub-agent-runner: the archival promise is assigned once and only read
  from its own settle callback, so it is a `const`.
- setup-cross-session-runtime: the delivery surface captures the chunk
  store before the dead-letter queue backing it is built. The late binding
  becomes an explicit holder assigned once, rather than a `let` that the
  rule cannot express.
- cross-session-sender: settling the announcement reservation threw from a
  `finally`, which discards the outcome it replaces. The precedence is
  deliberate — an unsettled reservation strands durable ownership no retry
  can reconcile — so it is preserved exactly and raised from ordinary
  control flow instead, where it is visible.

Adds a characterization test for release-failure-over-a-completed-send,
the settlement path that had no coverage, alongside the existing
cancellation-failure test that already pinned the error path.
Five of the seven gate failures on this branch, none of which were
caused by the work itself outrunning its own tests.

Stale tests — both asserted contracts this branch replaced:
- The cross-session announce test still expected the raw `sendToChannel`
  path. Announcements now go only through the recoverable boundary, which
  fail-closes when it is unwired, so the test wires that boundary and
  additionally pins that the raw port stays unused.
- The sub-agent auto-archive test advanced timers synchronously. The
  sweep awaits replay-guard retirement before removing the run, so the
  advance now drains those continuations.

Gates:
- Audit-stamps for the two wide announcement types. Both are legitimately
  wide: the dead-letter row carries whichever evidence the failing send
  reached, and the queue options are injectable capabilities rather than
  data.
- The five delivery-recovery port types are structurally satisfiable, so
  implementers match their shape without importing the name and the
  consumer scan sees no importer. Tracked in the public API policy beside
  the OutwardSendLedgerPort precedent.
- Regenerated the web contract artifacts.
The setup-cross-session/ tree carries a 600-line per-file cap; three files
had grown past it. Each split follows a seam the code already had rather
than cutting to hit a number.

- Reservation identity (does a stored reservation describe the same
  operation we are about to send?) moves out of recoverable delivery as
  two pure functions.
- The governed delivery shapes move to a types module, so wiring that only
  names them does not pull in the send implementation.
- The composition root sheds three cohesive pieces: retirement-producer
  state resolution, sub-agent result processing (condenser, caster,
  full-output store), and the parent announcement rewrite. The result type
  moves out too, so the wiring barrel does not depend on the builder.

No behavior change. The subdirectory gate passes and the 2415 daemon
wiring tests are unchanged.
Two files past the 1000-line production cap, each split at a seam that
was already there.

- Sub-agent failure notification is the LLM-free path: no model is
  available to phrase the message, so the text is built directly. It
  shares nothing with the result processing around it beyond the announce
  key, so it moves whole.
- Graph resume rehydrates a run that outlived its process. It is guard
  code before it is restore code — re-parsing the record, re-validating
  topology against the stored execution order, unwinding the announcement
  reservation on every abort path — and it reads as such on its own. The
  coordinator internals it drives are now named in an explicit context
  rather than captured implicitly.

No behavior change; 1195 graph and spawn tests unchanged.
…he cap

Three extractions, each along a boundary the code already respected.

- Announcement sanitization is a display boundary: it decides what reads as
  the agent's own words rather than as plumbing. It touches no batcher
  state.
- A single delivery attempt draws on the strongest boundary the caller
  actually wired, falling back through governed, receipt-aware, and plain
  sends. Each fallback is a real downgrade in what can be proven after the
  send, so the order is fixed. It captured only the deps and the admission
  signal, which are now parameters.
- The dead-letter record shapes and their validators move together. The
  JSONL file is the trust boundary — rows outlive restarts, can be
  hand-edited, and drift across versions — so every stored row is
  re-validated on the way in, and the comparators that decide whether a
  row still describes the caller's operation belong beside the shapes they
  compare.

No behavior change; 1264 orchestrator tests unchanged.
…ages

The queue had grown to 3396 lines in a single factory closure — one
function holding sixty inner closures over a shared record set. It is now
833 lines plus seven stage modules, split by lifecycle rather than by size.

The record set moves first. Six reassigned array bindings become one
`DeadLetterRecordStore`, because persistence is all-or-nothing: every
record kind is rewritten to the same JSONL file in one atomic replace, so
they are already read and swapped together.

Stages then take a named context instead of closing over the factory.
Function members forward at call time, so a stage can be constructed
before the stages it calls into exist — the call graph between them is
genuinely cyclic (drain settles decisions, decisions release producers,
promotion re-enters drain) and no ordering makes it acyclic.

Each stage keeps its own concern: storage (lazy idempotent load, atomic
rewrite), governed drain (ledger adjudication, park-don't-guess on an
ambiguous send), serial drain (ordering as the substitute for receipts),
delivery attempts (unknown as a real outcome), decisions, reservations,
producer promotion.

Function bodies are unchanged — each stage destructures the context back
into the names its code already used. 1264 orchestrator tests and the
file-size gate confirm it.
Each extracted stage started from the source file's whole import list, so
most modules carried names they never used, plus a few type aliases that
moved to the shared context. `lint:security` treats an unused binding as an
error, which fails the push gate and every CI tier behind it.
The splits left three source-level import cycles and one file the coverage
gate could not pair with a test.

- The record-store type lived in the queue module the stages import, so
  every stage pointed back at it. It belongs with the context that hands it
  to them, and moves there.
- The failure-notification module reached back into the runner for its deps
  type, while the runner already reaches it through the result processor.
  It now states the four capabilities it uses structurally.
- The cross-session result type is a result declaration, not a wiring
  stage, so it no longer carries the `setup-` prefix that puts it under the
  setup-file coverage invariant.
Adds coverage for paths the queue's existing suite never drove: an
unreadable store refusing every durable operation, an unwritable store
refusing to acknowledge an admission it could not persist, chunked
ledgerless replay at its attempt ceiling and against an unresolved prior
send, terminal-store quarantine of unreadable layout, retirement-intent
validation, and producer promotion across a restart.

Raises cross-session statement coverage from 80.76% to 82.13%.
…efusals

Adds cancellation coverage for an enqueue waiting on capacity, an
already-abandoned caller, and a producer reservation blocked on producer
capacity; plus governed-drain refusals where the ledger lookup is
unreadable, its begin is refused, or it already holds a terminal decision.
A release bump on the default branch only touches the packages that exist
there. A package added on a feature branch has nothing for the merge to
conflict on, so it silently keeps its pre-bump version while every sibling
moves — a drift no textual merge can surface, and one the release preflight
missed because it compared the tag against the umbrella manifest alone.

Assert both layers: every packages/* manifest agrees with the umbrella, and
the preflight rejects a tag that disagrees with any workspace package. The
preflight case runs against a synthetic workspace in a temp dir so it proves
the script's real behavior without mutating this repo's manifests.
The preflight compared the release tag against the umbrella manifest alone,
so a package left behind at an older version published silently — the drift
reached the tarball via bundledDependencies and surfaced at publish time
rather than in any local build.

Read every packages/* manifest and fail on the first disagreement, naming the
package and its path so the offender is fixable without a manual sweep. The
release steps in AGENTS.md and CLAUDE.md hardcoded a package count that was
already stale and re-drifts whenever a package is added; state the invariant
over packages/* instead and cite the gates that now enforce it.
Bundled skills auto-seed into every deployment's data directory at boot, so
a capability-service companion's opt-in skill shipped there reaches unrelated
deployments that do not run that service. The companion repository owns it and
the operator installs it into one selected agent workspace.
The bundled-skill tree auto-seeds into every deployment's data directory at
boot, so the DevCrew liaison skill reached deployments that run no capability
service and cannot call a single tool it describes. The companion repository
owns that skill; an operator copies it into the one agent workspace configured
for the service.

Live campaign targets now name the companion release as its source so they keep
installing it explicitly rather than relying on seeding.
The reducer already treats heartbeat freshness as an input and the record
carries lastHeartbeatAtMs, but nothing writes it, so a service that stopped
reporting is indistinguishable from one that never had to. Liveness must move
forward only, belong to the owning service instance, and stop at a terminal run.
Adds the store mutation the reducer's heartbeat inputs were already written
against. A beat advances only forward, belongs only to the owning service
instance, and is refused once the run is terminal, so a stalled or departed
service cannot present itself as current.
A service can report and can go quiet, but the wire has no way for it to say it
is still alive between reports, so the reducer's freshness inputs can never be
supplied. Liveness carries identifiers and one observation time — never run
state, which would become a second unsequenced path around report ingestion.
Closes the seam the reducer was already written against: a service could report
and could go quiet, but had no way to say it still held a run in between, so
heartbeat freshness never had a source and a departed service looked the same
as a healthy idle one.

The observation is advisory and bounded against the host clock. A future-dated
beat is refused rather than trusted, because accepting one would both overstate
freshness now and make every honest later beat look stale.

makeHost's optional dependencies are now named rather than positional: a caller
that passed undefined past the defaulted session callback silently disabled the
handshake its own assertions depended on.
The coordinator required a heartbeat from every service while nothing wrote
one, so in production every reduction resolved to unknown; these tests stayed
green only because the fixture record carried a hand-set beat. A service that
never requested the health scope cannot satisfy the requirement and must not
be held to it.
Every reduction demanded a heartbeat while nothing wrote one, so in production
a managed run could never leave unknown and therefore never reach succeeded.
The coordinator tests hid it: their fixture record carried a hand-set beat that
no real code path produced.

The obligation now comes from the definition's declared scopes, which is where
the rest of the host's per-run requirements already live. A service that never
requested the health scope has no way to send a beat and is no longer held to
one; a service that did is held at unknown until it reports.

Both resolvers now read the planned definition through one helper instead of
repeating the instance-to-definition lookup.
The host can bind and abandon a preparation but has no way to ask a running
service to stop, so an operator cancel has nothing to send. Cancellation names
the run and the host's reason only: how the service disposes of work the host
cannot see stays a domain decision it reports, not one the host dictates.
An operator cancel must persist the host's decision before it notifies the
service, and must stay cancelled when the service is unreachable: the authority
record is the host's, and a service that is down does not get to veto it.
Adds the wire method and its caller together: the host could bind and abandon a
preparation but had no way to ask a running service to stop, so an operator
cancel had nothing to send.

The transition commits before the service is notified and stands even when the
socket is down. The reducer already ranks a host-recorded cancellation above
every later report, so reporting failure because the service was unreachable
would contradict the record the operator can already read.
The platform had no way for an operator to see an installed service, list the
runs it owns, explain why one is stuck, or cancel it — the only diagnosis path
was the companion product's own CLI, which is the other half of the picture.

Both groups are admin-only with no agent route, so an installed service cannot
enumerate its siblings and a model turn cannot cancel a question another
principal is waiting on. Capabilities that have not shipped are carried as
explicitly unavailable rather than omitted, so a caller cannot render an absent
custody or process view as a healthy empty one.
The scoped list is what a service or a conversation uses and it must never see
another principal's work. An operator fleet view is a different caller class, so
it gets its own named method with its own discriminator — the shape listRecoverable
already established — rather than an optional scope on the scoped path, which
would let an omitted scope quietly become a cross-tenant read.
An operator fleet view cannot use the scoped list, which is bound to one
tenant, agent, principal, and conversation by design. Rather than making that
scope optional — where an omitted scope silently becomes a cross-tenant read —
these are separate methods behind the same explicit discriminator listRecoverable
already uses, so reaching another principal's work requires naming that you are
administering the host.

The attention read deliberately includes settled records: an operator asking why
a run is stuck needs the resolved and cancelled ones too.
A fleet row must carry no service-authored content, a capped page must say it
was capped, an unshipped capability must be labelled rather than omitted, and a
missing run must read as not found rather than an empty successful row.
Implements the eight admin-only methods the contracts declared: cross-run list,
run detail, a deterministic explain, host-authority cancel, installed-instance
views, and the attention records runs are blocked on. Until now the only way to
diagnose external work was the companion product's own CLI, which is the domain
half; this is the host half — whether authority bound, whether policy and the
capability snapshot resolved, which host records the run holds, and whether the
service is still alive.

The explain verdict is deterministic over the durable record and orders causes
so the outer fault wins: a missing instance outranks a liveness gap, which
outranks a run merely waiting, because repairing the outer one is what makes
the inner symptom go away.

Three gates shaped the result rather than being worked around. The shared
context type moved to its own module because a handler file may not import
another handler file. Two files crossed the size cap, so the outbound run
commands and the cross-scope reads were split out along the seams the
terminal-event sender already established. And "fleet" is a retired domain
term here, so the vocabulary is cross-run and summary throughout.
@mintlify

mintlify Bot commented Aug 25, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
comis 🟢 Ready View Preview Aug 25, 2026, 7:42 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@socket-security

socket-security Bot commented Aug 25, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedmailparser@​3.9.16991008094100

View full report

The bridge keeps only text content from an MCP tool result, so a server
that returns a screenshot leaves the agent with "Tool returned no text
content" and no image. Pin the expected behavior: sanitized image parts
after the text, a runtime-authored untrusted-output notice, content-free
drop reporting, and a visible notice instead of a silent drop when no
image policy is configured.
…model

Route `image` content blocks from MCP tool results through the same
sharp-backed sanitizer browser screenshots use, consider at most four
blocks per call (bounding sanitizer work, not just kept output), keep
text first, and prefix a runtime-authored notice that the frames are
untrusted tool output. Every dropped block — sanitizer rejection, limit,
unreadable data, or no policy — is reported content-free and named in the
result so the agent never mistakes a dropped frame for an empty result.

The daemon wires the policy through a small helper so the composition
root stays under the file-size cap and the errorKind literal resolves for
the log-payload gate. Documented under "Image results" in the MCP guide.
The public-command test reaches the fixture host through two nested
package-manager processes, so the spawned child is the `pnpm` wrapper
rather than the host itself. A SIGTERM sent to that wrapper is not
forwarded to its grandchild on every platform: on Linux the wrapper died
by signal and the assertion read that disposition as the host's exit,
failing the sharded unit job with `{ code: null, signal: 'SIGTERM' }`
while the same test passed on macOS.

Signal the process group so the host itself receives SIGTERM, and assert
the contract the host owns — its handler removes the credential and closes
the socket — rather than the wrapper's incidental exit disposition. A host
that is killed outright leaves both files behind, so their removal is what
proves the graceful shutdown.
@anconina
anconina merged commit a39df6c into main Aug 25, 2026
26 checks passed
@anconina
anconina deleted the release/1.0.64-prep branch August 25, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant