Repository navigation
feat: session-close reports an open parent goal instead of asking - #315
Conversation
A task-anchored session that finishes its anchor no longer ends on an approve: line asking the operator to confirm leaving the parent goal in_progress. Phase 4.5 splits the two cases it used to merge: - a goal with at least one open task is the ordinary steady state (goals span 1-4 weeks, sessions do not) and its status is the manager's own write, so it emits one report-only information line and never an outstanding item, approve: line, or AskUserQuestion; - a closable goal (every task terminal, every SC ticked) still owes a decision, so it is SendMessage-routed to its live manager and falls back to today's operator line when no live manager resolves. Only session-liveness.py exit 0 counts as live; a worker never resumes or starts a manager. The anchor-task block is unchanged.
…0 cut The release cut renamed Unreleased to v0.173.0 between the branch point and the master merge, absorbing this branch's bullet into a released section that no tag contains. Split it back out per the fold guard.
fleet-surface.md's task-level chain ends at the Fleet Manager; this goal-level chain stops at no live manager, because a worker messages only a manager it can prove is live. Stating the difference keeps the two rules from reading as one.
There was a problem hiding this comment.
Now let me do the manual review of the diff, addressing each concern from the Plan.
Manual Review
Concern 1: Manager resolution exit code correctness
The diff correctly implements the exit code interpretation:
- "Only exit 0
LIVEcounts as a live manager; exit 1ABSENT, 2UNKNOWN, 3AMBIGUOUS, 4MALFORMED, and no id anywhere all read as no live manager." - ✅ Correct
Concern 2: Open goal vs. closable goal split
The diff correctly implements the split at lines 345-358:
- "A goal with ≥1 open task — report only, never ask" → information line in Phase 9 summary
- "A closable goal (every task terminal, every SC ticked) — a decision is owed" → SendMessage to live manager or operator line
- ✅ Correct
Concern 3: SendMessage id-to-name mapping
The diff uses session-liveness.py --list --json which carries both sessionId and name — this is the correct source for the mapping. The ListAgents roster limitation (showing only registry ref) is explicitly noted.
- ✅ Correct
Concern 4: Within-one-session repeat-run carry-forward
The carry-forward clause at line 362 correctly:
- Suppresses re-flagging when a resolution already exists
- Carries forward in the summary block, not the numbered list
- Re-flags only if
statusor# Taskschanged - ✅ Correct
Concern 5: Goal-anchored exemption scope
The exemption correctly covers only the goal half of the gate (tasks still gated). The Phase 9 summary line (goal … information line) replaces the outstanding item for open goals.
- ✅ Correct
Additional observation: The warning "manager-liveness.py --state --topic is not a substitute" is a good addition that prevents a plausible mis-use of the wrong tool.
{
"verdict": "approve",
"summary": "The session-close command gains a Phase 4.5 split that routes open goals (≥1 open task) to a report-only information line and closable goals to SendMessage/live manager or operator line. All five Plan concerns are correctly addressed: exit code semantics are precise, the open/closable split is well-implemented, id-to-name mapping uses the correct source, the within-one-session carry-forward logic is sound, and the goal-anchored exemption scope is correct. No blocking issues found.",
"comments": [],
"concerns_addressed": [
{
"concern": "correctness: Manager resolution uses session-liveness.py --check exit code",
"disposition": "addressed",
"detail": "Lines 341-342 correctly interpret exit 0 as LIVE only; exits 1/2/3/4 and no id all map to 'no live manager'"
},
{
"concern": "correctness: The split between open goal and closable goal",
"disposition": "addressed",
"detail": "Lines 345-358 implement the split correctly: ≥1 open task → report-only info line; all terminal + all SC ticked → SendMessage or operator line"
},
{
"concern": "security: SendMessage id-to-name mapping",
"disposition": "addressed",
"detail": "Lines 354-355 use session-liveness.py --list --json (which carries sessionId AND name) rather than ListAgents roster"
},
{
"concern": "correctness: Within-one-session repeat-run carry-forward",
"disposition": "addressed",
"detail": "Lines 362-363 implement carry-forward correctly: summary block only, re-flag on status/Tasks change, suppression of approve: line"
},
{
"concern": "correctness: Goal-anchored exemption scope",
"disposition": "addressed",
"detail": "Lines 378-381 correctly scope exemption to goal half only; task half still gates; Phase 9 info line replaces outstanding item"
}
]
}
Why
A worker that finishes its anchor task under a goal that is still
in_progressendssession-closeon an operator-facingapprove:line asking to confirm the goal stays open. Goal status is the managing session's call, so the worker parks on a question nobody needs to answer and keeps its spawn slot.Measured 2026-10-19 by the Development Pipeline Throughput Manager: three finished workers in ~40 minutes — "Measure Free Capacity on the Nuke Dev, Nuke Prod and Quant Clusters", "Diagnose Whether the Serialized Cluster Agents Can Run More Than One Job", and "Count Only Operator-Typed Prompts as Interactions" (completed/done, 0 open boxes) — each ended on
approve: leave [[…]] in_progress. At the timeworker-sessions.py --countread 30 againstspawn.maxConcurrent30 soft / 50 hard, with 75 approved-not-started tasks waiting.What changed
Phase 4.5 previously merged two different cases into one outstanding item. It now splits them:
goal <title>: in_progress, <n>/<m> SC — managed by <manager>(or— no live manager)No outstanding item, no
approve:/pick:/you run:/review:line, noAskUserQuestionnaming the goal.SendMessage, and falls back to today's operator line when no live manager resolves.Manager resolution: the goal page's
manager_session_id, else themanager_session_idof a topic page whose## Goalslists the goal. The id is a lead, never a verdict — onlysession-liveness.py --checkexit 0 (LIVE) counts;ABSENT/UNKNOWN/AMBIGUOUS/MALFORMEDand no id all read as no live manager. A worker never resumes or starts a manager, soSendMessageis the only action available.The anchor-task block is unchanged.
Verification
make precommit— pass.scripts/mode-membership-rule-test.sh— 13 passed, 0 failed.scripts/check-changelog.sh— structure OK; fold guard run after mergingorigin/master, and the bullet was split back out of thev0.173.0section the release cut had absorbed it into.The task's own acceptance criteria need the deployed command (they read a real
session-closetranscript), so they run after release and localclaude plugin update, not against this branch:in_progressgoal emits no ask naming the goal and exactly onegoal …information line.manager_session_idis live →SendMessage, no operator line.Results land in the task's
# Resultssection.