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>
Split secrets into two SOPS-encrypted files:
- .env.enc for agent secrets (FORGE_TOKEN, CLAUDE_API_KEY, etc.)
- .env.vault.enc for vault secrets (GITHUB_TOKEN, deploy keys, etc.)
Add ephemeral vault-runner container (profiles: ["vault"]) that receives
only vault secrets at runtime. Agents never see vault secrets; vault-runner
never sees agent secrets.
Key changes:
- bin/disinto: vault-run subcommand, dual-file secrets management,
vault-runner service in compose template
- vault/vault-fire.sh: delegates action execution to vault-runner
container via disinto vault-run (bare-metal fallback preserved)
- vault/vault-poll.sh: new phase 5 detects vault-bot authorized
comments on issues with action label
- vault/vault-run-action.sh: entrypoint for ephemeral container,
dispatches to action handlers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
## 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
- 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>
Merge main into feat/exec-agent to pick up ba1ab6e which added
matrix_send_ctx to lib/env.sh and action/action-agent.sh. Without
this merge, CI smoke test fails on the PR merge commit.
Re-applied exec changes on top of main:
- .env.example, AGENTS.md, bin/disinto, lib/matrix_listener.sh
- .woodpecker/agent-smoke.sh: exec scripts added to checks
disinto init now silently downloads the compass from
https://disinto.ai/compass.md to ~/.disinto/compass.md, sets
EXEC_COMPASS in .env, and activates the exec agent. No prompts,
no friction — the compass is public philosophy, not a secret.
Once on disk, the factory cannot modify it. Only the executive
can edit ~/.disinto/compass.md directly.
- site/compass.md: compass hosted on disinto.ai (Codeberg Pages)
- bin/disinto: init downloads compass, sets env var, activates exec
- exec-session.sh, exec-briefing.sh: fallback to ~/.disinto/compass.md
- .env.example: updated comment to reflect auto-provisioning
The smoke test clones from an empty Forgejo repo, so there are no
refs to push. Skip the push and verification gracefully when HEAD
does not resolve.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove Dendrite from the default docker-compose.yml generated by
`disinto init`. Most deployments don't need Matrix, so Dendrite is now
opt-in via the `--matrix` flag.
When `--matrix` is passed:
- A minimal dendrite.yaml is generated at docker/dendrite/dendrite.yaml
- The Dendrite service is appended to docker-compose.yml with the
config file bind-mounted
- setup_matrix() provisions the bot user and coordination room
Without `--matrix`, no Dendrite container is started and fresh inits
no longer crash-loop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
POST /api/v1/users/{username}/tokens requires basic auth (reqBasicOrRevProxyAuth)
in Forgejo 11.x. The previous code used admin token auth which returns 401.
Fix: authenticate as the bot user with -u "${bot_user}:${bot_pass}" instead of
-H "Authorization: token ${admin_token}". The bot_pass is available in scope
from the user creation step.
Bug caught by the new smoke-init end-to-end test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split setup_woodpecker() into create_woodpecker_oauth() (pre-compose) and
activate_woodpecker_repo() (post-compose) so OAuth2 creds are in .env before
Woodpecker starts, and repo activation happens after the stack is up.
- Add ports: ["8000:8000"] to Woodpecker service in generate_compose()
- Fix .env var names: WP_FORGEJO_CLIENT/SECRET to match compose references
- Reorder disinto_init(): OAuth2 creation before compose up, repo activation after
- activate_woodpecker_repo() polls Woodpecker readiness with retry loop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add database readiness check (retry loop on `forgejo admin user list`) after
API becomes reachable to avoid the race where HTTP is up but SQLite isn't
accepting writes yet.
Remove `2>/dev/null || true` from user creation commands so failures are
logged with the actual error message. Verify each user exists via API after
creation. Fail init with a clear error if admin token, bot user creation,
or bot token creation fails — instead of silently writing an incomplete .env.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the claude-auth named Docker volume with bind mounts to the host
user's ~/.claude/ and ~/.claude.json. The named volume creates an empty
directory, so the agents container cannot authenticate with Claude CLI.
Bind-mounting from ${HOME} ensures the container picks up existing
credentials without manual intervention.
Closes codeberg.org/johba/disinto/issues/633
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>
In compose mode, skip host cron installation entirely since the agents
container runs cron internally via entrypoint.sh. In bare mode, check
for crontab before attempting to install entries and produce a clear
error with install instructions if missing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove curl|sh Claude CLI download from Dockerfile (no internet needed)
- Mount host Claude CLI binary into container via docker-compose volume
- generate_compose() resolves host claude path at init time
- entrypoint.sh fails fast with clear error if claude CLI is missing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add -u git to docker exec and docker compose exec calls in _forgejo_exec()
so Forgejo admin commands run as the git user instead of root.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add security_opt: [apparmor=unconfined] to all three compose services
(forgejo, woodpecker, agents) in generate_compose(). This prevents
su-exec from entering an infinite CPU loop when Docker runs inside an
LXD container whose default AppArmor profile blocks setuid/execve.
Harmless on bare-metal Docker hosts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add FORGEJO__security__INSTALL_LOCK: "true" to the forgejo service
environment in generate_compose(). Without this, Forgejo starts in
install-wizard mode and the API returns 404 for all endpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add EXIT trap in disinto_up() so the plaintext .env is removed even if
docker compose up fails. Previously set -euo pipefail would abort
before the cleanup block, leaving secrets on disk.
Replace the silent || true in the Dockerfile with an explicit
claude --version check so the build fails visibly if the CLI cannot
be installed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The entrypoint installed a crontab but never started a cron daemon,
leaving the container idle. Fix by running as root in the entrypoint
(cron requires it), installing the crontab for the agent user via
`crontab -u agent`, and starting cron in the foreground with `cron -f`.
Remove `USER agent` from the Dockerfile and `user: "1000:1000"` from
the compose template accordingly — cron jobs still execute as UID 1000.
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>
Replace /api/v1/user with /api/v1/repos/{owner}/{repo} in three places:
- preflight_check() auth verification
- setup_codeberg_auth() --token flag verification
- setup_codeberg_auth() interactive flow verification
The repo endpoint only requires repo-level access, which matches the
scopes disinto actually needs (write:issue, write:repository). Tokens
without read:user scope now pass verification correctly.
Also use generic "token" as netrc login since the username is no longer
retrieved from the API (git operations authenticate via the token, not
the login field).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add interactive Codeberg auth setup to `disinto init`:
- Guide user through token creation with URL and required scopes
- Save token to ~/.netrc with correct permissions (600)
- Verify token via API call before proceeding
- Support --token flag for non-interactive use
- Backwards compatible: existing CODEBERG_TOKEN / .netrc still work
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add --netrc flag to curl when CODEBERG_TOKEN is unset so ~/.netrc
auth users don't get false-positive API failures
- Check claude auth status exit code separately; only skip the check
when the subcommand is unrecognized (old claude version), otherwise
treat failures as auth errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace validate_env() with preflight_check() that verifies all factory
requirements before init proceeds:
- Required tools: claude, tmux, git, jq, python3, curl (hard errors)
- Claude Code authentication via claude auth status
- Codeberg auth: CODEBERG_TOKEN or ~/.netrc, verified with API call
- Codeberg SSH access: verified with ssh -T git@codeberg.org
- Optional: docker (warn only)
- Clear error messages with install hints for each missing tool
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- When --yes is passed with a differing --repo-root, auto-apply the TOML
update instead of silently skipping. Prevents stale repo_root in TOML.
- Replace sed with python3+re for updating repo_root to avoid delimiter
injection from user-supplied paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When projects/<name>.toml already exists (e.g. committed from another box),
skip TOML generation and continue with remaining setup steps (clone repo,
create labels, install cron). Reads repo_root and branch from the existing
TOML. If --repo-root flag differs from TOML value, prompts to update it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add bin/disinto CLI entry point with two subcommands:
- `disinto init <repo-url>`: bootstraps a new project by cloning the
repo, generating a projects/*.toml config, creating Codeberg labels
(backlog, in-progress, blocked, tech-debt, underspecified, vision,
action), generating a VISION.md template, and installing cron jobs
for dev-poll, review-poll, and gardener.
- `disinto status`: shows active sessions, backlog depth, and open PR
count for all configured projects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>