Drisp CLI
Deterministic orchestration for non-deterministic agents.
AI coding agents are getting better at reasoning -- but long-horizon tasks still break. Prompts drift, results vary between runs, and there's no good way to package what worked into something your whole team can reuse. The agent isn't the bottleneck anymore. The harness is.
Athenaflow is a workflow runtime for Claude Code and OpenAI Codex. It adds structured workflows, real-time observability, session persistence, and a plugin system -- so agent-driven tasks produce consistent results across runs, teams, and models.
npm install -g @drisp/cli && drisp
Read the docs -- guides, workflow authoring, plugin API, and more.
Why Athenaflow?
Coding agents work well for one-off tasks. The moment you need repeatable, multi-step execution -- e2e test generation, migration plans, release workflows -- things fall apart:
| Pain point | What goes wrong |
|---|---|
| No reproducibility | Same prompt, same model, different output every time |
| Long-horizon drift | Without checkpoints, agents compound small mistakes into big ones |
| Prompts aren't portable | What one developer got working doesn't transfer to the team |
| Black-box execution | You see the final output, not the 40 tool calls that got there |
| CI as an afterthought | Most harnesses are built for interactive use, not pipelines |
Athenaflow introduces a workflow layer between you and the underlying harness. Workflows are declarative, versioned, and shareable -- they define prompt templates, multi-session loops with completion tracking, plugin bundles, isolation policies, and model preferences. The runtime handles the rest.
Workflows encode what works -- not just a prompt, but the loop logic, progress tracking, and tool config that make it reliable. Define once, run anywhere.
A marketplace for agent workflows -- browse, install, and update community-built workflows like packages.
Real-time observability -- a live terminal feed of every tool call, permission decision, and result as it happens.
Sessions persist and resume -- every run is saved to SQLite. Pick up where you left off with full state.
Harness-agnostic -- same workflows, same UI, same session model across Claude Code and Codex.
CI-native -- drisp run runs headlessly with safe defaults, JSONL output, and structured exit codes.
Get Started
1. Install -- requires Node.js 20+ and at least one harness (claude or codex) on your PATH.
npm install -g @drisp/cli
2. Run -- the setup wizard handles theme, harness verification, and your first workflow.
drisp
3. Explore
drisp resume # Pick up where you left off
drisp sessions # Browse past sessions
drisp workflow install e2e-test-builder # Install a workflow from the marketplace
Harnesses
| Harness | Status | Integration |
|---|---|---|
| Claude Code | Supported | Hook events forwarded over a local Unix socket |
| OpenAI Codex | Supported | Integrated via codex app-server protocol |
| opencode | Planned | Adapter placeholder; not yet enabled |
Workflows
Workflows package prompt templates, loop strategies, plugin dependencies, isolation policies, and model config into a single portable unit. Anyone can author and share them.
drisp workflow list # See what's installed
drisp workflow search # Browse available workflows
drisp workflow install e2e-test-builder # Install from the marketplace
drisp workflow use e2e-test-builder # Set as the active workflow
drisp workflow upgrade # Re-sync all from source
Manage marketplace sources:
drisp marketplace add owner/repo # Add a marketplace source
drisp marketplace add ./local/path # Add a local marketplace
drisp marketplace refresh # Refresh configured remote sources
drisp marketplace refresh owner/repo # Refresh one remote source
drisp marketplace list # List configured sources
drisp marketplace remove owner/repo # Remove a source
Install from a local file or a specific marketplace ref:
drisp workflow install ./path/to/workflow.json
drisp workflow install e2e-test-builder@lespaceman/athena-workflow-marketplace
CI / Automation
drisp run is built for pipelines. Safe by default -- permission and question hooks fail unless you opt in.
Human gates
In a Workflow Run, four actions stop the session for a member: plan approval, merges, the token budget, and irreversible actions (deploys, releases, publishes, destructive git). Set each one to ask or auto in .athena/config.json under "gates", or for one run with --gate merge=ask. Merges and irreversible actions are enforced by PreToolUse hook rules for the session and every subagent. See docs/human-gates.md for the matcher's rules.
Permissions with no hub attached
drisp has a single door. A permission request or a question is answered by whoever is attached to the Run: the interactive terminal, or the hub delivering decisions through the Runner. With no hub attached (a headless drisp run) there is nobody to ask, so under guarded and standard the request waits: the Run holds on the pending decision, and nothing is auto-approved or auto-denied on its behalf. The hold ends one of two ways. If you set --timeout-ms, the run exits with the timeout exit code when it expires. If the Run belongs to a workflow, the request is held, then parked, then replayed:
- Hold. A permission request no rule answers is held for the grace window —
permissionGraceMsin~/.config/athena/config.jsonor.athena/config.json(project wins),--permission-grace-msper run, default 60 000 ms — so an attached hub can answer it. With no hub attached there is nobody to wait for, and the request is deferred at once. - Park. When the window elapses unanswered, the tool call is refused with a deferred result, the Turn ends, and the Run parks in
awaiting_attentionwith the request recorded — its id, the tool, and a one-line input summary — in the Journal and on the run record.drisp runsshows the pending question and the hub receives it as aneeds_humanframe. - Replay.
drisp run --continue=<session> --answer=allow|deny "<reply>"(or ananswerthe hub sends while the Run is parked) stores the answer against that request. On continue the agent re-issues the same call and the stored answer is replayed into it without a prompt; without one, the request is held again. See human resume.
--isolation autonomous runs with nobody watching. A permission prompt that no ask rule claims is answered allow by the preset's policy, within the tool surface the preset already grants, and the workflow runs to WORKFLOW_COMPLETE. Exactly two things park the Run as needs-human: an ask rule that fires, or a Turn that ends on <!-- NEEDS_HUMAN: reason -->. Ask rules are a provisional, per-workflow list of tool-name patterns in workflow.json whose prompts must always reach a person — "askRules": ["Bash", "mcp__github__*"] — and a parked Run shows in drisp runs as Parked with the rule that fired (or the marker's reason); drisp run --continue wakes it.
A Workflow can allow the native subagent tools no preset grants. "allowedTools": ["SendMessage", "ToolSearch", "Monitor", "TaskStop", "ListAgents"] in workflow.json answers those permission prompts allow under any preset, in drisp run and the interactive session alike. Only these five names and exact MCP tool names (mcp__<server>__<tool>, no wildcards) are accepted; anything else fails validation. In drisp run an ask rule naming the same tool still claims it; the interactive session does not apply ask rules. The presets themselves stay as they are.
drisp run "summarize risk in this PR" # plain text
drisp run "run checks" --json --on-permission=deny --on-question=empty # JSONL
drisp run "write release notes" --output-last-message release-notes.md # artifact
GitHub Actions
name: drisp-run
on: [pull_request]
jobs:
drisp:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx @drisp/cli run "summarize risk in this PR" \
--json --on-permission=deny --on-question=empty \
--output-last-message drisp-summary.md
- uses: actions/upload-artifact@v4
with:
name: drisp-summary
path: drisp-summary.md
GitLab CI
drisp_run:
image: node:20
script:
- npm ci
- npx @drisp/cli run "summarize pipeline status" \
--json --on-permission=deny --on-question=empty \
--output-last-message drisp-summary.md
artifacts:
paths:
- drisp-summary.md
Exit codes
| Code | Meaning |
|---|---|
0 | Execution returned; check workflow outcome for completion or suspension |
2 | Usage / validation error |
3 | Bootstrap / configuration failure |
4 | Runtime / process failure |
5 | Reserved legacy policy code (no longer emitted) |
6 | Timeout exceeded |
7 | Output write failure |
Runner
drisp runner is the long-lived process that connects a machine to a Team on the Hub, receives its Runs, and executes them. Its state is one execution.sqlite store in the configured data directory.
Connect a machine from Settings → Runners in the web console. It shows a one-use Connect code, valid for ten minutes, inside the command to paste on the machine:
drisp runner connect K7QF-M2PX --hub https://hub.example.com # join the Team and start the Runner as a background service
drisp runner connect --hub https://hub.example.com # or print a code to approve in the web console
drisp runner status # the saved identity, without its credential, and the service's state
drisp runner restart # refresh the service definition and restart it, e.g. after npm install -g @drisp/cli@latest
drisp runner disconnect [<team>] # leave a Team: stop its Runs there and delete that Team's credential
drisp runner recover <run-id> confirmed-stopped # record a failed outcome after an uncertain crash
To start from the machine instead, run drisp runner connect without a code. It prints a Connect code and the page that approves it, and waits. A signed-in member opens the page, checks the machine's hostname, OS and drisp version, chooses the Team, and connects it. The code works once and expires in ten minutes; if it is denied or expires, connect exits with an error and writes nothing.
A machine is one Runner per Team. Connect writes that Team's Runner identity and credential to ~/.config/drisp/runners/<hub>/<team>.json (under the Hub's host, since two Hubs can serve the same organization), readable only by you, beside the machine's installation identity in ~/.config/drisp/installation-id. Connecting again to a Team the machine already serves reports This machine is already connected to this Team as <name> and leaves the credential alone, once the Hub has accepted that credential. When the machine can no longer serve that Runner (its identity file is gone, belongs to another Runner, or its credential lapsed), Connect exits 1 and says why without using a code; drisp runner connect <code> --hub <url> --replace then connects a new Runner in its place, and the old one stays listed, offline, until Runners can be Disconnected. An identity the Hub no longer recognises (installation identity reset, new Hub epoch) is moved aside to <team>.json.replaced-<time> when Connect writes the new one. A machine may serve several Teams, one Runner each: the background service and drisp runner run them all, and drisp runner status lists each Team. Nothing secret is downloaded or copied between machines. DRISP_HUB_URL can stand in for --hub, and DRISP_RUNNER_CONFIG instead names one identity file of a separate installation.
Any member of the Team can connect their own machine. It joins as a Personal Runner named after its hostname, which runs only its owner's Runs; a Team admin approving a printed code may make it a Team Runner instead.
Connect then installs and starts a user-level service, so the Runner keeps running when the terminal closes and starts again at login: a LaunchAgent com.drisp.runner.<installationId> on macOS (log in ~/Library/Logs/drisp/runner-<installationId>.log), a systemd user unit drisp-runner-<installationId>.service on Linux (log in journalctl --user -u drisp-runner-<installationId>). drisp runner status prints the service's name. drisp runner restart rewrites the service definition for the CLI installed now (a new Node version or global prefix) and restarts it (on Linux it also turns start-at-login back on, with systemctl --user enable); it never installs a service, and like any Runner restart it fails a Run in progress (ADR-0021), so run it when no Run is executing. The service runs this drisp installation with the saved identity and the PATH you connected from, so it finds the same harnesses; there is no unit file to write. Each installation (a DRISP_RUNNER_CONFIG identity file is a separate one) gets its own service, so connecting one never stops another. On a Linux server, loginctl enable-linger keeps it running while you are logged out. Running Connect again where the identity file already exists is refused before the code is spent, and leaves the service as it is. So if the service cannot be installed or started, Connect exits with an error but keeps the identity: run drisp runner to run the Runner in the foreground. On macOS, a LaunchAgent that was installed but would not start still starts at your next login.
In a container or CI, or on a platform without launchd or systemd (where Connect says so and does this anyway), keep the Runner in the foreground instead:
drisp runner connect K7QF-M2PX --hub https://hub.example.com --foreground # join the Team, then run in this terminal (Ctrl-C stops it)
drisp runner # run it in the foreground again later
The Runner's credential renews whenever it connects, so a machine that slept over a weekend rejoins by itself; only 30 days without contact lapse it, and then it needs Connect again. A Runner leaves its Team only by Disconnect: drisp runner disconnect on the machine, or Disconnect in the web console by the member who connected it (an admin may Disconnect any Runner). The Hub Stops every Run on it ("Stopped: Runner disconnected"), and the machine deletes that Team's credential, at once from the box, or the next time it reaches the Hub when Disconnected from the console. The service is removed once the machine serves no Team; until then it runs on for the others. On a machine serving several Teams, name the one to leave by the id or Runner name drisp runner status shows.
recover never retries execution: stop the service and every harness first, then start the runner again to publish the outcome.
drisp dashboard <sub> is a deprecated alias of drisp runner, removed in 0.7.0.
Configuration
Config merges in order: global → project → CLI flags.
~/.config/athena/config.json # Global defaults
{projectDir}/.athena/config.json # Project overrides
{
"harness": "claude-code",
"model": "sonnet",
"plugins": ["/path/to/plugin"],
"activeWorkflow": "e2e-test-builder"
}
CLI flags
| Flag | Description |
|---|---|
--project-dir | Project directory (default: cwd) |
--plugin | Path to a plugin directory (repeatable) |
--isolation | guarded (default) · standard · autonomous |
--theme | dark (default) · light · high-contrast |
--ascii | ASCII-only UI glyphs |
--verbose | Extra rendering detail |
run-only:
| Flag | Description |
|---|---|
--continue | Resume most recent run session (or --continue=<id>) |
--steer | Queue a human steer for the next Turn (repeatable) |
--json | JSONL lifecycle events on stdout |
--output-last-message | Write final assistant message to a file |
--ephemeral | Disable session persistence for this run |
--on-permission | allow · deny · fail (default) |
--on-question | empty · fail (default) |
--timeout-ms | Hard timeout for the run |
Commands
| Command | Description |
|---|---|
| (default) | Start interactive session in cwd |
setup | Re-run setup wizard |
sessions | Interactive session picker |
resume [id] | Resume most recent or specific session |
run "<prompt>" | Headless run for CI / scripting (exec is a deprecated alias) |
workflow <sub> | install · list · search · remove · upgrade · use |
marketplace <sub> | add · refresh · remove · list |
Development
npm install && npm run build # Build
npm test # Test
npm run typecheck # Type-check
npm run lint # Lint
npm run dev # Watch mode
Codex protocol bindings
Files in src/harnesses/codex/protocol/generated are auto-generated from the codex app-server schema -- do not edit by hand. Refresh with:
scripts/update-codex-protocol-snapshot.mjs
Commit the output so others can build without the generator.
License
For a plain-English source map, see the architecture diagrams. The execution contract explains continuation, configuration precedence, failures, and contributor checks.