8000 A goal its sprints did not meet is retired, and the loop moves on (M9.50) by adeelahmad · Pull Request #103 · adeelahmad/aloop · GitHub
[go: up one dir, main page]

Skip to content

A goal its sprints did not meet is retired, and the loop moves on (M9.50) - #103

Merged
adeelahmad merged 7 commits into
masterfrom
claude/m9-loop-skill-zkmelo
Oct 20, 2026
Merged

adeelahmad merged 7 commits into
masterfrom
claude/m9-loop-skill-zkmelo

Conversation

@adeelahmad
@adeelahmad adeelahmad commented Oct 20, 2026 •
Copy link
Copy Markdown
Owner

Requested by Adeel · project thread

Before: suppose every intent of a goal was sprinted and the goal's eval still listed gaps. goal close refused, since the goal was not met, and goal next had no intent left to hand out. So mission tick stopped at BLOCKED ("the human decides") and the autonomous loop could not move past an unmet goal.

After: aloop mission goal retire GOAL --eval EVAL records goal.retired in the committed ledger. A retired goal takes no more intents, sprints or close. mission evaluate then covers the whole mission again, and the loop derives the next goal from the gaps the retired goal did not try. mission tick hands this step to mission-planner as RETIRE_GOAL. Three retired goals in a row still go to the human.

How:

  • goal.py:
    • retire_problems is the one rule, for the command and for tick. It requires all of these:
      • the goal is the content derive recorded, active and not met;
      • every gap has an intent, and every intent of the goal (a second one for a gap included) has a plan.created;
      • each of those sprints is closed (sprints.json, read the way tick reads it);
      • EVAL is the newest intact eval, narrowed to the goal and not met, made after the last close, from the newest state report, itself measured after that close.
    • cmd_retire writes the goal.retired record first (role mission-planner: the eval, its event, the goal's own open gap ids, the baseline and state), then the status. A bare retired status is repaired, never trusted.
    • check, next, intents and close refuse a retired goal. check reports state retired, which roles.json takes as the planner's finding at its stop.
    • The retired goal's own open gaps are held from derive on that baseline until a new baseline version or the human's next approval of the mission. A regression is never held.
    • goal next hands every intent of the goal to intent check, not only the first per gap, so a retry intent can be sprinted and a broken one is named.
  • evaluate.py: active_goal is only ever the newest goal (by start time, then id), and None when that goal is met or retired.
  • Ledger: goal.retired joins LEDGER_EVENTS and is gate-owned (events emit refuses it).
  • tick.py:
    • Where it used to say BLOCKED, it asks G.retire_problems. A stale state or eval becomes ASSESS or EVALUATE; any other refusal is BLOCKED with the gate's reason; none hands out RETIRE_GOAL.
    • A goal with a goal.retired record is not in flight, and it counts as "was retired" toward the 3-failed-goals block.
    • When every gap is held by a retired goal, tick says BLOCKED with grant GOALS on the newest one, so the human's tick --force --by and lift line free them. The lift is dated by the goal id the block names, as the hold reads the record.
  • Docs: ROADMAP M9.50 done, new item M9.55 (one goal in flight), CHANGELOG, SKILL.md rows.

Call I made: retire, not re-plan. Re-planning the same gaps repeats work the sprints already tried. The 3-failed-goals breaker bounds a run of retirements.

Review rounds

Three adversarial review workflows ran on this PR. Every confirmed finding is fixed with a test:

  • Round 1 (6a35eef): a retirement from a stale state or eval; a second intent still sprinting; the planner's stop refusing every retirement; the next goal re-deriving the same gaps; evaluate falling back to an older goal; status written before the record; malformed run-log and sprints.json rows crashing tick.
  • Round 2 (2e6b473): goal next never handed out a retry intent; open could list another goal's gaps and regressions; a held BLOCKED had no lift; two goals derived in the same second tied in active_goal.
  • Round 3 (2d1d965): goal next skipped a retry intent whose file was gone or broken, so tick repeated SPRINT until BLOCKED; a lift after the retired goal's file was edited recorded occurrence 0 and freed nothing; the spoon-fed derive --pick refusal offered tick --force when it only works once every gap is held.
  • Refuted: goal next's planned set differing from tick's (no real caller reaches it). Also refuted: a fresh checkout before retirement losing sprint history. It predates this PR and is tracked as M9.26.

Evidence

  • scripts/test/mission-goal.test.py: 14 checks. Check 13 refuses each unmet condition with its own message, retires once (record fields asserted), repairs a bare status, names a gone, unreadable or copied retry intent at goal next, then shows a retired goal refused everywhere, its gaps held, a regression not held, and approval freeing them.
  • scripts/test/mission-tick.test.py: 33 checks. Check 33 is a real-clock run: ASSESS, EVALUATE, RETIRE_GOAL, a gate refusal as BLOCKED, a status-only retirement still RETIRE_GOAL, retire, held BLOCKED (grant GOALS) before and after rm -rf .aladdin and an edit of the retired goal's file, goal derive refusing, then the human's lift giving DERIVE_GOAL.
  • scripts/test/mission-roles.test.py: the planner's stop passes with a retired verdict.
  • RED: against master's code the goal test fails with "unknown command 'retire'" and the tick test with "expected RETIRE_GOAL, got BLOCKED". Removing the lift clause, or dating the lift from the goal file, makes check 33 fail with "expected DERIVE_GOAL, got BLOCKED". The broken retry intent fails check 13 on round 2's code (goal next exits 1).
  • make pycheck, scripts/check-tracking.py (98 requirements) and make test-hosts pass locally on 29bd8f7 (master with Mission role gates: rounds 3 and 4 review fixes (M9.9, M9.42) #92 and aloop stop: one stop for the whole project (M10.4) #109 merged in).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM

….50)

`mission goal retire GOAL --eval EVAL` records goal.retired in the committed ledger when every
intent of the goal was sprinted, its sprints closed and the newest eval of it, made after those
closes, still lists gaps. A retired goal takes no intents, sprints or close; `mission evaluate`
covers the whole mission again and the next goal is derived. `mission tick` hands this out as
RETIRE_GOAL (mission-planner) from the command's own rule (goal.retire_problems), else BLOCKED
with its reason; a retirement counts toward the 3-failed-goals block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026 8000
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
# Conflicts:
#	docs/ROADMAP.md
…e gaps it tried (M9.50 review)

An adversarial review of the first commit found these, each reproduced:
- A retirement could be made from a state measured before the sprints closed, or from an eval older
  than the newest state. Now the eval must be computed from the newest state report, and that state
  must be measured after the last close. Tick maps the gate's refusal to ASSESS or EVALUATE.
- A second intent for a gap, still sprinting, was not counted. Now every intent of the goal counts,
  the same set tick reads.
- The planner's stop gate refused every retirement. The goal check now reports `retired` as the
  planner's finding (roles.json verdicts).
- The next goal re-derived the same gaps. A retired goal's open gaps are now held on that baseline
  until a new version or the human's next approval of the mission.
- evaluate fell back to an older goal nobody works. active_goal is now only ever the newest goal.
- The record is written before the status, and a bare `retired` status is repaired, never trusted
  (tick reads the record).
- Malformed run-log lines and sprints.json rows no longer crash tick or retire.
- Tick now asks the whole rule (derived content, status, met), not part of it.

Tests pin each rule (goal check 13, tick checks 28 and 33, roles). M9.54 records the remaining
one-goal-in-flight gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
…lift the hold (M9.50 review 2)

A second adversarial review found these, each reproduced:
- `goal next` handed out only the first intent per gap, so a retry intent that `goal retire` and tick
  count could never be sprinted. It now queues every intent of the goal.
- The retirement's `open` listed every gap of the eval, a regression elsewhere included. It now lists
  the goal's own gaps, and a regression is never held.
- A BLOCKED with every gap held had no lift. It now carries grant GOALS on the newest retired goal, so
  the human's `tick --force --by` and `lift` line free those gaps (`goal.retired_gaps` reads the lift).
- Two goals derived in the same second tied in `evaluate.active_goal`. The newer id now wins.

Tests: mission-goal 14 checks, mission-tick 33 checks (status-only retirement, held BLOCKED with
grant GOALS, `goal derive` refusing, the lift giving DERIVE_GOAL). Removing the lift clause fails
check 33.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
…goal id (M9.50 review 3)

A third adversarial review found these, each reproduced:
- `goal next` skipped a goal intent whose file was gone, unreadable or another intent's copy, while
  tick and `goal retire` still counted it, so the loop repeated SPRINT until BLOCKED. Every goal
  intent now goes to `intent check`, and `goal next` names the one that fails.
- A GOALS lift took its occurrence from the goal file, so after the retired goal's file was edited
  the lift recorded 0 and freed nothing while `tick --force` said it lifted. The occurrence now comes
  from the goal id the block names, as the hold reads the record.
- The spoon-fed `derive --pick` refusal offered `tick --force` when it only works once every gap is
  held. The message and docs now say so.

Tests: mission-goal check 13 (gone, unreadable and copied retry intent), mission-tick check 33 (the
retired goal's file edited before the lift).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
# Conflicts:
#	docs/ROADMAP.md
#	docs/TRACKING.md
@adeelahmad
adeelahmad marked this pull request as ready for review October 20, 2026 16:09
@adeelahmad
adeelahmad merged commit d157b41 into master Oct 20, 2026
5 checks passed
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
…follow-up

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBVFNrEg85XAnSfHQidCXN
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
… into the artifact gate fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
…oop stop) into the M9.31 branch

Conflicts: both sides' plugin constants and helpers kept (the door gate's
timeout and read-only set beside master's NEEDS-HUMAN release), the dsh
test's question helper renamed (master added a child named `ask`). This
branch's open items move to M9.56–M9.65: master took M9.55 and #114 claims
M9.48.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
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.

2 participants

0