From a36a45c5d8e51335d532094d1a786660e21ab0a9 Mon Sep 17 00:00:00 2001 From: Aleksey Bykhun Date: Mon, 15 Jun 2026 11:19:29 -0700 Subject: [PATCH] feat(connector): resume a prior claude-code session into the mesh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an optional `resume` to the connector LaunchOpts so a spawn/start can reattach a prior claude-code conversation instead of always starting fresh. - core: `LaunchOpts.resume?` (connector-specific session id) - claude-code connector: when set, push `--resume --fork-session` (fork so the original session id isn't hijacked — we adopt its context, not its identity); all existing args (dev-channels, mcp config, persona, model) are preserved - manager opStart threads `args.resume` into buildLaunch - `cotal start` and `cotal spawn` gain a `--resume ` flag - codex/opencode ignore `resume` (claude-specific) Closes #23 Co-Authored-By: Claude Opus 4.8 (1M context) --- extensions/connector-claude-code/src/extension.ts | 5 +++++ implementations/cli/src/commands/spawn.ts | 4 +++- implementations/manager/src/commands.ts | 4 +++- implementations/manager/src/manager.ts | 1 + packages/core/src/connector.ts | 5 +++++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/extensions/connector-claude-code/src/extension.ts b/extensions/connector-claude-code/src/extension.ts index fb45489e..ae9d7bb6 100644 --- a/extensions/connector-claude-code/src/extension.ts +++ b/extensions/connector-claude-code/src/extension.ts @@ -35,6 +35,11 @@ export const claudeConnector: Connector = { const args = ["--dangerously-load-development-channels", CHANNEL_REF]; + // Resume a prior conversation into the mesh. `--fork-session` makes claude mint a + // fresh session id from the resumed history, so the original session this id points + // at keeps running untouched (we adopt its context, not its identity). + if (opts.resume) args.push("--resume", opts.resume, "--fork-session"); + // An agent file carries identity (read in-session via COTAL_AGENT_FILE) plus // persona + model, which can only be applied to a `claude` session at launch. if (opts.configPath) { diff --git a/implementations/cli/src/commands/spawn.ts b/implementations/cli/src/commands/spawn.ts index 06a70e22..fbcd5b93 100644 --- a/implementations/cli/src/commands/spawn.ts +++ b/implementations/cli/src/commands/spawn.ts @@ -41,6 +41,7 @@ export async function spawn(argv: string[]): Promise { server: { type: "string" }, agent: { type: "string" }, role: { type: "string" }, + resume: { type: "string" }, }, }); @@ -49,7 +50,7 @@ export async function spawn(argv: string[]): Promise { const ref = values.config ?? positionals[0] ?? values.name; if (!ref) { console.error( - "usage: cotal spawn | --config | --name [--agent ] [--role ] [--space ] [--server ]", + "usage: cotal spawn | --config | --name [--agent ] [--role ] [--resume ] [--space ] [--server ]", ); process.exit(1); } @@ -111,6 +112,7 @@ export async function spawn(argv: string[]): Promise { creds: credsPath, servers: server, configPath: path, + resume: values.resume, }); console.error( diff --git a/implementations/manager/src/commands.ts b/implementations/manager/src/commands.ts index bccf4c73..574ad926 100644 --- a/implementations/manager/src/commands.ts +++ b/implementations/manager/src/commands.ts @@ -30,6 +30,7 @@ function parse(argv: string[]): Values { agent: { type: "string" }, config: { type: "string" }, creds: { type: "string" }, + resume: { type: "string" }, "console-port": { type: "string" }, drive: { type: "boolean" }, }, @@ -98,6 +99,7 @@ async function start(argv: string[]): Promise { role: v.role, agent: v.agent, config: v.config, + resume: v.resume, }, v.creds); failIfNotOk(reply); const d = reply.data as { name: string; role?: string; agent: string; mode: string }; @@ -333,7 +335,7 @@ const managerCommands: Command[] = [ name: "start", group: "Control plane", summary: - "ask the manager to spawn an agent — --name [--role ] [--agent ] [--config ] (auto-discovers .cotal/agents/.md)", + "ask the manager to spawn an agent — --name [--role ] [--agent ] [--config ] [--resume ] (auto-discovers .cotal/agents/.md)", run: start, }, { diff --git a/implementations/manager/src/manager.ts b/implementations/manager/src/manager.ts index 378513d6..5f8b8d62 100644 --- a/implementations/manager/src/manager.ts +++ b/implementations/manager/src/manager.ts @@ -210,6 +210,7 @@ export class Manager { creds: credsPath, servers: this.servers, configPath, + resume: args.resume ? String(args.resume) : undefined, }); handle = this.runtime.spawn(name, spec, this.workspaceRoot); } catch (e) { diff --git a/packages/core/src/connector.ts b/packages/core/src/connector.ts index bddfbe9c..b117bac4 100644 --- a/packages/core/src/connector.ts +++ b/packages/core/src/connector.ts @@ -17,6 +17,11 @@ export interface LaunchOpts { * passes it through (`COTAL_AGENT_FILE`) so the joined session reads its own * card from it, and applies the file's persona/model at launch. */ configPath?: string; + /** Resume a prior session of this agent type instead of starting fresh — the + * connector-specific session id to reattach (claude-code: a `--resume` id, + * forked so the original session isn't hijacked). Connectors that can't resume + * ignore it. */ + resume?: string; } /** A recipe for starting an agent as a mesh node — command, args, and extra env. */