Deterministic engineering triage loop. Production-grade dispatching, code generation, conflict resolution, PR verification, and code review for agentic software engineering teams. No LLM calls during detection. Matches agent throughput to human approval bandwidth.
Website: wlfghdr.github.io/agentic-dev
agentic-dev is the execution layer of the agentic-* suite — three building blocks for running an agentic organization on Git:
| Repo | Role |
|---|---|
| agentic-enterprise | The operating model — governance layers, process loops, policies, and templates. Humans decide, agents execute, Git governs. |
| agentic-kb | The knowledge layer — layered, vendor-neutral knowledge ops via the /kb command. |
| agentic-dev | The execution layer — deterministic engineering triage and execution loop (this repo). |
In a mature agentic organization:
- Git is the operating system of the company.
- Signals (customer issues, observability alerts) are turned into Missions (GitHub Issues).
- Missions are picked up by autonomous Engineers (AI agents).
- Engineer agents propose changes via Pull Requests.
- Reviewer agents audit the PRs and verify tests, passing them to Humans for approval and release.
agentic-dev is the engine that drives this cycle, ensuring the workspace remains clean, tasks do not conflict, and agents only work on what is ready.
┌─────────────┐
│ GH Issue │ (Mission assigned to agent)
└──────┬──────┘
│
▼
┌─────────────┐
│ detect.py │ (Deterministic, no-LLM scanner)
└──────┬──────┘
│
▼
┌─────────────┐
│ tick.sh │ (Dispatches parallel transient systemd jobs)
└──────┬──────┘
│
┌────────┴────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ engineer.sh │ │ review.sh │
└──────┬──────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Codex/Kiro/ │ │ Claude/Kiro/│ (LLM execution chains)
│ Claude/AGY │ │ Codex/AGY │
└──────┬──────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ PR Created │ │ Verdict │ (merge-ready / needs-fix / blocked)
└─────────────┘ └─────────────┘
scripts/detect.py: Deterministically scans watched repositories to identify issues assigned to the agent, PRs needing review, PRs falling behind, Dependabot PRs ready to merge, and repositories due for a release. No LLM calls are made here.tick.sh: The orchestrator timer script. Acquires item locks, checks concurrency limits, and dispatches tasks to transientsystemd-rununits for safe, parallel execution.cli_dispatch.sh: Shared CLI-chain configuration and execution helpers — resolves the configured agent CLI chain and runs prompts against it.engineer.sh: Sets up repository worktrees, runs the designated agent chain to write code/resolve conflicts, and pushes results or opens a PR.review.sh: Pulls the code, dispatches the reviewer agent chain, runs local verification tests, and posts the review comment with a finalVERDICT.merge.sh: Automatically merges approved PRs ifautomergeis enabled for the repository.dependabot_merge.sh: Deterministically merges green Dependabot PRs; behind/conflicting PRs are rebased first.release.sh: Creates at most one deterministic GitHub release per repo per UTC day when new commits exist after the latest semver tag.gc_worktrees.sh: Removes dispatch worktrees (and their agent branches) once the issue or PR is closed. Run bytick.sheveryTRIAGE_WORKTREE_GC_HOURS(default 6,0disables).
systemd/triage-tick.service: Systemd service to run the orchestrator tick.triage-tick.timer: Near-realtime timer that triggers the service every 60 seconds.
logrotate/agentic-triage: Log rotation configuration to prevent disk space issues on the VPS.
tests/test_cli_dispatch.sh: Regression tests for the CLI-chain dispatch helpers.
triage.toml: Main configuration file defining the triage agent name, human fallback, limits, execution chains, and watched repositories.
The loop is configured via triage.toml (default path: /srv/agentic-dev/triage.toml):
[agent]
login = "agent-login"
human_login = "human-login"
[limits]
max_engineer = 3 # Max parallel engineering dispatches
max_review = 2 # Max parallel review dispatches
max_maintenance = 1 # Max parallel Dependabot/release jobs
open_pr_cap_per_repo = 3 # Cap open PRs per repo to match human approval bandwidth
lock_ttl_hours = 2 # TTL for stale locks
[runtime]
# Optional host-local environment file forwarded to transient dispatch units.
dispatch_env_file = "/etc/agentic-dev/dispatch.env"
[cli_chain]
engineer = ["codex", "claude", "kiro", "agy"] # Writing code
review = ["claude", "codex", "kiro", "agy"] # Code reviews
rebase = ["claude", "kiro", "agy", "codex"] # Conflict resolution
[dependabot]
enabled = false # Opt in to green Dependabot merges without reviewer LLM cost
[release]
enabled = false # Opt in to daily releases when commits exist after latest semver tag
[cli_tools.kiro]
command = "kiro-cli"
args = ["chat", "--no-interactive", "--trust-all-tools"]
prompt_mode = "arg"
[[repos]]
name = "organization/repository-name"
automerge = true
dependabot_automerge = false
release = false
# Choose the repository's release contract explicitly when releases are on:
# "version", "config_yaml", "ios", "android", or "conventional".
version_source = "conventional"Built-in command definitions are provided for codex, claude, agy, and
kiro. Any tool named in a chain can be configured under [cli_tools.NAME]:
command: executable name or path.args: argument array. Each{worktree}token is replaced with the checkout path without shell evaluation.prompt_mode:stdinto pipe the prompt, orargto append it as the final argument.
For example, a custom agent CLI can be added without changing the scripts:
[cli_tools.my-agent]
command = "/opt/agents/my-agent"
args = ["run", "--workspace", "{worktree}", "--auto-approve"]
prompt_mode = "stdin"The loop coordinates through a small set of GitHub labels. Two are human control points you can apply by hand; the rest are agent-managed workflow state. Labels are the source of truth for what the loop will and will not pick up.
| Label | Set by | Meaning / effect |
|---|---|---|
do-not-work |
human | detect.py skips the issue entirely — use it to pause the agent on a specific item. |
blocked |
human or review.sh |
Halts work: blocked issues are skipped, and a VERDICT: blocked review marks the PR for human attention. |
in-progress |
agent | An engineer dispatch is actively working the issue or PR. |
needs-review |
agent | PR is awaiting a review dispatch. |
changes-requested |
review.sh |
Review verdict needs-fix — the engineer chain will revise on the next tick. |
approved |
review.sh |
Review verdict merge-ready — PR is handed to the human (and auto-merged if automerge is enabled for the repo). |
The three review verdicts emitted by review.sh map onto labels as: merge-ready → approved, needs-fix → changes-requested, blocked → blocked.
Review approval and automerge are bound to the immutable head commit inspected
by the reviewer. Publishing approval requires permission to read PR metadata and
checks, create a commit-pinned review, and update issue labels. Automerge also
requires permission to merge the PR. If the head or base changes, checks are not
successful, a human stop label is present, review publication fails, or the
pinned merge is rejected, the wrapper defers the merge and does not retain the
approved label. To stop automatic merging, set the repository's automerge
value to false; for an individual PR, apply blocked, do-not-merge, or
do-not-work and remove approved if it is already present.
Agent-authored PR titles should follow Conventional Commits (fix: ...,
feat: ..., chore(scope): ...). The daily release job derives the next
SemVer version from the merged commit subjects: breaking changes create a major
bump, feat creates a minor bump, and other merged changes create a patch bump.
The timer fires every 60 seconds, so every tick is kept cheap and every failure mode that cannot heal within one backoff window is parked instead of retried:
- One PR listing per repository.
detect.pyfetches open PRs once per repo and derives the open-PR cap, stale-approval demotion, Dependabot, fix/rebase, review, and linked-issue checks from it. Linked PRs are matched onclosingIssuesReferences, theissue-<n>branch, or a closing keyword in the body, which avoids the 30 requests/minute search API.ghpages the request itself, so theTRIAGE_OPEN_PR_LIMITceiling (1000) costs nothing on repos with few open PRs; reaching it fails the tick instead of silently detecting against a truncated slice. - Release discovery is re-checked hourly when no commits exist since the
latest release (
TRIAGE_RELEASE_RECHECK_SECONDS, default3600). - CLI cooldowns. When an agent CLI fails with a usage limit, a login
failure, or a missing binary, it is parked under
state/cli-cooldown/<tool>. An explicit reset time such astry again at Sep 21st, 2026 10:47 PMis honored; otherwise limits park forTRIAGE_CLI_LIMIT_COOLDOWN_SECONDS(900), authentication and missing binaries forTRIAGE_CLI_AUTH_COOLDOWN_SECONDS/TRIAGE_CLI_MISSING_COOLDOWN_SECONDS(3600). Parked CLIs are skipped inside a chain, andtick.shdoes not dispatch engineer or review work at all while every CLI in the chain is parked. After re-authenticating a CLI, delete its cooldown file to resume immediately. - Logs and history record activity only. Idle ticks go to the journal only;
logs/*-tick.logis kept for ticks that dispatched, warned, or failed, andstate/history/gets a snapshot only when the detected work changes. Files inlogs/older thanTRIAGE_LOG_RETENTION_DAYS(14) are pruned hourly.
Dependabot merging and daily releases are state-changing maintenance jobs, so
they are opt-in at both the global and repository level. Existing installations
that omit [dependabot], [release], dependabot_automerge, or release
remain disabled until the operator explicitly enables them in triage.toml.
Required GitHub permissions for the authenticated gh account:
- Dependabot merge: read repository metadata, read PR checks, merge PRs, and delete merged branches when allowed by the repository.
- Release: read repository metadata and tags, compare commits, and create tags and GitHub releases.
Failure behavior:
- Dependabot PRs with no checks, pending checks, red checks, unknown check
conclusions, draft state,
blocked, ordo-not-mergeare skipped. - Dependabot PRs that are behind or conflicting are rebased first. A clean
rebase is deterministic; only real conflicts use the configured rebase agent
chain. Unresolved conflicts are handed back as
blocked. - Missing or unreadable maintenance config fails closed. No merge or release is authorized without the explicit repository opt-in.
- Releases run at most once per UTC day per repository and only when commits
exist after the latest stable GitHub release whose tag is strict
vMAJOR.MINOR.PATCH. Discovery follows every releases API page, ignores drafts and prereleases, and chooses the numerically greatest usable tag. version_source = "version"reads a rootVERSION;"config_yaml"readsframework_versionfrom rootCONFIG.yaml;"ios"and"android"read their platform manifests; and"conventional"derives the next version from commit subjects. Repositories withVERSION,CONFIG.yaml, plugin manifests, or supported mobile version fields fail closed if no adapter is configured instead of falling back to0.0.0.- Authoritative adapters require a matching
CHANGELOG.mdrelease section. Every trackedplugin.jsonversion must also match. A stale version, manifest, or changelog stops publication with instructions to reconcile the metadata in a pull request; the release job never edits the default branch.
Upgrade notice: this is a breaking release-contract change. Before installing
this version, every release-enabled repository with existing version metadata
must set version_source explicitly. Choose the adapter matching the
authoritative metadata, or choose conventional to retain commit-derived
versioning intentionally. Without that migration, release publication fails
closed until the repository configuration is updated. Roll back to the prior
installed scripts if the configuration cannot be migrated immediately.
Rollback:
- Set
[dependabot].enabled = falseor a repo'sdependabot_automerge = falseto stop dependency auto-merges. - Set
[release].enabled = falseor a repo'srelease = falseto stop daily releases. - Reinstall after versioned script changes with
./install.sh; runtime config is preserved by the installer and can be reverted independently.
Existing public tags are immutable history. Do not move, delete, or recreate
the already published agentic-kb v6.4.1, agentic-enterprise v1.0.0, or
agentic-dev v0.4.0 tags. Reconcile each repository through its normal pull
request review workflow, then allow the next release to move forward:
agentic-kb: prepare at least6.4.2inVERSION, every packaged plugin manifest, README version references, and aCHANGELOG.md6.4.2section; configureversion_source = "version".agentic-enterprise: prepare a version later than both the public tag and its established4.4.1framework line (for example4.4.2) inCONFIG.yaml, README references, packaged manifests, and changelog; configureversion_source = "config_yaml".agentic-dev: prepare at least0.4.1across its authoritative version and release notes before enabling the corresponding adapter. If this repository intentionally has no stored version contract, explicitly chooseversion_source = "conventional"and add the0.4.1changelog narrative in the reviewed change.
After those pull requests merge, the deterministic job may publish new tags at the reconciled commits. Automatic repair must never rewrite the three historical public releases.
Prerequisites: Linux with systemd, Python ≥ 3.11 (tomllib), git, and an authenticated gh CLI, plus the agent CLIs you configure in your chains.
Run the installation script as root on your VPS or orchestrator host:
sudo ./install.shFor an isolated, non-root validation install, redirect the host integration
targets and disable systemctl mutations:
TRIAGE_DIR=/tmp/agentic-dev-runtime \
TRIAGE_SYSTEMD_DIR=/tmp/agentic-dev-systemd \
TRIAGE_LOGROTATE_DIR=/tmp/agentic-dev-logrotate \
TRIAGE_SKIP_SYSTEMD=1 \
./install.shProduction dispatch defaults to repositories under /srv/wulfai/repos and
worktrees under /srv/wulfai/worktrees. Set TRIAGE_REPOS_DIR and
TRIAGE_WORKTREES_DIR when running the installer to use different roots.
This will:
- Copy all scripts to
/srv/agentic-dev/bin/. - Initialize directories for state, locks, and history.
- Install systemd service and timer files.
- Enable the systemd timer.
To enable active dispatches in production, install the systemd drop-in override (set TRIAGE_ENABLE_DISPATCH=1 and configure HOME=/root so the gh CLI can authenticate). See systemd/triage-tick.service.d/dispatch.conf.
Issues and PRs welcome. Keep changes focused: this repo is a deterministic loop, not a framework — speculative features belong in agentic-enterprise discussions first.
Apache License 2.0 — see LICENSE.
Release history lives in CHANGELOG.md.