Config-driven multi-agent CLI.
oa gives Claude, Codex, OpenCode, Pi, and any future agent CLI a single normalized interface. Every backend gets the same flags, the same JSON and streaming output, and portable conversation threads. Adding a new agent is a JSON config edit — no code required.
- At least one supported agent CLI installed and signed in (e.g.,
claude,codex,opencode, orpi)
Homebrew:
brew install 1broseidon/tap/oaOr with Go:
go install github.com/1broseidon/oneagent/cmd/oa@latest# Talk to Claude (the default backend)
oa "explain this codebase"
# Use a different backend
oa -b codex "fix the auth bug"
# Machine-readable JSON output
oa --json "explain this codebase"
# Live text stream
oa --stream "review the repo"
# Normalized JSONL stream (for piping to other tools)
oa --jsonl "review the repo"
# Portable threads — start on one backend, continue on another
oa -t auth-fix "investigate the failing auth tests"
oa -b codex -t auth-fix "patch the bug"
oa -b claude -t auth-fix "summarize what changed"
# Specify model and working directory
oa -b pi -m "google/gemini-2.5-pro" -C ~/project "add tests"
# Resume a native session
oa -b claude -s abc123 "now refactor it"
# Pipe content as context
git diff | oa -b claude "review these changes"
cat internal/auth/handler.go | oa -b codex "find bugs in this file"
go test ./... 2>&1 | oa -b claude "fix these test failures"
# Thread management
oa thread list
oa thread show auth-fix
oa thread compact auth-fixWorks out of the box if claude, codex, opencode, or pi is installed and signed in.
oa works as a dispatch layer for agents that want to delegate work to other agents. An outer agent (e.g., Claude Code) can run oa as a background task to send a targeted edit to a different model, then inspect the diff when it's done:
# From inside an agent session — dispatch a file edit to gpt-5.4 via Pi
oa -b pi -m openai-codex/gpt-5.4 "Edit internal/auth/handler.go: add rate limiting to Login" --jsonl
# Verify the result
git diff internal/auth/handler.goThe normalized output means the outer agent can parse results from any backend without special handling. Portable threads let you chain follow-ups across models.
An agent skill is included for agents that support the Agent Skills format. Install it with:
npx skills add 1broseidon/oneagent --skill oa-dispatchChain agents sequentially with && and shared threads. Each step sees the file changes and conversation context from previous steps:
# Build → review → document, different agents, one thread
oa -b codex -t feat "add input validation to the signup handler" && \
oa -b codex -t feat "review the changes, run tests, report any issues" && \
git diff | oa -b claude "write a changelog entry for this change"Pipe content into any step as context:
# Feed test output to an agent for diagnosis
go test ./... 2>&1 | oa -b claude -t fix "diagnose these failures and fix them"
# Code review a specific diff
git diff main..HEAD | oa -b codex "review this PR for security issues"
# Summarize a log file
cat /var/log/app/errors.log | oa -b claude "summarize the error patterns"Piped content becomes context. Positional args become instructions. If both are provided, they're combined.
By default, oa prints plain text:
Here's what I found...
With --json, every invocation returns a normalized response:
{
"result": "Here's what I found...",
"session": "abc123-def456",
"thread_id": "auth-fix",
"backend": "claude"
}With --stream, you get live text output with activity indicators on stderr:
[activity] Read README.md
OK
With --jsonl (or --stream --json), output is normalized JSONL — one event per line:
{"type":"start","run_id":"run-...","ts":"2026-03-22T15:04:05Z","backend":"claude"}
{"type":"session","run_id":"run-...","ts":"2026-03-22T15:04:05Z","backend":"claude","session":"abc123-def456"}
{"type":"activity","run_id":"run-...","ts":"2026-03-22T15:04:06Z","backend":"claude","session":"abc123-def456","activity":"Read README.md"}
{"type":"heartbeat","run_id":"run-...","ts":"2026-03-22T15:04:15Z","backend":"claude"}
{"type":"delta","run_id":"run-...","ts":"2026-03-22T15:04:16Z","backend":"claude","session":"abc123-def456","delta":"OK"}
{"type":"done","run_id":"run-...","ts":"2026-03-22T15:04:16Z","backend":"claude","session":"abc123-def456","result":"OK"}Events are intentionally simple: start, optional session / activity / delta, library-emitted heartbeat while the process is alive, then done or error. Each event also carries a run_id and timestamp so supervisors can track liveness per attempt.
Threads let oa own the conversation history instead of relying on a single backend's session.
- Same backend, same thread: reuses that backend's native session when it was the last to contribute.
- Different backend: rebuilds context from saved turns and continues on the new backend.
- Concurrent-safe: thread files are locked during read/write, so a bot and a cron job can safely share a thread.
--threadand--sessionare mutually exclusive.- Threads are stored locally in
~/.local/state/oneagent/threads/<id>.json.
Use oa thread compact <id> to summarize older turns and keep long-running threads manageable.
Run commands before or after agent execution with --pre-run and --post-run:
# Set up a worktree before running, notify after
oa -b codex -t feat \
--pre-run 'git worktree add -b oa-$OA_THREAD_ID ../oa-$OA_THREAD_ID HEAD' \
--post-run 'curl -s -X POST https://hooks.example.com/notify -d @-' \
"add input validation"
# Post-run hook receives the result on stdin
oa -b claude --post-run 'cat > /tmp/last-result.txt' "explain this codebase"Pre-run hooks abort the run on non-zero exit. Post-run hooks are best-effort. Both receive env vars: OA_BACKEND, OA_THREAD_ID, OA_SOURCE, OA_MODEL, OA_CWD. Post-run adds OA_SESSION, OA_ERROR, OA_EXIT.
Hooks can also be set per-backend in config (run on every invocation) and as Go callbacks in the library. See docs/library.md and docs/config.md.
oa ships with built-in defaults for claude, codex, opencode, and pi — no config file needed.
To override a built-in backend or add a new one, create ~/.config/oneagent/backends.json:
{
"my-agent": {
"run": "my-agent --prompt {prompt} --model {model}",
"format": "json",
"result": "output.text",
"session": "session_id"
}
}Same-named entries replace the built-in. New entries are added alongside defaults. Use -c /path/to/backends.json to load only a specific file.
For the full config schema, field reference, match conditions, and example backends, see docs/config.md.
go get github.com/1broseidon/oneagent@latestimport "github.com/1broseidon/oneagent"
backends, _ := oneagent.LoadBackends("")
client := oneagent.Client{Backends: backends}
// One-shot
resp := client.Run(oneagent.RunOpts{
Backend: "claude",
Prompt: "explain this code",
CWD: "/path/to/project",
})
fmt.Println(resp.Result)
// Streaming
client.RunStream(oneagent.RunOpts{
Backend: "claude",
Prompt: "review the repo",
}, func(ev oneagent.StreamEvent) {
fmt.Print(ev.Delta)
})
// Portable threads
resp = client.RunWithThread(oneagent.RunOpts{
Backend: "claude",
ThreadID: "auth-fix",
Prompt: "continue debugging",
})For the full library API, streaming details, custom thread storage, and integration patterns, see docs/library.md.
| Backend | CLI | Session resume |
|---|---|---|
| Claude | claude |
--resume |
| Codex | codex exec |
codex exec resume |
| OpenCode | opencode run |
--session |
| Pi | pi |
--session |
Any CLI that outputs JSON or line-delimited JSON can be added via config.
- Changelog
- Go library guide
- Backend config reference
- Integration example
- Agent skill for dispatch
- Example scripts — multi-agent conversation, self-healing tests, parallel review, daily digest
- Troubleshooting