Documentation
¶
Overview ¶
Package adapterruntime is a Go SDK for ox adapter authors.
It handles protocol framing, serve-mode dispatch, graceful shutdown, and unknown-method responses so adapter authors can focus on agent-specific logic (session file discovery, transcript parsing, hook installation).
Non-Go adapters implement the same protocol directly against the spec. This package is a convenience layer, not a requirement.
Index ¶
- Constants
- Variables
- func AdapterDisabledByEnv(adapterName string) bool
- func AssistantEntry(ts time.Time, content string) adapterprotocol.RawEntry
- func LogConfirmedMatch(logger *slog.Logger, adapter string, c ConfirmedMatch)
- func Run(cfg Config)
- func RunWithArgs(cfg Config, args []string, stdin io.Reader, stdout io.Writer) error
- func SystemEntry(ts time.Time, content string) adapterprotocol.RawEntry
- func ToolResultEntry(ts time.Time, toolOutput string, isError bool) adapterprotocol.RawEntry
- func ToolResultWithID(ts time.Time, toolOutput string, isError bool, callID string) adapterprotocol.RawEntry
- func ToolUseEntry(ts time.Time, toolName, toolInput string) adapterprotocol.RawEntry
- func ToolUseWithID(ts time.Time, toolName, toolInput, callID string) adapterprotocol.RawEntry
- func UserEntry(ts time.Time, content string) adapterprotocol.RawEntry
- func ValidateRepoRoot(root string) error
- func ValidateSessionID(id string) error
- type CancelSubagentHandler
- type Config
- type ConfirmedMatch
- type EndSessionHandler
- type FileWatcher
- type FindSessionHandler
- type Match
- type Metrics
- type NopMetrics
- type PatternSource
- type ReadFromOffsetHandler
- type ReadFunc
- type Server
- func (s *Server) Context() context.Context
- func (s *Server) OnCancelSubagent(h CancelSubagentHandler)
- func (s *Server) OnEndSession(h EndSessionHandler)
- func (s *Server) OnFindSession(h FindSessionHandler)
- func (s *Server) OnReadFromOffset(h ReadFromOffsetHandler)
- func (s *Server) OnSpawnSubagent(h SpawnSubagentHandler)
- func (s *Server) OnSubagentStatus(h SubagentStatusHandler)
- func (s *Server) Serve()
- func (s *Server) Writer() *Writer
- type SessionStore
- type SpawnSubagentHandler
- type StructuredCheck
- type SubagentStatusHandler
- type TerminalDetector
- type TerminalPattern
- type Writer
- type YAMLCatalog
- type YAMLPatternEntry
- type YAMLStructuredRef
Constants ¶
const DefaultSilenceWindow = 20 * time.Second
DefaultSilenceWindow is the time the detector waits after a pattern match before confirming the session is terminal. If new entries arrive in this window — and they are not themselves matches — the pending match is revoked. Defeats the failure mode where a rendered prior transcript quotes a rate-limit line.
const MaxRawMessageBytes = 512
MaxRawMessageBytes caps how much of the matched line is forwarded to the daemon over the wire. Long lines are truncated to this length to keep telemetry / audit records compact and to avoid leaking secrets that may sit on the same line (the boundary guard in upstream code strips before write, but defense-in-depth).
Variables ¶
var ErrSessionNotFound = fmt.Errorf("session not found")
ErrSessionNotFound is returned when an agent_id has no stored session state.
Functions ¶
func AdapterDisabledByEnv ¶ added in v0.9.0
AdapterDisabledByEnv reports whether OX_DISABLE_TERMINAL_DETECTION names the given adapter. Adapter binaries call this BEFORE constructing a TerminalDetector — when true, skip detector instantiation. Comma-separated, case-insensitive, whitespace-tolerant. Empty env var (the common case) returns false for any adapter.
func AssistantEntry ¶
func AssistantEntry(ts time.Time, content string) adapterprotocol.RawEntry
AssistantEntry creates an assistant message entry.
func LogConfirmedMatch ¶ added in v0.9.0
func LogConfirmedMatch(logger *slog.Logger, adapter string, c ConfirmedMatch)
LogConfirmedMatch is a debug helper for adapter authors who want to see what fired without standing up a metrics implementation.
func Run ¶
func Run(cfg Config)
Run dispatches to the appropriate handler based on os.Args[1]. It reads the subcommand from the CLI arguments, calls the handler, serializes the result as compact JSON to stdout, and exits. For --serve, it enters serve mode (blocking).
func RunWithArgs ¶
RunWithArgs is like Run but accepts explicit args and IO for testing. Returns an error instead of calling os.Exit, making it safe for tests and embedding.
func SystemEntry ¶
func SystemEntry(ts time.Time, content string) adapterprotocol.RawEntry
SystemEntry creates a system/context injection entry.
func ToolResultEntry ¶
ToolResultEntry creates a tool result entry (error output from a tool call).
func ToolResultWithID ¶
func ToolResultWithID(ts time.Time, toolOutput string, isError bool, callID string) adapterprotocol.RawEntry
ToolResultWithID creates a tool result entry with a correlation ID.
func ToolUseEntry ¶
func ToolUseEntry(ts time.Time, toolName, toolInput string) adapterprotocol.RawEntry
ToolUseEntry creates a tool invocation entry (tool call, no result yet).
func ToolUseWithID ¶
func ToolUseWithID(ts time.Time, toolName, toolInput, callID string) adapterprotocol.RawEntry
ToolUseWithID creates a tool invocation entry with a correlation ID. Use the same callID in ToolResultWithID to correlate call and result.
func UserEntry ¶
func UserEntry(ts time.Time, content string) adapterprotocol.RawEntry
UserEntry creates a user message entry.
func ValidateRepoRoot ¶
ValidateRepoRoot checks that a repo root path is non-empty, absolute, and contains a .sageox/ directory. All adapters using this SDK get this validation for free when called via the find-session dispatch path. The .sageox stat uses a 500ms timeout to prevent NFS hangs in hook paths.
func ValidateSessionID ¶
ValidateSessionID checks that a session ID is safe to use in file path construction. It rejects path traversal attempts (../, absolute paths) and path separators that could escape the intended directory.
Types ¶
type CancelSubagentHandler ¶
type CancelSubagentHandler func(ctx context.Context, p adapterprotocol.CancelSubagentParams) (*adapterprotocol.CancelSubagentResult, error)
CancelSubagentHandler handles cancel-subagent requests.
type Config ¶
type Config struct {
Info func() (*adapterprotocol.InfoResponse, error)
Detect func() (*adapterprotocol.DetectResponse, error)
InstallHooks func(adapterprotocol.HookParams) (*adapterprotocol.InstallHooksResponse, error)
CheckHooks func(adapterprotocol.HookParams) (*adapterprotocol.CheckHooksResponse, error)
UninstallHooks func(adapterprotocol.HookParams) (*adapterprotocol.UninstallHooksResponse, error)
Read func(adapterprotocol.ReadParams) (*adapterprotocol.ReadResult, error)
ReadMetadata func(adapterprotocol.ReadParams) (*adapterprotocol.ReadMetadataResult, error)
Diagnose func(adapterprotocol.DiagnoseParams) (*adapterprotocol.DiagnoseResult, error)
FindSession func(adapterprotocol.FindSessionParams) (*adapterprotocol.FindSessionResult, error)
ReadFromOffset func(adapterprotocol.ReadFromOffsetParams) (*adapterprotocol.ReadFromOffsetResult, error)
ImportSession func(adapterprotocol.ImportSessionParams) (*adapterprotocol.ImportSessionResult, error)
CapturePrior func(adapterprotocol.CapturePriorParams) (*adapterprotocol.CapturePriorResult, error)
InstallRules func(adapterprotocol.RulesParams) (*adapterprotocol.InstallRulesResponse, error)
CheckRules func(adapterprotocol.RulesParams) (*adapterprotocol.CheckRulesResponse, error)
UninstallRules func(adapterprotocol.RulesParams) (*adapterprotocol.UninstallRulesResponse, error)
InstallCommands func(adapterprotocol.CommandsParams) (*adapterprotocol.InstallCommandsResponse, error)
CheckCommands func(adapterprotocol.CommandsParams) (*adapterprotocol.CheckCommandsResponse, error)
UninstallCommands func(adapterprotocol.CommandsParams) (*adapterprotocol.UninstallCommandsResponse, error)
InstallSkills func(adapterprotocol.SkillsParams) (*adapterprotocol.InstallSkillsResponse, error)
CheckSkills func(adapterprotocol.SkillsParams) (*adapterprotocol.CheckSkillsResponse, error)
UninstallSkills func(adapterprotocol.SkillsParams) (*adapterprotocol.UninstallSkillsResponse, error)
Serve func(*Server)
}
Config holds the handler functions for each adapter subcommand. Nil handlers cause the subcommand to return an error.
type ConfirmedMatch ¶ added in v0.9.0
type ConfirmedMatch struct {
SessionID string
Pattern TerminalPattern
Match Match
EntrySeq int64
DetectedAt time.Time
ConfirmedAt time.Time
}
ConfirmedMatch is what Tick returns when a pending match's silence window has elapsed. Translates 1:1 to a terminal_error event.
func (ConfirmedMatch) ToTerminalErrorData ¶ added in v0.9.0
func (c ConfirmedMatch) ToTerminalErrorData() adapterprotocol.TerminalErrorData
ToTerminalErrorData converts a ConfirmedMatch to the wire payload.
type EndSessionHandler ¶
type EndSessionHandler func(ctx context.Context, p adapterprotocol.EndSessionParams) error
EndSessionHandler handles end-session requests.
type FileWatcher ¶
type FileWatcher struct {
// contains filtered or unexported fields
}
FileWatcher watches session files for changes and pushes entry events. Thread-safe: multiple sessions can be watched concurrently.
func NewFileWatcher ¶
func NewFileWatcher(writer *Writer, readFn ReadFunc) (*FileWatcher, error)
NewFileWatcher creates a watcher that pushes entry events via the writer. readFn is called to read new entries from the session file at a given offset.
func NewFileWatcherWithDetector ¶ added in v0.9.0
func NewFileWatcherWithDetector(writer *Writer, readFn ReadFunc, detector *TerminalDetector) (*FileWatcher, error)
NewFileWatcherWithDetector creates a watcher with an optional terminal-error detector. Passing a nil or empty detector is equivalent to NewFileWatcher.
func (*FileWatcher) Unwatch ¶
func (fw *FileWatcher) Unwatch(agentID string)
Unwatch stops watching for the given agent.
type FindSessionHandler ¶
type FindSessionHandler func(ctx context.Context, p adapterprotocol.FindSessionParams) (*adapterprotocol.FindSessionResult, error)
FindSessionHandler handles find-session requests.
type Match ¶ added in v0.9.0
type Match struct {
PatternID string
Reason string
RawMessage string
ResetsAtRaw string
ResetsAt *time.Time
}
Match is what a TerminalPattern.Check returns when an entry matches. The detector populates additional fields (Source, ConfirmedAt) before emitting the terminal_error event.
type Metrics ¶ added in v0.9.0
type Metrics interface {
PatternHit(adapter, patternID string, source PatternSource, reason string)
PatternConfirmed(adapter, patternID string)
PatternRevoked(adapter, patternID string)
ResetsAtParseFailure(adapter, patternID string)
SilenceWindowObservedMs(adapter, patternID string, observed time.Duration)
}
Metrics is the observability surface for the detector. The watcher (or test harness) injects an implementation. Production wires this to OTLP counters / gauges; the default NopMetrics drops everything.
PatternHit fires for every match found (before silence-window confirmation). PatternConfirmed fires when the silence window elapses without a revoking entry. PatternRevoked fires when a non-matching entry arrives before the window elapses (false positive caught). ResetsAtParseFailure fires when a pattern matched but ParseResetsAt could not produce an absolute timestamp.
type NopMetrics ¶ added in v0.9.0
type NopMetrics struct{}
NopMetrics is a Metrics that discards all events.
func (NopMetrics) PatternConfirmed ¶ added in v0.9.0
func (NopMetrics) PatternConfirmed(string, string)
func (NopMetrics) PatternHit ¶ added in v0.9.0
func (NopMetrics) PatternHit(string, string, PatternSource, string)
func (NopMetrics) PatternRevoked ¶ added in v0.9.0
func (NopMetrics) PatternRevoked(string, string)
func (NopMetrics) ResetsAtParseFailure ¶ added in v0.9.0
func (NopMetrics) ResetsAtParseFailure(string, string)
func (NopMetrics) SilenceWindowObservedMs ¶ added in v0.9.0
func (NopMetrics) SilenceWindowObservedMs(string, string, time.Duration)
type PatternSource ¶ added in v0.9.0
type PatternSource string
PatternSource categorizes how a match was found.
const ( SourceStructured PatternSource = "structured" SourceRegex PatternSource = "regex" SourceExitCode PatternSource = "exit_code" )
type ReadFromOffsetHandler ¶
type ReadFromOffsetHandler func(ctx context.Context, p adapterprotocol.ReadFromOffsetParams) (*adapterprotocol.ReadFromOffsetResult, error)
ReadFromOffsetHandler handles read-from-offset requests.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server manages the serve-mode request/response loop.
func (*Server) OnCancelSubagent ¶
func (s *Server) OnCancelSubagent(h CancelSubagentHandler)
OnCancelSubagent registers the handler for cancel-subagent requests.
func (*Server) OnEndSession ¶
func (s *Server) OnEndSession(h EndSessionHandler)
OnEndSession registers the handler for end-session requests.
func (*Server) OnFindSession ¶
func (s *Server) OnFindSession(h FindSessionHandler)
OnFindSession registers the handler for find-session requests.
func (*Server) OnReadFromOffset ¶
func (s *Server) OnReadFromOffset(h ReadFromOffsetHandler)
OnReadFromOffset registers the handler for read-from-offset requests.
func (*Server) OnSpawnSubagent ¶
func (s *Server) OnSpawnSubagent(h SpawnSubagentHandler)
OnSpawnSubagent registers the handler for spawn-subagent requests.
func (*Server) OnSubagentStatus ¶
func (s *Server) OnSubagentStatus(h SubagentStatusHandler)
OnSubagentStatus registers the handler for subagent-status requests.
type SessionStore ¶
type SessionStore[T any] struct { // contains filtered or unexported fields }
SessionStore is a typed, concurrent-safe store for per-session state. Adapters use it to cache file handles, byte offsets, and other state keyed by agent_id.
func NewSessionStore ¶
func NewSessionStore[T any]() *SessionStore[T]
NewSessionStore creates a new SessionStore.
func (*SessionStore[T]) Delete ¶
func (s *SessionStore[T]) Delete(agentID string) (T, bool)
Delete removes and returns state for an agent.
func (*SessionStore[T]) Get ¶
func (s *SessionStore[T]) Get(agentID string) (T, bool)
Get retrieves state for an agent. Returns false if not found.
func (*SessionStore[T]) Set ¶
func (s *SessionStore[T]) Set(agentID string, state T)
Set stores state for an agent.
type SpawnSubagentHandler ¶
type SpawnSubagentHandler func(ctx context.Context, p adapterprotocol.SpawnSubagentParams) (*adapterprotocol.SpawnSubagentResult, error)
SpawnSubagentHandler handles spawn-subagent requests.
type StructuredCheck ¶ added in v0.9.0
type StructuredCheck func(entry adapterprotocol.RawEntry, raw json.RawMessage) *Match
StructuredCheck inspects a raw line (parsed by the adapter to a RawEntry, plus the original JSON bytes for free-form path lookups). Returns a non-nil Match when the entry matches the structured signal, or nil otherwise. Structured checks are the preferred detection path: they look at vendor-supplied fields (e.g. message.stop_reason) rather than guessing at free-text wording that vendors change quietly.
type SubagentStatusHandler ¶
type SubagentStatusHandler func(ctx context.Context, p adapterprotocol.SubagentStatusParams) (*adapterprotocol.SubagentStatusResult, error)
SubagentStatusHandler handles subagent-status requests.
type TerminalDetector ¶ added in v0.9.0
type TerminalDetector struct {
// contains filtered or unexported fields
}
TerminalDetector evaluates a list of patterns against every entry the watcher pushes, holds matches in a silence-window pending state, and emits confirmed matches via Tick.
Thread-safe: the watcher calls OnBatch from its read goroutine and Tick from a heartbeat goroutine concurrently.
func NewTerminalDetector ¶ added in v0.9.0
func NewTerminalDetector(adapterName string, patterns []TerminalPattern, silenceWindow time.Duration, metrics Metrics) *TerminalDetector
NewTerminalDetector constructs a detector with the given patterns. adapterName is used for metric labeling. Passing nil patterns yields a no-op detector that is safe to call (handy for the disabled case).
func (*TerminalDetector) Enabled ¶ added in v0.9.0
func (d *TerminalDetector) Enabled() bool
Enabled reports whether the detector has any patterns to evaluate. Callers can skip OnBatch/Tick wiring entirely when false.
func (*TerminalDetector) Forget ¶ added in v0.9.0
func (d *TerminalDetector) Forget(sessionID string)
Forget drops any pending state for the given session. The watcher calls this on Unwatch so a re-Watch under the same agentID does not inherit a pending match from a previous session.
func (*TerminalDetector) OnBatch ¶ added in v0.9.0
func (d *TerminalDetector) OnBatch(sessionID string, entries []adapterprotocol.RawEntry, seq int64, rawLines []json.RawMessage)
OnBatch evaluates entries (in order) against the registered patterns. If any entry matches, the match is stored as pending for this session. If a non-matching entry arrives while a match is pending, the pending match is revoked — vendor messages that look like a rate-limit but are followed by more conversational output were a false positive.
rawLines is parallel to entries (same length) carrying the original JSON bytes for structured-pass JSON-path checks. Pass nil for adapters that cannot supply raws; only structured patterns that look at RawEntry fields will fire in that case.
seq is the per-session monotonically increasing batch identifier from the entries event. The first session-batch should be 1, never 0 (0 reserved for "unknown").
func (*TerminalDetector) Tick ¶ added in v0.9.0
func (d *TerminalDetector) Tick(now time.Time) []ConfirmedMatch
Tick checks every pending match and returns the ones whose silence window has elapsed. Caller (typically the watcher heartbeat) emits a terminal_error event for each ConfirmedMatch.
type TerminalPattern ¶ added in v0.9.0
type TerminalPattern struct {
ID string
Reason string // empty means "log + metric, do not finalize"
Source PatternSource
Structured StructuredCheck
Substrings []string // cheap pre-filter for regex patterns; ANY substring must be present before Re is evaluated
Re *regexp.Regexp
Roles []string // for regex patterns: gate to these entry.Role values (default {"system"})
ParseResetsAt func(match []string) (raw string, parsed *time.Time)
}
TerminalPattern declares one detection rule for an adapter. Patterns are evaluated in registration order; the first match wins for a given entry. Structured patterns are always checked before regex patterns on the same entry — see TerminalDetector.checkEntry.
func LoadYAMLPatterns ¶ added in v0.9.0
func LoadYAMLPatterns( data []byte, structuredRegistry map[string]StructuredCheck, parserRegistry map[string]func(match []string) (string, *time.Time), ) ([]TerminalPattern, error)
LoadYAMLPatterns parses a YAML catalog and returns TerminalPattern values. structuredRegistry resolves named structured checks declared via YAMLStructuredRef.Check. parserRegistry resolves named ParseResetsAt entries (e.g. "relative_duration_or_clock"). Returns an error if any regex fails to compile or any named lookup fails — adapter init must surface this rather than silently skipping bad patterns.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer provides thread-safe JSON writing to stdout. Both serve-mode responses and push events share the same pipe. Writes are buffered to a bytes.Buffer and flushed as a single Write call to ensure atomicity on pipes (writes < PIPE_BUF are atomic on POSIX).
func (*Writer) PushEvent ¶
func (w *Writer) PushEvent(evt adapterprotocol.Event)
PushEvent writes an unsolicited event (e.g., file_watcher entries push).
func (*Writer) WriteResponse ¶
func (w *Writer) WriteResponse(resp adapterprotocol.Response)
WriteResponse writes a serve-mode response.
type YAMLCatalog ¶ added in v0.9.0
type YAMLCatalog struct {
Version int `yaml:"version"`
Patterns []YAMLPatternEntry `yaml:"patterns"`
}
YAMLCatalog is the on-disk representation of an adapter's pattern set. Adapters embed a YAML file via go:embed and pass the bytes to LoadYAMLPatterns to get back TerminalPattern values registered with a Go-resident StructuredCheck registry.
type YAMLPatternEntry ¶ added in v0.9.0
type YAMLPatternEntry struct {
ID string `yaml:"id"`
Source PatternSource `yaml:"source"`
Reason string `yaml:"reason"`
Roles []string `yaml:"roles,omitempty"`
Substrings []string `yaml:"substrings,omitempty"`
Re string `yaml:"re,omitempty"`
Structured YAMLStructuredRef `yaml:"structured,omitempty"`
ParseHint string `yaml:"parse_resets_at,omitempty"` // name of a registered ParseResetsAt
}
YAMLPatternEntry is a single pattern in the catalog file. Structured patterns reference a named check function from a registry (string indirection so the YAML stays declarative); regex patterns inline their regex + parser hint.
type YAMLStructuredRef ¶ added in v0.9.0
type YAMLStructuredRef struct {
JSONPath string `yaml:"json_path,omitempty"` // simple dotted path for the bundled equality check
Equals string `yaml:"equals,omitempty"`
Check string `yaml:"check,omitempty"` // named entry in StructuredRegistry (custom checks)
}
YAMLStructuredRef declares which named structured check to bind. Resolved against the catalog's structuredRegistry at load time.