agentmsg

package
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

Documentation

Overview

Package agentmsg is the leaf surface for typed agent-to-agent messages.

It defines the fixed message-kind vocabulary, strict validation, and construction helpers. This package may import only the standard library and internal/sdkadapter. It must never import ledger, coordinator, or cli: those layers own serialization into lifecycle payloads and delivery.

A2A mapping (documented only; not implemented):

Message  → A2A Message
Finding  → A2A Artifact
Question → A2A input-required status update
Answer   → A2A Message (reply)
Steer    → A2A Message (task-scoped guidance)
Ask      → A2A Message (parent-routed referral)

Progress/heartbeat is deliberately not an envelope kind; it stays on the existing agent.EventKind stream.

Index

Constants

View Source
const (
	DeclineParentModeUnimplemented = "parent_mode_unimplemented"
	DeclineNoSuchRole              = "no_such_role"
	DeclineNotAllowed              = "not_allowed"
	DeclineQuotaExceeded           = "quota_exceeded"
	DeclineDepthExceeded           = "depth_exceeded"
	DeclineCycle                   = "cycle"
	// DeclineTargetNotRunning reports an ask declined because its target is not
	// live (no same-run task with that role). Its value is unified with the
	// mid-park terminal decline reason (agentmsg.DeclineReasonResponderTerminal):
	// both describe a terminal/unavailable target, so one reason string is
	// load-bearing for callers that must not branch on the distinction. Prefer
	// agentmsg.DeclineReasonTargetTerminal for new code.
	DeclineTargetNotRunning   = DeclineReasonTargetTerminal
	DeclineSpawnQuotaExceeded = "spawn_quota_exceeded"
	DeclineInvalid            = "invalid"
)

Decline reasons are stable tool-result vocabulary (not free-form chat).

View Source
const AskDeclinePrefix = "\x00decline:"

AskDeclinePrefix is the wire-format prefix for a system decline delivered as an ask answer body. A body starting with this prefix is never a peer's real answer: it is a stable, machine-readable decline signal (e.g. the ask target task finalized without answering). The CLI wave depends on this exact constant name and value — do not rename.

View Source
const DeclineReasonParentNonInteractive = "parent_non_interactive"

DeclineReasonParentNonInteractive is the stable decline reason delivered at park time when a run's parent is a non-interactive controller that can never answer child questions. Generic mechanism: any parent that cannot mediate Q&A opts in via the run-creation flag, and the coordinator declines the asker's park immediately instead of letting it burn the full wait_seconds. Appended verbatim to AskDeclinePrefix to form the delivered answer body, so the CLI wait site reports {status:"no_answer", reason:...} with nil error.

View Source
const DeclineReasonResponderTerminal = DeclineReasonTargetTerminal

DeclineReasonResponderTerminal is the stable decline reason reported when the ask's target task reached terminal status without answering. The value is unified with the ask-time route decline reason (agentmsg.DeclineTargetNotRunning) — both describe a terminal target — but the exported name is retained so existing callers compile unchanged. The CLI wave depends on this exact constant name — do not rename.

View Source
const DeclineReasonTargetTerminal = "target_terminal"

DeclineReasonTargetTerminal is the single vocabulary for "the ask's target is terminal / not live": one reason string for the same fact whether it is declined at ask time (target not running) or mid-park (responder reached terminal status without answering). Appended verbatim to AskDeclinePrefix to form the delivered answer body. The CLI wave depends on this exact constant name and value — do not rename.

View Source
const DefaultMaxBodyBytes = 2048

DefaultMaxBodyBytes is the default inline body budget (matches config default).

View Source
const DefaultSynopsisBytes = 256

DefaultSynopsisBytes is the max synopsis length stamped into lifecycle payloads.

View Source
const ParentSentinel = "parent"

ParentSentinel is the To/From value for the parent principal.

Variables

View Source
var ErrInvalidMessage = errors.New("invalid agent message")

ErrInvalidMessage reports a message that fails strict validation.

Functions

func AllowPairKey

func AllowPairKey(from, to string) string

AllowPairKey formats a pair for config/docs.

func ContentRef

func ContentRef(body string) string

ContentRef mints the content-addressed reference for a message body. Empty body yields "".

func NewMessageID

func NewMessageID() string

NewMessageID returns an unguessable durable message ID (msg-<base32>). Same crypto/rand convention as coordinator run IDs; not the process-local attempt counter.

func RequireInReplyTo

func RequireInReplyTo(inReplyTo string) error

RequireInReplyTo is a small helper for answer construction.

func Synopsis

func Synopsis(body string, maxBytes int) string

Synopsis returns a bounded, redacted-safe one-line preview of the body for lifecycle payloads (never the full body). Truncates on a UTF-8 boundary.

func ValidKind

func ValidKind(k Kind) bool

ValidKind reports whether kind is in the fixed vocabulary.

func Validate

func Validate(msg Message, maxBodyBytes int) error

Validate applies strict envelope rules. maxBodyBytes must be positive.

Types

type Kind

type Kind string

Kind is the fixed message-kind vocabulary. Unknown kinds are rejected.

const (
	KindFinding  Kind = "finding"  // child→parent, durable, blackboard
	KindQuestion Kind = "question" // child→parent, blocking (phase 02)
	KindAnswer   Kind = "answer"   // reply to question/ask (phases 03, 04)
	KindSteer    Kind = "steer"    // parent→child (phase 03)
	KindAsk      Kind = "ask"      // peer via parent router (phase 04)
)

type LifecyclePayload

type LifecyclePayload struct {
	MessageID string `json:"message_id"`
	Kind      Kind   `json:"kind"`
	Synopsis  string `json:"synopsis"`
	// ContentRef is the durable body reference when the body was stored.
	// Empty when body was empty.
	ContentRef string `json:"content_ref,omitempty"`
}

LifecyclePayload is the bounded announcement shape for task_message events. Bodies never appear here - only ID + synopsis (and kind for routing).

func NewLifecyclePayload

func NewLifecyclePayload(msg Message) LifecyclePayload

NewLifecyclePayload builds the ID+synopsis announcement for a message.

type Message

type Message struct {
	ID        string    `json:"id"`
	RunID     string    `json:"run_id"`
	Kind      Kind      `json:"kind"`
	From      Party     `json:"from"`
	To        Party     `json:"to"`
	InReplyTo string    `json:"in_reply_to,omitempty"`
	Body      string    `json:"body"`
	Refs      []string  `json:"refs,omitempty"`
	CreatedAt time.Time `json:"created_at"`
	// Interrupt marks a mid-step steer: the receiving task should stop its
	// current step and act on this steer immediately. Only KindSteer may
	// carry it (enforced by Validate).
	Interrupt bool `json:"interrupt,omitempty"`
}

Message is the typed, budgeted, attributable envelope for sparse messaging. Append-only: no mutation or deletion after construction.

func NewMessage

func NewMessage(runID string, kind Kind, from, to Party, body string, refs []string, opts Options) (Message, error)

NewMessage constructs and validates a message. The ID is minted once (crypto/rand + base32, msg- prefix) unless Options.ID is set.

type Options

type Options struct {
	// MaxBodyBytes bounds Body. Zero means DefaultMaxBodyBytes.
	MaxBodyBytes int
	// Now overrides the clock (tests). Nil uses time.Now.
	Now func() time.Time
	// ID overrides minted ID (tests). Empty mints a new durable ID.
	ID string
	// InReplyTo links an answer (or ask reply) to a prior message ID.
	InReplyTo string
	// Interrupt marks the message as a mid-step interrupt steer.
	Interrupt bool
}

Options controls construction-time validation budgets.

type Party

type Party struct {
	TaskID string `json:"task_id,omitempty"`
	Agent  string `json:"agent,omitempty"`
	Role   string `json:"role,omitempty"`
}

Party identifies a message endpoint: a task, an agent role, or the parent.

func (Party) IsParent

func (p Party) IsParent() bool

IsParent reports whether p is the parent sentinel (empty TaskID/Agent/Role with no fields, or Role == ParentSentinel alone).

type RouteAction

type RouteAction string

RouteAction is the policy decision for one ask.

const (
	RouteDeliver RouteAction = "deliver" // live target in same run
	RouteSpawn   RouteAction = "spawn"   // referral-as-spawn (non-blocking only)
	RouteDecline RouteAction = "decline"
)

type RouteDecision

type RouteDecision struct {
	Action RouteAction
	Reason string // set when Action == RouteDecline
}

RouteDecision is the pure policy result.

func RouteAsk

func RouteAsk(policy RoutingPolicy, in RouteInput) RouteDecision

RouteAsk applies referral policy. No I/O, no side effects.

type RouteInput

type RouteInput struct {
	FromRole string
	ToRole   string
	// Blocking is true when the asker waits (wait_seconds > 0).
	Blocking bool
	// TargetRunning is true when a same-run task with ToRole is live.
	TargetRunning bool
	// AsksUsedByTask is asks already posted by the asker task this attempt.
	AsksUsedByTask int
	// ReferralSpawnsUsed is referral-as-spawn count for the run so far.
	ReferralSpawnsUsed int
	// ChainDepth is ancestor hop count for this ask (0 = first hop).
	ChainDepth int
	// Cycle is true when ToRole already appears in the ancestor chain.
	Cycle bool
}

RouteInput is one ask evaluation. Callers resolve live targets and quotas.

type RoutingPolicy

type RoutingPolicy struct {
	Mode                    string   // "policy" | "parent"
	MaxAsksPerTask          int      // default 4
	MaxReferralDepth        int      // default 2
	Allow                   []string // "from->to"; empty = any live pair; non-empty all-malformed list declines, never any-live
	MaxReferralSpawnsPerRun int      // default 4
}

RoutingPolicy is the pure [subagents.messaging.routing] decision input.

Jump to

Keyboard shortcuts

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