enno

package module
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: May 8, 2026 License: MIT Imports: 11 Imported by: 0

README

Enno

Enno is a lightweight Go agent framework that can be embedded as a package or installed as a CLI agent.

It provides a provider-agnostic Agent loop, a composable tool system, built-in OpenAI-compatible and Anthropic providers, and optional tools for a persistent task graph (task_create / task_update / task_list / task_get), filesystem access, shell execution, ripgrep-based content search (grep), and ripgrep-based file globbing (glob).

Repository: github.com/dean2021/enno

Features

  • Provider-neutral core package: Agent, Session, RunResult, Provider, Tool, Message, Request, and Response.
  • OpenAI-compatible provider via provider/openai (HTTP retries with backoff for 429/5xx; default retry budget raised above the SDK default for flaky gateways; optional fixed HTTP proxy via config or HTTPProxy field).
  • Anthropic Messages API provider via provider/anthropic (same retry behavior and optional proxy).
  • High-level sdk package for configuring built-in tools without importing their implementations: task graph, filesystem, shell, grep, glob, subagent, load_skill, compact, and allow/deny tool permissions.
  • Optional Agent events for observing model calls, tool calls, results, and token usage.
  • Installable CLI at cmd/enno.
  • Extensible tool and provider interfaces for custom integrations.

Installation

Install the CLI:

go install github.com/dean2021/enno/cmd/enno@latest

Use as a Go package:

go get github.com/dean2021/enno

Install a specific version:

go install github.com/dean2021/enno/cmd/enno@v0.9.0
go get github.com/dean2021/enno@v0.9.0

CLI Usage

Start the bubbletea (Charm) terminal UI interactive mode:

enno

Type a task and press Enter. Use Esc, Ctrl+C, q, or exit to leave the interactive UI. The main Enno window shows a single conversation stream: user prompts, model progress, tool calls, tool arguments, muted tool results, and final answers are appended in order so current activity stays visible. Tab switches focus between the prompt and the transcript; with focus on the transcript, use arrow keys, PgUp/PgDn, Home/End, gg/G, and / or Ctrl+F for jump search; with focus on the prompt, Alt+↑/↓ browses input history and Ctrl+↑/↓ scrolls the transcript. The mouse wheel scrolls the transcript even when the prompt is focused (no need to Tab first); wheel also works while the jump-search overlay is open. Because mouse tracking captures all mouse events, text selection requires Shift+drag in all terminals. It does not display hidden model chain-of-thought.

Run a single prompt:

enno run "Analyze this repository"

Configure the CLI in ~/.enno/config.yaml. A commented reference copy lives at config.yaml.example in this repository. Skills load from ~/.enno/skills by default (merge with optional skills_extra_dirs and --skills-dir). Set subagent: true to register the subagent tool (isolated child agent per delegation). If the default config file does not exist, Enno creates a commented template on startup:

provider: openai
model: your-model
api_key: your-key
base_url: https://example.com/v1
max_tokens: 4096
shell: true
filesystem: true

Use a custom config file:

enno --config /path/to/config.yaml
enno run --config /path/to/config.yaml "Analyze this repository"

Common flags:

enno --workdir .
enno --no-shell
enno --no-filesystem

Provider, model, API key, base URL, and max token settings are read from config.yaml only. The CLI does not read ENNO_* environment variables for these values.

Package Usage

package main

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/dean2021/enno"
	openaiprovider "github.com/dean2021/enno/provider/openai"
	"github.com/dean2021/enno/sdk"
)

func main() {
	provider, err := openaiprovider.New(openaiprovider.Config{
		APIKey:  os.Getenv("ENNO_API_KEY"),
		BaseURL: os.Getenv("ENNO_BASE_URL"),
		Model:   os.Getenv("ENNO_MODEL"),
	})
	if err != nil {
		panic(err)
	}

	agent, err := sdk.NewAgent(sdk.Config{
		Provider:     provider,
		SystemPrompt: "You are a helpful coding agent.",
		BuiltinTools: sdk.BuiltinTools{
			TaskGraph:  &sdk.TaskGraphTool{Root: ".", Timeout: 120 * time.Second},
			Filesystem: &sdk.FilesystemTool{Root: "."},
			Grep:       &sdk.GrepTool{Root: "."},
			Glob:       &sdk.GlobTool{Root: "."},
		},
	})
	if err != nil {
		panic(err)
	}

	session := &enno.Session{}
	result, err := agent.Run(context.Background(), session, "List the files in this workspace.")
	if err != nil {
		panic(err)
	}
	fmt.Println(result.Content)
}

Run returns detailed usage, stop reason, rounds, tool calls, and the messages produced by the run:

result, err := agent.Run(context.Background(), session, "List the files in this workspace.")
if err != nil {
	panic(err)
}
fmt.Printf("answer=%s rounds=%d usage=%+v stop=%s\n",
	result.Content, len(result.Rounds), result.Usage, result.StopReason)

Anthropic Provider

provider, err := anthropicprovider.New(anthropicprovider.Config{
	APIKey:    os.Getenv("ANTHROPIC_API_KEY"),
	Model:     "claude-sonnet-4-5-20250929",
	MaxTokens: 4096,
})
if err != nil {
	panic(err)
}
agent, err := sdk.NewAgent(sdk.Config{
	Provider:     provider,
	SystemPrompt: "You are a helpful agent.",
	BuiltinTools: sdk.BuiltinTools{
		TaskGraph: &sdk.TaskGraphTool{Root: ".", Timeout: 120 * time.Second},
	},
})

Custom Tools

type GreetArgs struct {
	Name string `json:"name"`
}

greet := enno.NewTypedToolFromSchema("greet", "Greet a person by name.", enno.SchemaObject().
	StringProp("name").
	Required("name"), func(ctx context.Context, args GreetArgs) (string, error) {
	return "Hello, " + args.Name + "!", nil
})

Then pass the tool to an agent:

agent, err := sdk.NewAgent(sdk.Config{
	Provider:    provider,
	CustomTools: []enno.Tool{greet},
})

Observability

Attach an optional event handler to observe model calls, tool calls, tool results, and token usage:

agent, err := sdk.NewAgent(sdk.Config{
	Provider: provider,
	EventHandler: func(ctx context.Context, event enno.Event) {
		fmt.Printf("%s round=%d usage=%+v\n", event.Type, event.Round, event.Usage)
	},
})

Events expose observable execution details and model-visible content only. They do not expose hidden model chain-of-thought.

The CLI renders these events directly in the main Enno conversation stream, similar to a coding-agent transcript: model progress, tool calls, parameters, and muted results appear inline as they happen.

Architecture

enno/
  agent.go              core Agent loop
  run_result.go         detailed run result types
  session.go            explicit conversation state
  stream.go             streaming interfaces and events
  request_options.go    provider-neutral request options
  hooks.go              lifecycle control hooks
  policy.go             loop policies
  config.go             Agent configuration
  message.go            provider-neutral messages
  tool.go               tool declaration and execution API
  provider_iface.go     provider interface

  provider/openai       OpenAI-compatible provider
  provider/anthropic    Anthropic provider
  sdk                   high-level SDK assembler and built-in tool config
  internal/builtintools internal built-in tool implementations
  internal/cliui        CLI-only terminal UI
  internal/cliconfig    CLI-only configuration parsing
  internal/history      CLI history recorder and reader
  internal/httpproxy    HTTP proxy client helper
  cmd/enno              installable CLI
  examples              usage examples
  docs                  design and usage documentation

See:

Versioning

Enno follows Semantic Versioning. The current initial release line is v0.x.y while the public API is still evolving.

Useful release commands:

make help
make version
make release-check
make tag

make tag creates a Git tag for the version in VERSION, such as v0.9.0. Pushing the tag triggers the release workflow.

Safety Notes

  • Avoid enabling sdk.ShellTool in production without sandboxing.
  • Always configure sdk.FilesystemTool with a restricted root directory.
  • Do not hard-code API keys in source code.
  • Use separate Session values for independent conversations; create separate Agent instances when you need parallel runs or isolated tool state.

License

Enno is released under the MIT License.

Documentation

Overview

Package enno provides a provider-neutral agent runtime for Go applications.

The core type is Agent. Callers use Agent.Run with an explicit Session and receive a structured RunResult. Optional streaming, events, policies, and hooks provide additional control and observability.

Index

Constants

View Source
const (
	ToolChoiceAuto     = "auto"
	ToolChoiceNone     = "none"
	ToolChoiceRequired = "required"
	ToolChoiceTool     = "tool"
)
View Source
const (
	ResponseFormatText       = "text"
	ResponseFormatJSONObject = "json_object"
	ResponseFormatJSONSchema = "json_schema"
)
View Source
const CompactionToolName = "compact"

CompactionToolName is the registered tool name for manual compaction.

Variables

View Source
var ErrMissingProvider = errors.New("enno: missing provider")
View Source
var ErrNilSession = errors.New("enno: nil session")
View Source
var ErrUnsupportedOption = errors.New("enno: unsupported request option")

Functions

func FormatCompactSummary added in v0.5.0

func FormatCompactSummary(raw string) string

FormatCompactSummary strips <analysis>, extracts <summary> body when present, and normalizes whitespace. If no <summary> tag is found, returns the whole string trimmed (backward compatible).

func ToolMap

func ToolMap(tools []Tool) map[string]Tool

Types

type AfterProviderCallResult added in v0.7.0

type AfterProviderCallResult struct {
	Response *Response
	Abort    error
}

type AfterProviderCallState added in v0.7.0

type AfterProviderCallState struct {
	Round    int
	Request  Request
	Response Response
}

type AfterToolCallResult added in v0.7.0

type AfterToolCallResult struct {
	ToolResult *ToolResult
	Abort      error
}

type AfterToolCallState added in v0.7.0

type AfterToolCallState struct {
	Round      int
	Request    Request
	ToolCall   ToolCall
	ToolResult ToolResult
	Err        error
}

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

func NewAgent

func NewAgent(config Config) (*Agent, error)

func (*Agent) Run

func (a *Agent) Run(ctx context.Context, session *Session, input string) (RunResult, error)

func (*Agent) RunStream added in v0.7.0

func (a *Agent) RunStream(ctx context.Context, session *Session, input string, handler StreamHandler) (RunResult, error)

type BeforeProviderCallResult added in v0.7.0

type BeforeProviderCallResult struct {
	Request *Request
	Abort   error
}

type BeforeProviderCallState added in v0.7.0

type BeforeProviderCallState struct {
	Round   int
	Request Request
}

type BeforeToolCallResult added in v0.7.0

type BeforeToolCallResult struct {
	ToolCall    *ToolCall
	Deny        bool
	DenyMessage string
	Abort       error
}

type BeforeToolCallState added in v0.7.0

type BeforeToolCallState struct {
	Round    int
	Request  Request
	ToolCall ToolCall
}

type CompactionConfig added in v0.5.0

type CompactionConfig struct {
	Enabled bool

	// TranscriptDir stores JSONL transcripts before summarization. Empty uses
	// ~/.enno/transcripts when Enabled (after withDefaults).
	TranscriptDir string

	// ModelContextTokens, when positive, sets auto-compact threshold to
	// ModelContextTokens - AutoCompactBufferTokens (buffer defaults to 13000 in withDefaults).
	// Takes precedence over AutoCompactInputTokens when the difference is positive.
	ModelContextTokens int64

	// AutoCompactBufferTokens is subtracted from ModelContextTokens for the effective threshold.
	// Ignored when ModelContextTokens is zero. Zero defaults to 13000 when ModelContextTokens > 0.
	AutoCompactBufferTokens int64

	// AutoCompactInputTokens triggers summarization when estimated/conservative input tokens
	// meet or exceed this value. Zero defaults to 50000 when ModelContextTokens is zero.
	AutoCompactInputTokens int64

	// KeepRecentToolResults is how many latest eligible RoleTool messages keep full content in Micro.
	// Zero defaults to 3.
	KeepRecentToolResults int

	// MicroCompactMinChars replaces longer RoleTool contents with a placeholder. Zero defaults to 100.
	MicroCompactMinChars int

	// MicroCompactToolNames, when non-empty, limits Micro compaction to tool results whose tool name
	// is in this list. Empty means all RoleTool messages participate (legacy behavior).
	MicroCompactToolNames []string

	// SkipOnSummarizeError, when true, causes automatic compaction to log an error event and continue
	// without replacing history if summarization fails. Manual compact via the compact tool remains strict.
	SkipOnSummarizeError bool
}

CompactionConfig enables optional context compaction (micro-trimming of old tool results, automatic summarization when estimated input size exceeds a threshold, and the manual compact tool). Nil in Config means compaction is disabled.

type Config

type Config struct {
	Provider     Provider
	SystemPrompt string
	Tools        []Tool
	// MaxToolRounds caps provider/model iterations for one Run (each round may invoke tools).
	// Zero or negative means unlimited (default), matching Claude Code interactive sessions where maxTurns is unset.
	// Set a positive value to bound tool loops.
	MaxToolRounds int
	EventHandler  EventHandler
	Compaction    *CompactionConfig
	Options       RequestOptions
	Policies      []Policy
	Hooks         []Hook
}

type Event added in v0.3.0

type Event struct {
	Type         EventType
	Round        int
	MessageCount int
	ToolCount    int
	Content      string
	Thinking     string
	ToolCall     ToolCall
	ToolResult   string
	ToolError    bool
	ToolMetadata map[string]any
	Usage        Usage
	Duration     time.Duration
	Err          error
}

type EventHandler added in v0.3.0

type EventHandler func(context.Context, Event)

type EventType added in v0.3.0

type EventType string
const (
	EventModelStart    EventType = "model_start"
	EventModelResponse EventType = "model_response"
	EventToolStart     EventType = "tool_start"
	EventToolResult    EventType = "tool_result"
	EventRoundComplete EventType = "round_complete"
	EventError         EventType = "error"
)

type Message

type Message struct {
	Role       Role
	Content    string
	ToolCallID string
	ToolCalls  []ToolCall
}

func AssistantMessage

func AssistantMessage(content string, toolCalls []ToolCall) Message

func ToolMessage

func ToolMessage(toolCallID, content string) Message

func UserMessage

func UserMessage(content string) Message

type NoopHook added in v0.7.0

type NoopHook struct{}

func (NoopHook) AfterProviderCall added in v0.7.0

func (NoopHook) AfterToolCall added in v0.7.0

func (NoopHook) BeforeProviderCall added in v0.7.0

func (NoopHook) BeforeToolCall added in v0.7.0

type Policy added in v0.7.0

type Policy interface {
	BeforeModel(context.Context, *RunState) error
	AfterModel(context.Context, *RunState) error
	AfterTools(context.Context, *RunState) error
}

type Provider

type Provider interface {
	Complete(ctx context.Context, req Request) (Response, error)
}

type Request

type Request struct {
	SystemPrompt string
	Messages     []Message
	Tools        []Tool
	Options      RequestOptions
}

type RequestOptions added in v0.7.0

type RequestOptions struct {
	Temperature     *float64
	MaxOutputTokens int64
	ToolChoice      ToolChoice
	ResponseFormat  ResponseFormat
	Metadata        map[string]string
}

func (RequestOptions) IsZero added in v0.7.0

func (o RequestOptions) IsZero() bool

func (RequestOptions) WithDefaults added in v0.7.0

func (o RequestOptions) WithDefaults(defaults RequestOptions) RequestOptions

type Response

type Response struct {
	Content   string
	Thinking  string
	ToolCalls []ToolCall
	Usage     Usage
}

func ConsumeStream added in v0.7.0

func ConsumeStream(ctx context.Context, stream Stream, handler StreamHandler) (Response, error)

type ResponseFormat added in v0.7.0

type ResponseFormat struct {
	Type        string
	Name        string
	Description string
	Schema      map[string]any
	Strict      *bool
}

type Role

type Role string
const (
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
	RoleTool      Role = "tool"
)

type RoundResult added in v0.7.0

type RoundResult struct {
	Round      int
	ModelUsage Usage
	Assistant  Message
	ToolCalls  []ToolCallResult
	Duration   time.Duration
}

type RunResult added in v0.7.0

type RunResult struct {
	Content    string
	Messages   []Message
	Usage      Usage
	Rounds     []RoundResult
	StopReason StopReason
	Duration   time.Duration
}

type RunStartPolicy added in v0.7.0

type RunStartPolicy interface {
	OnRunStart()
}

type RunState added in v0.7.0

type RunState struct {
	Round int

	Session *Session
	Request Request

	Response   Response
	ModelUsage Usage

	ToolCallResults   []ToolCallResult
	SkipToolExecution bool
}

type Schema added in v0.7.0

type Schema struct {
	// contains filtered or unexported fields
}

func SchemaObject added in v0.7.0

func SchemaObject() *Schema

func (*Schema) BooleanProp added in v0.7.0

func (s *Schema) BooleanProp(name string) *Schema

func (*Schema) EnumProp added in v0.7.0

func (s *Schema) EnumProp(name string, values ...string) *Schema

func (*Schema) IntegerProp added in v0.7.0

func (s *Schema) IntegerProp(name string) *Schema

func (*Schema) Properties added in v0.7.0

func (s *Schema) Properties() map[string]any

func (*Schema) Required added in v0.7.0

func (s *Schema) Required(names ...string) *Schema

func (*Schema) RequiredFields added in v0.7.0

func (s *Schema) RequiredFields() []string

func (*Schema) StringProp added in v0.7.0

func (s *Schema) StringProp(name string) *Schema

type Session added in v0.7.0

type Session struct {
	Messages []Message
	// contains filtered or unexported fields
}

func (*Session) Append added in v0.7.0

func (s *Session) Append(message Message)

func (Session) Clone added in v0.7.0

func (s Session) Clone() Session

func (*Session) Reset added in v0.7.0

func (s *Session) Reset()

type StopReason added in v0.7.0

type StopReason string
const (
	StopReasonEndTurn       StopReason = "end_turn"
	StopReasonMaxToolRounds StopReason = "max_tool_rounds"
	StopReasonError         StopReason = "error"
	StopReasonCanceled      StopReason = "canceled"
)

type Stream added in v0.7.0

type Stream interface {
	Next(context.Context) (StreamEvent, error)
	Close() error
}

func NewResponseStream added in v0.7.0

func NewResponseStream(response Response) Stream

type StreamEvent added in v0.7.0

type StreamEvent struct {
	Type     StreamEventType
	Text     string
	Thinking string
	ToolCall ToolCall
	Response Response
	Usage    Usage
	Err      error
}

type StreamEventType added in v0.7.0

type StreamEventType string
const (
	StreamEventTextDelta     StreamEventType = "text_delta"
	StreamEventThinkingDelta StreamEventType = "thinking_delta"
	StreamEventToolCallDelta StreamEventType = "tool_call_delta"
	StreamEventFinalResponse StreamEventType = "final_response"
	StreamEventUsage         StreamEventType = "usage"
)

type StreamHandler added in v0.7.0

type StreamHandler func(context.Context, StreamEvent)

type StreamProvider added in v0.7.0

type StreamProvider interface {
	Stream(context.Context, Request) (Stream, error)
}

type StructuredToolHandler added in v0.7.0

type StructuredToolHandler func(context.Context, json.RawMessage) (ToolResult, error)

type Tool

type Tool struct {
	Name              string
	Description       string
	Properties        map[string]any
	Required          []string
	Handler           ToolHandler
	StructuredHandler StructuredToolHandler
}

func NewStructuredTool added in v0.7.0

func NewStructuredTool(name, description string, properties map[string]any, required []string, handler StructuredToolHandler) Tool

func NewTool

func NewTool(name, description string, properties map[string]any, required []string, handler ToolHandler) Tool

func NewTypedTool

func NewTypedTool[T any](name, description string, properties map[string]any, required []string, handler func(context.Context, T) (string, error)) Tool

func NewTypedToolFromSchema added in v0.7.0

func NewTypedToolFromSchema[T any](name, description string, schema *Schema, handler func(context.Context, T) (string, error)) Tool

type ToolCall

type ToolCall struct {
	ID        string
	Name      string
	Arguments json.RawMessage
}

type ToolCallResult added in v0.7.0

type ToolCallResult struct {
	Call     ToolCall
	Result   string
	Error    bool
	Metadata map[string]any
	Err      error
	Duration time.Duration
}

type ToolChoice added in v0.7.0

type ToolChoice struct {
	Type string
	Name string
}

type ToolHandler

type ToolHandler func(context.Context, json.RawMessage) (string, error)

type ToolResult added in v0.7.0

type ToolResult struct {
	Content  string
	Error    bool
	Metadata map[string]any
}

type Usage added in v0.3.0

type Usage struct {
	InputTokens  int64
	OutputTokens int64
	TotalTokens  int64
	Estimated    bool
}

func EstimateUsage added in v0.3.0

func EstimateUsage(req Request) Usage

Directories

Path Synopsis
cmd
enno command
examples
anthropic command
custom_tool command
loadskill command
sdk_walkthrough command
simple_agent command
subagent command
internal
builtintools/glob
Package glob provides a read-only file listing tool backed by ripgrep (rg --files).
Package glob provides a read-only file listing tool backed by ripgrep (rg --files).
builtintools/grep
Package grep provides a read-only content search tool backed by ripgrep (rg).
Package grep provides a read-only content search tool backed by ripgrep (rg).
builtintools/taskgraph
Package taskgraph provides persistent DAG task tools (task_create / task_update / task_list / task_get) backed by JSON files under a configurable directory, aligned with s07-style task systems.
Package taskgraph provides persistent DAG task tools (task_create / task_update / task_list / task_get) backed by JSON files under a configurable directory, aligned with s07-style task systems.
anthropic
Package anthropic adapts Anthropic Messages APIs to enno.Provider.
Package anthropic adapts Anthropic Messages APIs to enno.Provider.
openai
Package openai adapts OpenAI-compatible Chat Completions APIs to enno.Provider.
Package openai adapts OpenAI-compatible Chat Completions APIs to enno.Provider.

Jump to

Keyboard shortcuts

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