Documentation
¶
Overview ¶
Package engine implements the v1 workflow engine core: procedure specs parsed from kind: procedure graph entries, a typed variable store per running instance, guards as boolean combinations of named Go predicates, a closed function registry (predicates, queries, commands), gate-transition cascade with chooser stops, and append-only JSONL session persistence.
Everything semantic lives in named Go functions composed by name in the procedure spec — the spec language has no expressions, no literals, no assignment. See the v1 surface spec (plan 20260702-220449-d-tac-ry0) and the engine directive (20260702-174833-d-cpt-3yw).
Index ¶
- Constants
- func ComposeInstructions(unitText string, diagnostics []string) string
- func IsEndTarget(to string) bool
- type BaseType
- type ChooserKind
- type ChooserOption
- type ChooserServe
- type CollectField
- type Command
- type CommandFunc
- type Context
- type Dispatch
- type Engine
- type Event
- type EventSink
- type EventType
- type ExportedValue
- type FailedPredicate
- type FuncClass
- type FuncDoc
- type GuardExpr
- type InjectCall
- type Instance
- type InstanceStatus
- type Option
- type Predicate
- type PredicateFunc
- type Provenance
- type Query
- type QueryFunc
- type ReadDepth
- type Ref
- type Registry
- func (r *Registry) Command(name string) (*Command, bool)
- func (r *Registry) Docs(class FuncClass) []FuncDoc
- func (r *Registry) Predicate(name string) (*Predicate, bool)
- func (r *Registry) Query(name string) (*Query, bool)
- func (r *Registry) RegisterCommand(c Command) error
- func (r *Registry) RegisterPredicate(p Predicate) error
- func (r *Registry) RegisterQuery(q Query) error
- type Serve
- type Session
- func (s *Session) Abandon(instanceID, reason string) error
- func (s *Session) Answer(instanceID, chooser, choice string, fields map[string]any, userWords string) (*Serve, error)
- func (s *Session) Instance(id string) (*Instance, bool)
- func (s *Session) Instances() []*Instance
- func (s *Session) LogRead(tool string, full, summary []string)
- func (s *Session) Park(instanceID, note string) error
- func (s *Session) ReadDepthOf(id string) ReadDepth
- func (s *Session) Report(instanceID string, fields map[string]any) (*Serve, error)
- func (s *Session) Serve(instanceID string) (*Serve, error)
- func (s *Session) SetLabel(label string)
- func (s *Session) Start(spec *Spec, params map[string]any, parent string) (*Serve, error)
- type SessionOption
- type Spec
- type SpecResolver
- type Step
- type Store
- func (s *Store) Export() map[string]ExportedValue
- func (s *Store) Get(name string) (any, bool)
- func (s *Store) Has(name string) bool
- func (s *Store) SetParams(params map[string]any) error
- func (s *Store) SetStart(inputs map[string]any) error
- func (s *Store) StateSnapshot() string
- func (s *Store) TemplateContext() map[string]any
- func (s *Store) WriteEngine(name string, v any)
- func (s *Store) WriteState(fields map[string]any) ([]string, error)
- type Transition
- type VarDecl
- type VarType
- type WriterSink
Constants ¶
const ( EndCompleted = "end(completed)" EndAbandoned = "end(abandoned)" )
Terminal transition targets. A procedure ends by transitioning to one of these instead of a step ID.
const LogVersion = 1
LogVersion stamps every event line. A session generally does not survive an sdd upgrade mid-flight — replay rejects unknown versions rather than guessing (accepted per the surface spec). Exported for shells that append terminal events to a parked log without replaying it (teardown by handle).
Variables ¶
This section is empty.
Functions ¶
func ComposeInstructions ¶
ComposeInstructions joins a rendered unit (or a shell-substituted reminder) with stall diagnostics — the one composition rule for the Instructions field, shared by the engine's serve and shells recomposing around served-instruction memory.
func IsEndTarget ¶
IsEndTarget reports whether a transition target is a terminal rather than a step ID.
Types ¶
type BaseType ¶
type BaseType string
BaseType is one of the closed set of domain types a procedure variable can declare. Adding a domain type means adding it here, once, with its validation — no other types exist.
const ( TypeText BaseType = "text" TypeBool BaseType = "bool" TypeEntryID BaseType = "entry-id" TypeRef BaseType = "ref" TypeLabel BaseType = "label" TypeParticipant BaseType = "participant" TypeEntryKind BaseType = "entry-kind" TypeLayer BaseType = "layer" TypeConfidence BaseType = "confidence" TypeIntent BaseType = "intent" TypeAttachmentHandle BaseType = "attachment-handle" TypePreflightFindings BaseType = "preflight-findings" )
type ChooserKind ¶
type ChooserKind string
ChooserKind classifies who advances a step: the engine (gate, auto-advance on guards), the agent (advisory pick, logged with evidence), or the user (dialogue-presence made structural).
const ( ChooserGate ChooserKind = "gate" ChooserAgent ChooserKind = "agent" ChooserUser ChooserKind = "user" )
type ChooserOption ¶
type ChooserOption struct {
Choice string
Collect []CollectField
}
ChooserOption is one selectable choice in a served chooser.
type ChooserServe ¶
type ChooserServe struct {
Chooser string
Kind ChooserKind
Options []ChooserOption
}
ChooserServe is the pending-chooser part of a serve: which step's chooser is pending, who answers, and what the options are. Chooser is the step ID the caller must name in a chooser answer — carried here so the value the answer requires appears in the payload it answers.
type CollectField ¶
CollectField names a state field a report may write at a step; Optional fields don't gate the cascade.
type CommandFunc ¶
CommandFunc executes a side effect (gate ops and chooser calls only). Its store writes go through Store.WriteEngine per the declared contract.
type Context ¶
type Context struct {
Store *Store
Graph *model.Graph
// Step is the step the function runs at — commands like confirmPlayback
// record it (the reopen target when a confirmation goes stale).
Step string
// Reads is the session's folded read set — the deepest depth each entry
// was served at (refsInspected evaluates it). Nil means nothing was
// served; reading a nil map is safe.
Reads map[string]ReadDepth
}
Context is what registry functions see: the running instance's store and the graph, read-only by convention for predicates and queries. Commands additionally reach their side-effect dependencies through the closures they were registered with — the registry itself stays dependency-free.
type Dispatch ¶
type Dispatch struct {
// Procedure optionally names the child canonical this option dispatches.
// When set, it guards the handoff — the seed applies only to a child of
// that procedure. Omit it for a generic junction whose child is chosen at
// runtime (e.g. engage's move), where the seed applies to whatever is
// dispatched next.
Procedure string
// Seed maps child field ← parent field: the key is the field the child
// declares as state, the value is this parent's own declared field carrying
// the evidence. Required — a Dispatch with no seed has nothing to carry.
Seed map[string]string
}
Dispatch is a junction option's declaration of the grounding it seeds into a child dispatched after this option is answered. The seeding contract (d-tac-tlo) keeps the declaration entirely in the parent's spec: answering an option carrying a Dispatch stashes its Seed on the parent instance, and the next child started under that parent inherits exactly that mapping. There is no engine-wide default — a handoff happens only where a junction declares one, so every inheritance is readable at the junction that grants it.
type Engine ¶
Engine executes procedure instances against a graph and a registry. It is pure Go over data — shells (MCP, webapp) sit on top; side-effectful commands come in through the registry with their own dependencies.
func (*Engine) NewSession ¶
func (e *Engine) NewSession(id, participant string, sink EventSink, opts ...SessionOption) *Session
NewSession creates an empty session appending to sink.
func (*Engine) ReplaySession ¶
func (e *Engine) ReplaySession(id, participant string, events []Event, resolve SpecResolver, sink EventSink, opts ...SessionOption) (*Session, error)
ReplaySession reconstructs a session by folding its event log: state is applied directly from the logged values — reports, op results, and transitions — never by re-running commands, so replay is free of side effects. The returned session continues appending to sink.
type Event ¶
type Event struct {
V int `json:"v"`
TS time.Time `json:"ts"`
Session string `json:"session"`
Seq int `json:"seq"`
Instance string `json:"instance,omitempty"`
Event EventType `json:"event"`
Data map[string]any `json:"data,omitempty"`
}
Event is one line of the append-only session log. Memory is the runtime source of truth; the log is the persistence, the session protocol, and the forensic record — transition reports are the trajectory evidence.
type EventSink ¶
EventSink receives events as they happen. The shell wires it to an append-only JSONL file; tests use an in-memory sink.
type EventType ¶
type EventType string
EventType is one of the closed set of session-log events.
const ( // EventSessionMeta is the session-level header line (no instance): // participant identity, so a log is self-describing for list_sessions // descriptors without resolving any procedure spec. EventSessionMeta EventType = "session_meta" // EventLabeled carries a human-meaningful session label (no instance): // the dialogue's subject, agent-supplied and updatable as it sharpens. // Last one wins on fold and replay. EventLabeled EventType = "labeled" EventStarted EventType = "started" EventReport EventType = "report" EventChooserAnswer EventType = "chooser_answer" EventOpResult EventType = "op_result" EventServed EventType = "served" EventTransition EventType = "transition" EventCompleted EventType = "completed" EventAbandoned EventType = "abandoned" // EventRead records what a read surface served to the session: the tool, // the entry IDs, and the depth — metadata only, never payloads. Reads stay // free and ungated; tracking is logging, not gating (d-tac-dbk). No // instance — the read set is session-level, so evidence inspected by a // dispatching parent counts for its seeded children. EventRead EventType = "read" // EventParked records that a running move was deliberately parked back to // the session junction — state kept, resumable through next. Forensic // only: a resuming agent can tell a shelved move from one that drifted. EventParked EventType = "parked" )
type ExportedValue ¶
type ExportedValue struct {
Value any `json:"value"`
Provenance Provenance `json:"provenance"`
}
ExportedValue is the persisted form of one store value.
type FailedPredicate ¶
FailedPredicate names a predicate that held a gate, with its registered failure message — the message becomes the served instruction.
type FuncDoc ¶
FuncDoc is a registry function's documented contract: what it reads from and writes into the store. YAML never maps fields — it only names functions; these contracts are what spec authors consult (registryList, the MCP registry tool) and what load-time collision checks enforce.
type GuardExpr ¶
type GuardExpr struct {
// contains filtered or unexported fields
}
GuardExpr is a parsed guard expression.
func ParseGuard ¶
ParseGuard parses a guard expression. Whitespace (including newlines from folded YAML scalars) separates tokens.
func (*GuardExpr) Eval ¶
Eval evaluates the expression against a predicate evaluator. Predicates are pure, so full evaluation (no short-circuit) is safe and keeps stall diagnostics complete.
func (*GuardExpr) Predicates ¶
Predicates returns the distinct predicate names the expression references, in first-appearance order. Used by spec validation (every name must exist in the registry) and by stall diagnostics.
type InjectCall ¶
InjectCall is a query op the engine runs before serving a step's unit. Args are literals or Go templates rendered against the store.
type Instance ¶
type Instance struct {
ID string
Spec *Spec
Store *Store
Step string
Status InstanceStatus
// Outcome is the end(...) label the instance finished with.
Outcome string
// Parent is the spawning instance's ID for sub-procedures, empty at the
// top level.
Parent string
// contains filtered or unexported fields
}
Instance is one running procedure — a spec, its typed store, and a step position. Instances belong to a session; sub-procedures carry a parent link.
type InstanceStatus ¶
type InstanceStatus string
InstanceStatus is a procedure instance's lifecycle state.
const ( StatusRunning InstanceStatus = "running" StatusCompleted InstanceStatus = "completed" StatusAbandoned InstanceStatus = "abandoned" )
type Option ¶
type Option struct {
Choice string
Call string // command run on selection, per its Go contract
Collect []CollectField
To string
// Dispatch declares the grounding this junction option seeds into a child
// dispatched after it is answered (child field ← parent field, named in the
// parent's own spec). Nil means this option hands nothing down.
Dispatch *Dispatch
}
Option is one choice on an agent or user chooser step.
type Predicate ¶
type Predicate struct {
Doc FuncDoc
Fn PredicateFunc
FailMessage string
// FailDetail, when set, renders the failure message against the live
// context — for gates whose rejection must name the offending values
// (refsInspected naming the un-inspected IDs). Empty results fall back
// to FailMessage.
FailDetail func(ctx *Context) string
}
Predicate is a registered predicate with its contract and failure text — the message served as instruction when the predicate stalls a gate.
type PredicateFunc ¶
PredicateFunc is a pure check over the store (and graph): no side effects, deterministic for a given state.
type Provenance ¶
type Provenance string
Provenance classifies who wrote a store value. The split carries the trust property from the surface spec: reports can only write declared state fields; params are fixed at start; everything trust-bearing (created IDs, findings, confirmation records) is engine-written via ops and chooser calls, structurally out of a report's reach.
const ( ProvenanceParam Provenance = "param" ProvenanceState Provenance = "state" ProvenanceEngine Provenance = "engine" )
type QueryFunc ¶
QueryFunc serves data for dynamic injection: pure read, result enters the template context (not the store).
type ReadDepth ¶
type ReadDepth string
ReadDepth classifies how deeply an entry was served: full means the body was served (a show primary, an injected chain's primary, or a same-session write); summary covers chain bullets, search headers, and snippets.
type Ref ¶
type Ref struct {
ID string `json:"id"`
Kind string `json:"kind"`
Desc string `json:"desc,omitempty"`
}
Ref is the engine-side value of a ref-typed variable: the same closed-kind reference shape entries carry, before it becomes a model.Ref at the write gate.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the closed set of Go functions a procedure spec may name. All semantics live here, composed by name in YAML — the spec language itself carries no logic. Enumerable so spec authors and lint can check every referenced name.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns a registry pre-populated with the built-in predicates (largely the mechanical pre-flight checks re-exposed — single path, no duplicated validation). Queries and commands with side-effect dependencies are registered by the shell that owns those dependencies.
func (*Registry) Docs ¶
Docs enumerates registered function contracts, optionally filtered by class, sorted by name — the registryList query's data.
func (*Registry) RegisterCommand ¶
RegisterCommand adds a command. Names are unique across all classes.
func (*Registry) RegisterPredicate ¶
RegisterPredicate adds a predicate. Names are unique across all classes.
func (*Registry) RegisterQuery ¶
RegisterQuery adds a query. Names are unique across all classes.
type Serve ¶
type Serve struct {
Instance string
Procedure string
Status InstanceStatus
Outcome string
Step string
// Goal is a one-line statement of what advances the instance from here.
Goal string
// Instructions is the step's instruction unit rendered against the
// store, with stall diagnostics appended when a gate is held.
Instructions string
// Unit names the instruction unit rendered into Instructions (empty when
// the step has none). Shells key their served-instruction memory on it.
Unit string
// UnitText is the rendered unit alone, without diagnostics — what a
// shell replaces with a one-line reminder once the unit was served.
UnitText string
// Diagnostics are the stall messages appended to Instructions ("Gate
// held" lines), kept separate so shells can recompose around UnitText.
Diagnostics []string
// Missing names the current step's required collect fields not yet in
// the store.
Missing []string
// Failing lists predicates currently holding the gate.
Failing []FailedPredicate
// ReportSchema is the JSON Schema for the current step's report.
ReportSchema map[string]any
// Chooser is set when the step awaits an agent or user choice.
Chooser *ChooserServe
// Produced carries the engine-written results on completion (e.g. the
// created entry ID), excluding internal trust machinery.
Produced map[string]any
}
Serve is what the engine returns after every advance — the current step's rendered instructions, the report schema, chooser material when one is pending, and stall diagnostics naming exactly what's missing.
type Session ¶
type Session struct {
ID string
Participant string
// Label is the session's human-meaningful subject line — what a user
// picks a parked dialogue by. Agent-supplied via SetLabel, updatable;
// empty until supplied.
Label string
// contains filtered or unexported fields
}
Session is one dialogue session: N procedure instances interleaved serially, one append-only event log. Per-participant; the shell owns file placement and lifecycle.
func (*Session) Abandon ¶
Abandon explicitly discards a running instance, logged as an abandonment transition. It never cleans up implicitly — anything the instance holds (a WIP marker, staged attachments) is left standing for resume or groom.
func (*Session) Answer ¶
func (s *Session) Answer(instanceID, chooser, choice string, fields map[string]any, userWords string) (*Serve, error)
Answer resolves the pending chooser at the instance's current step: the named chooser must be the current step (no early, late, or double answers), the choice must be one of its options, and any carried fields are limited to the option's collect list. User answers carry the user's words verbatim for the auditable-relay record.
func (*Session) LogRead ¶
LogRead records what a read surface served to the session: entry IDs at full depth (body served) and summary depth (headers, chain bullets, snippets), attributed to the serving tool. Every call appends a read event — re-reads are part of the forensic record — and folds into the session's read set, where full depth never downgrades. Empty calls append nothing.
func (*Session) Park ¶
Park records that a running move was deliberately parked back to the session junction: state kept, position kept, resumable through the normal advance path. The event is forensic — nothing about the instance changes — but it makes the shelving auditable and legible to a resuming agent.
func (*Session) ReadDepthOf ¶
ReadDepthOf returns the deepest depth the entry was served to this session at (empty when never served).
func (*Session) Report ¶
Report applies state fields from a transition report, re-evaluates, and cascades. Reports can only write declared state fields; batched fields for later steps are accepted. A report does not answer a pending chooser.
func (*Session) Serve ¶
Serve re-serves the instance's current position without advancing it — shells use it to rehydrate an agent after resume_session.
type SessionOption ¶
type SessionOption func(*Session)
SessionOption configures a session.
func WithClock ¶
func WithClock(now func() time.Time) SessionOption
WithClock injects the timestamp source (tests use a fixed clock).
type Spec ¶
type Spec struct {
// EntryID is the graph entry the spec was loaded from.
EntryID string
// Canonical is the procedure's stable identity (capture, engage, …).
Canonical string
// Class is the procedure's execution role: a move is started through the
// loop, a shell is a session base auto-started by the session door.
Class model.ProcedureClass
// Params are the typed inputs accepted at start_procedure.
Params map[string]VarDecl
// State is the report-writable variable store declaration. Engine-
// written values are deliberately not declared — they enter the store
// per the Go contracts of the functions the spec names, and collisions
// with these declarations are rejected at load.
State map[string]VarDecl
// Steps is the state graph, in declaration order. The first step is the
// entry step.
Steps []*Step
// StepByID indexes Steps.
StepByID map[string]*Step
// Units are the body's instruction units by name (`## unit: <name>`
// sections), rendered as Go templates against the store at serve time.
Units map[string]string
}
Spec is the typed, validated form of a procedure entry's state machine — what the engine executes. Parsed from the raw YAML the model retains on kind: procedure entries (structural validation happens here, at engine load time, per the type-system revision contract) plus the body's per-step instruction units.
func LoadSpec ¶
LoadSpec parses and fully validates a procedure entry against the registry — the one-call path the engine and lint use.
func ParseSpec ¶
ParseSpec parses a procedure entry into a typed Spec: variable declarations, the step graph with parsed guards, and the body's instruction units. Registry-independent validation happens here (types, step wiring, guard syntax, collect declarations); function existence and write-collision checks need the registry and run in Validate.
func (*Spec) AnswerSchemaForStep ¶
AnswerSchemaForStep generates the JSON Schema for a chooser step's answer: the chooser to name (its step id), the choice to pick, the user's verbatim words for a user chooser, and the option-collected fields nested under `fields`. A flat report schema at a chooser step misled callers into placing collected fields at the top level (rejected); this envelope makes the required nesting explicit. `fields` unions every option's collect fields — each option enforces its own required set at answer time, so the envelope marks none of them required.
func (*Spec) ReportSchemaForStep ¶
ReportSchemaForStep generates the JSON Schema for a step's report from the spec's variable declarations: the step's collect fields are the named, described properties (required unless marked optional), and every other declared state field is accepted too — reports may batch fields for later steps, and the cascade rule makes the one-shot full draft as fast as today. The same desc that feeds instruction text feeds the schema.
type SpecResolver ¶
SpecResolver resolves a procedure canonical to its loaded spec — replay uses it to rebind logged instances to their procedure definitions.
type Step ¶
type Step struct {
ID string
Collect []CollectField
Inject []InjectCall
Render string
Chooser ChooserKind
Options []Option
Guard *GuardExpr
Op string
Transitions []Transition
// Goal overrides the serve's generic per-chooser goal line for this
// step. Built for resident steps whose standing posture the generic
// wording would misstate (a session shell's junction is "dialogue
// freely", not "put the choice to the user").
Goal string
}
Step is one node of the state graph.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the typed variable store of one procedure instance. Declared params and state come from the spec; engine-written values enter through command contracts (their Writes set) and are validated against declaration collisions at spec load time, not here.
func NewStore ¶
NewStore creates an empty store bound to the spec that declares its params and state fields.
func (*Store) Export ¶
func (s *Store) Export() map[string]ExportedValue
Export returns all values with provenance for session-log persistence.
func (*Store) Has ¶
Has reports whether the field is present and non-empty. Empty strings, empty lists, and nil don't count as present — a presence predicate asking hasBody wants substance, not a key.
func (*Store) SetParams ¶
SetParams validates and writes the start-time params. Required params must be present; unknown params are rejected.
func (*Store) SetStart ¶
SetStart applies the start-time input map, the shared seeding primitive (d-tac-tlo): a key naming a declared param sets that param (fixed at start), a key naming a declared state field seeds it — a start-time state write, equivalent to an immediate report, so an entry gate reading it is satisfied on entry. Move dispatch and direct start share this: a caller passing an anchor a procedure declares as state seeds the resolver, and the parent handoff seeds the same fields from the parent's store. Unknown keys are rejected.
func (*Store) StateSnapshot ¶
StateSnapshot returns a deterministic fingerprint over the report-writable state values. confirmPlayback binds a confirmation to this fingerprint; playbackConfirmed compares it — any state edit after confirmation changes the snapshot and reopens playback.
func (*Store) TemplateContext ¶
TemplateContext exposes every store value by name for instruction-unit and inject-arg template rendering.
func (*Store) WriteEngine ¶
WriteEngine writes an engine-produced value (an op result or chooser-call effect) per the producing function's Go contract. Never reachable from a report.
type Transition ¶
Transition is one ordered {when, to} entry; the final entry may be an otherwise fallback (When nil).
type VarDecl ¶
VarDecl declares one typed variable. The desc seeds both the generated report schema and the "provide the following" instruction text — one declaration, both surfaces.
type VarType ¶
VarType is a declared variable type: a base domain type, optionally wrapped as list<T>.
func ParseVarType ¶
ParseVarType parses a type string from a variable declaration: a base domain type name or list<T> around one.
type WriterSink ¶
type WriterSink struct {
// contains filtered or unexported fields
}
WriterSink appends events as JSON lines to an io.Writer.
func NewWriterSink ¶
func NewWriterSink(w io.Writer) *WriterSink
NewWriterSink wraps a writer as an EventSink emitting one JSON line per event.
func (*WriterSink) Append ¶
func (s *WriterSink) Append(e Event) error
Append writes the event as one JSON line.