A Go framework for building multi-agent LLM applications with a modular, composable architecture.
Inspired by AgentScope, agentscope-go brings idiomatic Go design to multi-agent orchestration — with first-class support for ReAct agents, tool use, multi-model providers, pipelines, A2A protocol, and OpenTelemetry tracing.
- ReAct Agent — Built-in ReAct (Reason + Act) agent with configurable max iterations, system prompts, and hooks
- Multi-Model Support — OpenAI, Anthropic (Claude), and Google Gemini with provider-specific formatters; custom base URLs for OpenAI-compatible APIs
- Streaming — SSE-based streaming for all model providers
- Tool System — Manual registration, reflection-based
RegisterFunc()with struct tags, and built-in tools (shell, print) - Pipelines — Sequential, Fanout (parallel), ChatRoom, and MsgHub for multi-agent workflows
- Memory Management — Multiple backends (InMemory, Redis, SQL) with mark-based filtering, long-term memory with semantic search
- Session Persistence — JSON file and Redis-backed session storage
- A2A Protocol — Agent-to-Agent HTTP server/client with service discovery and bus registry
- OpenTelemetry Tracing — Built-in OTLP gRPC exporter with agent hooks for automatic instrumentation
- Multimodal Messages — Text, thinking, tool use/result, image, audio, and video content blocks
- State Management — Thread-safe key-value state module for sharing data across agents
- Agent Skills — Load specialized instruction sets from
SKILL.mddirectories with YAML front matter; generate skill prompts dynamically for agent system prompts
go get github.com/vearne/agentscope-goRequires Go 1.21+.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/vearne/agentscope-go/pkg/agent"
"github.com/vearne/agentscope-go/pkg/formatter"
"github.com/vearne/agentscope-go/pkg/memory"
"github.com/vearne/agentscope-go/pkg/message"
"github.com/vearne/agentscope-go/pkg/model"
)
func main() {
ctx := context.Background()
m := model.NewOpenAIChatModel("gpt-4o", os.Getenv("OPENAI_API_KEY"), "", false)
f := formatter.NewOpenAIFormatter()
ag := agent.NewReActAgent(
agent.WithReActName("assistant"),
agent.WithReActModel(m),
agent.WithReActFormatter(f),
agent.WithReActMemory(memory.NewInMemoryMemory()),
)
msg := message.NewMsg("user", "Hello! What can you do?", "user")
resp, err := ag.Reply(ctx, msg)
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.GetTextContent())
}pkg/
├── agent/ # Agent interfaces and implementations (ReAct, User)
├── model/ # Model providers (OpenAI, Anthropic, Gemini)
├── formatter/ # Provider-specific message formatters
├── message/ # Message types and content blocks
├── memory/ # Memory backends (InMemory, Redis, SQL) and long-term memory
├── pipeline/ # Multi-agent orchestration (Sequential, Fanout, ChatRoom, MsgHub)
├── tool/ # Tool registration and execution
├── session/ # Session persistence (JSON, Redis)
├── a2a/ # Agent-to-Agent protocol (HTTP server/client/bus)
├── module/ # State management
└── tracing/ # OpenTelemetry tracing
All agents implement the AgentBase interface:
type AgentBase interface {
Reply(ctx context.Context, msg *message.Msg) (*message.Msg, error)
Observe(ctx context.Context, msg *message.Msg) error
Name() string
ID() string
}ReActAgent — The primary agent type. It follows the ReAct loop: the LLM reasons about the user's message, optionally calls tools, and returns a final response. Configurable via functional options:
ag := agent.NewReActAgent(
agent.WithReActName("my-agent"),
agent.WithReActModel(model),
agent.WithReActFormatter(formatter),
agent.WithReActMemory(memory.NewInMemoryMemory()),
agent.WithReActToolkit(toolkit),
agent.WithReActMaxIters(10),
agent.WithReActSystemPrompt("You are a helpful assistant."),
agent.WithReActPreReply(preHook),
agent.WithReActPostReply(postHook),
)UserAgent — Reads input from stdin. Useful for interactive pipelines.
// OpenAI (or OpenAI-compatible APIs)
m := model.NewOpenAIChatModel("gpt-4o", apiKey, "https://api.openai.com/v1", false)
// Anthropic
m := model.NewAnthropicChatModel("claude-sonnet-4-20250514", apiKey, "", false)
// Google Gemini
m := model.NewGeminiChatModel("gemini-2.5-pro", apiKey, "", false)Each provider supports both Call() (synchronous) and Stream() (SSE streaming) methods.
Formatters convert internal Msg objects into provider-specific request formats:
f := formatter.NewOpenAIFormatter() // for OpenAI
f := formatter.NewAnthropicFormatter() // for Anthropic
f := formatter.NewGeminiFormatter() // for Geminitk := tool.NewToolkit()
// Method 1: Manual registration
tk.Register("calculator", "Evaluate a math expression", params, calcFunc)
// Method 2: Reflection-based registration with struct tags
type CalcArgs struct {
Expression string `json:"expression" description:"The math expression to evaluate"`
}
tk.RegisterFunc(func(ctx context.Context, args CalcArgs) (*tool.ToolResponse, error) {
// ...
}, tool.RegisterOption{Name: "calc", Description: "Calculate"})
// Method 3: Built-in tools
tool.RegisterShellTool(tk) // execute_shell
tool.RegisterPrintTool(tk) // print_textAgent Skills allow you to load specialized instruction sets into a Toolkit from directory-based skill definitions. Each skill directory must contain a SKILL.md file with YAML front matter:
---
name: Weather Query
description: Query current weather conditions for any city.
---
# Weather Query Skill
Use the `execute_shell` tool to fetch weather data:
1. Run `curl -s "wttr.in/{city}?format=3"` for a brief summary
2. Run `curl -s "wttr.in/{city}"` for a detailed forecastRegister and manage skills:
tk := tool.NewToolkit()
// Register a skill from a directory containing SKILL.md
tk.RegisterAgentSkill("./weather_skill")
tk.RegisterAgentSkill("./code_review_skill")
// List registered skills
for _, s := range tk.GetAgentSkills() {
fmt.Printf(" - %s: %s\n", s.Name, s.Description)
}
// Generate a skill prompt for injection into agent system prompts
prompt := tk.GetAgentSkillPrompt()
// Use custom templates for the prompt
customPrompt := tk.GetAgentSkillPromptWithTemplate(
"# Available Skills\nUse these skills to assist the user.",
"- **%s**: %s (see %s/SKILL.md)",
)
// Remove a skill by name
tk.RemoveAgentSkill("Weather Query")The generated prompt instructs the LLM about available skills and how to access their detailed instructions. You can inject the prompt into your agent's system prompt via WithReActSystemPrompt().
Sequential — Agents run one after another, passing the output forward:
result, err := pipeline.SequentialPipeline(ctx, []agent.AgentBase{agent1, agent2, agent3}, msg)Fanout — Agents run in parallel on the same input:
results, err := pipeline.FanoutPipeline(ctx, []agent.AgentBase{agent1, agent2, agent3}, msg)ChatRoom — Multi-round conversation where all agents observe each other's messages:
cr := pipeline.NewChatRoom([]agent.AgentBase{agent1, agent2}, announcement, 5)
history, err := cr.Run(ctx, msg)MsgHub — Broadcast and gather pattern with dynamic participants:
hub := pipeline.NewMsgHub(participants, announcement)
hub.Broadcast(ctx, msg) // send to all
responses, _ := hub.Gather(ctx, msg) // collect from all
hub.Add(newAgent) // add participant
hub.Remove(oldAgent) // remove participant// JSON file session
session := session.NewJSONSession("conversation.json")
session.Save(ctx, agent.Memory()) // persist
session.Load(ctx, agent.Memory()) // restore
// Redis session
redisSess := session.NewRedisSession("localhost:6379", "session:123",
session.WithRedisPassword("secret"),
session.WithRedisDB(0),
session.WithRedisTTL(24 * time.Hour),
)
redisSess.Save(ctx, agent.Memory())Expose a local agent as an HTTP service:
card := a2a.AgentCard{Name: "my-agent", ID: "agent-1", Endpoint: "http://localhost:8080"}
srv := a2a.NewA2AServer(agent, card)
srv.Start(":8080")Call a remote agent:
client := a2a.NewA2AClient("http://localhost:8080")
client.Discover(ctx) // fetch agent card
resp, _ := client.Reply(ctx, msg) // call remote agentService registry:
bus := a2a.NewA2ABus()
bus.Register(card)
bus.List() // all agents
bus.Get("id") // by IDshutdown, err := tracing.SetupTracing(ctx, "localhost:4317", tracing.WithInsecure())
defer shutdown(ctx)
// Create spans around agent operations
ctx, span := tracing.StartSpan(ctx, "agent.reply", attribute.String("agent", ag.Name()))
defer span.End()Use WithReActPreReply / WithReActPostReply hooks for automatic span creation.
Messages support multimodal content blocks:
msg := message.NewMsg("user", "Hello", "user")
// Text
msg.SetContent("plain text")
// Structured content blocks
msg.Content = []message.ContentBlock{
message.NewTextBlock("Describe this image"),
message.NewImageBlock(message.NewURLSource("https://example.com/img.png")),
}
// Tool use / result
message.NewToolUseBlock("tool-1", "calculator", map[string]interface{}{"expr": "2+2"})
message.NewToolResultBlock("tool-1", "4", false)
// Thinking (extended thinking models)
message.NewThinkingBlock("Let me think step by step...")See the examples directory for complete, runnable examples:
| Example | Description |
|---|---|
| hello | Basic agent creation and single-turn conversation |
| react_agent | ReAct agent with tool usage (calculator, weather) |
| streaming | Streaming model responses with SSE |
| tool_usage | Tool registration methods: manual, reflection, built-in |
| agent_skill | Agent skills: register SKILL.md directories, generate skill prompts, custom templates |
| session_persistence | Save/restore agent memory via JSON session |
| multi_model | Using OpenAI, Anthropic, and Gemini providers |
| multi_agent | Sequential pipeline and ChatRoom |
| fanout_pipeline | Parallel execution with FanoutPipeline |
| msghub | MsgHub broadcast/gather with dynamic participants |
| debate | Multi-agent debate with judge |
| deep_agent | DeepAgent with context compression, offloading, and subagent delegation |
| werewolves | Nine-player werewolves game with role-based gameplay |
| a2a_agent | A2A protocol: server, client, and bus |
| tracing | OpenTelemetry tracing with agent hooks |
# OpenAI (default)
export OPENAI_API_KEY="sk-..."
# Anthropic
export ANTHROPIC_API_KEY="sk-ant-..."
# Google Gemini
export GEMINI_API_KEY="AIza..."make build # build all packages
make test # run tests with race detector
make lint # run golangci-lint
make tidy # tidy modules
make fmt # format with gofmt and goimportsThis project is licensed under the terms found in the LICENSE file.