agent

package
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: 10 Imported by: 0

Documentation

Overview

Package agent provides utilities for using bond agents as tools, enabling sub-agent orchestration patterns.

Index

Examples

Constants

View Source
const EndNode = "__end__"

EndNode is the sentinel name indicating the graph should stop execution.

Variables

View Source
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": "..."}.

func ContextWithState added in v0.9.0

func ContextWithState(ctx context.Context, state State) context.Context

ContextWithState attaches a State to the context.

Types

type ActionFunc

type ActionFunc func(ctx context.Context, state State) error

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:

  1. Map reads state and produces a slice of inputs.
  2. Workers run concurrently (bounded by MaxConcurrency via a buffered channel semaphore when MaxConcurrency > 0).
  3. Results are collected in input order.
  4. 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

type EdgeCondition func(state State) string

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) AddEdge

func (g *Graph) AddEdge(from, to string)

AddEdge adds a static (unconditional) transition from one node to another.

func (*Graph) AddNode

func (g *Graph) AddNode(name string, node *GraphNode)

AddNode registers a named node in the graph.

func (*Graph) FanOutEdge added in v0.10.0

func (g *Graph) FanOutEdge(from string, targets []string, join string)

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.

func (*Graph) Stream

func (g *Graph) Stream(ctx context.Context, messages []bond.Message) iter.Seq2[bond.StreamEvent, error]

Stream implements bond.Agent. It walks the graph starting from the entry node, running each node's agent or action and forwarding events to the caller.

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

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.

func (*InMemoryCheckpointStore) Save added in v0.10.0

func (s *InMemoryCheckpointStore) Save(_ context.Context, id string, snapshot *Snapshot) error

Save persists a snapshot, overwriting any existing snapshot with the same ID.

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 NewMapState

func NewMapState() *MapState

NewMapState creates an empty MapState.

func NewMapStateFrom

func NewMapStateFrom(initial map[string]any) *MapState

NewMapStateFrom creates a MapState pre-populated with the given data.

func (*MapState) Get

func (m *MapState) Get(key string) (any, bool)

func (*MapState) Keys

func (m *MapState) Keys() []string

func (*MapState) Set

func (m *MapState) Set(key string, value any)

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.

func StateFromContext

func StateFromContext(ctx context.Context) State

StateFromContext retrieves the graph State from a context. Returns nil if no state is attached (e.g., not running inside a graph).

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.

func (*Swarm) Stream

func (s *Swarm) Stream(ctx context.Context, messages []bond.Message) iter.Seq2[bond.StreamEvent, error]

Stream implements bond.Agent. It runs the active agent and handles transfer_to_<name> tool calls to switch between agents.

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.

Jump to

Keyboard shortcuts

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