What happens
DEFAULT_PLANNER_SYSTEM_PROMPT (grapharc/planner/proposal.py) tells the model the catalog, the START/END literals, and the structural rules — and its own comments explain why: "stating the rule up front is cheaper than three wasted rounds." But the one rule models actually trip over is not stated: the edge policy. The prompt never says which transitions are denied.
Observed with a real model (qwen3:8b via ollama, grapharc plan against the incident registry): the goal said "find the cause and propose a fix", the generated policy denied *->deploy, and the model proposed an edge into deploy in all three rounds — round 1: edge_denied, round 2: edge_denied, cycle, round 3: edge_denied — until the loop stopped with admission_refused. The refusal reason came back each round and the model still could not infer "no edge may enter deploy, ever" from edge_denied alone.
Why it matters
Every wasted round is real tokens and real wall clock (three rounds ≈ 3.5 minutes of local inference in the observed run), and a run that dies admission_refused after max-rounds looks like a model failure when it is actually an information failure. The repo's own design note says enforcement must not live in prompt text — correct, and unchanged here: this is about disclosure, not enforcement. The gate still decides.
What to consider
- Render the compiled
EdgePolicy into the catalog section of the prompt: one line per deny rule, e.g. edges into 'deploy' are denied by policy — do not propose them. Allow rules and the default need no mention.
- Alternatively (or additionally), enrich the refusal feedback: instead of the bare check name
edge_denied, include the rule's reason string, which the policy already carries ("Deploy changes are dangerous..." in the generated document).
- The admission checker stays byte-identical: a model that ignores the disclosure is still refused.
Acceptance criteria
A scripted-model test in which round 1 would previously propose a denied edge now shows the deny rule present in the planner's prompt text, and the existing enforcement tests pass unchanged.
What happens
DEFAULT_PLANNER_SYSTEM_PROMPT(grapharc/planner/proposal.py) tells the model the catalog, the START/END literals, and the structural rules — and its own comments explain why: "stating the rule up front is cheaper than three wasted rounds." But the one rule models actually trip over is not stated: the edge policy. The prompt never says which transitions are denied.Observed with a real model (qwen3:8b via ollama,
grapharc planagainst the incident registry): the goal said "find the cause and propose a fix", the generated policy denied*->deploy, and the model proposed an edge intodeployin all three rounds —round 1: edge_denied,round 2: edge_denied, cycle,round 3: edge_denied— until the loop stopped withadmission_refused. The refusal reason came back each round and the model still could not infer "no edge may enter deploy, ever" fromedge_deniedalone.Why it matters
Every wasted round is real tokens and real wall clock (three rounds ≈ 3.5 minutes of local inference in the observed run), and a run that dies
admission_refusedafter max-rounds looks like a model failure when it is actually an information failure. The repo's own design note says enforcement must not live in prompt text — correct, and unchanged here: this is about disclosure, not enforcement. The gate still decides.What to consider
EdgePolicyinto the catalog section of the prompt: one line per deny rule, e.g.edges into 'deploy' are denied by policy — do not propose them. Allow rules and the default need no mention.edge_denied, include the rule'sreasonstring, which the policy already carries ("Deploy changes are dangerous..."in the generated document).Acceptance criteria
A scripted-model test in which round 1 would previously propose a denied edge now shows the deny rule present in the planner's prompt text, and the existing enforcement tests pass unchanged.