glue

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: May 26, 2026 License: MIT Imports: 16 Imported by: 0

README

glue

CI

Glue is a Go agent harness for building local and programmable agents, inspired by Flue and pi-mono. It is built around a reusable provider-agnostic agent loop, a code-first Agent / Session API, and pluggable providers — Gemini, plus OpenAI-compatible NVIDIA build and OpenRouter out of the box.

GitHub issues are the source of truth for the roadmap and implementation order:

Status

The harness is feature-complete for the 0.x series and is in active use behind the agents/glue-review reference agent (single GitHub comment per PR with a fenced ```markdown fix block downstream coding agents can paste). The library itself remains pre-1.0 — the public Agent / Session surface is stable in practice, but minor versions may still break API. Shipped today:

  • Normalized loop types and the provider-agnostic agent loop in loop/, with deterministic sequential tool execution, opt-in RunRequest.Parallel, and StopReasonMaxTurns for budget-exhaustion detection.
  • Public Agent / Session API: per-prompt event streaming with WithStreamWriter / WithToolLogger, structured JSON output (PromptJSON), Markdown-driven skills/roles/AGENTS.md discovery, opt-in Compactor interface, typed NewTool[T] helper.
  • Providers: gemini (Google genai SDK), nvidia and openrouter (OpenAI-compatible, sharing the providers/openaicompat core), a driver-style registry under providers/, and glue.WithFailover.
  • Storage: file-backed session store at stores/file. Tools: shared tools/fs and tools/git extension packages. CLI: cmd/glue runner plus cli.RegisterStandardFlags for downstream agents. Versioned prompts via prompts.NewCatalog.

See CHANGELOG.md for library-level notes.

Install

go get github.com/erain/glue

The module path is github.com/erain/glue. Subpackages: github.com/erain/glue/loop (reusable agent loop), github.com/erain/glue/providers/{gemini,nvidia,openrouter} (with the shared OpenAI-compatible core in providers/openaicompat and the driver-style registry in providers/), github.com/erain/glue/stores/file (file-backed session store), github.com/erain/glue/tools/{fs,git} (extension tool packages), github.com/erain/glue/prompts (versioned-prompt catalog), and github.com/erain/glue/cli (shared standard flags).

Quickstart: Gemini

Set a Gemini API key:

export GEMINI_API_KEY=...

Send a prompt:

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-2.5-flash",
	})

	session, err := agent.Session(ctx, "local-dev")
	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 session.Prompt(...) continues the conversation. Pass AgentOptions.Store (e.g. stores/file) to persist transcripts across processes.

Quickstart: NVIDIA build (Kimi K2 and friends)

The providers/nvidia package speaks the OpenAI-compatible API exposed at build.nvidia.com, so any model listed there (Kimi K2 family, Llama, Qwen, etc.) can be driven through Glue without a separate SDK.

export NVIDIA_API_KEY=nvapi-...
import (
	"github.com/erain/glue"
	"github.com/erain/glue/providers/nvidia"
)

agent := glue.NewAgent(glue.AgentOptions{
	Provider: nvidia.New(nvidia.Options{}),
	Model:    "moonshotai/kimi-k2.6",
})

The model id matches the org/name path on build.nvidia.com (e.g. moonshotai/kimi-k2.6, meta/llama-3.3-70b-instruct). Cold-start latency on Kimi K2 can reach tens of seconds for the first chunk; configure your HTTP client and context timeouts accordingly.

Quickstart: Codex (ChatGPT subscription)

The providers/codex package routes requests through the Codex Responses endpoint (chatgpt.com/backend-api/codex/responses), authenticated by your existing ChatGPT subscription rather than an OpenAI API key. Useful when your daily-driver agent should bill against the subscription you already pay for.

v0.1 piggybacks on the upstream Codex CLI's auth.json. Install the OpenAI Codex CLI and log in once:

codex login

Glue reads the same token file (~/.codex/auth.json by default; overridable via $GLUE_CODEX_AUTH or $CODEX_HOME) and refreshes stale tokens automatically.

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

agent := glue.NewAgent(glue.AgentOptions{
    Provider: codex.New(codex.Options{}),
    Model:    codex.DefaultModel, // "gpt-5-codex"
})

The provider quarantines all subscription-auth fragility (OAuth flow, Bearer + ChatGPT-Account-ID headers, Cloudflare cookie persistence, 401-refresh-retry) to the package — the rest of glue is unchanged. See docs/adr/0006-codex-provider.md for the full design. Subscription-auth use via third-party tools is not formally documented by OpenAI; the provider is intended for personal use.

The interactive PKCE login flow is deferred for v0.1 (full protocol in the ADR appendix). If you cannot install the upstream Codex CLI, file an issue.

Quickstart: OpenRouter

The providers/openrouter package speaks the OpenAI-compatible API at openrouter.ai, which aggregates many upstream model providers behind a single endpoint. The meta-route openrouter/free auto-picks a free underlying model — handy for tests and examples.

export OPENROUTER_API_KEY=sk-or-v1-...
import (
	"github.com/erain/glue"
	"github.com/erain/glue/providers/openrouter"
)

agent := glue.NewAgent(glue.AgentOptions{
	Provider: openrouter.New(openrouter.Options{}),
	Model:    "openrouter/free",
})

The provider sends HTTP-Referer and X-Title attribution headers by default; override them via Options.Headers for your own application. OpenRouter emits SSE comment-line keep-alives during cold routing — the provider drops them silently — so first-byte latency may be a few seconds even when the underlying model is fast.

Streaming events

Session.Subscribe registers a session-scoped handler that fires on every loop event for every prompt run on that session. glue.WithEvents registers a per-prompt handler that fires alongside it.

For the two most common cases — mirror text deltas and log tool starts to a writer — use the convenience options:

_, err := session.Prompt(ctx, "Stream a haiku about glue.",
	glue.WithStreamWriter(os.Stdout),
	glue.WithToolLogger(os.Stderr),
)

WithStreamWriter writes EventTextDelta.Delta straight to the writer; WithToolLogger emits [tool] <name>\n on EventToolStart. Both nil-safe and silently drop writer errors. They compose additively with WithEvents and each other — adding one does not displace any other handler.

For richer formatting, use WithEvents directly:

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

_, err := session.Prompt(ctx, "Stream a haiku about glue.")
if err != nil {
	log.Fatal(err)
}
Per-prompt overrides
result, err := session.Prompt(ctx, "Be concise.",
	glue.WithModel("gemini-2.5-pro"),
	glue.WithSystemPrompt("Reply in five words or fewer."),
	glue.WithMaxTurns(4),
)
Provider failover

glue.WithFailover(provs...) returns a Provider that tries each underlying provider in order until one accepts a Stream — useful when your CLI agent supports multiple LLM backends and you want it to skip providers whose API keys aren't set rather than fail. Pre-filter via the small registry under providers:

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

var provs []glue.Provider
for _, name := range []string{"nvidia", "openrouter", "gemini"} {
	if !providers.KeyAvailable(name) {
		continue
	}
	p, _, _, err := providers.New(name)
	if err == nil {
		provs = append(provs, p)
	}
}
agent := glue.NewAgent(glue.AgentOptions{
	Provider: glue.WithFailover(provs...),
	Model:    "", // let each provider use its DefaultModel
})

WithFailover only falls through before the first event commits to the consumer (Stream error, immediate ProviderEventError, or empty stream). Once any non-error event is observed, it commits to that provider for the rest of the turn. All-providers-failed surfaces as a typed *glue.FailoverError with per-provider attempts.

Roles

A role is a named instruction profile with an optional model override. Pass roles via AgentOptions.Roles or load them from <WorkDir>/roles/*.md with simple name: / description: / model: frontmatter.

agent := glue.NewAgent(glue.AgentOptions{
	Provider: gemini.New(gemini.Options{}),
	Model:    "gemini-2.5-flash",
	Roles: []glue.Role{
		{Name: "reviewer", Model: "gemini-2.5-pro", Instructions: "Review for SQL safety."},
		{Name: "writer", Instructions: "Write in plain English."},
	},
	Role: "writer", // agent default
})

session, _ := agent.Session(ctx, "review", glue.WithSessionRole("reviewer"))
result, _ := session.Prompt(ctx, "Review this PR.", glue.WithRole("reviewer"))

Effective role precedence: WithRole (call) > WithSessionRole (session)

AgentOptions.Role (agent). Effective model precedence: WithModel (call) > effective role's Model > AgentOptions.Model. Unknown role names return a typed error.

Project context and skills

Set AgentOptions.WorkDir to enable Markdown context discovery:

  • <WorkDir>/AGENTS.md is appended to the system prompt for every prompt on the agent's sessions (missing file is non-fatal).
  • <WorkDir>/.agents/skills/<name>/SKILL.md is loaded as a glue.Skill with optional name: and description: frontmatter.
agent := glue.NewAgent(glue.AgentOptions{
	Provider: gemini.New(gemini.Options{}),
	Model:    "gemini-2.5-flash",
	WorkDir:  ".",
})
session, _ := agent.Session(ctx, "skills")
result, err := session.Skill(ctx, "triage", map[string]int{"issue": 12})

Session.Skill renders the skill instructions, appends the args as formatted JSON, and runs the result through Session.Prompt. Unknown skill names return a typed error. Skills supplied via AgentOptions.Skills win on name collision over disk-discovered skills.

Versioned prompts

prompts.NewCatalog(fsys, dir, defaultVersion) wraps an embed.FS of <version>.md files so agents can A/B-test prompts and roll back without rebuilding history. Unknown versions return an error that lists every available version verbatim — silent fallback would hide A/B test misconfiguration.

import (
	"embed"

	"github.com/erain/glue/prompts"
)

//go:embed prompts/*.md
var promptFS embed.FS

cat, err := prompts.NewCatalog(promptFS, "prompts", "v2")
if err != nil { /* default version must exist at construction time */ }

systemPrompt, err := cat.Get("v1") // or cat.Get("") for the default

The catalog is read-only and concurrency-safe. Templating and variable substitution are intentionally out of scope; rendering is the caller's job.

Structured JSON results
var out struct {
	Name  string `json:"name"`
	Count int    `json:"count"`
}

_, err := session.PromptJSON(ctx, "Return a project name and count.", &out)

PromptJSON augments the prompt with JSON-only instructions and sets response_mime_type: application/json on the provider request. Pass glue.WithJSONSchema(schema) to forward an explicit JSON Schema (Gemini: response_json_schema). V1 validation is JSON decoding into the caller's Go type.

Tools

glue.NewTool[Args] decodes ToolCall.Arguments into a typed Go value before invoking the executor, so most tools no longer need a manual json.Unmarshal. Pair it with glue.TextResult / glue.ErrorResult for the result side:

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

weather := glue.NewTool[weatherArgs](
	glue.ToolSpec{
		Name:        "weather",
		Description: "Lookup 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
	},
)

Malformed arguments surface to the model as an error ToolResult rather than crashing the loop. Schema generation from Args is intentionally out of scope; supply Parameters explicitly.

stores/file writes one JSON file per session — the simple default, no new dependencies. For long-running agents that need cross-session recall (e.g. "what did I tell you about my dog last week?"), stores/sqlite implements the same glue.Store interface against a pure-Go SQLite database with FTS5 over message text:

import (
    "github.com/erain/glue"
    "github.com/erain/glue/stores/sqlite"
)

store, err := sqlite.Open(sqlite.Options{Path: "peggy.db"})
if err != nil { /* … */ }
defer store.Close()

agent := glue.NewAgent(glue.AgentOptions{
    Provider: /* … */,
    Store:    store,
})

The Searcher capability that lets callers query the FTS index is exposed through Agent.SearchSessions and Session.Search. The file store deliberately does not implement it — picking stores/sqlite is the signal that you want cross-session search.

hits, err := agent.SearchSessions(ctx, "Australian Shepherd",
    glue.WithLimit(5),
    glue.WithSince(time.Now().AddDate(0, -1, 0)), // last month
)
for _, h := range hits {
    fmt.Printf("[%s#%d] %s\n", h.SessionID, h.Index, h.Snippet)
}

// Scoped to one session:
hits, _ = session.Search(ctx, "what we decided about deployment")

Search options are functional: WithLimit, WithOffset, WithSessionID, WithSince, WithUntil. Session.Search ignores any WithSessionID and forces its own id. When the active store does not implement Searcher, both methods return glue.ErrSearchNotSupported — callers can fall back gracefully. The query string is forwarded straight to FTS5's MATCH syntax (bare words, "quoted phrases", AND / OR / NOT); hits are returned by BM25 score ascending (lower is better).

The implementation uses modernc.org/sqlite (no CGo, cross-compiles freely). Schema and FTS5 trigger details are in docs/adr/0007-memory-layer.md.

Testing without Gemini

The glue.Provider interface is small, so tests can drive sessions with a fake provider — no credentials required:

type fakeProvider struct{}

func (fakeProvider) Stream(_ context.Context, _ glue.ProviderRequest) (<-chan glue.ProviderEvent, error) {
	events := make(chan glue.ProviderEvent, 3)
	events <- glue.ProviderEvent{Type: glue.ProviderEventStart}
	events <- glue.ProviderEvent{Type: glue.ProviderEventTextDelta, Delta: "hello"}
	events <- glue.ProviderEvent{Type: glue.ProviderEventDone}
	close(events)
	return events, nil
}

func ExampleSession_Prompt() {
	ctx := context.Background()
	agent := glue.NewAgent(glue.AgentOptions{Provider: fakeProvider{}})
	session, _ := agent.Session(ctx, "test")
	result, _ := session.Prompt(ctx, "say hi")
	fmt.Println(result.Text)
	// Output: hello
}

The repository's own tests (glue/agent_test.go, loop/run_test.go, loop/tool_exec_test.go) use this pattern.

Run the tests

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

CI runs the same commands on every PR. The Gemini provider has a gated live smoke test:

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

Agents

Real agents built on the harness live under agents/ (peer of the harness itself), not examples/ (which holds tutorial-grade demos only).

  • agents/glue-review — a free, local pre-push branch reviewer. Reads the diff against main, deep-reads files when context demands it, and posts one sticky GitHub comment per PR — a short headline, ≤ 5 severity bullets, and a fenced ```markdown fix-instruction block downstream coding agents (Claude Code, Codex, Cursor, Aider, …) can paste and act on. Defaults to OpenRouter's openrouter/free meta-router; flags swap to NVIDIA build.nvidia.com or Gemini, with automatic provider failover.

    As a CLI:

    export OPENROUTER_API_KEY=sk-or-v1-...
    go run ./agents/glue-review              # review current branch vs main
    go run ./agents/glue-review --provider nvidia
    

    As a GitHub Action — drop into any repo:

    - uses: erain/glue/agents/glue-review@main
      with:
        openrouter-api-key: ${{ secrets.OPENROUTER_API_KEY }}
    

    See agents/glue-review/README.md for the full input/output contract and the eval evidence behind the current prompt.

  • agents/peggy — a long-running personal-assistant agent. v0.1 is a single-prompt CLI that remembers across sessions via sqlite + FTS5 and a token-aware summarizing compactor. Identity is injected from a Markdown SOUL.md; defaults to the codex provider (ChatGPT subscription via codex login). Tracker: #110.

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

    Peggy is also reachable on Telegram via the peggy-telegram binary — a chat-allowlisted bot built on the channel-adapter pattern. See agents/peggy/README.md for the config / identity / CLI surface and channels overview.

Examples

  • examples/local-agent is a small Gemini-backed tutorial CLI that registers a local_time tool, streams text to stdout, and persists sessions through stores/file. It's the shortest path from zero to "Glue agent that calls a Go function":

    export GEMINI_API_KEY=...
    go run ./examples/local-agent --prompt "Use local_time for America/Toronto." --id demo
    

CLI

A thin local CLI is built on the same library API:

go run ./cmd/glue run --prompt "Say hi" --id local-dev --store .glue/sessions

run flags:

  • --id — session id (default "default").
  • --prompt — prompt text (required).
  • --model — model id or gemini/<model> (default gemini-2.5-flash).
  • --store — file session store directory (default .glue/sessions).
  • --usage — print provider-reported token usage to stderr when available.
  • --usage-input-price, --usage-output-price, --usage-cache-read-price, --usage-cache-write-price — optional USD-per-1M-token prices used to append cost_usd=... to --usage output. Glue does not ship provider price tables.
  • --env.env file to load before reading GEMINI_API_KEY. Repeatable; shell environment wins on conflict.

The CLI streams text deltas to stdout, persists sessions through stores/file, and uses WorkDir="." so AGENTS.md, .agents/skills, and roles/ discovery work from the invocation directory. Errors return a non-zero exit code; missing GEMINI_API_KEY produces a clear message.

The same binary can also start the local HTTP+SSE daemon described in ADR-0010:

go run ./cmd/glue serve --store .glue/sessions

serve binds to 127.0.0.1:0 by default, generates a bearer token when --token / GLUE_DAEMON_TOKEN are not set, and writes connection metadata to glue/daemon.json under the user config directory with mode 0600. Startup output prints the effective base_url and metadata path but not the bearer token. Use --listen, --metadata, --work, and --permission-timeout to override daemon behavior.

Once serve is running, connect starts one daemon run, streams text events, and brokers permission requests in the terminal:

go run ./cmd/glue connect --inspect
go run ./cmd/glue connect --prompt "Say hi" --id local-dev

By default, connect reads the same metadata file written by serve. Use --base-url, --token, or --metadata when connecting to an explicit daemon endpoint. Use --inspect for a compact authenticated status-and-catalog preflight, or --status, --tools, --mcp-resources, or --mcp-prompts to render each view separately. When supported by the daemon, --inspect also includes memory entries; use --memory-limit to cap that section. Peggy daemons also support --mcp-read and --mcp-prompt for direct resource reads and prompt rendering, --memories for curated memory catalogs, --forget-memory for memory deletion, plus --recall for direct memory/session search. Each inspect/action mode also has a -json form for scripts. Add --usage to run or prompt-mode connect to print provider-reported token usage on stderr without changing streamed stdout. Add the --usage-*-price flags when you want a local USD estimate from prices you supply.

Peggy can serve the same daemon protocol with the personal-assistant agent, settings, memory store, and coding tools loaded once:

go run ./agents/peggy/cmd/peggy serve --coding --workdir .
go run ./cmd/glue connect --inspect
go run ./cmd/glue connect --memories
go run ./cmd/glue connect --forget-memory mem_123
go run ./cmd/glue connect --mcp-resources
go run ./cmd/glue connect --mcp-prompts
go run ./cmd/glue connect --mcp-read --server filesystem --uri file:///workspace/README.md
go run ./cmd/glue connect --mcp-prompt --server linear --name summarize_issue --arg issue=GLUE-123
go run ./cmd/glue connect --recall "Australian Shepherd"
go run ./cmd/glue connect --prompt "Say hi to Peggy" --id cli:daily

peggy serve writes metadata to the same default path as glue serve, so glue connect works without explicit connection flags. Permission requests for Peggy coding tools are brokered over the daemon stream and answered by the connected client.

Telegram can attach to that same daemon while preserving its chat allowlist and inline permission buttons:

go run ./agents/peggy/cmd/peggy-telegram --daemon

In daemon-client mode, allowlisted Telegram chats can also use /status, /roles, /role <name> <prompt>, /skills, /skill <name> key=value, /memories, /recall <query>, /recall_memories <query>, and /forget_memory <id> for role-shaped runs, reusable workflow runs, status checks, and memory inspection/search/correction.

Peggy can assign permission tiers by daemon client/channel in settings.json. The default remains prompt; read_only denies side-effecting tools before any client prompt, and trusted allows side-effecting tools without prompting while preserving workspace, binary, overwrite, timeout, and output limits:

{
  "permissions": {
    "default_tier": "prompt",
    "channels": {
      "cli": "trusted",
      "telegram": "prompt"
    }
  }
}
Standard flags for downstream agents

Agents that ship their own CLI binary share the same six flags (--provider, --model, --id, --store, --work, --max-turns). cli.RegisterStandardFlags wires them onto a flag.FlagSet and returns a getter:

import "github.com/erain/glue/cli"

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

--provider accepts a comma-separated list (e.g. nvidia,openrouter,gemini) which agents are expected to handle by chaining the providers registry with glue.WithFailover.

Adding a provider

Glue's Provider interface is small. See docs/provider-guide.md for the contract and common pitfalls, and examples/echo-provider for the shortest possible runnable implementation.

Roadmap

P0–P2 are shipped: the reusable loop, public Agent / Session API, file-backed sessions, structured JSON, skills, roles, the CLI runner, parallel tool execution, opt-in context compaction, the shell/filesystem tool extension packages (tools/fs, tools/git per docs/adr/0003-shell-filesystem-tools.md), the provider plugin guide (docs/provider-guide.md), and the GitHub issue automation workflow (docs/issue-automation.md). The current focus is hardening through dogfooding agents/glue-review and closing the agent-ergonomics wishlist (typed tools, provider failover, prompt catalog, stream writer, standard flags) plus the broader gaps in docs/flue-gap-analysis.md: multi-target deployment, sandbox connectors, subagent orchestration, MCP. See docs/project-plan.md and the project tracker (#1) for the next recommended issue.

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. Provider implementations live in subpackages (initially providers/gemini), and session persistence is provided by stores subpackages (initially stores/file).

Normalized message and event types are re-exported here so that callers only need to import "github.com/erain/glue".

Index

Constants

View Source
const (
	DefaultSearchLimit = 20
	MaxSearchLimit     = 100
)

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

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 = `` /* 580-byte string literal not displayed */

DefaultSummarizingSystemPrompt is the instruction the SummarizingCompactor sends to the provider when no override is configured. The prompt biases for fact retention over polish.

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 ErrSkipTool = loop.ErrSkipTool

ErrSkipTool re-exported from package loop.

Functions

This section is empty.

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

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 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
}

ExecCommand describes one argv-style command execution request.

type ExecResult

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

ExecResult is the captured result of one command execution.

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

	// 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.
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.
Package gemini adapts Google's Gemini API to Glue's provider interface.
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
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