Skip to content

Make an attached remote Planner own its document - #469

Merged
lavaman131 merged 4 commits into
remote-planner-linkfrom
planner-attach-ownership
Oct 11, 2026
Merged

lavaman131 merged 4 commits into
remote-planner-linkfrom
planner-attach-ownership

Conversation

@lavaman131

@lavaman131 lavaman131 commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on the remote Planner link PR. Under HARNESS=remote, attaching makes the client the document's Planner owner, and a document with no attached Planner refuses Planner requests clearly.

  • Ownership: attaching claims the owner through the existing atomic, generation-guarded ownership storage. Eligibility is instance admission, repository push or admin access, and a live Chopin browser sign-in (stricter than "can read"; documented in docs/hosted-agent.md).
  • One Planner per document: a second attach is refused with planner-attached, naming the holder.
  • Grace period: detach or a dropped socket releases ownership after a configurable grace period; the same login reattaching within it keeps ownership. Startup clears attachments. Signing out or a reset releases the attachment.
  • planner-not-attached: @chopin and MCP invoke_planner on an unattached document fail with that code, nothing posted or queued; the message names the document URL and says to connect a Planner client to it from a checkout of the repository. A stale attachment (owner sign-in expired) counts as unattached. Other harnesses are unchanged.
  • Delivery: with a Planner attached, invoke_planner and @chopin become turns, and Stop/Resume Planner reach the client.

Verification

  • bun run types: passed.
  • GIT_CONFIG_GLOBAL=/dev/null bun test: 4672 pass, 4 skip, 0 fail (on main with Atomic 0.9.33).
  • bun run ci: passed (0 errors).
  • PostgreSQL contract and lifecycle tests against a disposable postgres:17-alpine: 85 pass, 0 fail.
  • Real-WebSocket regressions for ownership, planner-attached, grace reattach/release, sign-out during attach (sign-in-required/4401), and Chat/MCP refusals with nothing saved.
  • Three review rounds plus a targeted re-review; the last review approved with no findings.
  • End-to-end against a deployed instance with an external Planner client written only from docs/planner-link.md: passed (summary in a comment on this PR).

Assistant-model: Claude Opus 5.5 (fast)
Assistant-workflow: hosted-planner-slices (run b51d4e3c, repairs by worker subagents)
Assistant-verification: bun test passed: 4672 pass, 0 fail
Assistant-verification: postgres contract passed: 85 pass on a disposable database
Co-authored-by: Alex Lavaee alexlavaee@microsoft.com

lavaman131 and others added 4 commits October 10, 2026 20:48
…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 run
pdd-plan with it as plan_document 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>
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
lavaman131 force-pushed the planner-attach-ownership branch from 9d01994 to 114ba56 Compare October 11, 2026 03:48
@lavaman131
lavaman131 marked this pull request as ready for review October 11, 2026 04:39
@lavaman131
lavaman131 merged commit 088851e into remote-planner-link Oct 11, 2026
3 checks passed
@lavaman131
lavaman131 deleted the planner-attach-ownership branch October 11, 2026 04:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant