Go Agents SDK
A Go SDK for building AI agents, reimagined around a filesystem-first
design inspired by Vercel's Eve.
Core agent capabilities live in conventional files and folders, so projects are
easy to inspect, extend, and operate — while the underlying engine remains a
plain Go library with multi-provider support, tools, skills, handoffs,
guardrails, memory, and tracing.

The filesystem is the authoring interface
An agent is a directory. Conventional files describe its behaviour:
myagent/
agent.yaml # model, provider, temperature, tools
instructions.md # system prompt
skills/
refunds.md # on-demand knowledge (YAML front-matter + markdown body)
subagents/
billing/ # a delegated agent (handoff), same layout recursively
agent.yaml
instructions.md
schedules/
nightly.yaml # cron triggers
channels/ # integration adapters (HTTP is built in)
| Path |
Purpose |
agent.yaml |
Model, provider, sampling params, and the tools the agent may use |
instructions.md |
The system prompt |
skills/*.md |
Procedures/knowledge surfaced to the model and loaded on demand |
subagents/<n>/ |
Specialized agents the parent can hand off to |
schedules/*.yaml |
Cron expressions that trigger the agent autonomously |
agent.yaml
name: assistant
provider: openai # openai | anthropic | gemini | ollama
model: gpt-4o
temperature: 0.7
max_tokens: 1024
tools:
- current_time
- add
A skill (skills/weather.md)
---
name: weather
description: How to answer questions about the weather using a public API.
---
1. Determine the location the user is asking about.
2. Use the `http_get` tool to fetch https://wttr.in/<location>?format=3.
3. Summarize the result in one friendly sentence.
Skills are not dumped into the prompt. Instead the model sees a catalog of
skill names and descriptions and pulls a skill's full body only when needed,
via the built-in load_skill tool.
Remote skills (pinned by commit SHA)
Skills can be shared across projects — like a plugin marketplace — by declaring
remote sources in agent.yaml. They're resolved at load time (never by
the model at runtime), pinned to an immutable commit SHA, integrity-checked
against an optional sha256, restricted to an allowlisted host, and cached
locally. This gives you marketplace ergonomics without the prompt-injection/SSRF
risk of fetching model-supplied URLs.
# agent.yaml — local skills/ are loaded too, in addition to these
skills:
- source: github.com/acme/agent-skills/refunds.md
ref: a1b2c3d4 # commit SHA — required for github.com sources
sha256: 9f86d0… # optional integrity pin
- source: https://example.com/pinned/skill.md
sha256: 0b1c2d…
Defaults are safe: only raw.githubusercontent.com / gist.githubusercontent.com
are allowed unless you widen the allowlist via loader.LoadWithOptions.
Subagent handoffs: three context modes
Each subagent handoff can carry context in one of three ways, set per subagent
in the parent's agent.yaml:
subagents:
researcher: forked # shared (default) | fresh | forked
| Mode |
Subagent sees |
Returns to parent? |
Use case |
| shared |
the full live history |
no (transfers control) |
routing/triage |
| fresh |
only the task |
yes |
clean delegation, no context bleed |
| forked |
a copy of the history |
yes |
independent exploration; parent resumes unaffected |
In code: agents.WithHandoffs(sub) (shared) or
agents.WithHandoff(sub, agents.ContextFresh|ContextForked). The model triggers
a handoff by calling the auto-generated handoff_<name> tool; the runner
intercepts it and applies the configured mode.
MCP servers (Model Context Protocol)
Agents gain real, dynamic tools by connecting to MCP servers declared in
agent.yaml. Servers are connected at load time over stdio (local
subprocess) or streamable-HTTP (remote), their tools are listed and adapted
into the SDK's tool interface, and they're closed when the agent is. This is
also how a CLI-run agent gets tools beyond the three builtins — without
recompiling.
# agent.yaml
mcp:
- name: github # tools are exposed to the model as github_<tool>
transport: stdio
command: ["npx", "-y", "@modelcontextprotocol/server-github"]
env: { GITHUB_TOKEN: "${GITHUB_TOKEN}" } # $VARs are expanded
- name: search
transport: http
url: https://mcp.example.com
headers: { Authorization: "Bearer ${SEARCH_KEY}" }
tools: [web_search] # optional allowlist of tool names
Built on the official modelcontextprotocol/go-sdk.
Servers are declared in config, never chosen by the model; tool names are
namespaced by server to avoid collisions. From code, use mcp.ConnectAll and
add manager.Tools() to an agent, or just let the loader do it. Remember to
defer agent.Close() to release MCP connections.
Observability: logging & tracing
Every run is instrumented. The runner emits structured logs (log/slog) and a
span tree covering the run, each turn, every LLM call, tool execution
(including skills' load_skill and MCP tools), guardrails, and handoffs/
delegations.
runner := agents.NewRunner(
agents.WithProvider(p),
agents.WithLogger(tracing.NewLogger(os.Stderr, slog.LevelDebug)), // structured logs
agents.WithTracer(tracing.NewConsoleTracer()), // indented span tree
)
result, _ := runner.Run(ctx, agent, "hello")
// The full trace tree is always captured programmatically, regardless of tracer:
for _, span := range result.Traces {
fmt.Printf("%*s%s %s\n", span.Depth*2, "", span.Name, span.Duration)
}
Console trace output looks like:
[trace] agent.run (12.4ms) agent=assistant turns=2 tokens=345
[trace] turn (8.1ms) n=0 agent=assistant
[trace] llm.complete (6.0ms) model=gpt-4o tokens=210 tool_calls=1
[trace] tool.execute (1.2ms) tool=add args={"a":5,"b":3} result_chars=1
[trace] turn (3.9ms) n=1 agent=assistant
[trace] llm.complete (3.8ms) model=gpt-4o tokens=135 tool_calls=0
Tracers available in pkg/tracing: NewConsoleTracer, NewSlogTracer,
NewRecorder (programmatic capture), and NewTee to combine them. With the
CLI, enable both with -v/--verbose, or set EVE_LOG=debug / EVE_TRACE=1.
The eve CLI
go build -o eve ./cmd/eve
eve init myagent # scaffold a new agent directory
eve validate myagent # load and report what was found (no API key needed)
eve run myagent "what time is it?" # run a single turn
eve dev myagent # serve over HTTP: POST /chat {"input": "..."}
eve schedules myagent # run the agent's cron schedules
Provider credentials come from the environment:
export PROVIDER=openai # optional; otherwise auto-detected
export OPENAI_API_KEY=... # or ANTHROPIC_API_KEY / GEMINI_API_KEY / OLLAMA_HOST
Because Go tools are compiled functions, agent.yaml references tools by
name. The CLI ships a small builtin registry (current_time, add,
http_get). To use your own Go tools, load the agent from your own program
with a custom registry (see below).
Library API
The CLI is a thin shell over the library. Load a filesystem agent with your own
tools and run it:
package main
import (
"context"
"fmt"
"github.com/ryanhill4L/agents-sdk/pkg/agents"
"github.com/ryanhill4L/agents-sdk/pkg/loader"
"github.com/ryanhill4L/agents-sdk/pkg/providers"
"github.com/ryanhill4L/agents-sdk/pkg/tools"
)
func main() {
// 1. Register your Go tools by name.
reg := tools.NewRegistry()
reg.MustRegister(tools.NewTool("add", "Adds two numbers",
func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
return args["a"].(float64) + args["b"].(float64), nil
},
tools.Param{Name: "a", Type: "number", Required: true},
tools.Param{Name: "b", Type: "number", Required: true},
))
// 2. Load the agent from the filesystem.
agent, err := loader.Load("myagent", reg)
if err != nil {
panic(err)
}
// 3. Run it with a provider resolved from the environment.
provider, _ := providers.Resolve(agent.Provider)
runner := agents.NewRunner(agents.WithProvider(provider))
result, err := runner.Run(context.Background(), agent, "What is 21 + 21?")
if err != nil {
panic(err)
}
fmt.Println(result.FinalOutput)
}
You can also build agents entirely in code with functional options
(agents.NewAgent, agents.WithInstructions, agents.WithTools,
agents.WithSkills, agents.WithHandoffs, …) — the loader simply translates
files into those calls.
Packages
| Package |
Responsibility |
pkg/agents |
Agent, Runner, the turn loop, handoffs, skills wiring |
pkg/loader |
Builds an Agent from a directory (agent.yaml, instructions.md, …) |
pkg/skills |
Skill parsing, the catalog, and the load_skill builtin tool |
pkg/tools |
Tool interface, Registry, FunctionTool (reflection), SimpleTool |
pkg/mcp |
Connects MCP servers (stdio/HTTP) and adapts their tools to tools.Tool |
pkg/channels |
Integration adapters; built-in HTTP channel (powers eve dev) |
pkg/schedules |
Cron parsing and a dependency-free scheduler |
pkg/providers |
OpenAI, Anthropic, Gemini, and Ollama integrations + Resolve |
pkg/memory |
SQLite-backed session persistence |
pkg/guardrails |
Pluggable input validation |
pkg/tracing |
Span-based observability (no-op and console tracers) |
Providers
| Provider |
Env var |
Notes |
| OpenAI |
OPENAI_API_KEY |
GPT-4o and friends |
| Anthropic |
ANTHROPIC_API_KEY |
Claude models |
| Gemini |
GEMINI_API_KEY |
Google Gemini |
| Ollama |
OLLAMA_HOST |
Local models (default :11434) |
Examples
Development
make build # build all packages
make build-cli # build the eve CLI into bin/eve
make test # run tests
make run-fs-example # validate the filesystem-first example
make check # fmt + vet + tidy
License
MIT — see LICENSE.