fix: feat: planner v2 — graph-driven formula, 648→200 lines (#667)

Rewrite run-planner.toml from 648 lines (6 steps) to 243 lines (3 steps):
- preflight → triage-and-plan → journal-and-commit
- Graph report (build-graph.py) replaces manual repo scanning
- tea CLI helpers replace inline curl commands
- One issue body template instead of three copies
- Graph bottlenecks + thin objectives replace hardcoded constraint patterns

Update planner-run.sh to generate and inject graph report (same pattern
as predictor-run.sh).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-25 13:47:48 +00:00
parent eaeac6da0b
commit 1e8e4e5112
2 changed files with 152 additions and 543 deletions

View file

@ -1,35 +1,33 @@
# formulas/run-planner.toml — Strategic planning formula (v3: Prerequisite Tree)
# formulas/run-planner.toml — Strategic planning formula (v4: graph-driven)
#
# Executed directly by planner-run.sh via cron — no action issues.
# planner-run.sh creates a tmux session with Claude (opus) and injects
# this formula as context. Claude executes all steps autonomously.
# this formula as context, plus the graph report from build-graph.py.
#
# Steps: preflight → prediction-triage → update-prerequisite-tree
# → file-at-constraints → journal-and-memory → commit-and-pr
# Steps: preflight → triage-and-plan → journal-and-commit
#
# Core change from v2: replaces gap-analysis-and-spray with a constraint-
# focused executive using a Prerequisite Tree (Theory of Constraints).
# Issues are only filed at the top 5 unresolved constraints — everything
# beyond the bottleneck exists in the tree but NOT as issues.
# v4 changes from v3:
# - Graph report (orphans, cycles, thin objectives, bottlenecks) replaces
# manual repo scanning and hardcoded constraint patterns.
# - tea CLI helpers replace inline curl commands.
# - 3 steps instead of 6.
#
# AGENTS.md maintenance is handled by the gardener (#246).
# All git writes (tree, journal, memory) happen in one commit at the end.
name = "run-planner"
description = "Planner v3: prerequisite tree + resource-aware constraint executive"
version = 3
description = "Planner v4: graph-driven planning with tea helpers"
version = 4
model = "opus"
[context]
files = ["VISION.md", "AGENTS.md", "RESOURCES.md", "planner/prerequisite-tree.md"]
# Recent planner/journal/*.md files are loaded by planner-run.sh (last 5 entries)
# Recent planner/journal/*.md files + graph report loaded by planner-run.sh
[[steps]]
id = "preflight"
title = "Pull latest code and load planner memory"
title = "Pull latest code, run graph, load context"
description = """
Set up the working environment for this planning run.
1. Change to the project repository:
cd "$PROJECT_REPO_ROOT"
@ -44,605 +42,202 @@ Set up the working environment for this planning run.
4. Read the planner memory file at: $PROJECT_REPO_ROOT/planner/MEMORY.md
If it does not exist, this is the first planning run.
Keep this memory context in mind for all subsequent steps.
5. Read the prerequisite tree at: $PROJECT_REPO_ROOT/planner/prerequisite-tree.md
If it does not exist, create an initial tree from VISION.md in the
update-prerequisite-tree step.
If it does not exist, create an initial tree from VISION.md in the next step.
6. Read the graph report injected into the prompt (## Structural analysis).
This JSON contains: orphans, cycles, disconnected clusters, thin_objectives,
bottlenecks (by betweenness centrality). Use it instead of manual file scanning.
"""
[[steps]]
id = "prediction-triage"
title = "Triage prediction/unreviewed issues"
id = "triage-and-plan"
title = "Triage predictions, update tree, file at constraints"
description = """
Triage prediction issues filed by the predictor (goblin).
Evidence from the preflight step informs whether each prediction is valid
(e.g. "red-team stale since March 12" is confirmed by evidence/ timestamps).
One unified step replacing the former prediction-triage, update-prerequisite-tree,
and file-at-constraints steps.
### Part A: Triage predictions
1. Fetch unreviewed predictions:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues?state=open&type=issues&labels=prediction%2Funreviewed&limit=50"
If none, skip to Part B.
If there are none, note that and skip to step 3b (label resolution
is still required the file-at-constraints step needs label IDs).
2. Read available formulas:
- Factory formulas: $FACTORY_ROOT/formulas/*.toml
- Project formulas: $PROJECT_REPO_ROOT/formulas/*.toml
Project formulas are dispatched via action issues on the project repo.
3. Fetch all open issues to check for overlap:
2. Fetch all open issues (for overlap check):
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues?state=open&type=issues&limit=50"
3b. Resolve label IDs needed for triage AND filing (fetch via $FORGE_API/labels).
ALWAYS execute this step, even if there are no predictions to triage
the file-at-constraints step depends on these IDs:
- <unreviewed_label_id> prediction/unreviewed
- <prediction_backlog_label_id> prediction/backlog
- <actioned_label_id> prediction/actioned (create if missing,
color #c2e0c6, description "Prediction triaged by planner")
- <backlog_label_id> backlog
- <action_label_id> action
- <priority_label_id> priority (create if missing,
color #d4c5f9, description "Queue priority — picked before plain backlog")
These are DISTINCT labels do not reuse IDs across them.
3. Read available formulas: $FACTORY_ROOT/formulas/*.toml and $PROJECT_REPO_ROOT/formulas/*.toml
4. For each prediction, read the title and body. Choose one action:
4. For each prediction, choose one action:
- PROMOTE_ACTION: maps to a formula -> create action issue, close prediction
- PROMOTE_BACKLOG: warrants dev work -> create backlog issue, close prediction
- WATCH: not urgent -> comment why, relabel to prediction/backlog, keep open
- DISMISS: noise or covered -> comment reasoning, close prediction
- PROMOTE_ACTION: maps to an available formula create an action issue
with YAML front matter referencing the formula name and vars.
Relabel prediction/unreviewed prediction/actioned, then close
with comment "Actioned as #NNN — <reasoning>".
5. Execute triage using tea helpers:
- Create issues: tea_file_issue "<title>" "<body>" "backlog" (or "action")
- Relabel: tea_relabel <num> "prediction/actioned" (or "prediction/backlog")
- Comment: tea_comment <num> "<reasoning>"
- Close: tea_close <num>
- PROMOTE_BACKLOG: warrants dev work create a backlog issue.
Relabel prediction/unreviewed prediction/actioned, then close
with comment "Actioned as #NNN — <reasoning>".
Issue body template (gardener quality gate requires these sections):
## Problem
<what the prediction identified>
- WATCH: not urgent but worth tracking post a comment explaining
why it is not urgent, then relabel from prediction/unreviewed to
prediction/backlog. Do NOT close.
## Proposed solution
<approach>
- DISMISS: noise, already covered by an open issue, or not actionable
relabel prediction/unreviewed prediction/actioned, post a comment
with explicit reasoning, then close the prediction.
## Affected files
- <file1>
## Acceptance criteria
- [ ] <criterion>
- [ ] CI green
Every decision MUST include reasoning in a comment on the prediction issue.
5. Executing triage decisions via API:
For PROMOTE_ACTION / PROMOTE_BACKLOG:
a. Create the new issue with the 'action' or 'backlog' label.
IMPORTANT the issue body MUST include these sections so the
gardener quality gate does not strip the backlog label:
- "## Acceptance criteria" with at least one checkbox (- [ ] ...)
- "## Affected files" with at least one file path
Example body structure:
## Problem\n<what the prediction identified>\n\n## Proposed solution\n<approach>\n\n## Affected files\n- <file1>\n- <file2>\n\n## Acceptance criteria\n- [ ] <criterion 1>\n- [ ] CI green
Create the issue:
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" "$FORGE_API/issues" \
-d '{"title":"...","body":"...","labels":[<label_id>]}'
Extract the issue number from the response (jq -r '.number').
a2. Verify the label was applied (the forge may silently drop labels
on creation). Re-apply via a separate POST if missing:
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<new_issue_num>/labels" \
-d '{"labels":[<label_id>]}'
b. Comment on the prediction with "Actioned as #NNN — <reasoning>":
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<pred_num>/comments" \
-d '{"body":"Actioned as #NNN — <reasoning>"}'
c. Relabel: remove prediction/unreviewed, add prediction/actioned:
curl -sf -X DELETE -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues/<pred_num>/labels/<unreviewed_label_id>"
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<pred_num>/labels" \
-d '{"labels":[<actioned_label_id>]}'
d. Close the prediction:
curl -sf -X PATCH -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<pred_num>" \
-d '{"state":"closed"}'
For WATCH:
a. Comment with reasoning why not urgent:
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<pred_num>/comments" \
-d '{"body":"Watching — <reasoning>"}'
b. Replace prediction/unreviewed label with prediction/backlog:
curl -sf -X DELETE -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues/<pred_num>/labels/<unreviewed_label_id>"
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<pred_num>/labels" \
-d '{"labels":[<prediction_backlog_label_id>]}'
For DISMISS:
a. Comment with explicit reasoning:
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<pred_num>/comments" \
-d '{"body":"Dismissed — <reasoning>"}'
b. Relabel: remove prediction/unreviewed, add prediction/actioned:
curl -sf -X DELETE -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues/<pred_num>/labels/<unreviewed_label_id>"
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<pred_num>/labels" \
-d '{"labels":[<actioned_label_id>]}'
c. Close the prediction:
curl -sf -X PATCH -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<pred_num>" \
-d '{"state":"closed"}'
6. Track promoted predictions they are added to the prerequisite tree
in the next step if they represent real prerequisites.
7. Validation: if you reference a formula, verify it exists on disk.
Fall back to a freeform backlog issue for unknown formulas.
Be decisive the predictor intentionally over-signals; your job is to filter.
CRITICAL: If this step fails, log the failure and move on to update-prerequisite-tree.
"""
needs = ["preflight"]
[[steps]]
id = "update-prerequisite-tree"
title = "Scan repo state and update the prerequisite tree"
description = """
This is the constraint discovery step. Read the current state, then update
the prerequisite tree to reflect reality.
### Part B: Update prerequisite tree
Read these inputs:
- VISION.md where we want to be (objectives come from milestones)
- planner/prerequisite-tree.md current tree (loaded in preflight)
- RESOURCES.md available agents, boxes, assets, formulas
- $FACTORY_ROOT/formulas/*.toml factory formulas
- $PROJECT_REPO_ROOT/formulas/*.toml project-specific formulas
- Open issues (fetched via API, or reuse from prediction-triage)
- Closed issues (fetch recently closed to detect resolved prerequisites):
- VISION.md, RESOURCES.md, planner memory (from preflight)
- Graph report: orphans, cycles, thin_objectives, bottlenecks, disconnected
- Open issues (from Part A or fresh fetch)
- Recently closed issues:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues?state=closed&type=issues&limit=50&sort=updated&direction=desc"
- Planner memory (loaded in preflight)
- Promoted predictions from prediction-triage (add as prerequisites if relevant)
### Comment scanning for bounce/stuck detection
For each issue referenced in the prerequisite tree (by #number), fetch its
recent comments to detect signals that the issue is stuck or bouncing:
Update the tree:
1. Mark resolved prerequisites ([x]) check if issue closed or capability present
2. Recalculate objective status (READY/BLOCKED/DONE)
3. Add new prerequisites discovered from graph report
4. Add new objectives from VISION.md not yet in tree
5. Check vault state: vault/pending/*.md (blocked-on-vault), vault/fired/*.md (resolved?)
6. Check RESOURCES.md for newly available capabilities
Bounce/stuck detection for issues in the tree, fetch recent comments:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues/<number>/comments?limit=10"
Signals: BOUNCED (too_large, underspecified), ESCALATED (needs human decision),
LABEL_CHURN (3+ relabels between backlog/underspecified).
Track as stuck_issues[] for constraint filing below.
Scan each comment body for these signals:
- **BOUNCED**: body contains "too large for single session", "too_large",
"underspecified", or "needs splitting" (case-insensitive). This means
the dev-agent refused the issue it needs breakdown before retry.
- **ESCALATED**: body contains "escalating for human decision", "needs
human decision", or "escalate" from a non-human author. The issue
needs steering input.
- **UNBLOCKED**: body contains "dependency .* is now closed" or
"unblocked". The issue may be ready to work.
- **LABEL_CHURN**: the issue has been relabeled between backlog and
underspecified (or blocked) 3+ times. Check via label change events
or multiple bounce comments. This indicates a ping-pong loop.
Track detected signals in a list: `stuck_issues[]` where each entry is:
{ issue: <number>, signal: BOUNCED|ESCALATED|LABEL_CHURN, count: <N>,
reason: "<summary from comments>" }
These signals feed into the file-at-constraints step to prevent the
planner from re-promoting stuck issues and to dispatch formula-based
breakdown instead.
Update the tree by applying these operations:
1. **Mark resolved prerequisites**: For each prerequisite in the tree,
check if the corresponding issue is closed or the capability is now
present in the repo. Mark resolved items with [x].
2. **Update objective status**: Recalculate each objective's status:
- All prerequisites resolved Status: READY (or DONE if the objective
itself is closed/implemented)
- Some unresolved Status: BLOCKED N prerequisites unresolved
- Depends on blocked objectives Status: BLOCKED prerequisite chain
3. **Discover new prerequisites**: As you scan repo state, you may find
new prerequisites not yet in the tree. Add them. The tree grows
organically this is expected and desirable.
4. **Add new objectives**: If VISION.md has objectives not yet in the
tree, add them with their prerequisite chains.
5. **Propose new capabilities**: If you identify a capability the factory
needs (e.g., "marketing formula, runs weekly"), add it to the tree as
a proposed prerequisite. Anything with recurring cost (new accounts,
new infra, new cron entries, new formulas) should be procured through
the vault see the file-at-constraints step for how to file requests.
6. **Check vault state**: Scan vault directories for procurement status:
- `$PROJECT_REPO_ROOT/vault/pending/*.md` requests awaiting human action.
Any prerequisite that depends on a pending procurement request should
be marked: `[ ] <name> blocked-on-vault (vault/pending/<id>.md)`
- `$PROJECT_REPO_ROOT/vault/approved/*.md` fulfilled, being processed.
- `$PROJECT_REPO_ROOT/vault/fired/*.md` completed. Check if the resource
now appears in RESOURCES.md and mark the prerequisite resolved.
- Do NOT file issues for objectives blocked on pending vault items.
7. **Re-read RESOURCES.md**: Check for newly available capabilities that
were not present last run. If a new resource appears, mark the
corresponding prerequisite as resolved.
Write the updated tree to: $PROJECT_REPO_ROOT/planner/prerequisite-tree.md
Use this format:
Hold the updated tree in memory written to disk in journal-and-commit.
Tree format:
# Prerequisite Tree
<!-- Last updated: YYYY-MM-DD -->
## Objective: <name> (#issue or description)
- [x] Resolved prerequisite (reference)
- [ ] Unresolved prerequisite (#issue or description)
- [ ] Resource need blocked-on-vault (vault/pending/<id>.md)
Status: READY | BLOCKED <reason> | BLOCKED awaiting vault | DONE
- [ ] Resource need blocked-on-vault (vault/pending/<id>.md)
Status: READY | BLOCKED <reason> | DONE
Keep the tree focused only include objectives from VISION.md milestones
and their genuine prerequisites. Do not inflate the tree with nice-to-haves.
### Part C: File at constraints
IMPORTANT: Do NOT write the tree to disk yet hold it in memory for the
next step. The tree will be written along with the journal in commit-and-pr.
"""
needs = ["prediction-triage"]
From the updated tree + graph bottlenecks, identify the top 5 constraints.
A constraint is an unresolved prerequisite blocking the most downstream objectives.
Graph bottlenecks (high betweenness centrality) and thin objectives inform ranking.
[[steps]]
id = "file-at-constraints"
title = "Identify top 5 constraints and file issues"
description = """
This is the constraint-focused filing step. The key principle from Theory
of Constraints: only work on the bottleneck. Everything else is waste.
Stuck issue handling:
- BOUNCED/LABEL_CHURN: do NOT re-promote. Dispatch groom-backlog formula instead:
tea_file_issue "chore: break down #<N> — bounced <count>x" "<body>" "action"
- ESCALATED: skip, mark in tree as "escalated — awaiting human decision"
From the updated prerequisite tree, identify the top 5 constraints:
Filing gate (for non-stuck constraints):
1. Check if issue already exists (match by #number in tree or title search)
2. If no issue, create one with tea_file_issue using the template above
3. If issue exists and is open, skip no duplicates
A **constraint** is an unresolved prerequisite that blocks the most
downstream objectives. To find them:
1. For each unresolved prerequisite ([ ] item), count how many objectives
it transitively blocks. A prerequisite that blocks objective A, which
in turn blocks objectives B and C, has a blocking score of 3.
2. Rank all unresolved prerequisites by blocking score (descending).
3. Select the top 5. These are the constraints.
When filing issues at constraints, choose the right agent type:
- **backlog issue** (label: backlog): requires code changes dev-agent
- **action issue** (label: action): runs an existing formula action-agent
Prefer action dispatch when:
- A formula exists that would produce data needed to RESOLVE or VALIDATE
a constraint
- Evidence is required before a constraint can be marked done
- A decision is blocked on data that a formula can provide
Action issues count toward the 5-issue constraint budget they are
strategic investments, not maintenance. The planner decides what data
matters based on current constraints, not what formulas exist.
### Stuck issue handling — dispatch to groom-backlog formula
Before filing, cross-reference the top 5 constraints against the
`stuck_issues[]` list from the update-prerequisite-tree step.
If a constraint issue was detected as BOUNCED or LABEL_CHURN:
- Do NOT re-promote it to backlog or add the priority label this
would restart the ping-pong loop.
- Instead, dispatch the groom-backlog formula to break it down.
Create an action issue that invokes groom-backlog with the stuck
issue as target:
Title: "chore: break down #<number> — bounced <count>x, needs splitting"
Body:
---
formula: groom-backlog
vars:
target_issue: <number>
mode: breakdown
reason: "<reason from stuck_issues entry>"
---
## Problem
Issue #<number> has bounced <count> time(s) between backlog and
underspecified. The dev-agent reports it is too large for a single
session. It needs to be broken into dev-agent-sized subtasks.
## Affected files
- formulas/groom-backlog.toml
## Acceptance criteria
- [ ] #<number> is split into implementable sub-issues
- [ ] Sub-issues have acceptance criteria and affected files
- [ ] Original issue updated with links to sub-issues
Label this action issue with the `action` label (not `backlog`).
This counts toward the 5-issue-per-run limit.
If a constraint issue was detected as ESCALATED:
- Do NOT file new work. Add a comment to the issue noting the
escalation was seen, and mark the prerequisite in the tree as:
`[ ] <name> escalated awaiting human decision`
- Do NOT count this against the 5-issue limit.
Filing gate for each constraint (that is NOT stuck):
1. Check if an issue already exists for this constraint (match by issue
number reference in the tree, or search open issues by title).
2. If no issue exists, create one.
IMPORTANT the issue body MUST include these sections so the
gardener quality gate does not strip the backlog label:
- "## Affected files" with at least one file path
- "## Acceptance criteria" with at least one checkbox (- [ ] ...)
Use this body structure:
## Problem\n<what this prerequisite is and which objectives it blocks>\n\n## Proposed solution\n<rough approach>\n\n## Affected files\n- <file1>\n- <file2>\n\n## Acceptance criteria\n- [ ] <criterion derived from the constraint>\n- [ ] CI green\n\n## Dependencies\n- #NNN (if depends on other open issues)
Create the issue:
curl -sf -X POST \
-H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues" \
-d '{"title":"...","body":"...","labels":[<backlog_label_id>]}'
Extract the issue number from the response (jq -r '.number').
2b. Verify the label was applied (the forge may silently drop labels
on creation). Always re-apply via a separate POST to be safe:
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<new_issue_num>/labels" \
-d '{"labels":[<backlog_label_id>]}'
3. If an issue already exists and is open, skip it no duplicate filing.
4. If an issue already exists but is in backlog without proper context,
consider adding a comment noting its constraint status.
### Priority label management
After identifying the top 5 constraints and their issues (existing or newly
filed), synchronize the `priority` label so only the current bottleneck
issues are prioritized. The `backlog` label is NEVER removed `priority`
is purely additive.
5. **Add `priority` to top-5 constraint issues:**
For each of the top 5 constraint issues (whether just filed or already
existing), check if it already has the `priority` label. If not, add it:
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/issues/<issue_number>/labels" \
-d '{"labels":[<priority_label_id>]}'
6. **Remove `priority` from issues no longer in top 5:**
Fetch all open issues that currently have the `priority` label:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues?state=open&labels=priority&type=issues&limit=50"
For each issue in this list that is NOT one of the current top 5
constraint issues, remove the `priority` label (demote back to plain
`backlog`):
Priority label sync:
- Add priority to current top-5 constraint issues (if missing):
tea_relabel <num> "backlog,priority"
- Remove priority from issues no longer in top 5:
curl -sf -X DELETE -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues/<issue_number>/labels/<priority_label_id>"
This keeps the priority set current only the active bottleneck issues
get priority, not stale constraints from previous runs.
"$FORGE_API/issues/<num>/labels/<priority_label_id>"
Vault procurement: if a constraint needs a resource not in RESOURCES.md with
recurring cost, create vault/pending/<resource-id>.md instead of an issue.
Rules:
- **Maximum 5 issues filed per run** only at constraints
- **No issues filed past the bottleneck** items beyond the top 5
constraints exist in the tree but NOT as issues
- **Existing premature issues left as-is** do not close issues filed
by previous planner versions, even if they're past the bottleneck
- Do NOT create issues that overlap with ANY existing open issue
- Only reference formulas that exist in formulas/*.toml
- When deploying/operating, reference the resource alias from RESOURCES.md
- Promoted predictions from triage may become constraints if they block
downstream objectives rank them the same way
- **Do NOT file issues for objectives blocked on pending vault items**
these are waiting for human procurement, not dev work
- Maximum 5 items per run (issues + procurement combined)
- No issues filed past the bottleneck
- Leave existing premature issues as-is
- Only reference formulas that exist on disk
- Do NOT file issues for objectives blocked on pending vault items
- Promoted predictions may become constraints rank them equally
### Filing vault procurement requests
If a constraint requires a resource the factory does not have (check
RESOURCES.md), and that resource has recurring cost (account, infra,
domain, API key, new cron job), file a procurement request instead of
an issue:
1. Check if a request already exists in vault/pending/ or vault/approved/
for this resource (match by filename).
2. If no request exists, create a markdown file at:
$PROJECT_REPO_ROOT/vault/pending/<resource-id>.md
Format:
```
# Procurement Request: <human-readable name>
## What
<description of what's needed>
## Why
<why the factory needs this which objectives it enables>
## Unblocks
<list prerequisite tree objectives this unblocks, with issue numbers>
## Proposed RESOURCES.md Entry
## <resource-id>
- type: <social|compute|asset|communication|ci|source-control>
- capability: <what it can do>
- env: <ENV_VAR_NAME if secrets needed>
```
3. Mark the prerequisite in the tree as blocked-on-vault:
`[ ] <name> blocked-on-vault (vault/pending/<resource-id>.md)`
4. vault-poll.sh will notify the human automatically.
Procurement requests count toward the 5-item-per-run limit (issues +
procurement requests combined).
If all top 5 constraints already have open issues or pending vault
requests, note that the backlog is aligned with the constraint focus.
No new items needed.
CRITICAL: If any part of this step fails, log the failure and continue.
"""
needs = ["update-prerequisite-tree"]
needs = ["preflight"]
[[steps]]
id = "journal-and-memory"
title = "Write prerequisite tree, journal entry, and periodic memory update"
id = "journal-and-commit"
title = "Write tree, journal, optional memory; commit and PR"
description = """
Three outputs from this step tree and journal are ALWAYS written,
memory is PERIODIC.
### 1. Write prerequisite tree
Write to: $PROJECT_REPO_ROOT/planner/prerequisite-tree.md
### 1. Prerequisite tree (always — committed to git)
Write the updated prerequisite tree to:
$PROJECT_REPO_ROOT/planner/prerequisite-tree.md
This is the tree you built in the update-prerequisite-tree step.
Include the "Last updated" comment at the top.
### 2. Journal entry (always — committed to git)
Create a daily journal file at:
$PROJECT_REPO_ROOT/planner/journal/$(date -u +%Y-%m-%d).md
If the file already exists (multiple runs per day), append a new section
with a timestamp header.
### 2. Write journal entry
Create/append to: $PROJECT_REPO_ROOT/planner/journal/$(date -u +%Y-%m-%d).md
Format:
# Planner run — YYYY-MM-DD HH:MM UTC
## Predictions triaged
- #NNN: PROMOTE_ACTION/PROMOTE_BACKLOG/WATCH/DISMISS — reasoning
(or "No unreviewed predictions" if none)
- #NNN: ACTION — reasoning (or "No unreviewed predictions")
## Prerequisite tree updates
- Resolved: <list of newly resolved prerequisites>
- Discovered: <list of newly added prerequisites>
- Proposed: <list of new capabilities proposed>
(or "No tree changes" if none)
- Resolved: <list> - Discovered: <list> - Proposed: <list>
## Top 5 constraints
1. <prerequisite> blocks N objectives issue #NNN (existing|filed|already open)
2. <prerequisite> blocks N objectives issue #NNN
3. <prerequisite> blocks N objectives issue #NNN
1. <prerequisite> blocks N objectives #NNN (existing|filed)
## Stuck issues detected
- #NNN: BOUNCED (Nx) — dispatched groom-backlog as #MMM
- #NNN: ESCALATED — awaiting human decision
- #NNN: LABEL_CHURN (Nx) — dispatched groom-backlog as #MMM
(or "No stuck issues detected" if none)
(or "No stuck issues detected")
## Issues created
- #NNN: title — why (constraint for objectives X, Y)
(or "No new issues — constraints already have open issues" if none)
- #NNN: title — why (or "No new issues")
## Priority label changes
- Added priority: #NNN, #NNN (top 5 constraints)
- Removed priority: #NNN (no longer in top 5)
(or "No priority changes" if the set is unchanged)
- Added/removed priority: #NNN (or "No priority changes")
## Observations
- Key patterns, resource state, metric trends noticed during this run
- Key patterns noticed this run
## Deferred (in tree, not filed)
- Items in the tree beyond the top 5 constraints, and why they're not filed yet
## Deferred
- Items in tree beyond top 5, why not filed
Keep each entry concise 30-50 lines max.
Keep concise 30-50 lines max.
### 3. Memory update (periodic — every 5th run, committed to git)
### 3. Memory update (every 5th run)
Count "# Planner run —" headers across all journal files.
Check "<!-- summarized-through-run: N -->" in MEMORY.md.
If (count - N) >= 5 or MEMORY.md missing, write to:
$PROJECT_REPO_ROOT/planner/MEMORY.md
Include: run counter marker, date, constraint focus, patterns, direction.
Keep under 100 lines. Replace entire file.
Decide whether to update memory:
1. Count the total number of run entries across ALL journal files in
planner/journal/*.md. Each "# Planner run —" header counts as one run.
2. Check the run count noted in MEMORY.md (look for the
"<!-- summarized-through-run: N -->" marker at the top).
If the marker is missing, treat it as 0.
3. If (current_run_count - last_summarized_count) >= 5, OR if MEMORY.md
does not exist, perform the memory update below.
4. Otherwise, skip the memory update MEMORY.md remains read-only context.
When updating memory, write to: $PROJECT_REPO_ROOT/planner/MEMORY.md
(replace the entire file)
Start the file with the run counter marker:
<!-- summarized-through-run: N -->
where N is the current total run count.
Include:
- Date of this summarization
- Current constraint focus (top 5 from this run)
- Distilled patterns and learnings from recent journal entries
- What was observed (resource state, metric trends, project progress)
- Strategic direction and watch list for future runs
Rules:
- Keep under 100 lines total
- Replace the file contents distill from journal, prune stale entries
- Focus on PATTERNS and LEARNINGS, not transient state
- Do NOT include specific issue counts or numbers that will be stale
- Read the recent journal files provided in context for source material
- Most recent entries at top
Format: simple markdown with dated sections.
"""
needs = ["file-at-constraints"]
[[steps]]
id = "commit-and-pr"
title = "One commit with all file changes, push, create PR"
description = """
Collect all file changes from this run into a single commit.
API calls (issue creation, prediction triage) already happened during the
run only file changes (tree, journal, MEMORY.md) need the PR.
1. Check for staged or unstaged changes:
cd "$PROJECT_REPO_ROOT"
git status --porcelain
If there are no file changes, skip this entire step no commit, no PR.
2. If there are changes:
a. Create a branch:
### 4. Commit and PR
If no file changes (git status --porcelain), skip.
Otherwise:
BRANCH="chore/planner-$(date -u +%Y%m%d-%H%M)"
git checkout -B "$BRANCH"
b. Stage prerequisite tree, journal entries, and planner memory:
git add planner/prerequisite-tree.md 2>/dev/null || true
git add planner/journal/ 2>/dev/null || true
git add planner/MEMORY.md 2>/dev/null || true
c. Stage any other tracked files modified during the run:
git add planner/prerequisite-tree.md planner/journal/ planner/MEMORY.md
git add -u
d. Check if there is anything to commit:
git diff --cached --quiet && echo "Nothing staged" && skip
e. Commit:
git diff --cached --quiet && skip
git commit -m "chore: planner run $(date -u +%Y-%m-%d)"
f. Push:
git push -u origin "$BRANCH"
g. Create a PR:
curl -sf -X POST \
-H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/pulls" \
Create PR via forge API:
curl -sf -X POST -H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" "$FORGE_API/pulls" \
-d '{"title":"chore: planner run — prerequisite tree update",
"head":"<branch>","base":"<primary-branch>",
"body":"Automated planner run — prerequisite tree update and journal entry."}'
h. Return to primary branch:
git checkout "$PRIMARY_BRANCH"
3. If the PR creation fails, log and continue the journal is committed locally.
"""
needs = ["journal-and-memory"]
needs = ["triage-and-plan"]

View file

@ -48,6 +48,19 @@ log "--- Planner run start ---"
load_formula "$FACTORY_ROOT/formulas/run-planner.toml"
build_context_block VISION.md AGENTS.md RESOURCES.md planner/prerequisite-tree.md
# ── Build structural analysis graph ──────────────────────────────────────
GRAPH_REPORT="/tmp/${PROJECT_NAME}-graph-report.json"
GRAPH_SECTION=""
if python3 "$FACTORY_ROOT/lib/build-graph.py" \
--project-root "$PROJECT_REPO_ROOT" \
--output "$GRAPH_REPORT" 2>>"$LOG_FILE"; then
GRAPH_SECTION=$(printf '\n## Structural analysis\n```json\n%s\n```\n' \
"$(cat "$GRAPH_REPORT")")
log "graph report generated: $(jq -r '.stats | "\(.nodes) nodes, \(.edges) edges"' "$GRAPH_REPORT")"
else
log "WARN: build-graph.py failed — continuing without structural analysis"
fi
# ── Read planner memory ─────────────────────────────────────────────────
MEMORY_BLOCK=""
MEMORY_FILE="$PROJECT_REPO_ROOT/planner/MEMORY.md"
@ -93,6 +106,7 @@ PROMPT="You are the strategic planner for ${FORGE_REPO}. Work through the formul
## Project context
${CONTEXT_BLOCK}${MEMORY_BLOCK}${JOURNAL_BLOCK}
${GRAPH_SECTION}
${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT}
}
## Formula