Documentation
¶
Overview ¶
Package event defines the sealed union of rig, session, loop, turn, step, and tool events. Enduring rig-control and workspace transitions are durable replay inputs; ephemeral streaming events are never persisted.
Every concrete event embeds a Header (producer identity), exactly one lifecycle mixin (ephemeral, enduring, or terminal — supplying Class()/EndsTurn()), and exactly one scope mixin (sessionScoped or loopScoped — supplying Scope()). The compile-time assertions below pin the sealed union: every concrete event type must satisfy Event, and the list is the authoritative enumeration of the union. Adding a new event without adding it here is harmless, but removing a type or breaking its interface satisfaction fails the build here first.
Index ¶
- Constants
- func CompactWaiterReplyID(attempt CompactAttemptID, commandID uuid.UUID, resolved bool) uuid.UUID
- func ErrKind(err error) string
- func MarshalEvent(ev Event) ([]byte, error)
- func ShouldDeliver(filter EventFilter, ev Event) bool
- func ValidLoopRestoreTombstoneCategory(category string) bool
- func ValidateEvent(ev Event) error
- type ActiveLoopChanged
- type AgentRuntime
- type BasisPoints
- type CancelReason
- type Class
- type Clock
- type CompactAttemptID
- type CompactRejectReason
- type CompactWaiterRejected
- type CompactWaiterResolved
- type CompactionCommitted
- type CompactionReason
- type CompactionRejected
- type CompactionStarted
- type ConfigEpoch
- type ConfigFingerprint
- type ConfigManifest
- type ConfigurationAdopted
- type ContextBasis
- type ContextField
- type ContextMeasured
- type ContextMeasurement
- type ContextPressure
- type ContextRevision
- type ContextValidationError
- type DecisionSource
- type DelegateDeliveryState
- type DelegateDeliveryStateChanged
- type DelegateRequestAccepted
- type Delivery
- type DriftAssessment
- type DriftCategory
- type DriftChange
- type DriftSeverity
- type EmptyResponseError
- type EphemeralNotPersistableError
- type Event
- type EventDecodeError
- type EventEncodeError
- type EventFilter
- type EventLimitError
- type EventName
- type EventVisibility
- type ExternalToolIdentity
- type Factory
- func (f *Factory) NewHeader() (Header, error)
- func (f *Factory) Stamp(h Header) (Header, error)
- func (f *Factory) StampCompactWaiterRejected(ev CompactWaiterRejected) (Header, error)
- func (f *Factory) StampCompactWaiterResolved(ev CompactWaiterResolved) (Header, error)
- func (f *Factory) StampWorkflowActivity(ev WorkflowActivity, deterministicID uuid.UUID) (WorkflowActivity, error)
- type FieldName
- type ForeignSessionBound
- type GateOpened
- type GatePrepared
- type GateResolved
- type Header
- type HustleCompleted
- type HustleFailed
- type HustleRunDescriptor
- type HustleStarted
- type IDGen
- type InputCancelled
- type InputQueued
- type IntegrationState
- type IntegrationStatus
- type InvalidEventError
- type LegacyRuntimeMigrationError
- type LoopAgentSessionBound
- type LoopExternalToolsetChanged
- type LoopIdle
- type LoopInferenceChanged
- type LoopModeChanged
- type LoopRestoreTombstoned
- type LoopScope
- type LoopStarted
- type ModelRuntime
- type PermissionDecided
- type PermissionDecisionEffect
- type PermissionRequested
- type PermissionReviewCompleted
- type PermissionReviewStarted
- type PressureLevel
- type ProcessBackgrounded
- type ProcessCompleted
- type ProcessLost
- type ProcessStarted
- type ProcessStopRequested
- type RejectReason
- type Reply
- type RestoreDone
- type RestoreErrored
- type RestoreStarted
- type RestoredError
- type RestoredModelFacingError
- type Rule
- type Scope
- type SessionActive
- type SessionIdle
- type SessionStarted
- type SessionStopped
- type SnapshotConsistency
- type SnapshotTriggerKind
- type StepDone
- type StrictnessLevel
- type Subscription
- type TokenDelta
- type ToolCallCompleted
- type ToolCallStarted
- type ToolLimitError
- type ToolManifestEntry
- type TurnDone
- type TurnFailed
- type TurnFoldedInto
- type TurnIndex
- type TurnInterrupted
- type TurnPanicError
- type TurnRejected
- type TurnStarted
- type UnknownEventTypeError
- type UnknownMessageRoleError
- type UnsupportedSchemaError
- type UserInputRequested
- type WorkflowActivity
- type WorkflowActivityKind
- type WorkflowRunStatus
- type WorkspaceCheckpointed
- type WorkspaceRestored
Constants ¶
const ( LoopRestoreTombstoneRuntimeMismatch = "runtime_mismatch" )
LoopRestoreTombstoneCategory is the bounded reason a restored child was kept in the durable topology without a live backend.
const ( // MaxIntegrationSourceBytes caps Source. MaxIntegrationSourceBytes = 64 // MaxIntegrationNameBytes caps Name. MaxIntegrationNameBytes = 128 // MaxIntegrationDetailBytes caps Detail. MaxIntegrationDetailBytes = 512 )
Maximum sizes for the free-text and identifier fields. They exist because an integration is not part of this module and its inputs are not this module's to trust: a server-influenced name or message must not be able to grow an event without bound. See ValidateEvent, which enforces them at the publish boundary.
const ( // KindEmptyResponse classifies *EmptyResponseError. KindEmptyResponse = "empty_response" // KindToolLimit classifies *ToolLimitError (the runaway guard). KindToolLimit = "tool_limit" // KindTurnPanic classifies *TurnPanicError (a recovered turn-goroutine panic). KindTurnPanic = "turn_panic" // KindUnknown is the fail-open fallback for any error the switch does not // recognize, including the open-ended provider/stream errors. It is stable: a // caller can rely on "unknown" for an unrecognized cause. KindUnknown = "unknown" )
Stable kind strings ErrKind projects the event package's own TurnFailed.Err causes to. They are part of the durable wire contract: never rename one (old journals carry the old string); only add new constants. Provider/stream errors that flow through streamFailure are not enumerated here on purpose — the event package is a leaf (it must not import the LLM provider layer), so they fall back to KindUnknown while their full Error() text is still preserved in RestoredError.Message.
const ( // MaxConfigMessageLen and MaxConfigActorLen bound the durable, partly // user-authored audit fields. They are exported so the restore constructor can // TRUNCATE a decider's over-long Message/Actor before building the adoption (a // long audit note must never brick a restore); the validator here still rejects // an over-long field on a hand-crafted, decoded journal record. MaxConfigMessageLen = 4096 MaxConfigActorLen = 1024 )
Bounds for ConfigurationAdopted's durable, partly user-authored payload: a hostile or buggy decision must not be able to append an unbounded record to the journal, and a legacy (SchemaVersion 0) manifest projection is never persisted.
const ( WorkflowActivityRunStarted WorkflowActivityKind = "run_started" WorkflowActivityVertexCompleted WorkflowActivityKind = "vertex_completed" WorkflowActivityRunInterrupted WorkflowActivityKind = "run_interrupted" WorkflowActivityRunResumed WorkflowActivityKind = "run_resumed" WorkflowActivityRunCompleted WorkflowActivityKind = "run_completed" WorkflowActivityRunCancelled WorkflowActivityKind = "run_cancelled" WorkflowActivityRunFailed WorkflowActivityKind = "run_failed" // Kind-prefixed aliases keep the enum discoverable for callers that group // constants by the declared type name. WorkflowActivityKindRunStarted = WorkflowActivityRunStarted WorkflowActivityKindVertexCompleted = WorkflowActivityVertexCompleted WorkflowActivityKindRunInterrupted = WorkflowActivityRunInterrupted WorkflowActivityKindRunResumed = WorkflowActivityRunResumed WorkflowActivityKindRunCompleted = WorkflowActivityRunCompleted WorkflowActivityKindRunCancelled = WorkflowActivityRunCancelled WorkflowActivityKindRunFailed = WorkflowActivityRunFailed )
const ( MaxWorkflowNameBytes = 64 MaxWorkflowVersionBytes = 64 MaxWorkflowVertexLabelBytes = 128 MaxWorkflowActivityMessageBytes = 512 MaxWorkflowActivityProgress = 1_000_000 )
Bounds for fields originating in workflow definitions or execution state. They are byte limits at the durable boundary; UTF-8 validity is checked separately so a multi-byte rune can never be split by a producer.
const ManifestSchemaVersion uint32 = 3
ManifestSchemaVersion is the current ConfigManifest schema version. Bumping it changes the canonical encoding, which changes every fingerprint — restore therefore never treats raw fingerprint inequality across schema versions as drift (see AssessDrift) and records a one-time baseline upgrade instead.
v2 adds both PermissionReviewPolicyRev (the review-policy-identity-drift- while-enabled fix) and HookPolicyRev (the operation-hooks feature) to the canonical encoding. Neither shipped independently — both landed in the same merge before v2 was ever persisted anywhere — so v2 is defined as carrying both fields together, not as two separate single-field bumps. v3 adds the explicit runtime identity fields. v1 and v2 canonical encodings remain immutable so previously persisted fingerprints remain verifiable.
Variables ¶
This section is empty.
Functions ¶
func CompactWaiterReplyID ¶
CompactWaiterReplyID derives the idempotency key for one per-command outcome.
func ErrKind ¶
ErrKind maps a concrete error to its stable kind string via an errors.As switch over the known in-package TurnFailed.Err causes. It is what the event codec uses to project TurnFailed.Err into RestoredError.Kind on marshal. An already-restored *RestoredError re-projects to its own Kind (idempotent re-marshal); a nil or unrecognized error yields KindUnknown. errors.As (not a bare type switch) so a wrapped known cause is still classified by its concrete type.
func MarshalEvent ¶
MarshalEvent encodes an Enduring event into the durable wire envelope: a JSON object carrying a "type" discriminator (== the classify name, the package's single naming source of truth), a "v" schema version, the embedded Header fields, and the type-specific payload. It fails closed on an Ephemeral event (EphemeralNotPersistableError) and on a type outside the sealed union (UnknownEventTypeError). The interface-valued fields that have no general codec (PermissionRequested.Request, TurnFailed.Err, RestoreErrored.Err) are projected onto durable forms; every other field round-trips through encoding/json.
func ShouldDeliver ¶
func ShouldDeliver(filter EventFilter, ev Event) bool
ShouldDeliver reports whether ev passes filter for one subscriber. Session-scoped events (including the durable WorkflowActivity timeline) always deliver, bypassing LoopScope. Loop-scoped events are matched by the class-appropriate LoopScope against the producing loop's Header.LoopID. It is evaluated at fan-out, before the bounded send, so a filtered-out firehose never enters the egress buffer.
func ValidateEvent ¶
ValidateEvent checks ev against the ID fill matrix and returns a typed *InvalidEventError on the first violation, nil when ev satisfies every invariant. EventID is required on every event; the per-type profile then pins the required and must-be-zero coordinates (and ToolExecutionID for tool-interaction and permission-review events). Fail-secure: an event whose concrete type is not in the sealed union is invalid with FieldType/RuleUnknownType — the caller learns the type is unknown, not that some coordinate is missing.
Types ¶
type ActiveLoopChanged ¶
type ActiveLoopChanged struct {
Header
PreviousLoopID uuid.UUID `json:"previous_loop_id,omitzero"`
ActiveLoopID uuid.UUID `json:"active_loop_id"`
// contains filtered or unexported fields
}
ActiveLoopChanged records the session's selected loop. The new selection is observable only after this session-scoped transition is durable.
type AgentRuntime ¶
type AgentRuntime struct {
Harness string `json:"harness"`
Profile string `json:"profile"`
CredentialMode string `json:"credential_mode"`
Source string `json:"source,omitempty"`
SelectionKind string `json:"selection_kind,omitempty"`
ModelAlias string `json:"model_alias"`
SmallModelAlias string `json:"small_model_alias,omitempty"`
ACPSessionID string `json:"acp_session_id,omitempty"`
}
AgentRuntime is the bounded, secret-free identity of the runtime selected for a loop. It is additive so legacy LoopStarted records decode with nil.
type BasisPoints ¶
type BasisPoints uint16
BasisPoints is a percentage scaled by 100. Values above 10_000 are invalid.
const FullScaleBasisPoints BasisPoints = 10_000
type CancelReason ¶
type CancelReason uint8
CancelReason explains why a queued input left the loop queue without committing. It is carried by InputCancelled.
const ( CancelClientRetracted CancelReason = iota CancelTurnInterrupted CancelTurnFailed )
type Class ¶
type Class uint8
Class is the delivery class of an event. It is semantic — "is this event reconstructable from a later authoritative event?" — not a transport flag, which is why it belongs on the event rather than on the transport.
type Clock ¶
Clock and IDGen are injected so tests are deterministic (mirrors session's injected idGenerator seam): the Factory mints from these rather than calling time.Now/uuid.New directly, so a test can pin both. IDGen returns an error so a crypto/rand failure propagates (matching session's idGenerator func() (uuid.UUID, error)) rather than being swallowed.
type CompactAttemptID ¶
func (CompactAttemptID) IsZero ¶
func (id CompactAttemptID) IsZero() bool
func (CompactAttemptID) MarshalText ¶
func (id CompactAttemptID) MarshalText() ([]byte, error)
func (*CompactAttemptID) UnmarshalText ¶
func (id *CompactAttemptID) UnmarshalText(text []byte) error
type CompactRejectReason ¶
type CompactRejectReason uint8
const ( CompactRejectUnspecified CompactRejectReason = iota CompactRejectControlLaneFull CompactRejectShuttingDown CompactRejectInterrupted CompactRejectCanceled CompactRejectStaleBasis CompactRejectProgressPublication CompactRejectExecutionFailed CompactRejectInvalidSummary CompactRejectContextCountFailed CompactRejectSummaryTooLarge CompactRejectInternal CompactRejectContextLimitUnknown CompactRejectRetainedTailTooLarge )
func (CompactRejectReason) Valid ¶
func (r CompactRejectReason) Valid() bool
type CompactWaiterRejected ¶
type CompactWaiterRejected struct {
Header
AttemptID CompactAttemptID `json:"attempt_id"`
Reason CompactRejectReason `json:"reason"`
// contains filtered or unexported fields
}
type CompactWaiterResolved ¶
type CompactWaiterResolved struct {
Header
AttemptID CompactAttemptID `json:"attempt_id"`
CommittedEventID uuid.UUID `json:"committed_event_id"`
// contains filtered or unexported fields
}
type CompactionCommitted ¶
type CompactionCommitted struct {
Header
AttemptID CompactAttemptID `json:"attempt_id"`
WaiterCommandIDs []uuid.UUID `json:"waiter_command_ids"`
Reason CompactionReason `json:"reason"`
Basis ContextBasis `json:"basis"`
Summary *content.UserMessage `json:"summary"`
Retained content.AgenticMessages `json:"retained,omitempty"`
PostContext ContextMeasurement `json:"post_context"`
Duration time.Duration `json:"duration,omitzero"`
// contains filtered or unexported fields
}
func (CompactionCommitted) MarshalJSON ¶
func (value CompactionCommitted) MarshalJSON() ([]byte, error)
MarshalJSON gives CompactionCommitted's additive retained graph the same tagged wire shape as the existing message-bearing events. Validation remains at ValidateEvent/MarshalEvent, so direct JSON marshaling retains the package's established behavior of encoding the value without revalidating all fields.
func (*CompactionCommitted) UnmarshalJSON ¶
func (value *CompactionCommitted) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes both legacy summary-only records and the additive retained message graph. Empty or omitted retained arrays normalize to nil, matching omitempty's fixed point and keeping old event values stable.
type CompactionReason ¶
type CompactionReason uint8
const ( CompactionReasonUnspecified CompactionReason = iota CompactionReasonManual CompactionReasonAutomatic )
func (CompactionReason) Valid ¶
func (r CompactionReason) Valid() bool
type CompactionRejected ¶
type CompactionRejected struct {
Header
AttemptID CompactAttemptID `json:"attempt_id"`
WaiterCommandIDs []uuid.UUID `json:"waiter_command_ids"`
Reason CompactionReason `json:"reason"`
Basis ContextBasis `json:"basis"`
RejectReason CompactRejectReason `json:"reject_reason"`
Duration time.Duration `json:"duration,omitzero"`
// contains filtered or unexported fields
}
type CompactionStarted ¶
type CompactionStarted struct {
Header
AttemptID CompactAttemptID `json:"attempt_id"`
Reason CompactionReason `json:"reason"`
Basis ContextBasis `json:"basis"`
// contains filtered or unexported fields
}
type ConfigEpoch ¶
type ConfigEpoch uint64
ConfigEpoch orders the configurations explicitly adopted within one Session. SessionStarted is epoch 1; each ConfigurationAdopted increments it.
type ConfigFingerprint ¶
type ConfigFingerprint struct {
// TopologyRev is the digest of ordered loop definitions, primer roots, active
// primer, and delegation edges owned by the rig.
TopologyRev string `json:"topology_rev,omitzero"`
// AgentKind names the application and Loop role this session ran (e.g. "carbon:carbon").
// It is empty for a caller that does not inject a kind (a non-swarm/legacy session).
AgentKind string `json:"agent_kind,omitzero"`
// ModelID is the model identifier the session ran against (the llm.Model.Name).
ModelID string `json:"model_id,omitzero"`
// SystemPromptRev is a content digest (hex sha256) of the system prompt text, so
// a prompt change is detectable without persisting the prompt itself.
SystemPromptRev string `json:"system_prompt_rev,omitzero"`
// ToolPolicyRev is a content digest (hex sha256) over the tool set's stable
// identity (its sorted tool names), so a tool-set change is detectable without
// persisting the tool definitions.
ToolPolicyRev string `json:"tool_policy_rev,omitzero"`
// RuntimeSkills records whether the untrusted, human-gated workspace skill source
// was enabled for this session. A session must not silently resume under a
// different skill-trust mode, so the flag is part of the fingerprint. It is the
// MODE only — the flag alone does NOT distinguish two repos' .skills/, which is
// what WorkspaceRoot is for.
RuntimeSkills bool `json:"runtime_skills,omitzero"`
// WorkspaceRoot is the canonical absolute workspace-root id (filepath.Clean of the
// absolute root). It binds the session to the repo whose .skills/ (and file tools)
// it ran against, so a session cannot silently resume under a different repo's
// workspace. Empty for a caller that does not inject a root.
WorkspaceRoot string `json:"workspace_root,omitzero"`
// AgentAdapter identifies the foreign-agent adapter that backed this session
// (e.g. "claude"). Empty for a native session. A session must not silently resume
// under a different foreign adapter, so it is part of the fingerprint.
AgentAdapter string `json:"agent_adapter,omitzero"`
// PermissionPosture is the non-interactive permission mode the foreign agent ran
// under (e.g. "default", "acceptEdits"). Empty for a native session. A change in
// posture is a behavior change that must not resume unnoticed.
PermissionPosture string `json:"permission_posture,omitzero"`
// NativePermissionPolicyRev is an opaque content digest (hex sha256) of the
// NATIVE permission and access configuration, computed and injected by the
// composition root (e.g. over the selected access profiles and rule policy).
// Harness only compares it. Empty for a foreign session (which uses
// PermissionPosture) or a caller that does not inject it. A change is a
// behavior change that must not resume unnoticed.
NativePermissionPolicyRev string `json:"native_permission_policy_rev,omitzero"`
// ExternalCapabilityRev is a content digest over the identity of the EXTERNAL
// capabilities an application attached to this session — tools, prompts and
// resources served by processes Harness does not own, such as MCP servers.
// Empty means the session had none, which is what makes the field additive:
// a journal written before it existed decodes it empty and compares Equal to
// a live config that also has none.
//
// Harness neither computes nor interprets it. It is supplied by the
// composition root, which is the only layer that knows what it attached; the
// canonical producer today is github.com/looprig/mcp's
// mcpharness.Manager.ConfigDigest. Harness's part of the contract is the two
// properties every other Rev field here has: it is a digest, so it carries
// identity and not the configuration — a server's credentials, headers, and
// environment must never reach a journal — and it is compared, never parsed.
//
// It is ONE opaque string rather than a structured manifest deliberately.
// The richer model — per-binding manifests, configuration epochs, and typed
// drift — is specified in docs/plans/2026-07-16-session-versioning-migration-design.md
// and is NOT implemented. Until it is, external capability drift is reported
// through the same one-shot mechanism as every other config change: a
// fingerprint mismatch at restore, which sessionruntime's
// WithAllowConfigMismatch decides on. A field that promised more than that
// would be a promise nothing here keeps.
ExternalCapabilityRev string `json:"external_capability_rev,omitzero"`
// RuntimeProfile is the secret-free backend profile selected for this bound
// loop. Empty preserves native and legacy callers.
RuntimeProfile string `json:"runtime_profile,omitzero"`
// RuntimeCatalogRev identifies the parent-scoped runtime catalog snapshot.
RuntimeCatalogRev string `json:"runtime_catalog_rev,omitzero"`
// RuntimeIdentityRev is an opaque hex digest of the selected runtime tuple.
// It is produced by loop.BoundDefinition.RuntimeIdentity().Digest() and
// covers the model alias, target key, effective effort (including canonical
// none), profile, and catalog without persisting raw model descriptors.
RuntimeIdentityRev string `json:"runtime_identity_rev,omitzero"`
}
ConfigFingerprint is the stable identity of the agent configuration a session started under, stamped onto SessionStarted so a durable journal can detect when a restore is being attempted against a materially changed config (a different model, system prompt, tool policy, skill-trust mode, workspace, foreign adapter, or permission posture). It is a fingerprint, not the config itself: each field is either a verbatim identifier (AgentKind, ModelID, WorkspaceRoot, AgentAdapter, PermissionPosture), a content digest (SystemPromptRev, ToolPolicyRev), or a mode flag (RuntimeSkills) — never the raw prompt text or tool definitions — so it is safe to persist and compare without leaking definition internals. Package rig freezes the fingerprint from its registered loop definitions, topology, and composition fields; this package only defines the durable value and its equality.
The fields evolve ADDITIVELY: every field is omitzero, so an old journal record that predates a field decodes it as the zero value and compares Equal to a record that also leaves it empty — a session persisted before a field was added restores without a spurious mismatch.
func (ConfigFingerprint) Equal ¶
func (f ConfigFingerprint) Equal(other ConfigFingerprint) bool
Equal reports whether two fingerprints identify the same configuration: true iff every field is equal. It is the comparison a restore uses to decide whether the persisted config still matches the live one. New fields are additive (omitzero), so an old record's empty new field equals a current record that also leaves it empty.
type ConfigManifest ¶
type ConfigManifest struct {
SchemaVersion uint32 `json:"schema_version"`
AgentKind string `json:"agent_kind,omitzero"`
TopologyRev string `json:"topology_rev,omitzero"`
ModelID string `json:"model_id,omitzero"`
SystemPromptRev string `json:"system_prompt_rev,omitzero"`
Tools []ToolManifestEntry `json:"tools,omitzero"`
RuntimeSkills bool `json:"runtime_skills,omitzero"`
WorkspaceRoot string `json:"workspace_root,omitzero"`
WorkspaceTrust string `json:"workspace_trust,omitzero"`
AgentAdapter string `json:"agent_adapter,omitzero"`
// PermissionPosture is the foreign-agent posture string; native sessions
// use NativePermissionPolicyRev + PermissionStrictness instead.
PermissionPosture string `json:"permission_posture,omitzero"`
NativePermissionPolicyRev string `json:"native_permission_policy_rev,omitzero"`
PermissionStrictness StrictnessLevel `json:"permission_strictness,omitzero"`
// PermissionReviewConfigured reports only whether ANY permission-review
// classifier was registered for this session — never the classifier or
// policy identity itself (the classifier SET's identity stays folded into
// TopologyRev, for detecting drift among already-enabled classifiers,
// classified Info like the rest of TopologyRev). It exists so AssessDrift
// has a directionally-comparable signal for the one transition an opaque
// digest can't distinguish: classifiers going from unconfigured to
// configured across a restore, which must never resume silently (design
// §21).
PermissionReviewConfigured bool `json:"permission_review_configured,omitzero"`
// PermissionReviewPolicyRev is the review POLICY's own Revision label
// (gate.PermissionReviewPolicy.Revision — e.g. "strict-policy-v1"),
// carried SEPARATELY from TopologyRev (which also folds this same value
// in, alongside classifier identity, for backward-compatible digest
// coverage). Without this dedicated field, a policy-identity change while
// classifiers stay configured on both sides is visible only as an opaque
// TopologyRev digest difference — classified DriftInfo like any other
// topology change and silently auto-accepted by DefaultPolicyDecider, so
// a session opened under a strict review policy (e.g. MaximumAutoRisk:
// low) could restore under a looser one (e.g. default MaximumAutoRisk:
// high) with no warning at all. AssessDrift compares this field
// DIRECTLY (Warn on any change) whenever PermissionReviewConfigured is
// true on both sides — the same kind of directional fix already applied
// to PermissionReviewConfigured itself for the disabled->enabled
// transition, extended to cover an already-enabled reviewer's policy
// identity changing underneath it.
PermissionReviewPolicyRev string `json:"permission_review_policy_rev,omitzero"`
ConfinementRev string `json:"confinement_rev,omitzero"`
ConfinementStrictness StrictnessLevel `json:"confinement_strictness,omitzero"`
ExternalCapabilityRev string `json:"external_capability_rev,omitzero"`
HookPolicyRev string `json:"hook_policy_rev,omitzero"`
RuntimeProfile string `json:"runtime_profile,omitzero"`
RuntimeCatalogRev string `json:"runtime_catalog_rev,omitzero"`
// RuntimeIdentityRev is the opaque hex digest of the selected runtime tuple;
// raw provider, model, effort, alias, endpoint, and credential values never
// enter the durable manifest.
RuntimeIdentityRev string `json:"runtime_identity_rev,omitzero"`
// AppFields are application-defined, secret-free compatibility fields.
// Canonically encoded in sorted key order.
AppFields map[string]string `json:"app_fields,omitzero"`
// contains filtered or unexported fields
}
ConfigManifest is the canonical, bounded, secret-free description of the behavior a Session runs under. It is a strict superset of the legacy ConfigFingerprint (see ManifestFromLegacy) and the input to both the SHA-256 fingerprint and typed drift assessment. Credentials, raw prompts, tool schemas, and environment contents never enter a manifest.
SchemaVersion 0 marks a legacy projection built by ManifestFromLegacy; it is never persisted and never fingerprinted.
func ManifestFromLegacy ¶
func ManifestFromLegacy(f ConfigFingerprint) ConfigManifest
ManifestFromLegacy projects a legacy ConfigFingerprint into a partial manifest for drift assessment against a live candidate. SchemaVersion 0 marks the projection: it is never persisted, never fingerprinted, and limits assessment to the fields the legacy fingerprint can distinguish (tool identity is names-only; permission and confinement are digest-only, so their changes classify Warn).
func (ConfigManifest) Fingerprint ¶
func (m ConfigManifest) Fingerprint() string
Fingerprint is SHA-256 over the canonical encoding: explicit domain, schema version, stable field order, length-delimited values, deterministic collection ordering. Equal fingerprints of the same SchemaVersion identify behaviorally identical configurations.
func (ConfigManifest) ToolNamesRev ¶
func (m ConfigManifest) ToolNamesRev() string
ToolNamesRev reproduces the legacy names-only tool digest from the manifest's tool entries, so a full manifest can be compared against a legacy baseline. It MUST stay byte-identical to rig's toolPolicyRev (sorted names joined by \n).
type ConfigurationAdopted ¶
type ConfigurationAdopted struct {
Header
Epoch ConfigEpoch `json:"epoch"`
PreviousFingerprint string `json:"previous_fingerprint,omitzero"`
AdoptedFingerprint string `json:"adopted_fingerprint"`
Manifest ConfigManifest `json:"manifest"`
Drift []DriftChange `json:"drift,omitzero"`
Source DecisionSource `json:"source"`
Actor string `json:"actor,omitzero"`
AppVersion string `json:"app_version,omitzero"`
// Message is durable user-authored data, not an instruction: it never gains
// authority during future prompt construction.
Message string `json:"message,omitzero"`
// contains filtered or unexported fields
}
ConfigurationAdopted commits a new configuration epoch: the durable record of an accepted restore drift or a one-time baseline upgrade. It is appended under the restore lease after the decision validates and before RestoreDone; the latest SessionStarted or ConfigurationAdopted is the baseline for the next restore's drift assessment. Like SessionStarted it is session-scoped and Enduring — Header.SessionID is set, LoopID/TurnID/StepID are zero, and the SessionID rides in the Header (there is no standalone SessionID field).
type ContextBasis ¶
type ContextBasis struct {
Revision ContextRevision `json:"revision"`
ThroughEventID uuid.UUID `json:"through_event_id"`
}
ContextBasis identifies the exact durable context included in a measurement.
type ContextField ¶
type ContextField string
ContextField identifies an invalid structural measurement field.
const ( ContextFieldRevision ContextField = "Revision" ContextFieldThroughEventID ContextField = "ThroughEventID" ContextFieldModel ContextField = "Model" ContextFieldRequestFingerprint ContextField = "RequestFingerprint" ContextFieldInputLimit ContextField = "InputLimit" ContextFieldQuality ContextField = "Quality" )
type ContextMeasured ¶
type ContextMeasured struct {
Header
Measurement ContextMeasurement `json:"measurement"`
// contains filtered or unexported fields
}
ContextMeasured durably publishes the latest authoritative measurement.
type ContextMeasurement ¶
type ContextMeasurement struct {
Basis ContextBasis `json:"basis"`
Model model.ModelKey `json:"model"`
RequestFingerprint [32]byte `json:"request_fingerprint"`
InputTokens content.TokenCount `json:"input_tokens"`
InputLimit content.TokenCount `json:"input_limit"`
Quality contextcount.CountQuality `json:"quality"`
}
ContextMeasurement is one authoritative complete-request input count.
func (ContextMeasurement) Validate ¶
func (m ContextMeasurement) Validate() error
Validate checks structural invariants without treating over-limit occupancy as malformed: raw counts above InputLimit remain valid audit evidence.
type ContextPressure ¶
type ContextPressure struct {
Header
Measurement ContextMeasurement `json:"measurement"`
Occupancy BasisPoints `json:"occupancy"`
Previous PressureLevel `json:"previous"`
Current PressureLevel `json:"current"`
// contains filtered or unexported fields
}
ContextPressure is a droppable public level-change signal.
type ContextRevision ¶
type ContextRevision uint64
ContextRevision is the loop-local revision of the committed active context. Zero is invalid because it cannot identify a committed context mutation.
type ContextValidationError ¶
type ContextValidationError struct {
Field ContextField
Cause error
}
ContextValidationError reports malformed replayable context metadata.
func (*ContextValidationError) Error ¶
func (e *ContextValidationError) Error() string
func (*ContextValidationError) Unwrap ¶
func (e *ContextValidationError) Unwrap() error
type DecisionSource ¶
type DecisionSource string
DecisionSource records who or what accepted a configuration adoption.
const ( DecisionSourceUser DecisionSource = "user" DecisionSourcePolicy DecisionSource = "policy" DecisionSourceOperator DecisionSource = "operator" // DecisionSourceMigration is stamped only by Harness itself when a Phase 2 // migration adopts a configuration; a RestoreDecider never produces it. DecisionSourceMigration DecisionSource = "migration" )
func (DecisionSource) Valid ¶
func (s DecisionSource) Valid() bool
Valid reports whether the source is one of the four closed DecisionSource values. An adoption record with any other source is malformed.
type DelegateDeliveryState ¶
type DelegateDeliveryState string
DelegateDeliveryState is the durable resolution vocabulary for a delegated message's delivery attempt. The zero value is intentionally not a state: an intent and a fallback queue are represented by the exact journaled command record, while these values record only the foreign-delivery outcomes that must survive replay.
const ( // DelegateDeliverySteerAttemptReserved is appended before a foreign adapter // may admit the steering request to its writer. It is an intermediate state: // restore may correlate a later turn terminal or replay a durable fallback; // only an intent-only reservation is repaired as resolved_unknown. DelegateDeliverySteerAttemptReserved DelegateDeliveryState = "steer_attempt_reserved" // DelegateDeliveryResolvedUnknown records an ambiguous delivery result. It // is terminal for automatic delivery recovery: the request may have reached // the adapter, so no fallback is allowed. DelegateDeliveryResolvedUnknown DelegateDeliveryState = "resolved_unknown" // DelegateDeliveryResolvedUntrackable records an adapter lifecycle breach // that delivered outside the host-owned turn contract. No synthetic turn or // automatic fallback may follow it. DelegateDeliveryResolvedUntrackable DelegateDeliveryState = "resolved_untrackable" )
func (DelegateDeliveryState) Valid ¶
func (s DelegateDeliveryState) Valid() bool
Valid reports whether s is one of the closed durable delivery states.
type DelegateDeliveryStateChanged ¶
type DelegateDeliveryStateChanged struct {
Header
RequestID uuid.UUID `json:"request_id"`
TargetLoopID uuid.UUID `json:"target_loop_id"`
State DelegateDeliveryState `json:"state"`
// contains filtered or unexported fields
}
DelegateDeliveryStateChanged is the durable state transition for a foreign delegate delivery attempt. It deliberately carries only the request id, the target loop, and the closed state vocabulary in its payload. The event never embeds command.UserInput (which would create an event↔command import cycle), broker tokens, or model-visible origin/session identity. The existing v1 event envelope versions this record on the journal wire.
It is session-scoped because the session owns the reservation and adjudicates adapter delivery; TargetLoopID is explicit so the state remains addressable without pretending the target loop produced the event.
type DelegateRequestAccepted ¶
type DelegateRequestAccepted struct {
Header // Cause.CommandID=request, Coordinates.LoopID=target child
// contains filtered or unexported fields
}
DelegateRequestAccepted is the durable actor-side acceptance of a follow-up machine NoFold request, emitted before it can queue or start.
type Delivery ¶
Delivery is one fan-in delivery: the event plus its durable journal sequence. JournalSeq is 0 for Ephemeral deliveries (never persisted, never sequenced) and the strictly-monotonic append sequence for Enduring deliveries. It rides only the LIVE delivery path — it is never part of the persisted event codec, so the durable envelope stays byte-compatible.
type DriftAssessment ¶
type DriftAssessment struct {
Changes []DriftChange `json:"changes,omitzero"`
BaselineUpgrade bool `json:"baseline_upgrade,omitzero"`
}
DriftAssessment is the typed comparison of the latest adopted baseline against the candidate live manifest.
func AssessDrift ¶
func AssessDrift(baseline, candidate ConfigManifest) DriftAssessment
AssessDrift compares baseline (the latest adopted manifest, possibly a legacy projection) against candidate (the frozen live manifest). Direction- sensitive categories classify Info when the posture tightened, Warn when it broadened, and Warn when direction is unknowable (an opaque digest-only change) — fail secure. The returned Changes are deterministically ordered.
func (DriftAssessment) AnyWarn ¶
func (a DriftAssessment) AnyWarn() bool
AnyWarn reports whether any change requires an explicit decision under default policy.
type DriftCategory ¶
type DriftCategory string
DriftCategory names the manifest field family a change belongs to.
const ( DriftTool DriftCategory = "tool" DriftModel DriftCategory = "model" DriftPrompt DriftCategory = "prompt" DriftTopology DriftCategory = "topology" DriftExternal DriftCategory = "external" DriftConfinement DriftCategory = "confinement" DriftPermission DriftCategory = "permission" DriftWorkspace DriftCategory = "workspace" DriftTrust DriftCategory = "trust" DriftAgentKind DriftCategory = "agent_kind" DriftAgentName DriftCategory = "agent_name" DriftAdapter DriftCategory = "adapter" DriftRuntimeSkills DriftCategory = "runtime_skills" DriftHookPolicy DriftCategory = "hook_policy" DriftRuntime DriftCategory = "runtime" DriftApp DriftCategory = "app" )
type DriftChange ¶
type DriftChange struct {
Category DriftCategory `json:"category"`
Field string `json:"field,omitzero"`
Old string `json:"old,omitzero"`
New string `json:"new,omitzero"`
Severity DriftSeverity `json:"severity"`
}
DriftChange is one typed configuration change: safe identities only (names, digests, levels), never raw configuration.
type DriftSeverity ¶
type DriftSeverity string
DriftSeverity is the two-tier classification of one configuration change, answering one question: does the change expand what the session can touch? Severity is advisory input to application policy, not authority.
const ( DriftInfo DriftSeverity = "info" DriftWarn DriftSeverity = "warn" )
type EmptyResponseError ¶
type EmptyResponseError struct{}
EmptyResponseError is the TurnFailed.Err cause when a provider returns a successful stream that contains no text or thinking content.
func (EmptyResponseError) Error ¶
func (EmptyResponseError) Error() string
type EphemeralNotPersistableError ¶
type EphemeralNotPersistableError struct{ Type string }
EphemeralNotPersistableError is returned by MarshalEvent when handed any Ephemeral event. The Ephemeral set is never persisted — it self-heals from a later authoritative event and TokenDelta.Chunk has no durable codec — so the marshaler fails closed rather than emit a lossy record. Type is the classify name of the rejected event so a caller learns exactly which event it tried to persist.
func (*EphemeralNotPersistableError) Error ¶
func (e *EphemeralNotPersistableError) Error() string
type Event ¶
type Event interface {
Class() Class
Scope() Scope
EndsTurn() bool // turn-terminal: the last event this turn's per-turn stream carries
EventHeader() Header
Visibility() EventVisibility
// contains filtered or unexported methods
}
Event is the sealed root of every loop event. Every concrete event embeds a Header, exactly one lifecycle mixin (ephemeral, enduring, or terminal), and exactly one scope mixin (sessionScoped or loopScoped). The lifecycle mixin supplies Class()/EndsTurn() and the scope mixin supplies Scope(), so the hub gets its delivery policy and consumers get producer identity without a transport-only envelope or a concrete type switch. Embedding two lifecycle mixins or two scope mixins makes the selectors ambiguous and the type stops satisfying Event, so the "exactly one of each" rule is enforced by the compiler.
func UnmarshalEvent ¶
UnmarshalEvent decodes a durable wire envelope back into a concrete Event. It fails closed on the untrusted restore boundary: input over the byte cap → EventLimitError; malformed envelope → EventDecodeError; an unknown or missing "type" tag → UnknownEventTypeError; a malformed payload for a known type → EventDecodeError. A successfully decoded event is validated against the ID fill matrix (ValidateEvent), so a structurally-valid but semantically-invalid record is rejected rather than resurrected.
type EventDecodeError ¶
EventDecodeError wraps a failure to unmarshal serialized event bytes (malformed JSON, wrong field types) once a known "type" tag has been read.
func (*EventDecodeError) Error ¶
func (e *EventDecodeError) Error() string
func (*EventDecodeError) Unwrap ¶
func (e *EventDecodeError) Unwrap() error
type EventEncodeError ¶
EventEncodeError wraps a failure to marshal an event's payload (a json.Marshal failure, or a delegated content/tool codec failure on the marshal path).
func (*EventEncodeError) Error ¶
func (e *EventEncodeError) Error() string
func (*EventEncodeError) Unwrap ¶
func (e *EventEncodeError) Unwrap() error
type EventFilter ¶
type EventFilter struct {
Ephemeral LoopScope // TokenDelta + tool lifecycle (ToolCallStarted/Completed) delivery
Enduring LoopScope // loop-produced StepDone, gates, terminals
}
EventFilter is a subscriber's declared interest: which loop producers it wants events from, separated by class. It is declared interest (deterministic, evaluated at fan-out before the bounded send), distinct from backpressure drop. An agent's token firehose excluded by Ephemeral never even enters that subscriber's egress buffer.
type EventLimitError ¶
EventLimitError is returned when an event codec input or output exceeds its cap.
func (*EventLimitError) Error ¶
func (e *EventLimitError) Error() string
type EventName ¶
type EventName string
EventName is the concrete event type name an InvalidEventError points at.
type EventVisibility ¶
type EventVisibility uint8
EventVisibility controls whether an event may enter ordinary product streams. Public is deliberately zero so legacy records remain public and byte-stable.
const ( Public EventVisibility = iota Internal )
func (EventVisibility) Valid ¶
func (v EventVisibility) Valid() bool
Valid reports whether visibility belongs to the closed durable domain.
type ExternalToolIdentity ¶
type ExternalToolIdentity struct {
Name string `json:"name"`
SchemaDigest string `json:"schema_digest"`
}
ExternalToolIdentity is the durable, secret-free identity of ONE external tool installed into a loop's external slot. Name is the model-facing tool name; SchemaDigest is the hex SHA-256 of the tool's compacted argument JSON Schema. The schema itself is deliberately NOT recorded: an external schema is attacker- or third-party-supplied and may carry descriptions, defaults, or examples that embed secrets, so only its digest crosses into the journal.
type Factory ¶
type Factory struct {
// contains filtered or unexported fields
}
Factory mints fresh event Headers, stamping each with a new EventID and the current CreatedAt at creation time. It is the single creation seam every Enduring event flows through so the journal sees a stable idempotency key and creation timestamp.
func NewFactory ¶
NewFactory wires the id generator and clock the Factory mints from.
func (*Factory) NewHeader ¶
NewHeader mints a fresh EventID + CreatedAt onto an empty Header. Callers fill Coordinates/Cause. It is Stamp of the zero Header.
func (*Factory) Stamp ¶
Stamp returns a COPY of h with a fresh EventID and CreatedAt, preserving the caller's existing Coordinates and Cause (the producer set those before stamping). A crypto/rand failure from newID is propagated, never swallowed, and no partial Header escapes — the returned Header carries a zero EventID on error.
func (*Factory) StampCompactWaiterRejected ¶
func (f *Factory) StampCompactWaiterRejected(ev CompactWaiterRejected) (Header, error)
StampCompactWaiterRejected stamps CreatedAt while preserving and verifying a rejected waiter's deterministic EventID. It cannot inject an arbitrary ID.
func (*Factory) StampCompactWaiterResolved ¶
func (f *Factory) StampCompactWaiterResolved(ev CompactWaiterResolved) (Header, error)
StampCompactWaiterResolved stamps CreatedAt while preserving and verifying a resolved waiter's deterministic EventID. It cannot inject an arbitrary ID.
func (*Factory) StampWorkflowActivity ¶
func (f *Factory) StampWorkflowActivity(ev WorkflowActivity, deterministicID uuid.UUID) (WorkflowActivity, error)
StampWorkflowActivity stamps a fully specified WorkflowActivity with the caller's stable source activity ID. Unlike Stamp, it never calls newID: retry logic must be able to submit the same journal identity again. An explicit CreatedAt is preserved so a deterministic producer can make the complete journal payload byte-identical across retries; a zero CreatedAt uses the factory clock for ordinary callers. The body is validated after stamping so the returned value is safe to send through the normal Hub/journal path.
type FieldName ¶
type FieldName string
FieldName is the identity/body field an InvalidEventError points at.
const ( FieldEventID FieldName = "EventID" FieldSessionID FieldName = "SessionID" FieldLoopID FieldName = "LoopID" FieldTurnID FieldName = "TurnID" FieldStepID FieldName = "StepID" FieldToolExecutionID FieldName = "ToolExecutionID" FieldConsistency FieldName = "Consistency" FieldTrigger FieldName = "Trigger" FieldCause FieldName = "Cause" FieldCommandID FieldName = "CommandID" FieldRequestID FieldName = "RequestID" FieldActiveLoopID FieldName = "ActiveLoopID" FieldTargetLoopID FieldName = "TargetLoopID" FieldCategory FieldName = "Category" FieldModel FieldName = "Model" FieldModelKey FieldName = "ModelKey" FieldContextLimits FieldName = "ContextLimits" FieldEffort FieldName = "Effort" FieldUsage FieldName = "Usage" FieldMessages FieldName = "Messages" FieldVisibility FieldName = "Visibility" FieldDefinition FieldName = "Definition" FieldRunID FieldName = "RunID" FieldRuntime FieldName = "Runtime" FieldAgentRuntime FieldName = "AgentRuntime" FieldACPSessionID FieldName = "ACPSessionID" FieldDuration FieldName = "Duration" FieldStage FieldName = "Stage" FieldReasonCode FieldName = "ReasonCode" FieldAttemptID FieldName = "AttemptID" FieldReason FieldName = "Reason" FieldRejectReason FieldName = "RejectReason" FieldWaiterCommandIDs FieldName = "WaiterCommandIDs" FieldSummary FieldName = "Summary" FieldRetained FieldName = "Retained" FieldPostContext FieldName = "PostContext" FieldCommittedEventID FieldName = "CommittedEventID" FieldSource FieldName = "Source" FieldActor FieldName = "Actor" FieldGeneration FieldName = "Generation" FieldTools FieldName = "Tools" FieldProcess FieldName = "Process" FieldGateID FieldName = "GateID" FieldClassifier FieldName = "Classifier" FieldClassifierRevision FieldName = "ClassifierRevision" FieldStatus FieldName = "Status" FieldRisk FieldName = "Risk" FieldAuthorization FieldName = "Authorization" FieldCategories FieldName = "Categories" FieldAutoApproved FieldName = "AutoApproved" // FieldIntegrationName names IntegrationStatus.Name. It is not spelled // "FieldName": that identifier is this file's FieldName TYPE. FieldIntegrationName FieldName = "Name" FieldState FieldName = "State" FieldDetail FieldName = "Detail" FieldEpoch FieldName = "Epoch" FieldAdoptedFingerprint FieldName = "AdoptedFingerprint" FieldManifest FieldName = "Manifest" FieldDrift FieldName = "Drift" FieldMessage FieldName = "Message" FieldWorkflowName FieldName = "WorkflowName" FieldWorkflowVersion FieldName = "WorkflowVersion" FieldActivityKind FieldName = "ActivityKind" FieldOccurredAt FieldName = "OccurredAt" FieldVertexID FieldName = "VertexID" FieldVertexLabel FieldName = "VertexLabel" FieldProgress FieldName = "Progress" // FieldType names the whole event (not one coordinate) on the fail-secure // unknown-type path, paired with RuleUnknownType. FieldType FieldName = "Type" )
Identity / body field names, named so an InvalidEventError reads precisely.
type ForeignSessionBound ¶
type ForeignSessionBound struct {
Header
ForeignSID string `json:"foreign_sid"`
// contains filtered or unexported fields
}
ForeignSessionBound records the foreign agent session id for adapters that cannot accept a pre-minted id at LoopStarted time. It is loop-scoped and Enduring because restore needs it to resume the foreign session.
type GateOpened ¶
type GateOpened struct {
Header
Gate gate.Gate `json:"gate,omitzero"`
// contains filtered or unexported fields
}
GateOpened is the PUBLIC activation event for a gate. It carries the pure public envelope (the Gate) and NO private payload — the typed payload stays server-private inside the GatePreparedRecord. It fans out to SSE/history and makes the gate listable/answerable.
type GatePrepared ¶
type GatePrepared struct {
Header
Gate gate.Gate `json:"gate,omitzero"`
// contains filtered or unexported fields
}
GatePrepared is the private/internal prepared projection inside a GatePreparedRecord. It is durable so restore can validate a later GateOpened, but it is NOT fanned out to SSE/history and does not make the gate answerable. It must never be appended through NewEventRecord or hub.PublishEvent; it is only valid inside journal.GatePreparedRecord.
type GateResolved ¶
type GateResolved struct {
Header
GateID gate.ID `json:"gate_id,omitzero"`
// Resolver is the self-contained scope discriminator the DECODER needs to pick
// this record's identity profile. GatePrepared and GateOpened embed the full
// gate.Gate (whose Resolver already names the owner), but GateResolved carries
// only the GateID and coordinates — so without this field a decoded GateResolved
// could not tell a host-owned gate (SessionID required; loop/turn/step optional)
// from a loop-owned one (full step profile). It is additive and omitempty: an old
// record written before this field existed decodes with an empty Resolver and is
// validated under the strict loop-owned profile, matching every gate record that
// could ever restore before this change.
Resolver gate.ResolverKind `json:"resolver,omitempty"`
Reason gate.CloseReason `json:"reason,omitempty"`
Action string `json:"action,omitempty"`
Source gate.ResponseSource `json:"source,omitzero"`
// Audit is a sealed interface (gate.ResponseAudit) with no general JSON codec,
// so it is excluded from direct serialization — like PermissionRequested.Request
// — and projected through gate.MarshalResponseAudit by the marshaler.
Audit gate.ResponseAudit `json:"-"`
// contains filtered or unexported fields
}
GateResolved is the SINGLE atomic close-with-answer record. The decision Action stays in the clear (for a permission gate it is one of the three exact gate.ApprovalAction strings); Reason is the close reason; per-kind Audit is redaction-aware (requirement and candidate descriptions, never grant tokens, never raw tool arguments). A non-answer close (abandon/owner) sets Reason with Action="".
type Header ¶
type Header struct {
// Coordinates is the producer's location in the hierarchy: SessionID on every
// event; LoopID for loop-scoped events; TurnID for turn events; StepID for
// step/tool scoped events. Session-scoped events leave LoopID/TurnID/StepID zero.
identity.Coordinates
// AgentName is the immutable attribution name of the agent driving the producing
// loop, stamped at loop creation onto its LoopStarted (and carried on the loop's
// other events via the same Header). It is empty (omitzero) for a plain loop and for
// any record persisted before AgentName existed, so old journals stay byte-compatible
// — the field serializes additively with no new codec case. Restore validates the
// root loop's stamped name against the configured primary's name.
AgentName identity.AgentName `json:"agent_name,omitzero"`
// EventID identifies this event. Header carries this identity directly; detailed
// wiring is sequenced after the journal follow-on.
EventID uuid.UUID `json:"event_id,omitzero"`
// CreatedAt is when this event was created (minted at creation, not delivery).
// It is the journal's creation timestamp for every Enduring event.
CreatedAt time.Time `json:"created_at,omitzero"`
// Cause is the direct cause of this event. For UserInput/SubagentResult
// resolution events (TurnStarted, TurnFoldedInto, InputCancelled, InputQueued,
// TurnRejected), Cause.CommandID is the submit command id. For an event caused by
// a SubagentResult, Cause.LoopID is the producing agent's loop id (its
// quiescence wake token). Cause.Agency surfaces who caused it, but ONLY the turn-
// resolution events stamp it — TurnStarted, TurnFoldedInto, and InputCancelled
// (per design §444-446); InputQueued and TurnRejected carry Cause.CommandID but
// NOT Cause.Agency.
Cause identity.Cause `json:"cause,omitzero"`
// EventVisibility is omitted for Public so pre-visibility journals remain a
// byte-for-byte fixed point. Internal events always persist the non-zero tag.
EventVisibility EventVisibility `json:"visibility,omitzero"`
}
Header is the producer identity stamped on every event. The producer (the loop for loop events, the session for session events) fills it in; fan-in, filter, and journal consumers read it without a transport-only envelope.
func (Header) EventHeader ¶
EventHeader returns the embedded Header so every event satisfies Event without per-type boilerplate.
func (Header) ReplyTo ¶
ReplyTo returns the id of the command this event answers (its Cause.CommandID). It is promoted onto every Reply event via the embedded Header.
func (Header) Visibility ¶
func (h Header) Visibility() EventVisibility
Visibility returns the event's durable product-delivery classification.
type HustleCompleted ¶
type HustleCompleted struct {
Header
Run HustleRunDescriptor `json:"run"`
Duration time.Duration `json:"duration,omitzero"`
Usage *content.Usage `json:"usage,omitempty"`
// contains filtered or unexported fields
}
HustleCompleted durably records one successful terminal outcome.
type HustleFailed ¶
type HustleFailed struct {
Header
Run HustleRunDescriptor `json:"run"`
Duration time.Duration `json:"duration,omitzero"`
Stage hustle.Stage `json:"stage"`
ReasonCode hustle.ReasonCode `json:"reason_code"`
Usage *content.Usage `json:"usage,omitempty"`
// contains filtered or unexported fields
}
HustleFailed durably records one bounded, security-safe failure outcome.
type HustleRunDescriptor ¶
type HustleRunDescriptor struct {
Definition hustle.DefinitionDescriptor `json:"definition"`
RunID hustle.RunID `json:"run_id"`
Runtime ModelRuntime `json:"runtime,omitzero"`
}
HustleRunDescriptor is the secret-free identity and resolved runtime of one invocation. Started events carry a zero Runtime; terminal events carry the resolved runtime whenever model resolution succeeded.
type HustleStarted ¶
type HustleStarted struct {
Header
Run HustleRunDescriptor `json:"run"`
// contains filtered or unexported fields
}
HustleStarted durably records ownership before scheduler eligibility.
type InputCancelled ¶
type InputCancelled struct {
Header
TurnIndex TurnIndex `json:"turn_index,omitzero"`
Reason CancelReason `json:"reason,omitzero"`
Message *content.UserMessage `json:"message,omitzero"`
// contains filtered or unexported fields
}
InputCancelled is emitted when a queued input leaves the loop queue without committing — client retract, or a return after an abnormal turn end. Header.Cause.CommandID is the submit command id; Header.TurnID is the active turn that caused a return, or zero for a pure client retract outside a turn. Message is the returned/retracted user message.
type InputQueued ¶
type InputQueued struct {
Header
// contains filtered or unexported fields
}
InputQueued is the Ephemeral Reply event for a UserInput accepted into the loop inbox but not yet assigned to a turn (it later resolves to TurnStarted, TurnFoldedInto, or InputCancelled). Header.Cause.CommandID is the submit command id. Ephemeral: it self-heals — the authoritative resolution event still follows if this is dropped.
type IntegrationState ¶
type IntegrationState uint8
IntegrationState is how an integration is doing, in the only granularity a consumer outside it can act on. The zero value is not a state.
It is five values on purpose. An integration's own lifecycle is invariably richer — the MCP client alone distinguishes configured, starting, authenticating, discovering, ready, degraded, reconnecting, failed, closing, and closed — and mirroring that here would be this package learning one integration's model and imposing it on the next. The projection is lossy by design; an integration that needs its full state machine seen renders it from its own status surface, which it still owns.
const ( // IntegrationStarting is an integration coming up. It is not serving yet, // and it is not a fault: everything is starting once. IntegrationStarting IntegrationState = iota + 1 // IntegrationReady is an integration serving normally. IntegrationReady // IntegrationDegraded is an integration still serving, but with less than // it advertised — a lost connection it is retrying, a capability that // failed. It is distinct from Failed because the difference is actionable: // a degraded integration may recover on its own. IntegrationDegraded // IntegrationFailed is an integration not serving. It may still recover. IntegrationFailed // IntegrationClosed is an integration that has shut down. It is terminal // for this Session. IntegrationClosed )
func (IntegrationState) String ¶
func (s IntegrationState) String() string
String returns a stable lowercase identifier, or "unknown".
func (IntegrationState) Valid ¶
func (s IntegrationState) Valid() bool
Valid reports whether s is a declared state. It fails closed on the zero value and on anything outside the set, which is what makes an unset State a rejected event rather than a silently rendered "starting".
type IntegrationStatus ¶
type IntegrationStatus struct {
Header
// Source names the kind of integration, e.g. "mcp". It is the namespace
// Name lives in: two integrations may both call a binding "github".
Source string `json:"source"`
// Name identifies the one integration within Source, e.g. a binding name.
Name string `json:"name"`
// State is how it is doing.
State IntegrationState `json:"state"`
// Detail is bounded, redacted explanatory text, or empty. It is
// diagnostics, never an instruction and never a fact the host should act
// on programmatically — that is what State is for.
Detail string `json:"detail,omitempty"`
// contains filtered or unexported fields
}
IntegrationStatus reports the live state of one external integration.
It is Ephemeral, which is a claim about what it means rather than a convenience: a status self-heals — the next one supersedes it completely, and a consumer that missed three learns the truth from the fourth. It is also the only honest classification available. An integration is a LIVE resource that a restore reconstructs by reconnecting, never by replaying bytes, so a journaled status could only ever describe a connection that no longer exists. Persisting one would invite a restored Session to render a server as "ready" before anything had dialled it.
Every field is safe to journal even though none of it is: Source and Name are the integration's own validated identifiers, State is a closed enum, and Detail is bounded prose the integration has already redacted. Credentials, tokens, URLs with secrets, raw arguments, and server content must never appear in Detail — an integration that cannot say what is wrong without one says less.
type InvalidEventError ¶
InvalidEventError reports that an event violates the ID fill matrix: Field names the offending identity/body field and Rule says whether it was required or had to be zero. It is a typed package-API error so a journal/test can errors.As it to inspect the exact violation rather than parse a string.
func (*InvalidEventError) Error ¶
func (e *InvalidEventError) Error() string
type LegacyRuntimeMigrationError ¶
LegacyRuntimeMigrationError reports a v1 lifecycle payload that cannot be migrated to the current ModelRuntime representation without guessing. Valid pre-runtime LoopInferenceChanged records always carry model; accepting a record with neither model nor runtime would silently erase the selected model.
func (*LegacyRuntimeMigrationError) Error ¶
func (e *LegacyRuntimeMigrationError) Error() string
type LoopAgentSessionBound ¶
type LoopAgentSessionBound struct {
Header
ACPSessionID string `json:"acp_session_id"`
// contains filtered or unexported fields
}
LoopAgentSessionBound records the durable foreign agent session binding for a loop.
type LoopExternalToolsetChanged ¶
type LoopExternalToolsetChanged struct {
Header
Source string `json:"source"`
Generation string `json:"generation"`
Tools []ExternalToolIdentity `json:"tools,omitempty"`
// contains filtered or unexported fields
}
LoopExternalToolsetChanged durably records that a loop's external tool slot for one Source was REPLACED, effective at the loop's next turn boundary. It records identity only (Source + Generation + per-tool name/schema digest) — never the tool factories and never the schemas themselves.
It is deliberately NOT a restore input: external tools are live resources (an MCP connection cannot be rebuilt from journal bytes), so a restored loop comes up with an EMPTY external slot and the composing application re-installs. The event exists for audit, drift detection, and catalog projection — replay folds it for its runtime identity only, never to reconstruct tools.
type LoopIdle ¶
type LoopIdle struct {
Header
// contains filtered or unexported fields
}
LoopIdle is emitted when a loop parks with no active turn. Header.SessionID and Header.LoopID are set; TurnID/StepID are zero. It drives session quiescence.
type LoopInferenceChanged ¶
type LoopInferenceChanged struct {
Header
Runtime ModelRuntime `json:"runtime,omitzero"`
// contains filtered or unexported fields
}
LoopInferenceChanged durably selects the resolved secret-free runtime for this loop at the next turn boundary.
type LoopModeChanged ¶
type LoopModeChanged struct {
Header
PreviousMode string `json:"previous_mode,omitzero"`
Mode string `json:"mode,omitzero"`
Runtime ModelRuntime `json:"runtime,omitzero"`
// contains filtered or unexported fields
}
LoopModeChanged durably selects one predeclared loop mode.
type LoopRestoreTombstoned ¶
type LoopRestoreTombstoned struct {
Header
Category string `json:"category"`
// contains filtered or unexported fields
}
LoopRestoreTombstoned records that a non-root child survived restore as a closed, failed registry entry because its durable runtime could not be re-authorized against the current runtime catalog.
type LoopScope ¶
type LoopScope struct {
All bool // deliver from every loop
Loops map[uuid.UUID]struct{} // when !All, only these loop ids
}
LoopScope selects which loops a class of events is delivered from. All is a short-circuit "every loop"; otherwise only loop ids present in Loops match.
type LoopStarted ¶
type LoopStarted struct {
Header
// Runtime is the initial resolved model identity, limits, and effort. It is
// durable so restore and catalog repair never consult a mutable catalog.
Runtime ModelRuntime `json:"runtime,omitzero"`
AgentRuntime *AgentRuntime `json:"agent_runtime,omitempty"`
// ParentToolUseID is the durable provider tool-use id of the agent tool call
// that spawned this loop (content.ToolUseBlock.ID), empty for loops not spawned by
// a tool call (e.g. the primary/root). It is the durable carrier that correlates a
// child loop back to its parent tool call across persist/restore; omitzero so old
// journal records without the field decode to "".
ParentToolUseID string `json:"parent_tool_use_id,omitzero"`
// ForeignSID is the foreign agent's session id this loop is bound to, for
// foreign-engine loops only; empty for native loops. It is the durable handle
// used to --resume the foreign session across turns and across restore. omitzero
// so old journal records (and native loops) decode to "". Mirrors
// ParentToolUseID: identity metadata carried on the loop's start event.
ForeignSID string `json:"foreign_sid,omitzero"`
// InitialMode is the validated mode selected when the loop was constructed.
// Empty identifies the base mode and preserves legacy records.
InitialMode string `json:"initial_mode,omitzero"`
// InitialRequestID proves the prepared delegate's initial command was accepted
// before this durable loop-creation commit. Zero for roots/plain loops.
InitialRequestID uuid.UUID `json:"initial_request_id,omitzero"`
// DisplayName is the loop's user-facing presentation label, empty when the loop
// declared none (consumers fall back to Header.AgentName). omitzero so old journal
// records decode to "".
DisplayName string `json:"display_name,omitzero"`
// Description is the loop's user-facing description, empty when none declared.
Description string `json:"description,omitzero"`
// contains filtered or unexported fields
}
LoopStarted is published by Session.NewLoop when a loop is registered. Header.Coordinates is the NEW loop (SessionID+LoopID set; TurnID/StepID zero). Header.Cause.Coordinates is the spawning loop/turn/step (zero for the primary = root); Cause.Agency = AgencyMachine. It is the durable loop-tree record for subscribers active at creation time.
type ModelRuntime ¶
type ModelRuntime struct {
Key model.ModelKey `json:"key"`
Limits model.ContextLimits `json:"limits"`
Effort model.Effort `json:"effort,omitzero"`
APIFormat model.APIFormat `json:"api_format,omitzero"`
BaseURL string `json:"base_url,omitzero"`
}
ModelRuntime is the single durable, secret-free description of the model runtime selected for a loop. It deliberately excludes endpoint and catalog data so journal replay and catalog repair do not depend on mutable config.
APIFormat and BaseURL identify the declared transport a selection resolved against at the moment it was accepted. A zero/absent value on either field means "use the definition's declared base transport" — this is both the interpretation for every pre-existing journal record written before these fields existed, and the default for any caller that does not yet populate them. Both fields are unconstrained by validateModelRuntime: they always originate from a model.Model that already passed model.Validate() at the point a live change accepted it, so re-validating them here would be redundant, not additional safety.
type PermissionDecided ¶
type PermissionDecided struct {
Header
ToolExecutionID uuid.UUID `json:"tool_execution_id,omitzero"`
Effect PermissionDecisionEffect `json:"effect,omitempty"`
Reason string `json:"reason,omitempty"`
Subject string `json:"subject,omitempty"`
Audit string `json:"audit,omitempty"`
// contains filtered or unexported fields
}
PermissionDecided is emitted for a non-gated permission decision. Subject and Audit are redacted summaries; grant tokens and raw args must never appear here.
type PermissionDecisionEffect ¶
type PermissionDecisionEffect string
PermissionDecisionEffect is the durable approve/deny outcome for a non-gated permission decision. Ask is intentionally absent: gated asks are represented by GateOpened/GateResolved, not PermissionDecided.
const ( PermissionEffectApprove PermissionDecisionEffect = "approve" PermissionEffectDeny PermissionDecisionEffect = "deny" )
type PermissionRequested ¶
type PermissionRequested struct {
Header
ToolExecutionID uuid.UUID `json:"tool_execution_id,omitzero"`
// Request is validated at the durable boundary (tool.ValidateRequest on
// marshal, the strict gate.DecodeRequest on unmarshal), so a malformed or
// token-bearing record can neither be journaled nor restored. It is
// projected by the marshaler rather than serialized directly.
Request tool.Request `json:"-"`
// contains filtered or unexported fields
}
PermissionRequested is emitted when a tool call needs interactive approval. The per-turn stream (TUI) renders the typed prepared Request — the summary plus the displayed unmet requirement and candidate descriptions. The wire carries the same typed request through the strict request decoder; it never carries grant tokens or raw tool arguments (tool.Request has neither).
type PermissionReviewCompleted ¶
type PermissionReviewCompleted struct {
Header
GateID gate.ID `json:"gate_id,omitzero"`
ToolExecutionID uuid.UUID `json:"tool_execution_id,omitzero"`
Classifier hustle.Name `json:"classifier,omitzero"`
ClassifierRevision string `json:"classifier_revision,omitzero"`
Status gate.ReviewStatus `json:"status,omitzero"`
Risk gate.ReviewRisk `json:"risk,omitzero"`
Authorization gate.ReviewAuthorization `json:"authorization,omitzero"`
Categories []gate.ReviewRiskCategory `json:"categories,omitzero"`
AutoApproved bool `json:"auto_approved,omitzero"`
// contains filtered or unexported fields
}
PermissionReviewCompleted records a classifier's closed terminal audit status. It deliberately excludes prompt, evidence, model output, rationale, gate candidates, and grant material.
type PermissionReviewStarted ¶
type PermissionReviewStarted struct {
Header
GateID gate.ID `json:"gate_id,omitzero"`
ToolExecutionID uuid.UUID `json:"tool_execution_id,omitzero"`
Classifier hustle.Name `json:"classifier,omitzero"`
ClassifierRevision string `json:"classifier_revision,omitzero"`
// contains filtered or unexported fields
}
PermissionReviewStarted records that one classifier began reviewing an already-open permission gate. It carries only stable identity and revision metadata; permission subject data and classifier inputs are deliberately absent from the durable event.
type PressureLevel ¶
type PressureLevel uint8
PressureLevel is the closed current-context pressure domain.
const ( PressureUnknown PressureLevel = iota PressureNormal PressureCompact PressureHardLimit )
type ProcessBackgrounded ¶
type ProcessBackgrounded struct {
Header
Process tool.ProcessLifecycleMetadata `json:"process"`
// contains filtered or unexported fields
}
ProcessBackgrounded durably records the transition from a foreground tool call to a session-owned background process.
type ProcessCompleted ¶
type ProcessCompleted struct {
Header
Process tool.ProcessLifecycleMetadata `json:"process"`
// contains filtered or unexported fields
}
ProcessCompleted durably records one terminal supervised-process outcome.
type ProcessLost ¶
type ProcessLost struct {
Header
Process tool.ProcessLifecycleMetadata `json:"process"`
// contains filtered or unexported fields
}
ProcessLost durably records a previously nonterminal local process that cannot be reattached after restore.
type ProcessStarted ¶
type ProcessStarted struct {
Header
Process tool.ProcessLifecycleMetadata `json:"process"`
// contains filtered or unexported fields
}
ProcessStarted durably records that a supervised process reached running.
type ProcessStopRequested ¶
type ProcessStopRequested struct {
Header
Process tool.ProcessLifecycleMetadata `json:"process"`
// contains filtered or unexported fields
}
ProcessStopRequested durably records a nonterminal portable stop request.
type RejectReason ¶
type RejectReason uint8
RejectReason explains why a UserInput submit was refused (carried by TurnRejected). It is the single source of truth for submit-rejection reasons — the loop publishes it on the event stream; there is no command-side copy (the former command.Disposition reply, with its command.RejectReason, was removed).
const ( // RejectUnspecified is the zero-value sentinel: NOT a reason the loop ever // produces. It exists so a zero-valued TurnRejected{} does not masquerade as a // real reason and so the json:"reason,omitzero" tag only drops a genuinely-empty // reason (every real reason below is non-zero and always serializes). A // RejectReason that compares equal to this came from a zero value, not a decision. RejectUnspecified RejectReason = iota // RejectQueueFull: the loop's inbox is at capacity. RejectQueueFull // RejectShuttingDown: the loop is shutting down and accepts no new input. RejectShuttingDown // RejectInternal: a transient internal failure (e.g. id generation); the loop is // healthy and the caller MAY retry. RejectInternal )
type Reply ¶
type Reply interface {
Event
ReplyTo() uuid.UUID // == Header.Cause.CommandID: the command this answers
// contains filtered or unexported methods
}
Reply is an event that is the direct outcome of a command, delivered on the normal fan-in (classed Ephemeral/Enduring like any other event — NOT a point-to-point channel). It is the typed replacement for the command.Disposition reply: an issuer recognises "the answer to my command" via ReplyTo() == its command id.
type RestoreDone ¶
type RestoreDone struct {
Header
// contains filtered or unexported fields
}
RestoreDone marks a successful end of a session restore from the durable journal — the session is reconstructed and ready to resume. It is session-scoped and Enduring; Header.SessionID is set, LoopID/TurnID/StepID are zero.
type RestoreErrored ¶
type RestoreErrored struct {
Header
// Err is the typed cause of the restore failure; tagged json:"-" because an
// error value has no stable codec (mirrors TurnFailed.Err). Callers inspect it
// in-memory via errors.As; a journal records the failure via the event itself.
Err error `json:"-"`
// contains filtered or unexported fields
}
RestoreErrored marks a failed session restore from the durable journal. Err carries the typed cause; like TurnFailed.Err an error value cannot round-trip through encoding/json, so it is tagged json:"-" — callers read it in-memory via errors.As, and the durable codec's projection lands in a later phase. It is session-scoped and Enduring; Header.SessionID is set, LoopID/TurnID/StepID are zero.
type RestoreStarted ¶
type RestoreStarted struct {
Header
// contains filtered or unexported fields
}
RestoreStarted marks the beginning of a session restore from the durable journal. Like SessionStarted it is session-scoped and Enduring (an authoritative session-lifecycle transition, never a turn-ender); Header.SessionID is set, LoopID/TurnID/StepID are zero.
type RestoredError ¶
RestoredError is the leaf error a restored TurnFailed (or RestoreErrored) carries in place of its original typed cause. A live error value has no stable JSON codec — its concrete type, fields, and wrapping chain cannot round-trip through encoding/json — so the event codec projects TurnFailed.Err to a {kind,message} pair on marshal and reconstructs a *RestoredError on unmarshal. Kind is the stable classification (see ErrKind); Message is the original Error() text, preserved verbatim so no human-readable detail is lost even when the concrete type is not. RestoredError deliberately does not implement ModelFacingError: ordinary and legacy journal records must never become model-facing merely because their message or kind happens to look safe.
func (*RestoredError) Error ¶
func (e *RestoredError) Error() string
Error renders "<kind>: <message>", mirroring how the original typed cause read.
type RestoredModelFacingError ¶
type RestoredModelFacingError struct {
Kind string `json:"kind"`
Message string `json:"message"`
Detail string `json:"-"`
}
RestoredModelFacingError is the distinct restore form for an error that was explicitly marked safe before it crossed the durable event boundary. Detail is kept separate from Kind and Message so the restore path never infers safety from arbitrary legacy error text.
func (*RestoredModelFacingError) Error ¶
func (e *RestoredModelFacingError) Error() string
func (*RestoredModelFacingError) ModelFacingError ¶
func (e *RestoredModelFacingError) ModelFacingError() string
ModelFacingError returns the already-normalized, bounded detail persisted by the event codec. Callers still bound it at their presentation boundary.
type Rule ¶
type Rule string
Rule is the human-readable invariant an InvalidEventError records, so the caller learns WHY the field is wrong (required vs must-be-zero), not just which.
const ( // RuleRequired: the field must be non-zero for this event. RuleRequired Rule = "must be set" // RuleMustBeZero: the field must be zero for this event's scope. RuleMustBeZero Rule = "must be zero" // RuleUnknownType: the event's concrete type is not in the sealed union, so it // fails fail-secure (the journal/restore caller must reject it rather than guess // an identity contract for it). RuleUnknownType Rule = "is not a known event type" // RuleInvalid: the field contains a value outside its closed domain. RuleInvalid Rule = "is invalid" )
type SessionActive ¶
type SessionActive struct {
Header
// contains filtered or unexported fields
}
SessionActive marks the Idle -> Active edge of the session quiescence model.
type SessionIdle ¶
type SessionIdle struct {
Header
// contains filtered or unexported fields
}
SessionIdle marks the Active -> Idle edge of the session quiescence model.
type SessionStarted ¶
type SessionStarted struct {
Header
Config ConfigFingerprint `json:"config,omitzero"`
Manifest ConfigManifest `json:"manifest,omitzero"`
// contains filtered or unexported fields
}
SessionStarted is published when the session's primary loop actor starts. Header.SessionID is set; LoopID/TurnID/StepID are zero. Config is the fingerprint of the agent configuration the session started under (model/system-prompt/tool policy), stamped at construction so a durable journal can detect a config change on restore.
Manifest is the richer, canonical description of the same configuration. During the deprecation window BOTH are populated: Config remains the legacy comparison baseline while Manifest carries the additive fields (workspace trust, permission and confinement strictness, application fields, and per-tool schema identity) that typed drift assessment consumes. It is additive (omitzero): a journal record that predates the field decodes it as the zero ConfigManifest and never spuriously mismatches.
type SessionStopped ¶
type SessionStopped struct {
Header
// contains filtered or unexported fields
}
SessionStopped marks the session phase transition on Shutdown.
type SnapshotConsistency ¶
type SnapshotConsistency uint8
SnapshotConsistency describes whether harness-managed workspace mutations could overlap a snapshot walk. Unknown exists only to decode legacy checkpoint events.
const ( SnapshotConsistencyUnknown SnapshotConsistency = iota SnapshotQuiescent SnapshotFuzzy )
type SnapshotTriggerKind ¶
type SnapshotTriggerKind uint8
SnapshotTriggerKind records the policy boundary that requested a snapshot. Unknown exists only to decode legacy checkpoint events.
const ( SnapshotTriggerKindUnknown SnapshotTriggerKind = iota SnapshotTriggerManual SnapshotTriggerIdle SnapshotTriggerInterrupt SnapshotTriggerTurnDone SnapshotTriggerStepDone SnapshotTriggerSeed )
type StepDone ¶
type StepDone struct {
Header
Messages content.AgenticMessages `json:"messages,omitempty"`
// contains filtered or unexported fields
}
StepDone is the enduring event emitted when a step's finalized group is committed: the step's single AIMessage followed by its ToolResultMessages. It is emitted at the actor-owned commit point, once the commit handshake lands, so it never claims a commit that did not happen — Messages is exactly what entered history.
It is emitted for a TRUNCATED step too. When a stream fails after the model has already delivered content, the loop commits the safe prefix of that response (text and sealed reasoning; never a partial or unpaired tool call) so the content the user watched arrive is not silently discarded. Such a group is a lone AIMessage whose last block is the truncation notice, and the turn still ends on TurnFailed. A consumer that needs to distinguish the two reads the turn terminal; a consumer that only renders history sees the notice. A step that decoded nothing usable commits nothing and emits no StepDone at all.
type StrictnessLevel ¶
type StrictnessLevel uint8
StrictnessLevel is an ordered security posture supplied by the composition root: higher is stricter. Zero means "unknown" — the posture exists only as an opaque digest, so a change cannot be direction-classified and drift assessment fails secure (Warn). Harness compares levels; it never computes them.
type Subscription ¶
Subscription is the consumer-facing handle to a session event fan-in: the read+teardown contract a TUI/CLI (or a future journal) depends on, independent of the concrete subscription implementation. It lives here in the (leaf) event package — the one package every event producer and consumer already imports — so neither side has to depend on the concrete hub type to name the contract (Dependency Inversion). The session hub's *EventSubscription satisfies it structurally.
Events yields the filtered fan-in stream and closes on Close or on a hub-forced loss; Close is the consumer's intentional, idempotent teardown; Err reports the typed termination cause (nil for an intentional Close, the loss error for a hub-forced drop).
type TokenDelta ¶
type TokenDelta struct {
Header
TurnIndex TurnIndex `json:"turn_index,omitzero"`
// Chunk is content.Chunk, a sealed interface that is never serialized (chunks
// have no wire codec — see content.Chunk); TokenDelta is an Ephemeral streaming
// delta that never reaches the journal, so the field is tagged json:"-".
Chunk content.Chunk `json:"-"`
// contains filtered or unexported fields
}
TokenDelta is emitted for each streaming chunk from the LLM. TokenDelta and the ToolCallStarted/ToolCallCompleted events (in tool.go) are the Ephemeral events.
type ToolCallCompleted ¶
type ToolCallCompleted struct {
Header
ToolExecutionID uuid.UUID `json:"tool_execution_id,omitzero"`
IsError bool `json:"is_error,omitzero"`
ResultPreview string `json:"result_preview,omitempty"`
// contains filtered or unexported fields
}
ToolCallCompleted is emitted when a tool finishes. ResultPreview is the capped tool output for the TUI.
type ToolCallStarted ¶
type ToolCallStarted struct {
Header
ToolExecutionID uuid.UUID `json:"tool_execution_id,omitzero"`
ToolName string `json:"tool_name,omitempty"`
Summary string `json:"summary,omitempty"`
// contains filtered or unexported fields
}
ToolCallStarted is emitted when an approved tool begins executing. Summary is capped at construction (never raw args).
type ToolLimitError ¶
ToolLimitError is the TurnFailed.Err cause when the agentic loop's runaway guard fires: the model requested another tool batch after either the per-turn iteration cap (LLM<->tool round-trips) or the total-call cap was exceeded. It is typed and secret-free (it carries only the counts), so it is safe to surface un-redacted in TurnFailed.Err — it never embeds raw messages or tool arguments. Callers may errors.As it to distinguish a runaway stop from a provider/network failure.
func (*ToolLimitError) Error ¶
func (e *ToolLimitError) Error() string
type ToolManifestEntry ¶
type ToolManifestEntry struct {
Name string `json:"name"`
InputSchemaRev string `json:"input_schema_rev,omitzero"`
OutputSchemaRev string `json:"output_schema_rev,omitzero"`
}
ToolManifestEntry is one model-facing tool's stable identity: its name plus content digests of its input and output schemas. Digests, never schemas — the manifest carries identity, not definitions.
type TurnDone ¶
type TurnDone struct {
Header
TurnIndex TurnIndex `json:"turn_index,omitzero"`
// Message is the complete AI response.
Message *content.AIMessage `json:"message,omitzero"`
// Usage is the checked sum of every completed request in this turn. Loop
// cumulative accounting folds StepDone only, so this projection is never
// added a second time.
Usage content.Usage `json:"usage,omitzero"`
// contains filtered or unexported fields
}
TurnDone is the terminal success event for a turn.
type TurnFailed ¶
type TurnFailed struct {
Header
TurnIndex TurnIndex `json:"turn_index,omitzero"`
// Err is the typed cause; an error value cannot round-trip through
// encoding/json (no codec, no stable shape), so it is tagged json:"-" to keep
// the journal output clean rather than emitting garbage. Callers read it
// in-memory via errors.As; a journal records the failure via the event itself.
Err error `json:"-"`
// contains filtered or unexported fields
}
TurnFailed is the terminal event for non-cancellation LLM/provider errors. Err carries the typed cause; callers may errors.As it to inspect and retry.
type TurnFoldedInto ¶
type TurnFoldedInto struct {
Header
TurnIndex TurnIndex `json:"turn_index,omitzero"`
Message *content.UserMessage `json:"message,omitzero"`
// contains filtered or unexported fields
}
TurnFoldedInto is emitted when queued input folds into a mandatory tool-continuation request. Header.Cause.CommandID is the submit command id; Header.Cause.LoopID is set for a SubagentResult hand-back. Message is the folded user message.
type TurnIndex ¶
type TurnIndex int
TurnIndex identifies a turn within one loop. Each loop numbers its own turns from 0; it is not unique across loops in a multi-loop session.
type TurnInterrupted ¶
type TurnInterrupted struct {
Header
TurnIndex TurnIndex `json:"turn_index,omitzero"`
// contains filtered or unexported fields
}
TurnInterrupted is the terminal event when the turn context is cancelled.
type TurnPanicError ¶
type TurnPanicError struct{ Detail string }
TurnPanicError is the TurnFailed.Err cause when the turn goroutine panics. Detail is the recovered value rendered as a string.
func (*TurnPanicError) Error ¶
func (e *TurnPanicError) Error() string
type TurnRejected ¶
type TurnRejected struct {
Header
Reason RejectReason `json:"reason,omitzero"`
// contains filtered or unexported fields
}
TurnRejected is the Enduring Reply event for a UserInput the loop refused (queue-full, shutting-down, or a transient internal failure). Enduring: a rejected user message must never silently vanish. Header.Cause.CommandID is the submit command id.
type TurnStarted ¶
type TurnStarted struct {
Header
TurnIndex TurnIndex `json:"turn_index,omitzero"`
Message *content.UserMessage `json:"message,omitzero"`
// contains filtered or unexported fields
}
TurnStarted is emitted when runLoop commits a turn's initial UserMessage. It is the first enduring turn event. Header.Cause.CommandID is the submit command id. Message is the exact UserMessage committed as the first message of the turn.
type UnknownEventTypeError ¶
type UnknownEventTypeError struct{ Type string }
UnknownEventTypeError is returned by UnmarshalEvent when the envelope's "type" tag names no concrete event (including the empty/missing tag), or by MarshalEvent when a foreign concrete type is handed in (one not in classify's sealed union). The restore path is an untrusted boundary; callers fail secure on this error rather than guess a concrete event to reconstruct.
func (*UnknownEventTypeError) Error ¶
func (e *UnknownEventTypeError) Error() string
type UnknownMessageRoleError ¶
type UnknownMessageRoleError struct{ Role string }
UnknownMessageRoleError is returned by the message-slice decoder when a message's "role" names no concrete Conversation type (including an empty/missing role). The Conversation union is sealed and discriminated by role; an unknown role fails closed rather than guess a concrete message to reconstruct.
func (*UnknownMessageRoleError) Error ¶
func (e *UnknownMessageRoleError) Error() string
type UnsupportedSchemaError ¶
UnsupportedSchemaError reports a durable record whose schema version is newer than this binary supports. It is the dispatch hook a future migration layer catches; today it is a hard, typed restore failure.
func (*UnsupportedSchemaError) Error ¶
func (e *UnsupportedSchemaError) Error() string
type UserInputRequested ¶
type UserInputRequested struct {
Header
ToolExecutionID uuid.UUID `json:"tool_execution_id,omitzero"`
Question string `json:"question,omitempty"`
Choices []string `json:"choices,omitempty"`
// contains filtered or unexported fields
}
UserInputRequested is emitted when a tool (AskUser) needs free-form input. The per-turn stream gets the full Question and Choices for rendering.
type WorkflowActivity ¶
type WorkflowActivity struct {
Header
RunID uuid.UUID `json:"run_id"`
WorkflowName string `json:"workflow_name"`
WorkflowVersion string `json:"workflow_version"`
Kind WorkflowActivityKind `json:"kind"`
Status WorkflowRunStatus `json:"status"`
VertexID uuid.UUID `json:"vertex_id,omitzero"`
VertexLabel string `json:"vertex_label,omitempty"`
CompletedVertices uint32 `json:"completed_vertices,omitzero"`
TotalVertices uint32 `json:"total_vertices,omitzero"`
Message string `json:"message,omitempty"`
OccurredAt time.Time `json:"occurred_at"`
// contains filtered or unexported fields
}
WorkflowActivity is the public, durable session notification for one safe workflow transition. It carries identifiers and bounded display metadata only: no checkpoint state, policy text, model output, document fragment, or metadata map belongs in this event.
The EventID is normally a deterministic source activity ID supplied through Factory.StampWorkflowActivity. That specialized path is the only factory path that preserves an externally derived identity; generic Factory.Stamp continues to mint fresh IDs for ordinary event construction.
type WorkflowActivityKind ¶
type WorkflowActivityKind string
WorkflowActivityKind is the closed lifecycle vocabulary projected from a workflow run. It deliberately describes safe user-facing milestones rather than exposing Flow's internal checkpoint or state shape.
func (WorkflowActivityKind) Valid ¶
func (k WorkflowActivityKind) Valid() bool
Valid reports whether k is one of the durable activity kinds.
type WorkflowRunStatus ¶
type WorkflowRunStatus string
WorkflowRunStatus is the bounded status projection carried by an activity. The failed value is included for workflow-host failures that occur after a durable run exists; Flow itself may represent recoverable execution errors as an interrupted run.
const ( WorkflowRunStatusRunning WorkflowRunStatus = "running" WorkflowRunStatusInterrupted WorkflowRunStatus = "interrupted" WorkflowRunStatusCompleted WorkflowRunStatus = "completed" WorkflowRunStatusCancelled WorkflowRunStatus = "cancelled" WorkflowRunStatusFailed WorkflowRunStatus = "failed" )
func (WorkflowRunStatus) Valid ¶
func (s WorkflowRunStatus) Valid() bool
Valid reports whether s is one of the durable workflow statuses.
type WorkspaceCheckpointed ¶
type WorkspaceCheckpointed struct {
Header
Ref string `json:"ref"`
Consistency SnapshotConsistency `json:"consistency"`
Trigger SnapshotTriggerKind `json:"trigger"`
// contains filtered or unexported fields
}
WorkspaceCheckpointed records that the session's workspace was durably snapshotted as Ref at this point in the event order. It is session-scoped and Enduring — the resume token's pointer to the workspace store; Header.SessionID is set, LoopID/TurnID/StepID are zero. Ref is an opaque "v1:sha256:<hex>" string (typed as workspacestore.Ref at the producer; pkg/event stays dependency-light).
type WorkspaceRestored ¶
type WorkspaceRestored struct {
Header
Ref string `json:"ref"`
// contains filtered or unexported fields
}
WorkspaceRestored records that the live workspace was replaced from Ref and that Ref is now the effective durable restore point.