docs(skill): Claude in the cloud, a local model on this Mac

SKILL.md gets a subsection after the DeepSeek Harness workers: why to hand
work to the model on this Mac (bulk work with no per-token bill, files
that stay on the machine, a second opinion, a worker that needs no
network), the copy-paste block (local_model, spawn_worker local, sendmark,
delete_session), and the limits as the code has them: a person installs
the model, one request at a time, slower than the cloud, markers not
hooks, a spawn can start a runtime the person stopped, admin only and
experimental.

The description now names the local model, so "use the local model"
reaches the skill. Section 2 gains a row, the hook-less row points at
sendmark, and reference/verbs.md gets 5.16 with every local_model reason,
the spawn steps and the sendmark exit codes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-11-08 07:48:32 +01:00
1 parent b913092be3
commit 28e8029ce5
4 files changed
+328 -10

No files matched your search

+71 -4
View File
@@ -7,7 +7,8 @@ description: >-
CLI, send them prompts, block until they finish (wait / wait-output / send-and-wait),
read their output, and clean up; where available, message claude workers directly
(Claude Code cross-session messaging). Use when asked to orchestrate or parallelize
work across Codeman sessions, get a second opinion from another CLI or model, watch
work across Codeman sessions, get a second opinion from another CLI or model, hand
work to the local model Codeman installed on this Mac ("use the local model"), watch
another session, start and manage workers, or retrieve saved local Codex history
by file/native ID or root and descendants with topic/date filters. Live API
operations require a Codeman-managed session (CODEMAN_MUX=1). Offline history
@@ -763,8 +764,8 @@ the time went into deliberation, not the API. The four things that actually cost
polled pid for nothing, and shipped its workers without lineage.
- **Verifying what is already checked for you.** Two verifications specifically are not
worth a call here, because `spawn_worker` carries them: the hooks check (it refuses a
name that resolved to a hook-less directory with one local grep, so a worker it hands
back always has a working `stop` and `sendwait` is trustworthy), and the pid poll,
name that resolved to a hook-less directory with one local grep, so a claude worker it
hands back always has a working `stop` and `sendwait` is trustworthy), and the pid poll,
which is dead weight because `wait-output` already blocks on the composer.
Four things this block leans on, each one link away, no detour needed to run it:
@@ -824,6 +825,70 @@ Three things to know before you spawn one:
worker whose pane clearly finished: that profile is one of them, so drive it with
markers instead.
### Claude in the cloud, a local model on this Mac
When Codeman has installed a model on the Mac it runs on (the **Run a local model** button
on its welcome screen), you can hand work to that model from here. You stay the planner and
the reviewer in the cloud; the Mac does the bulk. `spawn_worker <name> local` starts a worker
on the installed model, run by the coding agent it was installed with (OpenCode on the Mac
it was tested on), and from then on it is the same verbs. It mixes into one fleet like any
other mode: `spawn_workers plan review:deepseek summ:local`.
Reach for it when:
- **The work is bulk, not judgment**: summarizing a folder, classifying, first drafts of
tests, boilerplate. A local turn has no per-token bill.
- **The files should stay on this machine**: the worker reads them here, and only what you
read back from it enters your own context.
- **You want a second opinion from a different model**, with different blind spots.
- **The worker should not depend on the network**: the model's runtime listens on this Mac
only, started with its cloud features off. You, the lead, still need a connection.
```bash
. "${XDG_CACHE_HOME:-$HOME/.cache}/codeman-agent-$CODEMAN_SESSION_ID.sh" 2>/dev/null # §0 loader
[ "${CODEMAN_PREAMBLE:-}" = 1.50.0 ] || { echo "preamble missing or stale; run the full §0 block"; exit 1; }
local_model || exit 1 # one JSON line: model, agent, endpoint; or the reason there is none
SID=$(spawn_worker summ local) || exit 1 # starts a stopped runtime first; empty = failed
WD=$("${CURL[@]}" "$API/api/v1/sessions/$SID" | jq -r .data.workingDir)
cp ./docs/*.md "$WD/" # its input goes inside its own folder (§5.9)
R=$(sendmark "$SID" 'Summarize each .md file in this folder in two lines' 900); RC=$?
jq -r '.answer // "(no answer)"' <<<"$R"
if [ "$RC" = 0 ]; then delete_session "$SID" >/dev/null
else echo "kept $SID (sendmark rc $RC: 2 no marker yet, 3 ended, 4 no answer file): $R"; fi
```
`sendmark` is the local worker's `sendwait`: it adds one sentence to your prompt asking for
the answer in `codeman-answer-<token>.md` in the worker's folder and then a split marker,
waits for the marker with `wait-output`, and reads the file back
([§5.16](reference/verbs.md#516-a-worker-on-the-local-model)). Read its `answer`, not the
pane: a full-screen agent's pane is a poor transcript. ⚠️ A marker proves that the model
printed a token, not that the work is done or right. Review what comes back the way you
would review any first draft.
The limits, before you promise the user anything:
- **A Mac with Apple silicon, and a model a person installed** in Codeman's local model
window. This skill never installs one: that is a download of many gigabytes the person
confirms there. `local_model` says what is missing (`not-mac`, `not-installed`,
`install-unfinished`, `blocked`, `admin-only`, `unsupported`), so tell the user that
instead of guessing.
- **One model, one request at a time.** The runtime loads one model and answers one request
at a time, so two local workers take turns rather than work side by side. Fan out to the
cloud; give the Mac a queue.
- **Slower than a cloud model.** The one measured pair, OpenCode with Qwen3.6 35B-A3B on an
M5 Pro with 48 GB, ran at about 68 tokens a second, and the first request after a start
loads the model first. A local worker run by claude spends minutes on its first answer
(its fixed prompt is about 36,000 tokens).
- **Markers, not hooks.** OpenCode reports no end of turn, so `sendwait` answers 400 for
that worker; `sendmark` is the way through. If the installed agent is claude, the worker
has hooks and `sendwait` works as for any claude worker.
- **Spawning can change what the person set.** A stopped runtime is started, which also
undoes the person's Stop: it then starts with Codeman again. And while a local worker is
open, the window refuses Stop and Remove and names your workers, so delete them as soon
as they are done.
- **Admin only in multi-user mode, and experimental.** The local model routes answer 403
for anyone else (`admin-only`), and they may change in any release.
## 2. What do you want to do?
One row per job. Acting on this table alone is correct; the §5 links are the detail.
@@ -833,7 +898,8 @@ One row per job. Acting on this table alone is correct; the §5 links are the de
| start a worker **where the work is** | `POST /api/v1/quick-start {"caseName":…}`, which **creates** `~/codeman-cases/<name>` unless the name is already a case. Any other path (a git worktree): `POST /api/v1/sessions {"workingDir":…}` then `POST /api/v1/sessions/:id/interactive`. Both install hooks by default, so expect full signals in either, and **verify** rather than assume. N workers means N worktrees | [§5.1](reference/verbs.md#51-where-to-spawn) |
| know a new worker can accept a prompt | `GET .../wait-output?match=shift+tab&from=buffer` (urlencode the `+`); a `deepseek` worker draws `❯` instead, and its boot `stop` fires ~300 ms BEFORE that, so never read the signal as readiness | [§5.2](reference/verbs.md#52-readiness) |
| deliver a task **and** know when it finished | `POST .../input` with `"input":"…\r"`, `clientId`, `seq`, `"wait":true`. Resolves on `stop`, so it is trustworthy where the signal is real: claude mode with hooks (installed by default, but the operator can disable it and remote sessions never get them) and `deepseek` mode through its status bridge. Costs the worker one billed turn | [§5.3](reference/verbs.md#53-send-a-task-and-wait) |
| know a hook-less worker finished | it has no `stop`, and `wait:true` there resolves on flapping `idle` **without erroring**: make it print a split, unique marker and `wait-output` on that instead | [§5.5](reference/verbs.md#55-markers-for-hook-less-workers) |
| know a hook-less worker finished | it has no `stop`, and `wait:true` there resolves on flapping `idle` **without erroring**: make it print a split, unique marker and `wait-output` on that instead. For an agent worker, `sendmark <sid> "<prompt>"` does all of it and reads the answer back | [§5.5](reference/verbs.md#55-markers-for-hook-less-workers) |
| hand work to the local model on this Mac (cheap bulk work, files that stay here, a second opinion) | `local_model` says what is installed; `spawn_worker <name> local` (or `name:local` in `spawn_workers`) starts a worker on it; `sendmark` tasks it and returns the answer | [Local model](#claude-in-the-cloud-a-local-model-on-this-mac), [§5.16](reference/verbs.md#516-a-worker-on-the-local-model) |
| read the answer | `GET .../last-response`, **polled** (claude, codex and deepseek write a transcript; empty for the other modes) | [§5.4](reference/verbs.md#54-read-the-answer) |
| know if it is alive | `GET .../wait?until=exit&timeout=1000`: an immediate `signal:"exit"` means dead. `status` and `pid` both lie | [§5.6](reference/verbs.md#56-alive-and-stuck) |
| know if it is stuck | `GET .../active-tools` and `GET .../run-summary` are structured and free; two `terminal?tail=` samples are the crude fallback | [§5.6](reference/verbs.md#56-alive-and-stuck) |
@@ -949,6 +1015,7 @@ these**; open the one row you actually hit.
| [5.13 Messaging claude workers](reference/verbs.md#513-messaging-claude-workers) | `ListAgents` / `SendMessage` instead of the HTTP path |
| [5.14 Clean up](reference/verbs.md#514-clean-up) | what deleting a session does **not** remove, and how to list the case dirs you left |
| [5.15 Agent inbox](reference/verbs.md#515-agent-inbox) | messages that wait for the receiver instead of being typed: post, read until ack, who counts as the receiver |
| [5.16 A worker on the local model](reference/verbs.md#516-a-worker-on-the-local-model) | what `local_model` answers, what `spawn_worker <name> local` does step by step, and every `sendmark` outcome |
## 6. Setup and auth
@@ -484,7 +484,10 @@ snippet carries the exit code back to you.
For a **claude** worker with no hooks, ask for the marker in halves in the prompt
itself ("print the word WORKDONE immediately followed by `_<token>`") for the same
reason, and match the joined token. ⚠️ Against a TUI, match a single space-free token:
reason, and match the joined token. For any **agent** worker without `stop` (a hook-less
claude, opencode, gemini, pi, grok, omp, copilot, a worker on the local model), the
preamble's `sendmark <sid> "<prompt>"` is exactly that in one call, and also asks for the
answer in a file it reads back: [§5.16](#516-a-worker-on-the-local-model). ⚠️ Against a TUI, match a single space-free token:
a full-screen TUI positions text with cursor movements rather than literal spaces, so
the stripped stream can read `Yes,Itrustthisfolder`, and whether a phrase keeps its
spaces depends on how the TUI happened to draw it (observed live: some match, some
@@ -814,6 +817,95 @@ Rules that matter:
- **Lifetime.** An inbox goes with its session on delete. A server restart empties all
inboxes unless the user turned on *Keep agent inbox across restarts* in Settings.
### 5.16 A worker on the local model
The detail behind [Claude in the cloud, a local model on this
Mac](../SKILL.md#claude-in-the-cloud-a-local-model-on-this-mac). Three preamble functions,
all read from source and pinned by `test/agent-skill-local-model.test.ts` against a fake
server; the local model routes themselves are experimental.
**`local_model`** is one `GET /api/v1/local-model` (the `/api/v1` alias reaches the
experimental route like any other) and prints one JSON line. Exit 0 means a `local` worker
can be spawned now (`running: true`) or once `spawn_worker` has started the runtime
(`running: false`):
```json
{"spawnable":true,"state":"installed","running":true,"cli":"opencode","cliLabel":"OpenCode",
"endpointId":"local-ollama","modelId":"qwen3.6:35b-a3b","modelLabel":"Qwen3.6 35B-A3B"}
```
Exit 1 carries `reason` and `message` (the server's own sentence where it has one):
| `reason` | What it means | What to tell the user |
|----------|---------------|-----------------------|
| `not-mac` | the server is not on macOS | local models need a Mac with Apple silicon |
| `not-installed` | nothing installed; on a Mac where no model fits, `message` says why | install one with **Run a local model** on the welcome screen |
| `install-unfinished` | a job is installing, paused or failed (`state`, `step`, `errorCode`) | finish it in that window; nothing here continues it |
| `blocked` | installed, but the server refuses a session now (`errorCode`: `model-changed`, `port-in-use`, `runtime-start-failed`, `runtime-stopped`) | the window shows the one button that helps |
| `admin-only` | multi-user mode, and you are not an admin (403) | ask an admin |
| `unsupported` | the server has no local model routes (404) | it predates the feature |
| `unauthorized`, `unreachable`, `error` | the request itself failed | §6 for a 401 |
It never downloads, installs or starts anything. Installing is many gigabytes the person
confirms in the window, and no route this skill calls can do it.
**`spawn_worker <name> local`** (also `name:local` in `spawn_workers`), in order:
1. `local_model`; anything but exit 0 fails the spawn with that reason.
2. Runtime stopped: `POST /api/v1/local-model/runtime {"action":"start"}`, what the
window's Start button sends, then `local_model` again until it reports `running`
(about 30 s at most). This undoes a Stop the person made. Concurrent spawns may both
send it; the server runs one runtime verb at a time and keeps a runtime that runs.
3. `POST /api/v1/quick-start {"caseName":…,"mode":"<cli>","parentSessionId":…,
"customModel":{"endpointId":…,"modelId":…}}`: the launch of the window's "Start a
session", with the installed agent as the mode. No advisor, which is a cloud model.
The server checks again that its own runtime runs and that the model on disk is the one
it installed, and refuses otherwise (`OPERATION_FAILED`, with the reason).
4. An answer of `requiresContextWarning` or `requiresConfirmation` is a question for a
person (a context below the agent's safe floor, a model swap that would evict another
session's model). It fails the spawn with the answer on stderr and nothing on stdout,
and no `confirmed*` flag is ever sent.
5. Readiness: OpenCode's composer bar `┃` (its registry `promptGlyph`) through
`wait-output`, 45 s, and the session deleted on a miss. A claude agent takes the claude
path instead (hooks vetted, `shift+tab`, the trust dialog). Any other agent needs
`LOCAL_READY_MARK` set to a space-free token its composer draws, or the spawn is refused
before a session exists. Not measured here: an OpenCode first-run screen that hides the
composer would read as a miss.
**`sendmark <sid> <prompt> [timeoutSec]`** drives it, and any other agent worker without
an end-of-turn signal ([§5.5](#55-markers-for-hook-less-workers)):
- It joins the prompt onto one line and adds one sentence: write the complete answer to
`codeman-answer-<token>.md` in the working directory, and only then end the reply with
the word DONE immediately followed by `_<token>`. The typed text never holds the joined
marker, so its echo cannot match.
- It sends once with the exactly-once body (`$CID-<sid>`, an epoch-second `seq`, one `\r`)
and a one-second `exit` wait, so `delivered` is the pane's answer, not a guess.
- It waits on `wait-output?match=DONE_<token>&from=buffer` in rounds of at most 60 s until
the timeout (default 600 s), then reads the answer file from this machine's disk: only a
regular file (a symlink is refused), at most 256 KB.
| exit | JSON | Meaning |
|------|------|---------|
| 0 | `answer` holds the file | the marker printed and the file was read |
| 2 | `matched: false` | no marker before the timeout. The worker may still be working: re-check the pane, never resend blindly |
| 3 | `ended: true` or `delivered: false` | the worker's pane ended |
| 4 | `matched: true, answer: null` | the marker printed, but no file within `SENDMARK_FILE_WAIT` s (default 120): the model ignored the instruction, or a reasoning model restated the marker while planning and is still at work |
| 1 | `detail` | the send or a wait failed |
What a marker proves, and what it does not: the model printed the token. Not that the
work is done, not that it is right, and with a reasoning model not even that the turn is
over, which is why the file is the second gate. What a small local model writes is a
first draft; review it before you build on it. The answer files stay in the worker's
folder, a scratch case that [§5.14](#514-clean-up) lists for cleanup.
`SENDMARK_ANSWER_FILE=0` asks for the marker alone, for a worker that cannot write where
it runs (codex in a folder it runs read-only, an agent that asks before it writes a file,
a worker on another host): exit 0 then means only that the marker printed. A worker that
stops on such a question shows up here as exit 2, not as an error.
Close every local worker you opened: while one is open the window refuses Stop and
Remove, naming it.
## Experimental bounded inspection and guarded submission
These three commands and their HTTP routes are labeled experimental, so they may