Skip to content

The agent loop

Every tool description teaches the workflow, the server’s instructions spell it out, and most results end with the next step, so most agents follow this loop without extra prompting. If you’re writing your own agent, this is the shape to follow.

{ "name": "introduce", "arguments": { "name": "auth-refactor", "where": "api · feat/auth", "project": "api" } }

Use the name your human already knows the session by: its title in the CLI or app, the terminal tab, or the branch. The board shows it on the cards you hold and in its list of agents, so someone running ten Codex windows can tell which is which. Names are unique among live agents; if yours is taken, you get a numbered one (auth-refactor-2). Leave the name out and the board picks one, like jade-heron. Either way, title your session with the name you got if your client lets you, so the app and the board agree. It works the other way too: when the human asks “what is auth-refactor doing?”, any agent can answer with find_cards and held_by.

The human can rename an agent from the board’s list of agents. Its next tool results then carry the new name and ask it to retitle its session; the news repeats on reads and stops after the first call that changes something.

Some clients keep no MCP session (the Claude apps’ connector, for one), so every chat on a token reaches Yokka as one connection. There, introduce makes each chat an agent of its own and answers with an as name: pass it to claim_card (and to find_cards with held_by_me) so the board knows which chat holds which card. Tools that name a card you hold act as you without it. See Subagents and helpers.

project is optional: it makes that project your session’s default, so tools that take a project use it when you leave it out. Claiming or filing a card in another project moves the default there. On a shared connection the default is your own chat’s, and applies to calls made with your as name.

Call list_projects if you don’t know which project to work in, then get_board to see its swimlanes, lanes and cards. With more than one project and no default yet, name it on every tool that takes one: reads use the oldest active project, but tools that change the board refuse to guess, and so does get_next_card.

Track the work your human asks for on the board. Before starting a task, find its card with find_cards or take the next one with get_next_card; if they ask for something that isn’t on the board yet, file it with add_card and claim it before you start. Questions, explanations and quick lookups don’t need a card. The server’s instructions say this to every agent, so it holds without an AGENTS.md in the repo.

{ "name": "get_next_card", "arguments": { "swimlane": "Bugs" } }

get_next_card returns the top unclaimed card waiting to be picked up. Leave out swimlane to search every swimlane from top to bottom. Cards blocked by work that isn’t done yet are skipped. It only looks: to take the next card in one step, call claim_card without a card (step 3). Then read the brief in full:

{ "name": "get_card", "arguments": { "card": "YK-12" } }
{ "name": "claim_card", "arguments": { "card": "YK-12", "note": "Reproduce, then fix the date parsing" } }

Claim a card when you start on it, not a batch up front: the board then shows what you’re actually doing, and cards you haven’t reached stay free for other agents. The board moves the card (usually to Working) and shows your agent on it. The claim is refused if another active agent holds the card, or another live session of the same agent (same token and client) does, such as a second window. A blocked card is refused too, naming the cards it waits on; pass force: true only when the human asked you to work on it anyway.

When you plan work that has to happen in order, say so on the board: file each card with blocked_by listing the cards it waits on (update_card takes add_blocked_by and remove_blocked_by too). Agents then pick the cards up in order, and several can work the plan in parallel without starting anything too early.

Leave out card to claim the next card waiting, exactly as get_next_card would pick it (it takes the same project, swimlane and epic), in one step: cards other agents hold are skipped, so two agents asking at once never get the same card. The reply names the card it claimed and includes its brief, or says nothing is waiting.

{ "name": "claim_card", "arguments": { "swimlane": "Bugs" } }

The claim lasts while you keep calling Yokka: any call keeps it alive. After the project’s stale time (30 minutes by default) without a call it lapses and another agent may take the card, so call report_progress during long steps. It doesn’t lapse while a question you asked with request_input waits on the human. After a restart, find_cards with held_by_me shows the cards you still hold.

Subagents share their parent’s connection, and so do the chats of a client that keeps no MCP session (the Claude apps’ connector, for one). Yokka can’t tell them apart by the request, so each names itself when it claims:

{ "name": "claim_card", "arguments": { "card": "YK-12", "as": "date-parsing" } }

The board then shows date-parsing as its own agent, listed under the session that started it, with its own claims. Every tool that names a card it holds (report_progress, check_items, complete_card…) acts as it, so it only passes as when it claims. Use the same name for every card it claims; find_cards with held_by_me and as lists just its cards, and without as lists every helper’s.

Before you change anything, write your plan as the card’s checklist. Only a one-line fix may skip it:

{
"name": "set_checklist",
"arguments": { "card": "YK-12", "items": ["Add a failing test for ISO week dates", "Fix the parser", "Update the changelog"] }
}

The card shows 0/3 and a progress bar that follows the items. Tick each item off when it’s really done, by the number set_checklist and get_card show:

{ "name": "check_items", "arguments": { "card": "YK-12", "items": [1] } }

If the card already has items when you read it, the human wrote them: they’re the acceptance criteria. Keep them (set_checklist never drops them) and work through them. Call set_checklist again with the full list to revise your plan; unchanged items keep their number and tick. When complete_card succeeds, its reply lists any items still open.

{
"name": "report_progress",
"arguments": { "card": "YK-12", "message": "Added a failing test for ISO week dates", "step": "Tests" }
}

The human watches the board live, so keep each message to one short line and only report work you’ve done. On a card without a checklist you can pass a rough percent; with one, the bar follows the items.

When a long step starts (a test suite, a build, a deploy), say so, and say how it ended. Otherwise the board shows your last milestone for as long as the step runs, and the card looks stuck.

If you hand part of a card to a subagent, give it the card’s ref and have it call check_items and report_progress on that card itself. The human only sees what reaches the board: a helper that works for 25 minutes without a call looks, on the board, like nothing happening. Keep each card’s changes able to ship on their own, so you can complete one card while others are still in progress.

{
"name": "request_input",
"arguments": { "card": "YK-12", "question": "Should week 53 roll over to week 1 of next year, or stay 53?" }
}

The card is flagged for the human and usually moves to Needs you. The reply includes a cursor. Wait for the answer by reading only what happens after it, with wait_seconds:

{
"name": "get_card_activity",
"arguments": { "card": "YK-12", "after": "<cursor from request_input>", "wait_seconds": 50 }
}

The call answers as soon as a person (or another agent) adds something after the cursor, or after up to 50 seconds, and counts as one call however long it waits. A commented entry from a person is their answer. If there’s nothing yet, call it again with the cursor it returned. If the human answers you somewhere else (in your chat), note their answer with comment_card and "answers_question": true before carrying on: that takes the “your turn” flag off the card. Moving the card on yourself (say back to Working) does the same. Use comment_card to reply or leave a note without flagging the card again.

When the human should pick between a few choices, pass 2 to 6 of them as options so they can answer with one tap. For visual choices, attach each image with attach_file first and name the files in option_files, in the same order. Their reply’s picked says which option they chose, counting from 1.

When you can, attach proof: a screenshot of the change, a short screen recording or a test report.

{
"name": "attach_file",
"arguments": { "card": "YK-12", "filename": "week-53-tests.txt", "caption": "Year-boundary tests passing" }
}

The reply is a single-use upload link: PUT the file’s bytes to it within 15 minutes and it appears on the card. Attach before you complete the card.

If your app tells you what the work cost, send it with report_usage before you complete the card: tokens, the cost in US dollars, the model and how long you worked. The card shows its total, and on Team and up the workspace adds it up by epic, project, person, agent and month.

{
"name": "report_usage",
"arguments": {
"card": "YK-12",
"model": "claude-opus-4-5",
"input_tokens": 1200,
"output_tokens": 48000,
"cache_read_tokens": 2100000,
"cache_write_tokens": 96000,
"cost_usd": 3.81,
"duration_ms": 1260000
}
}

Send your running totals for the card, not just the latest step: each report replaces your last one for that card, so you can report after every milestone and nothing is counted twice. Every number is optional. Runs a runner started report their usage on their own, and the runner’s numbers win over yours for the same run.

{
"name": "complete_card",
"arguments": {
"card": "YK-12",
"summary": "ISO week parsing now handles week 53; added tests for year boundaries.",
"links": ["https://github.com/acme/app/pull/481"]
}
}

The board moves the card on, usually to Review (or Verify for bugs), where the human checks it before it’s shipped. If you can’t or shouldn’t finish, use release_card with a one-line reason instead, and the card goes back to be picked up again.

Before you stop, leave every card you hold completed, released, or waiting on the human with request_input; find_cards with held_by_me lists them. A session that ends without doing so leaves its cards claimed until the board lets them go: when its client closes the MCP session, or once the claim has been stale for twice the project’s stale time.

Found something out of scope? File it rather than doing it:

{
"name": "add_card",
"arguments": {
"title": "Date picker ignores the workspace locale",
"brief": "**What:** …\n\n**Why:** …\n\n**Done when:** …",
"swimlane": "Bugs",
"labels": ["frontend"]
}
}

Leave every card you hold in a state the board can explain: completed, released, waiting on the human with request_input, or with a report_progress line saying what it waits on (“e2e suite running in the background”). A card left in Working with a stale message is the one thing the human can’t tell apart from a stuck agent.

stop_check finds those cards: the ones you hold that aren’t done, released or waiting on the human, and that you haven’t said anything about for 10 minutes or more. It answers {} when there are none, and otherwise:

{
"decision": "block",
"reason": "Before you stop: the board shows this card as in progress, with no word from you for a while.\n- YK-12 \"Fix week 53\" (Working, last update 22 minutes ago: \"Running the e2e suite\")\n..."
}

That’s Claude Code’s Stop hook format, so Claude Code can run the check for you at the end of every turn: see the Claude Code tab of the Quickstart. The hook sends the agent back once per turn at most (stop_hook_active is true the second time, and then the check lets the turn end), and if the connection is down the turn ends as usual. Agents in other apps can call stop_check themselves before they stop.

See the tool reference for every argument.