Events and projections
Interaction keeps evidence, agent lifecycle, and presentation content distinct. The InteractionEvent wrapper gives them one ordered reduction surface without pretending they are the same event type.
import {
createInteractionProjection,
interactionContentEvent,
reduceInteractionProjection,
registerInteractionContentEvent,
} from "@geekist/llm-core/interaction";
import {
newCoreId,
type ConversationId,
type EventId,
type RunId,
} from "@geekist/llm-core/contracts";
const conversationId = newCoreId<ConversationId>("018f0f4e-8c5b-7a91-8c3b-123456789c01");
const source = registerInteractionContentEvent({
eventId: newCoreId<EventId>("018f0f4e-8c5b-7a91-8c3b-123456789c02"),
kind: "interaction.message.started",
occurredAt: "2026-07-30T00:00:00.000Z",
sequence: 0,
runId: newCoreId<RunId>("018f0f4e-8c5b-7a91-8c3b-123456789c03"),
facts: { messageId: "message-1" },
redaction: { kind: "not-required" },
});
const event = interactionContentEvent(conversationId, source);
const projection = reduceInteractionProjection(createInteractionProjection(conversationId), event);
console.log(projection.events[0]?.kind);Canonical input families
| Wrapper kind | Canonical source | Typical projection |
|---|---|---|
agent-run | AgentEvent | Run status, progress, intervention, cancellation |
tool-execution | ToolExecutionEvent | Tool receipt status and safe reason code |
content | InteractionContentEvent | Message, text, reasoning, tool-call, and tool-result UI content |
interactionAgentEvent, interactionExecutionEvent, and interactionContentEvent copy only the permitted facts into portable, deep-frozen values.
Registered content
Create content events with registerInteractionContentEvent before emitting them through a session. Registration enforces:
- canonical event and run identities;
- a canonical timestamp and non-negative sequence;
- a closed content-event kind and facts shape;
- explicit redaction metadata;
- safe JSON for projected tool input and results.
The projection rejects obvious sensitive field names such as access tokens, credentials, passwords, secrets, signed URLs, and authorization data. The host still owns semantic redaction of opaque strings before registration.
Deterministic reduction
reduceInteractionProjection enforces the lifecycle rather than merely appending values:
- conversation identity must match;
- an event ID may repeat only with identical facts;
- sequences increase within their event family key;
- messages start once and close once;
- tool results follow their tool call;
- no event follows a terminal run.
projectInteractionEvent returns null for canonical lifecycle events that do not need a UI representation. This keeps the canonical log richer than any one presentation protocol.
Projection is not evidence
A ConversationEvent is a deterministic view. It is useful for interfaces and stored conversation projections, but it does not replace the canonical ToolExecutionEvent, a durable receipt, or the terminal AgentResult.
An InteractionSession can expose projected conversation events while an explicit runtime integration supplies canonical run events. Raw InteractionEvent values, registration helpers, and reducers remain extension APIs for runtime and adapter authors.