mcp server
agentfactory
An agent multiplexer built on tmux
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 21, 2026AgentFactory
AgentFactory is an agent multiplexer built on tmux — Claude Code, Codex, Grok, OpenCode, or anything else: identity, lifecycle, live status, agent-to-agent coordination, and one dashboard over all of them. Daemonless, local-first.
Running several AI agents means juggling terminal windows with no
record of what each one is, what it's doing, or whether it's been
sitting blocked on a question for the last hour. Sessions die with
their terminal, results scroll away, and agents have no way to wait on
or steer each other. af exists to fix exactly that:
- One view of everything. Every agent's status —
working,idle,awaiting-input,done,exited(code)— in one list, from the CLI (af status,--json) or a live dashboard. - Define once, launch forever. An agent definition is a name +
harness + model + workdir.
af open plannerbeats re-typing flags into tmux windows. - Know when you're needed. Auto-wired hooks and terminal signals
report
awaiting-inputimmediately; quiet-screen detection catches common permission dialogs on the next reconciliation pass. The dashboard makes it loud, and the next output clears it automatically. - Perfect attach.
af attachexecs into tmux — full fidelity, resize, alt-screen. Works from inside your own tmux too (detach the nest withC-b C-b d). Detach and you're back at the dashboard. - Script the whole fleet.
af sendtypes into any session;af status --json | jq ...reads the fleet; exit codes are a contract. - Unkillable by design. No daemon. Sessions live in tmux, state in
SQLite; commands that observe or act on sessions reconcile the two
first, and the dashboard reconciles on every tick. Kill, upgrade, or
rebuild
affreely — your agents don't notice.
Install
Requires tmux ≥ 3.2 on PATH. Linux first; macOS works.
curl -fsSL agentfactory.sh/install.sh | sh
Downloads the release binary for your platform, verifies its checksum,
and installs af to ~/.local/bin
(read it first if you like).
Pre-built binaries — or grab the archive for your platform from
Releases and
drop af somewhere on PATH.
From source (Go 1.25+):
make install # → ~/.local/bin/af, with bash completion
af doctor # verify the environment
make help lists the rest (build, test, fresh, reset,
uninstall, ...). For zsh/fish completion:
af completion zsh|fish (details).
Quick start
The fastest way in: just prefix your agent with af.
af claude # opens (or resumes) a claude session here, and attaches
af claude --model opus # anything after the agent name passes straight to it
af --new claude # force a fresh session (af's own flags go before the agent)
af <agent> in a directory launches that agent as a managed session in
the current directory and drops you straight in — as low-friction as
running claude directly, but now it's tracked, survives detach, and
resumes with the same command. Works for any harness binary on your PATH
(af codex, af grok, af opencode, af agent). Detach with C-b d; you're back
at your shell and the session keeps running.
The explicit form is always there when you want control:
# Any command, managed ("custom" harness)
af open --cmd 'python train.py' --name trainer
# A Claude Code agent in a specific repo
af open --harness claude-code --model opus -C ~/src/api --name planner
af status # the fleet at a glance
af peek planner # its current screen, rendered
af send planner "refactor the auth middleware, then run tests"
af logs planner -f # follow its output without attaching
af attach planner # step in (detach: C-b d)
af close planner # graceful: quit keys → SIGTERM → SIGKILL
af kill planner # immediate: SIGKILL the process group
All sessions live on a dedicated tmux socket (tmux -L af) with a
locked config — your personal tmux and .tmux.conf are never touched.
Built-in harnesses
| Harness | Launches | Graceful close |
|---|---|---|
claude-code | claude --settings <auto-wired hooks> [--model <m>] | /exit |
codex | codex [--model <m>] | /quit |
grok | grok [--model <m>] | /exit |
opencode | opencode [--model <m>] | /exit |
cursor-agent | cursor-agent [--model <m>] (Cursor CLI) | SIGTERM |
agent | agent [--model <m>] — whatever binary named agent is on your PATH | /exit |
custom | whatever --cmd says | signal-only |
The --settings flag loads the generated hooks file that reports
awaiting-input — see docs/detection.md.
The agent harness is generic: it launches whatever binary named
agent sits on your PATH, so af agent works out of the box when
your CLI installs itself as agent. Cursor's CLI, for instance,
installs both agent and cursor-agent — use the cursor-agent
harness to launch it explicitly when agent is taken by another tool.
af never talks to models — harnesses do. --model is passed through
untouched. A harness is pure data (a command template, env, quit keys,
detect patterns, files), so adding or overriding one is a
config entry, not a code change.
Definitions: define once, launch forever
af define planner --harness claude-code --model opus -C ~/src/api
af define reviewer --harness claude-code --model sonnet -C ~/src/api --open
af open planner reviewer # open several at once
af defs # list definitions
af rm-def reviewer # delete one (running sessions unaffected)
--open defines and launches in one step.
Definitions may contain environment values. af defs --json returns
those values by design; treat its output as sensitive.
A multi-agent workflow
Three agents, three vendors, one repo — architect, builder, critic:
af define architect --harness claude-code --model opus -C ~/src/shop
af define builder --harness opencode --model sonnet -C ~/src/shop
af define critic --harness grok --model grok-4 -C ~/src/shop
af open architect builder critic
af dashboard
Kick off the pipeline without attaching to anything:
af send architect "design a checkout retry mechanism; write it to RETRY.md"
af send builder "implement RETRY.md; run the tests until green"
af send critic "review the implementation against RETRY.md; list gaps"
Watch all three from the dashboard: working while they work, a loud
awaiting-input when one blocks on you, a to attach and unblock,
detach drops you back. Or script the loop from outside:
# who is blocked on a human?
af status --json | jq -r '.[] | select(.status=="awaiting-input") | .name'
# block until the critic stops working, then collect the review
af wait critic
af logs critic -n 100
Because sessions live in tmux, the whole crew survives af upgrades,
terminal crashes, and SSH disconnects.
Agent-to-agent coordination
Agents can manage each other: every session has the full af CLI and
its own AF_SESSION_ID, so a coordinator agent spawns workers, waits on
them, and reads their results — no daemon, no message bus.
af open reviewer
af send reviewer "review the auth module. run: af signal \$AF_SESSION_ID done when finished"
af wait reviewer --for done # blocks until the reviewer reports done
af peek reviewer # read the result
af wait polls one reconciliation pass per tick (default --interval 1s) until the session reaches a --for status (default
idle,awaiting-input,done — "the agent stopped working"). Exit codes:
0 target reached · 1 the session ended in a terminal status outside
--for · 5 --timeout elapsed. af wait critic --for exited waits
for termination; --json prints the final session object.
af signal <session> done is the flip side: an agent (or a hook)
reports its task complete. done shows green in the dashboard and
sticks until the session's next real output — exactly like
awaiting-input, which any agent or hook sets with
af signal <session> awaiting-input.
MCP server
MCP-native harnesses (Claude Code, Codex, ...) can drive af through
typed tools instead of shelling out:
claude mcp add af -- af mcp # register for Claude Code
af mcp serves MCP on stdio — spawned per client, dies with it, so the
daemonless story holds. It exposes af_status, af_open, af_send,
af_peek, af_logs, af_wait, af_signal, af_close, and af_defs.
Destructive management (kill, rm, prune, define) stays
human-only. An agent signals itself done with af_signal(state: "done") — the session defaults to its own AF_SESSION_ID.
The dashboard
af dashboard — dense, fast, keyboard-first.
| Key | Action |
|---|---|
j/k, arrows | move selection (preview follows) |
a | attach; detach (C-b d) returns to the dashboard |
Enter | detail view — metadata, env (likely sensitive values masked by key name), recent log; scrollable |
l | logs view (f toggles follow) |
o | open a session from a definition (d deletes one) |
x / X | close / kill, with y/n confirm |
: | command bar — bounded, noninteractive af commands, run in-process |
? | help · q quit (sessions keep running) |
The preview pane mirrors the selected session's live screen at the
preview's own geometry. It's read-only — type via attach or af send.
The command bar rejects commands that would block, replace the process,
or start a protocol server; attach remains available through a.
Service sessions
Infrastructure (a local inference server, a build watcher) uses the
same machinery; --service just means close skips quit-keys and goes
straight to SIGTERM:
af define llama-8b --cmd "llama-server -m ~/models/llama3-8b.gguf --port 8080" --service
af open llama-8b
af open local-agent # a definition whose env points its harness at :8080
Scripting
--json is available on status, defs, peek, doctor, and
wait. Stable exit codes apply across the CLI:
0 success · 1 runtime error · 2 usage error · 3 not found ·
4 environment problem · 5 wait timeout. Sessions are addressed by
ID or name.
af status --json # array of session objects
af defs --json # definitions
af peek critic --json # {"screen": "..."}
af doctor --json # environment checks
af wait critic --json # final session object
Line-limited logs and MCP log reads cap the file bytes scanned at
4 MiB. af logs --lines 0 explicitly requests the entire log; peek
reads the rendered screen instead of the log file.
Cleanup is scriptable too: af close --all, af kill --all,
af rm <session> (drop one finished session + its log), af prune
(drop all exited/failed history).
Status model
| Status | Meaning |
|---|---|
starting | created; no output observed yet |
working | output within the last idle_threshold (default 5s) |
idle | alive, quiet |
awaiting-input | blocked on you — hook-signaled, terminal-declared (bell/notification/prompt mark), or screen-detected |
done | agent reported its task complete (af signal <id> done) |
exited | ended; exit code recorded (-1 = killed by signal) |
failed | tmux session disappeared without a recorded exit |
awaiting-input and done stick until the session's next real output
clears them (terminal-signal state also clears on a command-start mark;
screen-detected state clears as soon as the screen stops matching).
Blocked-detection is automatic and layered: auto-wired harness hooks
(claude-code out of the box), terminal signals (bell, notifications,
prompt marks), and universal screen patterns — with explicit
af signal always outranking them all. Details and per-harness wiring:
docs/detection.md.
Liveness and exit codes come from tmux itself; activity from log
growth (every session's output streams to
<data_dir>/logs/<id>.log) — with animation frames filtered out, so a
spinner doesn't read as work. Details:
docs/architecture.md.
Configuration
Optional YAML at ~/.config/agentfactory/config.yaml; every key has a
default. Precedence: flags > AF_* env > file > defaults
(global flags --socket, --data-dir; env AF_SOCKET, AF_DATA_DIR,
AF_IDLE_THRESHOLD, AF_CLOSE_TIMEOUT, AF_SEND_DELAY, AF_DETECT,
AF_SIGNALS).
socket: af # tmux -L <socket>
data_dir: ~/.local/share/agentfactory # db + logs
idle_threshold: 5s # working→idle cutoff
close_timeout: 10s # SIGTERM→SIGKILL escalation
send_delay: 50ms # gap between text and Enter in af send
detect: true # screen-pattern status detection
signals: # terminal-signal detection (bell, OSC 9/777, OSC 133)
enabled: true
# notify_awaiting: ["(?i)permission"] # optional: narrow which notifications count
tui:
tick: 1s # dashboard refresh
# Add harnesses, or override a built-in (same name wins):
harnesses:
aider:
command: "aider{{if .Model}} --model {{shellquote .Model}}{{end}}" # Go text/template
env: {}
quit_keys: ["/exit"] # empty = signal-only close
detect: # optional screen patterns (regex)
awaiting_input: ["\\? for shortcuts"] # quiet screen matching => awaiting-input
working: ["esc to interrupt"] # ...unless one of these also matches
Rendered commands run via sh -c, so harness configuration is trusted
code. Use {{shellquote .Model}}, {{shellquote .WorkDir}}, and
{{shellquote .FilesDir}} whenever a value enters shell syntax; the
built-in harnesses do this automatically. Session state is stored under
a private 0700 data directory with 0600 database and log files.
Shell completion
make install installs bash completion (picked up automatically if
the bash-completion package is present). Completion is dynamic:
session arguments complete with appropriate live names and IDs,
af open/af define --from complete definitions, and --harness
completes known harnesses.
af completion zsh > "${fpath[1]}/_af" # zsh
af completion fish > ~/.config/fish/completions/af.fish # fish
Notes
afmanages only sessions it started; pre-existing tmux sessions and stray processes are invisible to it.- Switching between different
afbuilds sharing one data dir? Runmake freshafter switching — a clean slate beats two builds fighting over one database. - Every session gets
AF_SESSION_IDandAF_SESSION_NAMEin its environment.
Development
make precommit # tidy + text/docs/shell + format + lint + race tests
make cover # coverage summary
make fuzz # byte-parser fuzzers (~20s)
make help
Integration tests use a throwaway tmux socket — your own sessions are never touched.
Suite design: docs/testing.md. Current enforced gates and audited limitations: docs/quality.md.
See CONTRIBUTING.md for guidelines and docs/architecture.md for the design. Coding agents: read AGENTS.md.