Repository navigation
feat: pause at a human gate — PausingInterviewer, checkpointed gate record, tracker.Answer - #734
clintecker wants to merge 12 commits into
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 25 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ed payload, stale records, OS run lock - Several gates waiting in one parallel node looped forever when answered one at a time: each answer re-ran the node, the unanswered sibling paused again and the given answer was discarded. The pausing interviewer now holds one answer per gate (NewPausingInterviewerFor), and tracker.ValidateAnswers requires an answer for every waiting gate (AnswerInput.Gates, keyed by node ID or <scope>/<node>); a single answer with several waiting is refused, naming them. A branch gate that is its branch's target no longer repeats its node ID in its scope path. AnswerInput.Node is gone (unreleased). - gate_paused carries the waiting gate's node and scope, how many gates wait, and the tracker answer command (pipeline.AnswerCommand), in GateDetail, the event message, the activity log and the NDJSON stream. - A run that ends any way but paused at a gate drops its waiting-gate record and gate.json (a failure at the gate, a --from resume around it). - The run lock is an OS file lock (flock / LockFileEx), released by the OS when the holder exits, so no stale-lock takeover (and no race in it). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ed payload, stale records, OS run lock - Several gates waiting in one parallel node looped forever when answered one at a time: each answer re-ran the node, the unanswered sibling paused again and the given answer was discarded. The pausing interviewer now holds one answer per gate (NewPausingInterviewerFor), and tracker.ValidateAnswers requires an answer for every waiting gate (AnswerInput.Gates, keyed by node ID or <scope>/<node>); a single answer with several waiting is refused, naming them. A branch gate that is its branch's target no longer repeats its node ID in its scope path. AnswerInput.Node is gone (unreleased). - gate_paused carries the waiting gate's node and scope, how many gates wait, and the tracker answer command (pipeline.AnswerCommand), in GateDetail, the event message, the activity log and the NDJSON stream. - A run that ends any way but paused at a gate drops its waiting-gate record and gate.json (a failure at the gate, a --from resume around it). - The run lock is an OS file lock (flock / LockFileEx), released by the OS when the holder exits, so no stale-lock takeover (and no race in it). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
266e1a8 to
961513d
Compare
…ly gate reads, answer command flags, interrupted resume keeps the gate - A parallel node whose answered gates re-pause one at a time (a changed question) alternated between its gates forever: the re-pause recorded only the gate that asked again. It now records the node's other gates again too (withAnsweredSiblings) — their answers were used, and the node re-runs. - PendingGates / WaitingRuns / Answer read only the secure checkpoint: a checkpoint planted in the tool-writable run dir is never listed or resumed. - The gate_paused answer command carries the run's -w / --artifact-dir (WithAnswerCommandFlags, set by the library from WorkingDir / ArtifactDir) and no shell comment; AnswerAlternatives lists the other answers. - A canceled resume (Ctrl+C during an interactive tracker -r) keeps the waiting-gate record instead of dropping it with the fail terminal. - Size-ratchet moves: generateRunID to engine_run_identity.go, the budget usage helpers to budget.go (baseline shrinks). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01647BKi372JDvjUGxQeYU9g
… mirror A PauseError can carry the waiting gate(s) (PausedGate: the gate's exact interviewer arguments, full prompt, fingerprint and nonce). The engine stores them on Checkpoint.PausedGates before saving, stamps the cumulative usage on PriorUsage, writes a 0600 gate.json mirror in the run dir (symlink refused, atomic rename; never read back), and puts the gate on the gate_paused event (GateDetail.Resolution "paused" + Fingerprint, also on the activity log and the NDJSON stream). The record and mirror clear when the paused node completes; a run start with no waiting gate removes a stale mirror. buildSuccessResult moves to terminal_emit.go for the size ratchet. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The human handler builds the gate's GateInfo once (now with the full prompt, ScopePath and BranchIndex) and passes it to the interviewer on the gate call's context (WithGateInfo / GateInfoFrom) as well as to BeginGate, so an interviewer serving concurrent branch gates reads the right gate from the call. Subgraph and manager_loop children run under WithSubgraphScope; the parallel handler marks each branch context with internal branch scope and index keys (internal, so they never leak past the join). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nswer once PausingInterviewer records exactly what each gate asked (its Ask* arguments, the GateInfo from the call's context, interview questions incl. context, a fingerprint and a fresh nonce) and pauses with NewPausedGateError, or returns the in-memory PendingAnswer once for the gate with the same node, scope and fingerprint. A changed question drops the answer and re-pauses with a note. In pause mode a gate inside a subgraph / manager_loop child fails closed (routable OutcomeFail); a paused gate's gate_resolved carries Resolution "paused" and no Error. GateQuestion gains Context. CallbackInterviewer and QueueInterviewer move to interviewer_callback.go for the size ratchet. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…first A parallel node whose branches pause at gates returns one paused_gate pause carrying every branch's gate record, ordered by branch index (it used to keep only the first branch's pause). A billing pause in any branch outranks gate pauses and carries no gate record: the resume re-runs the node and its gates ask again. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resuming a run whose checkpoint has PausedGates skips resume compaction (the gate must re-render the question it recorded, or the answer never matches) and seeds the budget guard's baseline from PriorUsage, so the cost and token caps bound the whole run across answers. Other resumes are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Config.GateMode ("pause" / "console", after Interviewer / AutoApprove /
WebhookGate / Autopilot; pause requires ArtifactDir). tracker.Answer resumes a
paused_gate run with an answer validated against the checkpoint's record
(ValidateAnswer: per-mode rules, case-insensitive labels, errors list the valid
answers), re-checking the record's nonce under the run lock; WaitingRuns and
PendingGates read the authoritative checkpoint. Every ResumeRunID run holds a
per-run lock in the secure dir (pipeline.AcquireRunLock: O_EXCL + PID, stale
locks replaced). Result / RunSummary gain PausedGate. Docs: embedding,
transport boundary, human handler (pause mode), engine (pausing), API
stability, site transport page. Size-ratchet moves: resume setup to
tracker_resume.go, terminal result builders to terminal_emit.go.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rprint Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The CLI's -r resolves the run to Config.CheckpointDir rather than setting ResumeRunID, so the lock keyed only on ResumeRunID missed it. Any existing checkpoint the run resumes from now takes the lock. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ed payload, stale records, OS run lock - Several gates waiting in one parallel node looped forever when answered one at a time: each answer re-ran the node, the unanswered sibling paused again and the given answer was discarded. The pausing interviewer now holds one answer per gate (NewPausingInterviewerFor), and tracker.ValidateAnswers requires an answer for every waiting gate (AnswerInput.Gates, keyed by node ID or <scope>/<node>); a single answer with several waiting is refused, naming them. A branch gate that is its branch's target no longer repeats its node ID in its scope path. AnswerInput.Node is gone (unreleased). - gate_paused carries the waiting gate's node and scope, how many gates wait, and the tracker answer command (pipeline.AnswerCommand), in GateDetail, the event message, the activity log and the NDJSON stream. - A run that ends any way but paused at a gate drops its waiting-gate record and gate.json (a failure at the gate, a --from resume around it). - The run lock is an OS file lock (flock / LockFileEx), released by the OS when the holder exits, so no stale-lock takeover (and no race in it). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ly gate reads, answer command flags, interrupted resume keeps the gate - A parallel node whose answered gates re-pause one at a time (a changed question) alternated between its gates forever: the re-pause recorded only the gate that asked again. It now records the node's other gates again too (withAnsweredSiblings) — their answers were used, and the node re-runs. - PendingGates / WaitingRuns / Answer read only the secure checkpoint: a checkpoint planted in the tool-writable run dir is never listed or resumed. - The gate_paused answer command carries the run's -w / --artifact-dir (WithAnswerCommandFlags, set by the library from WorkingDir / ArtifactDir) and no shell comment; AnswerAlternatives lists the other answers. - A canceled resume (Ctrl+C during an interactive tracker -r) keeps the waiting-gate record instead of dropping it with the fail terminal. - Size-ratchet moves: generateRunID to engine_run_identity.go, the budget usage helpers to budget.go (baseline shrinks). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01647BKi372JDvjUGxQeYU9g
…only Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01647BKi372JDvjUGxQeYU9g
5ed4be0 to
ec0347e
Compare
Problem
Second of three PRs for #732. With PR 1's pause plumbing in place, nothing yet pauses a human gate, and a library caller has no way to answer one. Running from an agent's shell, CI or a script still means stdin,
--auto-approve,--autopilotor a webhook.Fix
handlers.PausingInterviewer. Records exactly what each gate asked and pauses the run withpipeline.NewPausedGateError. The record (pipeline.PausedGate) holds:Ask*arguments as passed;GateFingerprint) and a random nonce.Alternatively it returns an in-memory
PendingAnsweronce, to the gate with the same node, scope and fingerprint. If the question changed, the answer is dropped and the gate pauses again with a note.Engine. Stores the records on
Checkpoint.PausedGates(secure state dir, authoritative) andCheckpoint.PriorUsage. It mirrors them to<run dir>/gate.json(0600, symlink refused, atomic, never read back) and puts the gate ongate_paused. Records clear when the paused node completes; a run start with no waiting gate removes a stale mirror.Per-gate context.
handlers.WithGateInfo/GateInfoFromcarry the gate's identity, full prompt,ScopePathand branch index on everyAsk*Contextcall. Subgraph andmanager_loopchildren run underpipeline.WithSubgraphScope, and branch contexts get internal_branch_scope/_branch_indexkeys.Pause mode inside a subgraph or
manager_loopchild fails closed (routableOutcomeFail). A paused gate'sgate_resolvedcarriesresolution: "paused"and no error.Parallel. Every waiting branch gate is recorded, lowest branch first. A billing pause outranks the gates and carries no gate records.
Resume of a gate-paused run. No context compaction (the question must re-render identically), and the budget baseline is seeded from
PriorUsage, so caps span every answer.Library.
Config.GateMode(pauserequiresArtifactDir;console; lowest priority).tracker.Answer,ValidateAnswer,PendingGates,WaitingRuns.Result.PausedGate/RunSummary.PausedGate.pipeline.AcquireRunLock, taken by everyResumeRunIDrun, stale locks replaced.Answerre-checks the nonce under the lock.Wire.
gate_resolution/gate_fingerprinton the activity log and NDJSON;GateQuestion.context.Docs.
embedding.md,transport-boundary.md,handlers/human.md(pause mode, plus the not-tamper-proof residual),engine.md(pausing),api-stability.md, site transport page, CHANGELOG.Size-ratchet moves.
interviewer_callback.go,parallel_events.go,parallel_pause.go,tracker_resume.go; terminal results moved toterminal_emit.go. The baseline only shrinks.Verification
gate_pauseddetail;GateInfoon context, including inside parallel branches;PausingInterviewerin every mode: apply-once, changed question, other scope, interview answers and cancel;ValidateAnswertable (incl." Approve ", RF3),GateModeprecedence,Answerround trip / not waiting / two gates / planted files ignored / nonce changed;WaitingRuns, run lock (concurrent + stale + released after a canceled answer, RF4).go test ./... -shortgreen; API surface and wire envelope goldens updated; activity schema regenerated; pre-commit gates green.PR 1 (#733) is merged; rebased onto
main.Part of #732
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.