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.
agent := glue.NewAgent(glue.AgentOptions{
Provider: gemini.New(gemini.Options{}),
Model: "gemini-2.5-flash",
Tools: []glue.Tool{weatherTool},
})
session, _ := agent.Session(ctx, "demo")
result, _ := session.Prompt(ctx, "What's the weather in Toronto?")
fmt.Println(result.Text)- New here and want to build an agent? → docs/building-agents.md
- Just want to send a prompt? → Quickstart
- Want the architecture? → Concepts · docs/design.md
go get github.com/erain/glueModule 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. |
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-2.5-flash",
})
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.
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).
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:
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.
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.
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.
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. 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).
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.
A thin CLI over the same library API, for trying things without writing
a main.go:
# 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."
# 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 demorun flags include --provider, --model, --id, --store,
--work, --coding (+ --allow-binary, --coding-allow-overwrite),
--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.MaxTurnsReal 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 againstmain, deep-reads files when needed, and posts one sticky GitHub comment with a fenced```markdownfix block downstream coding agents can paste. Runs as a CLI or a GitHub Action. Defaults toopenrouter/freewith 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?"
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.
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 LiveThe harness is feature-complete for the 0.x series, tagged at
v0.1.0, and in active use behind the
agents/glue-review and
agents/peggy reference agents. The library remains
pre-1.0: the public Agent / Session surface is stable in practice,
but minor versions may still break API. The full stability stance is
ADR-0013; 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.