Skip to content

docs(plans): plan offloading the harness to another device - #2364

Merged
jonathanKingston merged 5 commits into
mainfrom
claude/harness-cross-device-offload-o6ejvb
Sep 9, 2026
Merged

docs(plans): plan offloading the harness to another device#2364
jonathanKingston merged 5 commits into
mainfrom
claude/harness-cross-device-offload-o6ejvb

Conversation

@jonathanKingston

@jonathanKingston jonathanKingston commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add docs/plans/cross-device-harness-offload.md, a plan for running the harness on a second machine while the desktop stays in charge and the laptop is free to close
  • reconcile it with thread-in-container.md, which landed the loop-in-guest half while this was open
  • correct two plan statuses that no longer matched main, and register the new plan in the index

What changed since this was opened

main moved 197 commits and merged the container work, which falsified this plan's central qualification. It said "the runtime is portable and unused"; src/main/services/container-runtime/worker-entry.ts now calls runHeadlessAgent inside a guest container, so main-process code drives the portable runtime off the desktop today. The plan says that instead of the original claim.

The honest position is that this plan is partly overtaken:

Phase Status
O0 worker entry Delivered for a disposable local container
O3 unattended + deferral Delivered — unattended ledger, zero prompts, git push deferred
O4 leased credentials + egress Delivered, and more strictly than proposed here: --network none plus a per-origin unix-socket broker is execution-runtime-security.md R3's broker-only egress, met for that runtime
O1 fenced writer lease Unbuiltthread-in-container.md parks it as T2
O2 attach / detach / reattach Unbuilt — same T2

So the remaining value here is the ownership half. O1/O2 and T2 name the same mechanism (the per-thread writer lease from acp-session-continuity.md), and should be one piece of work rather than two. Where the two plans disagree, the shipped prototype wins — stated in the document.

Why the plan still earns its place

The argument it makes is that offloading is an ownership problem, not a transport one: the thread store's mutual exclusion is an in-process mutex rather than a file lock, so two processes over one workspace have no mutual exclusion at all. A fenced lease therefore comes before any second worker. That is unaddressed on main, and it is the exit gate thread-in-container.md sets for T2 ("close the desktop mid-run, reopen, and observe one converged thread").

It also records the measurement (30 of 517 non-test src/main files name electron, 13 for types only, so 17 at runtime; none on the agent, tool, hook, sandbox or thread-store path), the execution table including the container column, and nine binding decisions. Two of those now have a shipped prototype agreeing with them: Decision 7 (containment must be proven before unattended) matches the attestation, and Decision 8 (one leased credential in the worker) matches the narrowing of unattended-runs.md Decision 3.

Boundary with the other plans

  • library-splits.md did the extraction that makes the question worth asking.
  • client-server-split.md (#2312) owns the mechanism; step 4's copse-core daemon is this plan's worker.
  • thread-in-container.md owns the runtime, the contained-effect gate, the egress broker and the credential narrowing. It is the senior sibling and landed first.
  • This plan settles neither how the packages are cut, nor how the protocol is versioned, nor how the container is hardened.

Status corrections

Both verified against the tree, not inferred:

  • Headless automation contract — the doc said Phases 0–1 were "not yet on main". The contract module, its published JSON Schema, gen:headless-schema, and the bench consumer are all on main. Restated as Phase 0 landed.
  • Deferred approvals — the index said D0 only, while the doc says D0–D1 landed; deferral-mode.ts and deferred-approval-store.ts are on main. Index aligned to the doc.

The index's "Last audited" date is deliberately unchanged: this corrects three rows, it is not a full audit.

Validation

Documentation only; no source, schema, or fixture changes.

  • rebased onto current main (a288035), no conflicts
  • oxfmt --check passes across all 77 files in docs/plans/
  • every relative link resolves, including the new thread-in-container.md references
  • the index note is kept under the table's existing column width so the formatter does not re-pad every row; the index diff stays confined to the three rows this PR owns
  • the last full run with ci-full was green end to end, including all 8 e2e shards and screenshot-artifacts; CI re-runs on this head

Review note

The ci-full label is still applied. Now that this is out of draft it is only needed to defeat the oracle's docs-only thinning, which zeroes the e2e shards — remove it if you would rather this PR stop running the heavy tier on every push.

🤖 Generated with Claude Code

https://claude.ai/code/session_011i4xR3bwms6hEJDdd449qo

@jonathanKingston
jonathanKingston force-pushed the claude/harness-cross-device-offload-o6ejvb branch from 00d6b2a to 313acdc Compare September 5, 2026 00:29
github-actions Bot added a commit that referenced this pull request Sep 5, 2026
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

🖥️ PR preview

@jonathanKingston
jonathanKingston force-pushed the claude/harness-cross-device-offload-o6ejvb branch from 313acdc to 0fe4f22 Compare September 5, 2026 02:35
github-actions Bot added a commit that referenced this pull request Sep 5, 2026
@jonathanKingston

Copy link
Copy Markdown
Collaborator Author

Audit result: the documentation diff is internally consistent, git diff --check is clean, GitHub reports it mergeable, and its CI gate is green. It is two main commits behind, but there is no source/runtime validation gap to investigate in this docs-only proposal.

I would not open another implementation issue yet. The Proposed plan is the decision artifact; if Decisions 7/8 are accepted and work is scheduled, create one implementation tracker then (or convert/link an existing roadmap issue) rather than duplicating the unresolved design discussion now. Keep draft until that decision review is complete.

@jonathanKingston
jonathanKingston force-pushed the claude/harness-cross-device-offload-o6ejvb branch from 0fe4f22 to 97d2f70 Compare September 6, 2026 09:19
github-actions Bot added a commit that referenced this pull request Sep 6, 2026
@jonathanKingston
jonathanKingston force-pushed the claude/harness-cross-device-offload-o6ejvb branch from 97d2f70 to 766a2e5 Compare September 6, 2026 10:57
@jonathanKingston jonathanKingston added the ci-full Force the full e2e suite (bypass the test-oracle thinning) on this PR label Sep 6, 2026 — with Claude
github-actions Bot added a commit that referenced this pull request Sep 6, 2026
@jonathanKingston
jonathanKingston marked this pull request as ready for review September 9, 2026 04:01
Records what the package extraction made possible and what still blocks a run
continuing after the desktop disconnects.

The runtime is already portable and unused: 17 of 476 non-test files in
src/main import electron at runtime and none are on the agent, tool, hook,
sandbox or thread-store path; headless-agent-host.ts runs the whole product
loop with no renderer; verify-agent-path-import.mts enforces that with electron
poisoned; and the Tauri sidecar already runs the main process as plain Node.
The only non-test caller of runHeadlessAgent is a benchmark script.

The plan's argument is that offloading is an ownership problem rather than a
transport one. SSH already carries the tool layer, but the thread store's
mutual exclusion is an in-process mutex, not a file lock, so a fenced
per-thread writer lease has to come before any worker process. It sets out
nine binding decisions and phases O0-O6 with exit gates, and names where it
departs from the existing "provider keys never leave the desktop" line.

Also corrects two plan statuses that no longer match main:

- The headless automation contract module, its published JSON Schema and
  gen:headless-schema are on main; the doc still said they were only on a
  feature branch.
- Deferred approvals D1 has landed (the defer outcome and its durable
  append-only queue); the index still listed D0 only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i4xR3bwms6hEJDdd449qo
main gained library-splits.md, the plan that owns the extraction this one
builds on, so state the relationship instead of re-deriving it:

- name library-splits.md as the parent, and say this plan is the product
  capability its client/server split unlocks rather than a second opinion on
  how the packages are cut
- O0 now consumes step 9 (#2312, the versioned API protocol and ShellHost)
  instead of proposing its own worker entry mechanism; if #2312 lands first,
  O0 is a packaging step
- reconcile the Electron measurement with the count library-splits.md uses:
  30 of 476 non-test src/main files name electron, 13 for types only, so 17
  import it at runtime

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i4xR3bwms6hEJDdd449qo
main gained client-server-split.md, which promotes #2312 out of
library-splits.md step 9 into its own Active plan with step 1 landed. Follow it:

- O0 now consumes that plan rather than the library-splits step. Its step 4
  produces copse-core, "a daemon with three front doors: Electron IPC,
  WebSocket, and the ACP agent server" — that daemon is this plan's worker, so
  O0 is its first remote consumer, adding only what a remote deployment needs
  that a local daemon does not: a sandbox-state handshake and the O1 lease
- name both split plans as parents, and say this plan settles neither how the
  packages are cut nor how the protocol is versioned
- re-measure against the rebased tree: the denominator moved to 479 non-test
  src/main files, while 30 named / 13 type-only / 17 runtime still hold

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i4xR3bwms6hEJDdd449qo
main grew a non-test file in src/main, so the denominator is 480, not 479;
30 named / 13 type-only / 17 runtime are unchanged.

Also tighten the "only non-test caller" line: verify-agent-path-import.mts
imports runHeadlessAgent without calling it, purely to prove the graph loads
under a poisoned electron, so autonomy-regression-agent.mts remains the only
caller that actually drives a run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i4xR3bwms6hEJDdd449qo
thread-in-container.md and src/main/services/container-runtime/ are on main
now, and they falsify this plan's central qualification. It said "the runtime
is portable and unused"; container-runtime/worker-entry.ts calls
runHeadlessAgent inside a guest container, so main-process code drives the
portable runtime off the desktop today.

Reconciled rather than restated:

- status is Proposed (partly overtaken): O0, O3 and O4 are delivered for a
  disposable local container, and the plan says so at each phase instead of
  describing them as unbuilt
- O4 was delivered more strictly than proposed here — --network none plus a
  per-origin unix-socket broker is execution-runtime-security R3's broker-only
  egress, met for that runtime
- O1/O2 are the half nothing has built, and are the same work as that plan's
  T2, which names the same lease mechanism; say so rather than duplicating it
- the execution table gains a container column, including that the guest does
  not need the window but cannot be attached to
- where the two disagree, the shipped prototype wins

Also re-measure after 197 commits of main: 517 non-test src/main files, with
30 naming electron, 13 for types only, 17 at runtime.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i4xR3bwms6hEJDdd449qo
@jonathanKingston
jonathanKingston force-pushed the claude/harness-cross-device-offload-o6ejvb branch from 766a2e5 to b6cb073 Compare September 9, 2026 04:06
github-actions Bot added a commit that referenced this pull request Sep 9, 2026
@jonathanKingston
jonathanKingston merged commit ad9af1b into main Sep 9, 2026
25 checks passed
@jonathanKingston
jonathanKingston deleted the claude/harness-cross-device-offload-o6ejvb branch September 9, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-full Force the full e2e suite (bypass the test-oracle thinning) on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants