Documentation
¶
Overview ¶
Package agentic provides shared types for the SemStreams agentic processing system.
Overview ¶
The agentic package defines the foundational types used by the agentic component family: agentic-loop (orchestration), agentic-model (LLM integration), and agentic-tools (tool execution). These components work together to enable autonomous, tool-using AI agents that can execute complex multi-step tasks.
This package provides:
- Request/Response types for agent communication (AgentRequest, AgentResponse)
- State machine types for loop lifecycle (LoopState, LoopEntity)
- Tool system types (ToolDefinition, ToolCall, ToolResult)
- Immutable trajectory facts/evidence plus transient active-loop helper types
Architecture Context ¶
The agentic system uses a three-component architecture communicating over NATS JetStream:
┌─────────────────┐
│ agentic-loop │ Orchestrates the agent lifecycle
│ (state machine)│ Manages state, routes messages, records observations
└────────┬────────┘
│
┌────┴────┐
│ │
▼ ▼
┌────────┐ ┌────────────┐
│agentic-│ │agentic- │
│model │ │tools │
│(LLM) │ │(execution) │
└────────┘ └────────────┘
All three components share the types defined in this package, ensuring consistent serialization and validation across the system.
Request/Response Types ¶
AgentRequest encapsulates a request to an LLM endpoint:
request := agentic.AgentRequest{
RequestID: "req_001",
LoopID: "loop_123",
Role: "general", // or "architect", "editor"
Model: "gpt-4",
Messages: []agentic.ChatMessage{
{Role: "system", Content: "You are a helpful assistant."},
{Role: "user", Content: "Analyze this code for bugs."},
},
Tools: []agentic.ToolDefinition{
{Name: "read_file", Description: "Read file contents", Parameters: schema},
},
}
AgentResponse captures the LLM's response:
response := agentic.AgentResponse{
RequestID: "req_001",
Status: "complete", // or "tool_call", "error"
Message: agentic.ChatMessage{
Role: "assistant",
Content: "I found 3 potential issues...",
},
TokenUsage: agentic.TokenUsage{
PromptTokens: 150,
CompletionTokens: 200,
},
}
ChatMessage Roles ¶
The ChatMessage type supports four roles following the OpenAI convention:
- "system": System instructions that shape agent behavior
- "user": User input or task descriptions
- "assistant": Agent responses (from the LLM)
- "tool": Results from tool execution
Messages with tool calls use the ToolCalls field instead of Content:
assistantMessage := agentic.ChatMessage{
Role: "assistant",
ToolCalls: []agentic.ToolCall{
{ID: "call_001", Name: "read_file", Arguments: map[string]any{"path": "main.go"}},
},
}
Agent Roles ¶
The agentic system supports three agent roles for different task patterns:
- "general": Standard single-agent execution for most tasks
- "architect": High-level planning and design (used with editor split)
- "editor": Implementation based on architect's plan
The architect/editor split enables complex tasks where planning and execution benefit from separation. The loop orchestrator handles spawning editor loops when an architect completes.
State Machine ¶
LoopState represents the lifecycle of an agentic loop with seven states:
exploring → planning → architecting → executing → reviewing → complete
↘ failed
States are fluid checkpoints, not gates. The loop can move backward (e.g., from executing back to exploring if the agent needs to rethink). Only the terminal states (complete, failed) prevent further transitions.
Create and manage loop entities:
entity := agentic.NewLoopEntity("loop_123", "task_456", "general", "gpt-4")
// State transitions
entity.TransitionTo(agentic.LoopStatePlanning)
entity.TransitionTo(agentic.LoopStateExecuting)
// Iteration tracking (with guard)
if err := entity.IncrementIteration(); err != nil {
// Max iterations reached
}
// Check terminal state
if entity.State.IsTerminal() {
// Loop has finished
}
Tool System ¶
The tool system enables agents to interact with external systems through a well-defined interface.
ToolDefinition describes an available tool:
toolDef := agentic.ToolDefinition{
Name: "read_file",
Description: "Read the contents of a file",
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"path": map[string]any{"type": "string", "description": "File path"},
},
"required": []string{"path"},
},
}
ToolCall represents a request from the LLM to execute a tool:
call := agentic.ToolCall{
ID: "call_001",
Name: "read_file",
Arguments: map[string]any{"path": "/etc/hosts"},
}
ToolResult returns the outcome of tool execution:
result := agentic.ToolResult{
CallID: "call_001",
Content: "127.0.0.1 localhost\n...",
// Or on error:
// Error: "file not found",
}
Tool Validation ¶
The ValidateToolsAllowed function checks tool calls against an allowlist:
allowed := []string{"read_file", "write_file", "list_dir"}
calls := []agentic.ToolCall{{ID: "1", Name: "delete_file"}}
if err := agentic.ValidateToolsAllowed(calls, allowed); err != nil {
// Error: "disallowed tools: delete_file"
}
Trajectory Tracking ¶
Durable trajectory history consists of immutable TrajectoryFactV1 observations. Each fact is bounded and body-free; full prompts, messages, tool arguments/results, URLs, and raw errors belong in content-addressed TrajectoryEvidenceV1 storage.
Construct one observed fact:
fact := agentic.TrajectoryFactV1{
SchemaVersion: agentic.TrajectorySchemaV1,
LoopDigest: agentic.TrajectoryLoopDigest("loop_123"),
AttemptID: "attempt1",
AttemptOrdinal: 1,
Kind: agentic.TrajectoryKindModelCompleted,
CausalPhase: agentic.TrajectoryPhaseModelResult,
ObservedAt: time.Now(),
EvidenceCapture: agentic.TrajectoryEvidenceNone,
}
encoded, err := fact.CanonicalBytes()
One or more terminal facts mean terminal outcomes were observed; they are not seals or completeness proofs. Aggregate Trajectory and TrajectoryStep remain transient execution helpers, not durable/public read authority.
Token Usage ¶
TokenUsage tracks token consumption for cost monitoring and rate limiting:
usage := agentic.TokenUsage{
PromptTokens: 500,
CompletionTokens: 250,
}
total := usage.Total() // 750
Model Configuration ¶
ModelConfig provides default values for LLM parameters:
config := agentic.ModelConfig{
Temperature: 0.7,
MaxTokens: 2048,
}
// Apply defaults (Temperature: 0.2, MaxTokens: 4096)
config = config.WithDefaults()
Validation ¶
All types include Validate() methods for input validation:
request := agentic.AgentRequest{...}
if err := request.Validate(); err != nil {
// Handle validation error
}
Validation rules:
- AgentRequest: requires request_id, at least one message, valid role
- AgentResponse: requires valid status (complete, tool_call, error)
- ChatMessage: requires valid role, either content, reasoning_content, or tool_calls
- ToolDefinition: requires name and parameters
- ToolCall: requires id and name
- ToolResult: requires call_id
- TrajectoryStep: requires valid step_type and timestamp
- LoopEntity: requires id, valid state, positive max_iterations
Integration with NATS ¶
The agentic components communicate via NATS JetStream using these subject patterns:
agent.task.* - Task requests (external → loop) agent.request.* - Model requests (loop → model) agent.response.* - Model responses (model → loop) tool.execute.* - Tool execution (loop → tools) tool.result.* - Tool results (tools → loop) agent.complete.* - Completions (loop → external)
All subjects use the AGENT stream for reliable delivery.
KV Storage ¶
Loop state and observed trajectory facts are persisted to NATS KV buckets:
- AGENT_LOOPS: LoopEntity per loop ID
- AGENT_TRAJECTORIES: TrajectoryFactV1 per immutable attempt key
AGENT_TRAJECTORIES uses history 1 and no TTL. Full evidence is stored through a registered storage.Store and referenced by digest, exact size, content type, and logical storage instance.
Thread Safety ¶
Types in this package are not inherently thread-safe. When used concurrently, external synchronization is required. The agentic-loop component provides thread-safe loop/context managers and private active-loop mechanics that wrap these types.
Error Handling ¶
Validation errors are returned as standard Go errors with descriptive messages. Tool execution errors are captured in ToolResult.Error rather than returned as Go errors, allowing the agent to handle them gracefully.
Limitations ¶
Current limitations of the agentic type system:
- No streaming support (responses are complete documents)
- Tool parameters use map[string]any (no strong typing)
- Fact envelopes are strictly smaller than 8 KiB
- Visible facts are observed coverage, never a complete execution claim
See Also ¶
Related packages:
- processor/agentic-loop: Loop orchestration and state management
- processor/agentic-model: LLM endpoint integration
- processor/agentic-tools: Tool execution framework
Package agentic provides shared types for the agentic components system. This includes loop state management, tool execution interfaces, and trajectory tracking.
Index ¶
- Constants
- Variables
- func AdvertisedToolsFromMetadata(m map[string]any) (tools []string, present bool)
- func AgentLessonEntityID(org, platform, id string) string
- func AgentLessonMessageType() message.Type
- func AgentLessonRecordPrefix(org, platform string) string
- func AuthorizeLineageTriplePredicate(producer, predicate string) error
- func BashStepURLs(toolName string, toolArgs map[string]any) []string
- func CanonicalTrajectoryEvidence(kind TrajectoryKind, body any) (encoded []byte, digest, key string, err error)
- func ChainExecutionEntityID(org, platform, chainID string) string
- func ExtractFetchedURLs(command string) []string
- func FilesystemPolicyFromMetadata(m map[string]any) (policy string, scratch []string)
- func IsApprovalRejected(reason string) bool
- func IsApprovalRequired(reason string) bool
- func IsKnownFilesystemPolicy(policy string) bool
- func IsReadOnlyPolicy(policy string) bool
- func IsUserFacingDecideAction(action string) bool
- func LineageTriplePredicate(roleKey string) (string, error)
- func LoopExecutionEntityID(org, platform, loopID string) string
- func LoopExecutionMessageType() message.Type
- func LoopIDFromExecutionEntityID(entityID string) (string, bool)
- func ModelEndpointEntityID(org, platform, endpointName string) string
- func ModelEndpointMessageType() message.Type
- func OpsDiagnosisEntityID(org, platform, id string) string
- func OpsDiagnosisMessageType() message.Type
- func RegisterPayloads(reg *payloadregistry.Registry) error
- func SortTrajectoryFacts(facts []TrajectoryFactV1)
- func TrajectoryFactKey(loopID, attemptID string) (string, error)
- func TrajectoryFactPrefix(loopID string) string
- func TrajectoryLoopDigest(loopID string) string
- func TryChainExecutionEntityID(org, platform, chainID string) (string, error)
- func TryLoopExecutionEntityID(org, platform, loopID string) (string, error)
- func TryWebObservationEntityID(org, platform, rawURL string) (entityID, canonicalURL string, err error)
- func ValidateToolsAllowed(calls []ToolCall, allowed []string) error
- func WebObservationMessageType() message.Type
- type AgentRequest
- type AgentResponse
- type ApprovalPendingEvent
- type ApprovalResponse
- type Attachment
- type ChatMessage
- type ConstructedContext
- type ContextEvent
- type ContextSource
- type CoordinatorDecision
- type GraphContextSpec
- type LoopCancelledEvent
- type LoopCompletedEvent
- type LoopCreatedEvent
- type LoopEntity
- func (e *LoopEntity) BeginAwaitingApproval(callID, toolName string, arguments map[string]any, reason string, ...) error
- func (e *LoopEntity) IncrementIteration() error
- func (e *LoopEntity) ResolveApproval() error
- func (e *LoopEntity) TransitionTo(newState LoopState) error
- func (e *LoopEntity) Validate() error
- type LoopExecutionEntity
- type LoopFailedEvent
- type LoopState
- type ModelConfig
- type PendingApprovalState
- type ReasoningCarrierKind
- type ReasoningRecord
- type ResponseAction
- type ResponseBlock
- type ResponseFormat
- type TaskMessage
- type TokenUsage
- type ToolCall
- type ToolChoice
- type ToolDefinition
- type ToolEffect
- type ToolErrorKind
- type ToolResult
- type ToolResultHint
- type Trajectory
- type TrajectoryErrorCategory
- type TrajectoryEvidenceCapture
- type TrajectoryEvidenceFailure
- type TrajectoryEvidenceV1
- type TrajectoryFactV1
- type TrajectoryKind
- type TrajectoryObservedTotals
- type TrajectoryPage
- type TrajectoryPhase
- type TrajectoryQueryRequest
- type TrajectorySourceKind
- type TrajectoryStatus
- type TrajectoryStep
- type UserMessage
- type UserResponse
- type UserSignal
Constants ¶
const ( Domain = "agentic" SchemaVersion = "v1" )
Domain and version constants for message type identification.
const ( CategoryTask = "task" CategoryUserMessage = "user_message" CategorySignal = "signal" CategoryUserResponse = "user_response" CategoryRequest = "request" CategoryResponse = "response" CategoryToolCall = "tool_call" CategoryToolResult = "tool_result" CategoryLoopCreated = "loop_created" CategoryLoopCompleted = "loop_completed" CategoryLoopFailed = "loop_failed" CategoryLoopCancelled = "loop_cancelled" CategoryContextEvent = "context_event" CategorySignalMessage = "signal_message" CategoryApprovalPending = "approval_pending" CategoryApprovalResponse = "approval_response" )
Category constants for message types.
const ( ApprovalDecisionApprove = "approve" ApprovalDecisionReject = "reject" ApprovalDecisionModify = "modify" )
Decision values for ApprovalResponse — what the human-in-the-loop approver decided about a tool call gated by approval_required.
const ( OutcomeSuccess = "success" OutcomeFailed = "failed" OutcomeCancelled = "cancelled" OutcomeTruncated = "truncated" )
Outcome values for loop completion events.
const ( StatusComplete = "complete" StatusToolCall = "tool_call" StatusError = "error" StatusLengthTruncated = "length_truncated" )
Response status values from model responses.
const ( FinishReasonStop = "stop" FinishReasonLength = "length" FinishReasonToolCalls = "tool_calls" )
Finish reason values from model responses (OpenAI-compatible).
const ( ContextEventCompactionStarting = "compaction_starting" ContextEventCompactionComplete = "compaction_complete" // ContextEventCompactionRetry is emitted when a length-truncated // response triggers an inline compaction + within-iteration retry // (see beta.21). Carries pre-compaction Utilization and TokensSaved // so operators can see the recovery in the loop's history. ContextEventCompactionRetry = "compaction_retry" )
ContextEvent type values.
const ( RoleArchitect = "architect" RoleEditor = "editor" RoleGeneral = "general" RoleQualifier = "qualifier" RoleDeveloper = "developer" RoleReviewer = "reviewer" )
Role values for agent loops.
const ( // ResponseFormatJSONObject — legacy bare JSON validity (no schema). ResponseFormatJSONObject = "json_object" // ResponseFormatJSONSchema — strict-mode schema-constrained output (current standard). ResponseFormatJSONSchema = "json_schema" )
Response format type values for AgentRequest.ResponseFormat. See ADR-034.
const ( FilesystemPolicyReadOnly = "read_only" FilesystemPolicyWorkspaceWrite = "workspace_write" FilesystemPolicyHostWrite = "host_write" // valid enum; permissive, no v1 enforcement meaning (substrate concern) )
Filesystem policy values. Deliberately match the sandbox-substrate filesystem enum (ADR-052 vocabulary/sandbox: filesystem.read_only / filesystem.workspace_write) so there is ONE filesystem-policy model across the framework and the future substrate. host_write has no v1 meaning here (an environment-level concern the substrate owns).
const ( // DecideActionRespondDirect is the coordinator's answer to the // user. Delivered as a UserResponse of type result carrying the // decision's reason. DecideActionRespondDirect = "respond_direct" // DecideActionAskUser is the coordinator's clarification request. // Delivered as a UserResponse of type prompt carrying the // decision's reason. DecideActionAskUser = "ask_user" )
Reserved decide actions with framework-owned user-facing semantics (ADR-101, gh#1094). Every OTHER decide action is a handoff to a rule chain and is never delivered to a user channel.
The decide tool stays vocabulary-agnostic: its description enumerates no action, products name their own actions in persona prose, and the deployment-level restricted_decide_actions policy may still bar either reserved name (an autonomous deployment bars ask_user).
const ( // MetadataKeyDecideAction carries the resolved (allowlist-canonical // when an allowlist applies) action string. MetadataKeyDecideAction = "action" // MetadataKeyDecideReason carries the coordinator's reason, which is // the user-facing content of a reply decision. MetadataKeyDecideReason = "reason" )
MetadataKeyDecideAction and MetadataKeyDecideReason are the ToolResult.Metadata keys under which the decide executor returns its typed decision to the loop. The loop reads them to populate LoopCompletedEvent.Decision; nothing parses the Content JSON for the same facts (Content stays the canonical payload for read_loop_result).
const ( // TrajectoryEvidenceKeyPrefix makes logical evidence keys content-addressed. TrajectoryEvidenceKeyPrefix = "trajectory-evidence/v1/sha256/" // TrajectoryEvidenceContentType is stamped into backend-neutral references. TrajectoryEvidenceContentType = "application/vnd.semstreams.agentic-trajectory-evidence.v1+json" )
const ( // TrajectoryBucketName is the immutable fact-log KV bucket. TrajectoryBucketName = "AGENT_TRAJECTORIES" // TrajectorySchemaV1 is the wire version for immutable trajectory facts and evidence. TrajectorySchemaV1 = "v1" // TrajectoryFactMaxBytes is the framework-owned upper bound for one fact envelope. TrajectoryFactMaxBytes = 8 * 1024 )
const ( SignalCancel = "cancel" // Stop execution immediately SignalPause = "pause" // Pause at next checkpoint SignalResume = "resume" // Continue paused loop SignalApprove = "approve" // Approve pending result SignalReject = "reject" // Reject with optional reason SignalFeedback = "feedback" // Add feedback without decision SignalRetry = "retry" // Retry failed loop )
Signal type constants for user control signals
const ( ResponseTypeText = "text" // Plain text response ResponseTypeStatus = "status" // Status update ResponseTypeResult = "result" // Final result ResponseTypeError = "error" // Error message ResponseTypePrompt = "prompt" // Awaiting user input (approval, etc.) ResponseTypeStream = "stream" // Streaming partial content )
Response type constants
const ApprovalRejectedPrefix = "approval_rejected: "
ApprovalRejectedPrefix is prepended to rejection reasons synthesised by the loop after an ApprovalResponse with Decision == reject. The distinct prefix prevents the loop from re-triggering the awaiting-approval branch when the rejection result flows through HandleToolResult.
const ApprovalRequiredPrefix = "approval_required: "
ApprovalRequiredPrefix is prepended to rejection reasons when a tool requires human approval. The agentic-loop detects this prefix and transitions the loop to LoopStateAwaitingApproval instead of storing a normal error result.
const CategoryAgentLesson = "agent_lesson"
CategoryAgentLesson is the message category for the agent-lesson-record entity origin contract (ADR-080). It names the ENTITY type born when the ops agent's emit_lesson tool distils a reusable lesson into the graph — distinct from any event payload. Mirrors CategoryOpsDiagnosis / CategoryLoopExecution / CategoryModelEndpoint.
Lessons unify with the rest of agent memory under the agent.* entity-ID domain; diagnosis stays ops.* (it is an observability artifact, not memory). See ADR-080 decision 1.
const ( // CategoryLoopExecution is the message category for the loop-execution // entity origin contract (ADR-056 W0 4c-pre-1). Distinct from // CategoryLoopCreated (the event payload) — this category names the // entity type, not the event. CategoryLoopExecution = "loop_execution" )
const CategoryModelEndpoint = "model_endpoint"
CategoryModelEndpoint is the message category for the model-endpoint entity origin contract. It names the ENTITY type born when the agentic loop registers a model registry endpoint in the graph — distinct from any event payload. Mirrors CategoryLoopExecution.
const CategoryOpsDiagnosis = "ops_diagnosis"
CategoryOpsDiagnosis is the message category for the ops-diagnosis-finding entity origin contract. It names the ENTITY type born when the ops agent's emit_diagnosis tool records a finding in the graph — distinct from any event payload. Mirrors CategoryLoopExecution / CategoryModelEndpoint.
const CategoryWebObservation = "web_observation"
CategoryWebObservation identifies graph entities that represent one canonical URL observed by agent tools.
const DecideToolName = "decide"
DecideToolName is the name agents use to invoke the coordinator's terminal decision tool. It lives here — beside the decide metadata contract and the reply vocabulary — because agentic-tools (the executor), agentic-loop (the terminal observer), and any future reader must all spell the same name; before gh#1094 the literal was spelled in three places.
const LineageTripleNamespace = "agent.lineage"
LineageTripleNamespace is the fixed framework-owned namespace for cross-arc loop-ID lineage triples. Each entry in TaskMessage.Metadata[MetadataKeyRelatedLoops] becomes a triple of the form:
subject: <spawned loop entity ID> predicate: agent.lineage.<role-key> // e.g. agent.lineage.research-reviewer object: <upstream loop ID string>
Downstream rules that fire on the spawned entity read these via the existing $entity.triple.<predicate> substitution, e.g. $entity.triple.agent.lineage.researcher resolves to the upstream loop ID without any new substitution-token, tool, or persona-driven echo forwarding.
Stable namespace: ops-agent (ADR-027) and the operating-curve observability primitives (ADR-033) aggregate cross-arc lineage by scanning predicates with this prefix. Codifying as a public constant keeps producers and consumers aligned without string-literal drift.
const LineageTripleProducer = "agentic-loop-lineage"
LineageTripleProducer is the stable trusted producer identity granted the exact agent.lineage namespace. It names the framework integration boundary, not Triple.Source or caller-controlled task metadata.
const MetadataKeyAdvertisedTools = "agent.tools.advertised"
MetadataKeyAdvertisedTools is the ToolCall.Metadata key under which agentic-loop dispatch stamps the loop's ADVERTISED tool set — the names of the tool definitions cached for the loop at spawn (per-task task.Tools, or global discovery when unset) — so the tool executor can enforce advertise-and-enforce per loop (gh#551): a model that emits a tool outside its advertised set is rejected at execution even when the tool is in the executor's global AllowedTools. Defense-in-depth: providers normally only emit advertised tools, but one executor multiplexing many roles (semdev's coordinator/developer/reviewer) must not execute a call the loop never advertised.
[]string when stamped; comes back from BaseMessage decode as []any (each element still a Go string) — readers coerce via AdvertisedToolsFromMetadata. Absent == loop has no cached tool set; executor applies only the global allowlist (back-compat). Present-but-empty/malformed == executor fails closed (rejects), per the IsKnownFilesystemPolicy precedent: an unrecognized value on a security control must not degrade to permissive.
Deliberately NOT a member of DispatchEnforcedMetadataKeys: that set's contract is "stamped from the loop's cached TASK metadata" (LoopManager.GetCachedMetadata), while this key is stamped from the loop's tools CACHE (LoopManager.GetCachedTools) — like the MetadataKeyRunID stamp, it is a framework fact dispatch derives itself, authoritatively (OVERWRITE), at the same shared dispatchToolCall seam (ADR-067).
const MetadataKeyAgentRole = "agent.role"
MetadataKeyAgentRole is the ToolCall.Metadata key under which agentic-loop dispatch stamps the emitting loop's role (LoopEntity.Role). A tool executor reads it to attribute a role to its output WITHOUT the model supplying (and therefore being able to spoof) an identity parameter — e.g. emit_lesson derives agent.lesson.observed-role from it (ADR-080: "attribution is derived, not supplied").
Stamped authoritatively (overwrite) when the loop has a role, and DELETED when the role is empty, exactly like the run anchor: the role is a framework fact derived from the loop entity, not a caller-routable hint, so a caller/model-injected value must never survive. Absent for a roleless loop.
const MetadataKeyDecideActionAllowlist = "agent.decide.action_allowlist"
MetadataKeyDecideActionAllowlist is the TaskMessage.Metadata / ToolCall.Metadata key under which a closed action vocabulary for the decide tool flows from the spawning rule down to the executor.
When set, the decide executor validates its `action` argument against the contained []string and rejects non-members with ToolErrorInvalidArgs (the message names the valid set so the LLM can correct on retry).
Empty/missing leaves decide free-form (back-compat).
Set by rule.executePublishAgent from rule.Action.ActionAllowlist. Belt-and-suspenders for persona prose: the persona enumerates the vocabulary in the LLM's system prompt; this allowlist enforces it structurally on the wire.
const MetadataKeyFilesystemPolicy = "agent.exec.filesystem_policy"
MetadataKeyFilesystemPolicy is the TaskMessage.Metadata / ToolCall.Metadata key under which a task's filesystem write-scope flows to write-capable tool executors (v1: bash). Values are the sandbox-substrate filesystem enum (ADR-052 vocabulary): FilesystemPolicyReadOnly | FilesystemPolicyWorkspaceWrite. Absent/empty == workspace_write (unchanged, back-compat).
Under read_only the bash executor enforces a pre+post git worktree-and-HEAD non-mutation proof and returns a typed violation on mutation. The value is model-uncontrollable: stamped by dispatchToolCall as an authoritative overwrite (a framework enforcement fact), never read from tool Arguments.
Set by the product's role→policy mapping when spawning an inspect-role loop (role→policy is product-layer; the framework owns the enforcement floor). Per-task, NOT inherited by spawned sub-loops — a product must re-assert it on each spawned inspect child (see ADR-067 §5).
const MetadataKeyHasMore = "has_more"
MetadataKeyHasMore is the ToolResult.Metadata key set to a bool true when more pages of results remain after the current call. Always set when an executor opts into pagination (ToolDefinition.Paginated=true); absence on a paginated tool's result is a contract violation worth a Warn log. Mutually paired with either MetadataKeyNextOffset (for byte/index-based paging) or MetadataKeyNextCursor (for opaque-keyset paging) — never both.
The agent loop reads this in buildToolMessages and appends a canonical continuation hint to the model's next message so the model knows it can call the same tool again with the supplied continuation token, instead of having to re-narrow blind.
Names are unprefixed because they're the canonical wire shape read_loop_result has already shipped under (semspec is already integrated against these strings). Lifting the existing names into constants is the contract semspec asked for — promotion, not rename.
const MetadataKeyNextCursor = "next_cursor"
MetadataKeyNextCursor is the ToolResult.Metadata key carrying an opaque server-format-controlled cursor token to pass back on the next call. Use for keyset-paginated result SETS where there is no natural byte offset (graph_search-style result iteration). The agent must never inspect or modify the cursor — server owns the format, so the backend can change encoding without breaking in-flight pagination. Mutually exclusive with NextOffset.
const MetadataKeyNextOffset = "next_offset"
MetadataKeyNextOffset is the ToolResult.Metadata key carrying the byte/index offset to pass back on the next call to continue paging. Use for offset-stable result sources where bytes-from-the-start is meaningful (read_loop_result's byte-paging of a single string is the canonical example). Mutually exclusive with NextCursor.
const MetadataKeyRelatedLoops = "agent.related_loops"
MetadataKeyRelatedLoops is the TaskMessage.Metadata / ToolCall.Metadata key under which cross-arc loop-ID lineage flows from a spawning rule down to the spawned loop and into its tool calls. The value is a map[string]string whose keys are exact static lower-kebab predicate segments (maximum 64 bytes) and whose values are related loop IDs. Keys are neither substituted nor normalized.
Use case: a downstream role needs to read_loop_result against an upstream loop without the loop ID being baked into the spawn prompt. Architect needing the researcher's loop ID for harness selection (semteams smoke #8 run-2 wedge cause); challenger cross-grounding back to planner; ops-agent / ADR-033 chain_id stability.
String-to-string only by design. Non-string values are out of scope: if a future case needs structured data, it earns a dedicated typed field (the Tools / ToolChoice / Timeout precedent), not a generalized escape hatch through this map.
Empty/missing leaves no lineage threaded (back-compat).
Set by rule.executePublishAgent from rule.Action.RelatedLoops. JSON round-trip note: like ActionAllowlist, the value comes back from BaseMessage decode as map[string]any (with each value still a Go string) — readers coerce on access.
const MetadataKeyRunEntityID = "agent.run_entity_id"
MetadataKeyRunEntityID is the ToolCall.Metadata key carrying the resolved 6-part chain execution entity ID (org.platform.agent.chain.execution.<runID>) for the loop's run anchor. Mirrors LoopCreatedEvent.RunEntityID / LoopCompletedEvent. RunEntityID so a tool executor and an event subscriber resolve the same run/chain entity.
Stamped alongside MetadataKeyRunID by agentic-loop dispatch when the loop belongs to a run AND the handler has a valid platform identity (org+platform). Absent when RunID is empty or the platform identity is missing; a consumer that needs the entity ID under a missing platform can reconstruct it from MetadataKeyRunID plus its own org/platform via agentic.ChainExecutionEntityID.
const MetadataKeyRunID = "agent.run_id"
MetadataKeyRunID is the ToolCall.Metadata key under which agentic-loop dispatch stamps the loop's run anchor — the bare run loop-id (ADR-053 D7/D8) — when the loop belongs to a run. A tool executor reads it to obtain the run/chain identity directly, instead of re-deriving it by walking agent.loop.parent ancestry triples from its LoopID back to the chain root over graph.query.entity (the hand-rolled ancestry resolver the semteams product shell carried for ADR-053 Phase 5, issue #250).
Empty/absent when the loop is not part of a run — a standalone loop stamps neither this nor MetadataKeyRunEntityID (back-compat). Paired with MetadataKeyRunEntityID, which carries the resolved 6-part chain execution entity ID for the same run.
Stamped authoritatively (overwrite), unlike the loop_id soft-fallback: the run anchor is a framework fact derived from the loop's typed RunID (set via LoopManager.SetRunID at loop creation), and there is no legitimate caller-override use case, so dispatch always supplies it.
const MetadataKeyScratchPaths = "agent.exec.scratch_paths"
MetadataKeyScratchPaths is the TaskMessage.Metadata / ToolCall.Metadata key under which IN-WORKTREE paths exempt from the read_only proof flow (e.g. a declared ".scratch/" build dir). Out-of-worktree paths (the common case: /tmp) are exempt automatically — git in the worktree never reports them, so probes there are invisible to the proof by construction.
[]string. Comes back from BaseMessage decode as []any (each element still a Go string); readers coerce on access, like MetadataKeyDecideActionAllowlist. Empty == only out-of-worktree paths are writable.
const MetadataKeyTotalBytes = "total_bytes"
MetadataKeyTotalBytes is the OPTIONAL ToolResult.Metadata key carrying the total result count, when the executor can compute it cheaply (no expensive secondary round-trip). Useful for UIs that want progress bars; not load-bearing for the agent. Absent when the executor can't compute it without extra work.
Named `total_bytes` rather than `total_available` to match the existing read_loop_result wire — promotion of the shipped shape, not rename. Executors with non-byte units can set their own unit-specific key and document it; this one is reserved for byte-paging consistency.
Variables ¶
var DispatchEnforcedMetadataKeys = []string{ MetadataKeyFilesystemPolicy, MetadataKeyScratchPaths, MetadataKeyDecideActionAllowlist, }
DispatchEnforcedMetadataKeys is the set of task-scoped enforcement keys that dispatchToolCall stamps AUTHORITATIVELY (overwrite) from the loop's cached task metadata onto every tool call — so they reach every dispatch path (main, approval re-dispatch, queue dequeue), not just the main-path merge. These are framework enforcement facts; a fill-only merge would let a stray pre-existing value defeat the control. Absent keys stay absent (back-compat).
MetadataKeyDecideActionAllowlist is included because it had the identical approval-redispatch gap (an approved decide call silently lost its closed vocabulary) — fixed at the same seam (ADR-067 §2).
var ErrMaxIterationsReached = errors.New("max iterations reached")
ErrMaxIterationsReached is the typed sentinel LoopEntity.IncrementIteration returns when the loop's iteration counter has already met or exceeded its configured budget (gh#529). Callers MUST branch on errors.Is against this sentinel rather than treating any non-nil IncrementIteration error as budget exhaustion — processor/agentic-loop.LoopManager.IncrementIteration can also fail with an unrelated "loop not found" error, which is a distinct operational failure and must not be misreported as max_iterations. processor/agentic-loop.ErrMaxIterationsReached aliases this value (the import direction only works agentic → agenticloop) so both packages compare against the exact same sentinel.
var ErrToolNotFound = errors.New("tool not found")
ErrToolNotFound is the sentinel returned by tool registries when a requested tool name has no executor. Callers use errors.Is to detect the miss without parsing error strings — the previous string-match fallback in agentic-tools/component.go was the source of repeated extension friction. Lives here (alongside ToolCall/ToolResult) rather than in agentic-tools so consumers in other processor packages can check it without importing the executor package.
Functions ¶
func AdvertisedToolsFromMetadata ¶
AdvertisedToolsFromMetadata resolves the per-loop advertised tool set from a tool call's metadata. present reports whether the key exists at all — callers MUST branch on it, because key-absent (no per-loop restriction, back-compat) and key-present-but-empty/malformed (fail closed) have opposite enforcement outcomes. tools is the coerced name list: JSON decode lands it as []any (string elements); a native []string (in-process path) is also accepted; non-string and empty elements are dropped; any other value type yields (nil, true) so the executor rejects rather than silently allowing.
func AgentLessonEntityID ¶
AgentLessonEntityID returns the canonical entity ID for an agent lesson record. The id argument is the content-derived unique identifier (a UUIDv5 without dots, generated by the emit_lesson tool from the lesson's category + scope + summary + evidence — see emit_lesson.go).
Format: {org}.{platform}.agent.lesson.record.{id}
Example: AgentLessonEntityID("acme", "ops", "2c5acb9b-8283-5b34-a4d1-4b1c9f8502ca") Returns: "acme.ops.agent.lesson.record.2c5acb9b-8283-5b34-a4d1-4b1c9f8502ca"
This is a 6-part entity ID (org.platform / agent / lesson / record / id); the domain+system axes (agent.lesson) align the entity's identity with its predicate family (agent.lesson.*), exactly as ops.diagnosis.finding aligns with ops.diagnosis.*.
Panics if any input part is empty or contains a dot, as these represent programming errors — the caller is responsible for supplying well-formed identifiers. The id must be a UUID or equivalent unique token with no dots.
func AgentLessonMessageType ¶
AgentLessonMessageType returns the message.Type for the agent-lesson-record entity origin contract — key "agentic.agent_lesson.v1".
Registry decision (mirrors OpsDiagnosisMessageType / LoopExecutionMessageType, ADR-056 typed-origin): MUTATION-ONLY. Stamped on CreateEntityRequest.Entity.MessageType when EmitLessonExecutor births a lesson entity; NEVER published as a BaseMessage payload, NOT registered in the payload registry, never decoded. Each emit_lesson call mints a content-derived agent.lesson.record.{uuid5} entity that MUST be created with this envelope; append is must-exist and rejects an absent lesson entity.
func AgentLessonRecordPrefix ¶
AgentLessonRecordPrefix returns the 5-part entity-ID prefix shared by every lesson record for an org/platform: "{org}.{platform}.agent.lesson.record".
It is the query prefix a reader passes to graph.ingest.query.prefix to list all lesson entities (the brief-assembly LessonReader uses it). Keeping the "agent.lesson.record" segment string here — beside AgentLessonEntityID — prevents it drifting from the entity-ID format. Panics on empty/dotted parts, as those are programming errors.
func AuthorizeLineageTriplePredicate ¶
AuthorizeLineageTriplePredicate applies the fixed lineage namespace policy for a producer supplied by a trusted framework boundary.
func BashStepURLs ¶
BashStepURLs returns the external URLs a bash trajectory step fetched, or nil for any other tool or when no fetch URL is present. toolArgs is the step's ToolArguments map; the bash command lives under the "command" key.
func CanonicalTrajectoryEvidence ¶
func CanonicalTrajectoryEvidence(kind TrajectoryKind, body any) (encoded []byte, digest, key string, err error)
CanonicalTrajectoryEvidence deterministically encodes and addresses a full body.
func ChainExecutionEntityID ¶
ChainExecutionEntityID constructs a 6-part entity ID for a cross-arc agent chain. Format: {org}.{platform}.agent.chain.execution.{chainID}
Example: ChainExecutionEntityID("c360", "ops", "abc123") Returns: "c360.ops.agent.chain.execution.abc123"
A chain entity is the canonical anchor for cross-arc data flow: rules and product subscribers stamp milestone triples (chain.dispatched_at, chain.research_artifact_loop, chain.spec_artifact_loop, chain.paused.*, chain.decision.*, ...) on this entity. The chain_id is the dispatch loop's UUID — no new ID generation required at chain start. See semteams ADR-038 for the chain-anchor pattern. `chain` is a sibling component to `agentic-loop` within the `agent` domain.
Panics if any input part is empty or contains a dot, as these represent programming errors — the caller is responsible for supplying well-formed identifiers.
Runtime callers where a panic would silently crash a goroutine (event- construction in publish goroutines, tool executors) should use TryChainExecutionEntityID instead and surface the error explicitly.
func ExtractFetchedURLs ¶
ExtractFetchedURLs derives the external URLs a bash command fetches. It inspects only sub-commands whose leading program is a known fetch verb (curl/wget/httpie), so URLs that appear as data — echo strings, grep patterns, filesystem paths — are excluded. Returns nil when no fetch URL is detected (no false-positive noise); results are order-preserving and de-duplicated. This is a cheap observability heuristic, not a shell parser: it inspects only the leading token, so wrapped/prefixed fetches — `sudo curl ...`, `time curl ...`, `HTTPS_PROXY=x curl ...`, `echo url | curl -` — are known misses (under-count, never over-count).
func FilesystemPolicyFromMetadata ¶
FilesystemPolicyFromMetadata resolves the read-only execution policy from a tool call's metadata. It returns the policy string (FilesystemPolicyReadOnly or "" for the default workspace_write) and the in-worktree scratch-path exemptions. Nil/absent metadata yields ("", nil) — the back-compat default.
scratch values arrive from BaseMessage JSON decode as []any (each element a Go string); this coerces them, dropping non-string / empty entries.
func IsApprovalRejected ¶
IsApprovalRejected reports whether a rejection reason originates from a denied ApprovalResponse. Distinct from IsApprovalRequired so the loop can flow these through normally without re-entering the awaiting-approval branch.
func IsApprovalRequired ¶
IsApprovalRequired reports whether a rejection reason indicates the tool needs human approval rather than being a genuine error.
func IsKnownFilesystemPolicy ¶
IsKnownFilesystemPolicy reports whether policy is a recognized filesystem enum value. An UNRECOGNIZED non-empty value (a product typo like "readonly") must NOT silently degrade to permissive — for a security control that is a fail-open. Callers fail closed (refuse) + log on an unknown value; the empty string is the back-compat default (workspace_write) and IS known.
func IsReadOnlyPolicy ¶
IsReadOnlyPolicy reports whether a resolved policy string means read_only.
func IsUserFacingDecideAction ¶
IsUserFacingDecideAction reports whether a decide action is one of the reserved reply actions — the ONE classifier of the reply vocabulary (ADR-101 D1). Comparison is exact: no case folding, no separator coercion, no trimming (owner item 7). Any other action, including the empty string, is a handoff.
func LineageTriplePredicate ¶
LineageTriplePredicate returns the canonical predicate for a RelatedLoops role key. The key is one static lower-kebab predicate segment; validating the complete candidate through vocabulary.ParsePredicate keeps this narrow delegation from becoming unchecked authority to mint arbitrary agent predicates.
Centralising construction keeps producers (rule.executePublishAgent / agentic-loop loop-creation path) and consumers (rule authors using $entity.triple.agent.lineage.<key>, ops-agent aggregations) cannot drift on the format.
func LoopExecutionEntityID ¶
LoopExecutionEntityID constructs a 6-part entity ID for an agentic loop execution. Format: {org}.{platform}.agent.agentic-loop.execution.{loopID}
Example: LoopExecutionEntityID("c360", "ops", "abc123") Returns: "c360.ops.agent.agentic-loop.execution.abc123"
Panics if any input part is empty or contains a dot. Suitable for boot-time and post-completion paths where invalid input represents a programming error that should fail loud (operator config check at startup; framework bug in event-payload construction).
Runtime tool executors (where a panic silently kills the dispatch goroutine and the agent fails opaquely) should use TryLoopExecutionEntityID and surface the error as ToolErrorInternal instead. ADR-036 Stage 3.8 documents the panic-class concern; the beta.36 read_loop_result wedge is the in-tree precedent.
func LoopExecutionMessageType ¶
LoopExecutionMessageType returns the message.Type for the loop-execution entity origin contract — key "agentic.loop_execution.v1" (snake_case category, matching the agentic convention: tool_call, loop_created, approval_pending).
Registry decision (ADR-056, intentionally pinned — not implicit): this type is MUTATION-ONLY. It is stamped on CreateEntityRequest.Entity.MessageType at birth purely as producer identity; it is NEVER published as a BaseMessage payload and is therefore NOT registered in the payload registry (payload_registry.go) and never round-trips through payload decoding. It is an envelope-bearing graph-origin marker, not a wire payload. If a future producer needs to PUBLISH a loop_execution message over NATS (not merely stamp it at create), that producer must add the init() registration at that point — until then, registering it would advertise a decode path that does not exist.
func LoopIDFromExecutionEntityID ¶
LoopIDFromExecutionEntityID extracts the loop_id segment from a 6-part entity ID matching the LoopExecutionEntityID shape: {org}.{platform}.agent.agentic-loop.execution.{loopID}
Returns ("", false) when the input is not a valid 6-part entity ID, or when it doesn't match the agent.agentic-loop.execution.* shape (e.g. model-registry endpoints, trajectory steps, chain entities, or non-agent entity IDs). Used by the rule engine's publish_agent action to set task.ParentLoopID when a rule fires on a loop-execution-shaped trigger entity, so rule-fanned chains carry their parent linkage natively (semteams ADR-038 §D2).
Pure parser: no validation of the loop_id's content beyond IsValidEntityID's alphanumeric/hyphen/underscore guard.
func ModelEndpointEntityID ¶
ModelEndpointEntityID constructs a 6-part entity ID for a model registry endpoint. Format: {org}.{platform}.agent.model-registry.endpoint.{endpointName}
Example: ModelEndpointEntityID("c360", "ops", "claude-sonnet") Returns: "c360.ops.agent.model-registry.endpoint.claude-sonnet"
Panics if any input part is empty or contains a dot, as these represent programming errors — the caller is responsible for supplying well-formed identifiers.
func ModelEndpointMessageType ¶
ModelEndpointMessageType returns the message.Type for the model-endpoint entity origin contract — key "agentic.model_endpoint.v1".
Registry decision (mirrors LoopExecutionMessageType, ADR-056 typed-origin — intentionally pinned, not implicit): this type is MUTATION-ONLY. It is stamped on CreateEntityRequest.Entity.MessageType when WriteModelEndpoints births the endpoint entity, purely as producer identity; it is NEVER published as a BaseMessage payload and is therefore NOT registered in the payload registry (payload_registry.go) and never round-trips through payload decoding. A model endpoint is a config-derived FACT about the world, born once at startup via entity.create — not a wire message. If a future producer needs to PUBLISH a model_endpoint message over NATS (not merely stamp it at create), that producer must add the init() registration at that point — until then, registering it would advertise a decode path that does not exist.
func OpsDiagnosisEntityID ¶
OpsDiagnosisEntityID returns the canonical entity ID for an ops diagnosis finding. The id argument is a unique per-finding identifier (e.g., a UUID without dots generated by the emit_diagnosis tool).
Format: {org}.{platform}.ops.diagnosis.finding.{id}
Example: OpsDiagnosisEntityID("acme", "ops", "550e8400-e29b-41d4-a716-446655440000") Returns: "acme.ops.ops.diagnosis.finding.550e8400-e29b-41d4-a716-446655440000"
Panics if any input part is empty or contains a dot, as these represent programming errors — the caller is responsible for supplying well-formed identifiers. The id must be a UUID or equivalent unique token with no dots.
func OpsDiagnosisMessageType ¶
OpsDiagnosisMessageType returns the message.Type for the ops-diagnosis-finding entity origin contract — key "agentic.ops_diagnosis.v1".
Registry decision (mirrors LoopExecutionMessageType, ADR-056 typed-origin): MUTATION-ONLY. Stamped on CreateEntityRequest.Entity.MessageType when EmitDiagnosisExecutor births a finding entity; NEVER published as a BaseMessage payload, NOT registered in the payload registry, never decoded. Each emit_diagnosis call mints a fresh ops.diagnosis.finding.{uuid} entity that MUST be created with this envelope; append is must-exist and rejects an absent finding entity.
func RegisterPayloads ¶
func RegisterPayloads(reg *payloadregistry.Registry) error
RegisterPayloads registers all agentic payload types with the supplied registry. Called from payloadbuiltins.Register during process bootstrap. Returns aggregated errors via errors.Join so misconfigured deployments see every collision on a single boot.
Builders are intentionally omitted — the PayloadRegistry's JSON fallback (Factory + json.Unmarshal) handles payload construction for workflow variable interpolation without requiring duplicate field-mapping code.
func SortTrajectoryFacts ¶
func SortTrajectoryFacts(facts []TrajectoryFactV1)
SortTrajectoryFacts orders visible observations by their causal display tuple.
func TrajectoryFactKey ¶
TrajectoryFactKey builds a bounded NATS-safe immutable fact key.
func TrajectoryFactPrefix ¶
TrajectoryFactPrefix returns the native filtered-list prefix for one loop.
func TrajectoryLoopDigest ¶
TrajectoryLoopDigest returns the lowercase SHA-256 digest used inside facts.
func TryChainExecutionEntityID ¶
TryChainExecutionEntityID is the error-returning variant of ChainExecutionEntityID. Use this from runtime hot paths (event- construction in publish goroutines, milestone subscribers) where a panic would silently crash the goroutine. Boot-time and post-completion callers that own their inputs can keep using the panicking form.
Returns ("", error) when any input part is empty or contains a dot, or when the constructed ID fails IsValidEntityID.
func TryLoopExecutionEntityID ¶
TryLoopExecutionEntityID is the error-returning variant of LoopExecutionEntityID. Use this from runtime hot paths (tool executors, per-iteration prompt assembly) where a panic would silently crash the dispatch goroutine and the agent would fail opaquely. Boot-time and post-completion callers can keep using the panicking LoopExecutionEntityID.
Returns ("", error) when any input part is empty or contains a dot, or when the constructed ID fails IsValidEntityID.
func TryWebObservationEntityID ¶
func TryWebObservationEntityID(org, platform, rawURL string) (entityID, canonicalURL string, err error)
TryWebObservationEntityID returns the canonical 6-part entity ID for a URL observed by an agent (web_search) or fetched by an agent (http_request), along with the canonical URL the entity represents. Same URL across loops → same entity ID, so observations naturally dedup: rule queries against agent.web.observation entities see one vertex per URL with whatever predicates the system has so far accumulated.
Format: {org}.{platform}.agent.web.observation.{sha256-hex-16}
Returns ("", "", error) when org/platform are empty or contain dots, when rawURL fails to parse, or when the constructed ID fails IsValidEntityID. The Try-variant naming follows the beta.36 precedent for runtime tool executors: a panic in a tool handler silently kills the dispatch goroutine, so runtime code MUST use this error-returning form.
Canonicalisation (V1, conservative — easily extended in a follow-up without changing the entity hash for URLs that didn't hit the new rules):
- lowercase scheme and host
- strip default port (:80 for http, :443 for https)
- strip fragment (#section)
- strip trailing slash on bare-host URLs (preserve internal slashes)
- preserve query string as-is (tracking-param stripping and query-param sorting are V2 candidates; query-param semantics are too domain-specific to canonicalise generically)
Note that canonicalisation is one-way: callers who need to display the agent's original input URL should keep it from the tool call, not try to reverse the hash.
func ValidateToolsAllowed ¶
ValidateToolsAllowed validates that all tool calls are in the allowed list
func WebObservationMessageType ¶
WebObservationMessageType returns the mutation-only origin type stamped when a web observation entity is born.
Types ¶
type AgentRequest ¶
type AgentRequest struct {
RequestID string `json:"request_id"`
LoopID string `json:"loop_id"`
Role string `json:"role"`
Messages []ChatMessage `json:"messages"`
Model string `json:"model"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
Tools []ToolDefinition `json:"tools,omitempty"`
ToolChoice *ToolChoice `json:"tool_choice,omitempty"`
// ResponseFormat constrains output to JSON or JSON-schema-conformant
// structure. Optional; nil means no structuring constraint (current
// behavior; tool-calling remains the structured-output primitive when
// nil). Honored on OpenAI-compat providers and Ollama; stubbed
// (warn-once log) on Gemini and generic adapters for v1. See ADR-034.
ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
// Timeout caps this specific request. Go duration string (e.g. "30s").
// Empty means fall through to endpoint, capability, or component-level
// timeout. Takes precedence over all other timeout sources when set.
Timeout string `json:"timeout,omitempty"`
}
AgentRequest represents a request to an agentic service
func (*AgentRequest) MarshalJSON ¶
func (r *AgentRequest) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*AgentRequest) RuleFields ¶
func (r *AgentRequest) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
Withheld: Messages — THE PROMPT, in full, including system prompt, user text and the whole conversation so far. Nothing in the agentic set is more clearly content.
Also withheld as nested configuration with no scalar rule shape, matching TaskMessage: Tools, ToolChoice, ResponseFormat.
func (*AgentRequest) Schema ¶
func (r *AgentRequest) Schema() message.Type
Schema implements message.Payload
func (*AgentRequest) UnmarshalJSON ¶
func (r *AgentRequest) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (AgentRequest) Validate ¶
func (r AgentRequest) Validate() error
Validate checks if the AgentRequest is valid
type AgentResponse ¶
type AgentResponse struct {
RequestID string `json:"request_id"`
Status string `json:"status"`
FinishReason string `json:"finish_reason,omitempty"` // Raw finish_reason from provider (stop, length, tool_calls)
Message ChatMessage `json:"message,omitempty"`
Error string `json:"error,omitempty"`
TokenUsage TokenUsage `json:"token_usage,omitempty"`
RetryCount int `json:"retry_count,omitempty"`
}
AgentResponse represents a response from an agentic service
func (*AgentResponse) MarshalJSON ¶
func (r *AgentResponse) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*AgentResponse) RuleFields ¶
func (r *AgentResponse) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
`status` is the structural outcome — completed, asked for a tool, errored, or ran out of length — and it is the ONLY outcome field a rule gets. Token usage is exposed nested, as it is on the wire, so `$message.token_usage.prompt_tokens` resolves: a budget rule needs the numbers, not the text they paid for.
FINISH_REASON IS WITHHELD, AND ITS VOCABULARY IS NOT A CONTRACT. The field carries the provider's raw value verbatim, and TWO SUPPORTED IN-REPO LANES ALREADY DISAGREE about what that value looks like:
- the chat-completions lane writes the OpenAI chat vocabulary — stop, length, tool_calls (processor/agentic-model/client.go)
- the responses lane writes the Responses API status — completed, incomplete (processor/agentic-model/client_responses.go)
So a rule matching `finish_reason == "length"` breaks on a CONFIG-ONLY endpoint-mode switch inside this repository, before any third-party provider is involved. That is a stability property of the field itself, not an observation about who happens to call it today.
Nothing is lost. Both lanes feed the same switch that produces Status, so the normalised framework classification a rule actually needs is already exposed as `status` — and unlike finish_reason it is validated against a closed set by AgentResponse.Validate. Normalising finish_reason instead is a separately reviewed change (#1056), not a projection decision.
Withheld: FinishReason, Message — MODEL OUTPUT, the content this whole payload exists to deliver — and Error, which is free-form provider prose.
func (*AgentResponse) Schema ¶
func (r *AgentResponse) Schema() message.Type
Schema implements message.Payload
func (*AgentResponse) UnmarshalJSON ¶
func (r *AgentResponse) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (AgentResponse) Validate ¶
func (r AgentResponse) Validate() error
Validate checks if the AgentResponse is valid
type ApprovalPendingEvent ¶
type ApprovalPendingEvent struct {
LoopID string `json:"loop_id"`
CallID string `json:"call_id"`
ToolName string `json:"tool_name"`
Arguments map[string]any `json:"arguments,omitempty"`
Reason string `json:"reason"` // Original "approval_required: ..." rejection reason
RequestedAt time.Time `json:"requested_at"`
// Timeout is the duration after which the loop will auto-reject if
// no response arrives. Zero means wait indefinitely.
Timeout time.Duration `json:"timeout,omitempty"`
// TraceID propagates from the original ToolCall so consumers can
// correlate the approval prompt with the originating loop trace.
TraceID string `json:"trace_id,omitempty"`
}
ApprovalPendingEvent is published by agentic-loop when a tool call is gated by Config.ApprovalRequired and the loop has transitioned to LoopStateAwaitingApproval. Product-layer approval UIs subscribe to agent.approval_pending.<loop_id> and surface the request for human review. The corresponding response is published as agent.approval_response.<loop_id> with an ApprovalResponse payload.
func (*ApprovalPendingEvent) MarshalJSON ¶
func (e *ApprovalPendingEvent) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler.
func (*ApprovalPendingEvent) RuleFields ¶
func (e *ApprovalPendingEvent) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
Withheld: Arguments — the tool arguments are MODEL-AUTHORED, the single most content-shaped field in the agentic set (a bash command, a file body, a search query). Reason is withheld with them: it embeds the executor's free-text rejection message. `tool_name` is the structural fact an approval routing rule needs.
func (*ApprovalPendingEvent) Schema ¶
func (e *ApprovalPendingEvent) Schema() message.Type
Schema implements message.Payload.
func (*ApprovalPendingEvent) UnmarshalJSON ¶
func (e *ApprovalPendingEvent) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler.
func (*ApprovalPendingEvent) Validate ¶
func (e *ApprovalPendingEvent) Validate() error
Validate implements message.Payload.
type ApprovalResponse ¶
type ApprovalResponse struct {
LoopID string `json:"loop_id"`
CallID string `json:"call_id"`
Decision string `json:"decision"` // ApprovalDecisionApprove | ApprovalDecisionReject | ApprovalDecisionModify
// ModifiedArguments replaces the original tool-call arguments when
// Decision == ApprovalDecisionModify. Ignored for approve/reject.
ModifiedArguments map[string]any `json:"modified_arguments,omitempty"`
// Reason is an optional human-readable explanation for the
// decision. Carried into the audit trail and into the synthesized
// rejection result when Decision == reject.
Reason string `json:"reason,omitempty"`
// ApprovedBy identifies the approver. Required for approve and
// modify decisions so the audit trail knows who said yes; optional
// for reject. Propagated to ToolCall.ApprovedBy on re-dispatch so
// the agentic-tools approval filter recognises the bypass.
ApprovedBy string `json:"approved_by,omitempty"`
DecidedAt time.Time `json:"decided_at"`
}
ApprovalResponse is published by a product-layer approval UI in reply to an ApprovalPendingEvent. The agentic-loop subscribes to agent.approval_response.<loop_id>; the matching loop transitions out of LoopStateAwaitingApproval and either re-dispatches the tool (Decision == approve | modify) or synthesizes a rejection result for the LLM (Decision == reject).
func (*ApprovalResponse) MarshalJSON ¶
func (r *ApprovalResponse) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler.
func (*ApprovalResponse) RuleFields ¶
func (r *ApprovalResponse) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
`decision` is a closed enum and `approved_by` an actor identity — both structural, and together they are what an audit or escalation rule matches.
Withheld: ModifiedArguments (open, human- or model-authored map) and Reason (human free text).
func (*ApprovalResponse) Schema ¶
func (r *ApprovalResponse) Schema() message.Type
Schema implements message.Payload.
func (*ApprovalResponse) UnmarshalJSON ¶
func (r *ApprovalResponse) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler.
func (*ApprovalResponse) Validate ¶
func (r *ApprovalResponse) Validate() error
Validate implements message.Payload.
type Attachment ¶
type Attachment struct {
Type string `json:"type"` // file, image, code, url
Name string `json:"name"` // filename or title
URL string `json:"url,omitempty"` // URL to fetch content
Content string `json:"content,omitempty"` // inline content if small
MimeType string `json:"mime_type,omitempty"`
Size int64 `json:"size,omitempty"`
}
Attachment represents a file or other media attached to a message
type ChatMessage ¶
type ChatMessage struct {
Role string `json:"role"`
Content string `json:"content,omitempty"`
Name string `json:"name,omitempty"` // Function name for tool role messages (required by Gemini)
ReasoningContent string `json:"reasoning_content,omitempty"` // Thinking model chain-of-thought
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"` // Required for tool role messages
IsError bool `json:"is_error,omitempty"` // Tool result contains an error — preserved during context GC
// ReasoningRecords are provider-opaque reasoning blobs captured
// from the model's response, to be echoed back on subsequent
// turns. Loop owns cross-turn propagation; adapters reshape into
// wire format at the seam. See ReasoningRecord (agentic/reasoning.go)
// and ADR-051.
ReasoningRecords []ReasoningRecord `json:"reasoning_records,omitempty"`
}
ChatMessage represents a message in a conversation
func (*ChatMessage) UnmarshalJSON ¶
func (m *ChatMessage) UnmarshalJSON(data []byte) error
UnmarshalJSON accepts both "reasoning" (Ollama) and "reasoning_content" (DeepSeek/canonical). If both are present, reasoning_content wins.
func (ChatMessage) Validate ¶
func (m ChatMessage) Validate() error
Validate checks if the ChatMessage is valid
type ConstructedContext ¶
type ConstructedContext = types.ConstructedContext
ConstructedContext is an alias for types.ConstructedContext. The canonical type is defined in pkg/types/context.go.
type ContextEvent ¶
type ContextEvent struct {
Type string `json:"type"` // ContextEventCompactionStarting, ContextEventCompactionComplete, ContextEventGCComplete
LoopID string `json:"loop_id"`
UserID string `json:"user_id,omitempty"` // owning user (provenance hook); lets a consumer scope user-scoped artifacts without a separate KV lookup
Iteration int `json:"iteration"`
Utilization float64 `json:"utilization,omitempty"`
TokensSaved int `json:"tokens_saved,omitempty"`
Summary string `json:"summary,omitempty"`
}
ContextEvent represents a context management event (compaction, GC).
func (*ContextEvent) MarshalJSON ¶
func (e *ContextEvent) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*ContextEvent) RuleFields ¶
func (e *ContextEvent) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
Withheld: Summary — the compaction summary is LLM-authored prose about the conversation it replaced, which is the clearest case of content there is. `utilization` and `tokens_saved` are the measurements a rule reacts to.
func (*ContextEvent) Schema ¶
func (e *ContextEvent) Schema() message.Type
Schema implements message.Payload
func (*ContextEvent) UnmarshalJSON ¶
func (e *ContextEvent) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (*ContextEvent) Validate ¶
func (e *ContextEvent) Validate() error
Validate implements message.Payload
type ContextSource ¶
type ContextSource = types.ContextSource
ContextSource is an alias for types.ContextSource. The canonical type is defined in pkg/types/context.go.
type CoordinatorDecision ¶
CoordinatorDecision is the typed decision of a `decide` terminal, observed by agentic-loop at completion and carried on LoopCompletedEvent (ADR-101 D2). It is populated ONLY when the loop's terminal StopLoop tool result came from the decide tool; a synthesized needs_clarification decision (a graph triple written after completion) never populates it, and no consumer infers a decision from the shape of Result.
Both fields are required when the decision is present: an empty Action or Reason fails LoopCompletedEvent.Validate so a malformed decision is permanently rejected rather than silently classified as a handoff.
type GraphContextSpec ¶
type GraphContextSpec = types.GraphContextSpec
GraphContextSpec is an alias for types.GraphContextSpec. The canonical type is defined in pkg/types/context.go.
type LoopCancelledEvent ¶
type LoopCancelledEvent struct {
LoopID string `json:"loop_id"`
TaskID string `json:"task_id"`
Outcome string `json:"outcome"` // OutcomeCancelled
CancelledBy string `json:"cancelled_by"`
// ParentLoopID enables ancestry walks from cancelled loops (parity with
// LoopFailedEvent.ParentLoopID). Populated from LoopEntity.ParentLoopID
// at cancellation construction time (ADR-053 D8).
ParentLoopID string `json:"parent_loop,omitempty"`
WorkflowSlug string `json:"workflow_slug,omitempty"`
WorkflowStep string `json:"workflow_step,omitempty"`
CancelledAt time.Time `json:"cancelled_at"`
Metadata map[string]any `json:"metadata,omitempty"`
// RunID is the bare run loop-id this loop belongs to (ADR-053 D8).
// Empty when the loop is not part of a run.
RunID string `json:"run_id,omitempty"`
// RunEntityID is the full 6-part chain execution entity ID for the run.
// Empty when RunID is empty.
RunEntityID string `json:"run_entity_id,omitempty"`
}
LoopCancelledEvent is published when a loop is cancelled by user action.
func (*LoopCancelledEvent) MarshalJSON ¶
func (e *LoopCancelledEvent) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*LoopCancelledEvent) RuleFields ¶
func (e *LoopCancelledEvent) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
`cancelled_by` is an actor identity, not authored content, so it is exposed: a rule distinguishing an operator cancellation from a supervisor one is a structural decision.
Withheld: Metadata (open caller-populated map).
func (*LoopCancelledEvent) Schema ¶
func (e *LoopCancelledEvent) Schema() message.Type
Schema implements message.Payload
func (*LoopCancelledEvent) UnmarshalJSON ¶
func (e *LoopCancelledEvent) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (*LoopCancelledEvent) Validate ¶
func (e *LoopCancelledEvent) Validate() error
Validate implements message.Payload
type LoopCompletedEvent ¶
type LoopCompletedEvent struct {
LoopID string `json:"loop_id"`
TaskID string `json:"task_id"`
Outcome string `json:"outcome"` // OutcomeSuccess
Role string `json:"role"`
Result string `json:"result"`
Prompt string `json:"prompt,omitempty"` // Original user task prompt; enables NL/BM25 search
Model string `json:"model"`
Iterations int `json:"iterations"`
TokensIn int `json:"tokens_in"`
TokensOut int `json:"tokens_out"`
ParentLoopID string `json:"parent_loop,omitempty"`
WorkflowSlug string `json:"workflow_slug,omitempty"`
WorkflowStep string `json:"workflow_step,omitempty"`
CompletedAt time.Time `json:"completed_at"`
// User routing info for response delivery
ChannelType string `json:"channel_type,omitempty"`
ChannelID string `json:"channel_id,omitempty"`
UserID string `json:"user_id,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
// RunID is the bare run loop-id this loop belongs to (ADR-053 D8).
// Empty when the loop is not part of a run.
RunID string `json:"run_id,omitempty"`
// RunEntityID is the full 6-part chain execution entity ID for the run.
// Empty when RunID is empty.
RunEntityID string `json:"run_entity_id,omitempty"`
// Decision is the typed decision of a `decide` terminal (ADR-101,
// gh#1094). Nil for every other terminal — a non-decide StopLoop
// tool, a model-text completion, or a synthesized needs_clarification
// decision. Result is unchanged either way.
Decision *CoordinatorDecision `json:"decision,omitempty"`
}
LoopCompletedEvent is published when a loop completes successfully.
func (*LoopCompletedEvent) MarshalJSON ¶
func (e *LoopCompletedEvent) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*LoopCompletedEvent) RuleFields ¶
func (e *LoopCompletedEvent) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
`role` and `outcome` are the two fields the shipped architect→editor handoff conditions on (configs/rules/agentic-workflow/architect-editor.json); before this projection existed that rule could never fire.
Withheld: Result (MODEL OUTPUT — the completion body the agent authored), Prompt (the user's task text), Metadata (open caller-populated map). A rule branching on what the agent actually said is a quality judgement over unstructured text; that belongs to a coordinator, whose terminal tool emits a structured triple a later rule can match on. Downstream consumers retrieve the body on demand (read_loop_result), which is why omitting it costs nothing but the temptation.
func (*LoopCompletedEvent) Schema ¶
func (e *LoopCompletedEvent) Schema() message.Type
Schema implements message.Payload
func (*LoopCompletedEvent) UnmarshalJSON ¶
func (e *LoopCompletedEvent) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (*LoopCompletedEvent) Validate ¶
func (e *LoopCompletedEvent) Validate() error
Validate implements message.Payload
type LoopCreatedEvent ¶
type LoopCreatedEvent struct {
LoopID string `json:"loop_id"`
TaskID string `json:"task_id"`
Role string `json:"role"`
Model string `json:"model"`
WorkflowSlug string `json:"workflow_slug,omitempty"`
WorkflowStep string `json:"workflow_step,omitempty"`
ContextRequestID string `json:"context_request_id,omitempty"`
MaxIterations int `json:"max_iterations"`
CreatedAt time.Time `json:"created_at"`
Metadata map[string]any `json:"metadata,omitempty"`
// RunID is the bare run loop-id this loop belongs to (ADR-053 D8).
// Empty when the loop is not part of a run.
RunID string `json:"run_id,omitempty"`
// RunEntityID is the full 6-part chain execution entity ID for the run
// (e.g. "org.platform.agent.chain.execution.<runID>"). Empty when RunID is empty.
RunEntityID string `json:"run_entity_id,omitempty"`
}
LoopCreatedEvent is published when a new agentic loop is created.
func (*LoopCreatedEvent) MarshalJSON ¶
func (e *LoopCreatedEvent) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*LoopCreatedEvent) RuleFields ¶
func (e *LoopCreatedEvent) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
Withheld: Metadata (open caller-populated map).
func (*LoopCreatedEvent) Schema ¶
func (e *LoopCreatedEvent) Schema() message.Type
Schema implements message.Payload
func (*LoopCreatedEvent) UnmarshalJSON ¶
func (e *LoopCreatedEvent) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (*LoopCreatedEvent) Validate ¶
func (e *LoopCreatedEvent) Validate() error
Validate implements message.Payload
type LoopEntity ¶
type LoopEntity struct {
ID string `json:"id"`
TaskID string `json:"task_id"`
State LoopState `json:"state"`
Role string `json:"role"`
Model string `json:"model"`
Iterations int `json:"iterations"`
MaxIterations int `json:"max_iterations"`
PendingToolResults map[string]ToolResult `json:"pending_tool_results,omitempty"` // Accumulated tool results by call ID
StartedAt time.Time `json:"started_at,omitempty"` // When the loop was created
TimeoutAt time.Time `json:"timeout_at,omitempty"` // When the loop should timeout
ParentLoopID string `json:"parent_loop_id,omitempty"` // Parent loop ID for architect->editor relationship
// RunID is the 6-part-derived run anchor; the run loop-id this loop belongs to.
// Empty for loops not in a run. Inherited at spawn (ADR-053 D7).
RunID string `json:"run_id,omitempty"`
// Multi-agent depth tracking
Depth int `json:"depth,omitempty"` // Current depth in agent tree (0 = root)
MaxDepth int `json:"max_depth,omitempty"` // Maximum allowed depth for spawned agents
// Signal support fields
PauseRequested bool `json:"pause_requested,omitempty"` // Pause requested, will pause at next checkpoint
PauseRequestedBy string `json:"pause_requested_by,omitempty"` // User who requested pause
StateBeforePause LoopState `json:"state_before_pause,omitempty"` // State before pause (for resume)
CancelledBy string `json:"cancelled_by,omitempty"` // User who cancelled the loop
CancelledAt time.Time `json:"cancelled_at,omitempty"` // When the loop was cancelled
// Approval-gating fields (set when a tool call is rejected by the
// agentic-tools approval filter). The loop transitions to
// LoopStateAwaitingApproval and persists the pending call here so
// it can be re-dispatched on approval. StateBeforeApproval lets us
// restore the prior workflow state once the approval response
// arrives.
PendingApproval *PendingApprovalState `json:"pending_approval,omitempty"`
StateBeforeApproval LoopState `json:"state_before_approval,omitempty"`
// User context (for routing responses)
UserID string `json:"user_id,omitempty"` // User who initiated the loop
ChannelType string `json:"channel_type,omitempty"` // cli, slack, discord, web
ChannelID string `json:"channel_id,omitempty"` // Channel/session ID for routing responses
// Workflow context (for loops created by workflow commands)
WorkflowSlug string `json:"workflow_slug,omitempty"` // e.g., "add-user-auth"
WorkflowStep string `json:"workflow_step,omitempty"` // e.g., "design"
// Completion data (populated when loop completes)
// These fields enable SSE delivery of results via KV watch
Outcome string `json:"outcome,omitempty"` // success, failed, cancelled
Result string `json:"result,omitempty"` // LLM response content
Error string `json:"error,omitempty"` // Error message on failure
CompletedAt time.Time `json:"completed_at,omitempty"` // When the loop completed
// Domain context propagated from TaskMessage through lifecycle events
Metadata map[string]any `json:"metadata,omitempty"`
}
LoopEntity represents an agentic loop instance
func NewLoopEntity ¶
func NewLoopEntity(id, taskID, role, model string, maxIterations ...int) LoopEntity
NewLoopEntity creates a new LoopEntity with default values
func (*LoopEntity) BeginAwaitingApproval ¶
func (e *LoopEntity) BeginAwaitingApproval(callID, toolName string, arguments map[string]any, reason string, timeout time.Duration, traceID string) error
BeginAwaitingApproval transitions the loop into LoopStateAwaitingApproval and stores the pending call. Returns an error if the loop is already terminal or already awaiting approval for a different call (which would indicate a logic bug — two rejections for the same loop shouldn't be possible while the first is still pending).
func (*LoopEntity) IncrementIteration ¶
func (e *LoopEntity) IncrementIteration() error
IncrementIteration increments the iteration counter
func (*LoopEntity) ResolveApproval ¶
func (e *LoopEntity) ResolveApproval() error
ResolveApproval clears the pending approval and restores the prior state so the loop can resume normal iteration. Caller is responsible for re-dispatching the tool (approve/modify) or synthesizing a rejection (reject) before invoking this.
func (*LoopEntity) TransitionTo ¶
func (e *LoopEntity) TransitionTo(newState LoopState) error
TransitionTo transitions the entity to a new state
func (*LoopEntity) Validate ¶
func (e *LoopEntity) Validate() error
Validate checks if the LoopEntity is valid
type LoopExecutionEntity ¶
type LoopExecutionEntity struct {
Org string
Platform string
LoopID string
Task *TaskMessage
}
LoopExecutionEntity is the Graphable origin contract for an agentic loop execution entity (ADR-056 W0 4c-pre-1). It encodes the spawn-identity triples that give the entity its typed origin: role, task, parent, run, reply_to, workflow, workflow_step, user, and description.
EntityID() and Triples() together form the typed origin contract — the same data set that processor/agentic-loop's buildSpawnIdentityTriples emitted, now expressed through graph.Graphable so it can be born via the canonical entity-create operation.
This type lives in the agentic package (below processor/agentic-loop in the import graph) to keep the dependency direction agentic → processor (never the reverse).
func (*LoopExecutionEntity) EntityID ¶
func (e *LoopExecutionEntity) EntityID() string
EntityID returns the canonical 6-part entity ID for this loop execution.
func (*LoopExecutionEntity) Triples ¶
func (e *LoopExecutionEntity) Triples() []message.Triple
Triples returns the spawn-identity origin triples for this loop execution. The predicate set is identical to what buildSpawnIdentityTriples in processor/agentic-loop produced: always-on (role, task), conditionally-on (parent, run, run.entity_id, reply_to, workflow, workflow_step, user, description) when the corresponding TaskMessage field is non-empty.
All triples share a single timestamp (captured once at call time) so that every triple in one batch is guaranteed to have the same wall-clock value — mirrors the shared-timestamp invariant preserved by buildSpawnIdentityTriples.
Returns nil when Task is nil.
type LoopFailedEvent ¶
type LoopFailedEvent struct {
LoopID string `json:"loop_id"`
TaskID string `json:"task_id"`
Outcome string `json:"outcome"` // OutcomeFailed
Reason string `json:"reason"`
Error string `json:"error"`
Role string `json:"role"`
Prompt string `json:"prompt,omitempty"` // Original user task prompt; enables NL/BM25 search
Model string `json:"model"`
Iterations int `json:"iterations"`
TokensIn int `json:"tokens_in"`
TokensOut int `json:"tokens_out"`
// ParentLoopID enables ancestry walks from failed loops — required by
// chain-aware failure handlers (e.g. semteams chainpause writing
// chain.paused.* triples to the canonical chain entity per ADR-038).
// Without this, the agent.loop.parent triple wasn't stamped on the
// failed loop, so an ancestry walk terminated at the failure and
// returned chain_id == failed_loop_id even when the failed loop
// wasn't the chain root. Populated at construction the same way
// LoopCompletedEvent.ParentLoopID is — entity.ParentLoopID flows
// through.
ParentLoopID string `json:"parent_loop,omitempty"`
WorkflowSlug string `json:"workflow_slug,omitempty"`
WorkflowStep string `json:"workflow_step,omitempty"`
FailedAt time.Time `json:"failed_at"`
// User routing info for error notifications
ChannelType string `json:"channel_type,omitempty"`
ChannelID string `json:"channel_id,omitempty"`
UserID string `json:"user_id,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
// RunID is the bare run loop-id this loop belongs to (ADR-053 D8).
// Empty when the loop is not part of a run.
RunID string `json:"run_id,omitempty"`
// RunEntityID is the full 6-part chain execution entity ID for the run.
// Empty when RunID is empty.
RunEntityID string `json:"run_entity_id,omitempty"`
}
LoopFailedEvent is published when a loop fails.
func (*LoopFailedEvent) MarshalJSON ¶
func (e *LoopFailedEvent) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*LoopFailedEvent) RuleFields ¶
func (e *LoopFailedEvent) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
REASON IS WITHHELD, AND DO NOT RESTORE IT BY ENUMERATING ITS PRODUCERS. An earlier round exposed `reason` after finding that every in-tree caller passes a closed literal ("model_error", "length_truncated", "timeout"). That enumeration establishes a property of TODAY'S CALLERS, not a contract, and the contract is what a projection may rely on:
- LoopFailedEvent.Validate (events.go) constrains LoopID and TaskID only. NOTHING constrains Reason — not a validator, not a named type, not a closed set.
- MessageHandler.BuildFailureEvent and BuildFailureMessages (processor/agentic-loop/handlers.go) are EXPORTED and take `reason` as a free string.
So an adopter can legally place user or model prose in Reason using only exported API, and exposing it would route that prose into `$message.reason`, into action templates, and into NATS subject tokens — contradicting the structural-only guarantee this whole file exists to keep. Withholding is the answer that does not depend on who happens to call it.
Making `reason` rule-matchable is a TYPED-CONTRACT change, not a projection change: it needs a validated enum, rejection before publish side effects, and a migration for the two exported builders. That is its own proposal.
Withheld: Reason, Error, Prompt (the user's task text), Metadata (open map). `outcome` carries the failure signal a rule can act on today.
func (*LoopFailedEvent) Schema ¶
func (e *LoopFailedEvent) Schema() message.Type
Schema implements message.Payload
func (*LoopFailedEvent) UnmarshalJSON ¶
func (e *LoopFailedEvent) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (*LoopFailedEvent) Validate ¶
func (e *LoopFailedEvent) Validate() error
Validate implements message.Payload
type LoopState ¶
type LoopState string
LoopState represents the current state of an agentic loop
const ( // Standard workflow states LoopStateExploring LoopState = "exploring" LoopStatePlanning LoopState = "planning" LoopStateArchitecting LoopState = "architecting" LoopStateExecuting LoopState = "executing" LoopStateReviewing LoopState = "reviewing" // Terminal states LoopStateComplete LoopState = "complete" LoopStateFailed LoopState = "failed" LoopStateCancelled LoopState = "cancelled" // Cancelled by user signal // Signal-related states LoopStatePaused LoopState = "paused" // Paused by user signal LoopStateAwaitingApproval LoopState = "awaiting_approval" // Waiting for user approval )
Loop states for the agentic state machine. The state machine supports fluid transitions (can move backward) except from terminal states.
func (LoopState) IsTerminal ¶
IsTerminal returns true if the state is a terminal state
type ModelConfig ¶
type ModelConfig struct {
Temperature float64 `json:"temperature,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
}
ModelConfig represents configuration for a language model
func (ModelConfig) WithDefaults ¶
func (c ModelConfig) WithDefaults() ModelConfig
WithDefaults returns a copy of the config with default values applied
type PendingApprovalState ¶
type PendingApprovalState struct {
CallID string `json:"call_id"`
ToolName string `json:"tool_name"`
Arguments map[string]any `json:"arguments,omitempty"`
Reason string `json:"reason,omitempty"` // Original "approval_required: ..." rejection reason
RequestedAt time.Time `json:"requested_at"` // When the rejection arrived and the loop paused
Timeout time.Duration `json:"timeout,omitempty"` // Auto-reject deadline; zero means wait indefinitely
TraceID string `json:"trace_id,omitempty"` // Propagated for audit correlation
}
PendingApprovalState captures the gated tool call so the loop can re-dispatch (or reject) it once a human approval response arrives. Persisted on LoopEntity so a process restart mid-approval still remembers what the human is reviewing.
type ReasoningCarrierKind ¶
type ReasoningCarrierKind string
ReasoningCarrierKind enumerates the structural attachment shapes a provider's reasoning blob can take on the wire. Closed set: adapters must handle every value, and unknown values are an authoring error.
const ( // ReasoningCarrierToolCall indicates the blob attaches to a // specific tool call on the wire. Used by Gemini (thought_signature // per tool_call). ReasoningCarrierToolCall ReasoningCarrierKind = "tool_call" // ReasoningCarrierStandaloneItem indicates the blob is a sibling // output item with no attachment to messages or tool calls. Used // by OpenAI Responses (reasoning items in the output array). ReasoningCarrierStandaloneItem ReasoningCarrierKind = "standalone_item" // ReasoningCarrierAssistantContent indicates the blob is a content // part inside the assistant message. Reserved for Anthropic's // thinking blocks when/if we take that on. ReasoningCarrierAssistantContent ReasoningCarrierKind = "assistant_content" )
type ReasoningRecord ¶
type ReasoningRecord struct {
// Provider names the carrier provider. The adapter uses this to
// decide on-the-wire reconstruction. Known values: "google",
// "openai". Future providers extend the set.
Provider string `json:"provider"`
// ItemID is the provider-assigned identity for this record.
// Used for cross-turn echo when the provider needs it. OpenAI
// Responses uses it (id of the reasoning item); Gemini does not
// (signatures are carried per-tool-call, not by id).
ItemID string `json:"item_id,omitempty"`
// SummaryText is a human-readable description of the reasoning,
// when the provider exposes one. Safe to log. Used for trajectory
// and operator-facing trace.
SummaryText string `json:"summary_text,omitempty"`
// Opaque is the provider-specific blob that must be echoed back
// verbatim. Treat as bytes; do not parse, do not log in full.
// - Gemini: the base64 thought_signature string (bytes of UTF-8)
// - OpenAI: encrypted_content blob (when store:false)
// - Anthropic (future): thinking block content
Opaque []byte `json:"opaque,omitempty"`
// CarrierKind names the structural attachment constraint on the
// wire. Adapters use this to reshape correctly. The set is closed:
// unknown values are an authoring error and must fail validation,
// not silently pass.
CarrierKind ReasoningCarrierKind `json:"carrier_kind"`
// ToolCallID is set when CarrierKind == ReasoningCarrierToolCall.
// The signature belongs with this specific tool call; the adapter
// re-binds them on send.
ToolCallID string `json:"tool_call_id,omitempty"`
}
ReasoningRecord is the provider-neutral carrier for opaque reasoning state that must be echoed back on the next turn. Captured on response, attached to the next request. Provider-specific reshape happens at the adapter seam — loop code stays shape-neutral.
Replaces the per-provider MetadataKeyGoogleThoughtSignature carrier retired in ADR-051 Phase 1. The semantic role ("opaque blob the model wants echoed on the next turn for reasoning continuity") is stable across providers; the wire shape is not, so the abstraction lives at the role layer.
type ResponseAction ¶
type ResponseAction struct {
ID string `json:"id"`
Type string `json:"type"` // button, reaction
Label string `json:"label"`
Signal string `json:"signal"` // signal to send if clicked
Style string `json:"style"` // primary, danger, secondary
}
ResponseAction represents an interactive action in a response
type ResponseBlock ¶
type ResponseBlock struct {
Type string `json:"type"` // text, code, diff, file, progress
Content string `json:"content"`
Lang string `json:"lang,omitempty"` // for code blocks
}
ResponseBlock represents a block of content in a rich response
type ResponseFormat ¶
type ResponseFormat struct {
// Type: ResponseFormatJSONObject (legacy, bare JSON validity) or
// ResponseFormatJSONSchema (current standard, strict-mode schema
// adherence). Empty is invalid; callers must set one.
Type string `json:"type"`
// Schema is the JSON Schema document. Required when Type ==
// ResponseFormatJSONSchema; ignored when Type ==
// ResponseFormatJSONObject. Must be a valid OpenAI Structured Outputs
// schema (subset of JSON Schema — no $ref to external schemas, no
// anyOf at root, every property in required, additionalProperties:
// false). Adapters do not validate locally; trust the caller.
Schema map[string]any `json:"schema,omitempty"`
// Name is required by OpenAI when Type == ResponseFormatJSONSchema.
// Should describe the output (e.g. "decide_action_args"). Adapters
// that don't need a name ignore it.
Name string `json:"name,omitempty"`
// Strict enables OpenAI's strict mode (response is guaranteed
// schema-conformant; sampling is constrained). Defaults true via
// NewJSONSchemaFormat. Set false to opt into permissive mode (rare;
// mostly for compat testing).
Strict bool `json:"strict"`
}
ResponseFormat constrains the model's output to a JSON object or JSON-schema-conformant structure. Maps to OpenAI's response_format on OpenAI-compatible providers; translated to provider-specific equivalents on others (Ollama: native format field; Gemini: stubbed for v1; Anthropic: stubbed for v1, future translation to forced single-tool-call). See ADR-034.
Nil on AgentRequest means no structuring constraint — current behavior; tool-calling remains the structured-output primitive for personas that work fine on cloud providers without response_format.
func NewJSONObjectFormat ¶
func NewJSONObjectFormat() *ResponseFormat
NewJSONObjectFormat constructs a ResponseFormat that requires valid JSON output without imposing a schema. Less reliable than JSON-schema mode on small models; prefer NewJSONSchemaFormat when a schema is available.
func NewJSONSchemaFormat ¶
func NewJSONSchemaFormat(name string, schema map[string]any) *ResponseFormat
NewJSONSchemaFormat constructs a strict-mode JSON-schema ResponseFormat. Strict defaults to true — the OpenAI Structured Outputs guarantee. Pass the schema directly; do not wrap in an outer json_schema envelope (the adapter layer handles wire-shape translation per provider).
func (*ResponseFormat) Validate ¶
func (rf *ResponseFormat) Validate() error
Validate checks if the ResponseFormat has a valid type and required fields.
type TaskMessage ¶
type TaskMessage struct {
LoopID string `json:"loop_id,omitempty"` // loop to continue, or empty for new
TaskID string `json:"task_id"`
Role string `json:"role"`
Model string `json:"model"`
Prompt string `json:"prompt"`
// Workflow context (optional, set by workflow commands)
WorkflowSlug string `json:"workflow_slug,omitempty"` // e.g., "add-user-auth"
WorkflowStep string `json:"workflow_step,omitempty"` // e.g., "design"
// User routing info (optional, for error notifications)
ChannelType string `json:"channel_type,omitempty"` // e.g., "http", "cli", "slack"
ChannelID string `json:"channel_id,omitempty"` // session/channel identifier
UserID string `json:"user_id,omitempty"` // user who initiated the request
// Multi-agent hierarchy (optional, for parallel/nested agents)
ParentLoopID string `json:"parent_loop_id,omitempty"` // Parent loop ID for nested agents
// RunID is the 6-part-derived run anchor: the run loop-id this loop belongs to.
// Empty for loops not in a run. Inherited at spawn (ADR-053 D7).
RunID string `json:"run_id,omitempty"`
Depth int `json:"depth,omitempty"` // Current depth in agent tree (0 = root)
MaxDepth int `json:"max_depth,omitempty"` // Maximum allowed depth
// MaxIterations is an optional per-spawn iteration budget (gh#528). Nil
// means "use the component default" (agentic-loop's Config.MaxIterations).
// A non-nil value narrows the spawned loop's budget: agentic-loop computes
// the effective ceiling as min(*MaxIterations, component ceiling) at loop
// creation — a spawn may narrow its budget, never widen it past the
// operator-configured ceiling. Validate rejects a non-nil value below 1.
// The publish_agent rule action exposes this as loop_max_iterations
// (distinct from the action's own firing-cap max_iterations field).
MaxIterations *int `json:"max_iterations,omitempty"`
// InReplyTo is the bare loop-id this task is a reply to (gh#256). When
// set, agentic-loop stamps an agent.loop.reply_to triple (a 6-part loop
// entity reference, mirroring agent.loop.parent) on the spawned loop so a
// rule can detect a reply via $entity.triple.agent.loop.reply_to. Empty
// for non-reply tasks. Distinct from ParentLoopID (tree ancestry) — a
// reply re-enters a paused run rather than nesting under a parent.
InReplyTo string `json:"in_reply_to,omitempty"`
// Pre-constructed context (optional, skips discovery if present)
// When set, the agent loop uses this context directly instead of hydrating
Context *types.ConstructedContext `json:"context,omitempty"`
// Context assembly reference (links to assembled context)
ContextRequestID string `json:"context_request_id,omitempty"`
// Tools is a per-task tool override. The spawner sets this to scope
// which tools the agent may call; the loop consumes the field with
// nil-vs-empty semantics:
// - nil → no override, loop falls back to global discovery.
// - non-nil → explicit allowlist. Empty slice means "no tools".
// `omitempty` is deliberately omitted so an explicit empty slice
// round-trips as `"tools": []` and the receiver can distinguish it
// from an absent field.
Tools []ToolDefinition `json:"tools"`
// ToolChoice controls how the model selects tools for this task.
// Nil means "auto" (model decides). Cached for all iterations in the loop.
ToolChoice *ToolChoice `json:"tool_choice,omitempty"`
// Domain context propagated to all tool calls in this loop
Metadata map[string]any `json:"metadata,omitempty"`
// Timeout caps LLM calls issued for this task. Go duration string
// (e.g. "30s"). Empty means fall through to endpoint, capability, or
// component-level timeout. Highest precedence when set.
Timeout string `json:"timeout,omitempty"`
// ResponseFormat constrains the model's output to a JSON object or
// JSON-schema-conformant JSON for this task. ADR-034. The agentic-loop
// caches it on initial build and threads it onto every AgentRequest in
// the loop. Nil means tool-calling behaviour is unchanged. Set this
// from a rule.Action (publish_agent path) or from a dispatcher when
// the task needs structured output.
ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
}
TaskMessage represents a task to be executed by an agentic loop
func (*TaskMessage) MarshalJSON ¶
func (t *TaskMessage) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*TaskMessage) RuleFields ¶
func (t *TaskMessage) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
Withheld: Prompt (the task text itself), Context (the assembled context body), Metadata (open caller-populated map, and the carrier for MetadataKeyRelatedLoops and friends — framework keys a rule reads through $entity.triple.agent.lineage.* instead).
Also withheld, deliberately and for a different reason: Tools, ToolChoice and ResponseFormat. These are structural, not content, but they are nested configuration objects with no scalar a rule condition can compare — a rule wanting "was tool X offered" needs a shape this projection does not have. Widen when a real consumer asks, not before.
func (*TaskMessage) Schema ¶
func (t *TaskMessage) Schema() message.Type
Schema implements message.Payload
func (*TaskMessage) UnmarshalJSON ¶
func (t *TaskMessage) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (TaskMessage) Validate ¶
func (t TaskMessage) Validate() error
Validate checks if the TaskMessage is valid
type TokenUsage ¶
type TokenUsage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
}
TokenUsage tracks token consumption for a request
func (TokenUsage) Total ¶
func (u TokenUsage) Total() int
Total returns the total number of tokens used
type ToolCall ¶
type ToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"` // Domain context, propagated from task
LoopID string `json:"loop_id,omitempty"`
TraceID string `json:"trace_id,omitempty"`
// ApprovedBy is set by the loop when re-dispatching a previously
// gated tool call after receiving an ApprovalResponse. The
// agentic-tools approval filter recognises a non-empty ApprovedBy
// as the explicit bypass token (see C5). Empty means the call has
// not been through human approval — normal filter rules apply.
ApprovedBy string `json:"approved_by,omitempty"`
}
ToolCall represents a request to call a tool
func (*ToolCall) MarshalJSON ¶
MarshalJSON implements json.Marshaler
func (*ToolCall) RuleFields ¶
RuleFields implements message.RuleReadable.
Call identity and tool name, per the governance case this projection serves (ADR-039: a rule gating a proposed tool call needs loop_id, call_id and the tool name to build its verdict).
Withheld: Arguments (MODEL-AUTHORED content — see ApprovalPendingEvent) and Metadata (open caller-populated map; framework keys such as MetadataKeyRunID reach rules as loop-entity triples, not through here).
func (*ToolCall) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler
type ToolChoice ¶
type ToolChoice struct {
Mode string `json:"mode"` // "auto", "required", "none", "function"
FunctionName string `json:"function_name,omitempty"` // required when Mode is "function"
}
ToolChoice controls how the model selects tools. Mode is one of: "auto" (default), "required", "none", or "function". When Mode is "function", FunctionName specifies which function to call.
func (ToolChoice) Validate ¶
func (tc ToolChoice) Validate() error
Validate checks if the ToolChoice has a valid mode and required fields.
type ToolDefinition ¶
type ToolDefinition struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]any `json:"parameters"`
// Strict enables OpenAI's strict-mode tool calling: the model is
// constrained to emit tool_calls[].function.arguments that conform to
// Parameters. Symmetric to ResponseFormat.Strict — same provider table
// applies (see ADR-034): honored on OpenAI proper / vLLM / OpenRouter /
// sparky / any OpenAI-compat runtime under provider:"openai"; silently
// ignored on Anthropic and Gemini OpenAI-compat (adapters clear it +
// Warn). Best-effort on Ollama /v1 (model-dependent — gemma3 ignores
// per ADR-034 §gh#10001).
//
// Requires Parameters to satisfy OpenAI's strict-mode subset:
// additionalProperties:false at every object level, every property
// listed in required, no $ref/anyOf at the root, max nesting 5. A
// non-conforming schema returns 400 from the upstream — caller bug,
// not a framework concern.
Strict bool `json:"strict,omitempty"`
// Paginated declares that this tool supports continuation paging via
// the agentic.MetadataKey{HasMore,NextOffset,NextCursor} contract.
// When true, the executor MUST set MetadataKeyHasMore on every
// successful result (bool false for last page, bool true with one
// of NextOffset or NextCursor for intermediate pages). The agent
// loop reads has_more in buildToolMessages and appends a canonical
// continuation hint to the model's next message — telling the
// model it can call the same tool again with the supplied
// continuation token instead of having to re-narrow blind.
//
// Informational at the wire level today: the loop branches on the
// actual has_more value in result metadata, not on this flag. Future
// uses include operator introspection ("which tools paginate?") and
// loop-side contract-violation warnings when has_more arrives from
// a tool that didn't declare Paginated.
Paginated bool `json:"paginated,omitempty"`
// Effect declares the worst effect this tool can have (gh#749,
// ADR-089). Descriptive input to discovery and to a consumer's own
// default approval policy — it does NOT alter what semstreams
// admits, gates, or refuses. The authoritative controls remain the
// configured approval-required and allowed-tool name sets and the
// per-loop advertised-tool admission check.
//
// Empty means undeclared, and undeclared resolves to
// ToolEffectUnknown — never to ToolEffectReadOnly. Read it through
// Canonical() rather than comparing the raw value.
//
// Does not cross the provider wire: no provider function schema has
// a slot for it, and the model is not a party this classification is
// for. Paginated is the precedent.
Effect ToolEffect `json:"effect,omitempty"`
}
ToolDefinition represents the definition of a tool that can be called
func (ToolDefinition) Validate ¶
func (t ToolDefinition) Validate() error
Validate checks if the ToolDefinition is valid
type ToolEffect ¶
type ToolEffect string
ToolEffect classifies the worst effect a tool can have. It is an ordered severity claim, not a taxonomy of everything a tool does: a tool that POSTs to a third party is ToolEffectExternal, full stop, rather than "mutating and external". That is what lets one enum answer the question, and it is also the answer to argument-dependence — a tool whose severity varies with its arguments declares the worst case it admits.
The classification is framework-owned canonical metadata so that downstream consumers (semdev and the second gh#749 consumer) share one vocabulary instead of inventing parallel ones. It is DESCRIPTIVE: see ToolDefinition.Effect for the enforcement boundary.
OPEN FOR EXTENSION. Never switch exhaustively over the members without a default arm resolving to ToolEffectUnknown — a later member must be addable without a coordinated release across consumers.
const ( // ToolEffectUnknown means NO CLAIM has been made about this tool's // effect. It is not a middle rung between read_only and mutating: // a consumer mapping effect onto policy must treat it as at least // as restrictive as ToolEffectExternal. // // This is the resolution of an absent, empty, or unrecognized // value. Absence of a classification is not evidence of safety — // the tool counterpart of the framework rule that an absent // measurement must never render as a measurement of absence. ToolEffectUnknown ToolEffect = "unknown" // ToolEffectReadOnly means the tool observes and changes no state // anywhere, inside or outside the deployment. A GET against an // external API is read_only: what a query discloses is a governance // concern (processor/agentic-governance) and not an effect // classification. // // Distinct from FilesystemPolicyReadOnly (exec_policy.go), which is // a task-scoped filesystem WRITE SCOPE, not a tool classification. // The two are orthogonal and legitimately disagree: a tool may be // ToolEffectExternal while executing under filesystem policy // read_only — one classifies effect on the world, the other governs // worktree mutation. Same word, different subject. ToolEffectReadOnly ToolEffect = "read_only" // ToolEffectMutating means the tool can change state within the // deployment's own boundary — graph, KV, workspace files, rules, // flows, personas. ToolEffectMutating ToolEffect = "mutating" // ToolEffectExternal means the tool can change state or take // irrevocable action OUTSIDE the deployment boundary: a third-party // write, an email, a purchase. It DOMINATES ToolEffectMutating under // worst-effect semantics. // // "Spend" here means an irrevocable COMMERCIAL ACTION the tool // initiates — an order, a transfer, a booking. It does NOT mean the // metered cost of an external read: a query against a paid search or // data API consumes quota, and quota consumption is a cost, not an // effect on the world. A metered external read stays read_only. (The // two doc comments used to answer this differently; this is the // ruling.) // // MEDIATION DOES NOT LAUNDER EFFECT, but one hop through the // deployment is not itself external. bash is external_effect because // the command it runs can reach anything. A tool that writes a rule // or deploys a flow is mutating, even when the flow it deploys later // performs an outbound HTTP POST: the tool's own effect is the // configuration write, and the outbound action is the deployed // component's effect, classified where that component is described. // Classify what the tool does, not what a thing it configures might // later do — otherwise every configuration tool collapses to // external_effect and the enum stops discriminating. ToolEffectExternal ToolEffect = "external_effect" )
func (ToolEffect) Canonical ¶
func (e ToolEffect) Canonical() ToolEffect
Canonical resolves e to a declared enum member. Empty (undeclared) and unrecognized values both yield ToolEffectUnknown; a declared member returns itself.
Total by construction, and the only correct way to read the field: an unrecognized value must never degrade to a permissive answer (the IsKnownFilesystemPolicy precedent), and here the fail-safe is ToolEffectUnknown, which policy consumers treat as maximally restrictive.
func (ToolEffect) Known ¶
func (e ToolEffect) Known() bool
Known reports whether e names a declared enum member.
The empty string is NOT known — it is *undeclared*, which is a third state with its own handling on each side: registration ACCEPTS it (a producer need not classify itself) while resolution maps it to ToolEffectUnknown. Callers must therefore spell out which of the two they mean rather than relying on Known alone; overloading Known to return true for empty would collapse "declared nothing" into "declared something valid" at the one seam that must tell them apart.
type ToolErrorKind ¶
type ToolErrorKind string
ToolErrorKind classifies the source or nature of a tool execution failure. It is the structured counterpart to ToolResult.Error and feeds the agent.step.error_category graph predicate for queryable failure analysis.
const ( // ToolErrorTimeout means the tool exceeded its execution deadline // (context.DeadlineExceeded observed after the executor returned). ToolErrorTimeout ToolErrorKind = "timeout" // ToolErrorNotFound means the tool was not registered, the requested // resource did not exist, or the caller was not permitted to invoke it // via the component allowlist. ToolErrorNotFound ToolErrorKind = "not_found" // ToolErrorInvalidArgs means tool arguments failed validation // (missing required field, wrong type, schema violation). ToolErrorInvalidArgs ToolErrorKind = "invalid_args" // ToolErrorPermission means the request was refused on authorization // grounds — an external system's auth failure (e.g., HTTP 401/403) or an // internal framework policy refusal (approval filter, per-loop advertised // tool set). ToolErrorPermission ToolErrorKind = "permission" // ToolErrorNetwork means a transport-level failure occurred // (dial error, connection reset, DNS failure). ToolErrorNetwork ToolErrorKind = "network" // ToolErrorExternal means an external service returned a failure // that does not fall into the other categories (5xx, 429 rate limit, // operation-specific failure from an upstream API). ToolErrorExternal ToolErrorKind = "external" // ToolErrorInternal means an executor-internal bug // (marshal/unmarshal failure, unexpected nil, invariant violation). ToolErrorInternal ToolErrorKind = "internal" // ToolErrorUnknown means the failure was not classified. Used as the // default when a ToolResult has a non-empty Error but no ErrorKind. ToolErrorUnknown ToolErrorKind = "unknown" )
type ToolResult ¶
type ToolResult struct {
CallID string `json:"call_id"`
Name string `json:"name,omitempty"` // Tool function name (required by Gemini on tool result messages)
Content string `json:"content,omitempty"`
Error string `json:"error,omitempty"`
ErrorKind ToolErrorKind `json:"error_kind,omitempty"` // Structured classification of the failure
ResultHint ToolResultHint `json:"result_hint,omitempty"` // Structured action recommendation when call worked but agent should refine
Metadata map[string]any `json:"metadata,omitempty"`
LoopID string `json:"loop_id,omitempty"`
TraceID string `json:"trace_id,omitempty"`
StopLoop bool `json:"stop_loop,omitempty"` // Signal loop termination; Content becomes the completion result
}
ToolResult represents the result of a tool call
func (ToolResult) EffectiveErrorKind ¶
func (t ToolResult) EffectiveErrorKind() ToolErrorKind
EffectiveErrorKind returns the failure classification a consumer should act on, applying the default ToolErrorUnknown documents: a result carrying an Error but no ErrorKind is still a failure, classified as unknown.
The empty return is the THIRD state and the load-bearing one: it means the call did not fail at all, which is why callers must branch on emptiness rather than comparing against a member. Do not read ErrorKind directly to answer "did this fail" — an unclassified executor error reads as empty on the raw field and that is the fail-open shape.
This is the home for the normalisation. Two older copies predate it (processor/agentic-tools/component.go and processor/agentic-loop/handlers.go buildToolTrajectoryStep); they agree today and their migration is filed separately. New readers call this.
func (*ToolResult) MarshalJSON ¶
func (t *ToolResult) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*ToolResult) RuleFields ¶
func (t *ToolResult) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
Call identity, tool name and OUTCOME. The outcome is `error_kind`, derived through ToolResult.EffectiveErrorKind rather than copied: a result carrying an Error but no ErrorKind is still a failure, classified as unknown. So the presence of `error_kind` means "this call failed" and its value is the framework's own classification — a rule never has to string-match an error message to learn that much.
This is the one deliberate break from the mirror-the-wire convention on a non-time field: the wire omits `error_kind` for an unclassified failure and the projection does not. Deriving it is the point; a rule that had to spell `error_kind != "" OR error != ""` would be reading the content field the projection withholds.
Withheld: Content (THE RESULT BODY — arbitrary size, arbitrary origin, and the field the rules-carry-references discipline exists for), Error (free-form prose; error_kind is its structural counterpart), Metadata (open map, including the pagination keys, which are the loop's business and not a rule's).
func (*ToolResult) Schema ¶
func (t *ToolResult) Schema() message.Type
Schema implements message.Payload
func (*ToolResult) UnmarshalJSON ¶
func (t *ToolResult) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (ToolResult) Validate ¶
func (t ToolResult) Validate() error
Validate checks if the ToolResult is valid
type ToolResultHint ¶
type ToolResultHint string
ToolResultHint classifies non-error conditions on a SUCCESSFUL tool call where the agent should refine its approach before continuing. It is the structured sibling of ToolErrorKind: ErrorKind classifies errors (the tool failed), Hint classifies successes that returned data the agent should treat as a signal to adjust.
Distinct from ToolErrorKind because the call worked — there's nothing to "retry" in the failure sense. The cases are advisory: the model should narrow, broaden, or introspect on the next turn. The agent loop reads Hint in buildToolMessages and prepends a canonical hint line to the model's next message so small/mid-tier models don't have to parse free-form English advice from a successful result body.
Pre-2026-05-11, the only in-band signaling pattern was ApprovalRequiredPrefix — a stringly-typed magic-string sniffed off the Error field. ResultHint replaces that pattern's spirit with a typed enum: producers set it directly; consumers branch on the typed value.
const ( // HintTooLarge means the call returned more data than the executor // or framework permitted and the content was truncated (or the // raw response would have exceeded an internal cap). Action: the // model should narrow its query — add a filter, an entity_id, or // a smaller limit. Composes with the pagination contract // (MetadataKeyHasMore) when both are set: the model gets BOTH // "narrow your query" AND "or continue with cursor=..." in one // shot. HintTooLarge ToolResultHint = "too_large" // HintEmpty means the call succeeded with an empty result set // (no entities matched the filter, search returned zero hits). // Action: the model should try a broader filter, drop one of // the predicates, or invoke a different tool to find candidate // entities. Distinct from ToolErrorNotFound — empty results // from a well-formed query is not an error. HintEmpty ToolResultHint = "empty" // HintSyntaxError means the tool's query-language parser // rejected the request. Distinct from ToolErrorInvalidArgs — // InvalidArgs is the AGENT's arguments failing JSON-schema // validation at the framework boundary; SyntaxError is the // TOOL's deeper parse of the argument content (e.g. the // graph-query DSL itself rejecting a malformed expression). // Action: the model should call an introspect/help facility // on the tool before retrying with the same shape. HintSyntaxError ToolResultHint = "syntax_error" )
type Trajectory ¶
type Trajectory struct {
LoopID string `json:"loop_id"`
StartTime time.Time `json:"start_time"`
EndTime *time.Time `json:"end_time,omitempty"`
Steps []TrajectoryStep `json:"steps"`
Outcome string `json:"outcome,omitempty"`
TotalTokensIn int `json:"total_tokens_in"`
TotalTokensOut int `json:"total_tokens_out"`
Duration int64 `json:"duration"` // milliseconds
}
Trajectory represents the complete execution path of an agentic loop
func NewTrajectory ¶
func NewTrajectory(loopID string) Trajectory
NewTrajectory creates a new Trajectory with initialized values
func (*Trajectory) AddStep ¶
func (t *Trajectory) AddStep(step TrajectoryStep)
AddStep adds a step to the trajectory and updates totals. Compaction steps are excluded from token totals because their TokensIn/Out represent evicted/summarized tokens, not new LLM API consumption. Including them would double-count tokens already tallied by prior model_call steps.
type TrajectoryErrorCategory ¶
type TrajectoryErrorCategory string
TrajectoryErrorCategory is safe bounded metadata; raw errors stay in evidence.
const ( // TrajectoryErrorUnknown and its siblings enumerate bounded error categories. TrajectoryErrorUnknown TrajectoryErrorCategory = "unknown" // TrajectoryErrorModel classifies model errors. TrajectoryErrorModel TrajectoryErrorCategory = "model" // TrajectoryErrorTool classifies tool errors. TrajectoryErrorTool TrajectoryErrorCategory = "tool" // TrajectoryErrorTimeout classifies timeouts. TrajectoryErrorTimeout TrajectoryErrorCategory = "timeout" // TrajectoryErrorPermission classifies permission failures. TrajectoryErrorPermission TrajectoryErrorCategory = "permission" // TrajectoryErrorValidation classifies validation failures. TrajectoryErrorValidation TrajectoryErrorCategory = "validation" )
type TrajectoryEvidenceCapture ¶
type TrajectoryEvidenceCapture string
TrajectoryEvidenceCapture reports whether the referenced body was durably verified.
const ( // TrajectoryEvidenceNone and its siblings report durable evidence capture. TrajectoryEvidenceNone TrajectoryEvidenceCapture = "none" // TrajectoryEvidenceStored reports verified durable evidence. TrajectoryEvidenceStored TrajectoryEvidenceCapture = "stored" // TrajectoryEvidenceMissing reports absent durable evidence. TrajectoryEvidenceMissing TrajectoryEvidenceCapture = "missing" )
type TrajectoryEvidenceFailure ¶
type TrajectoryEvidenceFailure string
TrajectoryEvidenceFailure is the closed explanation for a missing body.
const ( TrajectoryEvidenceFailureProviderUnavailable TrajectoryEvidenceFailure = "provider_unavailable" // TrajectoryEvidenceFailureRead reports an evidence read failure. TrajectoryEvidenceFailureRead TrajectoryEvidenceFailure = "read_failed" // TrajectoryEvidenceFailureWrite reports an evidence write failure. TrajectoryEvidenceFailureWrite TrajectoryEvidenceFailure = "write_failed" // TrajectoryEvidenceFailureIntegrity reports an evidence integrity conflict. TrajectoryEvidenceFailureIntegrity TrajectoryEvidenceFailure = "integrity_conflict" )
type TrajectoryEvidenceV1 ¶
type TrajectoryEvidenceV1 struct {
SchemaVersion string `json:"schema_version"`
Kind TrajectoryKind `json:"kind"`
Body json.RawMessage `json:"body"`
}
TrajectoryEvidenceV1 contains the full semantic event body for one observation.
type TrajectoryFactV1 ¶
type TrajectoryFactV1 struct {
SchemaVersion string `json:"schema_version"`
LoopDigest string `json:"loop_digest"`
AttemptID string `json:"attempt_id"`
AttemptOrdinal uint64 `json:"attempt_ordinal"`
Kind TrajectoryKind `json:"kind"`
SourceKind TrajectorySourceKind `json:"source_kind,omitempty"`
SourceCorrelation string `json:"source_correlation,omitempty"`
CausalIteration uint32 `json:"causal_iteration"`
CausalPhase TrajectoryPhase `json:"causal_phase"`
CausalOrdinal uint32 `json:"causal_ordinal"`
ObservedAt time.Time `json:"observed_at"`
ElapsedMS int64 `json:"elapsed_ms,omitempty"`
Status TrajectoryStatus `json:"status,omitempty"`
TokensIn uint64 `json:"tokens_in,omitempty"`
TokensOut uint64 `json:"tokens_out,omitempty"`
MessageCount uint32 `json:"message_count,omitempty"`
ToolCount uint32 `json:"tool_count,omitempty"`
URLCount uint32 `json:"url_count,omitempty"`
ModelPreview string `json:"model_preview,omitempty"`
ProviderPreview string `json:"provider_preview,omitempty"`
ToolPreview string `json:"tool_preview,omitempty"`
CapabilityPreview string `json:"capability_preview,omitempty"`
ErrorCategory TrajectoryErrorCategory `json:"error_category,omitempty"`
EvidenceDigest string `json:"evidence_digest,omitempty"`
EvidenceSize uint64 `json:"evidence_size,omitempty"`
Evidence *message.StorageReference `json:"evidence,omitempty"`
EvidenceCapture TrajectoryEvidenceCapture `json:"evidence_capture"`
EvidenceFailure TrajectoryEvidenceFailure `json:"evidence_failure,omitempty"`
}
TrajectoryFactV1 is one immutable, finite observation. Bodies and arbitrary collections are deliberately absent and live only in TrajectoryEvidenceV1.
func (TrajectoryFactV1) CanonicalBytes ¶
func (f TrajectoryFactV1) CanonicalBytes() ([]byte, error)
CanonicalBytes validates, bounds previews, and deterministically encodes the fact.
type TrajectoryKind ¶
type TrajectoryKind string
TrajectoryKind is the closed v1 observation vocabulary.
const ( // TrajectoryKindLoopStarted and its siblings enumerate v1 observation kinds. TrajectoryKindLoopStarted TrajectoryKind = "loop.started" // TrajectoryKindModelRequested observes a model request. TrajectoryKindModelRequested TrajectoryKind = "model.requested" // TrajectoryKindModelCompleted observes a model completion. TrajectoryKindModelCompleted TrajectoryKind = "model.completed" // TrajectoryKindToolRequested observes a tool request. TrajectoryKindToolRequested TrajectoryKind = "tool.requested" // TrajectoryKindToolCompleted observes a tool completion. TrajectoryKindToolCompleted TrajectoryKind = "tool.completed" // TrajectoryKindContextCompacted observes context compaction. TrajectoryKindContextCompacted TrajectoryKind = "context.compacted" // TrajectoryKindLoopTerminal observes a terminal loop outcome. TrajectoryKindLoopTerminal TrajectoryKind = "loop.terminal" )
type TrajectoryObservedTotals ¶
type TrajectoryObservedTotals struct {
Facts uint64 `json:"facts"`
TokensIn uint64 `json:"tokens_in"`
TokensOut uint64 `json:"tokens_out"`
ElapsedMS int64 `json:"elapsed_ms"`
MessageCount uint64 `json:"message_count"`
ToolCount uint64 `json:"tool_count"`
URLCount uint64 `json:"url_count"`
ModelRequests uint64 `json:"model_requests"`
ModelCompletions uint64 `json:"model_completions"`
ToolRequests uint64 `json:"tool_requests"`
ToolCompletions uint64 `json:"tool_completions"`
ContextCompactions uint64 `json:"context_compactions"`
TerminalObservations uint64 `json:"terminal_observations"`
RequestedObservations uint64 `json:"requested_observations"`
CompletedObservations uint64 `json:"completed_observations"`
FailedObservations uint64 `json:"failed_observations"`
CancelledObservations uint64 `json:"cancelled_observations"`
}
TrajectoryObservedTotals summarizes only the facts returned in one page.
type TrajectoryPage ¶
type TrajectoryPage struct {
SchemaVersion string `json:"schema_version"`
LoopID string `json:"loop_id"`
Coverage string `json:"coverage"`
ObservedTotals TrajectoryObservedTotals `json:"observed_totals"`
TerminalObserved bool `json:"terminal_observed"`
Facts []TrajectoryFactV1 `json:"facts"`
NextCursor string `json:"next_cursor,omitempty"`
}
TrajectoryPage is one observed-only page of immutable fact metadata and durable evidence references. It never carries evidence bodies.
type TrajectoryPhase ¶
type TrajectoryPhase string
TrajectoryPhase gives causal ordering a stable rank independent of timestamps.
const ( // TrajectoryPhaseLoopStart and its siblings define stable causal phases. TrajectoryPhaseLoopStart TrajectoryPhase = "loop_start" // TrajectoryPhaseModelRequest orders model requests. TrajectoryPhaseModelRequest TrajectoryPhase = "model_request" // TrajectoryPhaseModelResult orders model results. TrajectoryPhaseModelResult TrajectoryPhase = "model_result" // TrajectoryPhaseToolRequest orders tool requests. TrajectoryPhaseToolRequest TrajectoryPhase = "tool_request" // TrajectoryPhaseToolResult orders tool results. TrajectoryPhaseToolResult TrajectoryPhase = "tool_result" // TrajectoryPhaseCompaction orders context compaction. TrajectoryPhaseCompaction TrajectoryPhase = "compaction" // TrajectoryPhaseTerminal orders terminal observations. TrajectoryPhaseTerminal TrajectoryPhase = "terminal" )
type TrajectoryQueryRequest ¶
type TrajectoryQueryRequest struct {
LoopID string `json:"loopId"`
Limit int `json:"limit,omitempty"`
Cursor string `json:"cursor,omitempty"`
}
TrajectoryQueryRequest is the strict internal request for one observed fact page. Cursor is opaque to callers and must be returned verbatim.
type TrajectorySourceKind ¶
type TrajectorySourceKind string
TrajectorySourceKind classifies optional correlation without making it identity.
const ( // TrajectorySourceTask and its siblings classify optional source correlation. TrajectorySourceTask TrajectorySourceKind = "task" // TrajectorySourceRequest identifies request correlation. TrajectorySourceRequest TrajectorySourceKind = "request" // TrajectorySourceToolCall identifies tool-call correlation. TrajectorySourceToolCall TrajectorySourceKind = "tool_call" // TrajectorySourceSignal identifies signal correlation. TrajectorySourceSignal TrajectorySourceKind = "signal" // TrajectorySourceMessage identifies message correlation. TrajectorySourceMessage TrajectorySourceKind = "message" // TrajectorySourceCompaction identifies compaction correlation. TrajectorySourceCompaction TrajectorySourceKind = "compaction" )
type TrajectoryStatus ¶
type TrajectoryStatus string
TrajectoryStatus is bounded display metadata, not loop authority.
const ( // TrajectoryStatusRequested and its siblings enumerate observation outcomes. TrajectoryStatusRequested TrajectoryStatus = "requested" // TrajectoryStatusCompleted reports successful completion. TrajectoryStatusCompleted TrajectoryStatus = "completed" // TrajectoryStatusFailed reports failure. TrajectoryStatusFailed TrajectoryStatus = "failed" // TrajectoryStatusCancelled reports cancellation. TrajectoryStatusCancelled TrajectoryStatus = "cancelled" )
type TrajectoryStep ¶
type TrajectoryStep struct {
Timestamp time.Time `json:"timestamp"`
StepType string `json:"step_type"`
RequestID string `json:"request_id,omitempty"`
Prompt string `json:"prompt,omitempty"`
Response string `json:"response,omitempty"`
TokensIn int `json:"tokens_in,omitempty"`
TokensOut int `json:"tokens_out,omitempty"`
ToolName string `json:"tool_name,omitempty"`
ToolArguments map[string]any `json:"tool_arguments,omitempty"`
ToolResult string `json:"tool_result,omitempty"`
Duration int64 `json:"duration"` // milliseconds
Messages []ChatMessage `json:"messages,omitempty"` // Full request messages (detail=full)
ToolCalls []ToolCall `json:"tool_calls,omitempty"` // Assistant tool calls (detail=full)
Model string `json:"model,omitempty"` // Model used
Provider string `json:"provider,omitempty"` // LLM provider (anthropic, openai, etc.)
Capability string `json:"capability,omitempty"` // Role/purpose (coding, planning, reviewing, etc.)
RetryCount int `json:"retry_count,omitempty"` // Number of retries before success
Utilization float64 `json:"utilization,omitempty"` // Context utilization at compaction trigger (0.0-1.0)
// Tool outcome fields, populated for tool_call steps. Emitted as graph
// triples (agent.step.tool_status / error_message / error_category) so
// ops agents can query failure rates by tool and category.
ToolStatus string `json:"tool_status,omitempty"` // "success" | "failed"
ErrorMessage string `json:"error_message,omitempty"` // Raw error text; omitted on success
ErrorCategory string `json:"error_category,omitempty"` // ToolErrorKind string form; omitted on success
// URLsFetched lists external URLs a bash step fetched (curl/wget/httpie),
// derived from the command string (gh#146). Lets citation/audit/governance
// dashboards filter and count external reach independently of generic shell
// activity. Empty/omitted when the step fetched no URL.
URLsFetched []string `json:"url_fetched,omitempty"`
}
TrajectoryStep represents a single step in an agentic trajectory
func (TrajectoryStep) Validate ¶
func (s TrajectoryStep) Validate() error
Validate checks if the TrajectoryStep is valid
type UserMessage ¶
type UserMessage struct {
// Identity
MessageID string `json:"message_id"`
ChannelType string `json:"channel_type"` // cli, slack, discord, web
ChannelID string `json:"channel_id"` // specific conversation/channel
UserID string `json:"user_id"`
// Content
Content string `json:"content"`
Attachments []Attachment `json:"attachments,omitempty"`
// Context
ReplyTo string `json:"reply_to,omitempty"` // loop_id if continuing
ThreadID string `json:"thread_id,omitempty"` // for threaded channels
Metadata map[string]string `json:"metadata,omitempty"` // channel-specific
ContextRequestID string `json:"context_request_id,omitempty"` // links to assembled context
// Resumable-reply context (gh#256). These are distinct from ReplyTo:
// ReplyTo routes the message to a loop to continue; the two below let a
// reply re-enter and resume a *paused run*.
//
// RunID is the bare run anchor the reply should re-attach to. A client
// resuming a paused run (ADR-053) echoes the RunID it held from the pause
// state so the resumed loop carries agent.loop.run / agent.run.entity-id even
// when the prior loop entity was evicted during the pause. Empty for
// non-run submissions.
RunID string `json:"run_id,omitempty"`
// InReplyTo marks this message as a reply to a specific loop's question
// (e.g. an ask_user clarification), stamped onto the resumed loop as the
// agent.loop.reply_to triple so a rule can fire on it. Deliberately
// separate from ReplyTo so ordinary continuations are NOT marked as
// replies. Empty for non-reply submissions.
InReplyTo string `json:"in_reply_to,omitempty"`
// Timing
Timestamp time.Time `json:"timestamp"`
}
UserMessage represents normalized input from any channel (CLI, Slack, Discord, web)
func (*UserMessage) MarshalJSON ¶
func (m *UserMessage) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*UserMessage) RuleFields ¶
func (m *UserMessage) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
Routing and threading only. This is the payload the content rule was written for: Content is what the human typed, and it does not reach a rule. Nor do Attachments, which are files by another name.
Withheld: Content, Attachments, Metadata (channel-specific caller map).
func (*UserMessage) Schema ¶
func (m *UserMessage) Schema() message.Type
Schema implements message.Payload
func (*UserMessage) UnmarshalJSON ¶
func (m *UserMessage) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (UserMessage) Validate ¶
func (m UserMessage) Validate() error
Validate checks if the UserMessage is valid
type UserResponse ¶
type UserResponse struct {
ResponseID string `json:"response_id"`
ChannelType string `json:"channel_type"`
ChannelID string `json:"channel_id"`
UserID string `json:"user_id"` // optional identity of the recipient
// What we're responding to
InReplyTo string `json:"in_reply_to,omitempty"` // message_id or loop_id
ThreadID string `json:"thread_id,omitempty"`
// Content
Type string `json:"type"` // text, status, result, error, prompt, stream
Content string `json:"content"`
// Rich content (optional)
Blocks []ResponseBlock `json:"blocks,omitempty"`
Actions []ResponseAction `json:"actions,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
UserResponse is sent back to users via their channel. ChannelType and ChannelID form the required delivery address; UserID is optional metadata.
func (*UserResponse) MarshalJSON ¶
func (r *UserResponse) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*UserResponse) RuleFields ¶
func (r *UserResponse) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
`type` is the closed response vocabulary (text/status/result/error/prompt/ stream) — the structural fact that says WHAT KIND of response this is without saying what it says.
Withheld: Content (the response text), Blocks (content by another name), and Actions — Actions look structural but carry human-authored Labels, and a rule matching on a button's caption is matching on content.
func (*UserResponse) Schema ¶
func (r *UserResponse) Schema() message.Type
Schema implements message.Payload
func (*UserResponse) UnmarshalJSON ¶
func (r *UserResponse) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (UserResponse) Validate ¶
func (r UserResponse) Validate() error
Validate checks if the UserResponse is valid
type UserSignal ¶
type UserSignal struct {
SignalID string `json:"signal_id"`
Type string `json:"type"` // cancel, pause, resume, approve, reject, feedback, retry
LoopID string `json:"loop_id"`
UserID string `json:"user_id"`
ChannelType string `json:"channel_type"`
ChannelID string `json:"channel_id"`
Payload any `json:"payload,omitempty"` // signal-specific data (e.g., rejection reason)
Timestamp time.Time `json:"timestamp"`
}
UserSignal represents a control signal from user to affect loop execution
func (*UserSignal) MarshalJSON ¶
func (s *UserSignal) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler
func (*UserSignal) RuleFields ¶
func (s *UserSignal) RuleFields() map[string]any
RuleFields implements message.RuleReadable.
Withheld: Payload — signal-specific data typed `any`, which carries a rejection reason or whatever else the sending channel chose. Unclassifiable by construction, so it fails closed. `type` is the closed signal vocabulary (cancel/pause/resume/approve/reject/feedback/retry) and is what a rule branches on.
func (*UserSignal) Schema ¶
func (s *UserSignal) Schema() message.Type
Schema implements message.Payload
func (*UserSignal) UnmarshalJSON ¶
func (s *UserSignal) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler
func (UserSignal) Validate ¶
func (s UserSignal) Validate() error
Validate checks if the UserSignal is valid
Source Files
¶
- agent_lesson_entity.go
- approval.go
- constants.go
- doc.go
- entity_ids.go
- events.go
- exec_policy.go
- loop_execution_entity.go
- model_endpoint_entity.go
- ops_diagnosis_entity.go
- payload_registry.go
- reasoning.go
- rule_fields.go
- state.go
- tools.go
- trajectory.go
- trajectory_evidence.go
- trajectory_fact.go
- trajectory_query.go
- types.go
- url_fetched.go
- user_types.go
- web_observation_entity.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package agentrun implements the AgentRun lifecycle Participant (ADR-053 D1–D6).
|
Package agentrun implements the AgentRun lifecycle Participant (ADR-053 D1–D6). |
|
Package identity provides local DID-based cryptographic identity primitives.
|
Package identity provides local DID-based cryptographic identity primitives. |
|
Package research defines the payload types for the ADR-045 graph search rule chain: Intent, SearchResult, and RouteDecision.
|
Package research defines the payload types for the ADR-045 graph search rule chain: Intent, SearchResult, and RouteDecision. |