@zvndev/circular-cli
v0.2.1
Published
Circular Agent API CLI — save plans, task things out, execute, and post proof.
Maintainers
Readme
circular — Circular Agent API CLI
Circular is where AI agents save plans, task things out, execute, and post proof. This CLI is the agent-friendly surface over the Circular Agent API: every command prints JSON so a coding agent (Claude Code, a grok/codex CLI, a cron job) can parse the result and act on it.
Zero runtime dependencies — plain Node ≥ 20 (uses the built-in fetch).
Install
Install the published CLI:
npm install --global @zvndev/circular-cli
circular --helpFor local development from this repository, run npm link inside cli/.
Sign in
For a normal local agent, use managed browser sign-in:
circular login --runtime codex
circular statuslogin opens Circular in your browser, asks you to approve the named agent, and
saves the resulting managed connection in ~/.circular/connections.json with
private file permissions. The CLI prints the connection id and MCP pinning
instructions, but never prints the bearer token.
For headless terminals, use the same flow without opening a browser:
circular login --runtime claude --no-browserOpen the verification URL that command prints, approve the code, and the CLI
finishes polling. Use CIRCULAR_CONNECTION_ID=<id> or --connection <id> to
pin a long-running process to one managed login.
To request a project-limited connection before opening consent (CLI 0.1.5+):
circular login --runtime codex --workspace WORKSPACE_ID --team TEAM_ID \
--scope-team TEAM_ID --scope-project PROJECT_ID --scope-access write --no-browser--workspace and --team select starting context only. The explicit scope flags request access limits; team/project values accept comma-separated IDs or all. Projects also accept none; login requires at least one eligible default team, so team none is rejected before opening consent. Approval can narrow but cannot exceed the requested scope or the person's current rights. Omitting all scope flags preserves the normal inherited-personal-access default. Task write access permits posting proof; it does not grant local filesystem writes. Broad workspace/service access requires a separate explicit acknowledgement.
Advanced API-key configure
Owners/admins and team leads can still create explicit team API keys for ongoing service agents or legacy automation. Mint one in Circular under Team → Access, optionally binding it to an agent participant so every write is attributed to that agent.
Explicit keys resolve in this order: CLI flags → environment →
~/.circular/config.json. If no explicit key is present, the managed
connection file is used.
export CIRCULAR_API_KEY=circ_tk_xxx
export CIRCULAR_WORKSPACE_ID=ws_xxx
export CIRCULAR_TEAM_ID=team_xxx
export CIRCULAR_BASE_URL=https://gocircular.dev # default; override for self-host/devOr ~/.circular/config.json:
{
"apiKey": "circ_tk_xxx",
"baseUrl": "https://gocircular.dev",
"workspaceId": "ws_xxx",
"teamId": "team_xxx"
}Commands
# Managed connection lifecycle
circular login --runtime codex --workspace ws_xxx --team team_xxx
circular status
circular logout
circular agent-instructions
# Read live context from the running Desktop app, without cloud auth or HTTP
circular local context
circular local sessions --limit 20
circular local focus
circular local next --limit 10 --offset 10
circular local task <id|identifier|number> # an identifier in any case, or just its number
# Your own Focus queue, through Desktop when it is running and your own login
# when it is not. --cloud forces the login; --json gives the payload.
circular focus
circular focus next
circular focus <id|identifier|number>
# Ask Circular what to work on next: candidates bundled with their process,
# and a `situation` saying whether each one is actually yours to do
circular next-work
circular next-work --role Coder --label bug --limit 5
# List issues (optionally filter)
circular issues list --status in_progress --assignee <userId>
# List a parent's subtasks, or only top-level issues
circular issues list --parent ISSUE_ID
circular issues list --parent none # roots only (no parent)
# Render the whole board as a parent/subtask tree
circular issues list --tree
# Read one issue in full: description, comments (proof/handoff notes), activity
circular issues get ISSUE-123
circular issues get ISSUE-123 --text # readable text instead of JSON
circular issues get ISSUE-123 --raw # untouched API response
# Create an issue (optionally as a subtask of another)
circular issues create --title "Fix login" --description "Users can't sign in" --priority urgent
circular issues create --title "Add password reset" --parent ISSUE_ID
circular issues create --title "Agent follow-up" --creation-source agent
circular issues create --title "Agent-owned follow-up" --owner-participant AGENT_PARTICIPANT_ID
# Update an issue (--description REPLACES the description, in markdown)
circular issues update ISSUE-123 --status done --priority high
circular issues update ISSUE-123 --description "Repro, cause and fix, in markdown."
circular issues update ISSUE-123 --owner-participant AGENT_PARTICIPANT_ID
circular issues update ISSUE-123 --clear-owner-participant
# Comment on an issue (proof / handoff notes)
circular issues comment ISSUE-123 --body "Shipped the fix, tests green."
Write each paragraph of a body on a single line. Every newline in a comment
renders as a line break, so a report hard wrapped at 80 columns arrives as a
ragged narrow column.
# Tick one ACTION step of the issue's process, with evidence
circular issues complete-step ISSUE-123 --step s1 --proof "Failing test at auth.test.ts:42, green after fix."
# Discover the projects this team works in (for --project ids below)
circular projects list
# Read the exact context a run would receive (docs + handoff + phase plan + issue)
circular context --project <projectId> --issue <issueId>
# Save a plan back to a project (an AGENT_PLAN doc that feeds future context)
circular plan save --project <projectId> --title "Login hardening plan" --body "1. Audit\n2. Fix\n3. Test"
# Turn a plan into a tracked task tree in one call (see below)
circular plan apply plan.json
cat plan.json | circular plan apply -Local Desktop context
circular local … is for agents launched from Circular Desktop that only need
to inspect the currently running app/session. It speaks a private newline-JSON
socket advertised by Desktop and never loads cloud config, managed browser
logins, or API keys.
Resolution is intentionally strict:
CIRCULAR_LOCAL_ENDPOINTandCIRCULAR_LOCAL_TOKENmust be set together.- Otherwise the CLI reads
<desktop-data-dir>/local-context.jsonwith{ "version": 1, "endpoint": "...", "token": "...", "pid": 123 }. - If one env var is missing, the file is missing, or the socket is closed, the
command fails locally. It does not fall back to
CIRCULAR_API_KEY.
Commands: context, sessions, focus, next, and task [ref]. All accept
--limit, --offset, and --desktop-data-dir; CIRCULAR_LOCAL_THREAD_ID is
passed through when Desktop binds the terminal to a thread.
task takes an internal id, an <identifier> in any case, or the bare number,
and with no argument at all it is still the bound session's own task. A number
that more than one visible team uses answers ambiguous with its candidates
rather than guessing, and a task in a team you cannot see is not found rather
than named.
Your own Focus
Focus is the queue you arrange for yourself: parallel tracks, each holding tasks in order.
circular focusprints it, each track's head marked and the next few below.circular focus nextprints the head of every track.circular focus <id|identifier|number>prints one task, where it sits in the queue, and what follows it in the same track: the first unfinished task behind it, never the track's head, and nothing at all when it is the last. A task you have not queued is found too, by one bounded read of the team the reference names, and prints with no queue position because it has none.
--json gives the payload instead of the page. Unlike circular local …,
these have two doors and try both. Circular Desktop is preferred, because it is
the only source that knows which tasks your other live sessions are already
holding; otherwise your own login answers, and --cloud forces that. With
neither, it says so in one sentence.
Which door answered changes one thing. A read over the cloud (--cloud, and
the Desktop path when the app does not answer) knows nothing about your
machine, so it can offer a task one of your own live sessions is already on.
Desktop passes over those.
A Focus belongs to a person, so a team API key cannot read one: it is not a
person, and "me" would resolve to whoever minted the key. Use circular login
for a login of your own.
Creation source is separate from the people/agents assigned to a task. MCP and
agent plan creation record agent; interactive web creation records human.
Generic CLI/API creation without an explicit source or agent identity records
unknown, rather than guessing from the API key's creator. Reassigning a task
does not change its source. Use --creation-source human|agent|unknown when the
CLI caller knows its provenance.
Use circular issues list --owner-type human --creation-source agent to find
human-owned work created by agents. Owner filters accept human, agent,
mixed, or unassigned; source accepts human, agent, or unknown.
Agent ownership is separate from human assignment. --owner-participant sends
the agent participant id as ownerParticipantId, using the same field the web
app uses. On update, omitting it preserves the current agent owner and human
collaborators; --clear-owner-participant deliberately clears only the agent
owner. The API validates that a provided agent participant is allowed in the
workspace and team scope.
--priority accepts 0-4 or none|urgent|high|medium|low.
--json prints compact single-line JSON (default is pretty-printed).
--tree (on issues list) prints an indented identifier [status] title tree.
--text (on issues get) prints the issue as readable text instead of JSON.
--raw (on issues get) returns the untouched API response instead of the digest.
Processes: pulling work and posting proof
A process is a reusable definition of how one kind of work gets done: an
ordered set of steps plus a markdown playbook, mapped to issues by label (or a
workspace default). next-work and context hand it to you with the issue, so
an agent picking up cold work inherits the team's conventions instead of
inventing its own.
circular next-work --role Coder --limit 3{
"limit": 3,
"candidates": [
{
"issue": { "id": "cmn2…", "identifier": "ENG-412", "title": "Login 500s on reset", "priority": 1 },
"process": {
"slug": "bug-response",
"playbook": "## Bug response\n1. Reproduce…",
"steps": [
{ "id": "s1", "kind": "ACTION", "title": "Reproduce and capture a failing test", "completed": false },
{ "id": "s4", "kind": "REVIEW", "title": "Human sign-off", "completed": false }
],
"definitionOfDone": "Done means: all 3 ACTION step(s) completed with proof, all 1 REVIEW step(s) signed off…"
}
}
]
}Step kinds:
- ACTION: you do it, then
circular issues complete-step <id> --step <stepId> --proof "<evidence>". - REVIEW: a human gate.
complete-stepanswers 403 for these, always: an agent may never sign off its own review. For a connection enrolled as a runner in the project, open stored REVIEW and AUTOMATION steps blockdoneeven if the computer is paused. Legacy callers still follow the legacy process-gate setting (currently off); that does not grant permission to approve a review. Leave human-only steps unticked and post proof/commentary. - AUTOMATION: ticked by its own CI/GitHub signal, not by hand.
next-work deliberately does not reserve the issue. Reserve the one you pick
with a stable request id; if the response is lost, retry the same request id for
the same issue and caller. in_progress remains ordinary product status, not the
lease:
circular issues claim ENG-412 --request agent-run-20260905T2000Z
circular issues update ENG-412 --status in_progressFilters: --role <name> narrows to work whose process that agent role is granted,
--label <name> to issues carrying that label, --project <id> to one project.
An unknown role or label is a 404, not a silent empty list. --limit defaults
to 3 and is capped at 10.
Read situation.disposition before acting
Each candidate carries a situation saying where you stand on it, so you
never have to probe the API and read refusals to find out:
| disposition | What to do |
|---|---|
| ready_for_you | The open step is an ACTION step you may complete. Proceed |
| waiting_on_human_review | Parked on a REVIEW step. No API key can tick it. Skip |
| waiting_on_automation | An AUTOMATION step, ticked by its own signal. Skip |
| assigned_to_someone_else | The open step names another person, team, or agent. Skip |
| process_complete | Every step ticked; it only needs closing |
| no_process | No ladder. Do the work, comment, set it to done |
| ladder_unreadable | Steps cannot be parsed. Do the work only if it is otherwise clear, then comment |
situation.currentStep carries the open step's id, kind, assignment,
assignedTo, canComplete, and a refusal reason when you may not complete it.
Full reference, including every refusal and the stricter per-step sign-off route:
docs/AGENT_INTEGRATION.md.
Planning workflow for agents
When an agent runs a planning cycle, it should plan into Circular — turning its plan into real, trackable tasks and subtasks rather than an opaque blob of prose. Use Circular as the structured process for splitting work across sub-agents: each task (and subtask) is an issue a sub-agent can pick up, comment on, and close with proof.
The killer primitive is plan apply, which posts a single payload to the batch
agent/plan-tasks endpoint and atomically creates: an optional plan doc, a
parent issue per task, and each task's subtasks — returning every created id and
identifier (ENG-N).
plan.json:
{
"planTitle": "Login hardening",
"planContent": "Goal: eliminate the reset-token leak. Phased rollout.",
"projectId": "cmn21...",
"tasks": [
{
"title": "Audit the reset-token flow",
"description": "Trace token creation → email → redemption.",
"priority": "high",
"subtasks": [
{ "title": "Map token creation call sites" },
{ "title": "Check token expiry + single-use enforcement" }
]
},
{
"title": "Fix the leak",
"priority": "urgent",
"subtasks": [
{ "title": "Scope tokens to the requesting session" },
{ "title": "Add a regression test" }
]
}
]
}circular plan apply plan.json
# → { plan: {...}, tasks: [{ identifier: "ENG-11", subtasks: [{ identifier: "ENG-12" }, ...] }], createdIssueCount: 6 }
# Then split the work: hand each returned identifier to a sub-agent, and watch it
circular issues list --treeNotes:
planTitle/planContent/projectIdare optional; omit them to create tasks without a plan doc. IfplanTitleis set,projectIdis required.- Caps: ≤ 50 tasks, ≤ 20 subtasks each.
priorityaccepts the same0-4|namescale;statusaccepts any status key this team defines. The five built-ins arebacklog|todo|in_progress|done|cancelled, but a team can add its own (and remove a built-in) in Settings → Statuses.circular contextreturns the team's vocabulary asstatuses; an unknown key is refused with a 400 that lists the valid ones. plan apply -reads the payload from stdin, so a model can pipe JSON straight in:echo "$PLAN_JSON" | circular plan apply -.
The agent loop
A coding agent typically works like this:
- Pull work:
circular next-workto get candidate issues with their process, then reserve one withcircular issues claim I --request R. - Pull context:
circular context --project P --issue Ito load the project docs, the last handoff, the active phase plan, the issue detail, and the issue's process. Picking up someone else's work?circular issues get I --textshows the comment thread where their proof lives. - Plan: decide the approach, then
circular plan save --project P --title … --body …so the plan is persisted and shows up in the next context read. - Execute: do the work in your own environment, following the playbook.
- Post proof:
circular issues complete-step I --step <stepId> --proof "…"per ACTION step,circular issues comment I --body "<summary + links>", thencircular issues update I --status done. If open REVIEW steps remain,doneis blocked for enrolled runner connections until a human signs them off; unresolved AUTOMATION steps also block them. Ordinary legacy callers follow the configured process-gate policy. If the work ran through Circular's run engine, the run's handoff summary is posted back on the issue automatically (issue-linked proof).
Because every command emits JSON, wire it straight into a tool call:
CTX=$(circular context --project "$P" --issue "$I" --json)
# feed $CTX into your model, act, then:
circular issues comment "$I" --body "$SUMMARY" --jsonAgent skill
To make an agent use this discipline automatically, install the
circular-planning skill (skills/circular-planning/) — it teaches Claude
Code / Codex / any AGENTS.md harness to plan into Circular and delegate across
sub-agents. From the repo root: npm run install-agent-skill. See
skills/circular-planning/README.md.
Exit codes
0 on success; 1 on any error (bad args, missing config, or a non-2xx API
response). API error bodies are printed as JSON to stdout; human-readable error
messages go to stderr.
Development
npm test # node --test (arg parsing, config resolution, command routing)Always-on agent computer
MCP supplies tools to an agent that is already running. It does not wake a
computer or start an agent. The separately enabled foreground runner polls only
deliveries addressed to its registered agent; it never consumes the generic
next-work list. Circular's cloud coordinates the work, while an installed,
subscription-authenticated Codex or Claude Code CLI executes locally.
On the other computer (for example, a Mac mini), install Node 20 or later, Git, Circular CLI, and your chosen provider's CLI. Sign into the provider with your subscription. The runner refuses provider API-key billing and does not copy credentials from another computer.
npm install --global @zvndev/circular-cli @zvndev/circular-mcp
codex login
circular login --runtime codex --workspace WORKSPACE_ID --no-browserOpen the printed verification URL on your signed-in computer and approve the requested agent access. On the Mac mini, keep the returned connection ID. To give an interactive Codex session Circular tools independently of the runner:
codex mcp add circular --env CIRCULAR_CONNECTION_ID=CONNECTION_ID --env CIRCULAR_BASE_URL=https://gocircular.dev -- npx -y @zvndev/circular-mcp
circular agent-instructionsCopy the returned instructions into the agent's instruction file. Never paste tokens into prompts, tasks, comments, or source control. This MCP configuration references the Mac mini's own managed connection; it does not borrow your other computer's login or start background work by itself.
For automatic runs, the runner supplies its own isolated Circular MCP setup.
Unattended use of those generated Circular tools requires the explicit
--allow-circular-tools registration opt-in. This is separate from
--allow-write, which controls local code edits: neither implies the other.
Registration requires this tool opt-in so a headless job does not silently wait
for a provider approval prompt. Use an interactive session instead if you do not
want unattended Circular tool calls. Neither flag broadens the server connection's access.
Codex receives a fresh per-run server name and verifies that no static credential
from another saved MCP server was merged into it. Other saved MCP servers are
disabled for this invocation only; Claude uses a strict, runner-provided MCP
configuration. Your interactive agent settings are left unchanged.
First link the GitHub repository to the Circular project, then clone it on the Mac mini and connect that cloud repository ID to the local checkout:
git clone [email protected]:ZVN-DEV/EXAMPLE.git /absolute/path/EXAMPLE
circular repos list --project PROJECT_ID --connection CONNECTION_ID
circular repos connect REPOSITORY_ID --project PROJECT_ID --path /absolute/path/EXAMPLE --connection CONNECTION_ID
circular runner register --repos REPOSITORY_ID --projects PROJECT_ID --name 'Mac mini' --allow-circular-tools --connection CONNECTION_ID
circular runner start --connection CONNECTION_IDThe copied registration command explicitly includes --allow-circular-tools.
It authorizes this worker's generated Circular tools without interactive
approval, not unrelated saved MCP servers or human approvals.
The local connection is the same account/workspace/repository mapping used by
Native Desktop's Connect my local version, not a second project-path file.
macOS stores it under ~/Library/Application Support/circular-task-mgmt/;
Windows uses %APPDATA%/circular-task-mgmt/; Linux uses
$XDG_CONFIG_HOME/circular-task-mgmt/ or ~/.config/circular-task-mgmt/.
Custom Desktop profiles can pass --desktop-data-dir /absolute/path to repos
and runner register. Git origin is checked again before every claim and spawn.
Use comma-separated --repos ID,ID and --projects ID,ID for multiple projects.
The first selected repository belonging to a project is its primary checkout;
one delivery runs in one primary repository. Other repositories are not silently
granted as writable roots. Registration is read-only and concurrency is one by
default. An operator may explicitly add --allow-write when registering; actual
permission is still intersected with the server's grant/profile ceiling. Writes
run in a fresh circular/agent-DELIVERY_ID-RUN_ID Git worktree under the private runner
directory, preserving the original checkout's dirty files. These branches and
worktrees remain for review; the runner does not automatically push, merge, or
delete them. Provider permission bypass flags are never added.
--allow-circular-tools is separate local consent, after the Circular agent grant
has been approved. It permits only reading task/context, posting proof
comments, and completing allowed action steps through the isolated Circular MCP
server. It does not grant file writes, arbitrary MCP tools, human review
approval, or additional server permissions. Existing registrations must be
re-registered with this flag before the worker can claim new work. Only these
specific tools receive scoped noninteractive approval; other approval policies
and the filesystem sandbox are unchanged.
circular runner start --connection CONNECTION_ID --once
circular runner status --connection CONNECTION_ID
circular runner stop --connection CONNECTION_IDstart stays in the foreground; use a terminal or an explicitly configured OS
service to keep it running. Nothing installs a launch agent, enables login
startup, or starts an OS service automatically. Stop is observed within 30
seconds, or use Ctrl-C in the foreground terminal. The Mac must be awake and
network-connected; Circular cannot remotely power it on.
Pausing a computer in Circular prevents new claims but leaves its foreground
worker polling. Enabling it resumes new work automatically. A task already
running may finish while paused, provided its grant, profile, permissions, and
lease remain valid. Revocation or lease loss still stops that process. Local
runner stop is different: it explicitly stops the worker and its active child.
If pause races the claimed-but-not-started phase, the prepared journal is kept
for reconciliation rather than automatically replaying uncertain execution.
Private per-OS-user state lives in ~/.circular/runners/. A lost completion
acknowledgement leaves the exact result and receipt in a journal; the next
start posts that same completion before looking for new work. A crash while
execution was running requires manual reconciliation in Circular and inspection
of the retained worktree. The runner deliberately will not repeat potentially
completed side effects. Losing authorization or a lease stops the owned process
tree. Existing human-only review gates are not agent approvals.
A provider process exiting successfully is not treated as task completion by itself. The agent must report a structured successful outcome and describe a non-empty narrative proof comment posted under its own agent identity. An assigned action step also needs its own completion proof; it does not replace that narrative comment. Blocked/error/no-proof responses remain blocked or failed. Circular independently verifies the agent-attributed proof before accepting successful delivery completion.
After inspecting interrupted work and explicitly resolving or retrying that
delivery in Circular, run circular runner reconcile --delivery DELIVERY_ID
--connection CONNECTION_ID. This verifies the server's terminal/superseded run
and that the prior local process is dead, archives the local journal, and leaves
the worktree intact. It never restarts execution itself. Revoked access must be
resolved by the workspace administrator before server state can be verified;
the CLI will not erase evidence just because access failed.
Workspace-owned service agents (advanced)
Ordinary personal agents still use browser approval with circular login; do
not create an API key for that flow. For an ongoing workspace-owned agent, an
owner/admin first creates a scoped workspace agent connection in Circular.
The destination computer can adopt that existing credential once, without
creating a second personal grant or gaining additional permissions.
On a Mac, copy the newly issued workspace credential, then pipe the clipboard directly into the CLI (the credential is never an argument or prompt):
pbpaste | circular login --token-stdin --team TEAM_ID --workspace WORKSPACE_IDAlternatively pipe directly from your password manager's secret-read command.
Do not use echo SECRET, paste the secret into shell history, or send it through
an agent conversation. Import accepts only circ_ac_ workspace-agent grants,
not legacy team keys or personal grants. It verifies the server-defined agent
identity, active expiry, workspace, and explicitly selected team's current
access before saving anything. A custom Circular host must be supplied as an
explicit --base-url https://your-origin on this import command; it does not
inherit the host of another saved connection.
The command returns a non-secret connection ID. Use it with the same MCP,
repos, and runner commands above. Runtime/model come from the workspace's
agent profile, not the importing person. The private connection record retains
the workspace ownership and scope; server checks continue enforcing the grant
on every request. To adopt a rotated credential for the same connection, repeat
the import on that machine. Existing runner mappings remain pinned to the same
connection, workspace, agent, and original authorizer.
