runtime

package
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

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

View Source
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).

View Source
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

type ApprovalResponse struct {
	ActionID string
	Approved bool
	Payload  map[string]any
}

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

func (e Event) MarshalJSON() ([]byte, error)

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

func NewRuntime(opts Options) (*Runtime, error)

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

func (r *Runtime) NewSession(id auth.Identity) *Session

NewSession creates a fresh, registered session for an identity.

func (*Runtime) RegisterTool

func (r *Runtime) RegisterTool(t toolreg.Tool)

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

func (r *Runtime) RemoveSession(id string)

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).

func (*Runtime) Session

func (r *Runtime) Session(id string) *Session

Session returns a registered session by id (nil when unknown).

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

func (s *Session) TargetCount(docType string) int

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

func (s *Session) Transcript() []llm.Message

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).

type Sink

type Sink interface {
	Send(Event)
}

Sink receives streaming events. The WebSocket handler and CLI stub both implement it; a nil sink on the Runtime is a no-op.

Jump to

Keyboard shortcuts

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