Root cause: env.sh skipped sourcing .env when DISINTO_CONTAINER=1,
assuming compose injects all env vars. But cron jobs do NOT inherit
compose env vars — they only get crontab-level variables.
Result: FORGE_TOKEN was empty in every cron poll. API calls returned
nothing, polls silently found "no open PRs" and exited.
Fix: always source .env regardless of DISINTO_CONTAINER. Compose env
vars (FORGE_URL) are set in the crontab env and take precedence.
Entrypoint also adds FORGE_URL to crontab env vars.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move SID_FILE recovery into agent_recover_session() in lib/agent-sdk.sh
to eliminate remaining duplicate block between dev-agent.sh and
review-pr.sh.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract agent_run() into shared lib/agent-sdk.sh to eliminate code
duplication between dev-agent.sh and review-pr.sh (CI dedup check).
Rewrite review-pr.sh from tmux-based agent-session.sh to synchronous
claude -p invocations via shared agent-sdk.sh, matching the SDK pattern
from dev-agent.sh (#798).
Key changes:
- Create lib/agent-sdk.sh with shared agent_run() function
- Both dev-agent.sh and review-pr.sh now source lib/agent-sdk.sh
instead of defining agent_run() inline
- Replace agent-session.sh (tmux + monitor_phase_loop) with agent_run()
- Add .sid file for session continuity: re-reviews resume the original
session via --resume, so Claude remembers its prior review
- Use worktree.sh for worktree cleanup
- Remove phase file signaling — completion is automatic when claude -p
returns
- Keep all review business logic unchanged
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New reusable library with clean function boundaries for the PR lifecycle:
- pr_create, pr_find_by_branch — PR creation and lookup
- pr_poll_ci — poll CI with infra vs code failure classification
- pr_poll_review — poll for review verdict (bot comments + formal reviews)
- pr_merge, pr_is_merged — merge with 405 handling and race detection
- pr_walk_to_merge — full orchestration loop (CI → review → merge)
- build_phase_protocol_prompt — git push instructions for agent prompts
The pr_walk_to_merge function uses agent_run() which callers must define
(guarded stub provided). This bridges to the synchronous SDK architecture
where the orchestrator bash loop IS the state machine — no phase files.
Extracted from: dev/phase-handler.sh, dev/dev-poll.sh, lib/ci-helpers.sh
Smoke test updated to include the new library.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When DISINTO_CONTAINER=1, load-project.sh now skips overriding env vars
that are already set by docker-compose (FORGE_URL, PROJECT_REPO_ROOT,
OPS_REPO_ROOT, etc.). This prevents the TOML's host-perspective values
(localhost, /home/johba/…) from clobbering the correct container values
(forgejo:3000, /home/agent/…).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add DISINTO_LOG_DIR to lib/env.sh: points to $HOME/data/logs inside the
container (writable volume) and $FACTORY_ROOT on the host (existing behavior).
Update all agent scripts to write logs, CI fix tracker, metrics, and vault
locks to DISINTO_LOG_DIR instead of FACTORY_ROOT. This keeps the factory
mount read-only while ensuring all writable state lands on the persistent
data volume.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes#757
## Changes
Separate operations from code into {project}-ops repo pattern. Added OPS_REPO_ROOT infrastructure (env.sh, load-project.sh, formula-session.sh with ensure_ops_repo helper). Updated all 8 agent scripts and 7 formulas to read/write vault items, journals, evidence, prerequisites, RESOURCES.md, and knowledge from the ops repo. Added setup_ops_repo() to disinto init for automatic ops repo creation and seeding. Removed migrated data from code repo (vault data dirs, planner journal/memory/prerequisites, supervisor journal/best-practices, evidence, RESOURCES.md). Updated all documentation. 55 files changed, ShellCheck clean, all 38 phase tests pass.
Co-authored-by: openhands <openhands@all-hands.dev>
Reviewed-on: https://codeberg.org/johba/disinto/pulls/767
Reviewed-by: Disinto_bot <disinto_bot@noreply.codeberg.org>
Each agent now gets its own Forgejo account (dev-bot, review-bot,
planner-bot, gardener-bot, vault-bot, supervisor-bot, predictor-bot,
action-bot) with a dedicated API token. This enables:
- Audit trail: every forge action attributable to a specific agent
- Permission boundaries: agents act under their own identity
- Vault authorization model: vault-bot comments = proof of approval
Changes:
- bin/disinto: setup_forge() creates all 8 bot accounts during init,
stores per-agent tokens (FORGE_*_TOKEN) in .env, adds all bots as
repo collaborators
- lib/env.sh: exports per-agent token vars with fallback to FORGE_TOKEN
for backwards compat; sets FORGE_BOT_USERNAMES default to all 8 bots
- Agent scripts: each agent overrides FORGE_TOKEN with its per-agent
token after sourcing env.sh (gardener, planner, supervisor, predictor,
vault, action)
- .env.example: documents all per-agent token fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fixes:
- vault/vault-agent.sh: Update comment and prompt to use PHASE:escalate
instead of "send a Matrix message"
- dev/dev-agent.sh: Update escalation instruction from "reply via Matrix"
to "respond via the forge"
- dev/phase-handler.sh: Update build_phase_protocol_prompt() escalation
text from "reply via Matrix" to "respond via the forge"
Minor fixes:
- bin/disinto: Remove duplicate comment line in docker-compose header
- README.md: Update vault table row from "via Matrix" to "via vault/forge"
- BOOTSTRAP.md: Remove "Matrix credentials" from TOML description
- lib/AGENTS.md: Remove "callers may follow up via Matrix" from
formula_phase_callback description
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The memory guard block in action-poll.sh and dev-poll.sh became
identical after removing matrix_send calls, triggering the
duplicate-detection CI check. Extract to a shared function in
lib/env.sh (already sourced by both scripts).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove all Matrix/Dendrite infrastructure:
- Delete lib/matrix_listener.sh (long-poll daemon), lib/matrix_listener.service
(systemd unit), lib/hooks/on-stop-matrix.sh (response streaming hook)
- Remove matrix_send() and matrix_send_ctx() from lib/env.sh
- Remove MATRIX_HOMESERVER auto-detection, MATRIX_THREAD_MAP from lib/env.sh
- Remove [matrix] section parsing from lib/load-project.sh
- Remove Matrix hook installation from lib/agent-session.sh
- Remove notify/notify_ctx helpers and Matrix thread tracking from
dev/dev-agent.sh and action/action-agent.sh
- Remove all matrix_send calls from dev-poll.sh, phase-handler.sh,
action-poll.sh, vault-poll.sh, vault-fire.sh, vault-reject.sh,
review-poll.sh, review-pr.sh, supervisor-poll.sh, formula-session.sh
- Remove Matrix listener startup from docker/agents/entrypoint.sh
- Remove append_dendrite_compose() and setup_matrix() from bin/disinto
- Remove --matrix flag from disinto init
- Clean Matrix references from .env.example, projects/*.toml.example,
formulas/*.toml, AGENTS.md, BOOTSTRAP.md, README.md, RESOURCES.md,
PHASE-PROTOCOL.md, and all agent AGENTS.md/PROMPT.md files
Status visibility now via Codeberg PR/issue activity. Human interaction
via vault items through forge. Proactive alerts via OpenClaw heartbeats.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On crash (PHASE:crashed or non-zero exit), preserve the worktree and log
its location instead of destroying it unconditionally. Successful sessions
still clean up normally. Supervisor runs housekeeping to remove stale
crashed worktrees older than 24h.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructure session.lock from command-wrapper flock to fd-based flock so
the lock can be released when Claude is idle and re-acquired before
injecting the next prompt.
- agent-session.sh: add session_lock_acquire/release helpers, open fd in
create_agent_session instead of wrapping claude with flock, auto-acquire
in agent_inject_into_session before injecting
- phase-handler.sh: call session_lock_release at start of awaiting_ci and
awaiting_review handlers (Claude is idle during CI polling / review wait)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## What
Removes the exec agent (PR #697). Its functionality is replaced by:
1. **OpenClaw skill** — teaches any OpenClaw instance to be the factory's face
2. **Vault API** — structured interface for proposals, approvals, rejections
The exec agent was rebuilding OpenClaw in bash. Every piece has a native OpenClaw equivalent:
- CHARACTER.md → SOUL.md
- exec/MEMORY.md → MEMORY.md
- exec-session.sh → session management
- exec-briefing.sh → heartbeats/cron
- Matrix dispatch → channel plugins
## Why
Prudence isn't a separate agent. She's what OpenClaw becomes when it has the disinto skill. One LLM, one vault API, no LLM-to-LLM.
## Related
- #721 — remove escalation, route through vault
- #709 — skill registry research
- #466 — example project (vault should have handled this, not escalation)
Co-authored-by: openhands <openhands@all-hands.dev>
Reviewed-on: https://codeberg.org/johba/disinto/pulls/722
Non-blocking flock (-n) silently drops work items when concurrent agents
race for the lock. Switch to -w 300 so sessions queue up to 5 minutes,
and single-quote the lock path to handle spaces in $HOME.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make ~/.claude volume mount read-write (was :ro) so containers can
write back refreshed OAuth tokens
- Wrap Claude CLI in flock(1) inside tmux sessions using
~/.claude/session.lock — prevents concurrent token refresh races
across agents sharing the same credentials
- Add ANTHROPIC_API_KEY detection in entrypoint.sh: when set, skips
OAuth entirely (no rotation issues, metered billing)
- Log active auth method (API key vs OAuth vs missing) at container
startup for easier 401 debugging
- Document 'claude auth login' requirement in disinto init output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two wins from the dev-agent's implementation:
1. exec-briefing.sh: rewritten to just call exec-inject.sh with a
briefing prompt (57 lines, down from 154). No more duplicated
compass/character/context loading.
2. exec-inject.sh: response capture now uses agent_wait_for_claude_ready
+ pane line diff instead of custom EXEC-RESPONSE-START/END markers.
Claude just responds naturally — no special output format needed.
Also: matrix listener uses nohup for robustness and validates TOML
path before passing to exec-inject.sh.
New agent: exec — message-driven executive assistant reachable via Matrix.
Unlike cron-driven agents, the exec activates on demand when the executive
sends a message, maintains persistent conversation context, and has a
distinct character defined in CHARACTER.md.
The CHARACTER.md defines the exec as an animal of light — born from data,
dedicated to bringing more light into the world. But it deliberately
refuses to define what light and darkness are, forcing deliberation
from first principles every time (cat questions | grep knowledge).
Components:
- exec-session.sh: spawn/reattach persistent Claude tmux session
- exec-inject.sh: message injection + response capture + Matrix posting
- exec-briefing.sh: optional daily morning briefing (cron)
- CHARACTER.md: personality and moral compass
- PROMPT.md: system prompt template reference
- MEMORY.md: persistent memory across sessions (seed)
Integration:
- Matrix listener: new exec dispatch case (spawn on demand)
- Root AGENTS.md: updated agent count (8→9), table, directory layout
- Graph analysis available on demand (not injected by default)
Move graph report generation into build_graph_section() in
lib/formula-session.sh. Both planner-run.sh and predictor-run.sh
now call the shared helper instead of duplicating the same 11 lines.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- tea_relabel: use `tea issues edit` instead of `tea issues labels`
(the latter is the list subcommand and ignores --labels)
- Dockerfile: verify tea binary sha256 after download
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add lib/tea-helpers.sh with tea_file_issue, tea_relabel, tea_comment,
tea_close — thin wrappers preserving secret scanning on write ops
- Add tea 0.9.2 binary to docker/agents/Dockerfile
- Configure tea login in docker/agents/entrypoint.sh from FORGE_TOKEN/FORGE_URL
- Derive TEA_LOGIN in lib/env.sh (codeberg vs local forgejo)
- Source tea-helpers.sh conditionally when tea binary is available
- Migrate predictor formula from inline curl to tea CLI commands
- Register tea-helpers.sh in smoke test function resolution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Skip sourcing .env/.env.enc when DISINTO_CONTAINER=1 since compose
already injects the correct env vars via env_file + environment
overrides. Re-sourcing .env was clobbering compose-level values
like FORGE_URL=http://forgejo:3000 with the localhost default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Critical: setup_matrix now runs after docker compose up -d so Dendrite
is actually running when provisioning is attempted
- Minor: replace sed with Python for .env credential writes to avoid
delimiter collisions with opaque Matrix access tokens
- Info: update matrix_listener.sh header to mention container mode
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The directed graph has mixed edge directions along the path from
agent/formula to objective (agent→formula→label←issue→objective),
so descendants() never reaches objectives. Use undirected connected
components for reachability instead. Also fix closed-issues query
to use forge_get (bounded at 50) instead of forge_get_all (unbounded).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add lib/build-graph.py that builds a NetworkX DiGraph from project docs
and forge API, runs structural analyses (orphans, cycles, disconnected
clusters, thin objectives, bottlenecks), and outputs a JSON report.
Predictor and reviewer agents now call build-graph.py before launching
their Claude sessions and inject the report as context.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add MATRIX_MENTION_USER config to project TOML and include a Matrix
mention pill in escalation notify_ctx calls so humans get notified
even in muted rooms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add docker-compose.yml generation, agent Dockerfile, and new CLI
commands (up/down/logs/shell) so the full stack runs containerized.
The --bare flag preserves the current bare-metal setup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add fire-and-forget mirror push support so merges to the primary branch
are automatically pushed to configured public mirrors (GitHub, Codeberg,
etc.). Mirror failures are logged but never block the pipeline.
- lib/mirrors.sh: new shared mirror_push() helper
- lib/load-project.sh: parse [mirrors] TOML section into MIRROR_* env vars
- dev/phase-handler.sh: call mirror_push after do_merge() success
- dev/dev-poll.sh: call mirror_push after try_direct_merge() success
- gardener/gardener-run.sh: call mirror_push after _gardener_merge() success
- bin/disinto: set up mirror remotes during init, add commented mirrors to
generated TOML
- projects/*.toml.example: show [mirrors] section (commented out)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Warn on stderr when .env.enc decryption fails instead of silent || true
- Guard ensure_age_key() against empty age-keygen -y output
- Fix stale comment on write_secrets_encrypted()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ci_commit_status() and ci_pipeline_number() helpers to
lib/ci-helpers.sh that query Woodpecker directly with a forge API
fallback. Replace all 12 inline forge commit status calls across 6
files with the new helpers.
Add setup_woodpecker() to bin/disinto init that creates a Forgejo
OAuth2 app for Woodpecker and activates the repo.
Document manual Woodpecker+Forgejo setup in BOOTSTRAP.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>