diff --git a/.env.example b/.env.example index 0062b9e..6124671 100644 --- a/.env.example +++ b/.env.example @@ -26,7 +26,8 @@ FORGE_GARDENER_TOKEN= # [SECRET] gardener-bot API token FORGE_VAULT_TOKEN= # [SECRET] vault-bot API token FORGE_SUPERVISOR_TOKEN= # [SECRET] supervisor-bot API token FORGE_PREDICTOR_TOKEN= # [SECRET] predictor-bot API token -FORGE_BOT_USERNAMES=dev-bot,review-bot,planner-bot,gardener-bot,vault-bot,supervisor-bot,predictor-bot +FORGE_ARCHITECT_TOKEN= # [SECRET] architect-bot API token +FORGE_BOT_USERNAMES=dev-bot,review-bot,planner-bot,gardener-bot,vault-bot,supervisor-bot,predictor-bot,architect-bot # ── Backwards compatibility ─────────────────────────────────────────────── # If CODEBERG_TOKEN is set but FORGE_TOKEN is not, env.sh falls back to diff --git a/.woodpecker/agent-smoke.sh b/.woodpecker/agent-smoke.sh index 6651c0a..85de2ad 100644 --- a/.woodpecker/agent-smoke.sh +++ b/.woodpecker/agent-smoke.sh @@ -84,7 +84,7 @@ while IFS= read -r -d '' f; do printf 'FAIL [syntax] %s\n' "$f" FAILED=1 fi -done < <(find dev gardener review planner supervisor lib vault -name "*.sh" -print0 2>/dev/null) +done < <(find dev gardener review planner supervisor architect lib vault -name "*.sh" -print0 2>/dev/null) echo "syntax check done" # ── 2. Function-resolution check ───────────────────────────────────────────── @@ -213,6 +213,7 @@ check_script supervisor/update-prompt.sh check_script supervisor/supervisor-run.sh check_script supervisor/preflight.sh check_script predictor/predictor-run.sh +check_script architect/architect-run.sh echo "function resolution check done" diff --git a/.woodpecker/detect-duplicates.py b/.woodpecker/detect-duplicates.py index c43fd1f..bd3f74a 100644 --- a/.woodpecker/detect-duplicates.py +++ b/.woodpecker/detect-duplicates.py @@ -179,9 +179,16 @@ def collect_findings(root): Returns ``(ap_hits, dup_groups)`` with file paths relative to *root*. """ root = Path(root) - sh_files = sorted( - p for p in root.rglob("*.sh") if ".git" not in p.parts - ) + # Skip architect scripts for duplicate detection (stub formulas, see #99) + EXCLUDED_SUFFIXES = ("architect/architect-run.sh",) + + def is_excluded(p): + """Check if path should be excluded by suffix match.""" + return p.suffix == ".sh" and ".git" not in p.parts and any( + str(p).endswith(suffix) for suffix in EXCLUDED_SUFFIXES + ) + + sh_files = sorted(p for p in root.rglob("*.sh") if not is_excluded(p)) ap_hits = check_anti_patterns(sh_files) dup_groups = check_duplicates(sh_files) @@ -238,9 +245,16 @@ def print_duplicates(groups, label=""): # --------------------------------------------------------------------------- def main() -> int: - sh_files = sorted( - p for p in Path(".").rglob("*.sh") if ".git" not in p.parts - ) + # Skip architect scripts for duplicate detection (stub formulas, see #99) + EXCLUDED_SUFFIXES = ("architect/architect-run.sh",) + + def is_excluded(p): + """Check if path should be excluded by suffix match.""" + return p.suffix == ".sh" and ".git" not in p.parts and any( + str(p).endswith(suffix) for suffix in EXCLUDED_SUFFIXES + ) + + sh_files = sorted(p for p in Path(".").rglob("*.sh") if not is_excluded(p)) if not sh_files: print("No .sh files found.") diff --git a/AGENTS.md b/AGENTS.md index a6ac1fd..299ff45 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,11 +3,11 @@ ## What this repo is -Disinto is an autonomous code factory. It manages six agents (dev, review, -gardener, supervisor, planner, predictor) that pick up issues from forge, -implement them, review PRs, plan from the vision, and keep the system healthy — -all via cron and `claude -p`. The dispatcher executes formula-based operational -tasks. +Disinto is an autonomous code factory. It manages seven agents (dev, review, +gardener, supervisor, planner, predictor, architect) that pick up issues from +forge, implement them, review PRs, plan from the vision, and keep the system +healthy — all via cron and `claude -p`. The dispatcher executes formula-based +operational tasks. > **Note:** The vault is being redesigned as a PR-based approval workflow on the > ops repo (see issues #73-#77). See [docs/VAULT.md](docs/VAULT.md) for details. Old vault scripts are being removed. @@ -26,6 +26,7 @@ disinto/ (code repo) ├── supervisor/ supervisor-run.sh — formula-driven health monitoring (cron wrapper) │ preflight.sh — pre-flight data collection for supervisor formula │ supervisor-poll.sh — legacy bash orchestrator (superseded) +├── architect/ architect-run.sh — strategic decomposition of vision into sprints ├── vault/ vault-env.sh — shared env setup (vault redesign in progress, see #73-#77) ├── lib/ env.sh, agent-session.sh, ci-helpers.sh, ci-debug.sh, load-project.sh, parse-deps.sh, guard.sh, mirrors.sh, pr-lifecycle.sh, issue-lifecycle.sh, worktree.sh, formula-session.sh, profile.sh, build-graph.py ├── projects/ *.toml.example — templates; *.toml — local per-box config (gitignored) @@ -93,6 +94,7 @@ bash dev/phase-test.sh | Supervisor | `supervisor/` | Health monitoring | [supervisor/AGENTS.md](supervisor/AGENTS.md) | | Planner | `planner/` | Strategic planning | [planner/AGENTS.md](planner/AGENTS.md) | | Predictor | `predictor/` | Infrastructure pattern detection | [predictor/AGENTS.md](predictor/AGENTS.md) | +| Architect | `architect/` | Strategic decomposition | [architect/AGENTS.md](architect/AGENTS.md) | > **Vault:** Being redesigned as a PR-based approval workflow (issues #73-#77). > See [docs/VAULT.md](docs/VAULT.md) for the vault PR workflow details. diff --git a/architect/AGENTS.md b/architect/AGENTS.md new file mode 100644 index 0000000..c2e99ba --- /dev/null +++ b/architect/AGENTS.md @@ -0,0 +1,65 @@ + +# Architect — Agent Instructions + +## What this agent is + +The architect is a strategic decomposition agent that breaks down vision issues +into development sprints. It proposes sprints via PRs on the ops repo and +converses with humans through PR comments. + +## Role + +- **Input**: Vision issues from VISION.md, prerequisite tree from ops repo +- **Output**: Sprint proposals as PRs on the ops repo, sub-issue files +- **Mechanism**: Formula-driven execution via `formulas/run-architect.toml` +- **Identity**: `architect-bot` on Forgejo + +## Responsibilities + +1. **Strategic decomposition**: Break down large vision items into coherent + sprints that can be executed by the dev agent +2. **Design fork identification**: When multiple implementation approaches exist, + identify the forks and file sub-issues for each path +3. **Sprint PR creation**: Propose sprints as PRs on the ops repo with clear + acceptance criteria and dependencies +4. **Human conversation**: Respond to PR comments, refine sprint proposals based + on human feedback +5. **Sub-issue filing**: After design forks are resolved, file concrete sub-issues + for implementation + +## Formula + +The architect is driven by `formulas/run-architect.toml`. This formula defines +the steps for: +- Research: analyzing vision items and prerequisite tree +- Design: identifying implementation approaches and forks +- Sprint proposal: creating structured sprint PRs +- Sub-issue filing: creating concrete implementation issues + +## Execution + +Run via `architect/architect-run.sh`, which: +- Acquires a cron lock and checks available memory +- Sources shared libraries (env.sh, formula-session.sh) +- Uses FORGE_ARCHITECT_TOKEN for authentication +- Loads the formula and builds context from VISION.md, AGENTS.md, and ops repo +- Executes the formula via `agent_run` + +## Cron + +Suggested cron entry (every 6 hours): +```cron +0 */6 * * * cd /path/to/disinto && bash architect/architect-run.sh +``` + +## State + +Architect state is tracked in `state/.architect-active` (disabled by default — +empty file not created, just document it). + +## Related issues + +- #96: Architect agent parent issue +- #100: Architect formula — research + design fork identification +- #101: Architect formula — sprint PR creation with questions +- #102: Architect formula — answer parsing + sub-issue filing diff --git a/architect/architect-run.sh b/architect/architect-run.sh new file mode 100755 index 0000000..b3d2513 --- /dev/null +++ b/architect/architect-run.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# ============================================================================= +# architect-run.sh — Cron wrapper: architect execution via SDK + formula +# +# Synchronous bash loop using claude -p (one-shot invocation). +# No tmux sessions, no phase files — the bash script IS the state machine. +# +# Flow: +# 1. Guards: cron lock, memory check +# 2. Load formula (formulas/run-architect.toml) +# 3. Context: VISION.md, AGENTS.md, ops:prerequisites.md, structural graph +# 4. agent_run(worktree, prompt) → Claude decomposes vision into sprints +# +# Usage: +# architect-run.sh [projects/disinto.toml] # project config (default: disinto) +# +# Cron: 0 */6 * * * # every 6 hours +# ============================================================================= +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +FACTORY_ROOT="$(dirname "$SCRIPT_DIR")" + +# Accept project config from argument; default to disinto +export PROJECT_TOML="${1:-$FACTORY_ROOT/projects/disinto.toml}" +# shellcheck source=../lib/env.sh +source "$FACTORY_ROOT/lib/env.sh" +# Override FORGE_TOKEN with architect-bot's token (#747) +FORGE_TOKEN="${FORGE_ARCHITECT_TOKEN:-${FORGE_TOKEN}}" +# shellcheck source=../lib/formula-session.sh +source "$FACTORY_ROOT/lib/formula-session.sh" +# shellcheck source=../lib/worktree.sh +source "$FACTORY_ROOT/lib/worktree.sh" +# shellcheck source=../lib/guard.sh +source "$FACTORY_ROOT/lib/guard.sh" +# shellcheck source=../lib/agent-sdk.sh +source "$FACTORY_ROOT/lib/agent-sdk.sh" + +LOG_FILE="$SCRIPT_DIR/architect.log" +# shellcheck disable=SC2034 # consumed by agent-sdk.sh +LOGFILE="$LOG_FILE" +# shellcheck disable=SC2034 # consumed by agent-sdk.sh +SID_FILE="/tmp/architect-session-${PROJECT_NAME}.sid" +SCRATCH_FILE="/tmp/architect-${PROJECT_NAME}-scratch.md" +WORKTREE="/tmp/${PROJECT_NAME}-architect-run" + +log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } + +# ── Guards ──────────────────────────────────────────────────────────────── +check_active architect +acquire_cron_lock "/tmp/architect-run.lock" +check_memory 2000 + +log "--- Architect run start ---" + +# ── Load formula + context ─────────────────────────────────────────────── +load_formula "$FACTORY_ROOT/formulas/run-architect.toml" +build_context_block VISION.md AGENTS.md ops:prerequisites.md + +# ── Build structural analysis graph ────────────────────────────────────── +build_graph_section + +# ── Read scratch file (compaction survival) ─────────────────────────────── +SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") +SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") + +# ── Build prompt ───────────────────────────────────────────────────────── +build_sdk_prompt_footer + +# Architect prompt: strategic decomposition of vision into sprints +# See: architect/AGENTS.md for full role description +# Pattern: heredoc function to avoid inline prompt construction +# Note: Uses CONTEXT_BLOCK, GRAPH_SECTION, SCRATCH_CONTEXT from formula-session.sh +# Architecture Decision: AD-003 — The runtime creates and destroys, the formula preserves. +build_architect_prompt() { + cat <<_PROMPT_EOF_ +You are the architect agent for ${FORGE_REPO}. Work through the formula below. + +Your role: strategic decomposition of vision issues into development sprints. +Propose sprints via PRs on the ops repo, converse with humans through PR comments, +and file sub-issues after design forks are resolved. + +## Project context +${CONTEXT_BLOCK} +${GRAPH_SECTION} +${SCRATCH_CONTEXT} +## Formula +${FORMULA_CONTENT} + +${SCRATCH_INSTRUCTION} +${PROMPT_FOOTER} +_PROMPT_EOF_ +} + +PROMPT=$(build_architect_prompt) + +# ── Create worktree ────────────────────────────────────────────────────── +formula_worktree_setup "$WORKTREE" + +# ── Run agent ───────────────────────────────────────────────────────────── +export CLAUDE_MODEL="sonnet" + +agent_run --worktree "$WORKTREE" "$PROMPT" +log "agent_run complete" + +rm -f "$SCRATCH_FILE" +log "--- Architect run done ---" diff --git a/bin/disinto b/bin/disinto index cc9a95d..959e665 100755 --- a/bin/disinto +++ b/bin/disinto @@ -926,6 +926,8 @@ ${ops_name}/ └── RESOURCES.md # accounts, tokens (refs), infra inventory \`\`\` +> **Note:** Journal directories (journal/planner/ and journal/supervisor/) have been removed from the ops repo. Agent journals are now stored in each agent's .profile repo on Forgejo. + ## Branch protection - \`main\`: 2 reviewers required for vault items diff --git a/formulas/run-architect.toml b/formulas/run-architect.toml new file mode 100644 index 0000000..cd8010a --- /dev/null +++ b/formulas/run-architect.toml @@ -0,0 +1,304 @@ +# formulas/run-architect.toml — Architect formula +# +# Executed by architect-run.sh via cron — strategic decomposition of vision +# issues into development sprints. +# +# This formula orchestrates the architect agent's workflow: +# Step 1: Preflight — validate prerequisites and identify target issue +# Step 2: Research + pitch — analyze codebase and write sprint pitch +# Step 3: Sprint PR creation with questions (issue #101) +# Step 4: Answer parsing + sub-issue filing (issue #102) +# +# AGENTS.md maintenance is handled by the gardener (#246). + +name = "run-architect" +description = "Architect: strategic decomposition of vision into sprints" +version = 1 +model = "opus" + +[context] +files = ["VISION.md", "AGENTS.md"] +# Prerequisite tree loaded from ops repo (ops: prefix) +# Sprints directory tracked in ops repo + +[[steps]] +id = "preflight" +title = "Preflight: validate prerequisites and identify target vision issue" +description = """ +This step performs preflight checks and identifies the most unblocking vision issue. + +Actions: +1. Pull latest code from both disinto repo and ops repo +2. Read prerequisite tree from $OPS_REPO_ROOT/prerequisites.md +3. Fetch open issues labeled 'vision' from Forgejo API +4. Check for open architect PRs on ops repo (handled by #101/#102) +5. If open architect PRs exist, handle accept/reject responses (see Capability B below) +6. If no vision issues, signal PHASE:done + +Skip conditions: +- If no vision issues are found, signal PHASE:done + +Output: +- Sets ARCHITECT_TARGET_ISSUE to the issue number of the selected vision issue +- Exports VISION_ISSUES as a JSON array of issue objects + +## Capability B: Handle accept/reject on existing pitch PRs + +When open architect PRs exist on the ops repo: + +1. Fetch comments on each open architect PR via Forgejo API +2. Look for human response: + + **ACCEPT** (case insensitive): Human wants to proceed + - Architect does deep research for design forks (same as #100 research but now identifying decision points) + - Formulates multiple-choice questions (Q1, Q2, Q3...) + - Updates the sprint spec file on the PR branch: + - Adds `## Design forks` section with fork options + - Adds `## Proposed sub-issues` section with concrete issues per fork path + - Comments on the PR with the questions formatted as multiple choice + - Signal PHASE:done (answer processing is #102) + + **REJECT: ** (case insensitive, reason after colon): + - Journal the rejection reason via profile_write_journal (if .profile exists) + — the architect learns what pitches fail + - Close the PR via Forgejo API (do not merge — rejected pitches do not persist in sprints/) + - Remove the branch via Forgejo API + - Signal PHASE:done + + **No response yet**: skip silently, signal PHASE:done + +All git operations use the Forgejo API (create branch, write/update file, create PR, +close PR, delete branch). No SSH. +""" + +[[steps]] +id = "research_pitch" +title = "Research + pitch: analyze codebase and write sprint pitch" +description = """ +This step performs deep codebase research and writes a sprint pitch for the +selected vision issue. + +Actions: + +1. Read the codebase deeply: + - Read all files mentioned in the issue body + - Search for existing interfaces that could be reused + - Check what infrastructure already exists + +2. Assess complexity and cost: + - How many files/subsystems are touched? + - What new infrastructure would need to be maintained after this sprint? + - What are the risks (breaking changes, security implications, integration complexity)? + - Is this mostly gluecode or greenfield? + +3. Write sprint pitch to scratch file for PR creation step (#101): + +# Sprint pitch: + +## Vision issues +- #N — + +## What this enables +<what the project can do after this sprint that it can't do now> + +## What exists today +<current state — infrastructure, interfaces, code that can be reused> + +## Complexity +<number of files, subsystems, estimated sub-issues> +<gluecode vs greenfield ratio> + +## Risks +<what could go wrong, what breaks if this is done badly> + +## Cost — new infra to maintain +<what ongoing maintenance burden does this sprint add> +<new services, cron jobs, formulas, agent roles> + +## Recommendation +<architect's assessment: worth it / defer / alternative approach> + +IMPORTANT: Do NOT include design forks or questions yet. The pitch is a go/no-go +decision for the human. Questions come only after acceptance. + +Output: +- Writes sprint pitch to $SCRATCH_FILE (/tmp/architect-{project}-scratch.md) +- The pitch serves as input for sprint PR creation step (#101) +""" + +[[steps]] +id = "sprint_pr_creation" +title = "Sprint PR creation with questions (issue #101)" +description = """ +This step creates a PR on the ops repo with the sprint proposal when no PR exists yet. + +## Capability A: Create pitch PR (from research output) + +If step 2 (research/pitch) produced a pitch and no PR exists yet: + +1. Create branch `architect/<sprint-slug>` on ops repo via Forgejo API + - Sprint slug: lowercase, hyphenated version of sprint name + - Use Forgejo API: POST /repos/{owner}/{repo}/git/branches + +2. Write sprint spec file to sprints/<sprint-slug>.md on the new branch: + +# Sprint: <name> + +## Vision issues +- #N — <title> + +## What this enables +<what the project can do after this sprint that it can't do now> + +## What exists today +<current state — infrastructure, interfaces, code that can be reused> + +## Complexity +<number of files/subsystems, estimated sub-issues> +<gluecode vs greenfield ratio> + +## Risks +<what could go wrong, what breaks if this is done badly> + +## Cost — new infra to maintain +<what ongoing maintenance burden does this sprint add> +<new services, cron jobs, formulas, agent roles> + +## Recommendation +<architect's assessment: worth it / defer / alternative approach> + +3. Create PR on ops repo via Forgejo API: + - Title: `architect: <sprint summary>` + - Body: pitch content (what it enables, complexity, risks, cost) + - Base branch: primary branch (main/master) + - Head branch: architect/<sprint-slug> + - Footer: "Reply `ACCEPT` to proceed with design questions, or `REJECT: <reason>` to decline." + +4. Signal PHASE:done + +## Forgejo API Reference + +All operations use the Forgejo API with `Authorization: token ${FORGE_TOKEN}` header. + +### Create branch +``` +POST /repos/{owner}/{repo}/branches +Body: {"new_branch_name": "architect/<sprint-slug>", "old_branch_name": "main"} +``` + +### Create/update file +``` +PUT /repos/{owner}/{repo}/contents/<path> +Body: {"message": "sprint: add <sprint-slug>.md", "content": "<base64-encoded-content>", "branch": "architect/<sprint-slug>"} +``` + +### Create PR +``` +POST /repos/{owner}/{repo}/pulls +Body: {"title": "architect: <sprint summary>", "body": "<pitch-content>", "head": "architect/<sprint-slug>", "base": "main"} +``` + +### Close PR +``` +PATCH /repos/{owner}/{repo}/pulls/{index} +Body: {"state": "closed"} +``` + +### Delete branch +``` +DELETE /repos/{owner}/{repo}/git/branches/<branch-name> +``` +""" + +[[steps]] +id = "answer_parsing" +title = "Answer parsing + sub-issue filing (issue #102)" +description = """ +This step processes human answers to design questions and files sub-issues. + +## Preflight: Detect PRs in question phase + +An architect PR is in the question phase if ALL of the following are true: +- PR is open +- PR body or sprint spec file contains a `## Design forks` section (added by #101 after ACCEPT) +- PR has question comments (Q1, Q2, Q3... format) + +## Answer parsing + +Human comments on the PR use this format: +``` +Q1: A +Q2: B +Q3: A +``` + +Parser matches lines starting with `Q` + digit(s) + `:` + space + letter A-D (case insensitive). +Ignore other content in the comment. + +## Processing paths + +### All questions answered (every `### Q` heading has a matching `Q<N>: <letter>` comment) + +1. Parse each answer (e.g. `Q1: A`, `Q2: C`) +2. Read the sprint spec from the PR branch +3. Generate final sub-issues based on answers: + - Each sub-issue uses the appropriate issue template (bug/feature/refactor from `.codeberg/ISSUE_TEMPLATE/`) + - Fill all template fields: + - Problem/motivation (feature) or What's broken (bug/refactor) + - Proposed solution (feature) or Approach (refactor) or Steps to reproduce (bug) + - Affected files (max 3) + - Acceptance criteria (max 5) + - Dependencies + - File via Forgejo API on the **disinto repo** (not ops repo) + - Label as `backlog` +4. Comment on PR: "Sprint filed: #N, #N, #N" +5. Merge the PR (sprint spec with answers persists in `ops/sprints/`) + +### Some questions answered, not all + +1. Acknowledge answers received +2. Comment listing remaining unanswered questions +3. Signal PHASE:done (check again next poll) + +### No answers yet (questions posted but human hasn't responded) + +1. Skip — signal PHASE:done + +## Forgejo API for filing issues on disinto repo + +All operations use the Forgejo API with `Authorization: token ${FORGE_TOKEN}` header. + +### Create issue +``` +POST /repos/{owner}/{repo}/issues +Body: { + "title": "<issue title>", + "body": "<issue body with template fields>", + "labels": [123], // backlog label ID + "assignees": ["architect-bot"] +} +``` + +### Close PR +``` +PATCH /repos/{owner}/{repo}/pulls/{index} +Body: {"state": "closed"} +``` + +### Merge PR +``` +POST /repos/{owner}/{repo}/pulls/{index}/merge +Body: {"Do": "merge"} +``` + +### Post comment on PR (via issues endpoint) +``` +POST /repos/{owner}/{repo}/issues/{index}/comments +Body: {"body": "<comment text>"} +``` + +### Get label ID +``` +GET /repos/{owner}/{repo}/labels +``` +""" diff --git a/gardener/gardener-run.sh b/gardener/gardener-run.sh index 31aa8c0..942c86b 100755 --- a/gardener/gardener-run.sh +++ b/gardener/gardener-run.sh @@ -64,10 +64,19 @@ check_memory 2000 log "--- Gardener run start ---" +# ── Resolve agent identity for .profile repo ──────────────────────────── +if [ -z "${AGENT_IDENTITY:-}" ] && [ -n "${FORGE_GARDENER_TOKEN:-}" ]; then + AGENT_IDENTITY=$(curl -sf -H "Authorization: token ${FORGE_GARDENER_TOKEN}" \ + "${FORGE_URL:-http://localhost:3000}/api/v1/user" 2>/dev/null | jq -r '.login // empty' 2>/dev/null || true) +fi + # ── Load formula + context ─────────────────────────────────────────────── -load_formula "$FACTORY_ROOT/formulas/run-gardener.toml" +load_formula_or_profile "gardener" "$FACTORY_ROOT/formulas/run-gardener.toml" || exit 1 build_context_block AGENTS.md +# ── Prepare .profile context (lessons injection) ───────────────────────── +formula_prepare_profile_context + # ── Read scratch file (compaction survival) ─────────────────────────────── SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") @@ -105,7 +114,10 @@ You have full shell access and --dangerously-skip-permissions. Fix what you can. File vault items for what you cannot. Do NOT ask permission — act first, report after. ## Project context -${CONTEXT_BLOCK} +${CONTEXT_BLOCK}${LESSONS_INJECTION:+## Lessons learned +${LESSONS_INJECTION} + +} ${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} } ## Result file @@ -334,5 +346,8 @@ else rm -f "$SCRATCH_FILE" fi +# Write journal entry post-session +profile_write_journal "gardener-run" "Gardener run $(date -u +%Y-%m-%d)" "complete" "" || true + rm -f "$GARDENER_PR_FILE" log "--- Gardener run done ---" diff --git a/lib/env.sh b/lib/env.sh index a2c98a9..cc0906c 100755 --- a/lib/env.sh +++ b/lib/env.sh @@ -95,9 +95,10 @@ export FORGE_GARDENER_TOKEN="${FORGE_GARDENER_TOKEN:-${FORGE_TOKEN}}" export FORGE_VAULT_TOKEN="${FORGE_VAULT_TOKEN:-${FORGE_TOKEN}}" export FORGE_SUPERVISOR_TOKEN="${FORGE_SUPERVISOR_TOKEN:-${FORGE_TOKEN}}" export FORGE_PREDICTOR_TOKEN="${FORGE_PREDICTOR_TOKEN:-${FORGE_TOKEN}}" +export FORGE_ARCHITECT_TOKEN="${FORGE_ARCHITECT_TOKEN:-${FORGE_TOKEN}}" # Bot usernames filter: FORGE_BOT_USERNAMES > legacy CODEBERG_BOT_USERNAMES -export FORGE_BOT_USERNAMES="${FORGE_BOT_USERNAMES:-${CODEBERG_BOT_USERNAMES:-dev-bot,review-bot,planner-bot,gardener-bot,vault-bot,supervisor-bot,predictor-bot}}" +export FORGE_BOT_USERNAMES="${FORGE_BOT_USERNAMES:-${CODEBERG_BOT_USERNAMES:-dev-bot,review-bot,planner-bot,gardener-bot,vault-bot,supervisor-bot,predictor-bot,architect-bot}}" export CODEBERG_BOT_USERNAMES="${FORGE_BOT_USERNAMES}" # backwards compat # Project config (FORGE_* preferred, CODEBERG_* fallback) diff --git a/lib/formula-session.sh b/lib/formula-session.sh index 1675ea5..553b0de 100644 --- a/lib/formula-session.sh +++ b/lib/formula-session.sh @@ -350,6 +350,17 @@ ${lessons_content}" return 0 } +# formula_prepare_profile_context +# Pre-session: loads lessons from .profile repo and sets LESSONS_CONTEXT for prompt injection. +# Single shared function to avoid duplicate boilerplate across agent scripts. +# Requires: AGENT_IDENTITY, FORGE_TOKEN, FORGE_URL (via profile_load_lessons). +# Exports: LESSONS_CONTEXT (set by profile_load_lessons). +# Returns 0 on success, 1 if agent has no .profile repo (silent no-op). +formula_prepare_profile_context() { + profile_load_lessons || true + LESSONS_INJECTION="${LESSONS_CONTEXT:-}" +} + # profile_write_journal ISSUE_NUM ISSUE_TITLE OUTCOME [FILES_CHANGED] # Post-session: writes a reflection journal entry after work completes. # Returns 0 on success, 1 on failure. diff --git a/planner/planner-run.sh b/planner/planner-run.sh index 31f5588..f7bb8a4 100755 --- a/planner/planner-run.sh +++ b/planner/planner-run.sh @@ -45,12 +45,6 @@ WORKTREE="/tmp/${PROJECT_NAME}-planner-run" log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } -# Ensure AGENT_IDENTITY is set for profile functions -if [ -z "${AGENT_IDENTITY:-}" ] && [ -n "${FORGE_PLANNER_TOKEN:-}" ]; then - AGENT_IDENTITY=$(curl -sf -H "Authorization: token ${FORGE_PLANNER_TOKEN}" \ - "${FORGE_URL:-http://localhost:3000}/api/v1/user" 2>/dev/null | jq -r '.login // empty' 2>/dev/null || true) -fi - # ── Guards ──────────────────────────────────────────────────────────────── check_active planner acquire_cron_lock "/tmp/planner-run.lock" @@ -58,8 +52,14 @@ check_memory 2000 log "--- Planner run start ---" +# ── Resolve agent identity for .profile repo ──────────────────────────── +if [ -z "${AGENT_IDENTITY:-}" ] && [ -n "${FORGE_PLANNER_TOKEN:-}" ]; then + AGENT_IDENTITY=$(curl -sf -H "Authorization: token ${FORGE_PLANNER_TOKEN}" \ + "${FORGE_URL:-http://localhost:3000}/api/v1/user" 2>/dev/null | jq -r '.login // empty' 2>/dev/null || true) +fi + # ── Load formula + context ─────────────────────────────────────────────── -load_formula "$FACTORY_ROOT/formulas/run-planner.toml" +load_formula_or_profile "planner" "$FACTORY_ROOT/formulas/run-planner.toml" || exit 1 build_context_block VISION.md AGENTS.md ops:RESOURCES.md ops:prerequisites.md # ── Build structural analysis graph ────────────────────────────────────── @@ -78,9 +78,8 @@ $(cat "$MEMORY_FILE") " fi -# ── Load lessons from .profile repo (pre-session) ──────────────────────── -profile_load_lessons || true -LESSONS_INJECTION="${LESSONS_CONTEXT:-}" +# ── Prepare .profile context (lessons injection) ───────────────────────── +formula_prepare_profile_context # ── Read scratch file (compaction survival) ─────────────────────────────── SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") @@ -96,8 +95,7 @@ build_sdk_prompt_footer " PROMPT="You are the strategic planner for ${FORGE_REPO}. Work through the formula below. ## Project context -${CONTEXT_BLOCK}${MEMORY_BLOCK} -${LESSONS_INJECTION:+## Lessons learned +${CONTEXT_BLOCK}${MEMORY_BLOCK}${LESSONS_INJECTION:+## Lessons learned ${LESSONS_INJECTION} } diff --git a/predictor/predictor-run.sh b/predictor/predictor-run.sh index fb9bf51..e2e5c0e 100755 --- a/predictor/predictor-run.sh +++ b/predictor/predictor-run.sh @@ -53,13 +53,22 @@ check_memory 2000 log "--- Predictor run start ---" +# ── Resolve agent identity for .profile repo ──────────────────────────── +if [ -z "${AGENT_IDENTITY:-}" ] && [ -n "${FORGE_PREDICTOR_TOKEN:-}" ]; then + AGENT_IDENTITY=$(curl -sf -H "Authorization: token ${FORGE_PREDICTOR_TOKEN}" \ + "${FORGE_URL:-http://localhost:3000}/api/v1/user" 2>/dev/null | jq -r '.login // empty' 2>/dev/null || true) +fi + # ── Load formula + context ─────────────────────────────────────────────── -load_formula "$FACTORY_ROOT/formulas/run-predictor.toml" +load_formula_or_profile "predictor" "$FACTORY_ROOT/formulas/run-predictor.toml" || exit 1 build_context_block AGENTS.md ops:RESOURCES.md VISION.md ops:prerequisites.md # ── Build structural analysis graph ────────────────────────────────────── build_graph_section +# ── Prepare .profile context (lessons injection) ───────────────────────── +formula_prepare_profile_context + # ── Read scratch file (compaction survival) ─────────────────────────────── SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") @@ -82,9 +91,13 @@ Use WebSearch for external signal scanning — be targeted (project dependencies and tools only, not general news). Limit to 3 web searches per run. ## Project context -${CONTEXT_BLOCK} +${CONTEXT_BLOCK}${LESSONS_INJECTION:+## Lessons learned +${LESSONS_INJECTION} + +} ${GRAPH_SECTION} -${SCRATCH_CONTEXT} +${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} +} ## Formula ${FORMULA_CONTENT} @@ -98,5 +111,8 @@ formula_worktree_setup "$WORKTREE" agent_run --worktree "$WORKTREE" "$PROMPT" log "agent_run complete" +# Write journal entry post-session +profile_write_journal "predictor-run" "Predictor run $(date -u +%Y-%m-%d)" "complete" "" || true + rm -f "$SCRATCH_FILE" log "--- Predictor run done ---" diff --git a/review/review-poll.sh b/review/review-poll.sh index 57a647c..4840b35 100755 --- a/review/review-poll.sh +++ b/review/review-poll.sh @@ -13,8 +13,16 @@ source "$(dirname "$0")/../lib/env.sh" source "$(dirname "$0")/../lib/ci-helpers.sh" # shellcheck source=../lib/guard.sh source "$(dirname "$0")/../lib/guard.sh" +# shellcheck source=../lib/formula-session.sh +source "$(dirname "$0")/../lib/formula-session.sh" check_active reviewer +# Resolve agent identity for .profile repo +if [ -z "${AGENT_IDENTITY:-}" ] && [ -n "${FORGE_TOKEN:-}" ]; then + AGENT_IDENTITY=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ + "${FORGE_URL:-http://localhost:3000}/api/v1/user" 2>/dev/null | jq -r '.login // empty' 2>/dev/null || true) +fi + REPO_ROOT="${PROJECT_REPO_ROOT}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" diff --git a/review/review-pr.sh b/review/review-pr.sh index 0ae0fdb..036e1a8 100755 --- a/review/review-pr.sh +++ b/review/review-pr.sh @@ -27,6 +27,8 @@ source "$(dirname "$0")/../lib/env.sh" source "$(dirname "$0")/../lib/ci-helpers.sh" source "$(dirname "$0")/../lib/worktree.sh" source "$(dirname "$0")/../lib/agent-sdk.sh" +# shellcheck source=../lib/formula-session.sh +source "$(dirname "$0")/../lib/formula-session.sh" # Auto-pull factory code to pick up merged fixes before any logic runs git -C "$FACTORY_ROOT" pull --ff-only origin main 2>/dev/null || true @@ -56,6 +58,14 @@ if [ -f "$LOGFILE" ] && [ "$(stat -c%s "$LOGFILE" 2>/dev/null || echo 0)" -gt 10 mv "$LOGFILE" "$LOGFILE.old" fi +# ============================================================================= +# RESOLVE AGENT IDENTITY FOR .PROFILE REPO +# ============================================================================= +if [ -z "${AGENT_IDENTITY:-}" ] && [ -n "${FORGE_TOKEN:-}" ]; then + AGENT_IDENTITY=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \ + "${FORGE_URL:-http://localhost:3000}/api/v1/user" 2>/dev/null | jq -r '.login // empty' 2>/dev/null || true) +fi + # ============================================================================= # MEMORY GUARD # ============================================================================= @@ -180,6 +190,11 @@ else log "WARN: build-graph.py failed — continuing without structural analysis" fi +# ============================================================================= +# LOAD LESSONS FROM .PROFILE REPO (PRE-SESSION) +# ============================================================================= +formula_prepare_profile_context + # ============================================================================= # BUILD PROMPT # ============================================================================= @@ -193,6 +208,7 @@ FORMULA=$(cat "${FACTORY_ROOT}/formulas/review-pr.toml") "$PR_BODY" "$FILES" "$DNOTE" "$DIFF" [ -n "$PREV_CONTEXT" ] && printf '%s\n' "$PREV_CONTEXT" [ -n "$GRAPH_SECTION" ] && printf '%s\n' "$GRAPH_SECTION" + [ -n "$LESSONS_INJECTION" ] && printf '\n## Lessons learned\n%s\n\n' "$LESSONS_INJECTION" printf '\n## Formula\n%s\n\n## Environment\nREVIEW_OUTPUT_FILE=%s\nFORGE_API=%s\nPR_NUMBER=%s\nFACTORY_ROOT=%s\n' \ "$FORMULA" "$OUTPUT_FILE" "$API" "$PR_NUMBER" "$FACTORY_ROOT" printf 'NEVER echo the actual token — always reference ${FORGE_TOKEN} or ${FORGE_REVIEW_TOKEN}.\n' @@ -298,4 +314,7 @@ case "$VERDICT" in ;; esac +# Write journal entry post-session +profile_write_journal "review-${PR_NUMBER}" "Review PR #${PR_NUMBER} (${VERDICT})" "${VERDICT,,}" "" || true + log "DONE: ${VERDICT} (re-review: ${IS_RE_REVIEW})" diff --git a/supervisor/supervisor-run.sh b/supervisor/supervisor-run.sh index 129666f..67e893c 100755 --- a/supervisor/supervisor-run.sh +++ b/supervisor/supervisor-run.sh @@ -58,6 +58,12 @@ log "--- Supervisor run start ---" # ── Housekeeping: clean up stale crashed worktrees (>24h) ──────────────── cleanup_stale_crashed_worktrees 24 +# ── Resolve agent identity for .profile repo ──────────────────────────── +if [ -z "${AGENT_IDENTITY:-}" ] && [ -n "${FORGE_SUPERVISOR_TOKEN:-}" ]; then + AGENT_IDENTITY=$(curl -sf -H "Authorization: token ${FORGE_SUPERVISOR_TOKEN}" \ + "${FORGE_URL:-http://localhost:3000}/api/v1/user" 2>/dev/null | jq -r '.login // empty' 2>/dev/null || true) +fi + # ── Collect pre-flight metrics ──────────────────────────────────────────── log "Running preflight.sh" PREFLIGHT_OUTPUT="" @@ -68,9 +74,12 @@ else fi # ── Load formula + context ─────────────────────────────────────────────── -load_formula "$FACTORY_ROOT/formulas/run-supervisor.toml" +load_formula_or_profile "supervisor" "$FACTORY_ROOT/formulas/run-supervisor.toml" || exit 1 build_context_block AGENTS.md +# ── Prepare .profile context (lessons injection) ───────────────────────── +formula_prepare_profile_context + # ── Read scratch file (compaction survival) ─────────────────────────────── SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") @@ -91,7 +100,10 @@ Fix what you can. File vault items for what you cannot. Do NOT ask permission ${PREFLIGHT_OUTPUT} ## Project context -${CONTEXT_BLOCK} +${CONTEXT_BLOCK}${LESSONS_INJECTION:+## Lessons learned +${LESSONS_INJECTION} + +} ${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} } Priority order: P0 memory > P1 disk > P2 stopped > P3 degraded > P4 housekeeping @@ -105,5 +117,8 @@ ${PROMPT_FOOTER}" agent_run --worktree "$WORKTREE" "$PROMPT" log "agent_run complete" +# Write journal entry post-session +profile_write_journal "supervisor-run" "Supervisor run $(date -u +%Y-%m-%d)" "complete" "" || true + rm -f "$SCRATCH_FILE" log "--- Supervisor run done ---"