broker

package
v0.28.2 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package broker provides a harness-agnostic run broker for agent communication. It handles run registration, control message queuing, report/finish/reply ingestion, and heartbeat tracking scoped by run ID.

Harness-specific concerns (e.g., MCP sidecar registration, tmux bootstrap, .mcp.json entries) live in the individual harness adapter packages, not here.

Index

Constants

View Source
const (
	// DefaultAskTimeout is how long a sender waits for a reply before
	// the ask edge is pruned and the waiter unblocks with a timeout.
	DefaultAskTimeout = 10 * time.Minute
)

Variables

This section is empty.

Functions

func EnrichStopReason

func EnrichStopReason(b *Broker, brokerRunID string, stopReason *string)

EnrichStopReason supplements a stop reason with the last finish status from a broker run, when the stop reason is empty and broker state is available. It is safe to call when broker is nil or brokerRunID is empty.

func MakeToken

func MakeToken() string

Types

type AgentMessage

type AgentMessage struct {
	// ID is a stable message UUID for deduplication, cancellation, and
	// ask/reply correlation. Generated by the sender; should be unique
	// per sender.
	ID string `json:"id,omitempty"`

	// From is the display name of the sender run (often same as FromRunID).
	From string `json:"from"`

	// FromRunID is the run ID of the sending agent.
	FromRunID string `json:"from_run_id"`

	// ToRunID is the run ID of the intended recipient.
	ToRunID string `json:"to_run_id,omitempty"`

	// Message is the body text.
	Message string `json:"message"`

	// Role is a display label such as "agent", "reviewer", or "supervisor".
	Role string `json:"role,omitempty"`

	// ReplyTo, when non-empty, marks this message as a reply to the
	// original message with the given ID. The broker uses this to match
	// a waiting ask.
	ReplyTo string `json:"reply_to,omitempty"`

	// ExpectsReply, when true, signals that the sender is blocking and
	// expects a reply with ReplyTo pointing back to this message's ID.
	ExpectsReply bool `json:"expects_reply,omitempty"`
}

AgentMessage is the payload for agent-to-agent channel messages. It is used as the payload of "agent_message" control messages pushed via SendTo.

The ID, ReplyTo, and ExpectsReply fields support blocking ask/reply coordination: the sender sets ExpectsReply=true and a stable ID, the receiver replies by sending a new AgentMessage with ReplyTo set to the original ID, and the broker routes the reply back to the waiting sender via the /wait_reply long-poll endpoint.

type AskEdge added in v0.28.1

type AskEdge struct {
	FromRunID string    `json:"from_run_id"`
	ToRunID   string    `json:"to_run_id"`
	MessageID string    `json:"message_id"`
	CreatedAt time.Time `json:"created_at"`
}

AskEdge tracks a pending ask (blocking request for reply).

type AskReply added in v0.28.1

type AskReply struct {
	MessageID string          `json:"message_id"`
	FromRunID string          `json:"from_run_id"`
	Payload   json.RawMessage `json:"payload,omitempty"`
}

AskReply carries a reply routed to a waiting sender.

type Broker

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

Broker is a harness-agnostic, in-process HTTP server that coordinates communication between agent harnesses and their orchestrators. It owns run-scoped state, message queuing, and lifecycle event ingestion.

func New

func New(globalToken string) *Broker

New creates a broker on an ephemeral loopback port. The resulting addr is available after Start.

func (*Broker) Addr

func (b *Broker) Addr() string

func (*Broker) CreateRun

func (b *Broker) CreateRun(runID string) (string, error)

CreateRun registers a new run and returns its token.

func (*Broker) DeleteRun

func (b *Broker) DeleteRun(runID string)

DeleteRun removes a run from the broker. Safe to call even if the run does not exist.

func (*Broker) DrainAgentMessages added in v0.28.1

func (b *Broker) DrainAgentMessages(runID string) ([]ControlMessage, error)

DrainAgentMessages returns and clears the agent_message control messages queued for runID (asks / sends from peers). Non-agent control messages are left untouched so other consumers still see them.

func (*Broker) EnsureRun

func (b *Broker) EnsureRun(runID string) (string, bool)

EnsureRun registers a new run if one does not already exist for the given runID. Returns the run's token (existing or newly created) and whether the run was newly created.

func (*Broker) FinishCount

func (b *Broker) FinishCount(runID string) int

func (*Broker) GetRun

func (b *Broker) GetRun(runID string) *RunState

GetRun returns the RunState for a given run ID, or nil if not found.

func (*Broker) Ingest

func (b *Broker) Ingest(runID string, kind string, payload json.RawMessage) error

Ingest routes an incoming message from an agent into the appropriate RunState bucket based on kind.

Known kinds:

  • "done", "failed", "blocked" → stored in Finishes
  • everything else → stored in Reports

func (*Broker) LastFinishStatus

func (b *Broker) LastFinishStatus(runID string) string

func (*Broker) PollAgentMessages

func (b *Broker) PollAgentMessages(ctx context.Context, runID string, onMessage func(wrappedContent string))

PollAgentMessages polls the broker for agent_message control messages destined to the given runID every 2 seconds. Each received message is wrapped with channelwrap and passed to onMessage. The goroutine exits when ctx is done.

func (*Broker) PushControl

func (b *Broker) PushControl(runID string, msg ControlMessage) error

PushControl queues a control message for the given run.

func (*Broker) Reset

func (b *Broker) Reset()

Reset clears all run state.

func (*Broker) RunCount

func (b *Broker) RunCount() int

func (*Broker) Send

func (b *Broker) Send(runID string, kind string, payload json.RawMessage, correlationID string) error

Send enqueues a control message for the given run using raw fields. It is a convenience wrapper around PushControl that builds the ControlMessage internally. correlationID is optional; it is used as the ControlMessage ID when non-empty.

func (*Broker) SendTo

func (b *Broker) SendTo(fromRunID, toRunID, kind string, payload json.RawMessage, correlationID string) error

SendTo sends a message from one run to another run's control queue. The fromRunID must belong to a registered run whose token matches the authenticated caller's token.

func (*Broker) Start

func (b *Broker) Start() error

Start binds the broker to an ephemeral loopback port.

func (*Broker) Stop

func (b *Broker) Stop() error

func (*Broker) UpdateSessionInfo added in v0.28.1

func (b *Broker) UpdateSessionInfo(runID string, info *SessionInfo)

UpdateSessionInfo sets or updates the metadata for a registered run.

type ControlMessage

type ControlMessage struct {
	ID        string          `json:"id"`
	Type      string          `json:"type"`
	RunID     string          `json:"run_id"`
	FromRunID string          `json:"from_run_id,omitempty"`
	Payload   json.RawMessage `json:"payload,omitempty"`
	CreatedAt time.Time       `json:"created_at"`
}

ControlMessage is a message pushed from Avenor to the sidecar for delivery to Claude.

func (ControlMessage) ToEnvelope

func (m ControlMessage) ToEnvelope() Envelope

type Envelope

type Envelope struct {
	FromRunID     string          `json:"from_run_id"`
	ToRunID       string          `json:"to_run_id"`
	To            string          `json:"to,omitempty"`
	Kind          string          `json:"kind"`
	CorrelationID string          `json:"correlation_id,omitempty"`
	Payload       json.RawMessage `json:"payload,omitempty"`
	CreatedAt     time.Time       `json:"created_at"`
}

Envelope is the internal generic message format used by the broker. It is not exposed on the wire; HTTP endpoints continue to use the existing Report/Finish/Reply/ControlMessage types.

type Finish

type Finish struct {
	RunID        string          `json:"run_id"`
	Status       string          `json:"status"`
	Summary      string          `json:"summary"`
	FilesChanged []string        `json:"files_changed,omitempty"`
	Payload      json.RawMessage `json:"payload,omitempty"`
}

Finish is the payload from an avenor_finish tool call.

func (Finish) ToEnvelope

func (f Finish) ToEnvelope() Envelope

type PermissionState

type PermissionState struct {
	RequestID string
	ToolName  string
	Desc      string
	Preview   string
	CreatedAt time.Time
}

PermissionState holds a pending permission relay request.

type Recorder

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

Recorder translates events.Event values into broker.Ingest calls so that any backend can mirror its event stream into the broker without an agent-side sidecar process.

Usage:

rec := broker.NewRecorder(brk, runID)
for evt := range provider.Events(ctx, sessionID) {
    rec.Feed(evt)
    subscriberCh <- evt  // keep existing fanout
}

func NewRecorder

func NewRecorder(b *Broker, runID string) *Recorder

NewRecorder creates a Recorder for the given broker and run ID.

func (*Recorder) Feed

func (rec *Recorder) Feed(evt events.Event)

Feed translates an event into a broker.Ingest call. Events not listed below are silently dropped — the Recorder is a filtered mirror, not a complete event log.

session.end             → kind = stop_reason (e.g. "done", "failed") — stored in Finishes
agent.status            → kind = "status" — stored in Reports
agent.message_chunk     → kind = "thinking" — stored in Reports
permission.request      → kind = "permission_requested" — stored in Reports
child.question          → kind = "child_question" — stored in Reports

Ingest errors (run not found, broker not started) are intentionally suppressed — the Recorder is best-effort and must not block or panic the event pump.

type Reply

type Reply struct {
	RunID   string          `json:"run_id"`
	To      string          `json:"to"`
	Payload json.RawMessage `json:"payload,omitempty"`
}

Reply is the payload from an avenor_reply tool call.

func (Reply) ToEnvelope

func (r Reply) ToEnvelope() Envelope

type Report

type Report struct {
	RunID   string          `json:"run_id"`
	State   string          `json:"state"`
	Payload json.RawMessage `json:"payload,omitempty"`
}

Report is the payload from an avenor_report tool call.

func (Report) ToEnvelope

func (r Report) ToEnvelope() Envelope

type RunState

type RunState struct {
	RunID               string
	Token               string
	ControlQueue        []*ControlMessage
	RegisteredAt        time.Time
	LastSeen            time.Time
	Reports             []Report
	Finishes            []Finish
	Replies             []Reply
	PermissionRequests  map[string]*PermissionState
	PermissionDecisions map[string]string // requestID -> "allow" or "deny"
	Mu                  sync.Mutex
	Notify              chan struct{}

	// Ask/reply tracking
	PendingAsks  map[string]*AskEdge      // messageID -> edge (sender is waiting)
	WaitingReply map[string]chan AskReply // messageID -> buffered(1) channel for /wait_reply

	// Session metadata for peer discovery
	Info *SessionInfo
}

RunState holds all per-run broker state.

func (*RunState) Lock

func (st *RunState) Lock()

func (*RunState) Unlock

func (st *RunState) Unlock()

type SessionInfo added in v0.28.1

type SessionInfo struct {
	RunID    string `json:"run_id"`
	Label    string `json:"label,omitempty"`
	Backend  string `json:"backend,omitempty"`
	Model    string `json:"model,omitempty"`
	Dir      string `json:"dir,omitempty"`
	Status   string `json:"status,omitempty"`
	LastSeen int64  `json:"last_seen"`
}

SessionInfo describes a registered run for peer discovery.

Jump to

Keyboard shortcuts

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