PRODUCT.md — Open Question (OQ) Escalation Channel
PRODUCT.md — Open Question (OQ) Escalation Channel
Section titled “PRODUCT.md — Open Question (OQ) Escalation Channel”Summary
Section titled “Summary”A formal, durable channel by which a cmux sub-worker (running
workflow-orchestration mid-Task) can escalate an Open Question (OQ) to its
parent orchestrator session, and by which the parent’s decision can flow back to
the worker. The channel supports both blocking OQs (worker pauses until the
parent decides) and non-blocking OQs (worker continues; parent processes
asynchronously). The channel exists so that the orchestrator-of-orchestrators
pattern scales beyond ad-hoc OQ embedding in final-turn assistant text or
out-of-band disk writes.
Problem
Section titled “Problem”S61’s orchestrator-of-orchestrators pattern dispatches multiple parallel cmux
sub-workers, each running the full workflow-orchestration skill on its own
ID-N Task. Workers proceed independently — but a worker mid-Task can hit
questions only the parent can resolve:
- Spec ambiguity at the Planning phase (PRODUCT.md / TECH.md inconsistency).
- Scope renegotiation request from a worker’s Executor.
- Checker FAIL pattern requiring parent-level judgment.
- A cross-Task dependency surfacing that breaks the sibling-only constraint.
Today, a worker has no formal way to ask. It can embed OQs in the final
assistant turn before stop, or scribble to disk; either way the parent must
poll or read post-hoc. This serialises an inherently parallel workflow and
forces the parent to either over-poll or accept arbitrary latency.
- Workers can emit OQs at any point in the Task lifecycle, durably, without losing state or terminating.
- The parent can consume OQs in a deterministic order and reply with decisions that workers reliably receive.
- Blocking and non-blocking semantics are both supported and explicit on emission.
- Worker and parent both survive restarts mid-OQ without losing or duplicating outcomes.
Non-goals
Section titled “Non-goals”- Transport choice. Whether OQs travel via JSONL append, SQLite, named
pipes, cmux
send, or something else is a TECH.md concern. This spec describes only the semantics the transport must satisfy. - OQ classification. Deciding which OQs are “good” vs “premature” vs
resolvable-locally is a
workflow-orchestrationconcern, not a channel concern. - Parent-side UI. Any UI for the parent to browse, triage, or batch OQs is a future enhancement; this spec covers the wire-level behaviour only.
- Multi-parent fan-in / OQ routing across orchestrator generations. A worker has exactly one parent and OQs go to that parent. Deeper trees are out of scope.
Behavior
Section titled “Behavior”OQs are the surface. “User” here means two consumers of the surface:
- Worker — the sub-Claude session emitting OQs and receiving decisions.
- Parent — the orchestrator session reading OQs and writing decisions.
Numbered invariants below are testable from either side of the channel. They
are grouped by dimension but share a single OQ-INV-N namespace so test ids
remain stable as the spec evolves.
Channel mechanism
Section titled “Channel mechanism”- OQ-INV-1. Each OQ is a self-contained record with at minimum: a unique
oq_idstable for the lifetime of the OQ; theworker_id(or session id) of the emitter; anemitted_attimestamp (UTC, ISO-8601); aquestionfield carrying the human-readable text the parent must answer; anurgencyone of{low, normal, high}; ablockingboolean; and acontext_refpointing to enough information (e.g. file path, commit SHA, Subtask ID) for the parent to act without re-deriving the situation. - OQ-INV-2. An OQ record is immutable once emitted. Corrections or
refinements are expressed as new OQs (with a
supersedesfield referencing the originaloq_id), never as in-place edits. - OQ-INV-3. OQ emission is atomic from the parent’s perspective: the parent either observes the complete record or does not observe it at all. Partial records (truncated JSON, half-written rows) are never visible.
- OQ-INV-4. Per-worker FIFO ordering: if a worker emits OQ A before OQ B, any reader of that worker’s OQ stream observes A before B. No global cross- worker ordering is required.
- OQ-INV-5. OQ emission is durable: once the emitter’s emit call returns success, the record survives a worker crash, parent crash, or host reboot, and is observable by the parent on the next read.
- OQ-INV-6. OQ records are append-only from the worker’s perspective. A
worker cannot retract an OQ; it can only emit a follow-up OQ that
supersedes or cancels it (
status: cancelled, withsupersedes).
Lifecycle
Section titled “Lifecycle”- OQ-INV-7. A worker MAY emit an OQ at any point during a Task — Planner
phase, Executor phase, Checker phase, Curator phase, or between phases. The
channel does not constrain when OQs are valid;
workflow-orchestrationdoes. - OQ-INV-8. When a worker emits a
blocking: trueOQ, it MUST NOT make further progress on the Task that depends on the answer until a matching decision is observed. The worker MAY continue independent side-work (e.g. running tests, writing journal entries) that does not depend on the OQ. - OQ-INV-9. When a worker emits a
blocking: falseOQ, it continues immediately. The OQ’s decision, when it arrives, is applied if still relevant, or discarded if the worker has already chosen a path that subsumes it. - OQ-INV-10. A blocking OQ has at most one resolving decision. The
decision either answers it (
decision.outcome ∈ {answered, deferred, cancelled}) or directs the worker to stop the Task (outcome: abort_task). The decision schema mirrors the OQ:oq_id,decided_at,outcome,answer(free text), and an optionaldirectivefield for structured instructions. - OQ-INV-11. Acknowledgement. A worker observes that its OQ has been
seen when (and only when) it observes a corresponding decision record.
There is no separate “seen-but-not-yet-decided” signal — absence of a
decision means “no decision yet”. This keeps the protocol two-state and
avoids spurious mid-state notifications.
- Open question: is a lightweight
receivedack required for observability (e.g. so the parent can show “OQ open for 30 minutes” in a future UI), or does decision-or-nothing suffice?
- Open question: is a lightweight
- OQ-INV-12. Idempotent emission. If a worker emits an OQ, restarts
before observing confirmation, and re-attempts the same OQ, the resulting
state is indistinguishable from a single emission. Workers achieve this by
deriving
oq_idfrom stable inputs (Task ID, phase, content hash) so a re-emission produces the same id; the channel deduplicates byoq_id. - OQ-INV-13. Cancellation. A worker MAY cancel an open OQ it emitted
by writing a follow-up OQ with the same
oq_idandstatus: cancelled. A cancellation is itself a record subject to OQ-INV-3 and OQ-INV-5. The parent SHOULD treat a cancelled OQ as resolved and not write a decision for it; if a decision arrives anyway, the worker ignores it.
Decision propagation
Section titled “Decision propagation”- OQ-INV-14. Decisions are durable, atomic, and append-only with the same guarantees as OQ records (OQ-INV-3, OQ-INV-5, OQ-INV-6).
- OQ-INV-15. Decisions are addressed: every decision carries the
oq_idit resolves. A worker’s decision-reader filters byoq_id; it never consumes a decision intended for another OQ. - OQ-INV-16. At-least-once delivery. A decision written by the parent is observed by the worker at least once, provided the worker is alive long enough to read it. The worker tolerates duplicate observations of the same decision (idempotent application — applying the same decision twice yields the same worker-state as applying it once).
- OQ-INV-17. In-order-per-OQ. Only one decision per OQ is consumed. If duplicates appear, only the first observed decision takes effect; subsequent duplicates are no-ops. If the parent writes conflicting decisions for the same OQ (a protocol violation), the worker takes the first and surfaces the conflict via a new OQ.
- OQ-INV-18. Latency bound. A blocking OQ that has a corresponding decision written by the parent unblocks the worker within a configurable latency budget (default: 10 seconds wall-clock) on the worker’s next decision-polling cycle. The exact polling cadence is a TECH concern; the budget is the testable behaviour.
- OQ-INV-19. Decisions never carry executable code intended to be
silently applied by the worker. A decision is advisory text plus an
optional structured
directive(e.g.{kind: 'abort_task'}or{kind: 'rewrite_spec', target: 'PRODUCT.md', section: '3.2'}); the worker decides how to act on it within its ownworkflow-orchestrationrules. This preserves the worker’s auditability.
Worker state during open OQ
Section titled “Worker state during open OQ”- OQ-INV-20. A worker with a blocking OQ open is in a distinct,
parent-observable state —
awaiting-decision— separate fromworking,stopped,crashed. The parent can enumerate workers by state without reading individual OQ streams. - OQ-INV-21. When a worker enters
awaiting-decision, it persists enough state to resume the blocked operation after the decision arrives. The worker treats the wait as a checkpoint boundary: on restart while blocked, it re-entersawaiting-decisionand resumes polling, without re-executing the work that produced the OQ. - OQ-INV-22. A worker with only non-blocking OQs outstanding remains in
working(notawaiting-decision). Non-blocking OQs do not affect the worker’s lifecycle state. - OQ-INV-23. When the parent observes a worker in
awaiting-decision, it can list that worker’s open OQs deterministically (FIFO per OQ-INV-4), so the parent always knows what to answer first. - OQ-INV-24. Worker
stopevents and OQ emissions are independent. A worker canstop(turn complete) with non-blocking OQs still open; the parent processes those OQs against the next worker turn or fleet-level review. A worker MUST NOTstopwhile a blocking OQ is open and undecided — it remains inawaiting-decisionuntil resolved.- Open question: is there an upper bound on how long a worker may
remain in
awaiting-decisionbefore the parent is required to either decide or abort? This spec defers the timeout policy toworkflow-orchestration; the channel itself imposes no timeout.
- Open question: is there an upper bound on how long a worker may
remain in
Reliability invariants (cross-cutting)
Section titled “Reliability invariants (cross-cutting)”- OQ-INV-25. No loss. No OQ that the worker considers successfully emitted is invisible to the parent on a subsequent read. Crash-then-read must yield the OQ.
- OQ-INV-26. No duplication, from the parent’s perspective. The
parent observes each distinct OQ exactly once when iterating by
oq_id. Re-emissions (OQ-INV-12) deduplicate to the same record. - OQ-INV-27. No silent corruption. A record that fails an integrity
check (missing required fields, malformed timestamp, unknown
urgency) is surfaced as a channel error, not silently dropped. The reader fails closed: it reports the error and refuses to advance past the bad record until an operator resolves it. - OQ-INV-28. Per-worker isolation. OQs emitted by worker A are not visible in worker B’s OQ stream. Cross-worker discovery, if needed, is the parent’s job (e.g. enumerating all worker directories).
- OQ-INV-29. Worker restart safety. A worker can crash and be
relaunched with the same identity (same
worker_id); on restart it can re-read its own OQ stream and the corresponding decision stream and re-derive whether each OQ is unresolved, decided, or cancelled, without parent involvement. - OQ-INV-30. Parent restart safety. A parent can crash and be relaunched; on restart it can re-read every worker’s OQ stream and decision stream and re-derive the set of open OQs awaiting decision, without worker involvement. No in-memory state is required for correctness.
- OQ-INV-31. Observable provenance. Every OQ and every decision is
traceable to its emitter via the record itself (
worker_id/ parent-session id) and to its temporal position (emitted_at/decided_at). The channel does not depend on filesystem metadata for provenance.
Acknowledgement and idempotency summary
Section titled “Acknowledgement and idempotency summary”- OQ-INV-32. The worker’s contract: “I emit; if I see a decision for my
oq_id, I act on it; if I crash and restart, I check before re-emitting; I never assume an OQ was received until I see a decision.” - OQ-INV-33. The parent’s contract: “I read OQs in per-worker FIFO
order; for each OQ I either write exactly one decision or leave it open;
I tolerate seeing the same OQ across restarts; I never write a decision
for an
oq_idI have already decided.”
Open questions
Section titled “Open questions”Collected from inline **Open question:** markers above:
- OQ-Q1 (OQ-INV-11). Should the channel carry a
receivedacknowledgement distinct from thedecision, to support future parent-side UI (“OQ open for 30 min”)? The current spec deliberately keeps the protocol two-state to minimise surface area; revisit if observability needs grow. - OQ-Q2 (OQ-INV-24). Should the channel impose a maximum
awaiting-decisionduration before auto-abort, or is that strictly aworkflow-orchestrationpolicy concern? Current default: channel imposes none; orchestrator decides. - OQ-Q3. Should non-blocking OQs accumulate beyond a per-worker cap to prevent runaway emission from a confused worker? Out of scope here; revisit with the first integration that exposes the pathology.