Documentation
¶
Overview ¶
Package trellis is a deterministic state machine engine (DFA) designed for building robust conversational agents, CLIs, and automation workflows.
It implements a "Reentrant DFA with Controlled Side-Effects" architecture, separating the narrative graph (Logic) from the execution state (Context) and side-effects (Tools).
Concept ¶
Trellis treats your application flow as a graph of nodes. The engine manages the state transitions, data binding, and persistence, while your application ("Host") manages the I/O and external tool execution. This Hexagonal Architecture allows Trellis to be embedded in any interface: CLI, HTTP Server, or AI Agent infrastructure.
Key Features ¶
- Deterministic Execution: Given the same state and input, the transition is always reproducible.
- Hexagonal Architecture: Core logic is decoupled from adapters (Storage, UI, Tools).
- State Persistence: Built-in support for long-running sessions ("Durable Execution").
- Strict Contracts: Validates graph integrity and data types to prevent runtime surprises.
Usage ¶
Initialize the engine using the "Start" entrypoint. You can use the default filesystem loader (Loam) or inject a custom one.
package main
import (
"context"
"log"
"github.com/aretw0/trellis"
)
func main() {
// Initialize Engine with default settings (reads from ./my-flow)
eng, err := trellis.New("./my-flow")
if err != nil {
log.Fatal(err)
}
// Start a new session
ctx := context.Background()
state, err := eng.Start(ctx, "session-123", nil)
if err != nil {
log.Fatal(err)
}
// Main Loop: Render -> Input -> Navigate
for {
// 1. Render View (What to show/do?)
actions, terminal, valErr := eng.Render(ctx, state)
if valErr != nil {
log.Printf("Error: %v", valErr)
break
}
// Handle Actions (Print text, Call tools...)
for _, act := range actions {
log.Println("Action:", act)
}
if terminal {
log.Println("End of flow.")
break
}
// 2. Navigate (Next Step)
// In a real app, this input comes from User or Tool Result
state, err = eng.Navigate(ctx, state, "user input")
if err != nil {
log.Fatal(err)
}
}
}
Index ¶
- Variables
- type Engine
- func (e *Engine) Inspect() ([]domain.Node, error)
- func (e *Engine) Loader() ports.GraphLoader
- func (e *Engine) Navigate(ctx context.Context, state *domain.State, input any) (*domain.State, error)
- func (e *Engine) Render(ctx context.Context, state *domain.State) ([]domain.ActionRequest, bool, error)
- func (e *Engine) Signal(ctx context.Context, state *domain.State, signalName string) (*domain.State, error)
- func (e *Engine) Start(ctx context.Context, sessionID string, initialContext map[string]any) (*domain.State, error)
- func (e *Engine) Watch(ctx context.Context) (<-chan string, error)
- type Option
- func WithConditionEvaluator(eval runtime.ConditionEvaluator) Option
- func WithDefaultErrorNode(nodeID string) Option
- func WithEntryNode(nodeID string) Option
- func WithInterpolator(interp runtime.Interpolator) Option
- func WithLifecycleHooks(hooks domain.LifecycleHooks) Option
- func WithLoader(l ports.GraphLoader) Option
- func WithLogger(logger *slog.Logger) Option
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var Version string
Functions ¶
This section is empty.
Types ¶
type Engine ¶
type Engine struct {
Name string
// contains filtered or unexported fields
}
Engine is the high-level entry point for the Trellis library. It wraps the internal runtime and provides a simplified API for consumers.
func New ¶
New initializes a new Trellis Engine. By default, it uses a Loam repository at the given path. If WithLoader option is provided, repoPath can be empty and Loam is skipped.
Example (Library) ¶
ExampleNew_library demonstrates how to use Trellis purely as a Go library, injecting an in-memory graph without reading from the filesystem.
package main
import (
"context"
"fmt"
"log"
"github.com/aretw0/trellis"
"github.com/aretw0/trellis/pkg/adapters/memory"
"github.com/aretw0/trellis/pkg/domain"
)
func main() {
// 1. Define your graph using pure Go structs
loader, err := memory.NewFromNodes(
domain.Node{
ID: "start",
Type: "text",
Content: []byte("Hello from Memory!"),
Transitions: []domain.Transition{
{ToNodeID: "finish"},
},
},
domain.Node{
ID: "finish",
Type: "text",
Content: []byte("Goodbye."),
},
)
if err != nil {
log.Fatal(err)
}
// 2. Initialize the Engine with the custom loader
// No file path needed ("") because we are providing a loader.
eng, err := trellis.New("", trellis.WithLoader(loader))
if err != nil {
log.Fatal(err)
}
// 3. Start a session
ctx := context.Background()
state, err := eng.Start(ctx, "session-mem", nil)
if err != nil {
log.Fatal(err)
}
// 4. Run the loop (simplified for example)
for {
// Render current state
actions, terminal, _ := eng.Render(ctx, state)
// Print content
for _, act := range actions {
if act.Type == domain.ActionRenderContent {
fmt.Println(act.Payload)
}
}
if terminal {
break
}
// Move to next state
state, err = eng.Navigate(ctx, state, nil)
if err != nil {
log.Fatal(err)
}
}
}
Output: Hello from Memory! Goodbye.
Example (Memory) ¶
ExampleNew_memory demonstrates how to use the Engine with an in-memory graph definition. This is useful for testing, embedded scenarios, or when you don't want to rely on the file system.
package main
import (
"context"
"fmt"
"log"
"github.com/aretw0/trellis"
"github.com/aretw0/trellis/pkg/adapters/memory"
"github.com/aretw0/trellis/pkg/domain"
)
func main() {
// 1. Define your graph using helper NewFromNodes for clean, type-safe construction.
loader, err := memory.NewFromNodes(
domain.Node{
ID: "start",
Type: "question",
Content: []byte("Hello! Do you want to proceed? [yes] [no]"),
Transitions: []domain.Transition{
{ToNodeID: "yes", Condition: "input == 'yes'"},
{ToNodeID: "no"},
},
},
domain.Node{
ID: "yes",
Type: "text",
Content: []byte("Great! You moved forward."),
},
domain.Node{
ID: "no",
Type: "text",
Content: []byte("Okay, bye."),
},
)
if err != nil {
log.Fatal(err)
}
// 2. Initialize Trellis with the custom loader
// Note: We leave path empty ("") because we are providing a loader.
engine, err := trellis.New("", trellis.WithLoader(loader))
if err != nil {
log.Fatal(err)
}
// 4. Start the flow
ctx := context.Background()
state, err := engine.Start(ctx, "test", nil)
if err != nil {
panic(err)
}
// 5. Navigate (Input: "yes")
// "start" -> (input: yes) -> "yes"
// Note: Example previously captured actions from Step.
// Render to show we can get actions, then Navigate.
actions, _, err := engine.Render(ctx, state)
if err != nil {
log.Fatal(err)
}
// Verify actions from start node (optional in example, but good for completeness)
nextState, err := engine.Navigate(ctx, state, "yes")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Current Node: %s\n", nextState.CurrentNodeID)
for _, action := range actions {
fmt.Printf("Action: %s\n", action.Type)
}
}
Output: Current Node: yes Action: RENDER_CONTENT Action: REQUEST_INPUT
func (*Engine) Inspect ¶
Inspect returns the full graph definition for visualization or introspection tools.
func (*Engine) Loader ¶ added in v0.3.3
func (e *Engine) Loader() ports.GraphLoader
Loader returns the underlying GraphLoader used by the engine.
func (*Engine) Navigate ¶ added in v0.3.2
func (e *Engine) Navigate(ctx context.Context, state *domain.State, input any) (*domain.State, error)
Navigate determines the next state based on input.
func (*Engine) Render ¶ added in v0.3.2
func (e *Engine) Render(ctx context.Context, state *domain.State) ([]domain.ActionRequest, bool, error)
Render generates the actions (view) for the current state without transitioning. Returns actions, isTerminal (true if no transitions), and error.
func (*Engine) Signal ¶ added in v0.5.2
func (e *Engine) Signal(ctx context.Context, state *domain.State, signalName string) (*domain.State, error)
Signal triggers a state transition based on a global signal (e.g. interrupt).
type Option ¶
type Option func(*Engine)
Option defines a functional option for configuring the Engine.
func WithConditionEvaluator ¶
func WithConditionEvaluator(eval runtime.ConditionEvaluator) Option
WithConditionEvaluator sets a custom logic evaluator for the engine.
func WithDefaultErrorNode ¶ added in v0.7.0
WithDefaultErrorNode sets a global fallback node for tool errors.
func WithEntryNode ¶ added in v0.7.12
WithEntryNode configures the initial node ID (default: "start").
func WithInterpolator ¶ added in v0.5.0
func WithInterpolator(interp runtime.Interpolator) Option
WithInterpolator sets a custom interpolator for the engine.
func WithLifecycleHooks ¶ added in v0.5.1
func WithLifecycleHooks(hooks domain.LifecycleHooks) Option
WithLifecycleHooks registers observability hooks.
func WithLoader ¶ added in v0.3.1
func WithLoader(l ports.GraphLoader) Option
WithLoader injects a custom GraphLoader, bypassing the default Loam initialization.
func WithLogger ¶ added in v0.5.3
WithLogger sets a custom structured logger for the engine.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
trellis
command
|
|
|
examples
|
|
|
compensation-durable
command
|
|
|
compensation-manual
command
|
|
|
compensation-native
command
|
|
|
hello-world
command
|
|
|
low-level-api
command
|
|
|
manual-security
command
|
|
|
observability-introspection
command
|
|
|
process-demo/scripts
command
|
|
|
structured-logging
command
|
|
|
internal
|
|
|
pkg
|
|
|
adapters/http
Package http provides primitives to interact with the openapi HTTP API.
|
Package http provides primitives to interact with the openapi HTTP API. |
|
domain
Package domain contains the core domain models and business logic for the Trellis engine.
|
Package domain contains the core domain models and business logic for the Trellis engine. |
|
dsl
Package dsl provides a Go DSL (Domain Specific Language) for programmatically constructing Trellis graphs.
|
Package dsl provides a Go DSL (Domain Specific Language) for programmatically constructing Trellis graphs. |
|
observability
Package observability provides tools for monitoring and introspecting the Trellis engine.
|
Package observability provides tools for monitoring and introspecting the Trellis engine. |
|
ports
Package ports defines the driven ports (interfaces) for the Trellis engine.
|
Package ports defines the driven ports (interfaces) for the Trellis engine. |
|
runner
Package runner implements the execution loop and I/O orchestration for the Trellis engine.
|
Package runner implements the execution loop and I/O orchestration for the Trellis engine. |
|
schema
Package schema provides a type-safe validation system for structured data.
|
Package schema provides a type-safe validation system for structured data. |
|
session
Package session implements session management and persistence orchestration.
|
Package session implements session management and persistence orchestration. |
|
fixtures/resilience/crashy
command
|
|
|
fixtures/resilience/good_citizen
command
|