agentctl: Headless Orchestration for Coding Agents

6 min read

I built a CLI that wraps coding agents for headless orchestration.

agentctl architecture

agentctl terminal

The friction of going headless

I’ve been running coding agents headlessly as my primary way of writing software. Not as a side experiment, but as the default. And the friction points became obvious fast.

You launch a Claude Code session, move on to something else, and forget what it’s doing. You launch another session in the same directory without realizing it, and they clobber each other’s work. You hear about a new model or a new agent tool, but the switching cost is high enough that you just stick with what works. Why bother?

Then there’s the supervision layer. I have AI agents that supervise my coding agents: launching sessions, checking progress, reacting when work finishes. For that to work, I need clean tooling that serves both humans and the agents doing the supervising.

So I built agentctl. It’s alpha, but a few folks have been daily driving it for weeks now.

What it is

agentctl is a lightweight CLI that wraps whatever coding agent you use and gives you a standard set of commands to manage headless sessions. It’s not a replacement for claude or any other agent CLI. It’s the supervisory layer that sits on top.

The core idea: agentctl reads from native sources. It doesn’t maintain its own session registry. The Claude Code adapter reads ~/.claude/projects/ and cross-references running processes. The Pi adapter reads Pi’s session files. This means agentctl always reflects ground truth about what’s actually running on your machine.

npm install -g @orgloop/agentctl

Getting started

The basics are simple:

# See everything running on your system
agentctl list

# Include completed sessions from the last 7 days
agentctl list -a

# Check what an agent is doing
agentctl peek <session-id>

# Launch a new session
agentctl launch -p "Read the spec and implement phase 2"

# Stop a session
agentctl stop <session-id>

# Resume with new instructions
agentctl resume <session-id> "fix the failing tests"

Session IDs support prefix matching, so you only need enough characters to be unique.

Directory locking

One of the first problems I hit was accidentally launching two agents in the same repo. agentctl tracks which directories have active sessions and prevents conflicting launches.

# Agents automatically lock their working directory on launch.
# Manual locks work too, for when a human is working:
agentctl lock ~/code/my-project --by charlie --reason "manual debugging"

# See all active locks
agentctl locks

Manual locks let a human claim a directory and hold it as long as they want. No agent will launch there until the lock is cleared.

Lifecycle hooks

Hooks let you wire agentctl into the rest of your workflow. They’re shell commands that fire at specific points in a session’s lifecycle:

agentctl launch -p "implement feature X" \
  --on-create "echo 'Session $AGENTCTL_SESSION_ID started'" \
  --on-complete "npm test"

Available hooks:

  • --on-create: fires after worktree creation but before the agent launches. This is where you bootstrap the environment: mise trust, yarn install, seed a test database, whatever the agent needs before it starts working.
  • --on-complete: fires when the session finishes. Run tests, send alerts, trigger the next step.
  • --pre-merge: fires before agentctl merge commits. Lint, test, validate.
  • --post-merge: fires after merge pushes or opens a PR. Notify, trigger CI.
agentctl launch -p "implement feature X" \
  --worktree \
  --on-create "cd $AGENTCTL_CWD && mise trust && yarn install" \
  --on-complete "npm test"

Hook scripts receive context through environment variables: AGENTCTL_SESSION_ID, AGENTCTL_CWD, AGENTCTL_ADAPTER, AGENTCTL_BRANCH, and AGENTCTL_EXIT_CODE.

The sweep pattern

This is the feature I’m most excited about. You can launch the same task across different agents, models, and configurations, each in its own git worktree, fully independent.

Want to know if Claude Opus or Sonnet does a better job on your refactor? Or whether Codex handles it differently than Claude Code? One command:

agentctl launch \
  --adapter claude-code --model claude-opus-4-6 \
  --adapter claude-code --model claude-sonnet-4-5 \
  --adapter codex \
  -p "refactor the auth module"

Each gets its own branch and worktree. No conflicts. When they finish, you compare the results and pick the winner. The switching cost drops to nearly zero.

For more complex sweeps, a YAML matrix file gives you full expressiveness over parameters.

Adapters

agentctl supports multiple agent runtimes through an adapter model:

  • Claude Code (default): reads from ~/.claude/projects/, cross-references running processes.
  • Codex CLI: reads from ~/.codex/sessions/, supports codex exec for headless mode.
  • OpenCode: reads from ~/.local/share/opencode/storage/, supports opencode run.
  • Pi: reads from ~/.pi/agent/sessions/, uses Pi’s print mode for headless execution.
  • OpenClaw: connects to the OpenClaw gateway via WebSocket RPC (read-only).

Adding a new adapter means implementing the AgentAdapter interface. The CLI and daemon don’t change.

Integration with OrgLoop

agentctl fits into a larger picture. In my setup, it’s one layer of three:

LayerRole
agentctlRead/control interface. Discovers sessions, emits lifecycle events.
OrgLoopRoutes lifecycle events to mechanical reactions.
OpenClawReasoning layer. Makes judgment calls.

agentctl streams lifecycle events as NDJSON:

agentctl events --json
{"type":"session.stopped","adapter":"claude-code","sessionId":"abc123","timestamp":"2025-06-15T11:00:00.000Z","session":{...}}

OrgLoop consumes these events and routes them to reactions. A session completes, OrgLoop checks the result, and an OpenClaw agent decides what to do next: re-launch with different instructions, open a PR, or flag it for human review.

This event-driven pattern means agentctl stays simple. It doesn’t need to know what should happen when a session finishes. It just says what happened, and the layers above decide.

Fuse timers

When running agents in worktree-per-branch workflows with Kind clusters, forgotten clusters accumulate. Fuse timers handle this automatically: when a session exits, agentctl starts a countdown. If no new session starts in that directory before the timer expires, the associated cluster gets deleted.

# See active fuses
agentctl fuses

Small feature, but it’s saved me from more than a few resource leaks.

The daemon

agentctl runs a lightweight daemon that provides session tracking, directory locks, fuse timers, and Prometheus metrics. It auto-starts on your first command and can be installed as a macOS LaunchAgent for persistence:

agentctl daemon install    # Auto-start on login
agentctl daemon status     # Check health

Prometheus metrics are exposed at localhost:9200/metrics for monitoring active sessions, lock counts, session durations, and fuse activity.

Where this is heading

I believe headless coding agents will become the default path sooner than most people expect. Not that interactive sessions go away, but that the majority of code written will come from headless agents. agentctl is the supervision layer I needed to make that work, and it’s a piece of what I’ve been building toward: organizations as code for autonomous intelligence scaling.

Open source, MIT licensed. Try it out:

npm install -g @orgloop/agentctl

GitHub: github.com/orgloop/agentctl