- Add sops --verify to validate GCM ciphertext tag before decryption
- Treat all decryption failures as fatal errors (exit 1) instead of warnings
- Added integrity check comment for clarity
- Ensures tampered .env.enc files are rejected before use
Add URL validation helper to prevent URL injection attacks in API calls.
- Added validate_url() helper in lib/env.sh to validate URL format
- Added validation to forge_api() to prevent URL injection
- Added validation to woodpecker_api() to prevent URL injection
- Added validation to ci-debug.sh api() function
- All URLs are already properly quoted with "${VAR}/..." patterns
- This adds defense-in-depth by validating URL variables before use
Same pattern as FORGE_URL — the llama container sets FORGE_TOKEN
to dev-qwen token via FORGE_TOKEN_OVERRIDE, but env.sh sources .env
which clobbers it back to dev-bot. All PRs and issue claims show
dev-bot instead of dev-qwen, and assignee locking fails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Some models (especially local) emit end_turn prematurely. After
agent_run completes, check if code was pushed. If not, resume the
session with a nudge: "You stopped but did not push. Complete the
implementation, commit, and push."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Save agent_run output to agent-run-last.json. On no_push, log the
result text, turn count, and cost. Save full output to
no-push-{issue}-{ts}.json for later analysis.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
source .env clobbers FORGE_URL from http://forgejo:3000 (Docker DNS)
to http://localhost:3000 (unreachable inside container). Save and
restore FORGE_URL around the source.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>