Artifact-driven multi-agent orchestration, in your terminal.
You assign work to AI agents the way you'd assign it to a teammate — they pick it up, report progress, raise blockers, and hand it back for review. No web UI, no lock-in, runs on your laptop or a 100-node cluster.
_/ __/ __/ __/ __/ __/ __/ __/ __/ __/ __/ __/ __/ __/
_/ __/ __/
_/ __/ TERMINAL-NATIVE MULTI-AGENT ORCHESTRATION __/
_/ __/ _/
_/ __/ artifact-driven · blackboard · C/S _/
_/ __/ _/
_/ __/ v1.3.0 · MIT · github.com/zealot00/Anvil _/
_/ __/__________________________________________/
$ af create --name "research-2025" --project "RD-01"
$ af status --id art_xxx # INIT → ... → PENDING_APPROVAL
$ af approve --id art_xxx # → APPROVED
$ af publish --id art_xxx --out out.md
| You want | But | Anvil |
|---|---|---|
| Self-hostable artifact state machine | LangChain / AutoGPT are LLM loops without durable artifacts | Git-as-source-of-truth + Postgres metadata + explicit state machine with 13 states |
| Distributed workers (100-node scale) | tmux + ssh + tail -f |
NATS JetStream KV leases, server-side Reaper, automatic republish on lease expiry |
| Plug any local Agent CLI | Vendor-specific SDKs | Model Context Protocol — works with opencode, claude, aider, hermes, openclaw, codex, … |
| Real HITL (human-in-the-loop) | "vibe coding" without review | First-class PENDING_APPROVAL state, Slack Interactive buttons, audit-logged transitions |
| Terminal-native workflow | Web dashboards and desktop apps get in the way | TUI + CLI + REPL + Connect-gRPC; everything scriptable |
- Artifact is a first-class citizen. Every output of every agent is a
versioned file (Markdown / JSON / code / image).
go-gittracks history; Postgres stores metadata. Diff, revert, audit by default. - 13-state state machine with
[human]-gated transitions (approve,reject,revision,publish,cancel) and a DAG fan-in for pipelines. - Blackboard dispatch — agents don't talk to each other; they coordinate through the artifact state machine + dependency gates.
- C/S cluster. Nodes pair in with a one-time code, heartbeat every 5 s,
get woken (or evicted) by the server. Two-level routing by
node_tagsandrole. - Multi-Agent daemon. The Worker scans
PATHfor any CLI that speaks MCP (opencode, claude, aider, hermes, openclaw, codex, custom), spawns each as a stdio JSON-RPC server, and registers them. The server then routes tasks based ontask.AgentProvider(server hint) → capability → any. - HITL Zen TUI (Bubble Tea).
j/knavigation,$EDITORintegration, diff/approve/reject,?help overlay, REPL mode with bash-style autocompletion. - Hook system. Three kinds:
webhook(outbound HMAC + retry/dead-letter),command(sh -c, optionally routed to a node),inbound(HMAC-validated, maps tocreate_artifact/trigger_pipeline). Slack Interactive callback is a special case. - Daily pipelines. Cron-scheduled templates with
depends_onfan-in; per-day runs view (af pipeline runs). - Material library. Cross-day knowledge assets, tagged with
tags[](GIN index) +pg_trgmfuzzy search, withReferencedByandUsedByreverse links for full provenance. - Observability.
/healthz,/readyz,/metrics(Prometheus text), audit-log table for every state change.
# 1. PostgreSQL (Docker).
docker compose up -d postgres
# 2. Server (auto-migrates schema + registers demo agents).
go run ./cmd/server
# → 2025/01/01 12:00:00 [server] NATS mode disabled (using in-process blackboard)
# → 2025/01/01 12:00:00 [server] listening on :9090
# 3. CLI — create, status, approve, export.
go run ./cmd/af create --name "research-2025" --project "RD-01"
# → Created artifact ID: "art_xxx" [INIT]
go run ./cmd/af status --id art_xxx
# → Current Status: PENDING_APPROVAL (demo agents auto-advance)
go run ./cmd/af approve --id art_xxx
# → Status transitioned to APPROVED
go run ./cmd/af export --ids art_xxx > report.md
# → # Research 2025 ...
# 4. TUI.
go run ./cmd/tui
# j/k navigate, Enter detail, a approve, r reject, e $EDITOR, / command, ? help# 1. PG + NATS.
docker compose up -d postgres
docker run -d --name nats -p 4222:4222 nats:latest -js
# 2. Server (NATS mode).
ANVIL_NATS_URL=nats://localhost:4222 go run ./cmd/server
# 3. Edge Worker daemon (auto-discovers Agent CLIs in $PATH).
ANVIL_NATS_URL=nats://localhost:4222 \
ANVIL_WORKER_ID=edge-1 ANVIL_NODE_ID=node-a \
go run ./cmd/worker
# → [worker edge-1] capabilities=planner,writer,reviewer node=node-a ...
# → [agent-pool] registered provider=hermes bin=... tools=10
# → [agent-pool] registered provider=openclaw bin=... tools=9No ANVIL_AGENT=mcp:... hard-coding required — the worker discovers what's
installed and reports it to the server.
# What does the server think this worker can do?
curl -s http://server:9090/agents | jq
# Pick a specific worker for a given capability:
curl -s "http://server:9090/agents?choose=1&capability=writer" | jqThree layers — presentation, application, infrastructure — with the domain model kept pure (no external imports).
┌────────────────────────────────────┐
┌──────────┐ │ Presentation Surface │
│ TUI │ ──HTTP──►│ Connect-gRPC (HTTP/1.1 + SSE) │
│ (cmd/ │ │ + plain HTTP (/healthz/readyz/ │
│ tui) │ │ /metrics /agents /webhooks) │
└────┬─────┘ └────────────────┬───────────────┘
│ │
┌────┴─────┐ ▼
│ CLI │ ┌──────────────────────────┐
│ (cmd/ │ ──HTTP─────►│ Use Cases (internal/app) │
│ af) │ │ · ArtifactService │
└──────────┘ │ · PipelineService │
│ · NodeService │
│ · MaterialService │
│ · HookService │
│ · NotificationService │
└─────────────┬────────────┘
│ ports
┌─────────────▼────────────┐
│ Ports (internal/ports)│
│ Store · Blackboard │
│ EventBus · Notifier │
│ Versioner · HookDis- │
│ patcher · LeaseReg- │
│ istry · Searcher │
└─────────────┬────────────┘
│ adapters
┌───────────────────┬───────────────┼───────────────────┬──────────┐
▼ ▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ git │ │postgres│ │ mem │ │ nats │ │ memstore│
│ + CAS │ │ store │ │ black │ │ (KV + │ │ (test) │
└────────┘ └────────┘ │ board │ │ Jet- │ └────────┘
└────────┘ │ Stream)│
└────────┘
┌─────────────────────────┐
│ Domain (pure Go) │
│ artifact · statemachine│
│ events · node · material│
│ pipeline · hook │
└─────────────────────────┘
The full architecture document — including the multi-agent daemon sequence
diagram, state machine diagram, and DAG fan-in flow — lives at
docs/architecture.md.
Everything an agent produces is an artifact — a Markdown report, a JSON diff, a generated image. Artifacts have:
- An immutable version chain (Git hash per version).
- A current status (see state machine below).
- A list of dependencies on other artifacts (with minimum required status).
- An owner (which agent or human created them).
13 states, 16 legal transitions, with HumanOnly guards for HITL gating:
stateDiagram-v2
INIT --> OUTLINE_DONE : planner
INIT --> FAILED : error
OUTLINE_DONE --> DRAFT_CREATED : writer
OUTLINE_DONE --> FAILED : error
DRAFT_CREATED --> PENDING_APPROVAL : writer/submit
DRAFT_CREATED --> FAILED : error
PENDING_APPROVAL --> APPROVED : [human] approve
PENDING_APPROVAL --> REVISION_REQUESTED : [human] revision
PENDING_APPROVAL --> REJECTED : [human] reject
REVISION_REQUESTED --> DRAFT_CREATED : writer-revise
REVISION_REQUESTED --> FAILED : error
APPROVED --> READY_TO_PUBLISH : pack
APPROVED --> FAILED : error
READY_TO_PUBLISH --> PUBLISHED : [human] publish
FAILED --> INIT : retry
REJECTED --> CANCELLED : [human] cancel
Run af diagram --format mermaid|dot to render. The canonical table is in
internal/domain/statemachine/statemachine.go.
Every agent carries a three-tuple identity:
| Field | Meaning | Example |
|---|---|---|
Name |
Human-readable label | writer-1 |
Provider |
Which CLI / vendor | opencode, claude, hermes, openclaw, aider, mock |
Runtime |
Where it runs | local, node:edge-1, remote:192.168.1.10 |
The Worker daemon (see internal/workersdk/) scans
PATH for known CLIs at boot, spawns each as a stdio MCP server, and registers
the agent in an AgentPool. Tasks arrive via NATS with an optional
task.AgentProvider hint from the server. pool.Pick(hint, status) resolves:
- Explicit provider → that exact agent.
- Capability (
status → planner | writer | reviewer) → first available agent. - Any available agent.
- Fall back to an inline
Handler(used in CI / when no CLI is installed).
The server tracks these via internal/server/agent_registry.go,
which subscribes to NATS heartbeats and rebuilds the worker ↔ agents map.
Templates can declare DependsOn: [other_template_id]. TriggerRun blocks
until every dependency's latest Run.Status == COMPLETED. Templates form
a DAG, fan-in enforced.
Cross-day knowledge assets. Two reverse-link fields track provenance:
ReferencedBy— artifacts that cite this material (others → me).UsedBy— artifacts that embed this material (me → others).
Queryable via af material search --tag ... with GIN + pg_trgm fuzzy match.
$ af --help
Available Commands:
agent view / pick a Worker's local Agent
approve HITL approve
completion generate shell completion (bash/zsh/fish/powershell)
create create an artifact and enter the blackboard
diagram render state machine (mermaid|dot)
diff show version diff
export package export (Markdown)
help help about any command
hook manage event hooks (webhook / command / inbound)
material material library
node node lifecycle (pair / approve-pair / evict)
notifications query notification history
pipeline pipeline templates & runs
publish mark APPROVED as PUBLISHED, export to file (D15)
reject HITL reject
revert revert to an older version (creates a new version pointing at it)
search full-text search across artifacts
shell enter REPL mode with bash-style completion
status query artifact status
Example session:
af create --name "research-2025" --project "RD-01"
af status --id art_xxx # INIT → PENDING_APPROVAL
af approve --id art_xxx # → APPROVED
af export --ids art_xxx > report.md # → 131 bytes
af publish --id art_xxx --out published.md # → READY_TO_PUBLISH → PUBLISHED
af diagram --format mermaid # 16 lines + [human] guards
af agent list # workers + their (provider, capabilities)
af agent choose --capability writer # server picks a worker
af search --query "research"
af material search --tag sic
af pipeline create-template --name daily --project demo --cron "0 8 * * *"
af pipeline runs --project demo --days 7
af node pair --name edge-1 --fingerprint fp-1
af notifications list --limit 20
af shell # REPL modeservices:
postgres:
image: postgres:16-alpine
environment: { POSTGRES_USER: anvil, POSTGRES_PASSWORD: anvil, POSTGRES_DB: anvil }
ports: ["5432:5432"]
volumes: [anvil-pg:/var/lib/postgresql/data]Add anvil-server and anvil-worker services in production. See
docs/architecture.md#containerized for
a full example.
See .env.example. Highlights:
| Variable | Required | Purpose |
|---|---|---|
ANVIL_HTTP_ADDR |
server | listen address (default :9090) |
ANVIL_PG_DSN |
server | Postgres DSN |
ANVIL_DATA_DIR |
server | git working tree root |
ANVIL_NATS_URL |
optional | if set, distributed mode |
ANVIL_JWT_SECRET |
production | node JWT HS256 secret |
ANVIL_SLACK_TOKEN / _SIGNING_SECRET |
optional | Slack notifications + interactive approval |
ANVIL_WORKER_ID |
worker | stable identifier |
ANVIL_NODE_ID |
worker | parent node grouping |
ANVIL_DISABLE_AUTODISCOVER |
worker | if 1, don't scan PATH |
ANVIL_FALLBACK_HANDLER |
worker | if 1, use deterministic handler when pool is empty |
# All unit tests (Postgres / NATS tests skip automatically if unavailable).
go test ./... -count=1
# Coverage report (excludes `gen/` proto-generated code).
go test ./... -coverprofile=cover.out -count=1 -short
grep -vE '^anvil/gen/' cover.out > cover2.out
go tool cover -func=cover2.out | tail -1
# Full-stack E2E (downloads embedded postgres + NATS, runs real binaries).
ANVIL_E2E_FULL=1 go test -timeout 180s ./internal/e2e/...
# Detailed endpoint dump (captures every /agents / /healthz / /metrics /
# CLI / JWT / webhook HMAC call as evidence).
ANVIL_E2E_DETAIL=1 go test -timeout 300s -run TestFullStackDetail ./internal/e2e/...Domain-layer coverage: 93.5%–100%. Adapter layer: 55–80%. See
.github/workflows/ci.yml for the CI matrix.
Anvil draws on ideas from the broader open-source multi-agent ecosystem and from the Model Context Protocol community. Where relevant, those influences are cited inline in the architecture doc; we try not to repeat them here.
If you are building a similar system and want to share patterns, please
open a Discussion — the design notes in docs/PLAN.md
record every decision D1–D15 in a way that should make cross-comparison easy.
| Phase | Status | Highlights |
|---|---|---|
| P0 Foundation | ✅ | in-process blackboard, PG, git, TUI/CLI, hooks, cron, materials |
| P1 Distributed cluster | ✅ | NATS JetStream + KV leases + Reaper + node commands |
| P2 External surface | ✅ | inbound webhook + pipeline runs + TUI SSE + Slack Interactive |
| P3 Ecosystem | ✅ | nkey/JWT + multi-project + WOL + terminal image + notification history |
| P4 v1.0 polish | ✅ | /healthz /readyz + PG outbox → NATS + /metrics + completion |
| P5 v1.1 UX | ✅ | REPL + diagram + rate limit + full-text search + TUI help |
| P6 v1.2 Agent bridge | ✅ | MCP protocol + Identity triple + daemon auto-discovery |
| P7 v1.3 completeness | ✅ | D2 CAS + D13 DAG + D14 UsedBy + D11 dedup + D15 af publish |
| P8 v1.4 (planned) | ⏳ | distributed cron leader election; workspace-level multi-tenant isolation; Lark / DingTalk / WeCom adapters; OpenTelemetry tracing |
See CHANGELOG.md for what shipped in each release.
Contributions of all sizes are welcome. See
CONTRIBUTING.md for the dev setup, branch / PR flow,
and how to add a new Agent CLI provider or state transition.
All participants are expected to follow the
CODE_OF_CONDUCT.md.
See SECURITY.md for the threat model, supported versions,
and how to report a vulnerability privately.
MIT — © 2025 Anvil Contributors.
- nats-io — JetStream + nkeys.
- go-git — pure Go Git implementation.
- pgx — Postgres driver.
- Bubble Tea — TUI framework.
- Connect-gRPC — HTTP/1.1-friendly gRPC.