375 lines
20 KiB
Markdown
375 lines
20 KiB
Markdown
# Cowork probe
|
|
|
|
Plan Step 1 (M1). An empirical record of what this operator's Cowork
|
|
installation actually does, measured before any code depends on it.
|
|
|
|
Operator decision 7 — Python as a CLI plus a host-side stdio MCP server — is
|
|
void if host-side servers never appear in a Cowork session. That is not
|
|
knowable from Claude Code, so it is measured here first, and the milestone that
|
|
depends on it carries a written degradation branch either way.
|
|
|
|
**Status: unanswered.** All four questions require the operator sitting in a
|
|
Cowork session on this Mac. The executor session built the probe vehicle and
|
|
this checklist; it did not and could not answer them. The verify command for
|
|
this step is written so an unanswered checklist *fails*:
|
|
|
|
```bash
|
|
test "$(grep -cE '^- (Sesjonsmodus|Host-MCP|python3|CLAUDE_PLUGIN_ROOT): +(lokal VM|sky|ja|nei)\b' docs/cowork-probe.md)" = 4
|
|
```
|
|
|
|
A bullet still reading `ubesvart` does not match, the count drops below four,
|
|
and the command exits non-zero. That is the intended behaviour until the
|
|
operator has run the probe.
|
|
|
|
## Results
|
|
|
|
Overwrite `ubesvart` with one of the allowed answers, and put the date of the
|
|
measurement in the Measured column. A `nei` is a result, not a failure — it
|
|
selects the degradation branch below.
|
|
|
|
- Sesjonsmodus: sky
|
|
- Host-MCP: ja
|
|
- python3: ja 3.11.15
|
|
- CLAUDE_PLUGIN_ROOT: ja
|
|
|
|
| Bullet | What the operator does in Cowork | Allowed answer | Measured |
|
|
|---|---|---|---|
|
|
| `- Sesjonsmodus:` | Open a Cowork session and read whether it is a local VM or cloud | `lokal VM` or `sky` | 2026-09-04 -- read off the measurement, not the settings screen: the sandbox reports `/root/...` paths and `[GCC 13.3.0]` |
|
|
| `- Host-MCP:` | Upload `jobbsok-probe.plugin` via Customize -> Plugins; does `probe-tools` appear as a connector, and does `probe_ping` return? | `ja` or `nei` | 2026-09-04 -- both halves hold: connected per `main.log`, and `probe_ping` returned `probe-tools 0.0.1, python 3.14.0 @ /usr/local/bin/python3` |
|
|
| `- python3:` | Run the `probe-versjon` skill; does it succeed in invoking `python3`, and which version does it report? | `ja` or `nei`, followed by the reported version | 2026-09-04 -- `3.11.15 (main, Mar 3 2026) [GCC 13.3.0]`, the SANDBOX interpreter, not the host's |
|
|
| `- CLAUDE_PLUGIN_ROOT:` | Run `probe-versjon`; does it print a resolved absolute path, or the literal variable name? | `ja` or `nei` | 2026-09-04 -- `/root/.claude/plugins/synced/<session>_<account>/jobbsok-probe`, and the path exists |
|
|
|
|
### Two-run record: is the session-location setting the lever?
|
|
|
|
Cowork exposes `Only on this computer` as a **toggle**, which means session mode
|
|
is a setting rather than a property of this machine -- something the four
|
|
bullets above cannot express, since each holds one value. Anthropic's
|
|
documentation says local MCP servers do not run in cloud sessions, and cloud has
|
|
been the default since 2026-07-07; whether a cloud-default session on this Mac
|
|
still spawns host-side servers via the desktop app is recorded as **unverified**
|
|
in `research/03-cowork-plugin-compatibility.md`, dimension 4. So run the probe
|
|
twice and fill this in **before** writing the bullets above. The off run is the
|
|
cheap one to lose: it is the current default, and a fresh install returns to it.
|
|
|
|
| Run | `Only on this computer` | `probe-tools` listed? | `probe_ping` answers? | Manifest version | Date |
|
|
|---|---|---|---|---|---|
|
|
| 1 | off (current default) | yes | yes -- returned host Python 3.14.0 | 0.0.1 | 2026-09-04 |
|
|
| 2 | on | not run -- see below | | | |
|
|
|
|
How to read the pair:
|
|
|
|
| Run 1 (off) | Run 2 (on) | What it means |
|
|
|---|---|---|
|
|
| no | yes | The toggle is the lever. Operator decision 7 works, but only with it on -- which then belongs in `README.md` and in every `Verifiser i Cowork` step, not in a footnote. |
|
|
| no | no | Decision 7's Cowork half is dead whatever the setting. The degradation branch below applies, and `jobbsok-tools` is not built on an assumption. |
|
|
| yes | yes | Host-side MCP runs even in cloud mode. That contradicts the documentation and is the most interesting of the three -- research 03 flagged exactly this as unverified. Record what you saw, verbatim. |
|
|
|
|
Then write the session-mode bullet above as the mode that produced the better
|
|
result, and put what the other run gave in its Measured column. If both runs
|
|
agree, say so there too: an agreement that was measured reads differently from
|
|
one that was assumed.
|
|
|
|
### What is already known, and what is not
|
|
|
|
Measured 2026-09-04 from Claude Code, before the probe was built:
|
|
|
|
- Cowork is installed and available on this Intel Mac.
|
|
- The **session mode is not readable from disk.** `cowork_settings.json` carries
|
|
only `extraKnownMarketplaces`; the desktop config carries `coworkNetworkMode`
|
|
and a trusted-folder list, and neither states where a session runs. So
|
|
`- Sesjonsmodus:` cannot be answered by reading files — it has to be read off
|
|
a live session.
|
|
- The other three each require a plugin installed in a live Cowork session.
|
|
Nothing about them is decidable from Claude Code.
|
|
|
|
## The probe vehicle
|
|
|
|
`jobbsok-probe` — a throwaway test plugin, deliberately **not** the `jobbsok`
|
|
plugin itself. `jobbsok`'s own `.mcp.json` (Step 12) and packaging script
|
|
(Step 13) land eleven steps later, and this probe must not wait for them.
|
|
|
|
It holds exactly four files, under `tests/fixtures/cowork-probe/`:
|
|
|
|
| File | What it is for |
|
|
|---|---|
|
|
| `.claude-plugin/plugin.json` | Manifest. Name `jobbsok-probe`, version `0.0.1`. The version is what `probe-versjon` echoes, so a mismatch reveals a cached build. |
|
|
| `skills/probe-versjon/SKILL.md` | A skill that reports the manifest version, the resolved `${CLAUDE_PLUGIN_ROOT}`, and whether it can invoke `python3`. Answers three of the four questions. |
|
|
| `.mcp.json` | Declares one stdio server, `probe-tools`. Its appearance in the session is the whole `- Host-MCP:` question. |
|
|
| `probe_tools.py` | Standard-library JSON-RPC server exposing one tool, `probe_ping`, returning the interpreter actually running it. No third-party imports: a server that failed on a missing dependency would answer a different question than the one asked. |
|
|
|
|
Verified from Claude Code 2026-09-04: the server completes an `initialize`
|
|
handshake, lists `probe_ping`, returns an interpreter version on
|
|
`tools/call`, and rejects an unknown tool name. What is unverified is whether
|
|
Cowork ever starts it.
|
|
|
|
### Building the archive
|
|
|
|
Run from the repository root. The file list is explicit, and that is the point:
|
|
|
|
```bash
|
|
cd tests/fixtures/cowork-probe && \
|
|
zip -X /tmp/jobbsok-probe.plugin \
|
|
.claude-plugin/plugin.json \
|
|
.mcp.json \
|
|
probe_tools.py \
|
|
skills/probe-versjon/SKILL.md
|
|
```
|
|
|
|
Never `zip -r` over the repository root. A recursive archive of the repo would
|
|
ship `.git`, `STATE.md`, `.claude/` and the virtualenv into an uploaded plugin.
|
|
The include list above names the four files and can carry nothing else.
|
|
|
|
## Running the probe, step by step
|
|
|
|
Everything below happens in Claude Cowork on this Mac, not in Claude Code.
|
|
Where a UI path is named it comes from Anthropic's own documentation
|
|
(`research/03-cowork-plugin-compatibility.md`, dimensions 1 and 6); where the
|
|
wording in the app turns out to differ, trust the app and note what you saw --
|
|
a changed menu label is itself a finding worth recording.
|
|
|
|
### A. Read the session mode -> answers `- Sesjonsmodus:`
|
|
|
|
Open Cowork and find its session-location setting (Cowork -> Settings). You are
|
|
looking for whether sessions run **on this machine** or **in the cloud**.
|
|
|
|
- On this machine: shell commands execute in a dedicated local Linux VM
|
|
(Apple Virtualization.framework), which needs working hardware
|
|
virtualization; Claude runs a readiness check for exactly that.
|
|
- In the cloud: code runs on Anthropic's servers. This has been the default
|
|
since 2026-07-07, so do not assume local just because the Mac is capable.
|
|
|
|
Write `lokal VM` or `sky`.
|
|
|
|
**Do not just read this setting -- probe both sides of it.** It is a toggle, so
|
|
the documented claim that local MCP servers do not run in cloud sessions is
|
|
testable here rather than merely quotable. Leave it as you found it for the
|
|
first pass, run sections B through E, then flip it, start a fresh session and
|
|
run B through E again. Record both in "Two-run record" above; that table also
|
|
says how to read the pair. Five minutes buys the difference between "Cowork
|
|
cannot do this" and "Cowork can do this if one setting is on", which are very
|
|
different findings for the milestone.
|
|
|
|
One honesty marker: that this toggle controls session location is an inference
|
|
from its own label and subtitle -- a local VM stops when the machine sleeps, a
|
|
cloud session does not -- and no documentation read so far names this exact
|
|
string. The probe is what settles it.
|
|
|
|
### B. Install the probe plugin (needed for C, D and E)
|
|
|
|
Two documented routes; either is fine.
|
|
|
|
1. Customize -> Plugins, choose the install-from-file / upload option, and pick
|
|
`/tmp/jobbsok-probe.plugin`.
|
|
2. Or drag the `.plugin` file into the Cowork chat. Anthropic's own plugin
|
|
packaging skill says a `.plugin` file appears in the chat as a rich preview
|
|
with a button to accept it.
|
|
|
|
Known wrinkle, not your mistake if it happens: uploaded plugins have been
|
|
reported not to survive an app relaunch (claude-code#65426, June 2026). If the
|
|
plugin disappears later, re-upload it rather than concluding anything about
|
|
the probe.
|
|
|
|
### C. Confirm you are testing the build you just uploaded
|
|
|
|
In the Cowork chat, type `/` (or `+`) to list available skills, and run
|
|
`probe-versjon`.
|
|
|
|
The first thing it reports is the manifest version. It must be **`0.0.1`**.
|
|
|
|
If it reports anything else, Cowork served a cached build rather than the one
|
|
you uploaded (claude-code#69020, open) -- and then every answer below is about
|
|
some *other* build. Uninstall, re-upload, and re-run before continuing. This
|
|
check exists because a stale cache produces confident, wrong measurements.
|
|
|
|
### D. Read the skill's output -> answers `- python3:` and `- CLAUDE_PLUGIN_ROOT:`
|
|
|
|
Both come from that same `probe-versjon` run.
|
|
|
|
**`- python3:`** -- did the skill succeed in running
|
|
`python3 -c 'import sys; print(sys.version)'`, and what did it print?
|
|
Write `ja` followed by the version, or `nei` followed by the error text.
|
|
|
|
One thing to be precise about, because it changes what the answer means: in
|
|
Cowork, shell commands run inside the sandbox (the local VM, or the cloud
|
|
environment), **not on your Mac**. So this measures the sandbox's `python3`,
|
|
not the host's. That is the right thing to measure here -- it tells us whether
|
|
a skill-invoked script can run in Cowork at all -- but it is not evidence about
|
|
the interpreter that `scripts/bootstrap.sh` just built a virtualenv for.
|
|
|
|
**`- CLAUDE_PLUGIN_ROOT:`** -- did the skill print a resolved absolute path, or
|
|
the literal unexpanded string `${CLAUDE_PLUGIN_ROOT}`? Write `ja` or `nei`.
|
|
An absolute path is the pass; anything literal or empty is `nei`.
|
|
|
|
### E. Look for the connector -> answers `- Host-MCP:`
|
|
|
|
This is the load-bearing one: it is the entire Cowork half of operator
|
|
decision 7, and the reason this probe is the first step of the build.
|
|
|
|
A server declared in `.mcp.json` does **not** show up as a skill. In Cowork it
|
|
appears as a **Connector**. Two checks, and both must hold:
|
|
|
|
1. **Is it listed?** Customize -> Plugins, open `jobbsok-probe`, and look at
|
|
its connectors page. Is `probe-tools` there?
|
|
2. **Does it answer?** In the chat, ask Claude to call the `probe_ping` tool.
|
|
It should return JSON containing `python_version` and `python_executable`.
|
|
|
|
Write `ja` only if both hold. If `probe-tools` is listed but never answers,
|
|
write `nei` and note that it appeared but did not respond -- a connector that
|
|
does not respond is not a working host-side server, and the difference between
|
|
"absent" and "present but mute" points at different causes.
|
|
|
|
Unlike D, this one *does* run on your Mac: Anthropic moved local MCP servers
|
|
out of the sandbox in May 2026 and runs them as ordinary host software. So a
|
|
`ja` here means the host Python, the host filesystem and `~/jobbsok-workspace`
|
|
are all reachable from Cowork -- which is what decision 7 is built on.
|
|
|
|
### F. Record and verify
|
|
|
|
Edit the four bullets under "Results" above, replacing `ubesvart`, and fill in
|
|
the Measured column with today's date. Then, from the repository root:
|
|
|
|
```bash
|
|
test "$(grep -cE '^- (Sesjonsmodus|Host-MCP|python3|CLAUDE_PLUGIN_ROOT): +(lokal VM|sky|ja|nei)\b' docs/cowork-probe.md)" = 4
|
|
```
|
|
|
|
Exit 0 means the probe is complete and plan Step 1 can be closed. Exit 1 means
|
|
at least one bullet is still unanswered or was written in a form the grep does
|
|
not recognise -- check that the answer follows the label directly, with a
|
|
space, and starts with one of the four allowed words.
|
|
|
|
## Observations beyond the four questions
|
|
|
|
Measured 2026-09-04 while running the probe. These are deliberately **not**
|
|
extra bullets: the four above are gated on a count of exactly four, and Step 31
|
|
already adds a fifth (`- Connector-hook:`). A sixth would break the Verify. They
|
|
are recorded here because they answer questions later milestones were going to
|
|
ask, and because a finding that lives only in a commit message is lost.
|
|
|
|
### O1. Host-side MCP reaches a cloud session, through a bridge
|
|
|
|
The strongest result of the probe, and it contradicts the reading this plan was
|
|
built on. `research/03` dimension 4 quotes Anthropic: local MCP servers "don't
|
|
run in sessions in the cloud", and flagged as **unverified** whether a
|
|
cloud-default session on this Mac still spawns them. It does.
|
|
|
|
Measured from the host, not recalled from a UI:
|
|
|
|
```
|
|
main.log 2026-09-04 18:48:28
|
|
[LocalMcpServerManager] Connecting to plugin:jobbsok-probe:probe-tools
|
|
[LocalMcpServerManager] ... negotiated protocol version: 2025-11-25
|
|
[LocalMcpServerManager] Connected to plugin:jobbsok-probe:probe-tools (1 tools)
|
|
[localMcpBridge] announcing plugin:jobbsok-probe:probe-tools: 1 tool(s)
|
|
|
|
ps: 12997 Claude.app/Contents/Helpers/disclaimer -- /usr/local/bin/python3
|
|
.../rpm/plugin_01NxHfqM495jtJzjVjRvgWDm/probe_tools.py
|
|
12998 Python.framework/Versions/3.14/... (the host interpreter)
|
|
```
|
|
|
|
The mechanism is `localMcpBridge`, the same one that announces Claude Desktop's
|
|
own `Filesystem` server. The session sees the tool as
|
|
`mcp__remote-devices__plugin_jobbsok-probe_probe-tools__probe_ping`. So
|
|
"host-side server" and "cloud session" are not exclusive here: the server runs
|
|
on the Mac and is bridged in.
|
|
|
|
One design choice was vindicated by the log. `probe_tools.py` echoes the
|
|
client's `protocolVersion` rather than asserting its own default. The client
|
|
asked for `2025-11-25`, which is newer than the server's fallback; a hardcoded
|
|
version would have made this handshake a different and worse story.
|
|
|
|
**The consequence, which is a cost and not only a capability.** Because the
|
|
server runs on the Mac and is bridged in, a plugin-declared MCP tool in Cowork
|
|
is reachable only from a session that can reach this machine. That is the
|
|
honest shape of operator decision 7: it buys real host access -- the guard, the
|
|
workspace, the filesystem -- at the price of tying the Cowork surface to one
|
|
machine.
|
|
|
|
Stating the risk precisely matters more than stating it darkly, because an
|
|
overstated risk gets discounted and takes the real one with it. So:
|
|
|
|
- **"This Mac is asleep" is not the risk here.** This operator's Mac does not
|
|
sleep. And there is no wider user population to generalise to: build-brief
|
|
lines 3 and 22 scope this to a single operator searching for their own job,
|
|
and the plugin is not in the marketplace catalog. A concern about "most other
|
|
users" would be a concern about people this project does not have.
|
|
- **"Cowork opened somewhere else" is the risk.** The bridge is named
|
|
`remote-devices` and the server is a child of `Claude.app` on this Mac. A
|
|
session started on a phone or a second laptop was **not measured**, and
|
|
whether it reaches this machine's servers should not be assumed in either
|
|
direction until it is.
|
|
|
|
So the degradation branch below answers two triggers, not one: a `nei` on
|
|
`- Host-MCP:`, and Cowork being used away from this Mac.
|
|
|
|
Also worth recording, because it shaped this script's own check: an individual
|
|
`tools/call` does **not** appear in `main.log`. The connection lifecycle does.
|
|
`grep -c probe_ping main.log` returned 0 both before and after a successful
|
|
call, so the absence of that line proves nothing -- which is why the check
|
|
script says so instead of reading it as an answer.
|
|
|
|
### O2. The sandbox and the host are two different interpreters
|
|
|
|
| Where | Interpreter | Evidence |
|
|
|---|---|---|
|
|
| Cowork sandbox (skills, Bash) | Python 3.11.15, `[GCC 13.3.0]`, Linux | reported by `probe-versjon` |
|
|
| Host (MCP servers) | Python 3.14.0, macOS framework build | `ps`, and the venv Step 2 built |
|
|
|
|
`${CLAUDE_PLUGIN_ROOT}` resolves inside the sandbox to
|
|
`/root/.claude/plugins/synced/<session>_<account>/jobbsok-probe`. Both facts
|
|
matter for M3: `guard_ingest.py` cannot import the guard in the sandbox, so it
|
|
belongs on the host side of the bridge, which O1 shows is reachable.
|
|
|
|
The Linux paths and the GCC build string are also what answer the session-mode
|
|
question. It was read off a measurement, not off a settings screen.
|
|
|
|
### O3. Cowork's built-in browser reads Finn, authenticated
|
|
|
|
Operator measurement, 2026-09-04. Cowork's built-in browser (shipped
|
|
2026-08-26) rendered a Finn job ad (`finn.no/job/ad/<id>`) in full -- employer, title,
|
|
`Soknadsfrist`, `Mulighet for hjemmekontor`, `Arbeidsspraak`, `Sektor`: the
|
|
exact fields the M3 extraction wants, at a clean snapshot-able URL. The
|
|
operator was logged in to Finn, having signed in by hand inside that browser.
|
|
|
|
This is the brief's invariant reached by a shorter route: the plugin handles no
|
|
credentials, the operator authenticates manually, and the browser is already
|
|
authenticated when it reads. It raises a real M3 question -- if the built-in
|
|
browser covers both public and logged-in sources, what does Playwright MCP over
|
|
CDP still add? -- but that is an M3 decision and is not taken here.
|
|
|
|
Not established, and worth knowing before the decision is taken: whether that
|
|
login persists across sessions or must be repeated each time. Nothing was
|
|
measured about persistence.
|
|
|
|
Unlike O1 and O2, this one has no host-side trace: the built-in browser writes
|
|
nothing to `main.log` (checked for `finn.no`, `webview`, `browserTool` --
|
|
no hits). The operator's observation is the only instrument.
|
|
|
|
## Degradation branch
|
|
|
|
Written now, so the answer does not get decided under pressure later.
|
|
|
|
**If `- Host-MCP:` is `nei`** — host-side stdio servers do not appear in this
|
|
Cowork installation:
|
|
|
|
1. Operator decision 7's Cowork half is unavailable. Nothing changes for Claude
|
|
Code, where the scripts are invoked directly.
|
|
2. Every script-backed skill degrades to reading files that the Claude Code CLI
|
|
produced. The skills stay useful; they stop being able to compute.
|
|
3. `Verifiser i Cowork` for the affected milestones means "the skills load and
|
|
read files produced from Claude Code" — not "the connector answers".
|
|
4. The degradation is recorded in `STATE.md`.
|
|
|
|
This is risk H12's mitigation, and it is why the probe is the first step of the
|
|
build rather than a later check.
|
|
|
|
**If `- python3:` is `nei`** — a skill cannot invoke `python3` in a Cowork
|
|
session: the same degradation applies, and more broadly, since it removes the
|
|
CLI half too. Escalate to the operator before building further on decision 7.
|
|
|
|
**If `- CLAUDE_PLUGIN_ROOT:` is `nei`** — the variable does not resolve: every
|
|
intra-plugin path in the plan is affected, not just this milestone's. Escalate;
|
|
do not paper over it with hardcoded paths.
|
|
|
|
**If `- Sesjonsmodus:` is `sky`** — sessions run in the cloud, and host-side
|
|
servers cannot reach this machine at all. `- Host-MCP:` should then read `nei`,
|
|
and its branch applies. If the two disagree, the disagreement is the finding.
|