Skip to main content
Every finish event carries an opaque session_id — the provider-managed conversation handle. Thread it back into the next call’s sessionId to continue.
Turn 1  ─► run({...})                       ─► finish { session_id: 'abc' }
Turn 2  ─► run({..., sessionId: 'abc'})     ─► finish { session_id: 'abc' }
Turn 3  ─► run({..., sessionId: 'abc'})     ─► finish { session_id: 'abc' }
Copass attaches the same Context Window to every turn in the session. Window-aware retrieval uses it to avoid re-delivering context the agent already has.

Full example

let sessionId: string | undefined;

async function turn(message: string) {
  for await (const event of router.run({
    provider: 'anthropic',
    model: 'claude-opus-4-7',
    system: 'You are a helpful agent.',
    message,
    endUserId: 'u-123',
    sessionId,
  })) {
    if (event.type === 'text') process.stdout.write(event.text);
    if (event.type === 'finish') sessionId = event.session_id ?? undefined;
  }
}

await turn('What are my open GitHub issues?');
await turn('Which ones are blocked?');         // knows about the prior answer
await turn('Draft a comment on the top one.'); // knows about "top one"

Swapping providers mid-session

// Turn 1 on Anthropic
let sessionId: string | undefined;
for await (const e of router.run({
  provider: 'anthropic', model: 'claude-opus-4-7',
  system, message: 'Summarize my issues', endUserId: 'u-123',
})) {
  if (e.type === 'finish') sessionId = e.session_id ?? undefined;
}

// Turn 2 on Google. Same window, same tools, same user.
for await (const e of router.run({
  provider: 'google', model: 'gemini-3.1-pro',
  reasoningEngineId, system, message: 'Which are blocked?',
  endUserId: 'u-123',
  // sessionId here is optional — see "When providers change" below
})) { /* … */ }

When providers change

session_id is provider-managed. Anthropic’s session IDs are not valid Google session IDs and vice versa. When you cross providers:
  • Memory of prior turns comes from the Copass Context Window — always available, regardless of provider.
  • Provider-native conversation state (prompt caches, model-side scratchpads) resets at the crossing point. That’s a feature: you’re explicitly changing runtimes.
In practice: pass sessionId when continuing on the same provider; omit or reset it when switching. The agent will still have the full memory of what’s been said because the Context Window lives in the sandbox. See Portable Context for the underlying reason this works.

The endUserId parameter

endUserId is how Copass ties a session to a human. Required on every run() call. Opaque, URL-safe, ≤128 chars, stable per human. See Multi-tenancy for picking the right value.

Cleaning up

Sessions don’t need explicit teardown — they’re ephemeral data sources that expire. If you want to proactively clear one:
await router.client.dataSources.archive(sandboxId, contextWindowSourceId);
For most agents, let them expire.

Next steps