bond

package module
v0.11.1 Latest Latest
Warning

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

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 13 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, OpenAI, Ollama included)
  • Hooks and plugins for cross-cutting concerns (logging, guardrails, metrics)
  • Orchestration patterns — graphs (LangGraph-style) and swarms (OpenAI Swarm-style)
  • Parallel graph nodes — fan-out/fan-in with deterministic output ordering
  • Map-reduce pattern — fan out work to parallel workers with bounded concurrency
  • Graph checkpointing — persist execution state to any backend, resume after restart
  • Async approval gates — human-in-the-loop with AsyncGate, store-backed suspend/resume
  • Swarm context carry-overHistoryPolicy filters what agents see on transfer
  • Automatic summarization — LLM-based summary of dropped context on overflow
  • A2A protocol support — remote agent communication, tool delegation, incremental streaming
  • Multiple runtimes — AgentCore (AWS), generic HTTP/A2A/MCP, ACP for code editors
  • MCP tool adapter — use MCP server tools in your agent
  • MCP handler with SSE observability — real-time lifecycle notifications during execution
  • Tool registry — expose large tool collections through a discovery gateway
  • Built-in toolbox — shell execution, file I/O, environment access
  • Session management — persistent history, conversation trimming, and summarization
  • 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, AsTool, MapReduce, parallel nodes, checkpoints
bond/provider/a2aproxy/      A2A protocol proxy (bond.Agent over remote A2A agent)
bond/provider/acpproxy/      ACP protocol proxy (bond.Agent over ACP transport)
bond/provider/bedrock/       Amazon Bedrock Converse streaming provider
bond/provider/openai/        OpenAI-compatible streaming provider
bond/provider/ollama/        Ollama local model provider
bond/runtime/                Generic protocol handlers (A2A, HTTP, MCP) for custom deployments
bond/runtime/acp/            ACP handler for code editors (Zed, JetBrains, VS Code)
bond/runtime/agentcore/      AWS Bedrock AgentCore defaults (ports, paths, session headers)
bond/tool/                   Tool infrastructure (schema, MCP adapter, structured output)
bond/tool/builtin/           Built-in tools: shell, file I/O, HTTP, environment
bond/tool/registry/          Tool discovery gateway plugin
bond/extra/approval/         Approval gates (sync and async) with store-backed suspend/resume
bond/extra/delegation/       A2A tool delegation (client + server)
bond/extra/session/          Session persistence, conversation trimming, and summarization
bond/extra/store/dynamostore/    DynamoDB-backed stores (sessions, approvals, checkpoints)
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{})
Parallel Nodes (Fan-Out/Fan-In)

Execute multiple nodes concurrently and converge at a single join point:

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

g.AddNode("dispatch", &agent.GraphNode{Agent: dispatchAgent})
g.AddNode("recon", &agent.GraphNode{Agent: reconAgent})
g.AddNode("signals", &agent.GraphNode{Agent: signalsAgent})
g.AddNode("debrief", &agent.GraphNode{Agent: debriefAgent})

// recon and signals run concurrently, then converge at debrief
g.FanOutEdge("dispatch", []string{"recon", "signals"}, "debrief")
Map-Reduce (Parallel Processing)

Fan out work to parallel workers with bounded concurrency:

action := agent.MapReduce(agent.MapReduceOptions[string, Summary]{
    Map: func(ctx context.Context, state agent.State) ([]string, error) {
        docs, _ := state.Get("documents")
        return docs.([]string), nil
    },
    Worker: func(ctx context.Context, doc string) (Summary, error) {
        return summarizeDocument(ctx, doc)
    },
    Reduce: func(ctx context.Context, state agent.State, results []agent.MapReduceResult[Summary]) error {
        var summaries []Summary
        for _, r := range results {
            if r.Err == nil {
                summaries = append(summaries, r.Value)
            }
        }
        state.Set("summaries", summaries)
        return nil
    },
    MaxConcurrency: 5,
})

g.AddNode("summarize_all", &agent.GraphNode{Action: action})
Checkpoint/Resume (Durability)

Persist execution state after each node — resume from where you left off after restart:

store := agent.NewInMemoryCheckpointStore() // or your own CheckpointStore

g := agent.NewGraph("mission", agent.GraphOptions{
    CheckpointStore: store,
    CheckpointID:    "mission-001",
})

// ... add nodes and edges ...

// Run — checkpoints saved after each node
bond.Invoke(ctx, g, prompt, bond.AgentOptions{})

// Later (after restart), resume from where we left off
for event, err := range g.ResumeFrom(ctx, "mission-001") {
    // continues from the last completed node
}
Async Approval Gates (Human-in-the-Loop)

Gate graph transitions on external approval — suspend gracefully, resume when approved:

import "github.com/nisimpson/bond/extra/approval"

store := approval.NewInMemoryStore()

gate := approval.NewAsyncGate(approval.AsyncGateOptions[*agent.BeforeNodeTransitionHook]{
    Store: store,
    KeyFunc: func(e *agent.BeforeNodeTransitionHook) string {
        return fmt.Sprintf("%s-to-%s", e.FromNode, e.ToNode)
    },
})

// Register on the hook registry (via plugin or directly)
plugin := bond.NewHooksPlugin("approval", func(r *bond.HookRegistry) {
    gate.Register(r)
})

// First run: gate fires, creates pending record, returns ErrInterrupted.
// Graph stops gracefully, checkpoint saved.

// External system approves:
approval.ApprovePending(ctx, store, "nodeB-to-nodeC")

// Resume: gate finds approved record, execution continues.
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)
MCP Handler: SSE Mode and Observability

Enable SSE mode for real-time visibility into long-running send_message calls:

mcpHandler := agentcore.NewMCPHandler(agent, agentcore.MCPOptions{
    SSEMode: true, // text/event-stream responses with observability notifications
})

In SSE mode, the handler emits structured ServerSession.Log notifications during execution:

  • state_transition — thinking, executing_tools, generating_response
  • tool_invoked / tool_result — tool call lifecycle with names and success indicators
  • hook_fired / hook_completed — stream lifecycle boundaries with duration

Clients filter by logger name "bond.agent" to receive these events over the SSE connection. JSON mode (the default) continues to work exactly as before with no notifications.

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

For non-AgentCore deployments, use the runtime package directly with your own ports and paths:

import "github.com/nisimpson/bond/runtime"

handler := runtime.NewMCPHandler(agent, runtime.MCPOptions{
    Port:    ":3000",
    MCPPath: "/api/mcp",
    SSEMode: true,
})
http.ListenAndServe(handler.Port(), handler)
ACP Runtime (Editor Integration)

Serve agents as coding assistants over stdio for editors like Zed, JetBrains, and VS Code:

import "github.com/nisimpson/bond/runtime/acp"

handler := acp.NewHandler(agent, acp.Options{
    AgentName:    "bond-assist",
    AgentVersion: "1.0.0",
    Transport:    acp.DefaultTransport(),
})
handler.Serve(ctx)

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:

reg := registry.New(registry.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{reg},
})

Session Management (Memory)

Agents need memory. The extra/session package provides persistent conversation state and context-window trimming — two plugins that compose on a single hook registry.

Session Persistence

Load and save conversation history automatically:

import "github.com/nisimpson/bond/extra/session"

store := session.NewInMemoryStore() // or dynamostore.New(opts) for production

plugin := session.NewSessionPlugin(session.SessionPluginOptions{
    Store: store,
    ResolveID: func(ctx context.Context) (string, error) {
        return extractSessionID(ctx), nil
    },
})

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

The plugin loads history before the stream starts and saves each new message as it's appended.

Conversation Trimming

Keep conversations within model context limits:

// Sliding window: keep the last 10 user/assistant pairs
policy, _ := session.NewSlidingWindowManager(session.SlidingWindowOptions{
    WindowSize: 10,
})

// Or token budget: fit within a token limit
policy, _ := session.NewTokenBudgetManager(session.TokenBudgetOptions{
    MaxTokens: 8000,
    Counter:   myTokenCounter,
})

// Or summarize dropped messages instead of discarding them
policy, _ = session.NewSummarizationManager(session.SummarizationManagerOptions{
    Summarizer: mySummarizer, // LLM-based summarizer
    Fallback:   slidingWindow, // underlying trim policy
})

trimPlugin := session.NewTrimmingPlugin(session.TrimmingPluginOptions{
    Policy:      policy,
    AutoRecover: true, // retry on ErrContextOverflow
    MaxRetries:  2,
})

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

Both strategies preserve system preamble messages and maintain relative message ordering.

Swarm Context Carry-Over

Control what history agents see when transferring between swarm agents:

// Only pass the last 3 exchanges to the specialist on transfer
policy, _ := session.NewSlidingWindowManager(session.SlidingWindowOptions{WindowSize: 3})

s.AddAgent("specialist", &agent.SwarmAgent{
    Agent:         specialistAgent,
    HistoryPolicy: policy, // receives only the last 3 exchanges
})

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

Examples

Constants

This section is empty.

Variables

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

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

View Source
var ErrContextOverflow = errors.New("bond: context overflow")

Sentinel error for context overflow

Functions

func OnAfter added in v0.2.0

func OnAfter[T AfterHookEvent](r *HookRegistry, hook AfterHookFunc[T])

OnAfter registers an observer hook for the after-event type T. Observer hooks fire in registration order (FIFO). They cannot return errors and cannot interrupt the agent loop.

func OnBefore added in v0.2.0

func OnBefore[T BeforeHookEvent](r *HookRegistry, hook BeforeHookFunc[T])

OnBefore registers a gate hook for the before-event type T. Gate hooks fire in registration order (FIFO). Return ErrAbort or any error to prevent the guarded operation.

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.

Example
package main

import (
	"context"
	"fmt"

	"github.com/nisimpson/bond"
	"github.com/nisimpson/bond/bondtest"
)

func main() {
	agent := &bondtest.Agent{
		Events: bondtest.TextEvents("Bond", ". James ", "Bond."),
	}

	var text string
	for event, err := range bond.Stream(context.Background(), agent, bond.TextPrompt("name?"), bond.AgentOptions{}) {
		if err != nil {
			panic(err)
		}
		if event.Type == bond.StreamEventTextDelta {
			text += event.TextDelta
		}
	}
	fmt.Println(text)
}
Output:
Bond. James Bond.

func WithHookRegistry added in v0.9.0

func WithHookRegistry(ctx context.Context, registry *HookRegistry) context.Context

WithHookRegistry attaches a HookRegistry to the context. Agent implementations (graphs, swarms) can retrieve it to fire custom events.

Types

type AfterHookEvent added in v0.2.0

type AfterHookEvent interface {
	HookEvent
	AfterHookEvent() // marker method
}

AfterHookEvent is implemented by observer hooks that fire after an operation. Observer hooks are informational — their handlers cannot return errors and cannot interrupt the agent loop.

type AfterHookFunc added in v0.2.0

type AfterHookFunc[T AfterHookEvent] func(context.Context, T)

AfterHookFunc is a handler for after (observer) events. It receives the concrete event type and cannot return an error — observer hooks are fire-and-forget.

func (AfterHookFunc[T]) NotifyHookEvent added in v0.2.0

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

NotifyHookEvent implements Hook. Always returns nil since after-hooks cannot produce errors.

type AfterMessageAppendedHook added in v0.2.0

type AfterMessageAppendedHook struct {
	Message Message
}

AfterMessageAppendedHook fires when a message is appended to conversation history.

func (*AfterMessageAppendedHook) AfterHookEvent added in v0.9.0

func (*AfterMessageAppendedHook) AfterHookEvent()

func (*AfterMessageAppendedHook) HookEvent added in v0.9.0

func (*AfterMessageAppendedHook) HookEvent()

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.

func (*AfterModelInvokeHook) AfterHookEvent added in v0.9.0

func (*AfterModelInvokeHook) AfterHookEvent()

func (*AfterModelInvokeHook) HookEvent added in v0.9.0

func (*AfterModelInvokeHook) HookEvent()

type AfterStreamHook

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

AfterStreamHook fires when the agent loop completes.

func (*AfterStreamHook) AfterHookEvent added in v0.9.0

func (*AfterStreamHook) AfterHookEvent()

func (*AfterStreamHook) HookEvent added in v0.9.0

func (*AfterStreamHook) HookEvent()

type AfterToolCallHook

type AfterToolCallHook struct {
	ToolUse *ToolUseBlock
	Result  *ToolResultBlock
}

AfterToolCallHook fires after a single tool execution completes.

func (*AfterToolCallHook) AfterHookEvent added in v0.9.0

func (*AfterToolCallHook) AfterHookEvent()

func (*AfterToolCallHook) HookEvent added in v0.9.0

func (*AfterToolCallHook) HookEvent()

type AfterToolCycleHook

type AfterToolCycleHook struct {
	Results []*ToolResultBlock
}

AfterToolCycleHook fires after all tool calls in a cycle complete.

func (*AfterToolCycleHook) AfterHookEvent added in v0.9.0

func (*AfterToolCycleHook) AfterHookEvent()

func (*AfterToolCycleHook) HookEvent added in v0.9.0

func (*AfterToolCycleHook) HookEvent()

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
	RetryPolicy RetryPolicy // retry on model invocation errors; nil means no retry
}

AgentOptions configures the agent loop.

type BeforeHookEvent added in v0.2.0

type BeforeHookEvent interface {
	HookEvent
	BeforeHookEvent() // marker method
}

BeforeHookEvent is implemented by gate hooks that fire before an operation. Gate hooks can return errors to prevent execution. Return ErrAbort to gracefully skip the operation, or any other error to halt with that error.

type BeforeHookFunc added in v0.2.0

type BeforeHookFunc[T BeforeHookEvent] func(context.Context, T) error

BeforeHookFunc is a handler for before (gate) events. It receives the concrete event type and may return an error to prevent the operation.

func (BeforeHookFunc[T]) NotifyHookEvent added in v0.2.0

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

NotifyHookEvent implements Hook.

type BeforeModelInvokeHook

type BeforeModelInvokeHook struct {
	Messages []Message // conversation state being sent to the model
	Attempt  int       // 0 on first call, increments on retries
}

BeforeModelInvokeHook fires before calling the provider's Stream method. Return ErrAbort to skip the invocation, or any error to stop the loop.

func (*BeforeModelInvokeHook) BeforeHookEvent added in v0.9.0

func (*BeforeModelInvokeHook) BeforeHookEvent()

func (*BeforeModelInvokeHook) HookEvent added in v0.9.0

func (*BeforeModelInvokeHook) HookEvent()

type BeforeStreamEventHook added in v0.2.0

type BeforeStreamEventHook struct {
	Event StreamEvent
}

BeforeStreamEventHook fires before each raw StreamEvent is processed. Return ErrAbort to stop consuming the stream.

func (*BeforeStreamEventHook) BeforeHookEvent added in v0.9.0

func (*BeforeStreamEventHook) BeforeHookEvent()

func (*BeforeStreamEventHook) HookEvent added in v0.9.0

func (*BeforeStreamEventHook) HookEvent()

type BeforeStreamHook

type BeforeStreamHook struct {
	Messages []Message
}

BeforeStreamHook fires before the agent loop begins. Return ErrAbort to cancel the stream, or any error to stop with that error.

func (*BeforeStreamHook) BeforeHookEvent added in v0.9.0

func (*BeforeStreamHook) BeforeHookEvent()

func (*BeforeStreamHook) HookEvent added in v0.9.0

func (*BeforeStreamHook) HookEvent()

type BeforeToolCallHook

type BeforeToolCallHook struct {
	ToolUse *ToolUseBlock
}

BeforeToolCallHook fires before a single tool is executed. Return ErrAbort or any error to skip this tool call and surface the error as a tool result to the model.

func (*BeforeToolCallHook) BeforeHookEvent added in v0.9.0

func (*BeforeToolCallHook) BeforeHookEvent()

func (*BeforeToolCallHook) HookEvent added in v0.9.0

func (*BeforeToolCallHook) HookEvent()

type BeforeToolCycleHook

type BeforeToolCycleHook struct {
	ToolCalls []*ToolUseBlock
}

BeforeToolCycleHook fires before the batch of tool calls begins. Return ErrAbort to skip the entire tool cycle, or any error to abort all tool calls with error results.

func (*BeforeToolCycleHook) BeforeHookEvent added in v0.9.0

func (*BeforeToolCycleHook) BeforeHookEvent()

func (*BeforeToolCycleHook) HookEvent added in v0.9.0

func (*BeforeToolCycleHook) HookEvent()

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 ContextPlugin added in v0.11.0

type ContextPlugin interface {
	InitContext(ctx context.Context) context.Context
}

ContextPlugin is optionally implemented by plugins that need to inject values into the stream context. This is useful for request-scoped state like loggers, trace IDs, or session identifiers that should flow through the entire agent loop and be accessible from hooks and tools.

InitContext is called after Plugin.Init, so the plugin is fully set up (hooks registered) before it enriches the context.

type ExponentialBackoff added in v0.9.0

type ExponentialBackoff struct {
	// MaxAttempts is the maximum number of retry attempts. Zero means no retries.
	MaxAttempts int
	// BaseDelay is the initial delay before the first retry. Defaults to 1s.
	BaseDelay time.Duration
	// MaxDelay caps the backoff duration. Defaults to 30s.
	MaxDelay time.Duration
	// Jitter adds randomness to the delay to avoid thundering herd.
	Jitter bool
	// ShouldRetryFunc optionally classifies errors. If nil, all errors are
	// considered retryable. Return false to treat an error as terminal.
	ShouldRetryFunc func(err error) bool
}

ExponentialBackoff is a RetryPolicy that uses exponential backoff with optional jitter. It retries all errors up to MaxAttempts times.

The delay for attempt n is: min(BaseDelay * 2^n, MaxDelay), with random jitter applied if Jitter is true.

func NewExponentialBackoff added in v0.9.0

func NewExponentialBackoff() *ExponentialBackoff

NewExponentialBackoff creates an ExponentialBackoff with sensible defaults: 3 max attempts, 1s base delay, 30s max delay, jitter enabled, all errors retryable.

func (*ExponentialBackoff) ShouldRetry added in v0.9.0

func (e *ExponentialBackoff) ShouldRetry(err error, attempt int) (time.Duration, bool)

ShouldRetry implements RetryPolicy.

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. This is the internal storage type.

type HookEvent

type HookEvent interface {
	HookEvent() // marker method
}

HookEvent is the interface for all hook event types. Every hook event is either a BeforeHookEvent (gate) or AfterHookEvent (observer). Users may define custom hook events by implementing these interfaces and firing them through a HookRegistry.

type HookRegistry

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

HookRegistry manages hooks keyed by event type.

func HookRegistryFromContext added in v0.9.0

func HookRegistryFromContext(ctx context.Context) *HookRegistry

HookRegistryFromContext retrieves the HookRegistry from a context. Returns nil if no registry is attached.

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. For before-events, all hooks are called and errors are joined. If any hook returns ErrAbort, the joined error will contain it. For after-events, hooks are called but errors are always nil (since AfterHookFunc cannot produce errors).

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.

Example
package main

import (
	"context"
	"fmt"

	"github.com/nisimpson/bond"
	"github.com/nisimpson/bond/bondtest"
)

func main() {
	agent := &bondtest.Agent{
		Events: bondtest.TextEvents("Hello, ", "world!"),
	}

	resp, err := bond.Invoke(context.Background(), agent, bond.TextPrompt("hi"), bond.AgentOptions{})
	if err != nil {
		panic(err)
	}
	fmt.Println(resp.Text)
}
Output:
Hello, world!

type JSONMarshaler

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

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 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 NewToolConfirmationPlugin added in v0.2.0

func NewToolConfirmationPlugin(provider ToolConfirmationProvider) Plugin

NewToolConfirmationPlugin returns a Plugin that intercepts BeforeToolCallHook and invokes the given provider before each tool execution. If the provider denies the call, the hook returns ErrAbort which causes the agent loop to skip the tool and return an error result to the model.

The provider is responsible for deciding which tools require confirmation. For example, it may allow all read-only tools unconditionally and only prompt for write operations.

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 RetryPolicy added in v0.9.0

type RetryPolicy interface {
	// ShouldRetry reports whether the error is retryable and, if so, how long
	// to wait before the next attempt. attempt is 0-indexed (first retry is
	// attempt 0, second is attempt 1, etc.). Return (0, false) to stop retrying.
	ShouldRetry(err error, attempt int) (backoff time.Duration, retry bool)
}

RetryPolicy decides whether and how long to wait before retrying a failed model invocation. The agent loop consults this policy when the provider returns an error during streaming.

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.

Example
package main

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/nisimpson/bond"
	"github.com/nisimpson/bond/bondtest"
)

func main() {
	type AddInput struct {
		A int `json:"a"`
		B int `json:"b"`
	}
	type AddOutput struct {
		Sum int `json:"sum"`
	}

	adder, _ := bond.NewFuncTool(
		func(ctx context.Context, in AddInput) (AddOutput, error) {
			return AddOutput{Sum: in.A + in.B}, nil
		},
		bond.FuncToolOptions{
			Name:        "add",
			Description: "Adds two numbers",
		},
	)

	// Simulate: model calls the tool, then responds with text
	agent := &bondtest.Agent{
		StreamFunc: bondtest.Sequence(
			bondtest.ToolUseEvents(&bond.ToolUseBlock{
				ID:    "call_1",
				Name:  "add",
				Input: json.RawMessage(`{"a":2,"b":3}`),
			}),
			bondtest.TextEvents("The sum is 5."),
		),
	}

	resp, err := bond.Invoke(context.Background(), agent, bond.TextPrompt("add 2+3"), bond.AgentOptions{
		Tools: []bond.Tool{adder},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(resp.Text)
}
Output:
The sum is 5.

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 ToolConfirmationFunc added in v0.2.0

type ToolConfirmationFunc func(ctx context.Context, toolUse *ToolUseBlock) (bool, error)

ToolConfirmationFunc adapts a plain function into a ToolConfirmationProvider.

func (ToolConfirmationFunc) ConfirmToolUse added in v0.2.0

func (f ToolConfirmationFunc) ConfirmToolUse(ctx context.Context, toolUse *ToolUseBlock) (bool, error)

ConfirmToolUse implements ToolConfirmationProvider.

type ToolConfirmationProvider added in v0.2.0

type ToolConfirmationProvider interface {
	// ConfirmToolUse is called before each tool invocation. Return true to
	// allow the call, or false to deny it. A denied call aborts with
	// [ErrAbort] and surfaces an error result to the model.
	ConfirmToolUse(ctx context.Context, toolUse *ToolUseBlock) (bool, error)
}

ToolConfirmationProvider decides whether a tool call should proceed. Implementations may prompt a user, check a policy, or consult an external service. The provider receives the full ToolUseBlock (tool name and input) and returns whether to allow or deny execution.

type ToolResultBlock

type ToolResultBlock struct {
	ToolUseID string
	Name      string  // Name of the tool that produced this result.
	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
approval
Package approval provides human-in-the-loop approval gates for bond agents.
Package approval provides human-in-the-loop approval gates for bond agents.
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.
guardrails
Package guardrails provides content filtering for bond agents.
Package guardrails provides content filtering for bond agents.
guardrails/bedrock
Package bedrock provides a guardrails.ContentFilter backed by the Amazon Bedrock ApplyGuardrail API.
Package bedrock provides a guardrails.ContentFilter backed by the Amazon Bedrock ApplyGuardrail API.
otel
Package otel provides an OpenTelemetry observability plugin for bond agents.
Package otel provides an OpenTelemetry observability plugin for bond agents.
session
Package session provides persistent conversation state management and conversation trimming strategies for bond agents.
Package session provides persistent conversation state management and conversation trimming strategies for bond agents.
slogger
Package slogger provides a structured logging plugin for bond agents.
Package slogger provides a structured logging plugin for bond agents.
store/dynamostore
Package dynamostore provides DynamoDB-backed implementations of bond storage interfaces: session.Store, approval.Store, and agent.CheckpointStore.
Package dynamostore provides DynamoDB-backed implementations of bond storage interfaces: session.Store, approval.Store, and agent.CheckpointStore.
internal
provider
acpproxy
Package acpproxy provides ACP (Agent Client Protocol) building blocks and a proxy client for connecting to external ACP-compatible agents.
Package acpproxy provides ACP (Agent Client Protocol) building blocks and a proxy client for connecting to external ACP-compatible agents.
anthropic
Package anthropic provides a bond.Agent implementation backed by the Anthropic Messages API.
Package anthropic provides a bond.Agent implementation backed by the Anthropic Messages API.
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.
gemini
Package gemini provides a bond.Agent implementation backed by the Google Gemini Generative Language API.
Package gemini provides a bond.Agent implementation backed by the Google Gemini Generative Language API.
internal/anthropic
Requirement: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 — map Bond messages to Anthropic format Requirement: 3.1, 3.2, 3.3 — map Bond tools to Anthropic tool definitions
Requirement: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 — map Bond messages to Anthropic format Requirement: 3.1, 3.2, 3.3 — map Bond tools to Anthropic tool definitions
internal/gemini
Requirement: 7.1, 7.2, 7.3, 7.4 — map Bond messages and tools to Gemini format
Requirement: 7.1, 7.2, 7.3, 7.4 — map Bond messages and tools to Gemini format
internal/openai
Requirement: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 — map Bond messages to OpenAI format Requirement: 3.1, 3.2, 3.3 — map Bond tools to OpenAI tool definitions
Requirement: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 — map Bond messages to OpenAI format Requirement: 3.1, 3.2, 3.3 — map Bond tools to OpenAI tool definitions
ollama
Package ollama provides a bond.Agent implementation backed by OpenAI-compatible Chat Completions endpoints (Ollama, LiteLLM, vLLM).
Package ollama provides a bond.Agent implementation backed by OpenAI-compatible Chat Completions endpoints (Ollama, LiteLLM, vLLM).
openai
Package openai provides a bond.Agent implementation backed by the OpenAI Chat Completions API (https://api.openai.com).
Package openai provides a bond.Agent implementation backed by the OpenAI Chat Completions API (https://api.openai.com).
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.
acp
Package acp provides an ACP (Agent Client Protocol) handler that serves bond agents over JSON-RPC 2.0 via stdio or other transports.
Package acp provides an ACP (Agent Client Protocol) handler that serves bond agents over JSON-RPC 2.0 via stdio or other transports.
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.
builtin
Package builtin provides a suite of reusable tools for Bond agents that cover common system interactions: shell command execution, HTTP fetching, file I/O, and environment variable access.
Package builtin provides a suite of reusable tools for Bond agents that cover common system interactions: shell command execution, HTTP fetching, file I/O, and environment variable access.
registry
Package registry provides a plugin that exposes a large collection of tools through a stable 3-tool gateway: list_tools, describe_tool, use_tool.
Package registry provides a plugin that exposes a large collection of tools through a stable 3-tool gateway: list_tools, describe_tool, use_tool.

Jump to

Keyboard shortcuts

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