Documentation
¶
Overview ¶
Package runtime implements the agent execution loop (Runtime.Execute), Session Manager, and Context Manager with the discovery/operation tool split (TAD §11.1) and ReAct/Plan-and-Execute mode switching (TAD §11.2).
See TAD §3.3, §11 and PRD §27 for the full specification. Implemented in Phase 8.
Package runtime implements the Agent Runtime: the Execute loop (TAD §3.3 / PRD §27.2), Session Manager, Context Manager (TAD §11.1 discovery/operation split, §11.2 planning-mode classification), and the Agent Executor that routes every tool call through the same Document and Workflow Engine entry points the API layer uses — never a separate agent execution path (PRD §23.1) — with audit.WithAgent set unconditionally so agent-initiated writes are always flagged via_agent=true (TAD §12.2, §13.3).
Index ¶
- Constants
- type ApprovalDetails
- type ApprovalGateway
- type ApprovalPayload
- type ApprovalResponse
- type Event
- type ExecuteOption
- type Options
- type Response
- type Runtime
- func (r *Runtime) Execute(ctx context.Context, userMessage string, opts ...ExecuteOption) (*Response, error)
- func (r *Runtime) NewSession(id auth.Identity) *Session
- func (r *Runtime) RegisterTool(t toolreg.Tool)
- func (r *Runtime) RemoveSession(id string)
- func (r *Runtime) Session(id string) *Session
- type Session
- type SessionManager
- type Sink
Constants ¶
const ( EventToken = "token" EventToolStart = "tool_start" EventToolEnd = "tool_end" EventApprovalRequired = "approval_required" EventPlan = "plan" )
Event types emitted to a Sink. Their JSON shapes on the WebSocket wire are fixed by TAD §6.2 (token/tool_start/tool_end/approval_required).
const DefaultSessionTTL = 30 * time.Minute
DefaultSessionTTL is the inactivity timeout applied when Options.SessionTTL is zero: an agent session untouched for this long is evicted, bounding the SessionManager's memory to live conversations (REVIEW-2026-08-12 finding 3).
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ApprovalDetails ¶
type ApprovalDetails struct {
DocType string `json:"doctype"`
Action string `json:"action"`
Payload map[string]any `json:"payload"`
PolicyReason string `json:"policy_reason"`
}
ApprovalDetails is the per-approval detail block (TAD §12.3).
type ApprovalGateway ¶
type ApprovalGateway interface {
RequestApproval(ctx context.Context, req ApprovalPayload) (ApprovalResponse, error)
}
ApprovalGateway performs the human-in-the-loop round trip. The WebSocket endpoint implements it by sending approval_required and blocking on the matching approval_response; the CLI stub prompts the terminal. Tests use a scripted implementation.
type ApprovalPayload ¶
type ApprovalPayload struct {
ActionID string `json:"action_id"`
Details ApprovalDetails `json:"details"`
}
ApprovalPayload is the extended approval_required payload of TAD §12.3: the §6.2 shape plus policy_reason so the UI can render branch-specific copy.
type ApprovalResponse ¶
ApprovalResponse is the human's answer to an approval_required round trip. Payload is non-nil when the human chose Modify (PRD §38.2) with corrected arguments; the executor substitutes them before executing.
type Event ¶
type Event struct {
Type string `json:"type"`
Content string `json:"content,omitempty"`
Tool string `json:"tool,omitempty"`
Success bool `json:"success,omitempty"`
Approval *ApprovalPayload `json:"approval,omitempty"`
Sender string `json:"sender,omitempty"` // "user" or "assistant"
}
Event is a streaming observation from the runtime, forwarded verbatim by the WebSocket endpoint (TAD §6.2) and the CLI chat stub.
func (Event) MarshalJSON ¶
MarshalJSON flattens an approval_required event to the TAD §6.2 wire shape (action_id and details at top level) instead of the internal nested approval object:
{"type":"approval_required","action_id":"req-123","details":{...}}
type ExecuteOption ¶
type ExecuteOption func(*executeConfig)
ExecuteOption configures a single Runtime.Execute turn (TAD §3.3). It is how a caller supplies a per-turn LLM provider (e.g. a test MockLLM) or approval gateway without constructing a fresh Runtime.
func WithApprovals ¶
func WithApprovals(a ApprovalGateway) ExecuteOption
WithApprovals overrides the Runtime's human-in-the-loop gateway for this turn only (TAD §12.3). It takes precedence over the WithProvider auto-wire.
func WithProvider ¶
func WithProvider(p llm.Provider) ExecuteOption
WithProvider overrides the Runtime's configured LLM provider for this turn only. When the override also implements ApprovalGateway and no explicit WithApprovals is given, it serves as the turn's approval gateway as well — which is what lets orjanda/testing.MockLLM script approval round trips from the same step queue as the tool/text responses (TAD §17, §12.3).
type Options ¶
type Options struct {
// Provider is the LLM backend driving the loop (TAD §2.7).
Provider llm.Provider
// Tools is a compiled ToolRegistry (Compile must already have run).
// If nil, a new registry is built from permEngine/workflow and compiled
// against Registry.
Tools toolreg.ToolRegistry
// PermEngine is required when Tools is nil (to build the registry). When
// set, the Executor also re-checks method/custom tool AllowedRoles at
// execution time through it (TAD §9.2 / §10.4, PRD §25.1).
PermEngine perm.Engine
// Registry is the compiled schema registry.
Registry schema.Registry
// DocEngine is the same Document Engine the REST API layer uses.
DocEngine *document.Engine
// Workflow is the same workflow Engine the API layer uses; nil when no
// workflowed DocTypes exist.
Workflow workflow.Engine
// Safety is the Safety Layer (rate limit, approvals, allowlist, budget).
Safety *safety.Layer
// Sink receives streaming events (may be nil).
Sink Sink
// Approvals resolves human-in-the-loop approvals (may be nil, in which
// case an approval-requiring call is rejected with an observation).
Approvals ApprovalGateway
// Model overrides the provider's default model (empty = provider default).
Model string
// MaxSteps caps tool-call iterations per turn. 0 = default (10).
MaxSteps int
// SystemPrompt overrides the default system message.
SystemPrompt string
// SessionTTL is the inactivity timeout after which a session is evicted
// from the SessionManager (0 = DefaultSessionTTL; see TAD §11.1/§12.1
// continuity across turns and REVIEW-2026-08-12 finding 3).
SessionTTL time.Duration
}
Options wires a Runtime together. Only Provider, Registry, DocEngine, and Safety are required; the rest fall back to safe defaults where possible.
type Response ¶
type Response struct {
// Content is the assistant's final answer text.
Content string
// SessionID identifies the session the turn ran in.
SessionID string
// ToolCalls is the number of tool invocations executed during the turn.
ToolCalls int
}
Response is the result of one Runtime.Execute turn (TAD §2.6).
type Runtime ¶
type Runtime struct {
// contains filtered or unexported fields
}
Runtime is the concrete agent Runtime (TAD §2.6).
func NewRuntime ¶
NewRuntime builds a Runtime from opts. The ToolRegistry (either provided or freshly compiled) must expose the identity-projected tool list; the Safety Layer is wired to the provider's token usage when the provider tracks it.
func (*Runtime) Execute ¶
func (r *Runtime) Execute(ctx context.Context, userMessage string, opts ...ExecuteOption) (*Response, error)
Execute runs one agent turn for the identity on ctx. The session id is read from the context (safety.WithSession); when absent a new session is created and returned in the Response. Per-turn overrides (ExecuteOption) apply to this call only and leave the Runtime untouched.
func (*Runtime) NewSession ¶
NewSession creates a fresh, registered session for an identity.
func (*Runtime) RegisterTool ¶
RegisterTool registers a custom agent tool (TAD §2.6, §10.4). It is re-exported to the runtime so callers can wire tools at runtime construction rather than only via package-level registration.
func (*Runtime) RemoveSession ¶
RemoveSession releases a session immediately (no-op for unknown ids). The WebSocket handler calls it on connection close so the per-connection session does not linger until SessionTTL (REVIEW-2026-08-12 finding 13).
type Session ¶
type Session struct {
// ID is a ULID identifying the session.
ID string
// UserID is the identity the session is bound to; an identity change
// forces a fresh session (isolation).
UserID string
// contains filtered or unexported fields
}
Session carries one agent conversation's state: the message transcript, the DocTypes that have appeared (the input to the TAD §11.1 discovery/operation tool split), and the record count of the most recent list/search result (the input to the TAD §12.1 bulk approval check).
func (*Session) TargetCount ¶
TargetCount returns the record count of the most recent list/search result (TAD §12.1 step 2), or 0 when docType does not match the DocType the count was recorded for. Scoping the count to its own DocType prevents a large list of one Document type from tripping the bulk approval on a later, unrelated read or write (TAD §12.1 step 2 applies the bulk check to the records the current call affects).
func (*Session) Transcript ¶
Transcript returns a copy of the message history.
type SessionManager ¶
type SessionManager struct {
// contains filtered or unexported fields
}
SessionManager owns live sessions keyed by id. Sessions are evicted once they go untouched for the manager's TTL (a zero TTL disables expiry), which bounds the memory an idle or abandoned conversation holds. Eviction runs lazily on New/Get and on explicit EvictExpired sweeps.
func NewSessionManager ¶
func NewSessionManager() *SessionManager
NewSessionManager builds a SessionManager with no TTL (sessions never expire on their own).
func NewSessionManagerWithTTL ¶
func NewSessionManagerWithTTL(ttl time.Duration) *SessionManager
NewSessionManagerWithTTL builds a SessionManager that evicts sessions untouched for ttl. A ttl <= 0 disables expiry.
func (*SessionManager) EvictExpired ¶
func (m *SessionManager) EvictExpired() int
EvictExpired removes every session whose last access predates the TTL and returns how many were evicted. It is called automatically (lazily by New/Get) and is exported so a caller can sweep on a timer without a lookup.
func (*SessionManager) Get ¶
func (m *SessionManager) Get(id string) *Session
Get returns a registered session by id, or nil. A hit touches the session's last-access clock; an expired session is evicted and returns nil.
func (*SessionManager) Len ¶
func (m *SessionManager) Len() int
Len returns the number of live (not yet evicted) sessions (test helper).
func (*SessionManager) New ¶
func (m *SessionManager) New(id auth.Identity) *Session
New creates and registers a session bound to an identity. It opportunistically sweeps expired sessions so the live set stays within one TTL window of the session creation rate.
func (*SessionManager) Remove ¶
func (m *SessionManager) Remove(id string)
Remove deletes a session from the manager by id (no-op when unknown). The WebSocket handler calls it when a connection closes so an abandoned conversation is released immediately instead of lingering for the TTL (REVIEW-2026-08-12 finding 13).
func (*SessionManager) Reset ¶
func (m *SessionManager) Reset()
Reset clears all sessions (test helper).