domain

package
v0.181.0 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const (
	SubagentModeHeadless    = "headless"
	SubagentModeInteractive = "interactive"
)

SubagentMode selects how a subagent is surfaced while it runs.

View Source
const EnvSubagentAgentMode = "INFER_SUBAGENT_AGENT_MODE"

EnvSubagentAgentMode names the environment variable the Agent tool sets to carry the parent chat's coding mode (the AgentMode.AllowedlistKey form - "standard"/"plan"/"auto") to a spawned subagent, so it starts in the same mode as its parent. It is absent for top-level `infer chat`/`infer headless` runs, which therefore stay Standard-by-default.

View Source
const EnvSubagentApprovalFile = "INFER_SUBAGENT_APPROVAL_FILE"

EnvSubagentApprovalFile names the environment variable the Agent tool sets on an interactive subagent's `infer chat` so it writes a SubagentApprovalFile JSON to that path whenever it blocks on a tool-approval prompt (and removes it when the prompt resolves). The parent watches this file to surface "subagent is awaiting approval" to the user and relay the decision (ApproveSubagent). Unset for normal `infer chat`, which writes nothing.

View Source
const EnvSubagentHistoryName = "INFER_SUBAGENT_HISTORY_NAME"

EnvSubagentHistoryName names the environment variable the Agent tool sets on an interactive subagent's `infer chat` so it uses its own history file (<configDir>/history/history-<name>) instead of the main agent's history. When unset or empty, the subagent falls back to the main history file; the reserved value SubagentHistoryMemoryOnly selects in-memory-only history.

View Source
const EnvSubagentResultFile = "INFER_SUBAGENT_RESULT_FILE"

EnvSubagentResultFile names the environment variable the Agent tool sets on an interactive subagent's `infer chat` so it writes its last assistant message (as a SubagentResultFile JSON) to that path on each completed turn. The parent reads it to deliver the subagent's real answer - not the tmux pane's chrome - when the subagent finishes. Unset for normal `infer chat`, which writes nothing.

View Source
const SubagentHistoryMemoryOnly = ":memory:"

SubagentHistoryMemoryOnly is the reserved EnvSubagentHistoryName value telling an interactive subagent to keep its input history in memory only (no file). The Agent tool sets it for subagents without a usable label so they don't create a new single-use history file per spawn. sanitizeSlug never yields this value (it contains ':'), so it can't collide with a real slug.

Variables

This section is empty.

Functions

func InheritedAgentMode added in v0.178.1

func InheritedAgentMode() agentdomain.AgentMode

InheritedAgentMode returns the coding mode a subagent should start in, read from EnvSubagentAgentMode. Falls back to standard mode when unset or unparseable.

Types

type A2AClearer

type A2AClearer interface {
	ClearAllAgents()
}

A2AClearer is the one-method projection of the A2A tracker used by conversation clear/switch to discard the A2A context/task graph. The concrete *utils.A2ATaskTrackerImpl (and the BackgroundTaskRegistry that embeds it) satisfies it; consumers that only clear depend on this instead of the whole tracker.

type A2AStateProvider

type A2AStateProvider interface {
	A2APollingState() agentdomain.TaskPollingState
}

A2AStateProvider is an optional BackgroundJob extension implemented by A2A task jobs so the supervisor can surface their live polling state (context/agent/task id and last known remote state) as the single source for the task view and status bar, without the generic JobMeta/TrackedJob carrying A2A-specific fields.

type BackgroundJob

type BackgroundJob interface {
	// Meta returns the job's identity/display snapshot.
	Meta() JobMeta
	// Run blocks until the job reaches a terminal state or ctx is cancelled,
	// emitting intermediate JobSignals via emit, and returns the terminal result.
	// It MUST return promptly once ctx is cancelled.
	Run(ctx context.Context, emit func(JobSignal)) agentdomain.ToolExecutionResult
	// Wind delivers a graceful wind-down (WindWrapUp) or hard stop (WindStop) to a
	// RUNNING job. It must be safe to call from another goroutine while Run is in
	// flight, be idempotent, and a no-op once the job has finished.
	Wind(ctx context.Context, sig WindSignal) error
	// Close tears down the resources the job owns (its per-kind tracker record, an
	// interactive subagent's tmux pane and temp files, ...). The supervisor calls
	// it exactly once when it reaps a finished job after the retention window. It
	// must be idempotent and must not touch the running process (use Wind for that).
	Close()
}

BackgroundJob is one unit of monitorable background work. The supervisor owns the single goroutine that calls Run plus all the lifecycle around it (events, queue notification, tracking, cleanup); a job only has to (a) block until it reaches a terminal state and return the outcome, and (b) honour wind-down/stop signals. This is the seam that lets A2A tasks, shells, and subagents share one fan-in implementation - each kind differs only in how it learns it is done.

type BackgroundShell

type BackgroundShell struct {
	ShellID      string
	Command      string
	Cmd          *exec.Cmd
	StartedAt    time.Time
	CompletedAt  *time.Time
	State        ShellState
	ExitCode     *int
	OutputBuffer OutputRingBuffer
	CancelFunc   context.CancelFunc
	ReadOffset   int64

	ReadersDone <-chan struct{}
}

BackgroundShell represents a command running in the background.

type BackgroundShellService

type BackgroundShellService interface {
	// DetachToBackground moves a running command to background. readersDone, when
	// non-nil, is closed once the caller's pipe readers reach EOF; the supervisor
	// waits on it before reaping so trailing output is not truncated.
	DetachToBackground(ctx context.Context, cmd *exec.Cmd, command string, outputBuffer OutputRingBuffer, readersDone <-chan struct{}) (string, error)

	// GetShellOutput retrieves output from a shell
	GetShellOutput(shellID string, fromOffset int64) (string, int64, ShellState, error)

	// GetShellOutputWithFilter retrieves filtered output from a shell
	GetShellOutputWithFilter(shellID string, fromOffset int64, filterPattern string) (string, int64, ShellState, error)

	// GetShell returns a specific shell by ID
	GetShell(shellID string) *BackgroundShell

	// GetAllShells returns all tracked shells
	GetAllShells() []*BackgroundShell

	// CancelShell cancels a running background shell
	CancelShell(shellID string) error

	// RemoveShell removes a shell from tracking
	RemoveShell(shellID string) error
}

BackgroundShellService defines the interface for managing background shells

type BackgroundTaskRegistry

type BackgroundTaskRegistry interface {
	agentdomain.A2ATaskTracker
	ShellTracker
	SubagentTracker

	// HasPending reports whether *any* background work is still in flight,
	// regardless of type. True when there is at least one A2A task being
	// polled, one running background shell, OR one running HEADLESS subagent.
	// It deliberately excludes interactive subagents so a one-shot `infer headless`
	// does not hang at exit waiting on a user-driven tmux pane.
	HasPending() bool

	// Submit hands a background job to the supervisor, which spawns its monitor
	// goroutine and folds its result back onto the conversation when it finishes.
	// This is the single entry point every kind (A2A task, shell, subagent) uses
	// instead of running its own poller.
	Submit(job BackgroundJob)

	// Snapshot returns the supervisor's view of all live and recently-finished
	// jobs for the task view and status line.
	Snapshot() []TrackedJob

	// CountRunningJobs returns how many supervised jobs are running, optionally
	// filtered to one kind (pass "" for all kinds).
	CountRunningJobs(kind JobKind) int

	// IsJobRunning reports whether the supervised job with the given id is still
	// running. It is the per-id liveness query a tool uses (via the narrow
	// JobLivenessReporter projection) to defer to the supervisor - the single
	// source of truth - instead of racing it with a manual read.
	IsJobRunning(id string) bool

	// WindJob sends a graceful wind-down or hard stop to one supervised job.
	WindJob(id string, sig WindSignal) error
}

BackgroundTaskRegistry is the single tracker that owns *all* in-flight background work an agent session can produce: A2A tasks (long-running work delegated to remote agents) and background bash shells (long-running commands the agent has detached from the foreground). Both are conceptually the same thing - async producers of results that need to land back on the conversation when they finish - so they live behind one type here.

The interface unifies what used to be two separate trackers (A2ATaskTracker and ShellTracker) via composition: depending on what a caller needs, it can use the narrower A2ATaskTracker or ShellTracker interface, or this full BackgroundTaskRegistry to access both plus the HasPending() aggregator method.

type BackgroundTaskService

type BackgroundTaskService interface {
	// GetBackgroundTasks returns all current background polling tasks
	GetBackgroundTasks() []agentdomain.TaskPollingState

	// CancelBackgroundTask cancels a background task by task ID
	CancelBackgroundTask(taskID string) error
}

BackgroundTaskService handles background A2A task operations Only enabled when A2A is enabled - provides task cancellation and retrieval

type JobKind

type JobKind string

JobKind identifies which background-work subsystem produced a job so one supervisor, one tracker, and one task view can treat A2A tasks, background shells, and subagents uniformly while still reporting per-kind counts.

const (
	JobKindA2A      JobKind = "a2a"
	JobKindShell    JobKind = "shell"
	JobKindSubagent JobKind = "subagent"
)

type JobLivenessReporter

type JobLivenessReporter interface {
	IsJobRunning(id string) bool
}

JobLivenessReporter reports whether a supervised background job is still running, by id. It is the narrow projection of BackgroundTaskRegistry a tool uses to tell whether the supervisor is still driving a job it launched - an A2A task being polled, a background shell, or a subagent - so a manual read defers to the supervisor (the single source of truth) instead of racing it.

type JobMeta

type JobMeta struct {
	ID           string
	Kind         JobKind
	Label        string
	Description  string
	Detail       string
	StartedAt    time.Time
	Silent       bool
	HoldsSession bool
}

JobMeta is the identity/display snapshot a background job exposes. The supervisor reads it once at submit (and surfaces it in the task view); it is not on any hot path.

type JobNotifier

type JobNotifier interface {
	Notification(result agentdomain.ToolExecutionResult) string
}

JobNotifier is an optional BackgroundJob extension. A job that implements it formats its own completion-notification body (the text enqueued for the agent to read when it finishes) - e.g. a shell reports its exit code and duration, which a generic tool-result formatter would not. Jobs that do not implement it get the default domain formatting of their ToolExecutionResult.

type JobOutputProvider

type JobOutputProvider interface {
	Output() string
}

JobOutputProvider is an optional BackgroundJob extension. A job that implements it provides its output text for the /tasks detail panel - e.g. a shell's captured stdout/stderr or a subagent's final result. Jobs that do not implement it show no output section in the detail panel.

type JobSignal

type JobSignal struct {
	// Note is a human-facing line surfaced to the UI and, when Enqueue is set,
	// landed on the message queue so the agent reads it on its next turn.
	Note string
	// Enqueue lands Note on the message queue and wakes the agent loop; otherwise
	// Note is a UI-only status update.
	Enqueue bool
	// State is an optional kind-specific status token (e.g. an A2A task state).
	State string
}

JobSignal is an intermediate, non-terminal event a running job emits to the supervisor (an A2A status change, a subagent that became blocked on a tool approval). The terminal outcome is Run's return value, never a signal.

type JobStatus

type JobStatus string

JobStatus is the unified lifecycle state across every background-work kind.

const (
	JobRunning   JobStatus = "running"
	JobCompleted JobStatus = "completed"
	JobFailed    JobStatus = "failed"
)

func (JobStatus) IsTerminal

func (s JobStatus) IsTerminal() bool

IsTerminal reports whether the job has finished (completed or failed).

type JobStopper

type JobStopper interface {
	WindJob(id string, sig WindSignal) error
}

JobStopper ends a supervised background job by id. It is the narrow projection of BackgroundTaskRegistry that CloseSubagent uses to wind down the supervised monitor of the subagent it closes - cancelling the job's Run context so the status-line running-count drops immediately instead of lingering until the pane-watcher next polls (or never, if a killed pane is not observed as gone).

type JobSubmitter

type JobSubmitter interface {
	Submit(job BackgroundJob)
}

JobSubmitter hands a background job to the supervisor. It is the narrow projection of BackgroundTaskRegistry that tools use to submit work without depending on the whole registry surface.

type OutputRingBuffer

type OutputRingBuffer interface {
	Write(p []byte) (n int, err error)
	ReadFrom(offset int64) (string, int64)
	Recent(maxBytes int) string
	TotalWritten() int64
	Size() int
	String() string
	Clear()
}

OutputRingBuffer defines the interface for the circular output buffer.

type PaneObservation

type PaneObservation struct {
	// Harvested is the subagent chat's real last assistant message (from its
	// result file); "" until its turn completes. The ONLY content ever delivered -
	// the pane is never scraped for content (its TUI chrome is noise).
	Harvested string
	// Screen is a snapshot of the pane's current tail, used by the poller to detect
	// idleness by stability: while the subagent works the chat's elapsed-time
	// spinner changes this every poll; at idle it is frozen. The input-box
	// placeholder ("Type your message") is NOT a usable idle signal - it is drawn
	// even mid-turn - so the stability of the whole tail is used instead.
	Screen string
	// Gone means the pane no longer exists (closed).
	Gone bool
	// Dead means the pane's process exited (the pane is kept open by remain-on-exit).
	Dead bool
	// AwaitingApproval means the subagent is blocked on a tool-approval prompt.
	AwaitingApproval bool
	// ApprovalSummary describes the pending tool call (name + args) when awaiting.
	ApprovalSummary string
}

PaneObservation is one probe of an interactive subagent's tmux pane, produced by a pane inspector and consumed by the interactive subagent monitor (interactiveSubagentJob) to decide when a turn completed or an approval is pending.

type RunEvent

type RunEvent struct {
	Line []byte
	Err  error
	Done bool
}

RunEvent is emitted by the scheduler as a job run progresses. Line events carry one raw agent stdout line (valid only for the duration of the callback); the terminal event has Done set, with Err populated on failure.

type RunRecord

type RunRecord struct {
	SessionID  string     `yaml:"session_id" json:"session_id"`
	JobID      string     `yaml:"job_id" json:"job_id"`
	Status     RunStatus  `yaml:"status" json:"status"`
	Error      string     `yaml:"error,omitempty" json:"error,omitempty"`
	StartedAt  time.Time  `yaml:"started_at" json:"started_at"`
	FinishedAt *time.Time `yaml:"finished_at,omitempty" json:"finished_at,omitempty"`
}

RunRecord is the persisted record of one scheduled-job fire. SessionID is both the record key and the conversation ID of the `infer headless` run, so consumers (e.g. the desktop app) can load the full transcript from conversation storage.

type RunStatus

type RunStatus string

RunStatus is the lifecycle state of a single scheduled-job run.

const (
	RunStatusRunning   RunStatus = "running"
	RunStatusCompleted RunStatus = "completed"
	RunStatusFailed    RunStatus = "failed"
)

type ScheduledJob

type ScheduledJob struct {
	ID             string     `yaml:"id" json:"id"`
	Name           string     `yaml:"name,omitempty" json:"name,omitempty"`
	Description    string     `yaml:"description,omitempty" json:"description,omitempty"`
	CronExpression string     `yaml:"cron_expression" json:"cron_expression"`
	Prompt         string     `yaml:"prompt" json:"prompt"`
	Channel        string     `yaml:"channel,omitempty" json:"channel,omitempty"`
	RecipientID    string     `yaml:"recipient_id,omitempty" json:"recipient_id,omitempty"`
	Model          string     `yaml:"model,omitempty" json:"model,omitempty"`
	RunOnce        bool       `yaml:"run_once,omitempty" json:"run_once,omitempty"`
	CreatedAt      time.Time  `yaml:"created_at" json:"created_at"`
	UpdatedAt      time.Time  `yaml:"updated_at" json:"updated_at"`
	LastRun        *time.Time `yaml:"last_run,omitempty" json:"last_run,omitempty"`
	LastError      string     `yaml:"last_error,omitempty" json:"last_error,omitempty"`
}

ScheduledJob describes a task that the LLM has asked the system to run on a cron schedule. Jobs are persisted through the configured storage backend and executed by the scheduler running inside the `infer daemon` process.

Each fire spawns a fresh `infer headless` subprocess with its own session ID - no context is carried between fires. Channel/RecipientID are an optional delivery target: when set, run output is forwarded to that channel; when empty, the run is record-only and its output lives in storage (run record + conversation).

type ShellInfo

type ShellInfo struct {
	ShellID     string
	Command     string
	State       ShellState
	StartedAt   time.Time
	CompletedAt *time.Time
	ExitCode    *int
	OutputSize  int64
	Elapsed     time.Duration
}

ShellInfo provides summary information about a shell for UI display.

func NewShellInfo

func NewShellInfo(shell *BackgroundShell) *ShellInfo

NewShellInfo creates a ShellInfo from a BackgroundShell.

type ShellState

type ShellState string

ShellState represents the state of a background shell.

const (
	ShellStateRunning   ShellState = "running"
	ShellStateCompleted ShellState = "completed"
	ShellStateFailed    ShellState = "failed"
	ShellStateCancelled ShellState = "cancelled"
)

func (ShellState) IsTerminal

func (s ShellState) IsTerminal() bool

IsTerminal returns true if the state is a terminal state (completed, failed, or cancelled).

func (ShellState) String

func (s ShellState) String() string

String returns the string representation of the shell state.

type ShellTracker

type ShellTracker interface {
	// Add adds a new shell to the tracker.
	// Returns an error if max concurrent limit is reached.
	Add(shell *BackgroundShell) error

	// Get retrieves a shell by ID.
	// Returns nil if not found.
	Get(shellID string) *BackgroundShell

	// GetAll returns all tracked shells.
	GetAll() []*BackgroundShell

	// Remove removes a shell from the tracker.
	Remove(shellID string) error

	// CountRunning returns the number of shells in running state.
	CountRunning() int
}

ShellTracker defines the interface for managing background shells.

type SubagentApprovalFile

type SubagentApprovalFile struct {
	Awaiting bool   `json:"awaiting"`
	Summary  string `json:"summary,omitempty"`
}

SubagentApprovalFile is the JSON an interactive subagent's chat writes while it is blocked on a tool-approval prompt. It is an authoritative signal (written the moment the chat blocks, removed when it resolves) so the parent does not have to scrape the pane's TUI to detect a pending approval.

type SubagentResultFile

type SubagentResultFile struct {
	FinalAssistant string `json:"final_assistant"`
	Success        bool   `json:"success"`
	Error          string `json:"error,omitempty"`
	SessionID      string `json:"session_id,omitempty"`
}

SubagentResultFile is the JSON written by `infer headless --result-file` on exit and read back by the Agent tool to harvest a subagent's outcome from a detached (tmux) run whose stdout the parent does not own.

type SubagentState

type SubagentState struct {
	ID          string
	Label       string
	Description string
	Model       string
	Mode        string // SubagentModeHeadless | SubagentModeInteractive
	SessionID   string
	PaneID      string
	Status      SubagentStatus
	StartedAt   time.Time
	CancelFunc  context.CancelFunc
	Silent      bool
}

SubagentState is the data record for one local subagent (an `infer headless` subprocess or tmux pane spawned by the Agent tool) that the subagent control tools (ListSubagents, CloseSubagent, ...) read. Monitoring is owned by the job supervisor (headlessSubagentJob / interactiveSubagentJob), not this struct.

type SubagentStatus

type SubagentStatus string

SubagentStatus represents the lifecycle state of a local subagent.

const (
	SubagentRunning   SubagentStatus = "running"
	SubagentCompleted SubagentStatus = "completed"
	SubagentFailed    SubagentStatus = "failed"
)

type SubagentTracker

type SubagentTracker interface {
	// AddSubagent registers a running subagent. Returns an error if the ID
	// is already tracked.
	AddSubagent(state *SubagentState) error

	// GetSubagent returns a subagent by ID, or nil if not tracked.
	GetSubagent(id string) *SubagentState

	// GetAllSubagents returns all tracked subagents.
	GetAllSubagents() []*SubagentState

	// RemoveSubagent removes a subagent from tracking.
	RemoveSubagent(id string) error

	// CountRunningSubagents returns the number of subagents in the running state.
	CountRunningSubagents() int

	// SetSubagentStatus atomically updates a subagent's status under the
	// tracker's lock. Returns an error if the ID is not tracked.
	SetSubagentStatus(id string, status SubagentStatus) error
}

SubagentTracker tracks local subagents spawned by the Agent tool. It is the third projection of BackgroundTaskRegistry (alongside A2ATaskTracker and ShellTracker); methods are suffixed with "Subagent" to avoid colliding with the shell tracker's same-named surface when embedded together.

type TaskInfo

type TaskInfo struct {
	// ADK Task contains: ID, ContextID, Status (with State), History, Artifacts, Metadata
	Task adk.Task

	// UI-specific fields
	AgentURL    string
	StartedAt   time.Time
	CompletedAt time.Time
}

TaskInfo wraps ADK Task with UI-specific metadata for completed/terminal tasks Used for A2A task retention and display

type TaskRetainer

type TaskRetainer interface {
	RetainedTask(result agentdomain.ToolExecutionResult) (TaskInfo, bool)
}

TaskRetainer is an optional BackgroundJob extension. A job that implements it contributes a TaskInfo to the A2A task-retention view when it reaches a terminal state, so a completed/failed/canceled task stays listed in the task view after its monitor goroutine exits (the supervisor drops it from the live "active" set on finish). ok=false opts out (e.g. a non-terminal-for-retention state such as input-required). Jobs that do not implement it are never retained.

type TaskRetentionService

type TaskRetentionService interface {
	// AddTask adds a terminal task (completed, failed, canceled, etc.) to retention
	AddTask(task TaskInfo)

	// GetTasks returns all retained tasks
	GetTasks() []TaskInfo

	// Clear removes all retained tasks
	Clear()

	// SetMaxRetention updates the maximum retention count
	SetMaxRetention(maxRetention int)

	// GetMaxRetention returns the current maximum retention count
	GetMaxRetention() int
}

TaskRetentionService manages in-memory retention of completed/terminal A2A tasks Only enabled when A2A is enabled - decouples task retention from StateManager

type TitleGenerator

type TitleGenerator interface {
	ProcessPendingTitles(ctx context.Context) error
}

TitleGenerator interface for conversation title generation

type TrackedJob

type TrackedJob struct {
	Meta        JobMeta
	Status      JobStatus
	CompletedAt *time.Time
	Output      string
}

TrackedJob is a point-in-time snapshot of one supervised job for the task view and status line.

type WindSignal

type WindSignal int

WindSignal is the one-directional graceful control signal the supervisor pushes into a running job. WindWrapUp asks it to start finishing (inject a wind-down prompt, SIGTERM, or cancel the remote task); WindStop terminates it now (kill pane, SIGKILL, cancel). Graceful shutdown sends WindWrapUp to all jobs, waits a grace window, then WindStop. The supervisor also uses WindStop as the teardown when it reaps a finished job.

const (
	WindWrapUp WindSignal = iota
	WindStop
)

func (WindSignal) String

func (w WindSignal) String() string

String renders the signal for logs.

Jump to

Keyboard shortcuts

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