diff --git a/.woodpecker/detect-duplicates.py b/.woodpecker/detect-duplicates.py index 6fe7366..bd3f74a 100644 --- a/.woodpecker/detect-duplicates.py +++ b/.woodpecker/detect-duplicates.py @@ -256,19 +256,6 @@ def main() -> int: sh_files = sorted(p for p in Path(".").rglob("*.sh") if not is_excluded(p)) - # Standard patterns that are intentionally repeated across formula-driven agents - # These are not copy-paste violations but the expected structure - ALLOWED_HASHES = { - # Standard agent header: shebang, set -euo pipefail, directory resolution - "c93baa0f19d6b9ba271428bf1cf20b45": "Standard agent header (set -euo pipefail, SCRIPT_DIR, FACTORY_ROOT)", - # formula_prepare_profile_context followed by scratch context reading - "eaa735b3598b7b73418845ab00d8aba5": "Standard .profile context setup (formula_prepare_profile_context + SCRATCH_CONTEXT)", - # Standard prompt template: GRAPH_SECTION, SCRATCH_CONTEXT, FORMULA_CONTENT, SCRATCH_INSTRUCTION - "2653705045fdf65072cccfd16eb04900": "Standard prompt template (GRAPH_SECTION, SCRATCH_CONTEXT, FORMULA_CONTENT)", - "93726a3c799b72ed2898a55552031921": "Standard prompt template continuation (SCRATCH_CONTEXT, FORMULA_CONTENT, SCRATCH_INSTRUCTION)", - "c11eaaacab69c9a2d3c38c75215eca84": "Standard prompt template end (FORMULA_CONTENT, SCRATCH_INSTRUCTION)", - } - if not sh_files: print("No .sh files found.") return 0 @@ -303,13 +290,8 @@ def main() -> int: # Duplicate diff: key by content hash base_dup_hashes = {g[0] for g in base_dups} - # Filter out allowed standard patterns that are intentionally repeated - new_dups = [ - g for g in cur_dups - if g[0] not in base_dup_hashes and g[0] not in ALLOWED_HASHES - ] - # Also filter allowed hashes from pre_dups for reporting - pre_dups = [g for g in cur_dups if g[0] in base_dup_hashes and g[0] not in ALLOWED_HASHES] + new_dups = [g for g in cur_dups if g[0] not in base_dup_hashes] + pre_dups = [g for g in cur_dups if g[0] in base_dup_hashes] # Report pre-existing as info if pre_ap or pre_dups: diff --git a/AGENTS.md b/AGENTS.md index 7fcca01..299ff45 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,9 +9,6 @@ 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. -Each agent has a `.profile` repository on Forgejo that stores lessons learned -from prior sessions, providing continuous improvement across runs. - > **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. @@ -42,6 +39,9 @@ disinto-ops/ (ops repo — {project}-ops) │ ├── approved/ approved vault items │ ├── fired/ executed vault items │ └── rejected/ rejected vault items +├── journal/ +│ ├── planner/ daily planning logs +│ └── supervisor/ operational health logs ├── knowledge/ shared agent knowledge + best practices ├── evidence/ engagement data, experiment results ├── portfolio.md addressables + observables @@ -49,35 +49,6 @@ disinto-ops/ (ops repo — {project}-ops) └── 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. - -## Agent .profile Model - -Each agent has a `.profile` repository on Forgejo that stores: -- `formula.toml` — agent-specific formula (optional, falls back to `formulas/.toml`) -- `knowledge/lessons-learned.md` — distilled lessons from journal entries -- `journal/` — session reflection entries (archived after digestion) - -### How it works - -1. **Pre-session:** The agent calls `formula_prepare_profile_context()` which: - - Resolves the agent's Forgejo identity from their token - - Clones/pulls the `.profile` repo to a local cache - - Loads `knowledge/lessons-learned.md` into `LESSONS_CONTEXT` for prompt injection - - Automatically digests journals if >10 undigested entries exist - -2. **Prompt injection:** Lessons are injected into the agent prompt: - ``` - ## Lessons learned (from .profile/knowledge/lessons-learned.md) - - ``` - -3. **Post-session:** The agent calls `profile_write_journal` which: - - Generates a reflection entry about the session - - Writes it to `journal/issue-{N}.md` - - Commits and pushes to the `.profile` repo - - Journals are archived after being digested into lessons-learned.md - > **Terminology note:** "Formulas" in this repo are TOML issue templates in `formulas/` that > orchestrate multi-step agent tasks (e.g., `run-gardener.toml`, `run-planner.toml`). This is > distinct from "processes" described in `docs/EVIDENCE-ARCHITECTURE.md`, which are measurement diff --git a/bin/disinto b/bin/disinto index 2e39c50..652e42d 100755 --- a/bin/disinto +++ b/bin/disinto @@ -894,6 +894,8 @@ setup_ops_repo() { mkdir -p "${ops_root}/vault/approved" mkdir -p "${ops_root}/vault/fired" mkdir -p "${ops_root}/vault/rejected" + mkdir -p "${ops_root}/journal/planner" + mkdir -p "${ops_root}/journal/supervisor" mkdir -p "${ops_root}/knowledge" mkdir -p "${ops_root}/evidence/engagement" @@ -912,6 +914,9 @@ ${ops_name}/ │ ├── approved/ # approved vault items │ ├── fired/ # executed vault items │ └── rejected/ # rejected vault items +├── journal/ +│ ├── planner/ # daily planning logs +│ └── supervisor/ # operational health logs ├── knowledge/ # shared agent knowledge and best practices ├── evidence/ # engagement data, experiment results ├── portfolio.md # addressables + observables @@ -919,8 +924,6 @@ ${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/gardener/gardener-run.sh b/gardener/gardener-run.sh index 62e9eb1..31aa8c0 100755 --- a/gardener/gardener-run.sh +++ b/gardener/gardener-run.sh @@ -64,19 +64,10 @@ 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_or_profile "gardener" "$FACTORY_ROOT/formulas/run-gardener.toml" || exit 1 +load_formula "$FACTORY_ROOT/formulas/run-gardener.toml" 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") @@ -114,7 +105,7 @@ 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}$(formula_lessons_block) +${CONTEXT_BLOCK} ${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} } ## Result file @@ -343,8 +334,5 @@ 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/formula-session.sh b/lib/formula-session.sh index e6c6aae..1675ea5 100644 --- a/lib/formula-session.sh +++ b/lib/formula-session.sh @@ -13,7 +13,6 @@ # build_prompt_footer [EXTRA_API] — sets PROMPT_FOOTER (API ref + env + phase) # run_formula_and_monitor AGENT [TIMEOUT] [CALLBACK] — session start, inject, monitor, log # formula_phase_callback PHASE — standard crash-recovery callback -# formula_prepare_profile_context — load lessons from .profile repo (pre-session) # # Requires: lib/agent-session.sh sourced first (for create_agent_session, # agent_kill_session, agent_inject_into_session). @@ -351,28 +350,6 @@ ${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:-}" -} - -# formula_lessons_block -# Returns a formatted lessons block for prompt injection. -# Usage: LESSONS_BLOCK=$(formula_lessons_block) -# Expects: LESSONS_INJECTION to be set by formula_prepare_profile_context. -# Returns: formatted block or empty string. -formula_lessons_block() { - if [ -n "${LESSONS_INJECTION:-}" ]; then - printf '\n## Lessons learned (from .profile/knowledge/lessons-learned.md)\n%s' "$LESSONS_INJECTION" - fi -} - # 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 663703c..31f5588 100755 --- a/planner/planner-run.sh +++ b/planner/planner-run.sh @@ -45,6 +45,12 @@ 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" @@ -52,14 +58,8 @@ 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_or_profile "planner" "$FACTORY_ROOT/formulas/run-planner.toml" || exit 1 +load_formula "$FACTORY_ROOT/formulas/run-planner.toml" build_context_block VISION.md AGENTS.md ops:RESOURCES.md ops:prerequisites.md # ── Build structural analysis graph ────────────────────────────────────── @@ -78,8 +78,9 @@ $(cat "$MEMORY_FILE") " fi -# ── Prepare .profile context (lessons injection) ───────────────────────── -formula_prepare_profile_context +# ── Load lessons from .profile repo (pre-session) ──────────────────────── +profile_load_lessons || true +LESSONS_INJECTION="${LESSONS_CONTEXT:-}" # ── Read scratch file (compaction survival) ─────────────────────────────── SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") @@ -95,7 +96,11 @@ build_sdk_prompt_footer " PROMPT="You are the strategic planner for ${FORGE_REPO}. Work through the formula below. ## Project context -${CONTEXT_BLOCK}${MEMORY_BLOCK}$(formula_lessons_block) +${CONTEXT_BLOCK}${MEMORY_BLOCK} +${LESSONS_INJECTION:+## Lessons learned +${LESSONS_INJECTION} + +} ${GRAPH_SECTION} ${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} } diff --git a/predictor/predictor-run.sh b/predictor/predictor-run.sh index 266829c..fb9bf51 100755 --- a/predictor/predictor-run.sh +++ b/predictor/predictor-run.sh @@ -53,22 +53,13 @@ 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_or_profile "predictor" "$FACTORY_ROOT/formulas/run-predictor.toml" || exit 1 +load_formula "$FACTORY_ROOT/formulas/run-predictor.toml" 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") @@ -91,10 +82,9 @@ 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}$(formula_lessons_block) +${CONTEXT_BLOCK} ${GRAPH_SECTION} -${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} -} +${SCRATCH_CONTEXT} ## Formula ${FORMULA_CONTENT} @@ -108,8 +98,5 @@ 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-pr.sh b/review/review-pr.sh index 8a9a29d..0ae0fdb 100755 --- a/review/review-pr.sh +++ b/review/review-pr.sh @@ -27,8 +27,6 @@ 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 @@ -58,14 +56,6 @@ 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 # ============================================================================= @@ -190,11 +180,6 @@ 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 # ============================================================================= @@ -208,7 +193,6 @@ 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" - formula_lessons_block 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' @@ -314,7 +298,4 @@ 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 4ba6ec3..129666f 100755 --- a/supervisor/supervisor-run.sh +++ b/supervisor/supervisor-run.sh @@ -58,12 +58,6 @@ 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="" @@ -74,12 +68,9 @@ else fi # ── Load formula + context ─────────────────────────────────────────────── -load_formula_or_profile "supervisor" "$FACTORY_ROOT/formulas/run-supervisor.toml" || exit 1 +load_formula "$FACTORY_ROOT/formulas/run-supervisor.toml" 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") @@ -100,7 +91,7 @@ Fix what you can. File vault items for what you cannot. Do NOT ask permission ${PREFLIGHT_OUTPUT} ## Project context -${CONTEXT_BLOCK}$(formula_lessons_block) +${CONTEXT_BLOCK} ${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} } Priority order: P0 memory > P1 disk > P2 stopped > P3 degraded > P4 housekeeping @@ -114,8 +105,5 @@ ${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 ---"