voku / agent-loop
Unified Composer CLI for the governed coding-agent loop: edit orchestration, board, session, recall, review, learning, memory review, and verification.
Requires
- php: ^8.3
- helgesverre/toon: ^3.1
- voku/agent-kanban: ^0.3.1
- voku/agent-learning: ^0.13.0
- voku/agent-map: ^0.8.1
- voku/agent-recall-compiler: ^0.13.1
- voku/agent-session: ^0.6.1
- voku/itp-context: ^0.3.0
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
Suggests
- voku/housekeeping: Standalone cron runner to auto-run agent-loop maintenance tasks against a target repository.
This package is auto-updated.
Last update: 2026-08-22 00:24:12 UTC
README
A governed coding-agent workflow for PHP repositories.
voku/agent-loop gives coding agents a controlled loop instead of more hidden
autonomy:
find the right code
-> approve the task scope
-> make the smallest correct change
-> validate with evidence
-> review blind spots
-> decide what should be learned
-> close only when the gates pass
It is local-first, auditable through files and Git, independent of a specific
LLM provider, and built around focused agent-* packages rather than one large
agent platform.
Installation
composer require --dev voku/agent-loop
Requirements:
- PHP 8.3 or newer
- Composer
The package exposes:
vendor/bin/agent-loop
Start a repository
Create the minimum local workflow infrastructure without inventing a board identity or fake task:
vendor/bin/agent-loop init scaffold
For a real board, provide its project prefix explicitly:
vendor/bin/agent-loop init scaffold --prefix=PROJECT
For the tutorial flow, opt into the example board and task:
vendor/bin/agent-loop init scaffold --demo vendor/bin/agent-loop board card show DEMO-1
See Your first governed task for the complete first run.
Project-scoped review policy
.agent-loop/init.json can register Git-tracked recall document manifests:
{
"version": 1,
"recall": {
"document_manifests": ["docs/agents/recall-documents.json"]
}
}
Each manifest uses the existing recall document schema. Empty scope applies a
document project-wide; path prefixes and tags narrow it to a directory, module,
or task taxonomy. Governed approval passes these manifests to Recall
automatically. Missing, malformed, absolute, or project-escaping configured
paths fail visibly instead of silently dropping review policy.
This repository registers its own pre-1.0 compatibility decision in
docs/agents/recall-documents.json. The
reusable breaking-change-review L2 recipe can turn the applicable policy and
current task evidence into a project-specific review without treating backward
compatibility or breakage as a universal default.
First-party agent discipline
agent-loop ships its own reviewed agent behavior. It does not download RTK,
Caveman, Ponytail, a plugin marketplace package, or a remote installer.
Preview and install the assets already present in the Composer package, then use the host-status front door until no repository-owned integration action remains:
vendor/bin/agent-loop init install-plan --profile=wsl2 --agent=codex vendor/bin/agent-loop init install-assets --agent=all --dry-run vendor/bin/agent-loop init install-assets --agent=all vendor/bin/agent-loop init host-status --format=json vendor/bin/agent-loop init doctor
host-status auto-detects a single visible Codex, Claude Code, OpenCode, Copilot,
or Gemini CLI runtime. Pass --agent=<host> when multiple runtimes are visible
or when using Antigravity, whose runtime is not auto-probed. It checks current
instructions, managed skill/subagent drift, and host-policy capability rather
than treating the existence of a manifest as proof that a projection is current.
The package-owned guidance separates three budgets:
- Human attention: progress, handoffs, and review findings stay concise and technically exact.
- Implementation complexity: stop at the first verified solution that fully satisfies the request.
- Context: use
agent-mapand recall to select bounded source reads.
Raw evidence is never compressed or rewritten. Source, full diffs, test output, static-analysis output, verification artifacts, redirected harness files, and decisive errors remain complete.
The first-party set adapts concrete mechanisms reviewed in Caveman and Ponytail:
| Asset | Purpose |
|---|---|
agent-loop-discipline |
Concise communication + minimal strict-PHP implementation ladder + evidence integrity |
agent-loop-investigate |
Read-only agent-map locator returning verified path/line/symbol/caller/test evidence |
agent-loop-surgical-edit |
Already-localized 1-2 file change; prefer deterministic agent-loop edit and refuse silent scope expansion |
agent-loop-code-review |
Concise correctness review of the complete raw diff |
agent-loop-simplify-review |
Diff-only review for deletion/reuse/stdlib/native/YAGNI opportunities |
agent-loop-simplify-audit |
Repo-wide simplify audit, prioritized through map/navigation evidence |
agent-loop-dogfood |
Compare observable baseline/candidate artifacts without invented savings |
The package also installs three dedicated roles where the client exposes a repository-local agent-role format:
investigator -> surgical builder -> code reviewer
The investigator locates and stops. The builder edits only already-understood small scope. The reviewer returns correctness findings only. Broader features stay in the normal governed workflow.
The same canonical role definitions are rendered for each supported client:
- Codex:
.codex/agents/*.toml, withname,description, anddeveloper_instructionsonly; - Claude Code:
.claude/agents/*.md; - OpenCode:
.opencode/agents/*.md, with the filename providing the agent name and nativedescription/mode: subagentfrontmatter; - Copilot:
.github/agents/*.agent.md; - Gemini CLI:
.gemini/agents/*.md; - Antigravity:
.agents/agents/*.md.
Model choice remains client/host policy. agent-loop does not pin a model,
reasoning level, or provider-specific economics into the role files.
Repository authority policy is a separate host capability, not a prerequisite
for portable host support. init sync-policy currently projects the narrow
remote-mutation boundary for Codex, Claude Code, and OpenCode:
- Codex:
.codex/rules/agent-loop.rulesusespromptforgit push, PR creation, and PR merge. Codex project trust remains an explicit host/user decision. - Claude Code:
.claude/settings.json#permissionsuses project-leveldenyfor those remote mutations. Auto Mode classifier configuration remains user/local/managed scoped and is reported as a runtime boundary instead of being falsely written as project configuration. - OpenCode:
opencode.json#permissionusesdenybecause OpenCode--automay auto-approveask, while explicit deny remains the hard boundary. - Copilot, Gemini CLI, Antigravity: portable instructions, skills, and agents are supported, but agent-loop does not claim a repository-native authority policy projector for them.
Existing project-owned policy is preserved. Conflicting owned rules fail closed
unless an explicit reviewed --force is used; comment-bearing opencode.jsonc
is reported as manual rather than rewritten destructively.
Codex additionally receives package-owned PHP hooks when install-assets is run
with the explicit --with-hooks opt-in:
SessionStartinjects the discipline.SubagentStartpropagates it to spawned agents.PreToolUseleaves ordinary Bash commands unchanged.PreToolUsedenies configured unbounded.agent-loop/mapdump patterns and suggests bounded map commands.
Hooks are behavioral guardrails, not a correctness or security boundary. The
security property is simpler: install-assets installs only files already
shipped in the Composer package and never fetches remote agent code.
Host-owned hooks for Codex and Claude Code
A host repository keeps its own hook bundle - hooks.json plus the PHP scripts
it references - and syncs it into the client:
vendor/bin/agent-loop init validate --kind=hooks --agent=codex vendor/bin/agent-loop init sync-hooks --agent=codex vendor/bin/agent-loop init validate --kind=hooks --agent=claude vendor/bin/agent-loop init sync-hooks --agent=claude
Source roots default to docs/agents/codex-hooks and docs/agents/claude-hooks
and are overridable through .agent-loop/init.json (codex_hooks_root,
claude_hooks_root) or --hooks-root.
The two clients register hooks differently, and the sync follows each:
- Codex reads
.codex/hooks.json, so the bundle is copied as-is. - Claude Code reads the
hookskey of.claude/settings.json, so the sync merges that single key and writes every other setting back unchanged. The manifest entrysettings.json#hooksrecords that one key as managed, which is what makes a later removal or an--adopt-existingrun safe.CLAUDE_CONFIG_DIRoverrides the target directory.
Hook commands must call a script inside the client's own directory
(php .codex/hooks/<name>.php, php .claude/hooks/<name>.php); validation
rejects anything else so a bundle cannot point at an unmanaged path.
Architecture rules
src/Context/ArchitectureRules.php declares the intent a reader cannot recover
from the code: which class owns state paths, what Workflow may call, that
generated evidence is not approval, and that external tools stay optional. Each
rule names the check that catches the next violation, and every one of them was
violated at least once by a change that passed review — that is the bar for
adding another.
composer context:validate
vendor/bin/itp-context-export var/itp-context src --exclude=vendor --exclude=tests
vendor/bin/itp-context-query var/itp-context --text='state path'
composer ci runs the validation. voku/itp-context
is a real dependency because the rules implement its contract; it needs only
PHP 8.3+, which this package already requires.
Deterministic slop review
voku/slop-scan reports heuristic findings
about a candidate diff. It cannot share this package's Composer project — 0.1.4
requires Simple-PHP-Code-Parser ^0.21 against agent-map's ^0.22 — so it
runs from an isolated tool project that init sync-tools writes:
vendor/bin/agent-loop init sync-tools composer install --working-dir=tools/slop-scan composer review:slop
slop-scan.config.json disables the rules that are noise here and
slop-baseline.json records what existed when the gate went in, so CI fails on
what a change adds rather than on the repository's history. A heuristic
finding is an observation, not a verdict: promote one to a blocker only when you
can state the defect from the real source.
init tools reports where both tools were found, the same way it reports rg
and docker; an absent tool is information, not a broken setup. How the planes
are used, and what "helped" has to mean before a tool earns trust, is in
the real-issue acceptance model.
Package-owned Git hooks
post-merge and post-checkout keep the agent-map index in step with the working
tree after Git moved it. That logic is the same in every project, so it ships here:
vendor/bin/agent-loop init sync-githooks \ --hooks-dir=.githooks \ --commit-template=.gitmessage \ --container-service=php --container-image=my-php \ --container-workdir=/var/www/html --container-user=www-data
That installs post-merge, post-checkout, agent-map-refresh.sh, and the shared
lib/agent-loop-hooks.sh, renders the project-specific values into
lib/agent-loop-hooks.env, and sets core.hooksPath plus commit.template
(--skip-git-config leaves the repository configuration alone).
pre-commit and commit-msg are installed as well. They carry no policy: both call
agent-loop githooks, which reads .agent-loop/githooks.json:
{
"pre_commit": {
"file_patterns": ["*.php"],
"exclude_paths": ["/vendor/", "/archiv/"],
"batch_size": 500,
"checks": [
{"name": "lint", "type": "php-lint"},
{"name": "sniffer", "type": "phpcs", "standard": "build/phpcs.xml"},
{"name": "phpstan", "type": "phpstan", "config": "phpstan.neon", "level": 8},
{"name": "project rule", "type": "command", "command": "tools/custom-check.php {files}"}
]
},
"commit_msg": {
"header_pattern": "/^\\[(\\+|~|!|\\*)\\]:\\s+.+\\s+->\\s+.+$/",
"header_hint": "[SYMBOL]: \"scope\" -> <subject>",
"trivial_header_pattern": "/^\\[\\*\\]:/",
"required_section": "WHY",
"vague_phrases": ["cleanup", "minor fixes"],
"vague_word_threshold": 8
}
}
Check types render the standard tool call from the package - php-lint, phpcs,
phpcbf, php-cs-fixer, phpstan - so a project configures its rule set rather
than another wrapper script, and type: command covers everything else.
{files} is replaced with the current batch of staged files (appended when the
placeholder is absent). Without that config file both hooks are a no-op, so
installing them cannot break a repository that has not configured them yet.
Any other hook in the same directory - prepare-commit-msg, pre-push, whatever a
project already has - is never read, rewritten, or removed. Only the installed
entries enter the target manifest, so a later sync cleans up exactly what it created.
The hooks never fail a Git operation and never build an index that does not exist
yet: a missing index means nobody opted into the map, and a cold build is not
something a hook starts on someone's behalf. Opt out entirely with
AGENT_LOOP_SKIP_MAP_REFRESH=1.
Make targets for a host repository
Host repositories should not re-declare one Make wrapper per client. Include the shipped targets instead:
-include vendor/voku/agent-loop/make/agent-loop.mk
That provides agent_init_doctor, agent_init_status, agent_init_install_plan,
validate_agent_skills, validate_agent_subagents, validate_codex_hooks,
validate_claude_hooks, the per-client install_*_skills / install_*_agents /
install_*_hooks targets, and the aggregates install_agent_skills,
install_agent_subagents, install_agent_hooks, install_agent_assets.
A host with its own entrypoint (extra bootstrap, raised memory limit, container dispatch) overrides one variable and keeps every target:
AGENT_LOOP_BIN := php -d memory_limit=4G tools/agent-loop-entrypoint.php
AGENT_LOOP_CONFIG (default .agent-loop/init.json) and AGENT_LOOP_SYNC_FLAGS
(default --force) are overridable the same way. The asset content stays in the
host repository; only the commands live here.
--agent=all installs portable skills and dedicated subagent definitions for
Codex, Claude Code, OpenCode, Copilot, Gemini CLI, and Antigravity. Executable
Codex/Claude hooks remain an explicit --with-hooks opt-in; repository authority
policy is converged separately through init host-status / init sync-policy.
The exact upstream-to-agent-* mapping and what was deliberately not ported are
documented in THIRD_PARTY_NOTICES.md.
The implementation and failed iterations are documented in the dogfood report.
Exact target edit
For a method-scoped change, let agent-loop resolve the symbol, build or refresh
the semantic map, compile bounded recall, and prepare an auditable execution
bundle:
vendor/bin/agent-loop edit 'App\Service\UserService::save' -- \ 'Reject inactive users before persistence and adapt affected callers.'
The default stdout runner writes the bundle but launches no external agent.
Artifacts are stored under:
.agent-loop/edit/<task-id>/
For a deterministic one-for-one replacement inside one resolved PHP method:
vendor/bin/agent-loop edit 'Legacy\ResourceService::save' \ --runner=auto \ --replace-old='$legacyUser->regionId' \ --replace-new='$legacyUser->getCurrentRegionId()' -- \ 'Replace the deprecated region property exactly once.'
The mechanical runner requires exactly one match inside the target method,
checks map freshness immediately before writing, runs php -l, and reverts on
lint failure. It does not invoke a model.
Verify an execution bundle separately:
vendor/bin/agent-loop edit verify \ --bundle=.agent-loop/edit/<task-id> \ --run-commands
edit verify checks one concrete edit execution. agent-loop verify checks the
cross-package workflow state. They are intentionally different gates.
Governed task workflow
Plan the exact scope and validation contract:
vendor/bin/agent-loop workflow plan ABC-123 \ --by lars \ --file src/Foo.php \ --goal 'Implement the approved task.' \ --behavior-anchor 'request -> Foo service -> persisted state' \ --validation 'vendor/bin/phpunit tests/FooTest.php'
Approve that exact revision through a named human actor:
vendor/bin/agent-loop workflow approve ABC-123 --by lars
Build navigation state and render the bounded working context:
vendor/bin/agent-loop map build --paths=src,tests vendor/bin/agent-loop map query Foo vendor/bin/agent-loop map related Foo vendor/bin/agent-loop workflow context ABC-123 \ --max-lines 120 \ --max-bytes 12000
After implementation, record the exact validation result:
vendor/bin/agent-loop session validation record ABC-123 \
--contract-revision 1 \
--command 'vendor/bin/phpunit tests/FooTest.php' \
--status passed \
--exit-code 0 \
--by lars
When a deliberately minimal implementation has a known ceiling, record the
ceiling and an observable revisit trigger in agent-session instead of leaving a
tool-specific debt marker in product code.
Review and verify:
vendor/bin/agent-loop review blindspots ABC-123 vendor/bin/agent-loop review code ABC-123 vendor/bin/agent-loop verify --task-id=ABC-123 vendor/bin/agent-loop workflow report ABC-123 \ --changed-file src/Foo.php
Record the learning outcome and close only when every required gate passes:
vendor/bin/agent-loop workflow learn ABC-123 \ --status no_durable_learning \ --by lars \ --reason 'No reusable finding from this bounded task.' vendor/bin/agent-loop workflow close ABC-123 --status done vendor/bin/agent-loop workflow status ABC-123 --expect complete
A re-plan creates a new Contract revision. Approval and validation evidence from an older revision remain auditable but cannot satisfy the revised task.
agent-map: navigate before reading broadly
Use the generated semantic map to locate relevant source and callers:
vendor/bin/agent-loop map query EvidenceValidator vendor/bin/agent-loop map related EvidenceValidator vendor/bin/agent-loop map file src/EvidenceValidator.php vendor/bin/agent-loop map changed --base=main vendor/bin/agent-loop map stats
The generated .agent-loop/map files are disposable navigation state. Do not paste
php-symbols.json or search.sqlite into a prompt. Use map results to select the
smallest real source range, then inspect that source directly.
What each package owns
| Package | Responsibility |
|---|---|
voku/agent-kanban |
Git-native Markdown task board and optional external issue comparison |
voku/agent-session |
Per-task working memory, decisions, assumptions, checkpoints, simplification ceilings, and validation evidence |
voku/agent-map |
Compact PHP symbol maps and bounded source navigation |
voku/agent-recall-compiler |
Task-scoped recall, validation plans, and deterministic review prompts |
voku/agent-learning |
Findings, proposals, decision history, constraints, and reviewed guidance maintenance |
voku/agent-loop |
Unified CLI, edit orchestration, governed lifecycle gates, first-party agent assets, memory review, and repository setup |
agent-loop does not become a second store for board, session, map, recall, or
learning state. It orchestrates the focused packages and verifies their shared
contract.
CLI namespaces
edit exact target -> semantic map -> bounded recall -> execution bundle
board local Markdown task board
board:verify board-source-only verification
session per-task working memory and validation evidence
map compact PHP symbol navigation
recall task-scoped recall / L2 briefing compilation
review deterministic blind-spot and code-review prompts
learn findings, proposals, constraints, and guidance maintenance
workflow plan / approve / contract / context / status / manifest / report / reflect / learn / close
verify cross-package workflow consistency
memory read-only durable-memory promotion review
init diagnostics, offline asset installation, sync, and scaffolding
Use the command itself as the executable reference:
vendor/bin/agent-loop help vendor/bin/agent-loop <namespace> help
Boundaries
agent-loop deliberately does not:
- call an LLM by itself;
- auto-commit, auto-push, or auto-merge;
- silently approve code or durable learning;
- inject recall artifacts into an agent without the host doing so explicitly;
- replace tests, PHPStan, code review, or human approval;
- treat hooks as a correctness or security sandbox;
- rewrite durable memory files merely to shorten them;
- turn every transcript or observation into memory;
- invent per-repo token or line savings from an unbuilt counterfactual;
- hide source or verification evidence behind a lossy summary.
Findings are not durable memory. Generated map output is not source evidence. Model confidence is not validation. A green close requires the recorded gates, not an agent claiming that everything looks fine.
See Learning boundary and Lifecycle contract.
Repository-managed assets
Use install-assets for the immutable defaults shipped with this package:
vendor/bin/agent-loop init install-assets --agent=all
install-assets is intentionally configuration-free: host configuration cannot
replace its package-owned skills, roles, or hooks. Use sync-* when a host
repository owns customized canonical assets:
vendor/bin/agent-loop init validate --kind=all vendor/bin/agent-loop init sync-skills --agent=codex --dry-run vendor/bin/agent-loop init sync-subagents --agent=codex --dry-run vendor/bin/agent-loop init sync-hooks --agent=codex --dry-run vendor/bin/agent-loop init sync-policy --agent=codex --dry-run
Both paths use managed-entry manifests where applicable and refuse to overwrite
unmanaged targets unless --force or --adopt-existing is explicit. Host policy
projection owns only its narrow host-native keys/files and preserves unrelated
project configuration.
Detailed asset behavior is documented in Agent Assets In agent-loop.
Dogfood and validation
The first-party behavior is tested at three levels:
- PHPUnit covers typed hook semantics and offline asset installation.
composer dogfood:disciplineexecutes the packaged discipline and hooks in an isolated workspace.- GitHub Actions installs
agent-loopas a non-symlinked Composer dependency, runsinit install-assets --agent=all, verifies the installed skills/roles/ hooks, and reruns the dogfood gate from the installed package.
Guidance changes also require an observable behavioral comparison. A green installer or hook test alone does not prove that agents became easier to review, more selective in context, or less likely to add unrequested work.
Local commands:
composer install
composer test
composer phpstan
composer dogfood:discipline
composer ci
composer ci runs:
composer validate --strict
phpunit
phpstan
php tools/project-phpstan-rules.php
itp-context-validate 'voku\AgentLoop\Context\ArchitectureRules'
php tools/agent-discipline-dogfood.php
Never report a command as passed unless it ran and its exit code was observed.
Documentation
- Your first governed task
- Agent asset and offline installation contract
- Cross-package lifecycle
- Learning and durable-memory boundary
- First-party discipline dogfood report
- Real-issue acceptance model
- Upstream mechanism mapping and notices
Scheduled execution
agent-loop is the workflow CLI, not a scheduler. Use a conservative runner such
as voku/housekeeping when selected
maintenance commands need cron or another scheduler.
Agents may suggest. Humans approve.
License
MIT. See LICENSE.