Skip to content

trace naming: revisit inference:content_block phase discriminator before publish #36

@Anarchid

Description

@Anarchid

Context

#35 introduces a new `inference:content_block` trace event with a sub-discriminator field. The field was originally named `event` (mirroring `StreamEvent.event` → `BlockEvent.event` from the membrane), which produced the unfortunate read `event.event.event` in `driveStream` and was flagged in code review. It was renamed to `phase` in 2d19aec.

That fixes the immediate read but raises a longer-lived design question worth settling before this ships to npm and external consumers form against the chosen name.

The collision

`TraceEvent` already has two precedents for representing lifecycle/phase information:

Precedent A — separate types per phase (the dominant pattern):
```ts
| { type: 'tool:started'; ... }
| { type: 'tool:completed'; ... }
| { type: 'tool:failed'; ... }
| { type: 'inference:started'; ... }
| { type: 'inference:completed'; ... }
```

Precedent B — sub-discriminator field named `event`:
```ts
| { type: 'branches:changed'; event: 'switched' | 'created' | 'deleted'; ... }
```
Existing consumers (e.g. connectome-host `src/tui.ts:1386`) already write `event.event === 'switched'`. So the `event` name had at least one precedent — `phase` matches no prior choice.

The current PR chose Precedent B with a renamed discriminator (`phase`). Neither precedent currently feels load-bearing enough to forbid revisiting.

Three options

Option 1 (current): keep `phase`
```ts
{ type: 'inference:content_block'; phase: 'block_start' | 'block_complete'; blockType: ...; blockIndex: ... }
// Consumer: case 'inference:content_block': switch on event.phase
```
Cleanest read at the call site; diverges from `branches:changed`'s naming.

Option 2: revert to `event`
```ts
{ type: 'inference:content_block'; event: 'block_start' | 'block_complete'; ... }
// Consumer: case 'inference:content_block': switch on event.event
```
Consistent with `branches:changed`; reads as `event.event === 'block_start'` which is the exact stutter the rename was meant to avoid.

Option 3: split into two trace types
```ts
| { type: 'inference:content_block_started'; blockType: ...; blockIndex: ... }
| { type: 'inference:content_block_completed'; blockType: ...; blockIndex: ... }
// Consumer: case 'inference:content_block_started': / case 'inference:content_block_completed':
```
Matches the `tool:started` / `tool:completed` / `inference:started` / `inference:completed` pattern, which is the stronger precedent (lifecycle pairs vs the flat verb-enum of `branches:changed`). Eliminates the nested-discriminator awkwardness structurally — `driveStream` writes `type: 'inference:content_block_started'` directly, no sub-field. Slight cost: two case arms on the consumer side.

Constraint

This is only a real one-shot decision while the trace type is unreleased. Once `@animalabs/agent-framework` publishes the version containing `inference:content_block`, downstream libraries can subscribe, and a later rename becomes a breaking change. The longer this sits in `main`, the more it ossifies.

Recommendation

Option 3 if we're willing to touch it once more before publish. It's the closest fit to the dominant `tool:` / `inference:` lifecycle convention, and it removes a category of confusion ("is the sub-discriminator named `phase`, `event`, `kind`?") rather than just choosing a name within it.

Mechanical impact:

  • AF `src/types/trace.ts`: replace the single content_block variant with two.
  • AF `src/framework.ts`: `driveStream`'s 'block' case emits one of two trace events based on `phase`. (Or destructure `phase` and switch — three lines either way.)
  • connectome-host `src/tui.ts`: one case arm becomes two; the body is identical to the current `if (event.phase === 'block_start')` branch.

No test rewrites needed beyond updating the assertion in `test/framework.test.ts` to check both event types separately.

Decision needed by

Before the next `@animalabs/agent-framework` npm publish that includes the contents of #35.

🤖 Filed via Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions