Parent: #864
Depends on: #866
Objective
Add durable ordered guidance and acknowledged live steering.
Scope
- Bump the task-store to schema version 6 with nullable
cluster_id TEXT, agent_id TEXT, live_guidance_base TEXT, and accepted_guidance_through TEXT, mapped exactly to task fields clusterId, agentId, liveGuidanceBase, and acceptedGuidanceThrough. Both cursors are canonical unsigned decimal strings and start at the task's captured pre-prompt ledger high-water; acceptedGuidanceThrough advances only contiguously. Existing rows migrate to NULL; an OMP live-steer attempt requires all four values and exact agreement with the active ompSessionOwnership.owner. Persist guidance before effects by appending the original USER_GUIDANCE_* row with metadata.delivery.status:'pending', then serialize delivery per agent. If an older queued sequence creates a cursor gap, return queued reason guidance-order-gap without writing steer.
For USER_GUIDANCE_AGENT, the original row carries {delivery:{status:'pending',reason:null,method:null,taskId,timestamp}}. For USER_GUIDANCE_CLUSTER, it carries {delivery:{summary:{injected:0,queued:<agent-count>,total:<agent-count>},agents:{<agentId>:{status:'pending',reason:null,method:null,taskId}},timestamp}}. Preserve caller metadata beside, never instead of, this closed delivery object. Publishing null because the ledger is closing aborts delivery before any provider write.
- Extend the length-prefixed attach wire with exact messages
{type:'stdin',requestId:'input_<32-lower-hex>',data:'<base64>',guidanceSequence?:'<canonical-decimal>'} and {type:'input_result',requestId,ok:boolean,error:string|null}; add INPUT_RESULT:'input_result' to MessageType. createStdinMessage generates or validates the request ID, bounds decoded input to the OMP 1 MiB outbound-frame limit, and accepts guidanceSequence only from the cluster path. Existing PTY attach replies only after write() succeeds; sendInput decodes frames and waits for the exact request ID rather than treating its socket write as acceptance.
- Map cluster guidance to correlated OMP
steer. After OMP accepts, atomically CAS the active task's contiguous acceptedGuidanceThrough, insert-or-observe the immutable receipt below, and only then return success or allow terminal task completion. A stale task/session owner fails before steer.
- Add constant
USER_GUIDANCE_DELIVERY = 'USER_GUIDANCE_DELIVERY' but deliberately exclude it from GUIDANCE_TOPICS. Append one receipt per (clusterId,guidanceSequence,agentId,taskId) with ID msg_guidance_delivery_<sha256>, where the digest is SHA-256 over UTF-8 zeroshot:user-guidance-delivery:v1\0 followed by canonical JSON of that four-string tuple. The message is exactly {cluster_id:clusterId,topic:USER_GUIDANCE_DELIVERY,sender:'orchestrator',receiver:agentId,content:{data:{guidanceSequence,agentId,taskId,outcome,method,reasonCode}}}. outcome is accepted|rejected|indeterminate; method is always rpc; reasonCode is null only for accepted, provider-rejected for rejected, or one of ack-lost|task-changed|cursor-persist-failed|process-failed for indeterminate. Never copy guidance text. Insert-or-observe returns the byte-equivalent existing receipt or fails closed on collision.
- Preserve public return shapes: accepted RPC maps to
{status:'injected',reason:null,method:'rpc',taskId}. Provider rejection, post-write indeterminacy, cursor gap, and Docker unsupported map to the existing {status:'unsupported',reason,method:null,taskId} shape with reason provider-rejected|indeterminate-delivery|guidance-order-gap|docker-live-guidance-unsupported; cluster summaries remain injected|queued. Receipt messages never enter mailbox content.
- Keep ledger history append-only. Add a resolver that reads legacy inline
metadata.delivery or folds all new receipts in canonical ledger sequence over pending, exposing the latest attempt without overwriting earlier rejection/indeterminate evidence; matching deterministic-ID retries must observe byte-equivalent content or fail closed. Update guidance status/export surfaces to use it. The mailbox still returns only original guidance rows, so old ledgers remain readable and no migration rewrites history.
- Advance the reusable provider-session guidance cursor through accepted live guidance only when that exact task commits logical/schema/hook success. Rejection or task failure leaves the original sequence queued for safe-point delivery. A crash after OMP acceptance but before durable task acknowledgement is explicitly indeterminate and may replay; the contract is at-least-once with no silent loss, not impossible cross-process exactly-once.
- Once
steer may have been written, a missing/ambiguous correlation or failure to persist the accepted cursor/receipt is a failed task commit barrier: record indeterminate when possible, issue bounded abort, fall back to owned termination, and move the partition to cleanup-required. A correlated negative response is instead a definitive rejected receipt and the task may continue. In either case the original guidance stays queued for a later safe point; external effects on an indeterminate path may therefore repeat.
- Direct standalone
zeroshot task attach input has no cluster ledger sequence: send correlated steer with requestId, return the exact provider acknowledgement, and make no durable-guidance or exactly-once claim. It never advances a cluster guidance cursor or emits USER_GUIDANCE_DELIVERY.
- Keep Docker live injection unsupported in this epic; adding it requires a separately scoped authenticated host/container bridge.
- Update
AGENTS.md with durable guidance ordering, receipt folding, commit barriers, and acknowledged attach semantics here.
Primary files
- OMP RPC session driver and pipe watcher
src/attach/protocol.js, src/attach/send-input.js, src/attach/attach-server.js
src/agent/agent-input-injector.js, src/agent/agent-lifecycle.js, src/orchestrator.js
src/guidance-topics.js, src/ledger.js, src/message-bus.js, and guidance status/export/receipt handling
- task-store guidance provenance/cursor transitions
- guidance/attach/crash-reconciliation tests and
AGENTS.md
Acceptance
- Every guidance effect has a prior durable sequence. OMP rejection records an immutable rejected receipt and leaves the sequence queued; a successful steered task advances a contiguous cursor and does not replay in the next context; task failure leaves it queued.
- Concurrent/gapped delivery, crash-before-write, crash-after-write-before-ack, cursor-before-receipt, deterministic-receipt replay/collision, and receipt-reconciliation vectors prove the stated ordered at-least-once/no-silent-loss semantics.
- Missing or ambiguous post-write correlation fails the task and retains cleanup ownership; it cannot be reported as accepted.
- Standalone OMP attach reports accepted/rejected
steer acknowledgements without claiming durable delivery; legacy PTY input receives an acknowledgement only after a successful write and otherwise behaves unchanged.
- Docker continues to return
unsupported and queues guidance for the next safe point.
Verification
npm run test:omp plus the focused guidance/attach/receipt/crash-window tests introduced by this issue.
Source contract
OMP RPC v2 steer/cancel contract
Delivery constraints
Keep this issue independently mergeable and OMP opt-in. Do not flip the default here unless this is issue I. Fix only this slice; do not add a second harness registry or retain compatibility shims. Validators inspect files and run the commands above directly; never put Git commands in validator prompts.
Parent: #864
Depends on: #866
Objective
Add durable ordered guidance and acknowledged live steering.
Scope
cluster_id TEXT,agent_id TEXT,live_guidance_base TEXT, andaccepted_guidance_through TEXT, mapped exactly to task fieldsclusterId,agentId,liveGuidanceBase, andacceptedGuidanceThrough. Both cursors are canonical unsigned decimal strings and start at the task's captured pre-prompt ledger high-water;acceptedGuidanceThroughadvances only contiguously. Existing rows migrate toNULL; an OMP live-steer attempt requires all four values and exact agreement with the activeompSessionOwnership.owner. Persist guidance before effects by appending the originalUSER_GUIDANCE_*row withmetadata.delivery.status:'pending', then serialize delivery per agent. If an older queued sequence creates a cursor gap, return queued reasonguidance-order-gapwithout writingsteer.For
USER_GUIDANCE_AGENT, the original row carries{delivery:{status:'pending',reason:null,method:null,taskId,timestamp}}. ForUSER_GUIDANCE_CLUSTER, it carries{delivery:{summary:{injected:0,queued:<agent-count>,total:<agent-count>},agents:{<agentId>:{status:'pending',reason:null,method:null,taskId}},timestamp}}. Preserve caller metadata beside, never instead of, this closed delivery object. Publishingnullbecause the ledger is closing aborts delivery before any provider write.{type:'stdin',requestId:'input_<32-lower-hex>',data:'<base64>',guidanceSequence?:'<canonical-decimal>'}and{type:'input_result',requestId,ok:boolean,error:string|null}; addINPUT_RESULT:'input_result'toMessageType.createStdinMessagegenerates or validates the request ID, bounds decoded input to the OMP 1 MiB outbound-frame limit, and acceptsguidanceSequenceonly from the cluster path. Existing PTY attach replies only afterwrite()succeeds;sendInputdecodes frames and waits for the exact request ID rather than treating its socket write as acceptance.steer. After OMP accepts, atomically CAS the active task's contiguousacceptedGuidanceThrough, insert-or-observe the immutable receipt below, and only then return success or allow terminal task completion. A stale task/session owner fails beforesteer.USER_GUIDANCE_DELIVERY = 'USER_GUIDANCE_DELIVERY'but deliberately exclude it fromGUIDANCE_TOPICS. Append one receipt per(clusterId,guidanceSequence,agentId,taskId)with IDmsg_guidance_delivery_<sha256>, where the digest is SHA-256 over UTF-8zeroshot:user-guidance-delivery:v1\0followed by canonical JSON of that four-string tuple. The message is exactly{cluster_id:clusterId,topic:USER_GUIDANCE_DELIVERY,sender:'orchestrator',receiver:agentId,content:{data:{guidanceSequence,agentId,taskId,outcome,method,reasonCode}}}.outcomeisaccepted|rejected|indeterminate;methodis alwaysrpc;reasonCodeisnullonly for accepted,provider-rejectedfor rejected, or one ofack-lost|task-changed|cursor-persist-failed|process-failedfor indeterminate. Never copy guidance text. Insert-or-observe returns the byte-equivalent existing receipt or fails closed on collision.{status:'injected',reason:null,method:'rpc',taskId}. Provider rejection, post-write indeterminacy, cursor gap, and Docker unsupported map to the existing{status:'unsupported',reason,method:null,taskId}shape with reasonprovider-rejected|indeterminate-delivery|guidance-order-gap|docker-live-guidance-unsupported; cluster summaries remaininjected|queued. Receipt messages never enter mailbox content.metadata.deliveryor folds all new receipts in canonical ledger sequence overpending, exposing the latest attempt without overwriting earlier rejection/indeterminate evidence; matching deterministic-ID retries must observe byte-equivalent content or fail closed. Update guidance status/export surfaces to use it. The mailbox still returns only original guidance rows, so old ledgers remain readable and no migration rewrites history.steermay have been written, a missing/ambiguous correlation or failure to persist the accepted cursor/receipt is a failed task commit barrier: recordindeterminatewhen possible, issue boundedabort, fall back to owned termination, and move the partition to cleanup-required. A correlated negative response is instead a definitiverejectedreceipt and the task may continue. In either case the original guidance stays queued for a later safe point; external effects on an indeterminate path may therefore repeat.zeroshot task attachinput has no cluster ledger sequence: send correlatedsteerwithrequestId, return the exact provider acknowledgement, and make no durable-guidance or exactly-once claim. It never advances a cluster guidance cursor or emitsUSER_GUIDANCE_DELIVERY.AGENTS.mdwith durable guidance ordering, receipt folding, commit barriers, and acknowledged attach semantics here.Primary files
src/attach/protocol.js,src/attach/send-input.js,src/attach/attach-server.jssrc/agent/agent-input-injector.js,src/agent/agent-lifecycle.js,src/orchestrator.jssrc/guidance-topics.js,src/ledger.js,src/message-bus.js, and guidance status/export/receipt handlingAGENTS.mdAcceptance
steeracknowledgements without claiming durable delivery; legacy PTY input receives an acknowledgement only after a successful write and otherwise behaves unchanged.unsupportedand queues guidance for the next safe point.Verification
npm run test:ompplus the focused guidance/attach/receipt/crash-window tests introduced by this issue.Source contract
OMP RPC v2 steer/cancel contract
Delivery constraints
Keep this issue independently mergeable and OMP opt-in. Do not flip the default here unless this is issue I. Fix only this slice; do not add a second harness registry or retain compatibility shims. Validators inspect files and run the commands above directly; never put Git commands in validator prompts.