Documentation
¶
Overview ¶
Package eventsource provides a REFERENCE implementation of the event-sourced rehydration path for a host whose system of record is an append-only EVENT LOG (rather than mecatl's own snapshot store): Fold reconstructs a *session.Session by folding a port.EventLog stream back into the aggregate.
WHY THIS EXISTS. mecatl persists a session via SNAPSHOTS (engine/adapter/sessnap, the JSON DTO the store adapters round-trip), and its OWN resume always reloads from that snapshot. But the durable EventLog records the FULL chronological timeline — the same events a relay emits — and a host that already keeps an append-only event log as its system of record (a downstream consumer) would rather implement port.SessionStore.Load by folding its own event stream into a Session than maintain a parallel snapshot. The durable RECORDING of that stream shipped first, with the RECONSTRUCTION direction left snapshot-only; this package is the documented, reference-implemented shape that closes that gap.
WHAT FOLD RECONSTRUCTS — AND WHAT IT CANNOT. A pure event fold rebuilds the STRUCTURAL conversation faithfully: the assistant/tool message sequence, every assistant text + tool-call, every tool result, paired so the history is provider-replayable (ValidateToolPairing passes). It derives cumulative Usage, the lifecycle State/stop, the Counters, and a trailing pending ask.
It is byte-identical-REPLAY-faithful, however, ONLY for providers that do not use the opaque assistant-message replay fields — because those fields NEVER cross the event stream:
- Message.Reasoning (the provider reasoning REPLAY blob)
- Message.ProviderPhase (the OpenAI Responses phase marker)
- Message.ReasoningItemID (the provider reasoning-item id)
- ToolCall.ItemID (the provider item id)
reach the conversation ONLY via Session.RecordAssistant in the loop, never via an emitted Event. (EvReasoningDelta carries a human-readable reasoning SUMMARY, which the loop deliberately never places on Message.Reasoning — so a fold MUST NOT either.) A reconstructed Session is therefore a faithful structural conversation, and a byte-identical replay only for providers that leave those four fields empty (e.g. mockllm, a plain chat model). For OpenAI/Anthropic reasoning models the snapshot (which carries them) is the byte-identical path, which is why mecatl's own resume uses the snapshot; the fold is for event-log-SoR hosts that accept (or themselves carry, in a richer event schema) this contract boundary. This is a DOCUMENTED CONTRACT LIMITATION (engine/COMPATIBILITY.md), not a bug.
CREATION METADATA is supplied via SessionMeta: id, mode, limits, exact EnvironmentRef, display-only placement metadata, profile, provider/model selector, reasoning effort, authoritative title/provenance, kind/relationship, and createdAt are facts that NO event carries, so the caller (who created or discovered the session and thus knows them) provides them alongside the stream. A legacy empty title falls back to the first genuine EvUserPrompt. There is deliberately no EvSessionCreated event.
USER MESSAGES: the loop emits a log-only EvUserPrompt at every site it records a user-role message — the genuine client prompt AND the harness-authored synthetic continuations (no-progress nudge, background-pending nudge, background-completion notice) — so the fold reconstructs user turns in stream order, and the pre-compaction span recovered from EvCompactionArchive carries its user messages verbatim. The reconstructed conversation is therefore COMPLETE except the provider-private replay fields above. (Project-instruction messages discovered at turn 0 — AGENTS.md/CLAUDE.md — are NOT event-carried; they are reassembled from the exactly reattached environment and are out of the conversation the fold rebuilds.)
This is an EXCLUDED reference adapter (engine/COMPATIBILITY.md): it carries no public-API stability promise and is not part of the guarded core surface.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrStream is returned when the event iterator yields an error; it wraps the // underlying per-item error. ErrStream = errors.New("eventsource: event stream error") // ErrReconstruct is returned when the folded events cannot be reconstructed into // a valid Session (an unpairable conversation, an inconsistent terminal state). ErrReconstruct = errors.New("eventsource: cannot reconstruct session") // ErrPrivateStateRequired is returned when an otherwise well-formed external // authorization lifecycle remains open. Safe events deliberately omit the // private continuation state required to resume it. ErrPrivateStateRequired = errors.New("eventsource: private state required") )
Errors returned by Fold.
Functions ¶
func Fold ¶
Fold reconstructs a *session.Session by folding the durable event stream back into the aggregate, supplemented by the creation metadata in meta. It is the reference implementation of an event-sourced port.SessionStore.Load.
The returned Session has its Conversation, Counters, cumulative Usage, lifecycle State, recorded stop reason, and trailing pending ask reconstructed from the stream. See the package doc for the replay-fidelity limitation (Reasoning / ProviderPhase / ReasoningItemID / ItemID are not event-carried).
It returns ErrStream (wrapping the per-item error) if the iterator yields an error, ErrReconstruct if the reconstructed history or authorization lifecycle is inconsistent, and ErrPrivateStateRequired if a well-formed authorization lifecycle remains open after the final event. It never panics.
Types ¶
type SessionMeta ¶
type SessionMeta struct {
// ID is the session id (session.New's first argument).
ID session.SessionID
// Mode is the permission posture.
Mode session.PermissionMode
// Limits are the configured stop conditions.
Limits session.Limits
// EnvironmentRef is the exact durable execution-environment identity.
EnvironmentRef session.EnvironmentRef
// Placement is safe display-only metadata; it is never used for reattachment.
Placement session.PlacementMetadata
// ExecutionCapabilities is the host-observed, creation-time execution ceiling.
ExecutionCapabilities *session.ExecutionCapabilities
// Profile is the opaque tool-surface profile label ("" = default).
Profile string
// ExecutionTemplateID/Revision select an exact versioned recipe; neither
// value can be reconstructed from events or inferred from EnvironmentRef.
ExecutionTemplateID string
ExecutionTemplateRevision string
// ProviderID and ModelID are the opaque neutral provider+model selector pair
// ("" / "" = server default).
ProviderID string
ModelID string
// ReasoningEffort is the opaque neutral reasoning-effort token, ""
// when unset. Opaque to the domain; carried so the rehydrated session re-mints
// the same-effort per-session engine via the factory.
ReasoningEffort string
// DebugMCPServers and DebugMCPTools are the durable selected global MCP names
// and exact direct-tool ceiling for a debug session.
DebugMCPServers []string
DebugMCPTools []string
DebugTargetFingerprint string
// Title and TitleProvenance are authoritative creation/discovery metadata when
// supplied. A legacy empty title is derived from the first genuine user event.
Title string
TitleProvenance session.TitleProvenance
// TitleRevision is the title-specific durable metadata revision. Zero is legacy.
TitleRevision uint64
// Title-generation metadata is authoritative metadata, not event-carried run data.
TitleGeneration session.TitleGenerationState
TitleSourcePrompts []string
TitleAttempts []session.TitleAttempt
// TokenUsage is the canonical durable accounting ledger supplied by snapshot metadata.
TokenUsage map[session.UsageKind]session.TokenUsage
// LatestContextOccupancy is the optional display-only context meter supplied by
// the event-log host's stored snapshot metadata, never reconstructed from events.
LatestContextOccupancy *session.ContextOccupancy
// Kind and Relationship are the trusted producer taxonomy supplied alongside
// the event stream. An empty kind is legacy and folds to unknown.
Kind session.SessionKind
Relationship session.SessionRelationship
// Incarnation and Owner are immutable creation identity. Empty Incarnation is
// legacy metadata and folds to the deterministic prefix-disjoint legacy token.
Incarnation session.IncarnationID
Owner *session.Principal
// Authority is the plain derived-capability payload supplied with creation
// metadata. Nil is a documented legacy record written without authority; a
// present payload is validated and bound before reconstruction proceeds.
Authority *session.Authority
// ExternalBinding is the opaque composition-issued process-external
// identity (e.g. an MCP broker attachment binding). Not event-carried:
// safe to omit for a pure fold UNLESS the host requires exact external-
// runtime reattachment, in which case it MUST be supplied here — there is
// no other path into Fold's reconstructed Session for it.
ExternalBinding session.ExternalBinding
// CreatedAt is the creation timestamp.
CreatedAt time.Time
}
SessionMeta carries the creation facts that NO event in the stream records, so a fold can construct the aggregate. The caller — the event-log-SoR host, which created the session and therefore knows these — supplies them alongside the stream. They mirror the inert creation labels sessnap restores by direct assignment (Profile / ProviderID / ModelID are opaque to the domain).