8000 The mission loop hands out a goal's commands only when the gate takes them (M9.53) by adeelahmad · Pull Request #111 · adeelahmad/aloop · GitHub
[go: up one dir, main page]

Skip to content

The mission loop hands out a goal's commands only when the gate takes them (M9.53) - #111

Draft
adeelahmad wants to merge 8 commits into
masterfrom
claude/m9-loop-skill-zkmelo
Draft

adeelahmad wants to merge 8 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: mission tick handed out PLAN_INTENTS, SPRINT or a met goal's close without asking whether the command would accept the goal. A goal the mission had outgrown (for example after a raised budget cap) got PLAN_INTENTS while goal intents refused it. After 3 handouts the repeat guard said BLOCKED with no cause named. A broken next intent, or a stray status on a met goal, looped the same way on goal next and goal close.

After: tick asks the gate first. If the gate refuses, tick says BLOCKED with the gate's own reason, for example "the mission changed since the goal was derived" or "the human answers it", so the human sees why at once. The check records nothing, so tick --peek and due still write no events. goal close now checks the ledger and the goal's content before it trusts a goal.met record, writes that record before the status, and repairs a bare met status left by a crash.

How:

  • goal.py:
    • next_intent is all of goal next's rule: goal check, a goal.met record, the status, a next intent at all, and that intent's intent check. cmd_next prints from it, so the command and the loop share one rule.
    • The intent check runs in process through intent.verdict (intent check without its intent.normalized). Only goal next itself, when it hands an intent out, runs intent check and emits the event. A check that crashes on a contract of the wrong shape is a refusal that names it.
    • With nothing to hand out, goal next says why: no intent of the goal is recorded, or every intent has a plan.
    • goal next refuses a goal that has a goal.met record.
    • close_problems is cmd_close's rule, extracted so the loop and the command share it.
    • cmd_close checks the ledger, then the content, then a matching goal.met record (repairing a bare status), then records goal.met and sets the status.
  • intent: verdict is intent check's rule without its event; cmd_check adds the event.
  • tick.py:
    • Before PLAN_INTENTS, goal_refused runs goal.check, the check goal intents runs. Before SPRINT it runs goal.next_intent.
    • Before the close, tick runs goal.close_problems.
    • Each refusal is BLOCKED, keyed on its problems, so a new problem is told again. goal next exiting 1 (nothing to hand out) counts as a refusal.
    • The BLOCKED row in the docstring and in SKILL.md names this case.
  • The test's goals are real.
    • Before, the tick test's goals were synthetic and could not pass goal check.
    • Project.at_clock runs aloop mission in-process with the clock frozen at a chosen project hour.
    • Project.goal runs real assess, evaluate and goal derive at the hours the sections used.
    • Every goal that reaches a goal command is real. The synthetic ones left are goals tick stops at earlier (hard lines, retirement).
    • Section 24 (union-merge pick order) now expects BLOCKED for the edited pick. The gate refuses an edited goal, and the result still tells the two fold orders apart.
    • Sections 1, 6, 12, 13, 15 and 18 hand off real intents with goal intents and sprint each one, since an active goal with no recorded intent is now BLOCKED.

Calls I made:

  • BLOCKED, not DERIVE_GOAL, for a goal the mission outgrew. A second goal in flight is M9.55's to rule out, and M9.43 decides which mission changes void a goal.
  • BLOCKED, not PLAN_INTENTS, for an active goal with no recorded intent. That happens on a fresh checkout (the run log is gone) or after a hand-set status, and re-running goal intents there would duplicate intents whose files still exist. Sprint history as run data is M9.26.
  • New ROADMAP item M9.70: ids freeze links a plan to the newest intent.normalized anywhere, so a human's intake between goal next and the freeze can link the wrong intent.

Review rounds

Round 1 ran on 0da88a6. Its 6 confirmed findings are fixed in 4cabc54, each with a test:

  • SPRINT checked only goal check, so a goal goal next refused (an unanswered ask in the next intent, a gone intent file, a status no record says) got SPRINT until BLOCKED.
  • goal next handed out intents for a goal with a goal.met record.
  • goal close trusted a goal.met record before checking the goal's content.
  • The close BLOCKED was keyed on the eval alone, so a second reason for the same eval was never told.
  • The docs said every goal in the tick suite was real.
  • Refuted (pre-existing, not this PR's): the planned sets of goal next and tick using different events, the M9.43 remedy for an outgrown goal, and an unused spoon-fed path in Project.goal.

Round 2 ran on 4cabc54 (3 lenses, each finding re-verified). It confirmed 5 findings, all fixed in 368d912 with tests:

  • tick's new goal next check ran intent check, which emits intent.normalized. So tick --peek and due wrote an intake event, and ids freeze could link a human's sprint to the goal's intent.
  • goal next exiting 1 still got SPRINT, then the causeless BLOCKED.
  • A crash of intent check exited 1, which read as "every intent has a plan".
  • goal close read a ledger that lost goal.derived as a goal never derived.
  • Check 8 did not cover a met goal with an unplanned intent.
  • Refuted: the close pin's test being only a negative check. The pin is keyed on the reason.

Evidence

  • scripts/test/mission-tick.test.py (34 checks), check 34:
    • A raised budget voids the goal. goal intents and goal next refuse it, and tick says BLOCKED with their reason for both PLAN_INTENTS and SPRINT.
    • A stray retired status, an unanswered ask in the next intent and a gone intent file each make goal next refuse, and tick says BLOCKED with that reason.
    • A stray retired status on a met goal is BLOCKED with goal close's reason, keyed on it.
    • A bare met status gets the close, and goal close records it.
    • An active goal with no recorded intent is BLOCKED naming that. A recorded tick, a --peek and due at SPRINT emit no intent.normalized. A crashing check is BLOCKED with "the planner fixes it".
  • scripts/test/mission-goal.test.py (14 checks), check 8: goal next refuses a goal with a goal.met record even with an unplanned intent left, a second close repairs the status, an edited goal's close is refused despite its record, and a ledger that lost goal.derived is named as the ledger's problem.
  • RED:
    • On master's tick.py and goal.py, check 34 fails twice: "expected BLOCKED, got PLAN_INTENTS", and in the close part "expected BLOCKED, got EVALUATE".
    • On 0da88a6's code, the round-1 tests fail: check 34 with "expected BLOCKED, got SPRINT", and the goal test because goal next hands out an intent for a met goal.
    • On 2c11541's code, the round-2 tests fail: check 34 at the no-intent case, and the goal test at the ledger case. Reverting each round-2 fix alone (exit 1 as acceptance, the emitting check, the crash catch, the goal.met check moved last) turns its test red.
  • CI on 368d912 failed on the tick suite's old fixtures (an active goal with no intents), on Linux and macOS. a9f6b13 fixes them.
  • On a9f6b13 (master afeeb59 merged in): mission-tick 34 checks, mission-goal 14, mission-roles 34, intent-contract 15, make pycheck, scripts/check-tracking.py (98 requirements) and make test-hosts (46 checks; the live dsh run skips without the binary) all pass locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM

Before PLAN_INTENTS and SPRINT, `mission tick` runs `goal check`, the check `goal intents` and
`goal next` run; before a met goal's close it runs `goal.close_problems`, now the rule `goal close`
runs. A refusal is BLOCKED naming the gate's reason (keyed on it), where the loop used to hand out
the same command 3 times and then block with no cause named.

`goal close` records goal.met before it sets the status, and repairs a bare `met` status.

The tick test's goals are now real: `Project.goal` runs `assess`, `evaluate` and `goal derive`
in-process on the project's clock (`Project.at_clock`), so every section's goal passes `goal check`.
New check 34: a raised budget voids a goal (BLOCKED, as `goal intents`/`next` refuse), a stray
status blocks the close with `goal close`'s reason, and a bare met status closes. It fails on
master's code ("expected BLOCKED, got PLAN_INTENTS" and "got EVALUATE").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
… first

- goal.next_intent is `goal next`'s whole rule (check, a goal.met record,
  the status, the next intent's `intent check`); tick's SPRINT asks it, so
  a goal next refuses is BLOCKED with its reason, not SPRINT 3 times.
- `goal next` refuses a goal with a goal.met record.
- `goal close` checks the goal's content before it trusts a goal.met
  record, then repairs a bare status.
- The close BLOCKED is keyed on its problems, so a new reason is told.
- Tick check 13 sprints real intents; docs say which goals are real.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
…fusal

- intent: `verdict` is `intent check` without its event; `cmd_check` adds
  intent.normalized. goal.next_intent asks it in process, so `tick --peek`
  and `due` no longer emit the intake event `ids freeze` links plans to.
- A crash of the check (a contract of the wrong shape) is its refusal,
  never "every intent has a plan".
- `goal next` with nothing to hand out names why (no intent recorded, or
  every intent planned), and tick says BLOCKED with it, not SPRINT x3.
- `goal close` checks the ledger before the goal's content.
- Tests: section 13 sprints real intents from the start; section 33 adds
  the no-intent, no-emit and crash cases; goal check 8 covers a met goal
  with an unplanned intent and a ledger that lost goal.derived.
- ROADMAP M9.70: `ids freeze` should link the handed-off intent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
An active goal with no recorded intent now gets BLOCKED (`goal next` has
nothing to hand out), so the lifecycle, spoon-fed, cadence and same-second
sections run `goal intents` and sprint each intent, as the planner does.

Co-Authored-By: Claude Opus 5.5 <noreply@a
8000
nthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
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
…t once, ids M9.80-87

An adversarial review of the live-run fixes confirmed 9 findings:
- seed_test_kept accepted an added def that rebinds the seed's test
  name (replacing it), an import or class shadowing TodoList, and code
  run at import through a decorator, default, annotation, base or class
  body. Added statements must now rebind none of the seed's names and
  run no code at import (pytest's own decorators with plain arguments).
- No test pinned check_product's wiring: it now has one on the stub
  run's repo (an append passes, a redefinition fails).
- journeys new returned on a runner problem before the other problems
  and the "nothing written" footer; it now names every problem at once.
  Its usage offers --from as the one form.
- ROADMAP ids M9.63-70 clash with #105 and #111: now M9.80-87.
- Docs: 39 checks, the M9.87 block count, run 1's three stop causes,
  and a test that pins the rival constraint's new wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018RxETAY6S1gcmdctBK8a8Z
…ntent

Round 3 of the M9.53 review: with real intents in sections 13 and 18,
nothing tested goal_sprints' no-intent branch (a SPRINT an older tick
handed out). A legacy case now does: an older sprint is not the goal's,
one planned in the second of the handout is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM

This branch has not been deployed

No deployments
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