Documentation
¶
Overview ¶
Package agent provides utilities for using bond agents as tools, enabling sub-agent orchestration patterns.
Index ¶
- Constants
- Variables
- func AsTool(a bond.Agent, opts ToolOptions) bond.Tool
- func ContextWithState(ctx context.Context, state State) context.Context
- type ActionFunc
- type AfterAgentHandoffHook
- type AfterNodeExecutionHook
- type BeforeAgentHandoffHook
- type BeforeNodeTransitionHook
- type CheckpointStore
- type EdgeCondition
- type Graph
- func (g *Graph) AddConditionalEdge(from string, condition EdgeCondition)
- func (g *Graph) AddEdge(from, to string)
- func (g *Graph) AddNode(name string, node *GraphNode)
- func (g *Graph) FanOutEdge(from string, targets []string, join string)
- func (g *Graph) ResumeFrom(ctx context.Context, snapshotID string) iter.Seq2[bond.StreamEvent, error]
- func (g *Graph) Stream(ctx context.Context, messages []bond.Message) iter.Seq2[bond.StreamEvent, error]
- type GraphNode
- type GraphOptions
- type HistoryPolicy
- type InMemoryCheckpointStore
- type MapReduceOptions
- type MapReduceResult
- type MapState
- type Snapshot
- type State
- type Swarm
- type SwarmAgent
- type SwarmOptions
- type ToolOptions
Examples ¶
Constants ¶
const EndNode = "__end__"
EndNode is the sentinel name indicating the graph should stop execution.
Variables ¶
var ErrSnapshotNotFound = errors.New("agent: snapshot not found")
ErrSnapshotNotFound is returned by CheckpointStore.Load when the requested snapshot does not exist.
Functions ¶
func AsTool ¶
func AsTool(a bond.Agent, opts ToolOptions) bond.Tool
AsTool wraps any bond.Agent as a bond.Tool. When invoked, it runs the full agent loop using the input prompt and returns the collected text response as the tool result.
The default input schema expects {"prompt": "..."} and the output returns {"response": "..."}.
Types ¶
type ActionFunc ¶
ActionFunc is a non-agentic node that performs an action (typically state mutation).
func MapReduce ¶ added in v0.10.0
func MapReduce[In, Out any](opts MapReduceOptions[In, Out]) ActionFunc
MapReduce creates an ActionFunc that runs a map-reduce pipeline. Suitable for use as a GraphNode.Action.
Execution model:
- Map reads state and produces a slice of inputs.
- Workers run concurrently (bounded by MaxConcurrency via a buffered channel semaphore when MaxConcurrency > 0).
- Results are collected in input order.
- Reduce receives all results (including errors) and writes to state.
type AfterAgentHandoffHook ¶ added in v0.9.0
type AfterAgentHandoffHook struct {
FromAgent string // the previously active agent
ToAgent string // the now-active agent
State State // shared swarm state after handoff
}
AfterAgentHandoffHook fires after the swarm has completed a handoff. This is an observer hook — it cannot interrupt the swarm.
func (*AfterAgentHandoffHook) AfterHookEvent ¶ added in v0.9.0
func (*AfterAgentHandoffHook) AfterHookEvent()
func (*AfterAgentHandoffHook) HookEvent ¶ added in v0.9.0
func (*AfterAgentHandoffHook) HookEvent()
type AfterNodeExecutionHook ¶ added in v0.9.0
type AfterNodeExecutionHook struct {
Node string // the node that just executed
State State // shared graph state after execution
}
AfterNodeExecutionHook fires after a node has completed execution. This is an observer hook — it cannot interrupt the graph.
func (*AfterNodeExecutionHook) AfterHookEvent ¶ added in v0.9.0
func (*AfterNodeExecutionHook) AfterHookEvent()
func (*AfterNodeExecutionHook) HookEvent ¶ added in v0.9.0
func (*AfterNodeExecutionHook) HookEvent()
type BeforeAgentHandoffHook ¶ added in v0.9.0
type BeforeAgentHandoffHook struct {
FromAgent string // the currently active agent
ToAgent string // the agent being transferred to
State State // shared swarm state
}
BeforeAgentHandoffHook fires before the swarm transfers control to a different agent. Return bond.ErrAbort to prevent the handoff and stop the swarm, or any error to halt with that error.
func (*BeforeAgentHandoffHook) BeforeHookEvent ¶ added in v0.9.0
func (*BeforeAgentHandoffHook) BeforeHookEvent()
func (*BeforeAgentHandoffHook) HookEvent ¶ added in v0.9.0
func (*BeforeAgentHandoffHook) HookEvent()
type BeforeNodeTransitionHook ¶ added in v0.9.0
type BeforeNodeTransitionHook struct {
FromNode string // current node (empty string on initial entry)
ToNode string // target node being transitioned to
State State // shared graph state
}
BeforeNodeTransitionHook fires before the graph transitions to a new node. Return bond.ErrAbort to stop the graph gracefully, or any error to halt with that error. This enables human-in-the-loop approval gates on graph traversal.
func (*BeforeNodeTransitionHook) BeforeHookEvent ¶ added in v0.9.0
func (*BeforeNodeTransitionHook) BeforeHookEvent()
func (*BeforeNodeTransitionHook) HookEvent ¶ added in v0.9.0
func (*BeforeNodeTransitionHook) HookEvent()
type CheckpointStore ¶ added in v0.10.0
type CheckpointStore interface {
// Save persists a snapshot. Overwrites any existing snapshot with
// the same ID.
Save(ctx context.Context, id string, snapshot *Snapshot) error
// Load retrieves a snapshot by ID. Implementations must return
// [ErrSnapshotNotFound] if the snapshot does not exist.
Load(ctx context.Context, id string) (*Snapshot, error)
// Delete removes a snapshot by ID. No-op if not found.
Delete(ctx context.Context, id string) error
}
CheckpointStore persists and retrieves graph execution snapshots. Implementations must return ErrSnapshotNotFound from Load when the requested snapshot ID does not exist.
type EdgeCondition ¶
EdgeCondition evaluates the shared state and returns the name of the next node to transition to. Return EndNode to terminate.
type Graph ¶
type Graph struct {
// contains filtered or unexported fields
}
Graph is an agent that orchestrates execution across a directed graph of sub-agents and action nodes. Each agent node runs its own bond.Stream loop internally; the graph yields events transparently to the caller.
Graph implements bond.Agent.
Example:
g := agent.NewGraph("triage", agent.GraphOptions{
State: agent.MapState{"user_tier": "premium"},
})
// Agent node — runs a full LLM loop with auto-injected state tools.
g.AddNode("triage", &agent.GraphNode{Agent: triageAgent, Options: triageOpts})
g.AddNode("billing_agent", &agent.GraphNode{Agent: billingAgent, Options: billingOpts})
// Action node — pure function, no LLM. Reads/writes state directly.
g.AddNode("fetch_order", &agent.GraphNode{
Action: func(ctx context.Context, state agent.State) error {
orderID, _ := state.Get("order_id")
order := db.FetchOrder(ctx, orderID.(string))
state.Set("order", order)
return nil
},
})
// Static edge: fetch_order always goes to billing_agent.
g.AddEdge("fetch_order", "billing_agent")
// Conditional edge: triage routes based on state.
g.AddConditionalEdge("triage", func(state agent.State) string {
cat, _ := state.Get("category")
switch cat {
case "billing":
return "fetch_order"
default:
return agent.EndNode
}
})
// Use like any other agent.
for event, err := range bond.Stream(ctx, g, bond.TextPrompt("help me"), bond.StreamOptions{}) {
// events flow transparently from all nodes
}
func NewGraph ¶
func NewGraph(entry string, opts GraphOptions) *Graph
NewGraph creates a graph with the given entry node name and options.
Example ¶
package main
import (
"context"
"fmt"
"github.com/nisimpson/bond"
"github.com/nisimpson/bond/agent"
"github.com/nisimpson/bond/bondtest"
)
func main() {
// A simple graph: classify (action) → conditional → respond (agent)
respond := &bondtest.Agent{
Events: bondtest.TextEvents("I can help with your bill."),
}
g := agent.NewGraph("classify", agent.GraphOptions{})
g.AddNode("classify", &agent.GraphNode{
Action: func(ctx context.Context, state agent.State) error {
state.Set("category", "billing")
return nil
},
})
g.AddNode("respond", &agent.GraphNode{Agent: respond})
g.AddConditionalEdge("classify", func(state agent.State) string {
cat, _ := state.Get("category")
if cat == "billing" {
return "respond"
}
return agent.EndNode
})
resp, err := bond.Invoke(context.Background(), g, bond.TextPrompt("my bill is wrong"), bond.AgentOptions{})
if err != nil {
panic(err)
}
fmt.Println(resp.Text)
}
Output: I can help with your bill.
func (*Graph) AddConditionalEdge ¶
func (g *Graph) AddConditionalEdge(from string, condition EdgeCondition)
AddConditionalEdge adds a dynamic transition that evaluates at runtime based on the shared state.
func (*Graph) FanOutEdge ¶ added in v0.10.0
FanOutEdge adds a fan-out edge from a source node to multiple target nodes that execute concurrently. After all targets complete, execution proceeds to the join node.
During parallel execution, only text output (bond.StreamEventTextDelta) from agent nodes is collected and merged into history. Other event types (tool-use, structured output) produced by parallel branches are not yielded to the caller. If full event visibility is required, use sequential edges instead.
func (*Graph) ResumeFrom ¶ added in v0.10.0
func (g *Graph) ResumeFrom(ctx context.Context, snapshotID string) iter.Seq2[bond.StreamEvent, error]
ResumeFrom loads a snapshot and resumes graph execution from the checkpointed next node. It restores state and history before continuing. Returns an error-yielding iterator if the checkpoint store is not configured or the snapshot cannot be loaded.
ResumeFrom performs additive state restoration — it sets keys from the snapshot but does not clear pre-existing keys. Callers should use a fresh Graph instance (or clear state manually) to avoid stale key leakage from prior executions.
type GraphNode ¶
type GraphNode struct {
// Agent node — runs bond.Stream with tools and hooks.
Agent bond.Agent
Options bond.AgentOptions
// Action node — runs a function that can read/write state.
// Mutually exclusive with Agent.
Action ActionFunc
}
GraphNode represents a node in the agent graph. A node is either an agent node (Agent is set) or an action node (Action is set), not both.
type GraphOptions ¶
type GraphOptions struct {
// State is the shared state for the graph. If nil, a MapState is created.
State State
// CheckpointStore persists graph execution snapshots after each node.
// If nil, checkpointing is disabled.
CheckpointStore CheckpointStore
// CheckpointID is the snapshot ID key used when saving/loading/deleting
// checkpoints. If empty and CheckpointStore is set, a default ID of
// "graph-<entry>" is generated to avoid key collisions when multiple
// graphs share a store.
CheckpointID string
}
GraphOptions configures a Graph.
type HistoryPolicy ¶ added in v0.10.0
type HistoryPolicy interface {
// Select returns a subset of messages satisfying the policy's constraints.
// The returned slice preserves the relative ordering of the input.
// Returns an error if the policy cannot produce a valid result.
Select(ctx context.Context, messages []bond.Message) ([]bond.Message, error)
}
HistoryPolicy selects a subset of messages from a conversation. Implementations are used for conversation trimming (extra/session), swarm transfer context carry-over, and summarization workflows.
The returned slice must preserve the relative ordering of the input.
type InMemoryCheckpointStore ¶ added in v0.10.0
type InMemoryCheckpointStore struct {
// contains filtered or unexported fields
}
InMemoryCheckpointStore is a thread-safe, in-process CheckpointStore for development and testing. It stores snapshots in a Go map protected by a sync.RWMutex.
func NewInMemoryCheckpointStore ¶ added in v0.10.0
func NewInMemoryCheckpointStore() *InMemoryCheckpointStore
NewInMemoryCheckpointStore creates a new InMemoryCheckpointStore ready for use.
func (*InMemoryCheckpointStore) Delete ¶ added in v0.10.0
func (s *InMemoryCheckpointStore) Delete(_ context.Context, id string) error
Delete removes a snapshot by ID. It is a no-op if the snapshot does not exist.
func (*InMemoryCheckpointStore) Load ¶ added in v0.10.0
Load retrieves a snapshot by ID. Returns ErrSnapshotNotFound if the snapshot does not exist.
The returned pointer references the stored object directly. Callers that need mutation isolation should copy the value before modifying it.
type MapReduceOptions ¶ added in v0.10.0
type MapReduceOptions[In, Out any] struct { // Map produces work items from the current state. Called once before // workers are launched. If Map returns an error, the action returns // that error immediately without launching any workers. Map func(ctx context.Context, state State) ([]In, error) // Worker processes a single input. Called concurrently for each // item produced by Map. Implementations should respect context // cancellation. Worker func(ctx context.Context, input In) (Out, error) // Reduce combines all results into state. Called once after all // workers finish. Results are in input order (indexed by position). // Check each result's Err field for individual worker failures. Reduce func(ctx context.Context, state State, results []MapReduceResult[Out]) error // MaxConcurrency limits parallel workers. Zero means unlimited. MaxConcurrency int }
MapReduceOptions configures a MapReduce action.
type MapReduceResult ¶ added in v0.10.0
type MapReduceResult[T any] struct { Index int // position in the original input slice Value T // worker output (zero value if Err != nil) Err error // nil on success }
MapReduceResult holds one worker's output alongside any error. The Index field corresponds to the position in the original input slice returned by the Map function.
type MapState ¶
type MapState struct {
// contains filtered or unexported fields
}
MapState is a concurrency-safe in-memory State implementation.
func NewMapStateFrom ¶
NewMapStateFrom creates a MapState pre-populated with the given data.
type Snapshot ¶ added in v0.10.0
type Snapshot struct {
ID string `json:"id"`
Node string `json:"node"` // node that just completed
NextNode string `json:"next_node"` // node to execute next
State map[string]any `json:"state"` // serialized shared state
History []bond.Message `json:"history"` // conversation history
}
Snapshot captures graph execution state at a point in time. It is serializable to JSON for storage in any backend.
type State ¶
type State interface {
// Get retrieves a value by key. Returns false if the key doesn't exist.
Get(key string) (any, bool)
// Set stores a value by key.
Set(key string, value any)
// Keys returns all keys currently in state.
Keys() []string
}
State is the interface for shared graph state. Implementations can be backed by a map, filesystem, database, or any other storage mechanism. Implementations must be safe for concurrent use.
type Swarm ¶
type Swarm struct {
// contains filtered or unexported fields
}
Swarm is an agent that enables dynamic handoffs between multiple agents. Each agent can transfer control to another by calling a `transfer_to_<name>` tool. The receiving agent picks up with the full conversation history.
Swarm implements bond.Agent.
Example:
s := agent.NewSwarm("triage", agent.SwarmOptions{
State: agent.MapState{"user_tier": "premium"},
})
s.AddAgent("triage", &agent.SwarmAgent{Agent: triageAgent, Options: triageOpts})
s.AddAgent("billing", &agent.SwarmAgent{Agent: billingAgent, Options: billingOpts})
s.AddAgent("tech", &agent.SwarmAgent{Agent: techAgent, Options: techOpts})
// Use like any other agent. Agents transfer between each other via tool calls.
for event, err := range bond.Stream(ctx, s, bond.TextPrompt("my bill is wrong"), bond.StreamOptions{}) {
fmt.Print(event.TextDelta)
}
func NewSwarm ¶
func NewSwarm(entry string, opts SwarmOptions) *Swarm
NewSwarm creates a swarm with the given initial active agent name.
Example ¶
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/nisimpson/bond"
"github.com/nisimpson/bond/agent"
"github.com/nisimpson/bond/bondtest"
)
func main() {
// Handler transfers to specialist, specialist responds.
// Sequence provides two invocations: the first emits a transfer tool call,
// the second completes the bond.Stream tool loop with no additional text.
handler := &bondtest.Agent{
StreamFunc: bondtest.Sequence(
bondtest.ToolUseEvents(&bond.ToolUseBlock{
ID: "xfer_1",
Name: "transfer_to_specialist",
Input: json.RawMessage(`{}`),
}),
// After bond.Stream processes the tool result, re-invoke yields end.
[]bond.StreamEvent{
{Type: bond.StreamEventStart},
{Type: bond.StreamEventStop, StopReason: bond.StopReasonEnd},
},
),
}
specialist := &bondtest.Agent{
Events: bondtest.TextEvents("I'm the specialist. Here's your answer."),
}
s := agent.NewSwarm("handler", agent.SwarmOptions{})
s.AddAgent("handler", &agent.SwarmAgent{
Agent: handler,
Description: "Routes requests to the right specialist.",
})
s.AddAgent("specialist", &agent.SwarmAgent{
Agent: specialist,
Description: "Handles specialized queries.",
})
resp, err := bond.Invoke(context.Background(), s, bond.TextPrompt("help me"), bond.AgentOptions{})
if err != nil {
panic(err)
}
fmt.Println(resp.Text)
}
Output: I'm the specialist. Here's your answer.
func (*Swarm) AddAgent ¶
func (s *Swarm) AddAgent(name string, agent *SwarmAgent)
AddAgent registers a named agent in the swarm.
type SwarmAgent ¶
type SwarmAgent struct {
Agent bond.Agent
Options bond.AgentOptions
// Description tells other agents when and why to transfer to this one.
// This becomes the transfer tool's description that the active agent sees.
Description string
// HistoryPolicy optionally filters conversation history before this agent
// receives it on transfer. If nil, the full history is passed (default).
HistoryPolicy HistoryPolicy
}
SwarmAgent represents a named agent in the swarm.
type SwarmOptions ¶
type SwarmOptions struct {
// State is the shared state for the swarm. If nil, a MapState is created.
State State
// MaxHandoffs limits the number of agent transfers. 0 means unlimited.
MaxHandoffs int
}
SwarmOptions configures a Swarm.
type ToolOptions ¶
type ToolOptions struct {
// Name is the tool identifier the model will reference.
Name string
// Description helps the model decide when to delegate to this agent.
Description string
// InputSchema describes the expected input. Defaults to {"prompt": string}.
InputSchema json.Marshaler
// StreamOptions configures the sub-agent's loop (tools, hooks, plugins, max turns).
StreamOptions bond.AgentOptions
}
ToolOptions configures an agent-backed tool.