agentboot

package
v0.260507.1 Latest Latest
Warning

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

Go to latest
Published: May 6, 2026 License: MPL-2.0 Imports: 15 Imported by: 0

README

AgentBoot Package

AgentBoot is a unified agent bootstrapping and management package that provides a clean abstraction for running different AI coding agents.

Features

  • Unified Agent Interface: Common interface for all agent types
  • Stream-JSON Support: Real-time event streaming for rich output
  • Permission Management: Flexible permission handling with multiple modes
  • Extensible Design: Easy to add new agent types
  • Configuration: Environment-based configuration

Supported Agents

Agent Status Description
claude ✅ Implemented Claude Code CLI
codex 🚧 Planned OpenAI Codex
gemini 🚧 Planned Google Gemini
cursor 🚧 Planned Cursor AI

Usage

Basic Usage
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/tingly-dev/tingly-box/agentboot"
    _ "github.com/tingly-dev/tingly-box/agentboot/claude" // Import to register Claude agent
)

func main() {
    // Create AgentBoot instance
    ab, err := agentboot.New(agentboot.Config{
        DefaultAgent:     agentboot.AgentTypeClaude,
        DefaultFormat:    agentboot.OutputFormatStreamJSON,
        EnableStreamJSON: true,
    })
    if err != nil {
        log.Fatal(err)
    }

    // Register Claude agent (or use agents that have auto-registered via init())
    claudeAgent := claude.NewAgent(ab.GetConfig())
    ab.RegisterAgent(agentboot.AgentTypeClaude, claudeAgent)

    // Get Claude agent
    agent, err := ab.GetAgent(agentboot.AgentTypeClaude)
    if err != nil {
        log.Fatal(err)
    }

    // Execute prompt
    result, err := agent.Execute(context.Background(), "Say hello", agentboot.ExecutionOptions{
        OutputFormat: agentboot.OutputFormatStreamJSON,
    })

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(result.TextOutput())
}
Alternative: Using Claude Agent Directly
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/tingly-dev/tingly-box/agentboot"
    "github.com/tingly-dev/tingly-box/agentboot/claude"
)

func main() {
    // Create config
    config := agentboot.Config{
        DefaultFormat:    agentboot.OutputFormatStreamJSON,
        EnableStreamJSON: true,
    }

    // Create Claude agent directly
    agent := claude.NewAgent(config)

    // Execute prompt
    result, err := agent.Execute(context.Background(), "Say hello", agentboot.ExecutionOptions{
        OutputFormat: agentboot.OutputFormatStreamJSON,
    })

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(result.TextOutput())
}
With Permission Handler
package main

import (
    "context"
    "log"

    "github.com/tingly-dev/tingly-box/agentboot"
    "github.com/tingly-dev/tingly-box/agentboot/claude"
    "github.com/tingly-dev/tingly-box/agentboot/permission"
)

func main() {
    // Create configuration
    config := agentboot.Config{
        DefaultFormat:    agentboot.OutputFormatStreamJSON,
        EnableStreamJSON: true,
    }

    // Create permission handler
    permHandler := permission.NewDefaultHandler(permission.Config{
        DefaultMode: agentboot.PermissionModeManual,
        Timeout:     300, // 5 minutes
    })

    // Create agent with permission handler
    agent := claude.NewAgentWithPermissionHandler(config, permHandler)

    // Set permission mode for a session
    permHandler.SetMode("session-123", agentboot.PermissionModeAuto)

    // Execute prompt
    result, err := agent.Execute(context.Background(), "List files", agentboot.ExecutionOptions{
        OutputFormat: agentboot.OutputFormatStreamJSON,
    })

    if err != nil {
        log.Fatal(err)
    }

    log.Println(result.TextOutput())
}

Output Formats

Text Format

Simple text output (default):

claude --print --output-format text "Say hello"
Stream-JSON Format

Rich event streaming:

claude --output-format stream-json --verbose --print "Say hello"

Event types:

  • text_delta - Incremental text output
  • tool_call_start - Tool invocation begins
  • tool_call_end - Tool completes
  • permission_request - Permission needed
  • status - Agent status change
  • thinking - Reasoning state

Permission Modes

Mode Description
auto Auto-approve all requests
manual Require user approval
skip Skip permission prompts

Configuration

Environment Variables
Variable Description Default
AGENTBOOT_DEFAULT_AGENT Default agent type claude
AGENTBOOT_DEFAULT_FORMAT Default output format text
AGENTBOOT_ENABLE_STREAM_JSON Enable stream-json true
AGENTBOOT_STREAM_BUFFER_SIZE Event buffer size 100
RCC_PERMISSION_MODE Permission mode auto
RCC_PERMISSION_TIMEOUT Approval timeout 5m
RCC_WHITELIST Whitelisted tools
RCC_BLACKLIST Blacklisted tools

Package Structure

agentboot/
├── agentboot.go      # Core package and factory
├── types.go          # Common types and interfaces
├── config.go         # Configuration
├── events/           # Event streaming
│   ├── events.go     # Event interfaces
│   ├── parser.go     # Stream parser
│   └── bus.go        # Event bus
├── permission/       # Permission management
│   ├── handler.go    # Permission handler interface
│   ├── handler_impl.go # Default implementation
│   └── store.go      # Permission history storage
└── claude/           # Claude Code agent
    ├── agent.go      # Claude agent
    ├── launcher.go   # Process launcher
    ├── config.go     # Claude config
    └── events.go     # Claude event types

Adding a New Agent

  1. Create agent package: agentboot/youragent/

  2. Implement agent.Agent interface:

type Agent struct {
    launcher *Launcher
}

func (a *Agent) Execute(ctx context.Context, prompt string, opts agentboot.ExecutionOptions) (*agentboot.Result, error)
func (a *Agent) IsAvailable() bool
func (a *Agent) Type() agentboot.AgentType
func (a *Agent) SetDefaultFormat(format agentboot.OutputFormat)
func (a *Agent) GetDefaultFormat() agentboot.OutputFormat
  1. Register in agentboot.go:
ab.agents[AgentTypeYourAgent] = youragent.NewAgent(ab.config)

License

MIT

Documentation

Index

Constants

View Source
const (
	// EventTypeInit indicates agent initialization
	EventTypeInit = "init"
	// EventTypeSystem indicates system-level messages
	EventTypeSystem = "system"
	// EventTypeAssistant indicates assistant/agent response messages
	EventTypeAssistant = "assistant"
	// EventTypeUser indicates user messages (echoed back)
	EventTypeUser = "user"
	// EventTypeToolUse indicates a tool is being invoked
	EventTypeToolUse = "tool_use"
	// EventTypeToolResult indicates the result of a tool invocation
	EventTypeToolResult = "tool_result"
	// EventTypePermissionRequest indicates a permission request is pending
	EventTypePermissionRequest = "permission_request"
	// EventTypePermissionResult indicates the result of a permission request
	EventTypePermissionResult = "permission_result"
	// EventTypeResult indicates the final result of execution
	EventTypeResult = "result"
	// EventTypeError indicates an error occurred
	EventTypeError = "error"
	// EventTypeStreamDelta indicates incremental streaming content
	EventTypeStreamDelta = "stream_delta"
)

EventType constants for unified agent events All agents should map their internal events to these standard types

Variables

View Source
var (
	// ErrHandleClosed is returned when the handle's event stream has already
	// closed (process exited, Cancel called, or ctx canceled).
	ErrHandleClosed = errors.New("agentboot: execution handle closed")

	// ErrUnknownRequestID is returned when reqID does not correspond to a
	// pending control request, either because the ID is wrong or because
	// the runner already received a response for it.
	ErrUnknownRequestID = errors.New("agentboot: unknown request id")
)

Sentinel errors returned by [ExecutionHandle.Respond].

Functions

func NewClaudeStore added in v0.260414.2000

func NewClaudeStore(projectsDir string) (common.SessionStore, error)

NewClaudeStore creates a new Claude session store

func NewControlledHandle added in v0.260507.1

func NewControlledHandle(
	bufferSize int,
	responseFn func(reqID string, resp ControlResponse) error,
	cancelFn func(),
	waitFn func() (*Result, error),
) (ExecutionHandle, *HandleControls)

NewControlledHandle builds an ExecutionHandle whose lifecycle is driven directly by the caller via the returned HandleControls.

Use this from Agent implementations that do not go through the process+protocol pipeline (e.g. in-process mocks).

The supplied closures define:

  • responseFn: how [ExecutionHandle.Respond] routes a ControlResponse back to the in-flight execution. For an in-process agent this is typically a channel send to a goroutine waiting for the response.
  • cancelFn: how [ExecutionHandle.Cancel] requests cooperative shutdown.
  • waitFn: how [ExecutionHandle.Wait] gathers the final Result and error.

Types

type Agent

type Agent interface {
	Execute(ctx context.Context, prompt string, opts ExecutionOptions) (ExecutionHandle, error)
	IsAvailable() bool
	Type() AgentType
	SetDefaultFormat(format OutputFormat)
	GetDefaultFormat() OutputFormat
}

Agent is the interface for all agent types.

Execute returns an ExecutionHandle; the caller iterates handle.Events() to consume the totally-ordered event stream, calls handle.Respond(...) to answer Approval/Ask requests, and calls handle.Wait() to obtain the aggregated Result. See the ExecutionHandle docs for lifecycle details.

type AgentBoot

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

AgentBoot manages agent instances

func New

func New(config Config) (*AgentBoot, error)

New creates a new AgentBoot instance. Returns an error only if ClaudeProjectsDir is set but cannot be initialized.

func (*AgentBoot) GetAgent

func (ab *AgentBoot) GetAgent(agentType AgentType) (Agent, error)

GetAgent returns an agent by type

func (*AgentBoot) GetConfig

func (ab *AgentBoot) GetConfig() Config

GetConfig returns the current configuration

func (*AgentBoot) GetDefaultAgent

func (ab *AgentBoot) GetDefaultAgent() (Agent, error)

GetDefaultAgent returns the default agent

func (*AgentBoot) GetSessionSummary added in v0.260507.1

func (ab *AgentBoot) GetSessionSummary(ctx context.Context, sessionID string, firstN, lastM int) (*common.SessionSummary, error)

GetSessionSummary returns a summary of a session

func (*AgentBoot) ListAgents

func (ab *AgentBoot) ListAgents() []AgentType

ListAgents returns all registered agent types

func (*AgentBoot) ListRecentSessions added in v0.260507.1

func (ab *AgentBoot) ListRecentSessions(ctx context.Context, projectPath string, limit int) ([]common.SessionMetadata, error)

ListRecentSessions returns recent sessions for a project

func (*AgentBoot) MustGetAgent

func (ab *AgentBoot) MustGetAgent(agentType AgentType) Agent

MustGetAgent returns an agent by type or panics

func (*AgentBoot) RegisterAgent

func (ab *AgentBoot) RegisterAgent(agentType AgentType, agent Agent)

RegisterAgent registers a new agent type

func (*AgentBoot) ResumeSession added in v0.260507.1

func (ab *AgentBoot) ResumeSession(sessionID string) ExecutionOptions

ResumeSession creates ExecutionOptions to resume a session

func (*AgentBoot) SetDefaultAgent

func (ab *AgentBoot) SetDefaultAgent(agentType AgentType) error

SetDefaultAgent sets the default agent type

type AgentDriver added in v0.260507.1

type AgentDriver interface {
	// Prepare returns a LaunchSpec describing how to start the agent.
	// The spec is consumed by a Runner; the Driver itself does not start anything.
	Prepare(ctx context.Context, prompt string, opts ExecutionOptions) (*LaunchSpec, error)

	// IsAvailable reports whether the agent binary is present and usable.
	IsAvailable() bool

	// Type returns the agent type this driver handles.
	Type() AgentType
}

AgentDriver knows how to prepare the launch of an agent process. Each agent type (Claude, Codex, opencode, …) provides its own Driver.

A Driver is responsible for:

  • Binary discovery
  • CLI argument construction
  • Environment setup
  • Initial prompt injection (stdin bootstrap)

A Driver does NOT manage the running process or the communication protocol.

type AgentMessage

type AgentMessage interface {
	// GetType returns the message type (one of EventType constants)
	GetType() string
	// GetTimestamp returns when the message was created
	GetTimestamp() time.Time
	// GetSessionID returns the session ID if available
	GetSessionID() string
	// GetAgentType returns the source agent type
	GetAgentType() AgentType
	// GetRawData returns the raw message data as a map
	GetRawData() map[string]interface{}
	// ToEvent converts the message to an Event
	ToEvent() Event
}

AgentMessage is the unified interface for all agent messages All agent implementations should convert their messages to this interface

func MessageFromEvent

func MessageFromEvent(event Event, agentType AgentType) AgentMessage

MessageFromEvent converts an Event to an AgentMessage if possible

type AgentTransport added in v0.260507.1

type AgentTransport interface {
	// Classify reports the kind of the event. For control events it also
	// returns the parsed StreamEvent (ApprovalRequestEvent or
	// AskRequestEvent) ready to emit on the handle.
	//
	// The execution-context fields (sessionID, chatID, platform, botUUID)
	// previously set via SetExecutionContext are stamped onto the StreamEvent
	// during Classify.
	Classify(ev common.Event) (kind EventKind, parsed StreamEvent)

	// AccumulateMessage feeds the event to the per-agent message accumulator
	// and returns 0+ rich message values to emit as [MessageEvent.Raw]. The
	// concrete type of each value is agent-specific (e.g.
	// *claude.AssistantMessage). The runner does not introspect them.
	AccumulateMessage(ev common.Event) []any

	// EncodeControlResponse converts a [ControlResponse] into the wire value
	// sent to the agent process's stdin via [protocol.Encoder].
	//
	// originalInput is the Input field of the corresponding
	// ApprovalRequestEvent / AskRequestEvent; some agents (e.g. claude) use
	// it when constructing the "allow" reply if the response did not supply
	// an UpdatedInput.
	EncodeControlResponse(reqID string, resp ControlResponse, originalInput map[string]any) any

	// SetExecutionContext injects per-execution routing metadata that is
	// stamped onto Approval/Ask events during Classify.
	SetExecutionContext(sessionID, chatID, platform, botUUID string)
}

AgentTransport is the per-agent protocol parser. It is pure: it consumes common.Event values and produces classifications and encoded responses, but performs no IO and owns no goroutines.

Each agent type (Claude, Codex, …) provides its own AgentTransport.

type AgentType

type AgentType string

AgentType defines the supported agent types

const (
	AgentTypeClaude    AgentType = "claude"
	AgentTypeMockAgent AgentType = "mock" // Mock agent for testing

)

func (AgentType) String

func (t AgentType) String() string

String returns the string representation of AgentType

type ApprovalHandler

type ApprovalHandler interface {
	OnApproval(ctx context.Context, req PermissionRequest) (PermissionResult, error)
}

ApprovalHandler handles permission confirmations

type ApprovalRequestEvent added in v0.260507.1

type ApprovalRequestEvent struct {
	ID        string
	AgentType AgentType
	ToolName  string
	Input     map[string]any
	Reason    string

	SessionID string
	ChatID    string
	Platform  string
	BotUUID   string
}

ApprovalRequestEvent is emitted when the agent requests permission to use a tool. Callers must call [ExecutionHandle.Respond] with ApprovalResponse to unblock the agent.

type ApprovalResponse added in v0.260507.1

type ApprovalResponse struct {
	Approved     bool
	UpdatedInput map[string]any
	Reason       string
}

ApprovalResponse responds to an ApprovalRequestEvent.

type AskHandler

type AskHandler interface {
	OnAsk(ctx context.Context, req AskRequest) (AskResult, error)
}

AskHandler handles user questions/selections

type AskRequest

type AskRequest struct {
	ID   string `json:"id"`
	Type string `json:"type"` // "permission", "question", "confirmation", "text_input"

	Platform  string `json:"platform"`
	ChatID    string `json:"chat_id"`
	BotUUID   string `json:"bot_uuid"`
	SessionID string `json:"session_id,omitempty"`

	AgentType AgentType              `json:"agent_type"`
	ToolName  string                 `json:"tool_name,omitempty"`
	Input     map[string]interface{} `json:"input,omitempty"`
	Message   string                 `json:"message,omitempty"`
	CallID    string                 `json:"call_id,omitempty"`
	Reason    string                 `json:"reason,omitempty"`
	Metadata  map[string]interface{} `json:"metadata,omitempty"`
}

AskRequest represents a request to ask the user something This is a simplified version of ask.Request to avoid circular imports

type AskRequestEvent added in v0.260507.1

type AskRequestEvent struct {
	ID        string
	AgentType AgentType
	Type      string
	ToolName  string
	Input     map[string]any
	CallID    string
	Message   string
	Reason    string

	SessionID string
	ChatID    string
	Platform  string
	BotUUID   string
}

AskRequestEvent is emitted for AskUserQuestion-style interactive prompts. Callers respond via [ExecutionHandle.Respond] with AskResponse.

type AskResponse added in v0.260507.1

type AskResponse struct {
	Approved     bool
	UpdatedInput map[string]any
	Reason       string
	Response     string
	Selection    map[string]any
}

AskResponse responds to an AskRequestEvent.

type AskResult

type AskResult struct {
	ID           string                 `json:"id"`
	Approved     bool                   `json:"approved,omitempty"`
	Response     string                 `json:"response,omitempty"`
	Selection    map[string]interface{} `json:"selection,omitempty"`
	Remember     bool                   `json:"remember,omitempty"`
	Reason       string                 `json:"reason,omitempty"`
	UpdatedInput map[string]interface{} `json:"updated_input,omitempty"`
}

AskResult represents the user's response to an ask request

type AssistantMessage

type AssistantMessage struct {
	BaseMessage
	Text    string                 `json:"text,omitempty"`
	Content []ContentBlock         `json:"content,omitempty"`
	Extra   map[string]interface{} `json:"extra,omitempty"`
}

AssistantMessage represents an assistant response

func NewAssistantMessage

func NewAssistantMessage(agentType AgentType, sessionID, text string) *AssistantMessage

NewAssistantMessage creates a new assistant message

func (*AssistantMessage) GetRawData

func (m *AssistantMessage) GetRawData() map[string]interface{}

GetRawData returns raw data

func (*AssistantMessage) GetText

func (m *AssistantMessage) GetText() string

GetText returns the text content

func (*AssistantMessage) ToEvent

func (m *AssistantMessage) ToEvent() Event

ToEvent converts AssistantMessage to Event

type BaseMessage

type BaseMessage struct {
	Type      string    `json:"type"`
	AgentType AgentType `json:"agent_type"`
	SessionID string    `json:"session_id,omitempty"`
	Timestamp time.Time `json:"timestamp"`
}

BaseMessage provides common fields for message implementations

func (*BaseMessage) GetAgentType

func (m *BaseMessage) GetAgentType() AgentType

GetAgentType returns the agent type

func (*BaseMessage) GetRawData

func (m *BaseMessage) GetRawData() map[string]interface{}

GetRawData returns the raw data - should be overridden by embedders

func (*BaseMessage) GetSessionID

func (m *BaseMessage) GetSessionID() string

GetSessionID returns the session ID

func (*BaseMessage) GetTimestamp

func (m *BaseMessage) GetTimestamp() time.Time

GetTimestamp returns the timestamp

func (*BaseMessage) GetType

func (m *BaseMessage) GetType() string

GetType returns the message type

func (*BaseMessage) ToEvent

func (m *BaseMessage) ToEvent() Event

ToEvent converts BaseMessage to Event - should be overridden by embedders

type CompletionCallback

type CompletionCallback interface {
	OnComplete(result *CompletionResult)
}

CompletionCallback handles completion notification

type CompletionResult

type CompletionResult struct {
	Success     bool
	DurationMS  int64
	SessionID   string
	Error       string
	ExtraFields map[string]any
}

CompletionResult contains the final result information

type CompositeHandler

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

CompositeHandler combines multiple handler interfaces into one MessageHandler. It allows composing different handlers for streaming, approval, ask, and completion.

func NewCompositeHandler

func NewCompositeHandler() *CompositeHandler

NewCompositeHandler creates a new empty CompositeHandler. Use Set* methods to configure individual handlers.

func (*CompositeHandler) OnApproval

OnApproval implements MessageHandler. Forwards to the ApprovalHandler if set, otherwise auto-approves.

func (*CompositeHandler) OnAsk

func (h *CompositeHandler) OnAsk(ctx context.Context, req AskRequest) (AskResult, error)

OnAsk implements MessageHandler. Forwards to the AskHandler if set, otherwise auto-approves.

func (*CompositeHandler) OnComplete

func (h *CompositeHandler) OnComplete(result *CompletionResult)

OnComplete implements MessageHandler. Forwards to the CompletionCallback if set.

func (*CompositeHandler) OnError

func (h *CompositeHandler) OnError(err error)

OnError implements MessageHandler. Forwards to the MessageStreamer if set.

func (*CompositeHandler) OnMessage

func (h *CompositeHandler) OnMessage(msg interface{}) error

OnMessage implements MessageHandler. Forwards to the MessageStreamer if set.

func (*CompositeHandler) SetApprovalHandler

func (h *CompositeHandler) SetApprovalHandler(a ApprovalHandler) *CompositeHandler

SetApprovalHandler sets the approval handler. Returns self for chaining.

func (*CompositeHandler) SetAskHandler

func (h *CompositeHandler) SetAskHandler(a AskHandler) *CompositeHandler

SetAskHandler sets the ask handler. Returns self for chaining.

func (*CompositeHandler) SetCompletionCallback

func (h *CompositeHandler) SetCompletionCallback(c CompletionCallback) *CompositeHandler

SetCompletionCallback sets the completion callback. Returns self for chaining.

func (*CompositeHandler) SetStreamer

SetStreamer sets the message streamer handler. Returns self for chaining.

func (*CompositeHandler) WithCompletionFunc added in v0.260507.1

func (h *CompositeHandler) WithCompletionFunc(f func(result *CompletionResult)) *CompositeHandler

WithCompletionFunc sets a function to be called on completion. Convenience method that wraps f in a CompletionCallback.

func (*CompositeHandler) WithErrorFunc added in v0.260507.1

func (h *CompositeHandler) WithErrorFunc(f func(err error)) *CompositeHandler

WithErrorFunc sets a function to be called on error. If a streamer is already set, the function is layered on top; otherwise a no-op streamer is created so only the error hook fires.

func (*CompositeHandler) WithMessageFunc added in v0.260507.1

func (h *CompositeHandler) WithMessageFunc(f func(msg interface{}) error) *CompositeHandler

WithMessageFunc sets a function to be called for each message. Convenience method that wraps f in a MessageStreamer.

type Config

type Config struct {
	DefaultAgent            AgentType     `json:"default_agent"`
	DefaultFormat           OutputFormat  `json:"default_format"`
	EnableStreamJSON        bool          `json:"enable_stream_json"`
	StreamBufferSize        int           `json:"stream_buffer_size"`
	DefaultExecutionTimeout time.Duration `json:"default_execution_timeout"`

	// Session configuration
	ClaudeProjectsDir string `json:"claude_projects_dir,omitempty"` // Path to Claude projects directory
}

Config holds the AgentBoot configuration

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default AgentBoot configuration

type ContentBlock

type ContentBlock struct {
	Type string `json:"type"`
	Text string `json:"text,omitempty"`
	// For tool_use
	ToolID   string                 `json:"tool_id,omitempty"`
	ToolName string                 `json:"tool_name,omitempty"`
	Input    map[string]interface{} `json:"input,omitempty"`
}

ContentBlock represents a block of content

type ControlResponse added in v0.260507.1

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

ControlResponse is the sum type passed to [ExecutionHandle.Respond].

The interface is sealed; agentboot owns the closed set of response shapes.

type ErrorEvent added in v0.260507.1

type ErrorEvent struct {
	Err error
}

ErrorEvent reports a non-fatal error noticed during execution. The runner continues processing after emitting an ErrorEvent. Fatal errors are surfaced via [ExecutionHandle.Wait]'s returned error instead.

type Event

type Event = common.Event

Event represents a generic agent event. Alias of common.Event — the two types are identical and interchangeable.

type EventKind added in v0.260507.1

type EventKind int

EventKind is the classification result returned by [AgentTransport.Classify].

const (
	// EventKindIgnore: the event is fully consumed by the transport (e.g.
	// internal-only system pings); the runner does not emit anything.
	EventKindIgnore EventKind = iota

	// EventKindMessage: a streamable agent message. The runner calls
	// [AgentTransport.AccumulateMessage] and emits a [MessageEvent] for each
	// rich message returned.
	EventKindMessage

	// EventKindControl: an interactive control request (permission/ask).
	// The corresponding parsed [StreamEvent] is returned alongside this
	// kind by Classify; the runner emits it on the handle and waits for a
	// response via [ExecutionHandle.Respond].
	EventKindControl

	// EventKindTerminalSuccess: the agent emitted a successful terminal
	// event. The runner records success and stops processing further events.
	EventKindTerminalSuccess

	// EventKindTerminalError: the agent emitted a failed terminal event.
	// The runner records failure and stops processing further events.
	EventKindTerminalError
)

type ExecutionHandle added in v0.260507.1

type ExecutionHandle interface {
	Events() <-chan StreamEvent
	Respond(reqID string, resp ControlResponse) error
	Wait() (*Result, error)
	Cancel()
}

ExecutionHandle is the per-execution result of [Agent.Execute].

Lifecycle:

  1. Caller iterates Events() to consume the totally-ordered event stream.
  2. For ApprovalRequestEvent / AskRequestEvent, caller computes a response and calls Respond(req.ID, response). The runner encodes the response and forwards it to the agent process's stdin.
  3. The channel closes after the underlying process has exited AND every decoded event has been delivered to the channel.
  4. After the channel closes, Wait() returns immediately with the aggregated Result.

Cancellation:

  • Cancel() requests cooperative shutdown; idempotent. The runner kills the process and joins all goroutines; the channel then closes.
  • The Context passed to Execute can also be canceled to the same effect.

Concurrency:

  • Events() may be consumed by exactly one goroutine.
  • Respond / Cancel / Wait are safe to call concurrently from any goroutine.

type ExecutionOptions

type ExecutionOptions struct {
	ProjectPath  string
	OutputFormat OutputFormat
	Timeout      time.Duration
	Env          []string
	// SessionID is the session ID to use or resume
	// If Resume is true, --resume <session_id> is used to continue an existing session
	// If Resume is false, --session-id <session_id> is used to create a new session with specific ID
	SessionID string
	// Resume indicates whether to resume an existing session (true) or create a new one (false)
	Resume bool
	// ChatID is the chat ID for permission requests (used by mock agent)
	ChatID string
	// Platform is the platform for permission requests (used by mock agent)
	Platform string
	// BotUUID is the bot UUID for permission callbacks
	BotUUID string

	// Model selection (per-execution override)
	Model         string
	FallbackModel string

	// Execution control
	MaxTurns int

	// Tool filtering (per-execution override)
	AllowedTools    []string
	DisallowedTools []string

	// MCP servers (per-execution override)
	MCPServers      map[string]interface{}
	StrictMcpConfig bool

	// System prompts (per-execution override)
	CustomSystemPrompt string
	AppendSystemPrompt string

	// Permission mode (per-execution override)
	PermissionMode string

	// Settings path (per-execution override)
	SettingsPath string

	// PermissionPromptTool specifies the tool for permission prompts (e.g., "stdio")
	// When set to "stdio", permission requests are sent via stdin/stdout for callback handling
	PermissionPromptTool string

	// Store, if set, receives session lifecycle events driven by the runner.
	// When non-nil and SessionID is non-empty the runner calls:
	//   SetRunning  — after the process starts successfully
	//   SetFailed   — if the process fails to start or Wait returns an error
	//   SetCompleted — if Wait returns without error
	Store agentsession.Store
}

ExecutionOptions controls agent execution

type HandleControls added in v0.260507.1

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

HandleControls bundles the operations a custom Agent implementation uses to drive an ExecutionHandle it created via NewControlledHandle.

Emit and Close are the inverse of Events() and channel-close that the consumer observes: the producer pushes events with Emit and signals completion with Close.

func (*HandleControls) Close added in v0.260507.1

func (c *HandleControls) Close()

Close closes the handle's Events channel exactly once. After Close, [ExecutionHandle.Respond] returns ErrHandleClosed.

func (*HandleControls) Emit added in v0.260507.1

func (c *HandleControls) Emit(ctx context.Context, ev StreamEvent)

Emit pushes a stream event to the handle's Events channel. It blocks until the consumer reads the event, or until ctx is canceled — in which case the event is dropped and any pending control registration is cleaned up automatically.

type InitMessage

type InitMessage struct {
	BaseMessage
	MaxIterations int `json:"max_iterations,omitempty"`
}

InitMessage represents agent initialization

func NewInitMessage

func NewInitMessage(agentType AgentType, sessionID string, maxIterations int) *InitMessage

NewInitMessage creates a new init message

func (*InitMessage) GetRawData

func (m *InitMessage) GetRawData() map[string]interface{}

GetRawData returns raw data

func (*InitMessage) ToEvent

func (m *InitMessage) ToEvent() Event

ToEvent converts InitMessage to Event

type LaunchSpec added in v0.260507.1

type LaunchSpec struct {
	// Command is the binary and arguments (e.g. ["claude", "--output-format", "stream-json", ...])
	Command []string

	// Env is the process environment. nil inherits the current process environment.
	Env []string

	// WorkDir is the working directory for the agent process. Empty means current directory.
	WorkDir string

	// InitialInput is an optional channel that feeds initial messages into the agent's stdin.
	// Close the channel to signal EOF. May be nil.
	InitialInput <-chan any
}

LaunchSpec describes how to start an agent process. It is pure data: no business logic.

func (*LaunchSpec) BuildCmd added in v0.260507.1

func (s *LaunchSpec) BuildCmd(ctx context.Context) *exec.Cmd

BuildCmd converts a LaunchSpec into an *exec.Cmd with the given context. WorkDir and Env are applied; the caller is responsible for piping stdin/stdout.

type MessageEvent added in v0.260507.1

type MessageEvent struct {
	Raw any
}

MessageEvent wraps a streamable agent message after the per-agent accumulator has consumed the raw common.Event. The concrete type of Raw is agent-specific (e.g. *claude.AssistantMessage, *claude.ToolUseMessage, or agentboot.AgentMessage); consumers type-switch.

In addition to emitting MessageEvents, the runner appends the raw underlying common.Event values to [Result.Events] for callers that prefer the aggregated form returned from [ExecutionHandle.Wait].

type MessageHandler

type MessageHandler interface {
	OnMessage(msg interface{}) error
	OnError(err error)
	OnComplete(result *CompletionResult)
	OnApproval(ctx context.Context, req PermissionRequest) (PermissionResult, error)
	OnAsk(ctx context.Context, req AskRequest) (AskResult, error)
}

MessageHandler is the primary interface for handling agent callbacks This interface is defined here to avoid circular dependencies

type MessageSink added in v0.260507.1

type MessageSink func(any)

MessageSink receives the [MessageEvent.Raw] value of each message event, in order. Pass nil to drop message events (e.g. when only completion matters).

type MessageStreamer

type MessageStreamer interface {
	OnMessage(msg interface{}) error
	OnError(err error)
}

MessageStreamer handles streaming messages (subset of MessageHandler)

type OutputFormat

type OutputFormat string

OutputFormat defines agent output format

const (
	OutputFormatText       OutputFormat = "text"
	OutputFormatStreamJSON OutputFormat = "stream-json"
)

func (OutputFormat) String

func (f OutputFormat) String() string

String returns the string representation of OutputFormat

type PermissionConfig

type PermissionConfig struct {
	DefaultMode       PermissionMode `json:"default_mode"`
	Timeout           time.Duration  `json:"timeout"`
	EnableWhitelist   bool           `json:"enable_whitelist"`
	Whitelist         []string       `json:"whitelist"`
	Blacklist         []string       `json:"blacklist"`
	RememberDecisions bool           `json:"remember_decisions"`
	DecisionDuration  time.Duration  `json:"decision_duration"`
}

PermissionConfig holds permission handler configuration

func DefaultPermissionConfig

func DefaultPermissionConfig() PermissionConfig

DefaultPermissionConfig returns the default permission handler configuration

type PermissionMode

type PermissionMode string

PermissionMode defines how permission requests are handled. Use ask.Mode for the ask-subsystem-specific mode values.

const (
	PermissionModeAuto   PermissionMode = "auto"   // Auto-approve all requests
	PermissionModeManual PermissionMode = "manual" // Require user approval
	PermissionModeSkip   PermissionMode = "skip"   // Skip permission prompts
)

func (PermissionMode) String

func (m PermissionMode) String() string

String returns the string representation of PermissionMode

type PermissionRequest

type PermissionRequest struct {
	RequestID string                 `json:"request_id"`
	AgentType AgentType              `json:"agent_type"`
	ToolName  string                 `json:"tool_name"`
	Input     map[string]interface{} `json:"input"`
	Reason    string                 `json:"reason,omitempty"`
	Timestamp time.Time              `json:"timestamp"`
	SessionID string                 `json:"session_id,omitempty"`
	BotUUID   string                 `json:"bot_uuid,omitempty"` // Bot UUID for routing permission requests
	ChatID    string                 `json:"chat_id,omitempty"`  // Chat ID for routing
	Platform  string                 `json:"platform,omitempty"` // Platform for routing
}

PermissionRequest represents a permission request from an agent

type PermissionRequestMessage

type PermissionRequestMessage struct {
	BaseMessage
	RequestID string                 `json:"request_id"`
	ToolName  string                 `json:"tool_name"`
	Input     map[string]interface{} `json:"input"`
	Reason    string                 `json:"reason,omitempty"`
	Step      int                    `json:"step,omitempty"`
	Total     int                    `json:"total,omitempty"`
}

PermissionRequestMessage represents a permission request

func NewPermissionRequestMessage

func NewPermissionRequestMessage(agentType AgentType, sessionID, requestID, toolName string, input map[string]interface{}, reason string) *PermissionRequestMessage

NewPermissionRequestMessage creates a new permission request message

func (*PermissionRequestMessage) GetRawData

func (m *PermissionRequestMessage) GetRawData() map[string]interface{}

GetRawData returns raw data

func (*PermissionRequestMessage) ToEvent

func (m *PermissionRequestMessage) ToEvent() Event

ToEvent converts PermissionRequestMessage to Event

type PermissionResponse

type PermissionResponse struct {
	RequestID string    `json:"request_id"`
	Approved  bool      `json:"approved"`
	Reason    string    `json:"reason,omitempty"`
	Timestamp time.Time `json:"timestamp"`
}

PermissionResponse represents the response to a permission request

type PermissionResult

type PermissionResult struct {
	Approved     bool                   `json:"approved"`
	Reason       string                 `json:"reason,omitempty"`
	UpdatedInput map[string]interface{} `json:"updated_input,omitempty"`
	Remember     bool                   `json:"remember,omitempty"`
}

PermissionResult represents the result of a permission check

type PermissionResultMessage

type PermissionResultMessage struct {
	BaseMessage
	RequestID string `json:"request_id"`
	Approved  bool   `json:"approved"`
	Reason    string `json:"reason,omitempty"`
	Remember  bool   `json:"remember,omitempty"`
}

PermissionResultMessage represents the result of a permission request

func NewPermissionResultMessage

func NewPermissionResultMessage(agentType AgentType, sessionID, requestID string, approved bool, reason string) *PermissionResultMessage

NewPermissionResultMessage creates a new permission result message

func (*PermissionResultMessage) GetRawData

func (m *PermissionResultMessage) GetRawData() map[string]interface{}

GetRawData returns raw data

func (*PermissionResultMessage) ToEvent

func (m *PermissionResultMessage) ToEvent() Event

ToEvent converts PermissionResultMessage to Event

type Prompter added in v0.260507.1

type Prompter interface {
	OnApproval(ctx context.Context, req PermissionRequest) (PermissionResult, error)
	OnAsk(ctx context.Context, req AskRequest) (AskResult, error)
}

Prompter is the consumer-supplied callback the bot layer (or any caller of RunWithPrompter) provides to satisfy approval and ask requests during agent execution.

Contract:

  • Timeout / cancellation. The caller's ctx carries the deadline. On ctx.Done() the Prompter MUST return promptly with Approved=false (i.e., default-deny). Implementations are free to enforce an additional internal timeout (IMPrompter uses 5min by default) but ctx is authoritative.

  • AlwaysAllow caching. If the user previously approved a tool with "remember", subsequent OnApproval calls for the same tool MUST short-circuit to Approved=true without prompting again. The Prompter owns this cache; executors do not maintain per-tool allowlists.

  • No partial failures. On internal error the Prompter returns the error AND a deny result (Approved=false), so RunWithPrompter always has a safe response to send to the agent.

Implementations: IMPrompter (production) and prompt.FakePrompter (tests).

type Result

type Result struct {
	Output   string // Agent output (text mode)
	ExitCode int    // Process exit code
	Error    string // Error message if failed
	Duration time.Duration
	Format   OutputFormat           // Output format used
	Events   []Event                // Stream events (stream-json mode)
	Metadata map[string]interface{} // Additional metadata
}

Result represents the result of an agent execution

func RunWithPrompter added in v0.260507.1

func RunWithPrompter(ctx context.Context, h ExecutionHandle, prompter Prompter, sink MessageSink) (*Result, error)

RunWithPrompter is the convenience consumer of an ExecutionHandle.

It iterates handle.Events() in order, dispatching:

  • MessageEvent → sink (if non-nil)
  • ApprovalRequestEvent → prompter.OnApproval, then handle.Respond
  • AskRequestEvent → prompter.OnAsk, then handle.Respond
  • ErrorEvent → logged and ignored

Approval/ask invocations are synchronous within the loop (matching the existing IMPrompter blocking semantics — Claude waits for a response before emitting more events, so back-pressure is not a concern).

Once the channel closes, RunWithPrompter calls handle.Wait() and returns its result.

Use this from executor code that does not need event-level visibility. Tests and executors that DO want fine-grained control should iterate handle.Events() directly.

func (*Result) GetAssistantMessages

func (r *Result) GetAssistantMessages() []Event

GetAssistantMessages returns all assistant message events

func (*Result) GetCostUSD

func (r *Result) GetCostUSD() float64

GetCostUSD extracts the total cost from result events if available

func (*Result) GetMessageChain

func (r *Result) GetMessageChain() []Event

GetMessageChain returns all events in order, excluding result/system events

func (*Result) GetMessagesByType

func (r *Result) GetMessagesByType(messageType string) []Event

GetMessagesByType returns all events of a specific type

func (*Result) GetSessionID

func (r *Result) GetSessionID() string

GetSessionID extracts the session ID from metadata or events

func (*Result) GetStatus

func (r *Result) GetStatus() string

GetStatus extracts the final status from events

func (*Result) GetToolResultMessages

func (r *Result) GetToolResultMessages() []Event

GetToolResultMessages returns all tool_result message events

func (*Result) GetToolUseMessages

func (r *Result) GetToolUseMessages() []Event

GetToolUseMessages returns all tool_use message events

func (*Result) GetUserMessages

func (r *Result) GetUserMessages() []Event

GetUserMessages returns all user message events

func (*Result) IsSuccess

func (r *Result) IsSuccess() bool

IsSuccess returns true if the execution was successful

func (*Result) TextOutput

func (r *Result) TextOutput() string

TextOutput returns the full text output from the result

type ResultMessage

type ResultMessage struct {
	BaseMessage
	Status   string  `json:"status"` // "success", "error", "cancelled", "permission_denied"
	Message  string  `json:"message,omitempty"`
	CostUSD  float64 `json:"cost_usd,omitempty"`
	Duration int64   `json:"duration_ms,omitempty"`
	Steps    int     `json:"steps_completed,omitempty"`
	IsError  bool    `json:"is_error,omitempty"`
	ErrorMsg string  `json:"error,omitempty"`
}

ResultMessage represents the final result

func NewResultMessage

func NewResultMessage(agentType AgentType, sessionID, status, message string) *ResultMessage

NewResultMessage creates a new result message

func (*ResultMessage) GetRawData

func (m *ResultMessage) GetRawData() map[string]interface{}

GetRawData returns raw data

func (*ResultMessage) IsSuccess

func (m *ResultMessage) IsSuccess() bool

IsSuccess returns true if result is successful

func (*ResultMessage) ToEvent

func (m *ResultMessage) ToEvent() Event

ToEvent converts ResultMessage to Event

type Runner added in v0.260507.1

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

Runner is a generic agent executor that composes an AgentDriver (process setup) with an AgentTransport (protocol parsing) and a process.Factory (process supervision) to implement the Agent interface.

The default Runner uses process.NewOSExecFactory to spawn real processes. Tests inject process.NewFakeFactory via NewRunnerWithFactory to substitute the binary while exercising the same driver and transport.

func NewRunner added in v0.260507.1

func NewRunner(driver AgentDriver, transport AgentTransport) *Runner

NewRunner creates a Runner backed by process.NewOSExecFactory.

func NewRunnerWithFactory added in v0.260507.1

func NewRunnerWithFactory(driver AgentDriver, transport AgentTransport, factory process.Factory) *Runner

NewRunnerWithFactory creates a Runner with a custom process factory. Use process.NewFakeFactory in tests.

func (*Runner) Execute added in v0.260507.1

func (r *Runner) Execute(ctx context.Context, prompt string, opts ExecutionOptions) (ExecutionHandle, error)

Execute starts the agent and returns an ExecutionHandle for the caller to consume events from and respond to control requests via.

The returned handle's Events channel closes after the underlying process has exited and all decoded events have been delivered. Wait then returns the aggregated Result.

func (*Runner) GetDefaultFormat added in v0.260507.1

func (r *Runner) GetDefaultFormat() OutputFormat

func (*Runner) IsAvailable added in v0.260507.1

func (r *Runner) IsAvailable() bool

func (*Runner) SetDefaultFormat added in v0.260507.1

func (r *Runner) SetDefaultFormat(f OutputFormat)

func (*Runner) Type added in v0.260507.1

func (r *Runner) Type() AgentType

type StreamDeltaMessage

type StreamDeltaMessage struct {
	BaseMessage
	Delta string `json:"delta"`
}

StreamDeltaMessage represents incremental streaming content

func NewStreamDeltaMessage

func NewStreamDeltaMessage(agentType AgentType, sessionID, delta string) *StreamDeltaMessage

NewStreamDeltaMessage creates a new stream delta message

func (*StreamDeltaMessage) GetRawData

func (m *StreamDeltaMessage) GetRawData() map[string]interface{}

GetRawData returns raw data

func (*StreamDeltaMessage) ToEvent

func (m *StreamDeltaMessage) ToEvent() Event

ToEvent converts StreamDeltaMessage to Event

type StreamEvent added in v0.260507.1

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

StreamEvent is the sum type of events flowing on [ExecutionHandle.Events]. Callers type-switch to specific event types.

The interface is sealed (its sentinel method is unexported) so that agentboot owns the closed set of event types that runners may emit.

Directories

Path Synopsis
fixture
Package fixture provides a Claude wire-format scripted process.Factory for testing.
Package fixture provides a Claude wire-format scripted process.Factory for testing.
Package process provides the seam between agentboot and the OS process that implements an agent (e.g.
Package process provides the seam between agentboot and the OS process that implements an agent (e.g.
Package prompt holds test doubles and helpers for the agentboot.Prompter contract.
Package prompt holds test doubles and helpers for the agentboot.Prompter contract.
Package protocol is the pure stream-protocol layer of agentboot.
Package protocol is the pure stream-protocol layer of agentboot.

Jump to

Keyboard shortcuts

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