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
- Variables
- func AllowPairKey(from, to string) string
- func ContentRef(body string) string
- func NewMessageID() string
- func RequireInReplyTo(inReplyTo string) error
- func Synopsis(body string, maxBytes int) string
- func ValidKind(k Kind) bool
- func Validate(msg Message, maxBodyBytes int) error
- type Kind
- type LifecyclePayload
- type Message
- type Options
- type Party
- type RouteAction
- type RouteDecision
- type RouteInput
- type RoutingPolicy
Constants ¶
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).
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.
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.
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.
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.
const DefaultMaxBodyBytes = 2048
DefaultMaxBodyBytes is the default inline body budget (matches config default).
const DefaultSynopsisBytes = 256
DefaultSynopsisBytes is the max synopsis length stamped into lifecycle payloads.
const ParentSentinel = "parent"
ParentSentinel is the To/From value for the parent principal.
Variables ¶
var ErrInvalidMessage = errors.New("invalid agent message")
ErrInvalidMessage reports a message that fails strict validation.
Functions ¶
func AllowPairKey ¶
AllowPairKey formats a pair for config/docs.
func ContentRef ¶
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 ¶
RequireInReplyTo is a small helper for answer construction.
func Synopsis ¶
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.
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.
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.
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.