Skip to content

ryanhill4L/agents-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

Go Reference License: MIT

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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors