Skip to content

Commit f2891e0

Browse files
committed
fix(todos): teach the priority marker in the machine's own authoring hint
The planner offers the current priority band, so an Agent Todo whose text has no `[P0]/[P1]/[P2]` marker is written to the active state and then cannot be bound with `quota should-run --todo-id` in the same Turn. Reproduced on the 2026-09-17 and 2026-09-20 wakes of goal `loopx-meta`: the rows authored from the hint's own `agent_todo_command_template` answered `state=quota_action_selection_rejected reason=candidate_not_currently_eligible`, and seven already-open rows in that lane had to be repaired by hand before the planner could offer them. The hint is the machine-readable guidance every heartbeat reads, so its agent template now prints the marker: `--text '[P1] <agent action>'`. The rule sentence is deliberately unchanged — agent-facing CLI output has a measured per-row budget (the first draft added a second field and failed `cli-output-budget-regression-smoke` with +271 chars against a 128-char allowance), and a template the agent copies teaches the marker without growing every quota should-run row. Validation: new tests/control_plane/test_todo_write_hint_contract.py (template carries the marker; rule and user templates unchanged), `project-agent-adoption-smoke` ok with the added assertion, `cli-output-budget-regression-smoke` ok, 65 quota settlement CLI tests passed, ruff and mypy clean, and `loopx canary premerge --from-git-diff` passed with 0 failures and 0 manual holds. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
1 parent bfbb5ac commit f2891e0

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

‎examples/project/project-agent-adoption-smoke.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ def main() -> int:
256256
assert approved_quota["todo_write_hint"]["agent_todo_command_template"].startswith(
257257
f"loopx todo add --goal-id {GOAL_ID} --role agent "
258258
), approved_quota
259+
# The planner offers the current priority band, so the machine's own
260+
# authoring template has to teach the marker that makes a row bindable.
261+
assert "[P1]" in approved_quota["todo_write_hint"]["agent_todo_command_template"], approved_quota
262+
assert "[P1]" in approved_quota["todo_write_hint"]["agent_todo_command_template"], approved_quota
259263

260264
packet = run_cli(root, registry_path, "review-packet", "--goal-id", GOAL_ID, "--scan-root", str(project))
261265
assert packet["ok"] is True, packet

‎loopx/control_plane/todos/write_hint.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55

66
def build_todo_write_hint(goal_id: str) -> dict[str, str]:
77
return {
8+
# Agent Todos are offered to the planner by the current priority band, so
9+
# a row authored without a marker is written but cannot be bound by the
10+
# same Turn. Following this hint on 2026-09-17 and 2026-09-20 produced
11+
# open, claimed rows that `quota should-run --todo-id` then rejected with
12+
# `candidate_not_currently_eligible`. Only the template teaches the
13+
# marker: agent-facing CLI output is budgeted per row, and this hint has
14+
# no room to grow.
815
"rule": "Write user/owner actions to User Todo, not Next Action/docs/chat.",
916
"user_gate_command_template": (
1017
f"loopx todo add --goal-id {goal_id} --role user "
@@ -16,7 +23,7 @@ def build_todo_write_hint(goal_id: str) -> dict[str, str]:
1623
"--task-class user_action --bound-agent <id> --text '<action>'"
1724
),
1825
"agent_todo_command_template": (
19-
f"loopx todo add --goal-id {goal_id} --role agent --text '<agent action>'"
26+
f"loopx todo add --goal-id {goal_id} --role agent --text '[P1] <agent action>'"
2027
),
2128
"section": "User Todo / Owner Review Reading Queue",
2229
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""The machine's own Todo-authoring hint must teach the bindable row shape."""
2+
3+
from __future__ import annotations
4+
5+
from loopx.control_plane.todos.write_hint import build_todo_write_hint
6+
7+
8+
def test_agent_template_carries_the_priority_marker_the_planner_requires() -> None:
9+
"""A row authored from this template must be bindable in the same Turn.
10+
11+
The planner offers the current priority band, so an Agent Todo whose text
12+
has no ``[P0]/[P1]/[P2]`` marker is written to the active state and then
13+
cannot be bound with ``quota should-run --todo-id``. Reproduced on the
14+
2026-09-17 and 2026-09-20 wakes; the hint has to name the marker instead of
15+
letting an agent copy a template that produces an unselectable row.
16+
"""
17+
18+
hint = build_todo_write_hint("fixture-goal")
19+
template = hint["agent_todo_command_template"]
20+
assert template.startswith(
21+
"loopx todo add --goal-id fixture-goal --role agent "
22+
), hint
23+
assert "--text '[P1] " in template, hint
24+
# The rule sentence is unchanged: agent-facing CLI output is measured per
25+
# row, so the marker is taught by the template the agent copies.
26+
assert hint["rule"] == (
27+
"Write user/owner actions to User Todo, not Next Action/docs/chat."
28+
), hint
29+
30+
31+
def test_user_templates_are_not_repointed_at_the_agent_band_rule() -> None:
32+
"""User rows keep their own contract; only the Agent template changes.
33+
34+
The marker guidance rides in the existing ``rule`` sentence instead of a new
35+
field: agent-facing CLI output has its own measured budget, and a separate
36+
field doubled the growth of every quota should-run row.
37+
"""
38+
39+
hint = build_todo_write_hint("fixture-goal")
40+
assert hint["user_gate_command_template"] == (
41+
"loopx todo add --goal-id fixture-goal --role user "
42+
"--task-class user_gate --blocks-agent <agent-id> "
43+
"--text '<blocking user decision>'"
44+
), hint
45+
assert hint["user_action_command_template"] == (
46+
"loopx todo add --goal-id fixture-goal --role user "
47+
"--task-class user_action --bound-agent <id> --text '<action>'"
48+
), hint
49+
assert hint["section"] == "User Todo / Owner Review Reading Queue", hint
50+
assert hint["rule"] == (
51+
"Write user/owner actions to User Todo, not Next Action/docs/chat."
52+
), hint

0 commit comments

Comments
 (0)