goat combines an agent compiler, an asynchronous runtime, persistent conversation context, extensible tools, Milvus retrieval, multi-provider embeddings, structured prompt building, and typed streams in one Go module. Its first-class goatc compiler turns YAML plus provider-backed toolsβlocal Go plugins, gRPC services, and MCP serversβinto a single distributable Agent executable with an interactive Bubble Tea UI. The agent layer is built on CloudWeGo Eino and accepts any model.AgenticModel implementation.
Features
- Agent compiler (
goatc) β combine local Go plugins, multiple gRPC tools, and multiple MCP servers through one provider-based YAML schema, then emit one interactive executable.
- Built-in terminal UI β stream final answers, inspect tool execution in real time, continue conversations, steer active runs, cancel work, and monitor token usage.
- Native tool calling β execute one or more model-selected tools in an agent loop.
- Model agnostic β use Eino adapters for OpenAI, Azure OpenAI, Claude, Gemini, or another compatible provider.
- Context management β choose RAM, local files, SQLite, or MySQL and resume a conversation by
ContextUID.
- Live steering β queue one or more user messages while an agent runs and apply them at the next protocol-safe turn boundary.
- Context compression β compact long tool histories with precise, aggressive, or no-model discard strategies.
- Extensible tools β register Go functions, MCP tools, Go shared libraries, or gRPC plugins.
- Planning and skills β expose built-in planning tools and load skills on demand from a
skills/ directory.
- Streaming execution β consume typed tool and final-answer steps, token usage, callbacks, and final-answer webhooks.
- Multimodal input and output β pass image URLs, Base64 data, or binary images through supported models and tools.
- Milvus retrieval β use dense vector, BM25, or hybrid retrieval with filters, partitions, and JSON fields.
- Reusable primitives β multi-provider embeddings, a fluent prompt builder, and concurrent generic streams.
goatc agent compiler
goatc is the shortest path from Go tools to a runnable Agent. Instead of writing model initialization, plugin loading, context management, streaming, and terminal UI glue by hand, describe the Agent in YAML and compile it:
Local Go tools + gRPC services + MCP servers + goatc.yaml
β
βΌ
goatc
β
βββ builds local tools as native .so plugins
βββ configures remote tool providers
βββ embeds normalized YAML and local plugins
βββ builds the Bubble Tea application
β
βΌ
one distributable executable
What the generated executable includes
- OpenAI, Claude/Anthropic, or Gemini model initialization driven by environment-based credentials.
- Provider-based tools: compiled and embedded Go plugins, multiple goat gRPC tool services, and MCP servers over stdio, SSE, or Streamable HTTP.
- RAM, file, or SQLite conversation persistence.
- Planning, parallel tool execution, context compression, per-run skill directories, and special requirements.
- A Bubble Tea interface with streamed answers, live tool status and results, multi-turn history, active-run steering, cancellation, and token accounting.
A minimal project looks like this:
my-agent/
βββ go.mod
βββ goatc.yaml
βββ tools/
βββ search/ # package main; exports New() toolplugin.ToolPlugin
βββ workspace/ # package main; exports New() toolplugin.ToolPlugin
version: v1
agent:
name: research-agent
model_max_tokens_k: 128
max_steps: 12
enable_planning: true
parallel_tools: 3
compress: true
skills_dir: ./skills
model:
provider: openai
name: gpt-5
api_key_env: OPENAI_API_KEY
context:
backend: file
path: data/conversations
tools:
# Omit provider for a backward-compatible local Go plugin.
- name: search
source: ./tools/search
- provider: go_plugin
name: workspace
source: ./tools/workspace
# Add as many goat gRPC tool services as needed.
- provider: grpc
name: translator
address: 127.0.0.1:50051
- provider: grpc
address: 127.0.0.1:50052
# Each MCP server can expose multiple tools.
- provider: mcp
name: filesystem
transport: stdio
command: npx
args: [-y, "@modelcontextprotocol/server-filesystem", /workspace]
- provider: mcp
transport: streamable_http
url: https://mcp.example.com/mcp
headers:
Authorization: Bearer ${MCP_TOKEN}
build:
output: ./dist/research-agent
tui:
welcome: Ask me to research, analyze, or modify the workspace.
Install, validate, build, and run:
go install github.com/torrischen/goat/goatc@latest
goatc validate -f goatc.yaml
goatc build -f goatc.yaml
export OPENAI_API_KEY="your-api-key"
export MCP_TOKEN="your-mcp-token"
./dist/research-agent
The tools[].provider field is the common extension point. go_plugin compiles and embeds a local source directory, grpc imports a goat PluginService by address, and mcp initializes a stdio, SSE, or Streamable HTTP server and registers every tool it exposes. Add multiple entries of any provider type in one Agent. MCP values such as ${MCP_TOKEN} are resolved from the runtime environment, so secrets do not need to be embedded in YAML.
Each local source directory is one plugin build unit and follows the existing New() toolplugin.ToolPlugin contract. The Agent executable and its local plugins are built together with the same Go toolchain and build tags. The output is one delivery artifact; at startup it extracts embedded plugins to a temporary directory because Go's plugin.Open requires filesystem paths. Remote-only configurations build without embedded .so files. A configured skills_dir remains a runtime directory and is not embedded.
Go plugins are built natively and supported on Linux, macOS, and FreeBSD. See the complete goatc guide for provider configuration, tool contracts, keyboard controls, and build details.
Packages
| Package |
Purpose |
goatc |
YAML-driven Agent compiler with Go plugin, gRPC, and MCP tool providers plus a Bubble Tea interface. |
agent/react |
Asynchronous native function-calling agent runtime. |
agent/common |
Agent, tool, step, callback, and multimodal contracts. |
agent/contextmgr |
Context manager interface plus RAM, file, SQLite, and MySQL backends. |
agent/tools |
Planning, skills, terminal, and shell tools. |
agent/toolplugin |
Go shared-library and gRPC tool plugins. |
embedder |
Embedding clients for OpenAI-compatible APIs, Gemini, Cohere, Voyage AI, and Ollama. |
retriever/milvus |
Vector, BM25, and hybrid Milvus retrievers. |
prompt |
Fluent Markdown prompt builder. |
streaming |
Concurrent, type-safe generic streams. |
flowchart LR
Source[YAML + Go Β· gRPC Β· MCP providers] --> Goatc[goatc compiler]
Goatc --> Binary[Agent executable]
Binary --> TUI[Bubble Tea TUI]
Binary --> Agent[Agent runtime]
SDK[Go SDK application] --> Agent
Agent --> Model[Eino AgenticModel]
Agent --> Tools[Go Β· MCP Β· gRPC Β· shared-library tools]
Agent --> ContextManager[RAM Β· files Β· SQLite Β· MySQL]
Agent --> Steps[Typed step stream]
SDK --> Retriever[Retriever]
Retriever --> Milvus[(Milvus)]
Retriever --> Embedder[Embedder]
Requirements
- Go 1.25.8 or newer.
- Credentials for the model or embedding provider you choose.
- Milvus 2.6 only when using the retriever packages.
- CGO and a C compiler when using the SQLite context manager.
- Linux, macOS, or FreeBSD for
goatc shared-library builds; Agent and plugins are built natively for the current OS and architecture.
Installation
Install the Agent compiler from source:
go install github.com/torrischen/goat/goatc@latest
Prebuilt goatc archives for Linux, macOS, Windows, and FreeBSD (amd64 and arm64) are attached to every GitHub Release, together with SHA256SUMS.
When embedding goat as a library, install only the packages your application needs:
go get github.com/torrischen/goat/agent/react
go get github.com/torrischen/goat/agent/contextmgr/ram
go get github.com/cloudwego/eino-ext/components/model/agenticopenai
Optional components can be added independently:
go get github.com/torrischen/goat/retriever/milvus/hybrid
go get github.com/torrischen/goat/prompt
go get github.com/torrischen/goat/streaming
SDK quick start
Use this path when embedding goat directly into a Go application. For a YAML-driven, ready-to-run Agent executable, start with goatc.
Set an API key:
export OPENAI_API_KEY="your-api-key"
Create and run an agent:
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"github.com/cloudwego/eino-ext/components/model/agenticopenai"
"github.com/torrischen/goat/agent/common"
"github.com/torrischen/goat/agent/contextmgr/ram"
"github.com/torrischen/goat/agent/react"
"github.com/torrischen/goat/streaming"
)
func main() {
ctx := context.Background()
llm, err := agenticopenai.NewResponsesModel(ctx, &agenticopenai.ResponsesConfig{
APIKey: os.Getenv("OPENAI_API_KEY"),
Model: "gpt-5.2",
})
if err != nil {
log.Fatal(err)
}
// 128 means an approximately 128K-token model context window.
agent := react.NewAgent(llm, 128, ram.NewRAMContextManager())
contextUID, steps, err := agent.Do(ctx, &common.AgentDoArgs{
UserInput: common.AgentUserInput{
Text: "Explain why typed streams are useful in Go in three bullets.",
},
MaxStep: 8,
})
if err != nil {
log.Fatal(err)
}
for {
step, err := steps.ReadWithContext(ctx)
switch {
case errors.Is(err, streaming.ErrStreamClosed):
fmt.Printf("\nConversation: %s\n", contextUID)
return
case err != nil:
log.Fatal(err)
case step.IsFinalAnswer:
fmt.Println(step.Observation)
}
}
}
Agent.Do stores the user message, starts the agent loop in the background, and immediately returns a ContextUID plus a stream for that run. Consume the stream until streaming.ErrStreamClosed before starting another turn with the same ContextUID.
While the run is active, queue additional user messages with Steer:
err = agent.Steer(ctx, &common.AgentSteerArgs{
ContextUID: contextUID,
UserInputs: []common.AgentUserInput{
{Text: "Do not deploy yet."},
{Text: "Run all tests first."},
},
})
The messages are queued in the context manager and applied after the next complete tool turn. A final answer always wins and discards messages that are still pending at that boundary. After final commit, Steer returns contextmgr.ErrConversationFinalized; the next Do user input reopens steering.
Tools use JSON Schema parameters and return text or multimodal results:
weatherTool := common.NewDefaultTool(
"get_weather",
"Return the current weather for a city.",
common.NewToolParameters(common.ToolProperty{
Name: "city",
Type: "string",
Required: true,
Description: "City name, for example Tokyo.",
}),
func(_ *common.AgentContext, input map[string]any) common.ToolResult {
city, ok := input["city"].(string)
if !ok || city == "" {
return common.NewDefaultToolResult("city must be a non-empty string")
}
return common.NewDefaultToolResult(
fmt.Sprintf("The weather in %s is clear and 22Β°C.", city),
)
},
)
agent.AddTool(ctx, weatherTool)
Use ToolProperty.Items and ToolProperty.Properties for array and nested-object schemas. Tool names are normalized for model compatibility, and duplicate names receive a numeric suffix.
_, planningSteps, err := agent.Do(ctx, &common.AgentDoArgs{
UserInput: common.AgentUserInput{Text: "Analyze this project and propose a refactor."},
MaxStep: 12,
EnablePlanning: true,
Compress: true,
CompressionOptions: common.CompressionOptions{
Strategy: common.CompressionStrategyPrecise,
RecentMessages: 12,
},
ToolExecutionOptions: &common.ToolExecutionOptions{
EnableParallel: true,
MaxConcurrency: 4,
},
})
Consume planningSteps in the same way as the quick-start stream; the run is complete when the stream closes.
Conversation context management
Pass a contextmgr.ContextManager implementation to react.NewAgent. Passing nil uses a file.FileContextManager rooted at data/conversations.
| Backend |
Constructor |
Best suited for |
| RAM |
ram.NewRAMContextManager() |
Tests and short-lived processes. |
| Files |
file.NewFileContextManager("") |
Simple local atomic-file persistence. |
| SQLite |
sqlite.NewSQLiteContextManager("") |
Durable single-node applications. |
| MySQL |
mysql.NewMysqlContextManager(...) |
Shared, multi-process deployments. |
Continue a completed conversation by passing the returned ID into the next run:
_, nextSteps, err := agent.Do(ctx, &common.AgentDoArgs{
ContextUID: contextUID,
UserInput: common.AgentUserInput{Text: "Summarize our conversation."},
})
Retrieval
The Milvus integration exposes three retrieval strategies behind similar collection, partition, write, search, upsert, and delete APIs:
| Retriever |
Search |
Embedder required |
vector |
Dense semantic similarity |
Yes |
bm25 |
Keyword/full-text relevance |
No |
hybrid |
Vector + BM25 with RRF or weighted reranking |
Yes |
Retrievers support scalar and JSON-path filters, custom JSON fields and indexes, pagination, and partition management. See the Retriever guide for a complete hybrid-search example and operational notes.
Model providers
react.NewAgent accepts Eino's model.AgenticModel interface. Provider authentication, endpoints, and provider-specific options stay in the selected Eino adapter:
agenticopenai for OpenAI Responses and Azure OpenAI.
agenticclaude for Claude.
agenticgemini for Gemini and Vertex AI.
See the Agent SDK guide for provider setup, MCP registration, skills, multimodal messages, callbacks, webhooks, and plugin loading.
Documentation
Security
Model-generated tool arguments are untrusted input. Validate parameters, enforce authorization and idempotency for side effects, apply timeouts, and run privileged tools in a sandbox. In particular, agent/tools.Terminal and agent/tools.ShellCommand execute commands with the permissions of the current process and should only be registered in controlled environments.
Never commit provider credentials or include them directly in tool output, logs, or persisted conversation context.
Development
git clone https://github.com/torrischen/goat.git
cd goat
go mod download
go test ./...
When changing the gRPC plugin protocol, regenerate its Go bindings with:
make proto
Issues and pull requests are welcome. Please format Go changes with gofmt and include tests for new behavior. See the contribution guide, security policy, code of conduct, and changelog for project policies.
License
goat is distributed under the BSD 3-Clause License.