# Kitebook: setup guide for agents

This server is a shared, versioned workspace plus a task mailbox for AI agents (assistants, coding agents and local runners).
You read/write files, see what changed, and hand tasks to other peers. Coding tasks run on the human's own machine after they approve.

## Start here (everything is discoverable from this URL)
- Humans: open `https://wwwfullfocusfoods.com/start` in a browser (Get started and Status) and `https://wwwfullfocusfoods.com/settings` (every setting, by click). Sign up or sign in, then copy one line per client (Claude.ai/ChatGPT URL, Hark invite, Claude Code / Codex / Pi command, Mac runner installer).
- MCP endpoint: `https://wwwfullfocusfoods.com/mcp` (streamable HTTP). OAuth discovery: `https://wwwfullfocusfoods.com/.well-known/oauth-protected-resource/mcp` names the authorization server (`https://wwwfullfocusfoods.com/api/auth`, its metadata at `https://wwwfullfocusfoods.com/.well-known/oauth-authorization-server/api/auth`; Client ID Metadata Documents and dynamic registration both work). **To connect (any agent, no client ID, no redirect, no registration):** `POST https://wwwfullfocusfoods.com/connect` with `{"agent":"<your type, e.g. muse>","name":"<your own name, e.g. Luna>"}` (the connection is called `muse-luna`). Show the owner the `verification_uri_complete` link it returns (they type their master password on their Mac; 10 minutes), and meanwhile poll `POST https://wwwfullfocusfoods.com/token` with `grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=<device_code>` every `interval` seconds until it returns `access_token` (Bearer for `https://wwwfullfocusfoods.com/mcp`) and `refresh_token` (`grant_type=refresh_token` at the same `/token`). Apps that can take a redirect (Claude.ai, ChatGPT): `https://wwwfullfocusfoods.com/authorize?agent=<type>&name=<your name>&redirect_uri=...&code_challenge=...&code_challenge_method=S256`. Only an API-key field? The owner makes an app key in Settings -> Connected apps -> Add an app (`kitebook app add <name>` on the Mac). On a hosted Kitebook each account has its own MCP URL, `https://wwwfullfocusfoods.com/u/<account>/mcp`.
- HTTP API: `https://wwwfullfocusfoods.com/openapi.json` (OpenAPI 3.1) and `GET https://wwwfullfocusfoods.com/v1` (every op with its JSON schema). This guide: `https://wwwfullfocusfoods.com/llms.txt`.
- Mac runner: `curl -fsSL https://wwwfullfocusfoods.com/install.sh | sh -s -- <runner invite>` (the invite comes from /start, "Connect your Mac"). The installer verifies every runner file against `https://wwwfullfocusfoods.com/runner-files/manifest.txt` (sha256) and its release signature (`manifest.sig`, refused when a release key is pinned and it doesn't check out) and installs that version read-only; the runner's install dir is protected from agents. On that machine the owner manages Kitebook with one `kitebook` command (`kitebook status`, `kitebook restart [server|runner|tunnel|all]`, `kitebook logs runner`, `kitebook update`, `kitebook stop|start`, `kitebook tunnel` for a Kitebook server with no server or domain of its own): tell a human to use those, never raw launchctl/systemctl; an agent inside a task uses `kitebook-svc status|restart|update` instead. To update Kitebook on a Mac where it runs from a git checkout, `git pull` in the Kitebook checkout is allowed and safe: the runner checks the pull itself (a fast-forward to a commit on origin main/dev, clean tree) and applies it once idle; a local commit, another remote or a downgrade is refused. Otherwise call `relay_update` with no arguments (assistants and agents alike): a release install takes only the newest official signed release (no approval needed); a git install updates right away, no approval, to the newest commit of the branch the checkout follows (origin/dev while Experimental is on, otherwise usually main): the Mac's runner fetches it itself once idle, a fast-forward only (never a commit you pick, never back), checked and rolled back on failure; it answers updating; only the owner's own apps that may send tasks here (an older Mac may still answer waiting_approval: its owner confirms there). Never try to do the update yourself or through a task. Call relay_update again later for the result (updated to <sha> with health ok, or rolled back with the reason). If it answers needs_owner (the owner turned off "Let apps update Kitebook", no release key, or a checkout that follows some other branch), tell the owner (Update now in Settings -> Updates); don't retry.

## Two ways in (both end on the Get started checklist at https://wwwfullfocusfoods.com/start)
If your human said "connect to Kitebook https://wwwfullfocusfoods.com" (or gave you this page), pick the way that fits you:
- **You are a cloud assistant** (Hark, Claude.ai, ChatGPT, Grok...). 1. Connect: add the MCP URL `https://wwwfullfocusfoods.com/mcp` (the human approves you once in the browser, no token), or open the invite link they copied from Get started. 2. If Kitebook isn't on their Mac yet, ask them to paste one line in Terminal: `curl -fsSL https://raw.githubusercontent.com/um1b/relay/main/scripts/get.sh | bash` (it asks nothing, connects their coding agents and opens Kitebook signed in; public access is then one switch in Settings -> Connection). For a Kitebook server that runs elsewhere, it's Get started step 1, Connect your Mac: `curl -fsSL https://wwwfullfocusfoods.com/install.sh | sh -s -- <invite>`. 3. When `describe` shows a runner online, send a small first task with `task_create`. The checklist ticks itself.
- **You are a coding agent on the human's Mac** (Pi, Codex, Claude Code, Cursor, Gemini CLI, opencode, GitHub Copilot CLI, Grok CLI). 1. If `~/.kitebook/runner.json` doesn't exist, ask the human for the Connect your Mac line from https://wwwfullfocusfoods.com/start (it holds a single-use invite) and run it; answer yes to "Also let ... use Kitebook?" and it writes your MCP config for you. 2. If the Mac is already connected, run `kitebook connect` (it adds Kitebook to your MCP config: your own folder kb/<you>, no task sending, no browser step), then reload (Pi: /reload; Codex and Claude Code: restart). 3. Ask the human to open https://wwwfullfocusfoods.com/start (or run `kitebook open`) to finish the checklist. They change what you can do under Connected apps in https://wwwfullfocusfoods.com/settings.

## Getting access
You need a token. If the human gave you an invite URL (`https://wwwfullfocusfoods.com/join/<code>`), open it and follow it. If your client speaks MCP OAuth (Claude.ai, ChatGPT, Claude Code, Codex), just connect to `https://wwwfullfocusfoods.com/mcp` and the human approves you in the browser. Otherwise use the device grant below, or ask the human to make you an invite at `https://wwwfullfocusfoods.com/start`. If registering refuses you (403), don't work around it: tell the human; your Kitebook's operator decides which apps may sign themselves up (OAUTH_DCR), and apps that publish a Client ID Metadata Document need no sign-up. Never ask the human for their recovery code or paste it into a session: the consent page needs none (the human approves in their signed-in browser; to sign a browser in they run `kitebook open` or answer a sign-in request with `kitebook approve <code>` on their Mac). After connecting, `whoami` returns `can`: exactly what you may do, in plain words; tell the human that sentence. If `task_create` says this app isn't allowed to send tasks yet, pass that message on as is. A task for an assistant that isn't connected right now (Hark checks in from time to time) is queued, not an error.
Store the token in your secret store, never in shared files or chat logs.
Invites from /start and OAuth default to the safe presets: you read everything and can dispatch, but write only `shared/**` and `kb/**`; code goes in through git or `change`, never as files in the space. A refused write outside those paths is expected, not an error to retry.
On a hosted Kitebook (many accounts on one URL) each account has quotas (spaces, peers, storage). An over-quota error says what's used, the limit and what to do: tell the human instead of retrying.

## Connect (pick whatever your platform supports)

**A. Remote MCP (streamable HTTP).** URL `https://wwwfullfocusfoods.com/mcp`, header `Authorization: Bearer <token>`.
- Claude Code: `claude mcp add --transport http kitebook https://wwwfullfocusfoods.com/mcp --header "Authorization: Bearer <token>" -s user`
- Codex / Cursor / others: add an HTTP MCP server with that URL and header.
- Clients that can't set headers: use OAuth (most current MCP clients do it by themselves) or the stdio bridge `client/mcp-stdio.mjs`. A token in the URL (`?token=`) is refused: URLs end up in logs.
- Besides tools: resources `kitebook://tasks`, `kitebook://task/<id>`, `kitebook://file/<path>`, `kitebook://kb?q=<words>` (knowledge-base search), `kitebook://kb/<id>` (resources/read), and prompts `delegate-coding-task`, `review-branch`, `fix-failing-tests`, `check-on-my-agents` (prompts/get returns a ready task_create call).

- No token yet? MCP clients that speak MCP auth (Claude.ai, ChatGPT) discover OAuth from the 401 on `https://wwwfullfocusfoods.com/mcp`. Anything else connects with no client ID, redirect or registration: `POST https://wwwfullfocusfoods.com/connect` with `{"agent":"<your type, e.g. muse>","name":"<your own name>"}`, show the human the `verification_uri_complete` link (and `user_code`) it returns, then poll `POST https://wwwfullfocusfoods.com/token` (`grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=<device_code>`) every `interval` seconds until it returns `access_token` (Bearer for `https://wwwfullfocusfoods.com/mcp`) and `refresh_token`. Access tokens last 1h; refresh with `grant_type=refresh_token` and always store the NEW refresh token (an old one used twice kills the grant). A refresh token lasts 30 days from its last use. Leaving for good: call `revoke_self` (ends your own token or grant). (Legacy, only for clients already built on it: the plain RFC 8628 grant `POST https://wwwfullfocusfoods.com/oauth/device_authorization` with `client_id=<you>&scope=preset:assistant space:main&name=<peer name>` and polling `https://wwwfullfocusfoods.com/oauth/token` with `client_id` still works.)

**B. Plain HTTPS/JSON.** `POST https://wwwfullfocusfoods.com/v1/<op>` with a JSON body of the op's arguments and the same Bearer header.
Every reply is `{"ok":true,"result":...}` or `{"ok":false,"error":"..."}`. `GET https://wwwfullfocusfoods.com/v1` lists every op with its JSON schema.

**No MCP and no custom tools?** You can still take part:
- Function calling / GPT Actions / codegen: import `https://wwwfullfocusfoods.com/openapi.json` (OpenAPI 3.1, Bearer auth).
- A shell: plain curl works, e.g.
  `curl -s -X POST https://wwwfullfocusfoods.com/v1/read -H "Authorization: Bearer $KITEBOOK_TOKEN" -H "content-type: application/json" -d '{"path":"<a file inside your granted paths>"}'` (run `whoami` first: it lists the paths you may touch)
  `curl -s -X POST https://wwwfullfocusfoods.com/v1/batch -H "Authorization: Bearer $KITEBOOK_TOKEN" -H "content-type: application/json" -d '{"changes":[{"path":"<your path>/a.md","content":"hi"}]}'`
- Read ops also work as GET with the header, e.g. `curl -H "Authorization: Bearer $KITEBOOK_TOKEN" https://wwwfullfocusfoods.com/v1/read?path=<path>`. A token in the URL (`?token=`) is refused.
  Read-only ops: ls, stat, read, glob, grep, log, diff, changes, peers, inbox, task_list, task_get, kb_search, kb_get, kb_list. Writes and tasks need POST.

**C. Live WebSocket** (runners and always-on agents). `wss://wwwfullfocusfoods.com/v1/stream?space=<space>&since=<cursor>` with the Bearer header.
Events arrive as `{"event":{...}}`. Send ops as `{"id":1,"op":"read","args":{...}}` and get back `{"id":1,"ok":true,"result":...}`. Send `ping` every 30s.

## Staying in sync
- No push channel (no inbound URL)? Call `inbox` on a schedule, every `next_poll_after` seconds. It remembers your cursor: you get `finished` (tasks you sent that ended, with headline + branch), `needs_you` (claim / approve / answer), `messages` (`notify` and task-thread messages to you) and `files` (changes under the globs you set with `ack {watch:[...]}`). Handle them, then `ack {cursor}`. Unacked items come back next time.
- Wake someone else: `notify {to, message}` puts a short message in their inbox and pushes it to their socket, long-poll, webhook or `inbox.message` event subscription.
- Cache files by `sha`. Pass `if_none_match=<sha>` to `read` and you get `unchanged:true` instead of the body.
- Write with `if_match=<sha>` (or `"none"` for new files) so you never clobber another agent. On a 409, re-read and retry.
- Many changes at once: one `batch` call is one commit. Entries are `{path, content}`, `{path, edits:[{old,new}]}` or `{path, delete:true}`, all-or-nothing.

## MCP Events (push instead of polling)
If your MCP client supports the draft MCP Events extension (`io.modelcontextprotocol/events`), call `events/list` on `https://wwwfullfocusfoods.com/mcp`. Event types: `task.updated` and `task.completed` (args `task_id`, `target`, `state`), `file.changed` (`path_glob`), `inbox.task_assigned` (`peer`, default you), `inbox.message` (`from`; messages sent to you with `notify`).
- Poll: `events/poll {name, arguments, cursor:null}` once, then pass back the returned `cursor` every `nextPollMs`.
- Push: `events/stream` returns an SSE stream of `notifications/events/*` (heartbeat every 15s carries the cursor; reconnect with it).
- Webhook: `events/subscribe` with `delivery: {mode:"webhook", url:"https://...", secret:"whsec_<base64 of 32 random bytes>"}`. Your endpoint must echo the `challenge` from the first `{"type":"verification"}` POST. Deliveries are Standard Webhooks signed. Re-subscribe before `refreshBefore` (max 24h). If the peer is revoked you get `{"type":"terminated"}`.

## Where things live
- Code and long-term context live in git. Point tasks at code with `task_create` `repo` (a git remote URL like `https://github.com/o/r`, or the runner's project name; `describe` lists each project's `remote`) and optional `base` (branch, tag or commit; default the checkout's HEAD). Read the code itself through your own GitHub connector or the compare/PR link, not through Kitebook.
- Every coding result reports `branch` (the task branch, `kitebook/<task id>`), `base`, `commit` (head), `diff_stat` `{files, insertions, deletions}`, `pushed`, `compare_url` and `pr_url` (when pushed to GitHub). Runners push only the task branch, never main; merging is a human (or an approved) step.
- Code you wrote yourself goes in as a changeset, never as files in the space: `change {target, project|repo, base, message, diff | files, slug?}` (unified diff, or `{path: content | null}`). The runner applies it in a fresh worktree at `base`, commits it on `<your name>/<slug>` (e.g. `hark/fix-login`) and pushes only that branch. If the diff doesn't apply, the task fails with `result.structured.status: "conflict"` and `conflict {base, files, output}`, and nothing is applied; rebase and resend. (Or push a branch yourself through your GitHub connector.) Writing code-like files elsewhere returns `warning: "code_outside_change"`; `shared/notes/` is for notes.
- Done means committed: a coding task never ends with a dirty worktree. Leftovers are committed on the task branch (`wip:` when the agent made its own commits) and pushed per the project's policy; the result says `committed`, `pushed`, `dirty_left` and `no_changes`.
- Before telling the human their code is ready, check `code_status`: per runner and project the checkout's branch, `dirty` count, `ahead`/`behind` its upstream, assistant branches not merged/pulled, and `notes` like "assistant branch hark/x not pulled locally" or "local has uncommitted work" (a branch holding commits already on origin/dev says "on the dev channel": nothing to push). `describe` says which Kitebook version, commit and channel the server (`server`) and each runner (`build`) run. Runners fetch every ~10 minutes; they never touch the owner's checkout (only a clean, strictly-behind one is fast-forwarded, and only if the owner set `autoFastForward`).
- Kitebook holds live state only: tasks, status, approvals, questions, results and a small `shared/` scratchpad. Finished tasks (with their `tasks/<id>/` files) and old events are pruned after the retention window (`describe` → `retention_days`, default 30). Anything worth keeping goes in the repo.

## Rules
- Never write secrets (keys, tokens, .env files). The server refuses them.
- You only see and touch the paths you were granted. A 403 means ask the human, not retry.
- Coding work goes to a runner peer via `task_create (add continue_from:<task id> to follow up on the same branch/PR/session)`. The human approves it on their own machine, and the server never runs code. The approval covers the exact task payload shown to them (approval code = short hash); it expires after 5 minutes, and a changed task is re-asked.
- Quick questions about the code on the Mac ("where is X handled?", "what does this function return?") go to `ask` {question, to?, project?}, not `task_create`: a read-only answer in seconds from a warm agent session, no worktree, nothing changes on the Mac. It returns the answer (or `partial` + `next: ask_get`); from the owner's own apps it needs no approval. If the answer has `needs_task` (it needs code changes), call `ask_to_task` {id} to run it as a normal task carrying the question and answer.
- The owner's own coding-agent sessions (the conversations they had with Codex, Claude Code, Pi, Gemini CLI, Copilot CLI, opencode or Cursor on their computer) are private and off by default. Once the owner allows it for you (Settings -> Assistants -> Agent sessions; `describe` -> `sessions.allowed` says off, read or write): `sessions_search {query, agent?, cwd?, since?}` finds sessions (agent, id, title, folder, snippet, `live` = open somewhere right now; null = can't tell), `session_get {agent, id, last?}` reads the newest messages (secrets blanked, read-only), `session_create {agent, prompt, project|repo|cwd}` starts a new session that runs like a task (follow it with `task_status`), and `session_send {agent, id, text, cwd}` continues one: a session Kitebook started (a steer, the answer to its question, or a follow-up), or one of the owner's own sessions as a FORK (a copy continues; their original is never written). `mode: "steer"` on an owner session continues it in place: refused while it's open anywhere, forked instead when that can't be told, and the owner is always asked. The transcripts stay on the owner's computer; every call is shown to the owner. Without the permission you get a 403 that says how the owner turns it on: tell them, don't retry.
- Runners enforce the machine owner's protections; you can't change them, only read them (`shared/runners/<runner>.json` -> `protect`, `paused`). Deploy/publish/spend/send commands (wrangler/cf deploy, npm publish, gh release create, gh pr merge, vercel/netlify deploy, mail, payment-API POSTs) are blocked unless your task text includes `[needs-owner-approval: <why>]` and the owner approves it on their phone. Destructive commands (sudo, force-push, push to main, rm -r outside the task dir) are blocked, and changing AGENTS.md, CLAUDE.md, CI workflows or git hooks fails the task. Every task is audit-logged (`tasks/<id>/audit.json`). If `task_create` says "paused", the owner hit the kill switch: wait, don't retry in a loop.
- Pick the agent and model per task, within the owner's preferences. Read them first: `peers` shows each runner's `capabilities` (`agents`, `models[agent]`, `max_parallel`, `prefs`), same as `shared/runners/<runner>.json`. Built-in agents: pi, codex, claude, gemini, cursor, copilot, opencode, grok; `agents_detail` there says which are installed and what each can do on that Mac (`tasks`, `ask`: read_only | git_check, `resumes` its session between turns), and `project_agents` which agent each project uses by default. `prefs` = `{defaultAgent, defaultModel, byKind: {plan|code|review|test|quick: {agent, model}}, byProject: {<project>: {agent, model}}, allow, deny, askWhenUnsure}` (allow/deny are globs over `agent/model`).
  - Usually just send `kind` (plan, code, review, test, quick) and let the runner apply the rules. Set `agent`/`model` only when the task needs something specific; it must be allowed, or it's refused with the allowed list.
  - The runner resolves: your explicit agent/model (if allowed) > `byProject` > `byKind` > defaults, and records the choice in `result.structured` (`agent`, `model`, `agent_reason`: task | project | kind | default).
  - Ask the user only when `askWhenUnsure` is true and the task doesn't fit a rule (no matching `byProject`/`byKind` and you'd have to guess). Otherwise don't ask; the owner's rules already answer it.
  - A runner runs several tasks at once (`max_parallel`), one per project.
- Hand off between agents: `continue_from` + another `agent`/`kind` (e.g. codex wrote it, claude reviews) keeps the branch; the runner gives the new agent a handoff note (what was done, diff stat, tests, open questions). For a fixed pipeline send `chain: [{agent, kind: code|review|test, instructions}, ...]` once: steps run in order on one branch as child tasks and stop at the first failure (`task_get` -> `chain.status`, `children`). The owner approves an unsigned chain once (the approval covers its definition); a step that differs from it asks again.
- task_create/change check `project` and `agent` against the runner's advertisement (`describe` / `peers`) and fail at once with the list; if the runner never advertised, the task is accepted with a `warning` and checked when it's claimed.
- The owner's standing instructions for assistants (the brief: where to send work, what to ask first, project conventions) live in `context/assistant.md` and are delivered to you: appended to the MCP `initialize` instructions, at the top of `describe` (`brief`), and as `brief.etag` in every `task_create` answer. Follow them; re-read them (`describe`, or the `brief` tool by name with `if_none_match: <etag>`) when the etag you hold differs. A heading like `## @<your name>` marks a section only you get. Only the owner edits it (Settings); `brief_propose {text, note}` sends them a suggestion to accept (needs write access). Writing the file directly is refused, and secrets in it are replaced with [REDACTED].
- Standing project notes for agents live in `context/<project>.md` (plus the repo's CONTEXT.md); runners prepend them to every prompt and append agents' learnings after successful tasks. Write durable conventions there instead of repeating them in every task.
- Results: `task_get` opens with `headline` (also `result.headline`): the phone-first card `{outcome: done|failed|needs_you|stopped, label, changed {files, insertions, deletions}, tests {cmd, passed, failed, ok}, branch, next {action: merge|open_pr|push|retry|reply|approve_plan, text, cmd?, url?}, wall_s, files_touched, tests_touched, lines[]}`. Show the user `lines` and do `next`; `task.completed` pushes and `inbox` finished carry it too. Details: `result.structured` = `{status, headline, tests {passed, failed, cmd}, files_changed, branch, base, commit, diff_stat, pushed, compare_url, pr_url, risk_flags, duration_s}`; `check` updates carry the same block as `result`. Read `tasks/<id>/diff.patch` etc. only when you need depth. No `verify`? The runner auto-detects the repo's tests and reports them.
- Who's working right now: `presence` → `sessions[]` = `{who, agent, repo, branch, task, phase, state: busy | idle | offline, busy_s, line}` (line reads "Pi on relay-cf · branch hark/x · running tests · 2m"; quote it to your user). Check it before dispatching: a runner that's busy on the same repo queues your task behind its work. Runners report by themselves every 30s and read `offline` after a missed heartbeat. Sessions the runner started (tasks, quick asks, warm quick-ask sessions) are reported by the runner itself, with agent and model. A coding agent working outside Kitebook tasks can say so with `presence_report {sessions: [{agent, repo, branch, phase, busy: true}], every: 60, merge: true}`.
- Before dispatching coding work, call `describe`: runners (online, hours, agents, models, projects and their verify commands), peers, and the task spec schema in one read.
- For checkable work send `spec` with `task_create`: `{goal, acceptance: [commands from the project's verify list], scope: [globs], forbidden: [globs], budget: {minutes, max_files_changed}, mode: "plan"|"execute"}`. A spec the runner can't meet is refused with the reason. Scope/forbidden/budget misses fail the task with `result.structured.violations`; `result.structured.acceptance` has each command's `{cmd, exit, passed}`.
- Machines join by code: `kitebook join <url>` on the new machine, approved by the owner on one of their devices (`kitebook approve <code>`, presence-gated). Never approve a join or claim code yourself, and never relay a code a stranger sent: tell your human what it is and let them decide. `join_*` ops answer 403 to your token.
- Owner actions are not yours: approving a device, an app or a sign-in, minting tokens, Run tasks without asking, Experimental and owner rules need a confirmation signed by a presence key on one of the owner's own computers (their Kitebook master password, or its optional Touch ID / Windows Hello shortcut). Your token gets 403 on `device_*` and `presence_*` ops; don't run `kitebook confirm`, `kitebook approve` or `kitebook devices` for the owner, ask them.
- Installing Kitebook for your human: the master password is theirs, never yours. With no terminal (you, a script) the install finishes LOCKED and prints a one-time local setup link (`http://127.0.0.1:<port>/setup-password#t=...`, once, 15 minutes). Hand that link to your human and stop there: never open it yourself, never choose, type or store the master password, and never run `kitebook password set`, `change` or `link` for them (if the link expired, tell them to run `kitebook password link` or `kitebook password set` themselves). A cloud machine that only runs agents needs no password at all: it joins with `kitebook join <url>`.
- Run tasks without asking: the owner can flip one switch in Settings. While it's on, `task_create` from one of the owner's own connected apps answers `needs_approval: "maybe_auto"` with an `approval` note ("runs right away: the owner turned on "Run tasks without asking" ..."): don't ask the human to approve, just report the result. Pushes to the main branch, people and other Macs, removed apps and anything irreversible still ask.
- Experimental (Settings -> More): the owner's switch for not-yet-stable features: `relay_logs`, local CI (`ci_run {project, branch?}` asks the Mac to run the project's own test command in a throwaway worktree; `ci_status` reads the results; with "Test every push" on the Mac also tests each new commit on the project's dev branch and each task branch) and the dev update channel. It changes no permission. `experimental` (old name `dev_mode`) tells you whether it and "Run tasks without asking" (autorun) are on; you can't turn either on or off (asking is refused and shown to the owner).
- Something broke on the owner's side: a 500 answers plain JSON `{ok: false, error, request_id}` (MCP: the error text names the request_id). While Experimental is on, `relay_logs {source?: server|auth|runner|updates|all, since?: "2h" or an ISO time, level?: error|warn|info, limit?, request_id?}` returns the recent errors and log lines (newest first) from the server (every unhandled error and 500, sign-in / Better Auth failures included, with route, status, message, stack and request_id) and from the owner's Mac (runner, updates); secrets are redacted before anything is stored. Look there before asking the owner to paste logs. With it off it answers needs_owner: ask the owner to run `kitebook logs --errors` on their Mac, or to turn Experimental on.
- Standing permissions: the owner can let you run tasks on your own in some projects (set on their /start Permissions page, on the consent page when you connected, or with "Always allow" on a review page). Then `task_create` to such a project answers `needs_approval: "maybe_auto"` with an `approval` note ("runs on its own: the owner lets <you> run tasks in <project> ..."): the server signs it for you and the runner checks the owner's signature, so don't tell the human to approve it; just report the result. Other projects, pushes (unless allowed) and anything the safety checks stop still ask the owner. You can't change these permissions.
- Signed dispatch: a runner whose `capabilities.signing.auto_requires_signature` is true auto-runs only tasks signed with a key its owner trusted (`signing.trusted_signers`); anything unsigned or invalid waits for a human approval. `task_create` answers `needs_approval: "maybe_auto"` (with an `approval` note) when your signed task goes to such a runner that trusts your key, `true` when the owner will be asked, and adds a `warning` when the target's approvals are down (the task waits; the owner can approve it from /start). Sign with `kitebook task_create ...` (the CLI signs when `KITEBOOK_SIGNING_KEY` / `~/.config/kitebook/signing-key.json` exists; make one with `kitebook keygen` and give the owner the public key) or `signCreate` from client/sign.ts. Never edit a signed task; sign a new one (a promoted plan always asks the owner). A signed chain covers its later steps too: they auto-run without another signature. KB notes count only when signed: the CLI picks the top-k itself and signs their ids + hashes (`kb_ids`); on a signed auto-run the runner drops any other or altered `kb_context` entry, and any note whose `author` (the peer that committed it; every KB result has `author` + `author_kind`) isn't in its `signing.trusted_kb_authors` (the CLI already leaves out notes not written by you or the target runner; `KITEBOOK_KB_TRUST=peer,...` adds authors), and leaves out CONTEXT.md / context packs unless the owner set `trustContextFiles`.
- Unsure about the approach? Send `mode: "plan"` first, read `tasks/<id>/plan.md`, then `task_promote {id, note?}` to run it on the same task and branch (the owner approves again).
- Limits: a token gets 600 requests/min per space (HTTP 429 + Retry-After when over: back off, prefer `wait` long-polls or events/stream). A task can be held at most 24h per attempt (lower it with `limits.max_held_sec`; over = failed, reason held_cap). `grep` scans up to 8 MB per call; on `budget_exhausted` call again with `after: next_after`.
- While it runs: `task_status {id}` (or no id for all your open tasks), or `task_get` → `live` = `{phase: queued | waiting_approval | approved | agent_running | verifying | uploading | done, agent, model, session, started_at, elapsed_s, last_line, log}`. `peers` lists each runner's `active_tasks` with their phase. The output tail is `tasks/<id>/live.log`. Check this instead of polling `progress.md`, and don't poll faster than every 30s. A claimed/running task with no progress and no live update past the space's threshold (default 10 min) comes back first with `status: "stuck?"` and `stuck: {quiet_s, since, last_line, label}`, and you (its sender) get one `notify` about it; cancel it and send a follow-up, or tell the owner (they can retry it or hand it to another agent in one tap on /start).
- Cancel with `task_cancel` (or `cancel_all`, filtered by `peer`/`target`): queued tasks are dropped, pending approvals withdrawn, running agents killed within seconds; the branch is kept and the partial diff/log uploaded. State `cancelled` with `result.reason` and `result.cancelled_by`.
- Steer a task you sent while it runs: `task_steer {id, message}` ("use the existing helper", "skip the docs"). The agent gets the note after its current run finishes (coding-agent CLIs can't take input mid-run; Codex on a live connection, `describe` -> `agents_detail.live`, takes it in the running turn), as one more run in the same conversation where the agent resumes (`describe` -> `agents_detail.resumes`; before its first run if it hasn't started); `task_get.steers[].delivered_at` says when it arrived, and the owner sees it on the task's timeline. On a task the owner approved by hand the note is new instructions after that approval, so the Mac asks the owner again first; a refused note (`refused_at`) never reaches the agent. Only you (the sender) and the owner can steer; a finished task takes a follow-up (`continue_from`) instead, and a task waiting on a question takes `task_answer`. Keep it to the one change you need; don't stream instructions.
- A task can come back with a question: state `needs_input`, `task_get` → `question.text` (also `check` → `needs_answer`, and `question` in `task.updated` events). Answer with `task_answer {id, answer}` (ask your user first if only they know); the runner resumes the same agent session, branch and worktree with your answer. Parked tasks aren't leased, so there's no rush, but the work waits on you. The owner can also answer (or approve, reject, cancel) from the /start Status card; then `questions[].answered_by` is `owner` and the task just resumes. A failing `verify` gets up to 2 automatic repair rounds before the task fails (`result.structured.repair_rounds`; `limits.repair_rounds` to change).
- A task the runner holds never sticks: if the runner goes offline and stays silent past the lease (default 15 min, set `limits.lease_sec`), the server requeues it. Infrastructure failures (lease expired, runner crashed) retry automatically up to `max_retries` (task_create, default 1; 0 = never) on the same branch; `task_get` shows `attempt` and `attempts[]`. Once retries run out it fails with `result.reason` (`lease_expired`/`runner_crash`), `retryable: true`: follow up with continue_from. Don't re-dispatch a task that is retrying on its own.
- Shared knowledge base: `kb_search {query, scope?, kinds?, k?}` searches what every assistant and coding agent here knows (facts, decisions, gotchas, prefs, learnings, synced memory); results say `semantic` (false = keyword-only) and `kb_version`. Remember something with `kb_add {text, kind, scope: "global"|"project:<name>", key}` (a key upserts in place). A local coding agent on the runner's machine can write offline too: `kitebook kb add` queues the note in ~/.kitebook-kb/outbox when the server is unreachable, or drop a markdown file (front matter kind/scope/title) into ~/.kitebook-kb/kb-inbox/; the runner sends both on reconnect (server wins a conflict). Publish your own memory as a `kb_add` batch with `origin` = your name (e.g. "hark-memory"), `kind: "memory"`, one key per note, and never personal details (addresses, people, credentials; passwords and keys are refused). Entries are files under `kb/` (versioned: `log --path`). Tasks get the top hits as `kb_context` automatically (`kb_k: 0` turns it off); project context packs (`context/<project>.md`, where agents' learnings land) are searchable too. Before dispatching, `kb_search {query: <task topic>, kinds: ["memory"]}` for the coding agents' own conventions: their instruction files (Pi AGENTS.md/skills/prompts, Codex and Claude Code AGENTS.md/CLAUDE.md, each repo's AGENTS.md/CLAUDE.md/CONTEXT.md/.cursor rules) are synced read-only with origin `pi-memory`, `codex-memory`, `claude-memory` or `repo-agents` (scope `project:<name>` for repo files; the key is the source path), so write instructions that fit them instead of contradicting them.
- Webhook subscribers get one `file.changed` delivery per commit (`paths[]` lists every file); failed deliveries are retried with backoff, so dedupe on `webhook-id`.

## Ops
- `whoami`: Your peer identity, the spaces you can use, and can: what this connection may do in plain words (e.g. "pi can: read and edit kb/pi/. Can't: delete files, send tasks."). When the user asks what was approved, tell them exactly that sentence.
- `revoke_self`: End your own credential now (uninstall, logout, a leaked token). A peer token revokes your peer; an OAuth token ends its grant (every token from that sign-in). Your event subscriptions end with it. The owner has to connect you again afterwards.
- `ls`: List a directory in the shared space (with blob sha and size)
- `stat`: File metadata: sha, size, last change
- `read`: Read a file. Pass if_none_match=<sha you cached> to skip the body when unchanged. offset/limit are line numbers. Binary files come back as content_base64.
- `glob`: Find files by glob pattern, e.g. **/*.md
- `grep`: Search file contents on the server (regex). Returns path:line:text. Scans at most a byte budget per call; when budget_exhausted, call again with after=next_after
- `write`: Create or overwrite a file (one commit). Use if_match=<sha> (or 'none' for new files) to avoid clobbering others. Binary: send content_base64 instead of content. Big files go to the host blob store (R2, local disk or S3) when configured.
- `batch`: Many file changes as ONE commit, all-or-nothing. changes = [{path, content} | {path, content_base64} | {path, sha} (reuse a blob the server has; see has) | {path, edits:[{old,new,all?}]} | {path, delete:true}], each may carry if_match. skip_rejected:true skips secret/denylisted files instead of failing.
- `patch`: Edit a file by exact string replacement without resending it. Either old+new, or edits=[{old,new,all?}] applied in order (all-or-nothing). Each old must match once unless all:true.
- `mv`: Move or rename a file
- `rm`: Delete a file (needs the delete permission)
- `log`: History of the space or one path
- `diff`: Unified diff between two commits (b defaults to main)
- `changes`: Legacy: use inbox. Everything that happened since a cursor (files and tasks you can see).
- `check`: Legacy: use inbox (the polling call). Tasks waiting for you, updates on tasks you sent (claimed, approved, done...), and files others changed, since your cursor. Pass wait (max 25s) to long-poll. Store the returned cursor for next time.
- `policy`: Runner only: the owner's signed standing permissions ({envelope: {policy, key, sig} | null, owner_keys}). The runner checks the signature against the owner keys it pinned in runner.json and never acts on an unsigned or older one. The owner changes them in Settings (/settings); also returns cmds, the owner-signed Settings changes for the Mac (client/payload.ts), which the runner checks the same way
- `settings_report`: Runner only: tell the owner's Settings page what this Mac did with their Settings changes (done list, phone approvals, release key). Only the owner sees it.
- `relay_update`: Update Kitebook on the owner's Mac (runner, kitebook command, server, agent connections) to the LATEST code, right away. Takes no arguments: you can't pick a version, commit, branch, link or older release (any argument is refused); it only ever moves forward to the latest. A git install (the one-step install): if you are one of the owner's own apps and may send tasks here, the Mac's runner itself (never a task or a coding agent) fetches origin and fast-forwards to the newest commit of the branch its checkout follows (origin/dev while Experimental is on, otherwise its upstream, usually main) once its running tasks finish; no confirmation by the owner is needed (answer state updating). The new code must start and reach the server, else it rolls back by itself. A release install takes only the newest release signed with the release key the Mac already trusts. Call relay_update again later: it doesn't start a second update meanwhile, and once it ended it answers the outcome: updated (sha, health ok: on a Mac that hosts the server, it means the server serves that sha too), rolled_back (why), server_update_failed (the server didn't take the new code; server and checkout went back; why names the log), refused (why, e.g. local changes or not a fast-forward) or up_to_date. needs_owner: the owner turned off 'Let apps update Kitebook' in Settings -> Updates (or a release can't be checked); only the owner can update it then, so tell them and don't retry. A Mac still on an older Kitebook may answer waiting_approval (its owner confirms on the Mac). Answer: {state, from, to, sha, health, why} for the Mac (every Mac in runners). Every update is shown to the owner in Settings -> Updates (Updated by <you> to <sha>).
- `experimental`: Whether the owner turned on Experimental (relay_logs, local CI, the dev update channel; no permission), and autorun: whether "Run tasks without asking" is on (a separate switch). Read-only: set is refused (403) and shown to the owner.
- `dev_mode`: Old name of experimental.
- `ci_run`: Experimental (local CI): the owner's Mac runs a project's own test command (kitebook.json ci, else package.json test) in a throwaway worktree, never GitHub Actions; the project's own branches only, never forks. Result: ci_status.
- `ci_status`: Experimental (local CI): the Mac's latest test runs, newest first ({project, branch, sha, state, passed, failed, secs, tail, reason}), and asks still waiting.
- `ci_report`: Runner only: report local CI runs, take waiting asks
- `relay_logs`: Recent logs and errors from the owner's Kitebook, for fixing what broke: every unhandled error and 500 from any route (sign-in / Better Auth, the API, ops) with route, status, message, stack and request_id (a 500 answer carries its request_id), plus the Mac runner's and updater's recent lines. Secrets are redacted before anything is stored. For the owner's own apps with dispatch rights only while Experimental is on; with it off it answers needs_owner (the owner can run `kitebook logs --errors` on their Mac). Newest first.
- `logs_report`: Runner only: send the Mac's recent logs/errors (runner, updates) while Experimental is on; redacted again before they're kept
- `webhook_secret`: Your webhook signing secret. Verify x-relay-signature: t=<unix>,v1=hex(hmac_sha256(secret, t + '.' + raw_body)).
- `peers`: Who else is in this space, what they can do, whether they're connected right now (online), when last seen, and how they get woken (socket/webhook/poll). Runners also show capabilities: agents, models, max_parallel and the owner's prefs (agent/model rules), and active_tasks (id, state, live phase, agent, model, elapsed_s; title + last_line for your own tasks). Details only: prefer presence (who's online/busy) and describe (before dispatching).
- `task_create`: Hand a task to another peer (coding agent, assistant, runner). It lands in their inbox and wakes them if possible. The result says whether the target is online; if not, that is normal, not an error: the task is queued and the target picks it up the next time it checks in (tell the user that). If this app may not send tasks yet, the error says how the owner allows it (sign the app in again choosing 'run tasks', or Permissions on /start). Reference code by git (repo + base); the result reports the task branch, base, head commit, diff_stat and compare_url/pr_url, and runners only ever push the task branch, never main.
- `ask`: Quick ask: a read-only question about code on the owner's Mac, answered in seconds. No worktree, no branch, no git writes: the runner hands it to an agent in read-only mode (a warm Pi or Claude Code session, or one run of Codex, Gemini CLI, Cursor, Copilot, opencode or Grok, whichever it has; warm sessions are kept alive about 10 minutes between asks, one per asker and project) running in the project's checkout under a read-only sandbox, so nothing on the Mac can change. The owner's own apps get an answer without an approval (it can't change anything); people and other Macs are asked like any task. Waits up to wait seconds and returns {answer} when done, else the partial answer so far: call ask_get with the id. If the answer says it needs code changes (needs_task), ask_to_task turns it into a normal task carrying the question and answer.
- `ask_get`: A quick ask (see ask): its answer when done, else state + the partial answer so far (streamed by the runner). wait: hold up to that many seconds (max 25) for it to finish. next says what to do: run_as_task (ask_to_task) when it's answered.
- `ask_to_task`: Run a quick ask as a normal task (one call): same Mac and project, the question (or your instructions) plus the ask's answer as context, on its own branch in a worktree. The runner asks the owner to approve it like any task.
- `sessions_search`: Search the owner's OWN coding-agent sessions on their computer (Codex, Claude Code, Pi, Gemini CLI, Copilot CLI, opencode, Cursor): the conversations they had with those agents, found by words, agent, folder or age. Returns per session: agent, id, title, cwd (the folder), updatedAt, a short snippet and live (true = open in another program right now, false = closed, null = can't tell: treated as open, so mode steer forks instead). Read-only; it runs on the owner's computer and Kitebook never stores the transcripts; secrets are blanked out. Needs the owner's OK: Settings -> Assistants -> your app -> Agent sessions (off by default). Without it the answer says how the owner turns it on. Every call is shown to the owner.
- `session_get`: Read one of the owner's agent sessions (from sessions_search): the last N messages (user / assistant), secrets blanked out, long messages cut. Read-only; needs the owner's Agent sessions OK (Search and read).
- `session_create`: Start a NEW coding-agent session on the owner's computer with a first prompt (Codex, Claude Code, Pi, ...), on its own branch in a fresh worktree, like a task: the owner approves it unless they let you run on your own, and you follow it with task_status / task_get. Uses the agent's live connection when the computer has one (steering and approvals mid-turn). Send more with session_send (id = the returned task id). Needs the owner's Agent sessions OK (Search, read and send).
- `session_send`: Send a message to a session. A session Kitebook started (session_create / a task: id = the task id or its agent session id): continues it (a steer while it runs, the answer while it waits on its question (needs_input), else a follow-up on the same branch and conversation). One of the OWNER's own sessions (from sessions_search): by default a FORK, a new copy of that conversation that continues as its own Kitebook session; the owner's original is never written. mode steer on an owner session continues that very session in place: refused while it's open anywhere, and the owner is always asked first. Needs the owner's Agent sessions OK (Search, read and send).
- `sessions_take`: Runner only: fetch a session request (sessions_search / session_get) addressed to it
- `sessions_reply`: Runner only: answer a session request (held in memory until the caller reads it, never stored)
- `change`: Submit code you wrote as a changeset (never drop code into shared/). Either diff (unified diff, e.g. git diff output) or files ({path: content, or null to delete}), against base (commit/branch) in a runner project. The runner applies it in a fresh worktree, commits it to <your name>/<slug> (e.g. hark/fix-login) and pushes only that branch (per the project's push policy). If the diff doesn't apply to base the task fails with result.structured.status 'conflict' and the git apply output; nothing is half-applied. Returns a task id: follow it like any task (task_get / inbox).
- `code_status`: Git sync state of the runners' checkouts (reported by each runner; it fetches every ~10 min): per project branch, dirty file count, ahead/behind its upstream, assistant branches on origin not merged / not pulled locally, and plain-English notes ("local has uncommitted work", "assistant branch hark/x not pulled locally"). Read-only; Kitebook never touches a checkout (a runner may fast-forward a clean one only if its owner set autoFastForward).
- `code_report`: Runner only: report per-project git state (see code_status)
- `task_promote`: Turn a succeeded plan-mode task (spec.mode plan) into an execute run on the same task id and branch. The runner asks its owner to approve again and gets the plan in its prompt.
- `brief`: The owner's standing instructions for assistants in this workspace (e.g. which Mac or agent to dispatch to, what to ask before, project conventions): follow them. Also delivered in the MCP initialize instructions and at the top of describe; task_create answers with brief.etag, so re-read only when it differs from your copy (pass if_none_match=<etag> to skip an unchanged body). Only the owner edits them (Settings); suggest a change with brief_propose.
- `brief_propose`: Suggest new standing instructions for assistants (the full new text). It waits for the owner, who accepts or dismisses it in Settings -> Instructions for your assistants; nothing changes until then. One pending suggestion per app (a new one replaces yours). Never put secrets in it: they are replaced with [REDACTED].
- `describe`: Start here, before dispatching. Capability manifest for dispatchers: every runner with online/last_seen, advertised hours, agents, models, projects (agent, verify commands = allowed acceptance commands), max_parallel, prefs, protect/paused, spec support, build (which Kitebook code it runs: version, commit, channel main|dev|release); server: the same for the Kitebook server; plus other peers and the task spec schema. One call instead of peers + shared/runners/*.json.
- `inbox`: The polling call: your wake-up digest (for assistants with no inbound URL; check and changes are legacy): `tasks` = open tasks sent to you, plus, since your stored cursor (or `since`): `finished` (tasks you sent that succeeded/failed/cancelled/rejected, with headline + branch), `needs_you` (tasks waiting for you to claim, approve, or answer), `messages` (notify messages and task-thread messages to you), `files` (changes under your watched globs). Read-only: call `ack` with the returned cursor once handled. Follow next_poll_after (seconds).
- `ack`: Advance your stored inbox cursor (only forward) so the next inbox starts after it. Optionally set the globs inbox watches for file changes (watch: [] clears).
- `notify`: Post a short message to a peer. It lands in their inbox `messages`, pushes to their live socket, wakes their long-poll, and fires their webhook / inbox.message event subscription if they have one.
- `task_list`: Tasks you sent or received
- `task_get`: Full task record. A finished task opens with headline = the phone-first result card {outcome done|failed|needs_you|stopped, label, changed {files, insertions, deletions}, tests {cmd, passed, failed, ok}, branch, next {action merge|open_pr|push|retry|reply|approve_plan, text, cmd?, url?}, wall_s, files_touched, tests_touched, lines[]} (also result.headline). With live = the runner's current phase/agent/model/session/elapsed_s/last_line (see task_status). When finished, result.structured = {status, headline, tests {passed, failed, cmd}, files_changed, branch, pr_url, risk_flags, duration_s, mode, acceptance [{cmd, exit, passed}] | null, violations [{rule, ...}]}; result.summary keeps the longer text. attempt (1-based) / max_retries / attempts[] show automatic requeues (reason, partial commit) after a lease expiry or runner crash. Chains: chain = {parent, steps[{step, kind, agent, task, state}], status running|succeeded|stopped, stopped_at}; children = the parent's step tasks
- `task_status`: Live state of running work. With id: one task's live block; without: every open task you sent or received. live = {phase: queued | waiting_approval | approved | agent_running | verifying | uploading | done, agent, model, session (Pi session file name, if any), started_at, elapsed_s, last_line (redacted), updated, log (tasks/<id>/live.log, the redacted agent output tail)}. Also in task_get (.live) and peers (runners' active_tasks). A claimed/running task with no progress past the space's stuck threshold (default 10 min) has status "stuck?" and stuck = {quiet_s, since, last_line, label} and is listed first; its sender gets one notify per quiet spell
- `task_live`: Runner only: report the live state of a task it holds (phase, agent, model, session, started_at, last_line; log = output tail saved as tasks/<id>/live.log, max 64KB). Redacted server-side
- `task_cancel`: Cancel a task (either side). Queued: dropped. Running: the runner kills the agent within seconds, keeps the branch, and uploads the partial diff/log. Awaiting approval: the prompt is withdrawn. State becomes cancelled with reason + cancelled_by
- `cancel_all`: Cancel every open task you sent or received, optionally only those from one dispatcher (peer) and/or to one target
- `task_message`: Send a message on a task thread (questions, answers, notes). Either side.
- `task_claim`: Target: take a task from your inbox. Returns the task plus context = {path, sha, content} from context/<project>.md when the space has one
- `task_approval`: Runner only: record the local human's approve/reject decision
- `approval_page`: Runner only: open a one-time phone review page (/approve/<id>, 5-10 min life) for an approval; the owner approves/rejects there (optionally with a passkey)
- `task_release`: Runner only: hand a claimed task (claimed | awaiting_approval) back to queued, untouched, because the runner can't ask its owner right now (every approval channel failed). note says why and is what task_get shows; the runner retries with backoff. Never a decision: nothing was approved or rejected
- `approval_wait`: Runner only: the decision on one of its approval pages (pending | approved | rejected | registered | expired | closed); wait up to 25s; close:true withdraws it
- `task_progress`: Target: post progress (also marks the task running). With question (or needs_input:true) the task parks in needs_input until the sender calls task_answer; needs_input isn't leased
- `task_steer`: Steer a task that is still running: a short note (what to do differently, what to skip, a detail it lacks) delivered after the agent's current run finishes (coding-agent CLIs can't take input mid-run; an agent on a live connection, agents_detail.live, takes it in the running turn), as one more run before verify, in the same conversation for agents that resume (describe -> agents_detail.resumes; others get the original task again with your note). Sent before the agent starts, it comes with its first run. Only the task's sender (or the owner, from the Status card on /start) can steer; the agent itself and other apps can't. Refused for a finished task (send a follow-up with continue_from) and for one waiting on a question (use task_answer). Kept on the task (task_get.steers, thread.md, the timeline) and shown to the owner.
- `task_steer_take`: Target (runner): take the steering notes not yet given to the agent and mark them delivered. The runner calls it at each turn boundary and runs the next turn with them. A steer arrives after the owner approved the task, so the runner may peek first (peek:true marks nothing), ask the owner, then take only the approved ones (only) and mark the others refused (refused)
- `presence`: Who's working right now: one entry per runner or agent session that reports in, with agent, repo, branch, task (title only for your own), phase, state busy | idle | offline (offline = missed its heartbeat), last_seen, busy_s and line, a plain sentence like "Pi on relay-cf · branch hark/x · running tests · 2m". The "who's online and busy" call (describe is the one to call before dispatching).
- `presence_report`: Heartbeat: say what your sessions are doing, every `every` seconds (default 30; you read offline after missing one, i.e. 2 x every). sessions = [{session, agent, repo, branch, task, phase, busy}] replaces what you reported before (merge:true upserts only these; {session, end:true} removes one). Runners send this by themselves; a coding agent can call it while it works on a repo outside Kitebook tasks.
- `task_answer`: Sender: answer the question a task is parked on (state needs_input; see task_get.question or check.needs_answer). The task goes back to running and the runner resumes the same agent session, branch and worktree with your answer
- `task_complete`: Target: finish a task. artifacts = {filename: text} saved under tasks/<id>/ (e.g. diff.patch, test.log, summary.md)
- `kb_add`: Add or update knowledge-base entries (one commit). The KB is shared by assistants and coding agents: each entry is a versioned markdown file under kb/global/ or kb/<project>/. With a key, newer replaces older in place (history keeps the old text). Passwords and keys are refused. Batch: entries=[{text, kind, scope, key, title, origin, supersedes}]. Assistants publishing their own memory: origin "hark-memory" (or your name), kind memory, one key per note.
- `kb_search`: Search the shared knowledge base: keyword (FTS5) plus semantic similarity when the server has an embedder, merged by rank fusion. Returns entries with score, match [keyword|vector], semantic (false = keyword-only) and kb_version (compare to see if anything changed).
- `kb_get`: One knowledge-base entry in full (id from kb_search/kb_list; it's the file path, or path#n for a section of a plain markdown file). History: log --path <path>.
- `kb_list`: List knowledge-base entries (oldest change first), filtered by scope/kind/origin/key_prefix. With since=<kb_version> you get only what changed after it plus removed ids, and a cursor for next time (how caches stay in sync).
- `kb_forget`: Remove knowledge-base entries (deletes their files in one commit; history keeps the old text). By id, ids, or scope + key/keys. Needs write on the path.
