bond

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 11 Imported by: 0

README

Bond

Bond

Test Go Reference Release

The name's Bond. Agent Bond.

A(nother) Go framework for building agentic applications. Bond provides the streaming loop, tool execution, orchestration primitives, and runtime integrations — you bring the model. License to build.

Features

  • Streaming agent loop with parallel tool execution and context cancellation
  • Provider-agnostic — implement bond.Agent for any LLM (Bedrock included)
  • Hooks and plugins for cross-cutting concerns (logging, guardrails, metrics)
  • Orchestration patterns — graphs (LangGraph-style) and swarms (OpenAI Swarm-style)
  • A2A protocol support — remote agent communication, tool delegation
  • AgentCore runtime — deploy to AWS Bedrock AgentCore with A2A, HTTP, and MCP handlers
  • MCP tool adapter — use MCP server tools in your agent
  • Tool registry — expose large tool collections through a discovery gateway
  • Zero external deps in the root — sub-packages isolate SDK dependencies

Install

go get github.com/nisimpson/bond

Quick Start

package main

import (
    "context"
    "fmt"

    "github.com/nisimpson/bond"
    "github.com/nisimpson/bond/provider/bedrock"
    "github.com/aws/aws-sdk-go-v2/config"
    bedrockrt "github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
)

func main() {
    ctx := context.Background()
    cfg, _ := config.LoadDefaultConfig(ctx)
    client := bedrockrt.NewFromConfig(cfg)

    // Every agent needs a mission briefing.
    agent := bedrock.New(client, bedrock.AgentOptions{
        ModelID: "anthropic.claude-sonnet-4-20250514-v1:0",
        System:  "You are a secret agent. Be helpful, be discreet.",
    })

    resp, err := bond.Invoke(ctx, agent, bond.TextPrompt("Brief me on the situation."), bond.AgentOptions{})
    if err != nil {
        panic(err)
    }
    fmt.Println(resp.Text)
}

Core Concepts

Agent

The bond.Agent interface is the single abstraction at the center of everything:

type Agent interface {
    Stream(ctx context.Context, messages []Message) iter.Seq2[StreamEvent, error]
}

Anything that implements Agent can participate in bond: model providers, A2A proxies, graphs, swarms, test doubles.

Stream and Invoke
// Streaming — live intel as it arrives
for event, err := range bond.Stream(ctx, agent, messages, opts) { ... }

// Synchronous — debrief in one shot
resp, err := bond.Invoke(ctx, agent, messages, opts)
Tools (Gadgets)

Every good agent needs gadgets. Define them with NewFuncTool:

laserWatch, _ := bond.NewFuncTool(
    func(ctx context.Context, in CutInput) (CutOutput, error) {
        return CutOutput{Result: "lock disabled"}, nil
    },
    bond.FuncToolOptions{
        Name:        "laser_watch",
        Description: "Cuts through locks and barriers",
        InputSchema: tool.SchemaFor[CutInput](),
    },
)

Equip your agent:

bond.Stream(ctx, agent, msgs, bond.AgentOptions{
    Tools:    []bond.Tool{laserWatch, grappleHook, smokeBomb},
    MaxTurns: 10,
})
Plugins (Q Branch)

Plugins bundle gadgets and lifecycle hooks — your personal Q Branch:

bond.Stream(ctx, agent, msgs, bond.AgentOptions{
    Plugins: []bond.Plugin{surveillancePlugin, counterIntelPlugin},
})

Package Structure

bond/                        Core interfaces (Agent, Tool, Block, Stream, Invoke)
bond/agent/                  Orchestration: graph, swarm, A2A adapter, AsTool
bond/provider/bedrock/       Amazon Bedrock Converse streaming provider
bond/runtime/agentcore/      AWS AgentCore handlers (A2A, HTTP, MCP)
bond/tool/                   Tool infrastructure (schema, MCP adapter, structured output)
bond/extra/delegation/       A2A tool delegation (client + server)
bond/extra/toolregistry/     Tool discovery gateway plugin
bond/bondtest/               Test utilities (deterministic agent, event helpers)

Orchestration

Graph (Mission Planning)

Route between agents with conditional edges and shared state:

g := agent.NewGraph("intake", agent.GraphOptions{})

g.AddNode("intake", &agent.GraphNode{Agent: dispatchAgent})
g.AddNode("fieldwork", &agent.GraphNode{Agent: fieldAgent})
g.AddNode("gather_intel", &agent.GraphNode{
    Action: func(ctx context.Context, state agent.State) error {
        state.Set("dossier", fetchDossier(ctx))
        return nil
    },
})

g.AddConditionalEdge("intake", func(state agent.State) string {
    threat, _ := state.Get("threat_level")
    if threat == "critical" { return "gather_intel" }
    return agent.EndNode
})
g.AddEdge("gather_intel", "fieldwork")

bond.Invoke(ctx, g, bond.TextPrompt("new assignment"), bond.AgentOptions{})
Swarm (Multi-Agent Cell)

Agents transfer control dynamically — a cell of operatives:

s := agent.NewSwarm("handler", agent.SwarmOptions{})

s.AddAgent("handler", &agent.SwarmAgent{
    Agent:       handlerAgent,
    Description: "Coordinates operations and dispatches field agents.",
})
s.AddAgent("infiltrator", &agent.SwarmAgent{
    Agent:       infiltratorAgent,
    Description: "Specializes in social engineering and access.",
})
s.AddAgent("analyst", &agent.SwarmAgent{
    Agent:       analystAgent,
    Description: "Processes intelligence and provides situational analysis.",
})

// The handler decides when to bring in specialists
bond.Invoke(ctx, s, bond.TextPrompt("we need eyes inside"), bond.AgentOptions{})
Sub-Agent Tool (Delegation)

Wrap any agent as a tool — delegate missions to specialists:

tool := agent.AsTool(researchAgent, agent.ToolOptions{
    Name:        "research_operative",
    Description: "Delegates intelligence gathering to a specialist",
    StreamOptions: bond.AgentOptions{Tools: osintTools},
})

Tool Delegation (Double Agent Protocol)

When agents communicate over A2A, a client agent can lend its tools to a server agent. The server uses them as if they were local — never knowing the gadgets belong to someone else.

Client Side (The Quartermaster)
// Your agent connects to a remote specialist and offers its gadgets.
specialist := delegation.NewAgent(delegation.AgentOptions{
    Client: a2aClient,  // connected to remote agent
    Tools:  []bond.Tool{searchTool, databaseTool, satelliteTool},
})

// Use it like any agent — delegation is transparent.
resp, _ := bond.Invoke(ctx, specialist, bond.TextPrompt("find the target"), bond.AgentOptions{})
Server Side (The Field Agent)
// The server negotiates: "tell me what gadgets you have."
// Then it uses them as its own.
executor := delegation.NewExecutor(delegation.ExecutorOptions{
    Factory: func(ctx context.Context, skills []delegation.Skill, requester delegation.Requester) (bond.Agent, bond.AgentOptions) {
        agent := bedrock.New(client, bedrock.AgentOptions{
            ModelID: "anthropic.claude-sonnet-4-20250514-v1:0",
            System:  "You are a field operative. Use your available tools.",
        })
        return agent, bond.AgentOptions{
            Plugins: []bond.Plugin{
                delegation.NewPlugin(delegation.Options{
                    Requester: requester,
                    Skills:    skills,
                }),
            },
        }
    },
})

handler := agentcore.NewA2AHandlerFromExecutor(executor, agentcore.Options{})
http.ListenAndServe(handler.Port(), handler)

The server's model calls a delegated tool → Bond sends "input required" back to the client → the client executes the tool locally → result returns seamlessly. The model never knows the gadget was remote. Shaken, not stirred.

Runtime: AgentCore (Field Deployment)

Deploy your agent to AWS Bedrock AgentCore:

// A2A protocol (port 9000) — agent-to-agent communication
a2aHandler := agentcore.NewA2AHandler(agent, agentcore.Options{
    Card: &a2a.AgentCard{Name: "007", Description: "Licensed to assist"},
    AgentOptions: bond.AgentOptions{Tools: myTools},
})
http.ListenAndServe(a2aHandler.Port(), a2aHandler)

// HTTP protocol (port 8080) — direct invocations
httpHandler := agentcore.NewHTTPHandler(agent, opts)
http.ListenAndServe(httpHandler.Port(), httpHandler)

// MCP protocol (port 8000) — A2A operations as MCP tools
mcpHandler := agentcore.NewMCPHandler(agent, opts)
http.ListenAndServe(mcpHandler.Port(), mcpHandler)

// Or deploy all at once with graceful shutdown
agentcore.Serve(agent, opts)

MCP Integration

Equip your agent with tools from any MCP server:

session, _ := client.Connect(ctx, transport, nil)
tools, _ := tool.FromMCP(ctx, session)

bond.Stream(ctx, agent, msgs, bond.AgentOptions{Tools: tools})

Tool Registry (The Armory)

When you have more gadgets than an agent can carry, use the registry:

registry := toolregistry.New(toolregistry.Options{
    Tools: fiftyGadgets,
})

// Agent sees 3 tools: list_tools, describe_tool, use_tool
// It discovers what it needs on demand.
bond.Stream(ctx, agent, msgs, bond.AgentOptions{
    Plugins: []bond.Plugin{registry},
})

Testing (Training Exercise)

double := &bondtest.Agent{Events: bondtest.TextEvents("Mission accomplished.")}
resp, _ := bond.Invoke(ctx, double, bond.TextPrompt("status report"), bond.AgentOptions{})
// resp.Text == "Mission accomplished."

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrAbort = errors.New("bond: operation aborted by hook")

ErrAbort is returned by a hook to signal that the current operation should be cancelled. Use with Before* hooks to prevent execution.

Functions

func On

func On[T HookEvent](r *HookRegistry, hook HookFunc[T])

On registers a hook for the event type T. The type is inferred from the generic parameter — no event instance needed. Hooks fire in registration order (FIFO) when Notify is called for the corresponding event type.

func Stream

func Stream(ctx context.Context, agent Agent, messages []Message, opts AgentOptions) iter.Seq2[StreamEvent, error]

Stream runs the full agent loop: it streams from the provider, and when the model requests tool use, it executes the tools, appends results to the conversation, and re-invokes the provider. Events are yielded to the caller transparently across turns.

Types

type AfterModelInvokeHook

type AfterModelInvokeHook struct {
	Blocks     []Block    // assembled assistant content blocks
	StopReason StopReason // why the model stopped
}

AfterModelInvokeHook fires after a provider Stream call has fully drained.

type AfterStreamHook

type AfterStreamHook struct {
	Messages []Message // full conversation history at completion
}

AfterStreamHook fires when the agent loop completes.

type AfterToolCallHook

type AfterToolCallHook struct {
	ToolUse *ToolUseBlock
	Result  *ToolResultBlock
}

AfterToolCallHook fires after a single tool execution completes.

type AfterToolCycleHook

type AfterToolCycleHook struct {
	Results []*ToolResultBlock
}

AfterToolCycleHook fires after all tool calls in a cycle complete.

type Agent

type Agent interface {
	// Stream sends the conversation messages to the model and returns
	// an iterator of streaming events. The last message in the slice
	// is treated as the current prompt; preceding messages are context.
	Stream(ctx context.Context, messages []Message) iter.Seq2[StreamEvent, error]
}

Agent is the core interface for streaming LLM interactions.

type AgentOptions

type AgentOptions struct {
	Tools    []Tool
	Plugins  []Plugin
	MaxTurns int // max tool-use round-trips; 0 means unlimited
}

AgentOptions configures the agent loop.

type BeforeModelInvokeHook

type BeforeModelInvokeHook struct {
	Messages []Message // conversation state being sent to the model
}

BeforeModelInvokeHook fires before calling the provider's Stream method.

type BeforeStreamHook

type BeforeStreamHook struct {
	Messages []Message
}

BeforeStreamHook fires before the agent loop begins.

type BeforeToolCallHook

type BeforeToolCallHook struct {
	ToolUse *ToolUseBlock
}

BeforeToolCallHook fires before a single tool is executed. Return ErrAbort to skip this tool call.

type BeforeToolCycleHook

type BeforeToolCycleHook struct {
	ToolCalls []*ToolUseBlock
}

BeforeToolCycleHook fires before the batch of tool calls begins.

type Block

type Block interface {
	// contains filtered or unexported methods
}

Block is the interface satisfied by all content blocks.

type BlockType

type BlockType string
const (
	BlockTypeText       BlockType = "text"
	BlockTypeMedia      BlockType = "media"
	BlockTypeToolUse    BlockType = "tool_use"
	BlockTypeToolResult BlockType = "tool_result"
)

type FuncTool

type FuncTool struct {
	// contains filtered or unexported fields
}

FuncTool is a Tool implementation backed by a function. The zero value is not usable; create instances with NewFuncTool.

func (FuncTool) Description

func (ft FuncTool) Description() string

func (FuncTool) InputSchema

func (ft FuncTool) InputSchema() json.Marshaler

func (FuncTool) Name

func (ft FuncTool) Name() string

func (FuncTool) OutputSchema

func (ft FuncTool) OutputSchema() json.Marshaler

func (FuncTool) Run

func (ft FuncTool) Run(ctx context.Context, input json.RawMessage) ([]Block, error)

type FuncToolOptions

type FuncToolOptions struct {
	Name         string
	Description  string
	InputSchema  json.Marshaler
	OutputSchema json.Marshaler
}

type Hook

type Hook interface {
	NotifyHookEvent(ctx context.Context, event HookEvent) error
}

Hook handles a specific hook event.

type HookEvent

type HookEvent interface {
	// contains filtered or unexported methods
}

HookEvent is the sealed interface for all hook event types.

type HookFunc

type HookFunc[T HookEvent] func(context.Context, T) error

HookFunc adapts a typed function into a Hook. The function receives the concrete event type directly.

func (HookFunc[T]) NotifyHookEvent

func (fn HookFunc[T]) NotifyHookEvent(ctx context.Context, event HookEvent) error

type HookRegistry

type HookRegistry struct {
	// contains filtered or unexported fields
}

HookRegistry manages hooks keyed by event type.

func (*HookRegistry) Notify

func (r *HookRegistry) Notify(ctx context.Context, event HookEvent) error

Notify dispatches an event to all registered hooks for that event type. All hooks are called; errors are joined. If any hook returns ErrAbort, the joined error will contain it (check with errors.Is).

type InvokeResponse

type InvokeResponse struct {
	// Text is the concatenated text output from the agent.
	Text string
	// Media contains any media content produced by the agent.
	Media []Media
	// StopReason indicates why the agent stopped.
	StopReason StopReason
}

InvokeResponse holds the collected result of a synchronous agent invocation.

func Invoke

func Invoke(ctx context.Context, agent Agent, messages []Message, opts AgentOptions) (*InvokeResponse, error)

Invoke runs the full agent loop synchronously, collecting all streamed events into a single InvokeResponse. This is a convenience wrapper over Stream for cases where streaming is not needed.

type JSONMarshaler

type JSONMarshaler = func(any) ([]byte, error)

type LoggingPlugin

type LoggingPlugin struct {
	Logger *slog.Logger
	Level  slog.Level
}

func (*LoggingPlugin) Init

func (p *LoggingPlugin) Init(registry *HookRegistry)

func (*LoggingPlugin) Name

func (p *LoggingPlugin) Name() string

func (*LoggingPlugin) Tools

func (p *LoggingPlugin) Tools() []Tool

type Media

type Media struct {
	// MIMEType is the media type (e.g. "image/png", "audio/wav").
	MIMEType string
	// Data is the raw bytes.
	Data []byte
}

Media represents a complete piece of media content in a response.

type MediaBlock

type MediaBlock struct {
	Type      MediaType
	MIMEType  string
	Source    io.Reader // content payload; wrap bytes with bytes.NewReader
	SourceURI string    // optional URI reference (e.g. S3 key, URL)
}

MediaBlock holds binary content (images, audio, video, documents).

type MediaDelta

type MediaDelta struct {
	// MIMEType is the media type (e.g. "image/png", "audio/wav").
	MIMEType string
	// Data is the raw bytes for this chunk.
	Data []byte
}

MediaDelta represents a chunk of binary/media content in a stream.

type MediaType

type MediaType string

MediaType describes the kind of binary content in a MediaBlock.

const (
	MediaTypeImage    MediaType = "image"
	MediaTypeAudio    MediaType = "audio"
	MediaTypeVideo    MediaType = "video"
	MediaTypeDocument MediaType = "document"
)

type Message

type Message struct {
	Role    Role
	Content []Block
	// Metadata holds provider-specific annotations (reasoning traces,
	// citations, guard content, cache points, etc.) that don't map
	// universally across providers.
	Metadata map[string]any
}

Message represents a single turn in a conversation.

func Conversation

func Conversation(turns ...string) []Message

Conversation builds a message list from alternating user/assistant strings. The first string is user, second is assistant, third is user, etc. Useful for constructing few-shot examples or test fixtures.

func ImagePrompt

func ImagePrompt(text, imageURI, mimeType string) []Message

ImagePrompt creates a user message with text and an image from a URI.

func MultiBlockPrompt

func MultiBlockPrompt(blocks ...Block) []Message

MultiBlockPrompt creates a user message from arbitrary blocks.

func TextPrompt

func TextPrompt(text string) []Message

TextPrompt is a convenience helper that wraps a plain string into the []Message format expected by Agent.Stream.

type OnMessageAppendedHook

type OnMessageAppendedHook struct {
	Message Message
}

OnMessageAppendedHook fires when a message is appended to conversation history.

type OnStreamEventHook

type OnStreamEventHook struct {
	Event StreamEvent
}

OnStreamEventHook fires for each raw StreamEvent received from the provider.

type Plugin

type Plugin interface {
	// Name identifies the plugin (used for logging/debugging).
	Name() string
	// Tools returns the tools this plugin contributes to the agent loop.
	Tools() []Tool
	// Init registers hooks on the provided registry. Called once during
	// stream setup before the loop begins.
	Init(registry *HookRegistry)
}

Plugin bundles tools and hook registrations into a reusable unit.

func NewHooksPlugin

func NewHooksPlugin(name string, onInit func(*HookRegistry)) Plugin

NewHooksPlugin creates a Plugin that registers hooks via the provided callback but does not contribute any tools. This is useful for cross-cutting concerns like logging, metrics, or guardrails that only need to observe or modify the agent loop through hooks.

func NewToolsPlugin

func NewToolsPlugin(name string, tools ...Tool) Plugin

NewToolsPlugin creates a Plugin that contributes the given tools to the agent loop without registering any hooks. This is useful when you only need to expose tools and don't require lifecycle callbacks.

type Role

type Role string

Role represents the sender of a message in a conversation.

const (
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
)

type StopReason

type StopReason string

StopReason indicates why the model stopped generating.

const (
	StopReasonEnd     StopReason = "end"      // natural end of response
	StopReasonToolUse StopReason = "tool_use" // paused to call a tool
	StopReasonLength  StopReason = "length"   // hit max token limit
)

type StreamEvent

type StreamEvent struct {
	Type StreamEventType

	// TextDelta is populated when Type == StreamEventTextDelta.
	TextDelta string

	// MediaDelta is populated when Type == StreamEventMediaDelta.
	MediaDelta *MediaDelta

	// ToolUse is populated when Type == StreamEventToolUse.
	ToolUse *ToolUseBlock

	// StopReason is populated when Type == StreamEventStop.
	StopReason StopReason

	// Metadata carries provider-specific event data (usage stats, trace IDs, etc.)
	Metadata map[string]any
}

StreamEvent represents a single event in a streaming response.

type StreamEventType

type StreamEventType string

StreamEventType identifies what kind of event was emitted during streaming.

const (
	// StreamEventStart signals the beginning of the response stream.
	StreamEventStart StreamEventType = "start"
	// StreamEventTextDelta delivers an incremental chunk of text.
	StreamEventTextDelta StreamEventType = "text_delta"
	// StreamEventMediaDelta delivers a chunk of binary/media content.
	StreamEventMediaDelta StreamEventType = "media_delta"
	// StreamEventToolUse signals the model wants to invoke a tool.
	StreamEventToolUse StreamEventType = "tool_use"
	// StreamEventStop signals the response is complete.
	StreamEventStop StreamEventType = "stop"
)

type TextBlock

type TextBlock struct {
	Text string
}

TextBlock holds plain text content.

type Tool

type Tool interface {
	// Name is the tool identifier the model will reference.
	Name() string
	// Description helps the model decide when to use this tool.
	Description() string
	// InputSchema is the JSON schema describing expected input parameters.
	InputSchema() json.Marshaler
	// Run executes the tool and returns result blocks.
	Run(ctx context.Context, input json.RawMessage) ([]Block, error)
}

Tool represents a tool the agent can invoke during streaming.

func NewFuncTool

func NewFuncTool[In, Out any](fn func(context.Context, In) (Out, error), options FuncToolOptions) (Tool, error)

NewFuncTool creates a Tool backed by a typed function. The function's input is unmarshaled from JSON, and its output is marshaled back into a TextBlock. Returns an error if required options (Name, Description) or the handler are missing.

func ToolsFromContext

func ToolsFromContext(ctx context.Context) []Tool

ToolsFromContext retrieves the tools available for the current invocation. Providers use this to include tool definitions in API requests. Returns nil if no tools are configured.

type ToolResultBlock

type ToolResultBlock struct {
	ToolUseID string
	Content   []Block // typically TextBlock or MediaBlock
	IsError   bool
}

ToolResultBlock holds the response from a tool invocation.

type ToolUseBlock

type ToolUseBlock struct {
	ID    string
	Name  string
	Input json.RawMessage
}

ToolUseBlock represents the model requesting a tool invocation.

Directories

Path Synopsis
Package agent provides utilities for using bond agents as tools, enabling sub-agent orchestration patterns.
Package agent provides utilities for using bond agents as tools, enabling sub-agent orchestration patterns.
Package bondtest provides test utilities for the bond agent framework.
Package bondtest provides test utilities for the bond agent framework.
extra
delegation
Package delegation enables transparent tool delegation between agents communicating over the A2A protocol.
Package delegation enables transparent tool delegation between agents communicating over the A2A protocol.
toolregistry
Package toolregistry provides a plugin that exposes a large collection of tools through a stable 3-tool gateway: list_tools, describe_tool, use_tool.
Package toolregistry provides a plugin that exposes a large collection of tools through a stable 3-tool gateway: list_tools, describe_tool, use_tool.
internal
provider
bedrock
Package bedrock provides a bond.Agent implementation backed by the Amazon Bedrock Converse streaming API.
Package bedrock provides a bond.Agent implementation backed by the Amazon Bedrock Converse streaming API.
Package runtime provides protocol handlers for serving bond agents over A2A, HTTP, and MCP.
Package runtime provides protocol handlers for serving bond agents over A2A, HTTP, and MCP.
agentcore
Package agentcore provides convenience constructors for running bond agents on AWS Bedrock AgentCore Runtime.
Package agentcore provides convenience constructors for running bond agents on AWS Bedrock AgentCore Runtime.
Package toolmcp adapts MCP server tools for use in bond agent loops.
Package toolmcp adapts MCP server tools for use in bond agent loops.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL