Repository navigation
Run the Planner in a client attached over a Planner link - #468
Merged
Merged
Conversation
HARNESS=remote adds a HarnessV1 adapter whose sessions and turns run in a client attached at /planner-link instead of in the server. The server keeps the document, Decisions and room writes: Chopin's host tools still execute here against the room, and only the model and the client's own tools run on the client's machine. The link is a versioned, schema-validated WebSocket protocol in the new `@chopin/planner-link` package. Frames are strict JSON objects of at most 2 MiB; a malformed, out-of-order or oversized frame ends the link with an error message and close code 1008 or 1009. docs/planner-link.md states the whole wire format, and the package's minimal client uses only that format at runtime, so the remote harness contract suite runs both in Bun and as a separate Node.js process. The upgrade request authenticates a GitHub token bearer exactly as hosted MCP does (401, 403, 503 before upgrading), and attach requires pull access to the document's repository; refusals post nothing. Access is rechecked before every session. Client questions become Decisions through createHumanInput, which now also reports whether a request was answered, expired or cancelled, keeping the verbatim text, 30-minute expiry, cancellation and paused-run hold of the in-process Atomic Planner. Run cards, Stop and Resume travel over the link. A link that drops mid-turn ends the turn with an error, withdraws its questions and shows its runs stopped. HARNESS=remote is accepted on a public bind without MODEL, refuses any HARNESS_AUTH, and requires BACKGROUND_JOBS=off because summary and research workers would have no model. Until one Planner per document is enforced, the newest attachment serves new sessions. Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: hosted-planner-slices Assistant-duration: 21m converged, estimated 2h Assistant-verification: bun run types passed: every workspace package and e2e Assistant-verification: bun test passed: 4621 pass, 4 skip, 0 fail (GIT_CONFIG_GLOBAL=/dev/null bun test) Assistant-verification: harnessContract passed: remote harness over a real WebSocket with the Bun client and a Node.js 24 client process Assistant-verification: bun run ci passed: dprint, oxlint (31 warnings, same as main), tokens, design contract, Impeccable Assistant-verification: relative doc links passed: scripted check of paths and anchors in the changed docs Co-authored-by: Alex Lavaee <alexlavaee@microsoft.com>
A remote Planner's run reports no longer start follow-up turns: the runs
are only the client's word, so a pull-only client could otherwise start
a turn no writer asked for. Its blocked or failed runs still get their
Chat notice.
The link rechecks admission and pull access before every turn, including
turns of a session kept for its runs, and every 5 minutes while
attached; a lost check closes the link with access-revoked. A host call
id may be used once per turn even after its call has finished, and a
host call for an ended session gets the documented error result. Every
host-tool error result is now { "error": "..." }, as HarnessAgent's own
are.
The package client settles a waiting host call as failed on abort,
destroy, turn end or close, asks nothing when its signal is already
aborted, settles requests made after the link closed instead of leaving
them pending, and cuts generated message and error text to the 4000
character wire limit.
docs/planner-link.md states the input-result value for every status and
method, the QuestionAnswer shapes, partial answers on cancellation, and
error output shapes; docs/architecture.md describes the remote Planner
and its link.
Assistant-model: Claude Opus 5.5 (fast)
Assistant-workflow: hosted-planner-slices
Assistant-duration: 8m converged, estimated 45m
Assistant-verification: bun run types passed: every workspace package and e2e
Assistant-verification: bun test passed: 4630 pass, 4 skip, 0 fail (GIT_CONFIG_GLOBAL=/dev/null bun test)
Assistant-verification: bun run ci passed: dprint, oxlint (31 warnings, same as main), tokens, design contract, Impeccable
Assistant-verification: client regression tests passed: the 4 new client tests fail against the previous client.ts and pass now
Co-authored-by: Alex Lavaee <alexlavaee@microsoft.com>
… results A repository lookup that GitHub answers with 401 now counts as revoked access, so the link sends access-revoked and closes even while admission is cached; an outage still leaves it open. An oversized host-tool result becomes the same error for the server's turn and the client. The client closes on an unparseable server frame with 4400, which Node.js permits, and settles what waits at once. /planner-link answers 404 when the server does not run the remote Planner, and the wire docs state every field limit the schemas enforce. Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: hosted-planner-slices Assistant-duration: 8m converged, estimated 30m Assistant-verification: bun run types passed: every workspace package and e2e Assistant-verification: bun test passed: 4636 pass, 4 skip, 0 fail (GIT_CONFIG_GLOBAL=/dev/null bun test) Assistant-verification: bun run ci passed: dprint, oxlint (31 warnings, same as before), tokens, design contract Assistant-verification: regression tests passed: the repository-401 and oversized-result tests fail against the previous link.ts/endpoint.ts; the Node.js v24.21.0 malformed-frame test fails with InvalidAccessError against close code 1008 Co-authored-by: Alex Lavaee <alexlavaee@microsoft.com>
…start fails A remote turn that ended while one of Chopin's host tools still ran left that tool waiting: the harness waits for outstanding host tools before it settles the turn, and they ran only under the caller's signal. A link that dropped during an `ask` therefore left the stream pending, Chat busy, and the Decision open. Each remote turn now carries its own host tool signal, which the session passes to the Planner's tools beside the caller's and the turn aborts whenever it ends: on link loss, protocol violation, detach, the client's turn-end, or destroy. The tool withdraws its cards through the normal cancellation path, and a dropped link still ends the turn with the disconnect error rather than as a stop. A session refused with start-failed or not started in time was dropped without withdrawing questions the client asked meanwhile. A failed start now closes the session like destroy, so those Decisions are withdrawn and the client's ask settles as cancelled. Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: hosted-planner-slices (run 8cf1bc0a) repair (worker subagent) Assistant-verification: bun run types passed: every workspace package and e2e Assistant-verification: GIT_CONFIG_GLOBAL=/dev/null bun test passed: 4639 pass, 4 skip, 0 fail across 673 files Assistant-verification: bun run ci passed: dprint, oxlint (31 warnings, 0 errors), tokens, design contract, Impeccable Assistant-verification: new link tests failed before the fix (timeouts, open Decision) and pass after it Co-authored-by: Alex Lavaee <alexlavaee@microsoft.com>
A Planner link forgot an input-request id once its question settled, and never recorded an id it answered failed at once, so a client could reuse a completed id and get a second result. The link now remembers every open id and the last 1,024 it received, and a reuse it remembers is a protocol violation that closes the link with 1008. Tool call ids had the same gap within a turn: a host call refused for an unavailable tool did not record its id, and an own tool-call part could repeat its id. Both now fail the turn as a repeated call. Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: hosted-planner-slices (run 8cf1bc0a) repair (worker subagent) Assistant-verification: bun run types passed (exit 0) Assistant-verification: GIT_CONFIG_GLOBAL=/dev/null bun test passed (4642 pass, 4 skip, 0 fail) Assistant-verification: bun run ci passed (exit 0, 0 lint errors) Co-authored-by: Alex Lavaee <alexlavaee@microsoft.com>
The image build copied each workspace's manifest by name and missed packages/planner-link, so `bun install --frozen-lockfile` refused the pruned checkout and the image did not build. Copy its manifest and its production node_modules like the other server workspaces. Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: inline Assistant-verification: docker build passed: full image built locally from this commit Assistant-verification: image smoke passed: `@chopin/planner-link`, its client and apps/server harness/remote/endpoint.ts import inside the runtime image Assistant-verification: bun run ci passed Co-authored-by: Alex Lavaee <alexlavaee@microsoft.com>
lavaman131
marked this pull request as ready for review
October 11, 2026 04:39
…hed requests Under HARNESS=remote, attaching a client now makes its account the document's Planner owner through the same generation-guarded claim every owner takes. The owner record names the account's live browser login, since Chopin's repository tools run with that login's GitHub App token and never with the link's bearer; an account without one is refused with sign-in-required (4401). Attaching needs the eligibility every Planner owner already needs, instance admission plus push or administration access, rather than the design's pull access, and the periodic recheck now holds a link to that too. A document has one Planner. Another attach while one is attached is refused with planner-attached (4409), naming the holder's login. A detach or dropped socket keeps the ownership for PLANNER_ATTACH_GRACE_MS (two minutes by default) so the same account can reattach under the same generation; then it is cleared under that generation. Attachments live in the process, and startup already clears every owner reference. Members, MCP callers and research requests no longer claim ownership under this harness. An `@chopin` message or invoke_planner call for a document with no attached client is refused with planner-not-attached before anything is posted or queued; the message names the document URL and says to connect a Planner client to it from a checkout of the repository. The browser shows that text in full on the request, and MCP returns it beside the code. With a client attached, both arrive as turns on its link, and Stop and Resume Planner reach it. Other harnesses are unchanged. Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: hosted-planner-slices Assistant-duration: 13m converged, estimated unmeasured Assistant-verification: bun run types passed: every workspace package and e2e Assistant-verification: GIT_CONFIG_GLOBAL=/dev/null bun test passed: 4657 pass, 0 fail Assistant-verification: bun run ci passed: dprint, oxlint (0 errors), tokens, design contract, Impeccable Assistant-verification: PostgreSQL contract passed: bun test apps/server/src/storage/postgres against a disposable postgres:17-alpine on port 55432, 83 pass, 0 fail Assistant-verification: mutation check passed: disabling the planner-not-attached refusal in processSend fails the new chat service test Co-authored-by: Alex Lavaee <alexlavaee@microsoft.com>
An attachment trusted its in-memory link after the stored ownership under it was gone. Signing the owner's browser login out, its expiry, or a repository writer's Planner reset left the link attached, so `@chopin` and invoke_planner failed later with planner-owner-unavailable instead of planner-not-attached, and no one could attach until the old socket closed. Ending a login now closes every link it owns with sign-in-required (4401) and clears that ownership under its generation; a reset closes the link with access-revoked (4403). Neither starts a grace period, so any eligible account can attach at once. The checks before every start and turn, and the periodic one, also close a link whose stored owner no longer names its login and generation, or whose login expired without being revoked. The documentation now limits planner-not-attached to `@chopin` and invoke_planner, says how other Planner requests behave unattached, and says model-backed research is unavailable under the remote Planner. Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: hosted-planner-slices Assistant-duration: 6m converged, estimated 10m Assistant-verification: bun run types passed: every workspace package and e2e Assistant-verification: GIT_CONFIG_GLOBAL=/dev/null bun test passed: 4663 pass, 0 fail Assistant-verification: bun run ci passed: dprint, oxlint, tokens, design contract, Impeccable Assistant-verification: mutation check passed: disabling sessionRevoked, ownerReset and verify fails all six new link and chat service tests Co-authored-by: Alex Lavaee <alexlavaee@microsoft.com>
A grace reattach read the stored owner and then bound the hold it had captured, without checking whether a Planner reset or the owner's sign-out had released that hold during the read. A snapshot taken before the reset could still match, so the client reported attached while the server had no owner, refused attached turns, and kept an orphaned registry link. Each attach now records the sign-ins it rests on while it runs. A reset of its document or the end of one of those sign-ins marks it, and it binds only if nothing marked it and its owner sign-in is still live after its last await; otherwise it releases any ownership it claimed, under its generation, and is refused with the code an attached link would be closed with. The recheck of an attached link now asks whether the owner's sign-in is live before comparing the stored owner. PostgreSQL and memory storage report an expired sign-in's ownership as released, so an expiry that cleanup had not yet seen closed the link with access-revoked (4403) instead of sign-in-required (4401). Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: hosted-planner-slices Assistant-duration: 9m converged, estimated 15m Assistant-verification: bun run types passed: every workspace package and e2e Assistant-verification: GIT_CONFIG_GLOBAL=/dev/null bun test passed: 4668 pass, 4 skip, 0 fail Assistant-verification: bun run ci passed: dprint, oxlint, tokens, design contract, Impeccable Assistant-verification: PostgreSQL contract passed: bun test apps/server/src/storage/postgres against disposable postgres:17-alpine on 55432, 85 pass, container stopped Assistant-verification: mutation check passed: the previous attachments.ts fails all five new link and storage contract tests Co-authored-by: Alex Lavaee <alexlavaee@microsoft.com>
lavaman131
force-pushed
the
remote-planner-link
branch
from
October 11, 2026 04:46
088851e to
3961355
Compare
The Chat and invoke_planner preflight under HARNESS=remote accepted any in-memory attachment with a link, without asking whether its owner's browser sign-in was still live or storage still assigned that owner. If the sign-in expired before the link's periodic recheck, Chat saved and announced the member's message before owner resolution failed, and invoke_planner returned planner-owner-unavailable instead of planner-not-attached. Both now confirm the attachment as the periodic recheck does. A stale one is closed with the code an attached link would get (sign-in-required or access-revoked), its ownership is released, and the request is refused with planner-not-attached and the attach guidance, with nothing saved or queued. A fresh attach registered its sign-in with the attempt only after the repository and admission checks, so a sign-out during them went unnoticed and the ownership claim then threw once storage had deleted the session, refusing the client with unavailable (1011). The sign-in is now registered before those checks, and a claim that storage refuses because the sign-in ended is refused with sign-in-required (4401). Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: hosted-planner-slices (run b51d4e3c) repair (worker subagent) Assistant-verification: bun run types passed: every workspace package and e2e, 14 exited with code 0 Assistant-verification: GIT_CONFIG_GLOBAL=/dev/null bun test passed: 4672 pass, 4 skip, 0 fail Assistant-verification: bun run ci passed: dprint, oxlint 0 errors, tokens, design contract, Impeccable Assistant-verification: PostgreSQL contract passed: bun test apps/server/src/storage/postgres against disposable postgres:17-alpine on 55432, 85 pass, container stopped Assistant-verification: mutation check passed: the previous attachments.ts and main.ts preflight fail all four new tests; removing only the claim fallback fails the storage-expiry attach test Co-authored-by: Alex Lavaee <alexlavaee@microsoft.com>
lavaman131
force-pushed
the
remote-planner-link
branch
from
October 11, 2026 04:58
3961355 to
2cf7ee3
Compare
Collaborator
Author
End-to-end on a deployed instanceThis stack was built with the repository Dockerfile and deployed with
The run also found that the Docker image didn't ship the new |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
HARNESS=remote: the Planner's model turns run in a client attached to the document over an authenticated WebSocket at/planner-link, usually a member's own Atomic session on their machine. The server keeps the document, Decisions, room writes and anchors; the client keeps its code, model credentials and its own tools.packages/planner-link,docs/planner-link.md): versioned, schema-validated JSON messages of at most 2 MiB, documented as a complete wire format for clients that cannot import Chopin packages. Covers attach, start, turns with abort, streamed parts, host tool calls, input requests with answer/expiry/cancel, Stop/Resume, run cards, destroy and detach.apps/server/src/harness/remote/endpoint.ts): the upgrade carries a GitHub bearer checked like hosted MCP (401/403/503 before upgrading); the bearer's account needs pull access to the document's repository, rechecked before every session and turn and every 5 minutes.HARNESS=remote): forwards start/turn/abort/destroy. Host tools still run in the server against the room. Client questions become Decisions throughcreateHumanInput, with the in-process Planner's expiry, cancellation and paused-run hold. A turn ending or the link dropping stops its running host tools and withdraws their Decisions; a failed start withdraws questions it asked.HARNESS=remoteis accepted on a non-loopback bind withoutHARNESS_AUTHorMODEL, since the model runs on the client.Ownership (one Planner per document,
planner-attached,planner-not-attached) is the stacked follow-up PR.Verification
bun run types: passed.GIT_CONFIG_GLOBAL=/dev/null bun test: 4642 pass, 4 skip, 0 fail (rebased on main with Atomic 0.9.33).bun run ci: passed (0 errors).harnessContractsuite againstHARNESS=remotethrough a test client that speaks only the documented wire format, over real WebSockets on Node 24: passed.ask, failed start with an open question, and reused request/tool-call IDs.docs/planner-link.md: passed (summary in a comment on this PR).Assistant-model: Claude Opus 5.5 (fast)
Assistant-workflow: hosted-planner-slices (runs 8cf1bc0a, repairs by worker subagents)
Assistant-verification: bun test passed: 4642 pass, 0 fail on main + this branch
Assistant-verification: harness contract passed: HARNESS=remote over real WebSockets
Co-authored-by: Alex Lavaee alexlavaee@microsoft.com