Skip to content

Latest commit

 

History

History
768 lines (680 loc) · 48.5 KB

hosted-agent.md

File metadata and controls

768 lines (680 loc) · 48.5 KB

Hosted agent (Planner)

Chopin's Copilot-backed document agent is currently named Planner. It can inspect one selected GitHub repository, co-author the shared document, ask the participants structured questions, and anchor decisions to prose. For documents used as plans, it can also draft an implementation graph. Under copilot-sdk it does not implement code or change GitHub; under atomic it runs as a full Atomic session, described below; under remote its model runs in a member's own client, described in Remote Planner.

The product role is document co-authoring. The current prompt and tool vocabulary remain optimized for planning and may structure another document type as a plan; that is an implementation limitation, not the document model's boundary.

Chopin runs the Planner through @ai-sdk/harness: HARNESS selects one adapter from a code-owned map, defaulting to copilot-sdk, a host-process adapter over @github/copilot-sdk. atomic, which embeds Atomic's headless SDK (@bastani/atomic) in the server process, is a further reviewed adapter and requires an explicit HARNESS_AUTH. remote runs no model in the server at all: each turn goes to a client attached over the agent connection. The harness owns the agent loop and model. Under copilot-sdk, Chopin owns every tool the Planner can call.

Choosing HARNESS=atomic gives the Planner shell and filesystem access as the server process's user, on hosted instances as well as local ones. No other flag turns this on or off. Operators who do not want it should use copilot-sdk. See Self-hosting for HARNESS/HARNESS_AUTH selection and adapter trust.

Coaching in an invoked turn

The Planner is instructed to develop a small, provisional account of the problem early, using the current document and relevant repository evidence. It distinguishes reported experience, verified behavior, hypotheses, its own recommendations, and human decisions. A suggested remedy, such as onboarding, does not become an agreed requirement merely because someone mentioned it.

Every request for human input, including missing intent and clarification, uses the open_decisions tool. The Planner drafts provisional prose, then batches the known independent questions (up to ten) into separate cards beside the passages they inform. Written answers capture intent, experience, constraints and unknown alternatives; choice cards offer grounded alternatives where useful. Both forms can appear in one batch. Cards stay open after the Planner finishes its turn, and people can answer in any order. Only Save settles an answer. Repository facts need answers, not votes. Optional exploration can remain provisional without becoming a Build blocker.

On every harness, Chat buffers each complete Planner text message and checks it with the deployment's decisions classifier before publication. Requests for input are withheld and the same session gets one continuation to create inline cards. Completed tools and the original request are not replayed. Failed, missing, malformed or interrupted checks never publish unchecked prose; uncertain judgments also withhold it. Chat prose therefore arrives after validation rather than token by token. This is a semantic check and can misclassify language. If both generated replies are withheld after the turn durably captured new inline decisions, Chat reports their count in a fixed server-authored summary. It never publishes the withheld text or claims the whole request succeeded. Without new captured decisions, the turn still reports failure.

An answer, correction, or contradictory finding should change the affected prose and its anchors, followed by the next useful authorized action. Disagreement stays attributed rather than becoming artificial consensus. Pauses, dismissals, and requests to continue exploring remain meaningful. An unanswered, cancelled, or expired question is not consent.

The Planner can recommend a small first build and explain what it would teach, while leaving broader questions open. This is advice about scope, not technical readiness or approval. Graph preparation retains its separate read/edit workflow and does not edit prose; only people approve and start implementation.

Saving an anchored Planner decision durably queues a scoped prose update, independently of conversation-analysis flags. It uses the complete saved answers affecting that passage and updates only its explicit target. Applied answer generations commit with the source, so an interrupted retry cannot write them twice. Lost or ambiguous targets refuse safely. Work waits for an existing authorized Planner owner; it never claims ownership from a viewer or saver. See Decisions for the shared answer lifecycle.

This does not start background research without an explicit request. Blocking ask remains available for an explicitly synchronous exchange and keeps that turn waiting for answers or cancellation. Ordinary unaddressed Chat retains its addressing and title-maintenance behavior.

The coaching evaluation suite separates live model transcripts and independent rubric review from deterministic tool-boundary tests. Its synthetic repository is an evaluation fixture, not a description of Chopin's current interface.

Ownership

The first eligible editor whose saved Chat message starts title maintenance, who invokes the Planner, or who starts a model-backed research request supplies the GitHub App user access token for that channel and, under the default copilot-sdk harness, the Copilot entitlement. Under HARNESS=atomic the owner supplies only the GitHub token; model access comes from the operator's HARNESS_AUTH mode. Under HARNESS=remote the owner also supplies only the GitHub token, and the model runs in the client attached to the document; only attaching makes an account the owner there. The user must pass instance admission and have repository push or administration access. Ownership is assigned atomically in storage and guarded by a generation token.

That login owns the channel's model usage in this process until it expires, logs out, the server restarts, or the authenticated reset API releases it. The current web application does not expose a reset control. Under copilot-sdk, a user without Copilot entitlement sees the provider failure on the first model-backed action and remains owner until one of those release conditions occurs.

Title maintenance uses an isolated heading job after a member message is saved, including ordinary coworker messages. It works with CONVERSATION_PLAN=off without Jev and is disabled by AGENT=off. It preserves weak-context titles and existing body prose. When the topic changes it may refine a blank title, the generated name of a document created without one, or the title a Planner last drafted (kept in the sidecar as plannerTitle). A title a person set, at creation, by renaming or by editing the heading, is never replaced; the job may still seed a goal beneath it. Missing credentials, an absent remote attachment and an implementation lock keep new work pending. Closing or detaching interrupts live work; reconnecting never replays an interrupted job. Under remote, title work uses only the attached owner and cannot claim ownership for a message sender.

An MCP invoke_planner call follows the same rules. Its instruction is posted as the caller's own message; the turn runs under the channel's current owner, whoever that is. A channel without one is claimed for the caller's live browser login, hosted or local, and without such a login the call is refused and nothing is posted. With as: "system" the call is an automatic follow-up: Chat shows a system notice naming the caller instead of their message, and the instruction goes to the Planner alone. Only the document's creator or the account holding its Planner may send one, at most 10 an hour per document. Under HARNESS=remote only attaching claims ownership. An @chopin message or invoke_planner call for a document without an attached Planner is refused with planner-not-attached, and with one it runs as a turn on that Planner under its owner. Other Planner requests, such as sending a comment to the Planner or a Build draft, claim nothing either: with a client attached they run on it, and without one they fail as any request without a usable owner does, not with planner-not-attached. Model-backed research is unavailable, because the remote Planner requires BACKGROUND_JOBS=off; a research request claims no ownership and is refused as not ready.

An attachment lasts only as long as its owner. Signing that browser login out, its expiry, or a repository writer's reset of the document's Planner closes the attached connection at once and frees the document for any eligible account to attach; see Agent connection ownership.

Planner ownership stores only the owner session ID, referring to a login loaded in this process. Hosted logins separately persist encrypted credentials in PostgreSQL. Startup clears every owner reference while preserving valid hosted sessions, the document, transcript, reserved context fields, and ownership generation. A returning browser must present its cookie and pass authorization before that session is available for a new Planner claim.

Runtime isolation

Under copilot-sdk the Planner receives only Chopin's host-executed tools: document, question, relationship, implementation-graph, repository, and GitHub tools, each bound to the channel's repository through toolsContext. No harness built-in is active for the Planner. The default copilot-sdk adapter additionally runs the shared Copilot runtime in SDK mode: "empty": each disposable session receives its owner's token when created and has no client-level service token or logged-in-user fallback. That detail is specific to the Copilot SDK adapter, not a property every harness in HARNESS shares.

The two pull-request tool names and input schemas are available when a Planner session opens. GitHub MCP discovery runs on the first pull-request tool call, using the owner's current credential. An unavailable server or missing required tool fails that call; document-only turns do not wait for discovery.

That isolated Planner has no:

  • checkout, shell, or host filesystem;
  • skills, plugins, or configuration discovery;
  • repository-local instruction loading;
  • shared embeddings or cross-session store; or
  • ability to change GitHub.

Chopin's tools available to the Planner under every harness are:

  • Chopin document, question, relationship, and implementation-graph tools (with current plan-oriented tool names);
  • Chopin's child document tools (list_children, read_child, edit_child, anchor_child), bound to the root document's own children and rechecked on every call (owner access, parentage, repository, and archive state). They are never offered to background jobs, and unexpected storage errors reach the model only as a generic message;
  • bounded file and tree reads plus commit history using the session's default branch, or an explicitly supplied full commit ID when inspecting investigation evidence;
  • repository-scoped code search, post-filtered by repository node ID; and
  • bounded reads of document and historical research references attached to the current Chat context; and
  • pull-request tools that only read, reached through createMCPClient against GitHub's remote MCP server and bound to the channel's repository. This is the Planner's own tool set; machines, not the Planner, use the App's write permissions (see Authentication).

A comment note sent to Chopin (to: "planner" on comment:start or comment:reply; the server never looks for a mention in the text) starts or joins a turn about that one thread, under the sender's claimant session after the same write-authority recheck as Chat. That turn alone may call reply_comment { thread, text }: it refuses any thread other than the one the running turn was sent, and a resolved or full one, and persists the Planner's note before the room sees it. The turn's edit_plan writes are recorded as the thread's result. If the turn ends having said something in Chat but nothing in the thread, that last message is posted to the thread for it. Background jobs cannot call reply_comment.

A child document has no Chat. A comment sent to Chopin inside one is admitted by its root's queue and starts or joins a turn on the root's Chat, with the child as the turn's focus; the root's transcript notice names the child. That turn alone may reply_comment in that child thread, and its edit_child writes are the thread's result. The note is saved in the child before the turn is queued, and the reply is saved in the child before the child's room sees it. Admission, queueing the turn, and reply_comment each re-ask storage that the child still belongs to a live root in the same repository; admission refuses the note before saving it when it does not. The end-of-turn fallback reply is posted to the child room the note came from, which stays held until that turn ends, without asking again. Planner ownership is the root's.

Issue and general search MCP tools are refused because linked objects and free-form qualifiers can cross the selected repository boundary. Repository REST tools construct owner and repository coordinates on the server, bound response sizes and line ranges, reject path escape, and post-filter code search by GitHub repository node ID.

Foreground Planner writes on every harness and MCP document creation and updates share a server-owned visual admission check. New prose, lists and nested passages are assessed for whether a faithful visual is possible, helpful, and which native type fits. A selected diagram or comparison table must appear beside its passage before the write is accepted. A successful prose judgment permits prose; there is no diagram quota. This applies to blank drafts, long requests, and imported plans without requiring PLANNER_VISUALS.

Assessments run in bounded batches outside the room queue. Failed or malformed assessments refuse the write. Admission follows the shared assessment's probability thresholds and selected type, including an explicit no-suitable-type result. It does not add a confidence veto: confidence describes competition between types, not whether the document write is valid. Every selected visual is still required and validated. Refusals identify the passage and required representation and include syntax guidance for repair. Successful assessments are cached by passage and context while diagrams are repaired. Unchanged prose with its existing visual is preserved. A concurrent edit still invalidates the proposed revision. Background jobs retain their isolated contracts. The legacy assess_visual single-paragraph handoff remains available to sessions without the shared gate.

The isolated Planner does not see a user's local checkout, current branch, working tree, or uncommitted changes. A coding agent must compare the repository context returned by Chopin with its checkout before claiming work. The server validates the shape of creation provenance but does not resolve its branch and commit against GitHub or independently inspect the coding agent's checkout.

Under HARNESS=atomic the Planner is a full Atomic session, but the summary and research workers each still own one isolated in-process Atomic AgentSession. It is built with every shipped Atomic package disabled (workflows, subagents, MCP, web access, and Intercom). It has no Atomic coding tools, and its resource loader discovers no extensions, skills, prompt templates, themes, or context files. Its working and configuration directory is an empty private temporary directory, and its session, settings, and credentials stay in memory. A per-turn hook replaces the whole system prompt with the turn's instructions, so the model never receives Atomic's coding-agent preamble. The adapter fails the turn before any model request when the live session reports an extension, tool, context file, skill, prompt template, or system prompt beyond that set. It checks the tools offered to the model again before every model request.

Full Atomic Planner

Under HARNESS=atomic every Planner session is a full Atomic session, in local and hosted deployments alike, with no separate flag. The workflows it starts get shell and filesystem access as the server process's user, not a sandbox confined to a repository. Operators who do not want that should use copilot-sdk. Both HARNESS_AUTH=auto and ai-gateway work, under their usual bind rules.

The Planner's own turns offer only read-only tools beside Chopin's document and repository tools: read, find, search, and ast_grep for the working directory, Atomic's web research tools, ask_user_question, workflow, and intercom. It cannot edit files, run commands, or start subagents itself, so it never implements the plan; a workflow it starts keeps the tools its stages declare. Atomic's workflows, MCP, web access, and Intercom run alongside Chopin's tools. The normal Atomic agent directory (including ATOMIC_CODING_AGENT_DIR or the legacy PI_CODING_AGENT_DIR override) supplies extensions, skills, prompt templates, context files, and a read-only copy of its settings. A verified checkout's .atomic/settings.json is read the same way, as trusted project settings, so packages installed for that project load too; the document's own directory has none. Paths in HARNESS_EXTENSIONS load in every Planner session as if passed to Atomic's --extension flag, so a package listed there adds its extensions, skills, and workflows without being installed in the agent directory. Tools those extensions register load but are not offered to the Planner's own turns unless they are named above; workflow stages still load them. Chopin's compaction, summary, and cache overrides still apply on top. Chopin appends its Planner instructions to Atomic's assembled prompt. Session, settings, and model credentials remain in memory; Atomic's own enabled tools, extensions, MCP servers, and workflow journal can perform writes as the server process's user. Background summary and research workers still use the isolated sessions described above.

The working directory comes only from the checkout argument of invoke_planner. Chopin checks the path with Git and compares origin's owner/repository to the document's repository, case-insensitively. HTTPS, ssh://, and scp-style remotes are accepted. Remote host spellings are ignored because SSH aliases are common; this verifies repository coordinates, not the authenticity of a remote host. An unverified path refuses the invocation before anything is posted. A verified one is remembered in memory for that document until the process exits, and every later Planner session for the document, browser-started or MCP-started, re-verifies it before using it as its working directory.

Without a remembered checkout that still verifies, the session runs with the same tools in a directory Chopin keeps for that document alone under its per-user state directory ($XDG_STATE_HOME/chopin/planner/<document id>, defaulting to ~/.local/state; ~/Library/Application Support/Chopin/planner on macOS; %LOCALAPPDATA%\Chopin\planner on Windows), with mode 0700. It is never shared with another document, keeps what the Planner and its workflows write there across later sessions and server restarts, and is not placed in the shared temporary directory. A symlink or file at that path is refused. The Planner's instructions state which case applies: a verified checkout, or an empty directory with no repository files, in which case it reads the repository through Chopin's repository tools.

The Planner still does not implement the plan. When a member asks to implement it now, its instructions note that its intercom tool can reach other sessions on the same machine, such as one working in a checkout of the document's repository, and that it can pass the request to one of them and tell the member where the work continues. Chopin does not track or verify that handoff.

Chopin implements Atomic's HostInput, bound through extensionBindings.humanInput; it does not intercept tools. ask_user_question, extension dialogs, and workflow-stage input become ordinary shared Decisions:

  • Questionnaires retain question and option order, multi-selection, and the exact text Atomic supplied; answers return their Atomic question indices and answer kinds, including a selected option's preview.
  • Every card offers a written answer. It returns as Atomic's custom kind where Atomic's own dialog accepts typed text (single-select without previews), and otherwise as typed chat, which Atomic delivers to the model as the member's inline message (blank chat text is Atomic's plain request to talk first). Chopin never reports written text as a chosen option, and Atomic workflows treat chat as a request to keep discussing, not approval.
  • Confirm uses Yes/No; only choosing Yes approves. Select returns only a supplied choice. Input and editor use free-text cards; hints and initial editor text appear verbatim in the prompt. Explicitly submitted empty text is an answer, not cancellation.
  • Workflow cards show their question exactly as asked, with no run or stage ids; Chat's run card shows which run is waiting on Decisions. A request that names the block it concerns goes, as one adjacent batch, directly after that block and is linked to it (see Agent connection questions). One that names none, or a block that has changed since, is appended at the end of the document, not linked yet.
  • An abort withdraws still-open cards, removes their document nodes, and records cancellation by @chopin. Member cancellations retain any answers already given in that batch. Late submissions cannot approve withdrawn input.
  • A request has no time limit. It waits until members answer it or it is withdrawn: the Planner stops, its run is cancelled, the document is archived or deleted, or the asker's link closes. A document on Default machine keeps its Planner running while that Planner waits on a question or a live or paused workflow run, so nobody viewing the document does not withdraw it.
  • Cards an earlier Chopin closed unanswered after a fixed time stay in the document and in Decisions, among the resolved cards, marked status="expired"; each reads "Nobody answered this question, so the Planner used its best judgement for this decision." Nobody can answer an expired card.

Host input is not held to the Planner ask tool's per-field limits on question and option counts or header, question, label, description, and answer lengths. Only Chopin's aggregate bounds apply. A request whose cards would take the document past its 256 KiB source limit fails with that message before any card appears. A written answer is limited by one shared-draft edit (64 KiB) and the whole draft (256 KiB). Text is never silently truncated. Atomic keeps durable workflow approvals pending on withdrawal; Chopin does not automatically resume workflows or replay interrupted turns after a restart. A new Atomic session must explicitly resume a saved run.

A coding agent can hand an instruction to the Planner with invoke_planner under any harness. The instruction is posted as the MCP caller's own message and runs under the document's Planner ownership rules (see Ownership). Harnesses other than atomic ignore its checkout.

Remote Planner

Under HARNESS=remote the Planner's model runs in a client attached to the document over the agent connection, usually an agent on a member's own machine. The server keeps the document, Decisions, room writes, and anchors; the client keeps its code, model credentials, and its own tools. The adapter (apps/server/src/harness/remote) forwards each Planner session as start and each turn as turn, and re-emits the parts the client streams back.

The agent connection is a WebSocket at /api/v1/agent, on the application's own origin, with the chopin.agent.v1 subprotocol. Agent connection is its complete wire format, written for clients that cannot import Chopin's packages. Its messages are declared and validated by @chopin/agent-link (packages/agent-link), whose minimal client in @chopin/agent-link/client uses only that wire format at runtime. Every message is a JSON text frame of at most 2 MiB, checked against a strict schema; a binary, malformed, out-of-order, or oversized message ends the connection (close codes 1008 or 1009) after an error message naming the problem.

Server to client Client to server
attached or refused (answers attach) attach (version, document id or URL, checkout identity)
start (session, planner or isolated mode) started or start-failed
turn (prompt, instructions, host tool specs) tools (the client's own tools for this turn)
abort part (text, reasoning, tool input, own tool call/result, finish-step, finish, error)
host-tool-result host-tool-call
input-result (answered, expired, cancelled, failed) input-request, input-cancel
stop, resume (Stop and Resume Planner) control-result, runs (run cards for Chat)
destroy turn-end, detach

Attaching. The upgrade request offers the chopin.agent.v1 subprotocol, or is refused with HTTP 426. It carries Authorization: Bearer <GitHub token>, checked exactly as hosted MCP checks it: the token must resolve through instance admission, a bad or missing token gets HTTP 401, a refused account 403, a foreign Origin 403, and GitHub outages 503, all before anything is upgraded. The first message must be attach with protocol version 1. Its document is resolved like an MCP document locator, and the bearer's own account must have push or administration access to the document's repository. An unsupported version, a repository it cannot write, or a missing or archived document is refused with a refused message and close code 4426, 4403, or 4404. Neither attaching nor a refusal posts anything to the document. The account and repository access are checked again before every session and every turn the connection starts, including turns of a session kept for its runs, and every 5 minutes while the connection stays attached; losing either fails that session or turn and closes the connection with access-revoked.

A paired machine can attach instead with a document-scoped Planner credential from POST /api/connector/planner, which chopin connect --planner hands to a Planner backend command it starts per document (Planner backend commands). Its access is checked with the machine's own GitHub authorization, and it owns the document through a delegated login of the machine's owner that no browser holds (apps/server/src/harness/remote/machine-planners.ts), so no browser sign-in is needed and signing out does not release it. Revoking the machine, or GitHub refusing its authorization, ends that login and closes its connections with access-revoked.

Ownership. Attaching makes the account the document's Planner owner through the same generation-guarded storage claim as any other owner (apps/server/src/harness/remote/attachments.ts). The owner record names the account's live browser login, not the connection, because Chopin's repository tools run with that login's GitHub App token and never with the connection's bearer; an account with no live sign-in since the server started is refused with sign-in-required (4401). The login must pass the same eligibility as every Planner owner: instance admission and push or administration access. This is stricter than the design's "can read the repository", because an attached client steers what the Planner writes and runs repository tools as the owner. An owner left without an attachment, which only a race or an operator reset can produce, is cleared under its generation and the document is claimed again.

One client holds a document's Planner. While it is attached, every other attach for that document is refused with planner-attached (4409) and a message naming its GitHub login. A connection that detaches or drops keeps the ownership for PLANNER_ATTACH_GRACE_MS (2 minutes by default). A connection that answers none of the server's protocol pings for AGENT_CONNECTION_TIMEOUT_MS (2 minutes by default) is closed as dropped, so a client whose network silently went away does not keep the document attached. Within the grace period the same account can attach again and keeps the same ownership generation, and anyone else is refused. When it ends, the ownership is cleared under its generation, so a newer owner is never released by an old timer. An attach, including a grace reattach, binds only if no sign-out, expiry, or Planner reset of its ownership landed while it was checking, including the eligibility checks of the sign-in it is about to claim with; otherwise it is refused like an attached connection would be closed. Attachments live in the server process; startup already clears every owner reference, which releases every attachment.

Who is attached. Each room socket hears session:planner after session:hello, and the whole room hears it again whenever an attachment changes: an attach, a detach into the grace period with the time it ends, the grace period ending, a sign-out, a Planner reset, or the server closing a connection whose ownership was released. It names only the holder's GitHub login and state, never a machine, checkout, or token. Chat shows it as one line under the composer. Other harnesses never send it, and Chat then shows nothing.

Unattached documents. Members, MCP callers, and research requests never claim ownership under HARNESS=remote. An @chopin message or invoke_planner call for a document with no attached client, including one within its grace period, is refused with planner-not-attached before anything is posted or queued. Before accepting either, the server rechecks an attached connection as its periodic recheck would: if the owner's browser sign-in has ended or storage no longer assigns that owner, it closes the connection, releases the ownership, and refuses the request the same way. The refusal names the document URL and says to connect a Planner client to it over the agent connection from a session in a checkout of the repository, which attaches that session as the Planner; the browser shows that text on the @chopin request. Room messages are unaffected. Once a client is attached, both arrive as turns on its connection and run under its ownership whoever sent them.

Turns and tools. Chopin's host tools (document, repository, and job tools) still run in the server against the room, under the document's Planner owner and the permission checks below. The client calls them with host-tool-call and receives host-tool-result. Its own tools run on its machine: it must declare them with tools before calling any, may not reuse a host tool's name, and reports them as provider-executed tool-call and tool-result parts. An undeclared tool fails the turn as a tool boundary failure. Background job sessions start in isolated mode, where the client may offer no tools of its own and ask nothing. A turn ends with turn-end. An abort waits briefly for the client's turn-end, then ends the turn anyway. A turn that ends while one of its host tools still runs, such as an ask waiting on Decisions, stops that tool and withdraws the cards it opened. A connection that closes mid-turn ends the turn with an error rather than as a stop, its open questions are withdrawn, and runs it reported are shown stopped; nothing waits on a client that has gone. A session the client refuses or does not start in time is destroyed, and any question it asked while starting is withdrawn.

Questions. An input-request carries one Atomic HostInput call (questionnaire, confirm, select, input, or editor) and becomes Decisions through the same path as the full Atomic Planner: the same verbatim text, the same 30-minute expiry, cancellation that withdraws open cards, and answers to a workflow question held while the client reports that run's root as paused. input-result returns what Atomic would have received in process, with a status that tells an expiry from a cancellation.

Runs. The client reports its workflow runs with runs; Chat shows those cards and keeps the session after its turn while any run is live or paused, as for an Atomic Planner. Stop Planner and Resume Planner send stop and resume, optionally for one run, and wait for control-result. Unlike an Atomic Planner's, a reported run that ends blocked or failed between turns gets only its Chat notice: the reports are the client's word, so they never start a Planner turn that no writer asked for. A client that asks for the run-stage-deltas feature sends only new and changed stages, so a run's stages have no count limit; Chopin merges them into the run's stored stages as it does an Atomic Planner's (see Agent connection).

Trust boundary. The server never holds the client's model credentials and gains no shell or filesystem access from this harness. The client, in turn, receives everything a Planner turn sees: the conversation, the document, and the results of repository tools that run with the document Planner owner's GitHub App token, limited to the document's repository, which its own account can already read. The attached client decides what the Planner writes to the document during turns writers start, which is why attaching requires the push or administration access every Planner owner needs. Summary and research workers have no model in the server, so HARNESS=remote requires BACKGROUND_JOBS=off.

Permission checks

Before each Chopin host tool or its repository-bound GitHub MCP tool executes, callbacks recheck:

  • current instance admission;
  • the owner process session and its user;
  • ownership generation;
  • credential revision and expiry;
  • repository push or administration access; and
  • the App installation's repository access.

Permission is decided before execution. A refusal therefore produces no normal tool start or completion event; the Chat service renders permission denials explicitly so the boundary remains visible. These checks do not mediate the atomic Planner's Atomic coding tools, operator extensions, or operator-configured MCP servers; those act with the server process's own authority.

A harness session is bound to one credential revision. Before an eight-hour GitHub App token refresh, Chopin aborts and discards every Planner session using that revision. The next turn creates a fresh session with the new token.

Chat context

Automatic participation is enabled with the Planner and lets Chopin reply promptly in solo conversation and yield during human exchanges. Group turns use a bounded turn-taking judgment and a quiet window. Natural-language requests can invite Chopin without @. It uses this same Planner lifecycle, with a shared durable pause control; explicit @chopin requests remain immediate.

The channel chat transcript is durable, but not every historical message is sent to every turn.

  • Messages since the last turn are retained as immediate backscroll, capped at 40 entries and normally 8,000 characters. One message is retained intact even when it alone exceeds that character budget.
  • A recreated Copilot session receives at most the last 100 transcript entries and 50,000 characters. Reserved Planner transcript-summary and cursor fields exist in storage, but the current runtime does not advance them. Generated descriptions and legacy summaries under durable document-summary@1 are separate and are not bootstrap context.
  • The Planner reads the current document through the plan-named read_plan tool instead of receiving a stale embedded copy. That result, and every structured edit_plan result (success, or a locked, stale, or invalid refusal), carry document: { id, url }: the room's own document id and the canonical URL MCP read_document returns for it, nested under the parent for a child document. Argument and storage errors return plain error text without it. Code running inside the session, such as an Atomic extension, can use either to address that document over MCP.

After an ask questionnaire has a saved answer, its result may include current_plan with the same document, revision, source, block outline, comments, and question records as read_plan. The Planner can edit from that revision and still uses anchor_plan to relate the answer to prose. The optional snapshot has a 50 ms budget for document lookup, owner revalidation, and room access; if it cannot finish, the saved answer returns without waiting and the Planner can call read_plan. When none of a batch's questions were answered, or lookup or revalidation fails, the result contains only the outcomes.

Chat references are typed server-side resources, not URLs the model can follow. # selects another ordinary document in the current repository. References persist with their message, but read_reference accepts only the bounded set retained by the active Planner session. A document reference reads latest canonical source and reports whether it changed since selection. New messages no longer offer % research references; persisted references from the removed Research Workspace interface still return a bounded compatibility projection. Both forms are untrusted evidence, and neither changes the room-fixed target of read_plan or editing tools.

Messages from people retain their GitHub handles so disagreement is not merged into one anonymous user voice.

Session lifecycle

A harness session is disposable. A process restart, credential rotation, logout, or ownership reset discards it. A later turn bootstraps from the bounded transcript and reads the current document.

The one exception is an atomic Planner session that still owns Atomic workflow runs when its turn ends. A workflow the Planner starts outlives the turn that launched it, and its runs belong to that session, so Chopin keeps the session and its owner binding, and keeps the document loaded, until every run has finished. The next turn reuses that session, so the Planner can still see and steer its runs over Intercom; people steer through Chat and Decisions, never a stage directly. Stop Planner also pauses the session's live runs, resumably, through Atomic's session run control, and Resume Planner resumes the runs it paused. A run waiting on a question pauses too: Atomic withdraws the question from Decisions while the run is paused and presents it again on Resume, and an answer that arrives during the pause reaches the run only after Resume, so a paused run never advances. A run that ends blocked or failed while the Planner is between turns starts a Planner turn about it, on the kept session, so the Planner reports what happened and what the team needs to do; Chat records the run's end as that turn's reason, and the session is let go after the turn. The adapter stops any turn Atomic starts on its own, such as for its lifecycle notices, because Chopin would neither show nor record it. Chat shows the session's runs as one stack, one row per run: its name, status (running, waiting on Decisions, paused, or ended), and elapsed time. Several runs can be live at once. Runs waiting on Decisions come first, then running, paused, and ended runs, newest first within each, and more than three rows fold behind "more" without ever hiding a waiting run. Only the first waiting run, or else the newest live one, shows its stages; any row opens on click to show its six most recent stages with their status and duration, and a waiting row links to its Decisions. Chopin stores every stage of a run, with no limit on how many, in workflow_run_stages (see Storage); the card itself carries only the latest 12, with an unfinished stage under an ended ancestor already shown as stopped. Its "+N earlier" button unfolds every stage in a list that scrolls inside the card, loading the stored ones 200 at a time from GET /api/channels/<id>/runs/<run id>/stages?offset=&limit= (for anyone who can read the document; at most 500 a page) only when someone unfolds it, and says how many earlier stages nobody reported; "Show fewer" folds it back and forgets them. Each live row can be paused, and each paused row resumed, on its own; Chat records who did. The stack and every stage are stored with the document, so a reload or restart keeps them, including for finished runs; a run that was live when the server stopped comes back stopped. A stored run's stages go when its card does. Ended rows stay until the next workflow starts in the document. When a run ends, Chat also records a line saying how it ended and how long it took. The session is let go when its runs finish, when its owner binding ends, or when the document closes; run state is durable in Atomic's workflow journal, so runs interrupted that way can be resumed from a later session. Chopin keeps that journal at $XDG_STATE_HOME/chopin/atomic/workflows.sqlite under the same per-user state directory as the documents' own directories, because a run resumes only with the journal and working directory it started in. Each Planner session is given that file through Atomic's createAgentSession({ journal }), so the server's process environment is left unchanged. An operator's non-empty ATOMIC_WORKFLOW_JOURNAL_PATH names another file instead; Atomic's workflows.journal setting is not used for Planner sessions. A Build is not a workflow run and has no card in Chat: its tasks, their state and pull requests show under Build, whichever machine runs it. The Planner's instructions say so, so it does not point people to Chat for a Build.

An interrupted turn is visible and is never replayed automatically because it may already have made durable document or question changes. HarnessAgent.stream() returns an AI SDK stream that chat/service.ts consumes part by part; the Chat handler remains active until that stream finishes.

The runtime starts lazily on the first Planner turn or model-backed worker attempt. AGENT=off prevents those turns and disables the background-job runner. For the copilot-sdk adapter this also avoids starting Copilot CLI. AGENT=off does not disable /mcp, and the prototype UI may still contain Planner-oriented explanatory copy.

Background jobs

Background jobs are durable Chopin requests, not child Planner turns. Registered definitions control their input and artifact codecs, enqueue origins, credential mode, timeout, failure budget, declared progress, and artifact settlement. Every model-backed stage uses a fresh disposable harness session with a structured output schema. Job output is not automatically injected into Chat or recreated Planner context, although the Planner may explicitly read an artifact in a later turn.

An inline /research submission persists the exact brief and starts work immediately. During an explicit member turn, the Planner can call the plan-named create_research_workspace tool with that same exact brief; it may not refine, broaden, or replace it. The public worker's structured output binds a host web_search tool reached through GitHub MCP; the public worker receives only the brief. Parent-document context goes to a separate no-web worker after evidence completes. A validated initial report publishes as an ordinary child document; it never edits the parent's collaborative prose automatically.

Jobs with credential: "active-planner" use the channel's process-local Planner owner and entitlement, while fenced claims store no token. Full definition registration, lifecycle, isolation, disclosure, retry, configuration, and testing guidance is in Background jobs and workers.

Generated document descriptions use this active owner in a private disposable worker. The durable definition remains document-summary@1; marked V1 requests produce one-line type, purpose, and subject metadata, while markerless legacy V1 artifacts remain readable only as summaries. The generated value is untrusted model output and is neither the structured MCP creation brief nor the reserved Planner transcript summary.

Open, edit, restore, and MCP creation paths schedule descriptions lazily. They do not establish Planner ownership, and there is no unattended scan of every document. Without an active owner, model-backed work cannot run; the last completed description, if any, remains visible while work is pending or failed.

Diagrams and wireframes

The prompt teaches two validated fence languages. seecode holds a JSON spec for any registered SeeCode diagram or numerical chart. The ordinary prompt lists the full native catalog, including bar, line, scatter, radar, polar, waterfall, treemap, heatmap, and Sankey. Numerical aliases have variant-specific purpose and data requirements; the guide includes labelled synthetic bar, Marimekko and dumbbell examples that are checked against the renderer. This capability and the shared visual admission check do not require PLANNER_VISUALS. Ordinary and Atomic Planners prefer native seecode whenever a registered type can represent the required information. Mermaid is a fallback only for a representation the native catalogue cannot provide or an explicit user request; it supports numerical charts such as pie and bar/line charts. Jev-routed diagrams must keep the selected native type and SeeCode fence, with no Mermaid substitution. wireframe holds an indented outline of interface parts for layouts and mockups; the prompt asks for low fidelity (short labels on key areas, lines, image and grid with repeat for content, w= and flush for proportions and app chrome) and forbids box-drawing or ASCII art in text fences. edit_plan and MCP update_document refuse a batch that introduces or changes an invalid fence of either language; a wireframe refusal names the failing lines, counted inside the fence. An unchanged malformed fence, such as one a person left mid-edit, is carried through unrelated edits and moves rather than blocking them.

Implementation graph status

The Planner can draft and revise a graph with read_implementation_graph and edit_implementation_graph. It cannot approve, lock, or start implementation. Those are explicitly human and coding-agent responsibilities.

The graph tools remain technically available in any channel, but the child browser surface exposes no implementation or task destination. Child implementation is therefore outside the supported product workflow. The supported MCP handoff can read only graphs on MCP-created documents, and no current production interface lets a person approve the draft. See Experimental implementation lifecycle.

Main implementation points

  • Harness selection, adapter factory map, and startup checks: apps/server/src/harness/harnesses.ts
  • Planner, summary, and research HarnessAgent configurations: apps/server/src/harness/agents.ts
  • Planner session lifecycle: apps/server/src/harness/session.ts
  • Host-executed GitHub MCP tools: apps/server/src/harness/github-tools.ts
  • Copilot SDK adapter: apps/server/src/harness/copilot-sdk/adapter.ts
  • Atomic SDK adapter: apps/server/src/harness/atomic/adapter.ts
  • Planner prompt and document tools: apps/server/src/agent/planner.ts and apps/server/src/agent/tools.ts
  • Repository-fixed tools: apps/server/src/agent/repository.ts
  • Ownership and Chat lifecycle: apps/server/src/chat/service.ts
  • GitHub App session lifecycle: apps/server/src/auth/session.ts
  • Background job registry and runner: apps/server/src/jobs/registry.ts and apps/server/src/jobs/runner.ts