trellis

package module
v0.7.17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 6, 2026 License: AGPL-3.0 Imports: 11 Imported by: 2

README

Trellis

Go Report Card Go Doc License Release

The Neuro-Symbolic Backbone for Agents & Automation.

Trellis é um motor determinístico de fluxos para orquestrar ferramentas, scripts e guardrails com previsibilidade. Use como framework de CLI/Markdown ou como biblioteca Go dentro do seu backend.

Hybrid Nature: Use como Framework (CLI + Markdown) para prototipagem rápida, ou como Biblioteca (Go) para controle total em seu backend. "Opinionated by default, flexible under the hood."

Onde brilha

  • CLIs e Ops: fluxos guiados, com validação e auditabilidade.
  • Automação de ferramentas: scripts e APIs encadeados como um grafo versionável.
  • Agentes com governança: guardrails reais, sem depender apenas de prompt.
  • Backends duráveis: execuções longas com pause/resume e padrões SAGA.

O Conceito Neuro-Simbólico

O Trellis preenche a lacuna entre a Rigidez dos Processos e a Flexibilidade da Inteligência.

O decisor (seja IA ou Humano) escolhe qual caminho tomar, mas o Trellis garante que ele existe e é válido.

💡 Para entender a filosofia completa e o posicionamento estratégico, leia PRODUCT.md.
Para a arquitetura hexagonal e definição formal (DFA), veja TECHNICAL.md.

Como modelar um fluxo

Você define um Grafo de Estados com Nós (Passos) e Transições (Regras), e o Trellis gerencia a navegação.

Você pode definir esse grafo de duas formas:

1. Declarativo (Arquivos)

Ideal para prototipagem, visualização (Mermaid) e edição por LLMs. Suporta Markdown (Frontmatter), YAML ou JSON via Loam.

# start.yaml
type: question
content: Olá! Qual é o seu nome?
save_to: user_name  # Data Binding automático
to: greeting        # Transição incondicional
2. Programático (Go Structs)

Ideal para integração profunda em backends, performance crítica e type-safety total.

&domain.Node{
    ID: "start",
    Type: "question",
    Content: []byte("Olá! Qual é o seu nome?"),
    SaveTo: "user_name",
    Transitions: []domain.Transition{{ToNodeID: "greeting"}},
}

Nota: Ambas as formas geram a mesma estrutura em memória e podem co-existir (ex: carregar arquivos e injetar nós via código).

Funcionalidades Principais

Para produto e UX

  • Data Binding & Contexto: Capture inputs (save_to) e use variáveis ({{ .name }}) nativamente.
  • Namespaces (Sub-Grafos): Organize fluxos complexos em pastas e módulos (jump_to).
  • MCP Server: Integração nativa com Model Context Protocol para conectar Agentes de IA.

Para engenharia

  • Strict Typing: Garante que seus fluxos sejam robustos e livres de erros de digitação.
  • Embeddable & Agnostic: Use como CLI, Lib ou Service. O Core é desacoplado de IO e Persistência.
  • Native SAGA Support: Orquestração de transações distribuídas com undo e rollback automático.

Quick Start

Instalação
Windows (Recomendado)

A forma mais fácil de instalar no Windows é via Scoop:

# 1. Adicione o bucket (apenas a primeira vez)
scoop bucket add aretw0 https://github.com/aretw0/scoop-bucket

# 2. Instale o Trellis
scoop install trellis
macOS / Linux

Instale via Homebrew:

brew install aretw0/tap/trellis
Via Go (Library Mode)

Para usar o Trellis como biblioteca dentro do seu backend (sem arquivos, puramente em memória):

go get github.com/aretw0/trellis
// Exemplo: Instanciando o Engine sem ler arquivos
loader, _ := memory.NewFromNodes(myNodes...)
eng, _ := trellis.New("", trellis.WithLoader(loader))
Rodando o Golden Path (Demo)
# Execução do Engine (Demo)
trellis run ./examples/tour

Se você estiver desenvolvendo dentro do repo, veja a seção "Modo de Desenvolvimento" abaixo.

Usage

Rodando um Fluxo (CLI)
# Modo Interativo (Terminal)
trellis run ./examples/tour

# Modo HTTP Server (Stateless API)
trellis serve --dir ./examples/tour --port 8080
# Swagger UI disponível em: http://localhost:8080/swagger

# Modo MCP Server (Para Agentes de IA)
trellis mcp --dir ./examples/tour

Detalhes e variações em:

Introspecção

Visualize seu fluxo como um grafo Mermaid:

trellis graph ./my-flow
# Saída: graph TD ...
Para quem está contribuindo

Usando Makefile (Recomendado):

make gen    # Gera código Go a partir da spec OpenAPI
make serve  # Roda servidor com exemplo 'tour'
make test   # Roda testes

Executar direto do repo (Go):

go run ./cmd/trellis run ./examples/tour
go run ./cmd/trellis serve --dir ./examples/tour --port 8080
go run ./cmd/trellis mcp --dir ./examples/tour

Hot Reload Manual: Itere mais rápido observando mudanças de arquivo:

trellis run --watch --dir ./my-flow

O engine monitorará seus arquivos .md, .json, .yaml. Ao salvar, a sessão recarrega automaticamente (preservando o loop de execução).

Documentação

Mais em docs/.

Estrutura

trellis/
├── cmd/           # Entrypoints (trellis CLI)
├── docs/          # Documentação do Projeto
├── examples/      # Demos e Receitas (Tours, Patterns)
├── internal/      # Implementação Privada (Runtime, TUI)
├── pkg/           # Contratos Públicos (Facade, Domain, Ports, Adapters)
└── tests/         # Testes de Integração (Certification Suite)

Licença

AGPL-3.0

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

Examples

Constants

This section is empty.

Variables

View Source
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

func New(repoPath string, opts ...Option) (*Engine, error)

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

func (e *Engine) Inspect() ([]domain.Node, error)

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).

func (*Engine) Start

func (e *Engine) Start(ctx context.Context, sessionID string, initialContext map[string]any) (*domain.State, error)

Start creates the initial state for the flow and triggers lifecycle hooks.

func (*Engine) Watch added in v0.3.2

func (e *Engine) Watch(ctx context.Context) (<-chan string, error)

Watch returns a channel that signals when the underlying graph changes. Returns error if the loader does not support watching.

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

func WithDefaultErrorNode(nodeID string) Option

WithDefaultErrorNode sets a global fallback node for tool errors.

func WithEntryNode added in v0.7.12

func WithEntryNode(nodeID string) Option

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

func WithLogger(logger *slog.Logger) Option

WithLogger sets a custom structured logger for the engine.

Directories

Path Synopsis
cmd
trellis command
examples
hello-world command
low-level-api command
manual-security command
internal
cli
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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL