glue

package module
v1.13.0 Latest Latest
Warning

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

Go to latest
Published: Jun 9, 2026 License: MIT Imports: 16 Imported by: 0

README

glue

CI

Glue is a Go framework for building agents. It gives you a reusable, provider-agnostic agent loop, a small code-first Agent / Session API, typed tools, pluggable model providers, and optional persistence — so you can build anything from a one-shot CLI to a long-running, multi-channel assistant without rewriting the loop each time.

Inspired by Flue and pi-mono.

agent := glue.NewAgent(glue.AgentOptions{
	Provider: gemini.New(gemini.Options{}),
	Model:    "gemini-3.1-pro-preview",
	Tools:    []glue.Tool{weatherTool},
})
session, _ := agent.Session(ctx, "demo")
result, _ := session.Prompt(ctx, "What's the weather in Toronto?")
fmt.Println(result.Text)

Install

go get github.com/erain/glue

Module path: github.com/erain/glue. Key subpackages:

Import Purpose
github.com/erain/glue Public API: Agent, Session, Tool, options.
.../loop The provider-agnostic agent loop.
.../providers/{gemini,codex,nvidia,openrouter} Model providers (+ shared openaicompat core, driver registry in providers).
.../stores/{file,sqlite} Session persistence (sqlite adds FTS5 search).
.../tools/{fs,git,shell,coding,mcp} Reusable tool bundles.
.../prompts Versioned-prompt catalog.
.../cli Shared standard flags for agent binaries.

Quickstart

Pick a provider and send a prompt. With Gemini:

export GEMINI_API_KEY=...
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/erain/glue"
	"github.com/erain/glue/providers/gemini"
)

func main() {
	ctx := context.Background()
	agent := glue.NewAgent(glue.AgentOptions{
		Provider: gemini.New(gemini.Options{}),
		Model:    "gemini-3.1-pro-preview",
	})
	session, err := agent.Session(ctx, "demo")
	if err != nil {
		log.Fatal(err)
	}
	result, err := session.Prompt(ctx, "Reply with the single word: glue.")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(result.Text)
}

The session keeps an in-memory transcript, so a second Prompt continues the conversation. Other providers are one import away — see Providers. To go from here to a real tool-calling, persistent agent, follow docs/building-agents.md.

Concepts

Glue has a small vocabulary. Once these click, the rest is API surface.

Type What it is
Provider A model backend that streams assistant events.
Agent The configured unit: provider, model, tools, store, work dir, roles. Built with glue.NewAgent.
Session A named conversation with its own transcript, opened from an Agent; driven with session.Prompt.
Tool A function the model can call. Define with glue.NewTool[Args].
Store Where transcripts persist (stores/file or stores/sqlite). Optional.
Skill / Role Markdown-driven reusable instructions and named instruction profiles.
loop The engine: stream → run tools → append results → repeat until the model stops.

Every session.Prompt runs the same loop:

prompt ─▶ provider streams events ─▶ text? emit deltas
                                  └─▶ tool calls? run tools, append results, loop
                                  └─▶ stop ─▶ return final text

The loop is provider-agnostic, and product concerns (sandboxing, channels, scheduling, policy) enter only as interfaces you fill in — they are not baked into core glue (ADR-0005).

Build your own agent

The full walkthrough — typed tools, persistence, streaming, project context, subagents, structured output, multi-provider failover, packaging as a CLI, and testing — lives in one place:

➡️ docs/building-agents.md

The shortest complete example is examples/local-agent (~100 lines: provider + store + a typed local_time tool + streaming). Real agents live under agents/.

The sections below are a feature reference for when you need the specifics.

Providers

Glue ships four providers and a driver-style registry. Construct one directly, or select by name via providers.New.

Provider Import Auth Notes
Gemini providers/gemini GEMINI_API_KEY Google genai SDK.
Codex providers/codex ChatGPT subscription (codex login) No per-token bill; reuses the upstream Codex CLI's auth.json.
NVIDIA build providers/nvidia NVIDIA_API_KEY OpenAI-compatible; Kimi K2, Llama, Qwen, etc. by org/name.
OpenRouter providers/openrouter OPENROUTER_API_KEY OpenAI-compatible aggregator; openrouter/free auto-picks a free model.
// Codex — bill against your ChatGPT subscription instead of an API key:
agent := glue.NewAgent(glue.AgentOptions{
	Provider: codex.New(codex.Options{}),
	Model:    codex.DefaultModel, // "gpt-5-codex"
})

Codex quarantines all subscription-auth fragility (OAuth, token refresh, Cloudflare cookies) to its package — run codex login once; Glue reads ~/.codex/auth.json (override with $GLUE_CODEX_AUTH / $CODEX_HOME). Subscription-auth via third-party tools is not formally documented by OpenAI; the provider is intended for personal use. See ADR-0006.

NVIDIA and OpenRouter share the providers/openaicompat core. Both can have multi-second first-byte latency on cold routing. To add your own provider, see docs/provider-guide.md and examples/echo-provider.

Failover across providers

glue.WithFailover(provs...) tries providers in order until one accepts the stream — handy when a CLI supports several backends and should skip those whose keys aren't set:

import (
	"github.com/erain/glue"
	"github.com/erain/glue/providers"
	_ "github.com/erain/glue/providers/codex"
	_ "github.com/erain/glue/providers/gemini"
	_ "github.com/erain/glue/providers/nvidia"
)

var provs []glue.Provider
for _, name := range []string{"codex", "nvidia", "gemini"} {
	if p, _, _, err := providers.New(name); err == nil {
		provs = append(provs, p)
	}
}
agent := glue.NewAgent(glue.AgentOptions{Provider: glue.WithFailover(provs...)})

Failover only falls through before the first event commits to the consumer; once a non-error event arrives it stays on that provider for the turn. All-providers-failed surfaces as a typed *glue.FailoverError.

Tools

Define typed tools with glue.NewTool[Args]. It decodes ToolCall.Arguments into your Go type before the executor runs and turns malformed arguments into a model-visible error result instead of a panic. Pair it with glue.TextResult / glue.ErrorResult:

type weatherArgs struct {
	City string `json:"city"`
}

weather := glue.NewTool[weatherArgs](
	glue.ToolSpec{
		Name:        "weather",
		Description: "Look up current weather for a city.",
		Parameters:  json.RawMessage(`{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}`),
	},
	func(ctx context.Context, a weatherArgs) (glue.ToolResult, error) {
		report, err := lookup(ctx, a.City)
		if err != nil {
			return glue.ErrorResult(err), nil
		}
		return glue.TextResult(report), nil
	},
)

Return ErrorResult for recoverable failures (the model can retry); a Go error only for failures that should stop the run. Schema generation is out of scope — write Parameters by hand.

Ready-made bundles under tools/: tools/fs (read/write/edit, plus read-only list_dir/find_files/grep), tools/git, tools/shell, and the assembled tools/coding bundle. See Coding tools.

Subagents. glue.SubagentTool wraps a child *glue.Agent as a tool, so a parent can delegate a focused task to a fresh, isolated transcript:

researchTool, _ := glue.SubagentTool(glue.SubagentOptions{
	Name:        "research",
	Description: "Delegate a focused research question.",
	Agent:       researcher, // a *glue.Agent
})

MCP servers. tools/mcp consumes Model Context Protocol servers (stdio / Streamable HTTP), mapping their tools to permission-gated glue.Tool values. See ADR-0011.

stores/file writes one JSON file per session — the dependency-free default. stores/sqlite implements the same glue.Store against a pure-Go SQLite DB with FTS5 over message text, for cross-session recall:

store, err := sqlite.Open(sqlite.Options{Path: "agent.db"})
defer store.Close()
agent := glue.NewAgent(glue.AgentOptions{Provider: prov, Store: store})

hits, _ := agent.SearchSessions(ctx, "Australian Shepherd", glue.WithLimit(5))
for _, h := range hits {
	fmt.Printf("[%s#%d] %s\n", h.SessionID, h.Index, h.Snippet)
}

Search options: WithLimit, WithOffset, WithSessionID, WithSince, WithUntil. The query is FTS5 MATCH syntax; hits come back by BM25 score. stores/file does not implement search, so both Agent.SearchSessions and Session.Search return glue.ErrSearchNotSupported there — picking stores/sqlite is the signal that you want it. Uses modernc.org/sqlite (no CGo). Schema details in ADR-0007.

Streaming, roles, skills, structured output

Streaming. Mirror text deltas with the convenience options, or subscribe for full control:

session.Prompt(ctx, "Stream a haiku.",
	glue.WithStreamWriter(os.Stdout),  // EventTextDelta → writer
	glue.WithToolLogger(os.Stderr),    // "[tool] <name>" on tool start
)

unsubscribe := session.Subscribe(func(e glue.Event) {
	if e.Type == glue.EventTextDelta { fmt.Print(e.Delta) }
})
defer unsubscribe()

Per-prompt overrides: glue.WithModel, glue.WithSystemPrompt, glue.WithMaxTurns.

Roles are named instruction profiles with optional model overrides, from AgentOptions.Roles or <WorkDir>/roles/*.md. Precedence: WithRole (call) > WithSessionRole (session) > AgentOptions.Role.

Project context & skills. Set AgentOptions.WorkDir: <WorkDir>/AGENTS.md is appended to the system prompt; <WorkDir>/.agents/skills/<name>/SKILL.md becomes a runnable skill via session.Skill(ctx, name, args).

Structured output. session.PromptJSON(ctx, prompt, &out) requests JSON-only and decodes into your Go type; glue.WithJSONSchema(schema) forwards an explicit schema.

Versioned prompts. prompts.NewCatalog(embedFS, dir, default) wraps an embed.FS of <version>.md files so you can A/B-test and roll back system prompts; unknown versions error with the available list.

Long context. AgentOptions.Compactor + CompactionThreshold. glue.KeepRecentMessages(n) is the zero-dependency default; SummarizingCompactor is token-aware (ADR-0002, ADR-0007).

Coding tools

tools/coding.Tools(...) assembles a permission-gated local coding bundle — read_file, write_file, edit_file, list_dir, find_files, grep, shell_exec, git_diff_branch, git_log_branch — over tools/fs, tools/git, tools/shell, and glue.Executor. The glue binary exposes it directly:

go run ./cmd/glue run --provider codex --coding --work . \
  --prompt "Run the tests and fix the first failure."

Side-effecting tools (write_file, edit_file, shell_exec) are permission-gated; reads and navigation are not. Execution defaults to the local process via glue.Executor — not a sandbox. Implement your own Executor to run in a container/VM. See ADR-0012.

The glue CLI

A thin CLI over the same library API, for trying things without writing a main.go — and a full terminal coding agent in its own right (homepage: https://glue-coding-agent-site.vercel.app):

# Interactive TUI (default when stdin/stdout are a terminal and no --prompt):
go run ./cmd/glue run --provider codex --coding --work .

# One-shot run (any registered provider; default gemini):
go run ./cmd/glue run --prompt "Say hi" --id demo --store .glue/sessions
go run ./cmd/glue run --provider codex --coding --work . --prompt "Fix the failing test."

# Scripted: pipe the prompt in.
echo "summarize main.go" | go run ./cmd/glue run --provider codex --coding

# Local HTTP+SSE daemon + a client that streams and brokers permissions:
go run ./cmd/glue serve --store .glue/sessions
go run ./cmd/glue connect --inspect
go run ./cmd/glue connect --prompt "Say hi" --id demo

# Headless goal loop (schedulable from cron/CI; exit code reflects the
# outcome: 0 achieved · 2 blocked · 3 max-iterations · 4 budget · 1 error):
go run ./cmd/glue goal --coding --yolo --worktree "Make the linter pass on ./..."
go run ./cmd/glue goal --list
go run ./cmd/glue goal --resume

Interactive mode (designed in ADR-0014). With no --prompt and a terminal on both stdin and stdout, glue run opens a bubbletea TUI: scrollable transcript with sticky-scroll, multi-line input, streaming text (re-rendered as markdown after each turn settles via charmbracelet/glamour), tool-call cards with a moving spinner while running and an inline [a] [s] [t] [n] permission prompt right inside the card when a side-effecting tool needs approval, and a small edit_file diff preview. Slash commands: /help, /exit, /clear / /new, /usage, /tools, /model <id>, /session [id], /compact (token-aware summarization of older messages to free context window), /resume (modal picker over past sessions; ↑/↓ navigate, Enter replays the chosen one into the transcript), /fork [N] (branch from message N — defaults to "just before my last user turn" — into a new session id, keeping the original intact), /clone (full duplicate of the current session), /tree (visualize the session lineage with ├─ / └─ glyphs, current node marked ; pick one to switch — see ADR-0015), and /goal <objective> (pursue a goal autonomously in the background via Agent.PursueGoal — plan → maker → independent checker per iteration, with a live [x]/[ ] checklist card in the transcript, a ◎ goal · iter 2/10 · 1/4 ✓ status-bar segment, and /goal status / pause / resume (continues from the verified checklist without re-planning — even in a new process, since progress is checkpointed to the session store) / list (recent goals with status and age) / clear subcommands; /goal -w <objective> isolates the run in its own git worktree at .glue/worktrees/<goal-id> on branch goal/<id>, so the loop never touches your checkout and the result is a reviewable branch — see ADR-0016). Anywhere in a prompt, @<path> inlines that file's contents (@"path with space" for spaces, @@literal to escape — and the workspace blocklist refuses .env / id_rsa / etc.). Typing @ in the TUI input also opens an inline file-picker popup that fuzzy-matches workspace files; ↑/↓ to navigate, Tab/Enter to insert. Enter sends; Ctrl+J inserts a newline (works on every terminal — Shift+Enter does not). Esc cancels the current turn; Ctrl+C once cancels (and a second press quits); mouse wheel scrolls the transcript; PgUp/PgDn does too. The TUI dependencies (charmbracelet/{bubbletea,bubbles,lipgloss,glamour}) live under cmd/glue/tui/ only — go get github.com/erain/glue consumers pull zero TUI code.

run flags include --provider, --model, --id, --store, --work, --coding (+ --allow-binary, --coding-allow-overwrite), --tools name1,name2 (allowlist) / --no-tools (text-only), --mode text|json (one-shot output format; json emits stable JSONL events for scripting), --yolo (auto-approve every side-effecting tool call — daily-driver mode for trusted feature branches), --usage, and repeatable --env. serve brokers coding-tool permission requests to the connected connect client; it writes connection metadata to the user config dir (never the bearer token). The daemon protocol is ADR-0010.

Standard flags for your own binary. cli.RegisterStandardFlags wires the same six flags (--provider, --model, --id, --store, --work, --max-turns) onto a flag.FlagSet:

fs := flag.NewFlagSet("my-agent", flag.ContinueOnError)
get := cli.RegisterStandardFlags(fs, nil)
fs.Parse(os.Args[1:])
cfg := get() // cfg.Provider, cfg.Model, cfg.ID, cfg.Store, cfg.Work, cfg.MaxTurns

Reference agents

Real agents built on the framework live under agents/ (peer of the harness), not examples/ (tutorial demos only).

  • agents/glue-review — a free, local pre-push branch reviewer. Reads the diff against main, deep-reads files when needed, and posts one sticky GitHub comment with a fenced ```markdown fix block downstream coding agents can paste. Runs as a CLI or a GitHub Action. Defaults to openrouter/free with automatic provider failover.

  • agents/peggy — a long-running personal-assistant agent: CLI + Telegram + a shared HTTP+SSE daemon, durable sqlite+FTS5 memory with curated recall, opt-in coding tools, MCP servers, scheduled/proactive runs, and per-channel permission tiers. The best reference for a feature-rich agent. Tracker: #110.

    go install github.com/erain/glue/agents/peggy/cmd/peggy@latest
    codex login
    peggy "Hello — what should I be working on today?"
    

Testing without API keys

The Provider interface is tiny, so tests drive sessions with a fake — no credentials, no network. Test tools by calling tool.Execute(ctx, glue.ToolCall{...}) and asserting on the ToolResult (including IsError). See the testing step of the build guide for a copy-paste fake.

Run the tests

go build ./...
go vet ./...
go test ./...

CI runs the same commands on every PR. Live provider tests are gated behind their API keys and skipped in CI, e.g.:

GEMINI_API_KEY=... go test ./providers/gemini -run Live

Project status & contributing

The project advances on three fronts: the framework (the library you go get — feature-complete and stable in practice), the glue binary as a coding agent (interactive TUI, coding tools, and the autonomous goal loop, ADR-0016), and Peggy, the long-running personal assistant built on top (agents/peggy). Releases are tagged on a 1.x line (currently well past v1.10), but the stability stance is still the pre-stability one recorded in ADR-0013 (see its addendum for why the tags say 1.x): the public Agent / Session surface is stable in practice, but minor versions may still break API until a deliberate surface-review pass. Breaking changes always land with a **Breaking:** entry in CHANGELOG.md, never on a patch release. Security reports go through SECURITY.md.

Glue is built one GitHub issue at a time. The contributor workflow, branch/PR conventions, and the active tracker are documented in CONTRIBUTING.md; the roadmap shape lives in docs/project-plan.md, and durable design decisions are recorded as ADRs under docs/adr/. The canonical architecture reference is docs/design.md.

Documentation

Overview

Package glue provides the public API for defining and running agents.

It is intentionally thin over the lower-level loop package. Users construct an Agent with NewAgent, open a named Session with Agent.Session, and drive turns with Session.Prompt. Tools are defined with NewTool, skills and roles are discovered from a WorkDir, and structured output is produced with Session.PromptJSON.

Provider implementations live in subpackages (providers/gemini, providers/codex, providers/nvidia, providers/openrouter, with the shared OpenAI-compatible core in providers/openaicompat and a driver-style registry in providers). Session persistence is provided by stores subpackages (stores/file for the simple default, stores/sqlite for cross-session FTS5 search). Reusable tools live under tools (tools/fs, tools/git, tools/shell, tools/coding, tools/mcp).

Normalized message and event types are re-exported here so that callers only need to import "github.com/erain/glue". For a guided, end-to-end walkthrough of building an agent, see docs/building-agents.md.

Index

Constants

View Source
const (
	DefaultSearchLimit = 20
	MaxSearchLimit     = 100
)

Search-related limits. Exported so callers can size paging.

View Source
const (
	MetadataKeyParentSessionID    = "glue/tree:parent_session_id"
	MetadataKeyParentMessageIndex = "glue/tree:parent_message_index"
)

MetadataKeyParentSessionID and MetadataKeyParentMessageIndex are the namespaced keys that record a session's place in the session tree. They are set by Agent.ForkSession (and copied by Agent.CloneSession when the source already had them) into SessionState.Metadata; SessionParent reads them back.

Layered on top of the existing Store interface so the tree adds zero breaking changes to disk format — a fork is just a new session whose metadata points at its parent.

View Source
const (
	RememberNever         = loop.RememberNever
	RememberSession       = loop.RememberSession
	RememberSessionTarget = loop.RememberSessionTarget
	RememberForever       = loop.RememberForever
)

RememberScope constants re-exported from package loop.

View Source
const (
	MessageRoleUser      = loop.MessageRoleUser
	MessageRoleAssistant = loop.MessageRoleAssistant
	MessageRoleTool      = loop.MessageRoleTool
)

MessageRole constants re-exported from package loop.

View Source
const (
	ContentTypeText     = loop.ContentTypeText
	ContentTypeThinking = loop.ContentTypeThinking
	ContentTypeImage    = loop.ContentTypeImage
	ContentTypeToolCall = loop.ContentTypeToolCall
)

ContentType constants re-exported from package loop.

View Source
const (
	StopReasonStop     = loop.StopReasonStop
	StopReasonLength   = loop.StopReasonLength
	StopReasonToolUse  = loop.StopReasonToolUse
	StopReasonError    = loop.StopReasonError
	StopReasonCanceled = loop.StopReasonCanceled
	StopReasonMaxTurns = loop.StopReasonMaxTurns
)

StopReason constants re-exported from package loop.

View Source
const (
	EventLoopStart    = loop.EventLoopStart
	EventLoopEnd      = loop.EventLoopEnd
	EventTurnStart    = loop.EventTurnStart
	EventTurnEnd      = loop.EventTurnEnd
	EventMessageStart = loop.EventMessageStart
	EventMessageEnd   = loop.EventMessageEnd
	EventTextDelta    = loop.EventTextDelta
	EventToolStart    = loop.EventToolStart
	EventToolEnd      = loop.EventToolEnd
	EventError        = loop.EventError
)

EventType constants re-exported from package loop.

View Source
const (
	ProviderEventStart         = loop.ProviderEventStart
	ProviderEventTextDelta     = loop.ProviderEventTextDelta
	ProviderEventThinkingDelta = loop.ProviderEventThinkingDelta
	ProviderEventToolCall      = loop.ProviderEventToolCall
	ProviderEventDone          = loop.ProviderEventDone
	ProviderEventError         = loop.ProviderEventError
)

ProviderEventType constants re-exported from package loop.

View Source
const DefaultExecMaxOutputBytes = 64 * 1024

DefaultExecMaxOutputBytes is the per-stream output cap used by LocalExecutor when ExecCommand.MaxOutputBytes is zero.

View Source
const DefaultSummarizingSystemPrompt = `` /* 876-byte string literal not displayed */

DefaultSummarizingSystemPrompt is the instruction the SummarizingCompactor sends to the provider when no override is configured. It requests a structured state snapshot (the consensus shape across pi, Codex CLI, and Gemini CLI) rather than freeform narrative: a continuation model needs exact paths, decisions, and next steps, not polish. It also carries a prompt-injection firewall, because compacted transcripts contain arbitrary repo text.

View Source
const SessionStateVersion = 1

SessionStateVersion is the on-disk version tag for SessionState.

Variables

View Source
var ErrSearchNotSupported = errors.New("glue: store does not support search")

ErrSearchNotSupported is returned when the active Store does not implement Searcher. Callers can fall back gracefully (e.g. show "search not configured" in a UI).

View Source
var ErrSessionListingNotSupported = errors.New("glue: store does not support session listing")

ErrSessionListingNotSupported is returned when the active Store does not implement SessionLister. Callers (e.g. a TUI session picker) should treat this as "no session catalog available" and degrade gracefully — show only the current session id, hide the picker, etc.

View Source
var ErrSessionNotFound = errors.New("glue: session not found")

ErrSessionNotFound is returned by Agent.ForkSession and Agent.CloneSession when the source session id does not exist in the store. The standard Store.Load returns found=false on the same condition; this typed error lets the tree-aware operations report it without leaking storage internals.

View Source
var ErrSkipTool = loop.ErrSkipTool

ErrSkipTool re-exported from package loop.

Functions

func SessionParent added in v1.3.0

func SessionParent(state SessionState) (id string, atIndex int, ok bool)

SessionParent returns the parent session id and message index a forked session was branched from. ok is false for a root session (no parent metadata) or for malformed metadata.

Types

type Agent

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

Agent owns shared configuration and an in-memory session map.

func NewAgent

func NewAgent(options AgentOptions) *Agent

NewAgent creates an agent. When AgentOptions.Store is set, sessions are loaded from and saved to that store; otherwise sessions are in-memory. The Provider must be supplied for Session.Prompt to succeed.

func (*Agent) CloneSession added in v1.3.0

func (a *Agent) CloneSession(ctx context.Context, srcID, newID string) error

CloneSession copies srcID's full transcript and metadata into newID, preserving any existing parent linkage so a clone of a forked session is still attributable to the same root. Returns ErrSessionNotFound if srcID is absent.

func (*Agent) ForkSession added in v1.3.0

func (a *Agent) ForkSession(ctx context.Context, srcID string, atMessage int, newID string) error

ForkSession copies the first atMessage messages of srcID into a fresh newID, recording the parent linkage in metadata. Returns ErrSessionNotFound if srcID is absent, or an error if atMessage is out of range. atMessage may be 0 (an empty branch with parent linkage) or len(messages) (equivalent to a clone). The new session is persisted with a fresh CreatedAt; the message slice is cloned so later edits to either session do not bleed across.

Forking does not start a session in memory or run a turn; callers typically call Agent.Session on newID immediately afterward to take the forked transcript live.

func (*Agent) ListGoals added in v1.12.0

func (a *Agent) ListGoals(ctx context.Context, opts ListGoalsOptions) ([]GoalRecord, error)

ListGoals returns durable goal records, most recently updated first. It requires the store to implement the optional SessionLister capability and returns ErrSessionListingNotSupported otherwise.

func (*Agent) ListSessions added in v1.3.0

func (a *Agent) ListSessions(ctx context.Context, opts ListSessionsOptions) ([]SessionSummary, error)

ListSessions returns a metadata-only catalog of stored sessions if the agent's Store implements SessionLister. Returns ErrSessionListingNotSupported when the store does not.

Mirrors Agent.SearchSessions in shape: provider-free, no transcript content, intended for picker UIs and dashboards.

func (*Agent) LoadGoal added in v1.12.0

func (a *Agent) LoadGoal(ctx context.Context, id string) (GoalRecord, bool, error)

LoadGoal returns the durable record for the goal that ran under id (its GoalSpec.SessionPrefix). ok is false when the session does not exist or carries no goal metadata.

func (*Agent) PursueGoal added in v1.10.0

func (a *Agent) PursueGoal(ctx context.Context, spec GoalSpec) (GoalResult, error)

PursueGoal runs an autonomous goal loop until the objective is verifiably met or a guardrail trips. It returns the terminal GoalResult; a non-nil error is returned only for context cancellation or an underlying run failure (an unmet-but-clean stop such as GoalBlocked or GoalMaxIterations is not an error).

func (*Agent) SearchSessions

func (a *Agent) SearchSessions(ctx context.Context, query string, opts ...SearchOption) ([]SearchHit, error)

SearchSessions returns FTS-ranked hits across all sessions stored by the agent's Store. Returns ErrSearchNotSupported if the active store does not implement Searcher.

query is passed straight through to the underlying Searcher. For the SQLite/FTS5 backend the syntax is FTS5's MATCH expression — a bare word matches that word; quoted phrases match exactly; AND / OR / NOT are supported.

func (*Agent) Session

func (a *Agent) Session(ctx context.Context, id string, options ...SessionOption) (*Session, error)

Session returns an existing session by id, or creates a new one. When the agent has a configured Store, an existing on-disk state is loaded into the new in-memory session. Empty ids resolve to the default session ("default").

func (*Agent) ToolCatalog

func (a *Agent) ToolCatalog() []ToolSpec

ToolCatalog returns a cloned provider-visible catalog of the agent's configured tools, including permission metadata for hosts that need to display or expose the tool surface without starting a session.

type AgentOptions

type AgentOptions struct {
	Provider     Provider
	Model        string
	SystemPrompt string
	Tools        []Tool
	Options      map[string]any
	MaxTurns     int
	Store        Store
	WorkDir      string
	Skills       map[string]Skill
	Roles        []Role
	Role         string
	Permission   Permission
	Hooks        []Hook

	// Compactor, if non-nil and CompactionThreshold > 0, is invoked
	// before every prompt whenever the in-memory transcript has more
	// than CompactionThreshold messages. The compactor's output replaces
	// the in-memory transcript before [loop.Run] is called and is
	// persisted by the next save.
	Compactor           Compactor
	CompactionThreshold int

	// AutoContinue opts every session into the loop's next-speaker
	// stall check: an assistant turn that narrates a future action
	// without calling a tool gets a bounded "Please continue." nudge.
	// Most useful on Gemini, which is prone to the narrate-then-stop
	// stall.
	AutoContinue bool
}

AgentOptions configures a Glue agent.

All fields are wired. When WorkDir is set, the agent loads `<WorkDir>/AGENTS.md` (non-fatal if missing), discovers Markdown skills under `<WorkDir>/.agents/skills/<name>/SKILL.md`, and discovers Markdown roles under `<WorkDir>/roles/*.md`. Programmatic entries supplied via Skills and Roles are merged with the on-disk catalog; programmatic entries win on name collision.

Role is the agent-default role applied to every prompt unless overridden at session or call level. Effective role precedence is call (WithRole) > session (WithSessionRole) > agent (Role). Effective model precedence is call (WithModel) > effective role's Model > Model.

type AllowAll

type AllowAll = loop.AllowAll

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type ChecklistItem added in v1.10.0

type ChecklistItem struct {
	Title    string `json:"title"`
	Done     bool   `json:"done"`
	Evidence string `json:"evidence,omitempty"`
}

ChecklistItem is one verifiable deliverable derived from the objective.

type Compactor

type Compactor interface {
	Compact(ctx context.Context, messages []Message) ([]Message, error)
}

Compactor rewrites a session transcript before it is sent to the provider. Implementations should preserve the user's most recent intent and the assistant context that depends on it; older turns can be dropped, summarized, or replaced. Compactors must not mutate the input slice; they return a new slice.

func KeepRecentMessages

func KeepRecentMessages(n int) Compactor

KeepRecentMessages returns a Compactor that keeps the last n messages of a transcript and replaces everything older with a single system-style summary message that records how many turns were dropped.

This is the simplest useful compaction policy: it has no token model and never calls a provider. It is appropriate when a session is allowed to accumulate but the caller can tolerate losing older context past a fixed window.

n must be positive. If the input transcript has n or fewer messages, the compactor returns it unchanged.

type CompactorFunc

type CompactorFunc func(ctx context.Context, messages []Message) ([]Message, error)

CompactorFunc adapts a function to the Compactor interface.

func (CompactorFunc) Compact

func (f CompactorFunc) Compact(ctx context.Context, messages []Message) ([]Message, error)

Compact implements Compactor.

type ContentPart

type ContentPart = loop.ContentPart

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type ContentType

type ContentType = loop.ContentType

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type DenyAll

type DenyAll = loop.DenyAll

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type Event

type Event = loop.Event

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type EventType

type EventType = loop.EventType

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type ExecCommand

type ExecCommand struct {
	// Argv is the executable plus arguments. It must be non-empty and
	// is never interpreted through a shell.
	Argv []string

	// Dir, when non-empty, is the child process working directory.
	Dir string

	// Env is the exact child environment. Nil means inherit no
	// environment from the agent process.
	Env []string

	// Stdin, when non-nil, is connected to the child process stdin.
	Stdin io.Reader

	// Timeout limits this command independently from the caller's
	// context. Zero means no executor-level timeout.
	Timeout time.Duration

	// MaxOutputBytes caps stdout and stderr independently. Zero uses
	// DefaultExecMaxOutputBytes.
	MaxOutputBytes int

	// SpoolDir, when non-empty, asks the executor to write each
	// stream's complete output to a temp file under this directory.
	// The file is kept (and named in ExecResult.StdoutSpool /
	// StderrSpool) only when that stream exceeded MaxOutputBytes;
	// otherwise it is removed. Executors may ignore this field.
	SpoolDir string
}

ExecCommand describes one argv-style command execution request.

type ExecResult

type ExecResult struct {
	Stdout    []byte
	Stderr    []byte
	ExitCode  int
	TimedOut  bool
	Truncated bool

	// StdoutTail / StderrTail hold the kept tail of a truncated
	// stream. Nil when the stream fit within the cap.
	StdoutTail []byte
	StderrTail []byte

	// StdoutOmitted / StderrOmitted count the bytes dropped between
	// head and tail. Zero when the stream fit.
	StdoutOmitted int
	StderrOmitted int

	// StdoutLines / StderrLines are the total lines observed on each
	// stream, including dropped output.
	StdoutLines int
	StderrLines int

	// StdoutSpool / StderrSpool name temp files holding the complete
	// stream. Set only when the stream truncated and
	// ExecCommand.SpoolDir was provided.
	StdoutSpool string
	StderrSpool string
}

ExecResult is the captured result of one command execution.

When a stream exceeds the output cap, the executor keeps the head in Stdout/Stderr and a rolling tail in StdoutTail/StderrTail — build logs need both the first error and the final status. The bytes dropped between them are counted in StdoutOmitted/StderrOmitted.

type Executor

type Executor interface {
	Run(ctx context.Context, cmd ExecCommand) (ExecResult, error)
}

Executor abstracts command execution for shell-capable tools. The default implementation, LocalExecutor, runs commands on the local machine; sandboxed or remote executors can implement the same interface without changing tool code.

type FailoverAttempt

type FailoverAttempt struct {
	Index int
	Err   error
}

FailoverAttempt is one provider's result inside a FailoverError.

type FailoverError

type FailoverError struct {
	Attempts []FailoverAttempt
}

FailoverError aggregates per-provider failures from a WithFailover stream attempt. It implements error and exposes Attempts so callers can inspect each provider's outcome.

func (*FailoverError) Error

func (e *FailoverError) Error() string

type GoalEvent added in v1.10.0

type GoalEvent struct {
	Type      GoalEventType
	Iteration int
	Message   string
	Checklist []ChecklistItem
	Usage     Usage
}

GoalEvent is an observability event emitted during a goal loop.

type GoalEventType added in v1.10.0

type GoalEventType string

GoalEventType identifies a GoalEvent.

const (
	GoalEventPlan           GoalEventType = "plan"
	GoalEventIterationStart GoalEventType = "iteration_start"
	GoalEventMakerDone      GoalEventType = "maker_done"
	GoalEventVerdict        GoalEventType = "verdict"
	GoalEventDone           GoalEventType = "done"
	GoalEventBlocked        GoalEventType = "blocked"
	GoalEventBudget         GoalEventType = "budget_limited"
	GoalEventMaxIterations  GoalEventType = "max_iterations"
)

type GoalRecord added in v1.12.0

type GoalRecord struct {
	ID         string          `json:"id"`
	Objective  string          `json:"objective"`
	Status     GoalStatus      `json:"status"`
	Checklist  []ChecklistItem `json:"checklist,omitempty"`
	Iterations int             `json:"iterations"`
	Usage      Usage           `json:"usage"`
	Summary    string          `json:"summary,omitempty"`
	WorkDir    string          `json:"work_dir,omitempty"` // tool root, e.g. an isolated worktree
	UpdatedAt  time.Time       `json:"updated_at"`
}

GoalRecord is the durable snapshot of a goal loop, checkpointed by Agent.PursueGoal as it runs (when the agent has a Store) and read back by Agent.LoadGoal / Agent.ListGoals. ID is the GoalSpec.SessionPrefix the goal ran under.

func (GoalRecord) Resumable added in v1.12.0

func (r GoalRecord) Resumable() bool

Resumable reports whether the goal can be continued from its checklist: any unmet, non-running state with verified items to seed from. A stale "running" record left behind by a crashed process is excluded — a caller that knows the owning process is gone may still choose to resume it.

type GoalResult added in v1.10.0

type GoalResult struct {
	Status     GoalStatus
	Objective  string
	Checklist  []ChecklistItem
	Iterations int
	Usage      Usage // summed across planning, makers, and checkers
	Summary    string
}

GoalResult is the outcome of Agent.PursueGoal.

type GoalSpec added in v1.10.0

type GoalSpec struct {
	// Objective is the natural-language goal. Required.
	Objective string

	// SessionPrefix is the base for the generated maker/checker session ids.
	// Defaults to "goal".
	SessionPrefix string

	// Model overrides the maker model. Empty uses the agent default.
	Model string
	// CheckerModel overrides the verifier model. Empty defaults to Model
	// (i.e. the maker model, or the agent default when Model is empty).
	CheckerModel string

	// MaxIterations caps the outer loop. Defaults to 10.
	MaxIterations int
	// MaxTurns overrides the inner per-iteration turn budget for both maker
	// and checker. Zero uses the agent/loop default.
	MaxTurns int
	// TokenBudget caps total tokens across planning, makers, and checkers.
	// Zero means unlimited.
	TokenBudget int64
	// NoProgressLimit stops the loop (status GoalBlocked) after this many
	// consecutive iterations leave the set of unfinished items unchanged.
	// Defaults to 2.
	NoProgressLimit int

	// Checklist seeds the loop with an existing plan instead of asking the
	// model to decompose the objective; done flags and evidence are kept as
	// given. This is how a paused or restarted goal resumes from its last
	// verified state without re-planning.
	Checklist []ChecklistItem

	// StartIteration numbers this run's first iteration (default 1). A
	// resumed goal passes its prior Iterations+1 so maker/checker session
	// ids stay fresh (iter-4, check-4, …) instead of appending to the
	// previous run's transcripts. MaxIterations still counts iterations
	// in this run.
	StartIteration int

	// WorkDir records where this goal's tools are rooted (e.g. an isolated
	// git worktree). The loop itself never reads it — tools already carry
	// their root — but it is persisted on the GoalRecord so a host can
	// rebuild the right tool set when resuming.
	WorkDir string

	// Tools overrides the maker tool set. Empty uses the agent's tools.
	Tools []Tool
	// CheckerTools overrides the verifier tool set. Empty uses the agent's
	// tools; the checker is instructed to verify, not modify.
	CheckerTools []Tool

	// SystemPrompt overrides the maker system prompt. Empty uses the agent's.
	SystemPrompt string
	// CheckerSystemPrompt overrides the verifier system prompt. Empty uses a
	// built-in audit prompt.
	CheckerSystemPrompt string

	// Permission overrides the permission policy for side-effecting tools
	// in the planner, maker, and checker sessions. Nil uses the agent's
	// policy.
	Permission Permission

	// Emit, when set, receives progress events as the loop runs.
	Emit func(GoalEvent)
}

GoalSpec configures an autonomous goal loop driven by Agent.PursueGoal.

The loop is glue's take on "loop engineering" / the `/goal` command: a single persistent Objective drives plan → act → verify → repeat until the goal is verifiably met or a guardrail trips. The maker (which acts) and the checker (which audits) run in separate sessions so the writer does not grade its own homework, and each maker iteration runs in a fresh session seeded from the durable checklist (a Ralph-style loop) rather than an ever-growing transcript. See docs/adr/0016-goal-loop.md.

type GoalStatus added in v1.10.0

type GoalStatus string

GoalStatus is the terminal state of a goal loop.

const (
	// GoalRunning is never returned by PursueGoal; it appears only on the
	// durable [GoalRecord] while the loop is in flight.
	GoalRunning GoalStatus = "running"
	// GoalPaused is never returned by PursueGoal; it is the durable record
	// of a context cancellation — the loop stopped cleanly mid-objective
	// and can resume from its checklist.
	GoalPaused GoalStatus = "paused"
	// GoalAchieved means the checker confirmed every deliverable.
	GoalAchieved GoalStatus = "achieved"
	// GoalBlocked means the loop stopped because no progress was made for
	// NoProgressLimit consecutive iterations.
	GoalBlocked GoalStatus = "blocked"
	// GoalBudgetLimited means the token budget was exhausted.
	GoalBudgetLimited GoalStatus = "budget_limited"
	// GoalMaxIterations means the iteration cap was reached unmet.
	GoalMaxIterations GoalStatus = "max_iterations"
	// GoalErrored means a maker/checker run failed or the context was
	// cancelled.
	GoalErrored GoalStatus = "errored"
)

type Hook

type Hook = loop.Hook

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type ImageContent

type ImageContent = loop.ImageContent

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type ListGoalsOptions added in v1.12.0

type ListGoalsOptions struct {
	// Prefix restricts results to goal ids beginning with Prefix
	// (e.g. "goal-" for goals started by the TUI).
	Prefix string

	// Limit caps returned records. Non-positive values return all matches
	// the underlying session listing yields.
	Limit int
}

ListGoalsOptions filters and pages a goal record listing.

type ListSessionsOptions

type ListSessionsOptions struct {
	// Prefix restricts results to session ids beginning with Prefix.
	Prefix string

	// Limit caps returned rows. Non-positive values use the store default.
	Limit int

	// Offset skips rows after filtering and ordering. Negative values are
	// treated as zero.
	Offset int
}

ListSessionsOptions filters and pages a session history listing.

type LocalExecutor

type LocalExecutor struct{}

LocalExecutor runs commands on the local machine with os/exec. It is intentionally not a sandbox.

func (LocalExecutor) Run

Run implements Executor.

type Message

type Message = loop.Message

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type MessageRole

type MessageRole = loop.MessageRole

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type Permission

type Permission = loop.Permission

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type PermissionDecision

type PermissionDecision = loop.PermissionDecision

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type PermissionRequest

type PermissionRequest = loop.PermissionRequest

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type ProjectContext

type ProjectContext struct {
	AgentsMD string
	Skills   map[string]Skill
	Roles    map[string]Role
}

ProjectContext is the WorkDir-loaded state injected by LoadContext. AgentsMD is appended to the system prompt; Skills are exposed via Session.Skill; Roles are looked up by name during prompt configuration.

func LoadContext

func LoadContext(workDir string) (ProjectContext, error)

LoadContext loads AGENTS.md (non-fatal if missing) and skills under `<workDir>/.agents/skills/<name>/SKILL.md`. An empty workDir returns an empty context.

type PromptOption

type PromptOption func(*promptConfig)

PromptOption configures one Session.Prompt invocation.

func WithEvents

func WithEvents(handler func(Event)) PromptOption

WithEvents registers a per-prompt event handler. It receives every loop event for that prompt in addition to any session-scoped subscribers installed via Session.Subscribe.

func WithJSONSchema

func WithJSONSchema(schema any) PromptOption

WithJSONSchema attaches a JSON Schema for Session.PromptJSON. The schema may be passed as a Go value (map / struct), a json.RawMessage, a []byte, or a string; bytes/string forms are JSON-decoded once. When the active provider supports structured output, the schema is forwarded as the provider's structured-response config (Gemini: `response_json_schema`).

func WithMaxTurns

func WithMaxTurns(maxTurns int) PromptOption

WithMaxTurns overrides the loop max-turn guard for one prompt.

func WithModel

func WithModel(model string) PromptOption

WithModel overrides the model for one prompt. When set, this beats any role's Model and the agent's Model.

func WithPermission

func WithPermission(permission Permission) PromptOption

WithPermission overrides the agent's Permission implementation for one prompt. Passing nil explicitly disables permission handling for that prompt, so side-effecting tools are denied by the loop.

func WithProviderOptions

func WithProviderOptions(options map[string]any) PromptOption

WithProviderOptions replaces provider options for one prompt.

func WithRole

func WithRole(role string) PromptOption

WithRole overrides the effective role for one prompt. The named role must exist in AgentOptions.Roles or the loaded WorkDir context, or the prompt fails with a typed error.

func WithStreamWriter

func WithStreamWriter(w io.Writer) PromptOption

WithStreamWriter mirrors EventTextDelta.Delta to w on every prompt event. Composes additively with WithEvents and other event-related options — installing one does not displace any other.

Errors from the writer are silently dropped: this is a convenience option, not a delivery-guaranteed pipe. Callers that need backpressure or error visibility should register a custom WithEvents handler instead.

A nil writer is a no-op so callers can pass conditional writers without branching.

func WithSystemPrompt

func WithSystemPrompt(systemPrompt string) PromptOption

WithSystemPrompt overrides the agent system prompt for one prompt.

func WithToolLogger

func WithToolLogger(w io.Writer) PromptOption

WithToolLogger mirrors EventToolStart events to w as "[tool] <name>\n". Composes additively with other event-related options. Errors from the writer are silently dropped; nil w is a no-op.

func WithTools

func WithTools(tools []Tool) PromptOption

WithTools replaces the agent tools for one prompt.

type PromptResult

type PromptResult struct {
	// Text concatenates all text parts of the final assistant message.
	Text string
	// Message is the final assistant message of this run, cloned. Nil if no
	// assistant message was produced.
	Message *Message
	// NewMessages contains every message produced during this run, in append
	// order (assistant + tool messages, possibly across multiple turns).
	NewMessages []Message
	// Messages is a snapshot of the full session transcript after this run.
	Messages []Message
}

PromptResult is returned by Session.Prompt.

type Provider

type Provider = loop.Provider

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

func WithFailover

func WithFailover(provs ...Provider) Provider

WithFailover returns a Provider that tries each underlying provider in order until one accepts a Stream. Failure modes that trigger fallover:

  • the underlying Stream call returns a non-nil error before any event is emitted;
  • the underlying stream's first event is a ProviderEventError;
  • the underlying stream closes immediately with no events.

Once any non-error event is observed the wrapper commits to that provider for the rest of the turn — it does not recover mid-stream. This preserves the loop's "no half-streamed transcripts on retry" invariant.

Callers that want env-key probing (skip provider X when its API key is unset) should pre-filter via [providers.KeyAvailable] from github.com/erain/glue/providers and pass only the candidates whose keys are present.

Returns a typed error implementing FailoverError when all providers fail; use errors.As to inspect per-provider attempts.

type ProviderEvent

type ProviderEvent = loop.ProviderEvent

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type ProviderEventType

type ProviderEventType = loop.ProviderEventType

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type ProviderRequest

type ProviderRequest = loop.ProviderRequest

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type RememberScope

type RememberScope = loop.RememberScope

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type Role

type Role struct {
	Name         string
	Description  string
	Instructions string
	Model        string
}

Role is a named instruction profile with an optional model override. Roles are loaded from `<WorkDir>/roles/*.md` (with `name:`, `description:`, `model:` frontmatter) or supplied via AgentOptions.Roles.

type SearchHit

type SearchHit struct {
	// SessionID identifies which session this message lives in.
	SessionID string

	// Index is the ordinal position of the message within its session
	// (zero-based).
	Index int

	// Role is the message author role.
	Role MessageRole

	// Snippet is a Searcher-supplied excerpt with highlighting around
	// the matched terms (FTS5's snippet() output for the SQLite
	// backend; markers are << and >>).
	Snippet string

	// Score is the implementation-specific relevance score. For the
	// SQLite/FTS5 backend this is BM25 — lower is better.
	Score float64

	// Timestamp is the message's CreatedAt (or the session updated_at
	// if the message had no per-message timestamp).
	Timestamp time.Time
}

SearchHit is one row returned by a Searcher.

type SearchOption

type SearchOption func(*SearchOptions)

SearchOption configures a SearchOptions via functional-options.

func WithLimit

func WithLimit(n int) SearchOption

WithLimit overrides SearchOptions.Limit. Non-positive values fall back to DefaultSearchLimit; values > MaxSearchLimit are clamped.

func WithOffset

func WithOffset(n int) SearchOption

WithOffset overrides SearchOptions.Offset. Negative values are treated as zero.

func WithSessionID

func WithSessionID(id string) SearchOption

WithSessionID restricts the search to a single session.

func WithSince

func WithSince(t time.Time) SearchOption

WithSince sets the lower time bound (inclusive).

func WithUntil

func WithUntil(t time.Time) SearchOption

WithUntil sets the upper time bound (inclusive).

type SearchOptions

type SearchOptions struct {
	// SessionID restricts results to one session. Empty means
	// "across all sessions".
	SessionID string

	// Limit caps the number of hits returned. Zero falls back to
	// DefaultSearchLimit; values larger than MaxSearchLimit are
	// clamped silently.
	Limit int

	// Offset skips this many hits. Useful for paging.
	Offset int

	// Since restricts results to messages with timestamps ≥ Since.
	// Zero means no lower bound.
	Since time.Time

	// Until restricts results to messages with timestamps ≤ Until.
	// Zero means no upper bound.
	Until time.Time
}

SearchOptions controls a Searcher.Search call. Zero values mean "no filter" for SessionID / Since / Until, "default" for Limit (20), and zero Offset.

type Searcher

type Searcher interface {
	Search(ctx context.Context, query string, opts SearchOptions) ([]SearchHit, error)
}

Searcher is the optional capability a Store may implement to support cross-session content search. Stores that do not implement Searcher cause Agent.SearchSessions and Session.Search to return ErrSearchNotSupported.

Designed in docs/adr/0007-memory-layer.md §3.

type Session

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

Session is an in-memory conversation with an Agent. Sessions are goroutine-safe but a single session executes one Session.Prompt at a time.

func (*Session) ID

func (s *Session) ID() string

ID returns the session id.

func (*Session) Messages

func (s *Session) Messages() []Message

Messages returns a snapshot of the session transcript.

func (*Session) Prompt

func (s *Session) Prompt(ctx context.Context, text string, options ...PromptOption) (PromptResult, error)

Prompt sends a user message through the agent loop and stores the resulting transcript in memory.

func (*Session) PromptJSON

func (s *Session) PromptJSON(ctx context.Context, text string, outPtr any, options ...PromptOption) (PromptResult, error)

PromptJSON sends a prompt and decodes the assistant's final text into outPtr, which must be a non-nil pointer. It augments the user prompt with JSON-only instructions so non-Gemini providers can still produce parseable output, and sets `response_mime_type: application/json` on the provider request. When WithJSONSchema is provided, the schema is also forwarded as `response_json_schema`.

V1 validation is intentionally limited to JSON decoding into the caller's Go type; full JSON Schema validation is out of scope.

func (*Session) Search

func (s *Session) Search(ctx context.Context, query string, opts ...SearchOption) ([]SearchHit, error)

Search restricts Agent.SearchSessions to this session. Any WithSessionID in opts is overridden with this session's id.

func (*Session) Skill

func (s *Session) Skill(ctx context.Context, name string, args any, options ...PromptOption) (PromptResult, error)

Skill renders the named skill (looked up from AgentOptions.Skills or the agent's WorkDir context), appends args as JSON, and runs the result through Session.Prompt. Unknown skill names return a typed error.

func (*Session) State

func (s *Session) State() SessionState

State returns a snapshot of the durable session state.

func (*Session) Subscribe

func (s *Session) Subscribe(handler func(Event)) func()

Subscribe registers a session-scoped event handler that receives every loop event for every prompt run on this session. The returned function removes the handler.

type SessionLister

type SessionLister interface {
	ListSessions(ctx context.Context, opts ListSessionsOptions) ([]SessionSummary, error)
}

SessionLister is an optional store capability for provider-free session history browsers.

type SessionOption

type SessionOption func(*sessionConfig)

SessionOption configures a session at creation time.

func WithSessionRole

func WithSessionRole(role string) SessionOption

WithSessionRole sets the session-default role used when no per-call WithRole is provided.

type SessionState

type SessionState struct {
	Version   int            `json:"version"`
	ID        string         `json:"id"`
	Messages  []Message      `json:"messages,omitempty"`
	Metadata  map[string]any `json:"metadata,omitempty"`
	CreatedAt time.Time      `json:"created_at"`
	UpdatedAt time.Time      `json:"updated_at"`
}

SessionState is the durable representation of a session.

type SessionSummary

type SessionSummary struct {
	ID                string    `json:"id"`
	CreatedAt         time.Time `json:"created_at"`
	UpdatedAt         time.Time `json:"updated_at"`
	Messages          int       `json:"messages"`
	UserMessages      int       `json:"user_messages"`
	AssistantMessages int       `json:"assistant_messages"`
}

SessionSummary is provider-free metadata about one stored session.

type Skill

type Skill struct {
	Name         string
	Description  string
	Instructions string
}

Skill is a Markdown-defined reusable prompt loaded from `<WorkDir>/.agents/skills/<name>/SKILL.md` or supplied directly via AgentOptions.Skills.

type StopReason

type StopReason = loop.StopReason

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type Store

type Store interface {
	Load(ctx context.Context, id string) (SessionState, bool, error)
	Save(ctx context.Context, id string, state SessionState) error
	Delete(ctx context.Context, id string) error
}

Store persists Glue session state. Implementations are expected to be goroutine-safe across distinct session ids; concurrent calls for the same id within a single session are serialized by the session itself.

Load returns found=false when the id is not present, with a zero-valued SessionState and a nil error. Save must be atomic against partial writes. Delete must be idempotent — removing a missing id is a no-op success.

type SubagentOptions

type SubagentOptions struct {
	// Name is the model-visible tool name.
	Name string

	// Description is the model-visible tool description.
	Description string

	// Agent is the child agent invoked when the tool runs. Required.
	Agent *Agent

	// SessionID is an optional prefix for generated child session ids.
	// Each tool instance and tool call appends a generated suffix so calls
	// use isolated transcripts. Empty uses "subagent:<Name>".
	SessionID string

	// MaxTurns optionally overrides the child prompt's loop turn budget.
	// Zero uses the child agent/default loop budget.
	MaxTurns int

	// SystemPrompt optionally overrides the child agent system prompt for
	// each delegated prompt.
	SystemPrompt string
}

SubagentOptions configures SubagentTool.

type SummarizingCompactor

type SummarizingCompactor struct {
	// Provider streams the summary. Required.
	Provider Provider

	// Model is the model id used for the summary call. When empty the
	// provider's default applies.
	Model string

	// TargetTokens is the soft cap: when the estimated transcript size
	// is below this value the compactor returns its input unchanged.
	// Zero or negative falls back to the default (8000).
	TargetTokens int

	// KeepRecent is the number of most-recent messages retained
	// verbatim. Zero or negative falls back to the default (8). When
	// the input transcript has KeepRecent or fewer messages the
	// compactor returns it unchanged regardless of TargetTokens.
	KeepRecent int

	// KeepRecentTokens, when positive, selects the verbatim-kept tail
	// by estimated token budget instead of message count: messages are
	// kept newest-first until the budget is exhausted (at least one is
	// always kept). Overrides KeepRecent.
	KeepRecentTokens int

	// SystemPrompt is the instruction sent to the summarizer. When
	// empty [DefaultSummarizingSystemPrompt] is used.
	SystemPrompt string
}

SummarizingCompactor is a token-aware Compactor that summarizes older transcript messages by calling the configured Provider. It replaces older messages with a single assistant-role marker whose text content is the summary and whose metadata records the compaction.

SummarizingCompactor is the token-aware drop-in anticipated by ADR-0002 and designed in ADR-0007 §1. It composes with — and does not replace — KeepRecentMessages; callers pick the policy that matches their agent's lifetime.

Provider must be set. The compactor calls Provider.Stream once per invocation with a single user message containing the formatted transcript-to-summarize.

Token estimation is intentionally a heuristic in v0.1: a word-count-based proxy that does not need to match any specific tokenizer. A later PR can swap the implementation without changing this type's public surface.

Errors from the underlying provider propagate. The compactor does not silently fall back to dropping context; callers that want a degraded-mode behavior should wire a CompactorFunc that tries SummarizingCompactor first and falls back to KeepRecentMessages explicitly.

func (*SummarizingCompactor) Compact

func (s *SummarizingCompactor) Compact(ctx context.Context, in []Message) ([]Message, error)

Compact implements Compactor. See the type docs for behavior.

type Tool

type Tool = loop.Tool

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

func NewTool

func NewTool[T any](spec ToolSpec, fn func(ctx context.Context, args T) (ToolResult, error)) Tool

NewTool builds a Tool whose executor decodes ToolCall.Arguments into a typed Go value before invoking fn. Empty arguments decode as the zero value of T. JSON decode failures surface to the model as an error ToolResult — matching the existing manual pattern — so a malformed call does not crash the loop.

Callers still supply spec.Parameters (a JSON Schema). Schema generation from T is intentionally out of scope.

func SubagentTool

func SubagentTool(opts SubagentOptions) (Tool, error)

SubagentTool exposes a child Agent as a Tool. Each tool call creates a fresh child session, forwards only the explicit prompt argument, and returns the child agent's final text as the tool result. Child prompt failures become model-visible error results except for context cancellation/deadline errors, which are returned as Go errors so the parent loop stops promptly.

type ToolCall

type ToolCall = loop.ToolCall

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type ToolExecutor

type ToolExecutor = loop.ToolExecutor

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type ToolResult

type ToolResult = loop.ToolResult

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

func ErrorResult

func ErrorResult(err error) ToolResult

ErrorResult wraps an error in a ToolResult tagged with IsError=true so the model sees it as a tool failure rather than the loop crashing. The error's Error() string becomes the visible text.

func TextResult

func TextResult(s string) ToolResult

TextResult wraps a string in a ToolResult with a single text content part. Use it from tool executors when the tool succeeds with textual output.

type ToolSpec

type ToolSpec = loop.ToolSpec

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

type Usage

type Usage = loop.Usage

Re-export the loop package's normalized types as part of the public API so callers only need to import `glue`.

Directories

Path Synopsis
agents
glue-review command
Command glue-review is a free, local pre-push branch reviewer built on the Glue agent harness.
Command glue-review is a free, local pre-push branch reviewer built on the Glue agent harness.
peggy
Package peggy is a long-running personal-assistant agent built on the glue framework.
Package peggy is a long-running personal-assistant agent built on the glue framework.
peggy/channels/telegram
Package telegram is the Telegram-bot channel adapter for Peggy.
Package telegram is the Telegram-bot channel adapter for Peggy.
peggy/cmd/peggy command
Command peggy is the CLI entry point for the personal-assistant agent.
Command peggy is the CLI entry point for the personal-assistant agent.
peggy/cmd/peggy-telegram command
Command peggy-telegram is the binary form of the Telegram channel adapter.
Command peggy-telegram is the binary form of the Telegram channel adapter.
Package cli provides small helpers downstream agents share with the canonical cmd/glue runner.
Package cli provides small helpers downstream agents share with the canonical cmd/glue runner.
cmd
glue command
Command glue is the local CLI runner for Glue agents.
Command glue is the local CLI runner for Glue agents.
glue/atmentions
Package atmentions implements `@<path>` expansion for the glue CLI: before the prompt is sent to the provider, every recognized @-mention is read off disk and appended to the prompt under a fenced header.
Package atmentions implements `@<path>` expansion for the glue CLI: before the prompt is sent to the provider, every recognized @-mention is read off disk and appended to the prompt under a fenced header.
glue/tui
Package tui implements the interactive multi-turn terminal interface for `glue run`.
Package tui implements the interactive multi-turn terminal interface for `glue run`.
glue/worktree
Package worktree creates the dedicated git worktrees isolated goals run in: <repo root>/.glue/worktrees/<goal-id> on branch goal/<id-suffix>.
Package worktree creates the dedicated git worktrees isolated goals run in: <repo root>/.glue/worktrees/<goal-id> on branch goal/<id-suffix>.
Package daemon serves local Glue sessions over the ADR-0010 HTTP+SSE protocol.
Package daemon serves local Glue sessions over the ADR-0010 HTTP+SSE protocol.
examples
echo-provider
Package echo is a minimal example of a custom Glue provider.
Package echo is a minimal example of a custom Glue provider.
local-agent command
Command local-agent is a small Gemini-backed CLI built directly on the glue library.
Command local-agent is a small Gemini-backed CLI built directly on the glue library.
Package loop contains Glue's provider-agnostic agent loop.
Package loop contains Glue's provider-agnostic agent loop.
Package prompts provides a small versioned-prompt loader that wraps an embed.FS rooted at a directory of `<name>.md` files.
Package prompts provides a small versioned-prompt loader that wraps an embed.FS rooted at a directory of `<name>.md` files.
Package providers exposes a small driver-style registry of provider constructors.
Package providers exposes a small driver-style registry of provider constructors.
codex
Package codex is a glue.Provider that routes through the Codex Responses endpoint at chatgpt.com/backend-api/codex/responses, authenticated with a ChatGPT subscription via OAuth tokens read from the upstream Codex CLI's auth.json (run "codex login" once outside glue).
Package codex is a glue.Provider that routes through the Codex Responses endpoint at chatgpt.com/backend-api/codex/responses, authenticated with a ChatGPT subscription via OAuth tokens read from the upstream Codex CLI's auth.json (run "codex login" once outside glue).
codex/auth
Package auth handles ChatGPT-subscription OAuth tokens for the providers/codex transport.
Package auth handles ChatGPT-subscription OAuth tokens for the providers/codex transport.
gemini
Package gemini adapts Google's Gemini API to Glue's provider interface using the google.golang.org/genai SDK.
Package gemini adapts Google's Gemini API to Glue's provider interface using the google.golang.org/genai SDK.
nvidia
Package nvidia implements a Glue provider for the NVIDIA build inference API (https://build.nvidia.com), which exposes an OpenAI-compatible chat-completions endpoint at https://integrate.api.nvidia.com/v1.
Package nvidia implements a Glue provider for the NVIDIA build inference API (https://build.nvidia.com), which exposes an OpenAI-compatible chat-completions endpoint at https://integrate.api.nvidia.com/v1.
openaicompat
Package openaicompat implements the shared streaming, tool-call, and convert logic for OpenAI-style chat-completions endpoints used by the concrete providers under providers/.
Package openaicompat implements the shared streaming, tool-call, and convert logic for OpenAI-style chat-completions endpoints used by the concrete providers under providers/.
openrouter
Package openrouter implements a Glue provider for OpenRouter (https://openrouter.ai), an OpenAI-compatible aggregator that routes requests across many underlying model providers.
Package openrouter implements a Glue provider for OpenRouter (https://openrouter.ai), an OpenAI-compatible aggregator that routes requests across many underlying model providers.
stores
file
Package file provides a local JSON-backed session store for Glue.
Package file provides a local JSON-backed session store for Glue.
sqlite
Package sqlite provides a SQLite-backed glue.Store with FTS5 over message text.
Package sqlite provides a SQLite-backed glue.Store with FTS5 over message text.
tools
coding
Package coding assembles the reusable local coding-agent tool bundle.
Package coding assembles the reusable local coding-agent tool bundle.
fs
Package fs provides filesystem helpers and tool factories that agents can register without reinventing path safety, output truncation, or the sensitive-file blocklist.
Package fs provides filesystem helpers and tool factories that agents can register without reinventing path safety, output truncation, or the sensitive-file blocklist.
git
Package git provides git tool factories and shell-out helpers that agents can register without re-implementing PATH lookup, timeout management, or pathspec construction.
Package git provides git tool factories and shell-out helpers that agents can register without re-implementing PATH lookup, timeout management, or pathspec construction.
mcp
Package mcp implements the client foundation for consuming Model Context Protocol servers from glue hosts.
Package mcp implements the client foundation for consuming Model Context Protocol servers from glue hosts.
shell
Package shell provides permission-gated command execution tools for agents that explicitly opt into local coding workflows.
Package shell provides permission-gated command execution tools for agents that explicitly opt into local coding workflows.

Jump to

Keyboard shortcuts

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