Skip to content

feat: wire sessionId alongside instanceId on the trace wire (accept-only) - #347

Closed
EdwardIrby wants to merge 1 commit into
mainfrom
agent/session-id-wire
Closed

EdwardIrby wants to merge 1 commit into
mainfrom
agent/session-id-wire

Conversation

@EdwardIrby

Copy link
Copy Markdown
Member

Context

The engine self-mints instanceId = ueid('bp_') and stamps it on every trace via TraceBase. An ACP ingress is landing later; the ACP host owns session identity policy (mints on session/new, passes a client's old id on session/load), so the engine must ACCEPT a host-supplied session id — it never mints one and never returns ids. Decision: no rename of instanceId; the two ids are separate axes and both live on the wire.

Summary

  • TraceBase (src/behavioral/behavioral.types.ts) gains sessionId: string alongside instanceId, with doc comments describing the two-axis trace wire. A new exported TraceBaseSchema is the one home for the trace wire's common shape; per-kind trace validators derive from it (src/behavioral/tests/schemas.spec.ts) instead of hand-mirroring.
  • behavioral() (src/behavioral/behavioral.ts) gains an optional { sessionId?: string } options param; sessionId = options?.sessionId ?? instanceId is computed once at factory time and stamped on every sendTrace call (step, pending_bids, frontier, deadlock, idle, thread_added, add_thread_error, trigger_error, transform, transform_error, selection, interrupt). The frozen return object is unchanged — no ids are returned.
  • resumePendingThreadsForSelectedEvent (src/behavioral/behavioral.utils.ts) takes and stamps sessionId on interrupt traces.
  • Frontier faculty (src/faculties/frontier/faculty.ts) — same treatment, no schema drift with TraceBase: replay/explore/verify inputs accept optional sessionId (declared in all three AJV input schemas, nullable), defaulting to the instanceId; replayToFrontierRaw/exploreFrontiersRaw thread it through, and every synthetic trace they build (frontier, selection, deadlock) carries both ids. resumePendingThreadsForSelectedEvent receives it for interrupt traces.
  • src/cli/serve.ts / src/cli/trace-consumer.ts are pass-through only — no production code constructs Trace objects by hand (verified; redactTrace clones). Only spec fixtures gained the field (trace-consumer.spec.ts, serve.spec.ts).
  • skills/behavioral/references/behavioral.md factory signature corrected to behavioral({ sessionId?: string }).

Changed Files

  • src/behavioral/behavioral.types.ts — TraceBase.sessionId + TraceBaseSchema
  • src/behavioral/behavioral.ts — options param, factory-time default, stamping
  • src/behavioral/behavioral.utils.ts — interrupt trace stamping
  • src/faculties/frontier/faculty.ts — input types/schemas + synthetic trace stamping
  • src/behavioral/tests/session-id.spec.ts — new (both branches: host-supplied + default)
  • src/behavioral/tests/schemas.spec.ts — validator derived from TraceBaseSchema, rejects missing sessionId
  • src/cli/tests/trace-consumer.spec.ts, src/cli/tests/serve.spec.ts — construction-site fixtures
  • src/faculties/frontier/tests/faculty.spec.ts — boundary test for sessionId stamping + defaulting
  • skills/behavioral/references/behavioral.md — doc sync

Known Failures / Drift

None. bun --bun tsc --noEmit clean; behavioral (152), frontier faculty (40), and cli serve/trace-consumer suites pass (211 specs total).

Review Notes / Residual Risks

  • The engine never mints or returns session ids; a later ACP ingress passes the host's id at factory time (behavioral({ sessionId })) or on session/load by constructing a fresh program with the client's id.
  • TraceBaseSchema is a plain-const schema (not JSONSchemaType<T>-cast) because it describes the shared trace prefix, not a complete wire event; per-kind validators extend it with the discriminating kind/step constraints, as schemas.spec.ts demonstrates.
  • No host wiring in b-program.ts/serve.ts yet — deliberately pass-through only per the task scope.

The engine self-mints `instanceId = ueid('bp_')` and stamps it on every
trace. With an ACP ingress landing later, the host owns session identity
policy (mints on `session/new`, passes a client's old id on `session/load`),
so the engine must ACCEPT a host-supplied session id — it never mints one
and never returns ids. No rename: the two ids are separate axes and both
live on the wire.

- `TraceBase` gains `sessionId: string` alongside `instanceId`; a new
  exported `TraceBaseSchema` is the one home for the trace wire's common
  shape, so per-kind validators derive from it instead of hand-mirroring.
- `behavioral()` gains `{ sessionId?: string }`; the frozen return object
  is unchanged. Session id is computed once at factory time
  (`options?.sessionId ?? instanceId`) and stamped on every trace next to
  `instanceId`; `resumePendingThreadsForSelectedEvent` stamps it on
  interrupt traces too.
- The frontier faculty speaks the same wire with no drift: replay/explore/
  verify inputs accept `sessionId` (AJV `nullable`, defaulting to the
  `instanceId`), and every synthetic trace it builds (frontier, selection,
  deadlock) carries both ids.
- `serve.ts`/`trace-consumer.ts` are pass-through only — no construction
  sites existed in production code; only spec fixtures gained the field.
- `skills/behavioral/references/behavioral.md` factory signature updated.

Validation: `bun --bun tsc --noEmit` plus the behavioral, frontier
faculty, and cli serve/trace-consumer suites (211 specs).
export const behavioral = (options?: { sessionId?: string }) => {
const instanceId = ueid('bp_')
/** @internal Host session identity — accepted at factory time, never minted. */
const sessionId = options?.sessionId ?? instanceId
@EdwardIrby EdwardIrby closed this Sep 24, 2026
@EdwardIrby
EdwardIrby deleted the agent/session-id-wire branch September 24, 2026 05:08
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.

2 participants