background

package
v0.20.0 Latest Latest
Warning

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

Go to latest
Published: Sep 15, 2026 License: AGPL-3.0 Imports: 14 Imported by: 0

Documentation

Overview

Package background implements a background task manager for long-running commands and subagent work. Tasks can be started asynchronously, observed via live UI events, waited on by tools, and queried for their final results.

Index

Constants

View Source
const (
	// DefaultExecTimeout is the default timeout for foreground exec calls.
	DefaultExecTimeout int32 = 30
	// MaxExecTimeout is the maximum allowed timeout (10 minutes).
	MaxExecTimeout int32 = 600
	// BackgroundExecTimeout is the timeout for background tasks (30 minutes).
	BackgroundExecTimeout int32 = 1800
	// DefaultWaitTimeout bounds a single wait_until call. The task keeps
	// running afterwards; callers re-wait to keep observing.
	DefaultWaitTimeout = 120 * time.Second
	// DefaultIdleThreshold is how long an exec task's output must stay quiet
	// before a waiter is woken with WaitIdle.
	DefaultIdleThreshold = 20 * time.Second
	// DefaultCleanupInterval is how often the manager prunes old completed tasks.
	DefaultCleanupInterval = time.Hour
	// DefaultTaskRetention is how long completed tasks are retained in memory.
	DefaultTaskRetention = 24 * time.Hour
	// OutputLogDir is the default directory where background task output logs
	// are written.
	OutputLogDir = "/tmp/memoh-bg"
)
View Source
const MaxRunningSpawnTasks = 3

MaxRunningSpawnTasks caps concurrently running background spawn tasks per bot+session to prevent subagent storms across agent runs.

View Source
const SpawnTaskTimeout = 30 * time.Minute

SpawnTaskTimeout is the safety ceiling for a background spawn task, mirroring BackgroundExecTimeout for exec tasks.

Variables

View Source
var ErrManagedOutcomeUnknown = errors.New("managed operation outcome is unknown")

ErrManagedOutcomeUnknown marks a lost execution connection without proof that the underlying process stopped or committed. Callers must reconcile its state.

Functions

This section is empty.

Types

type AdoptResult

type AdoptResult struct {
	Stdout         string
	Stderr         string
	ExitCode       int32
	ExitReceived   bool
	Err            error
	OutputRecorded bool
}

AdoptResult carries the outcome of a command whose execution was started externally (e.g. via ExecStream) and then handed off to the Manager.

ExitReceived distinguishes "the bridge actually sent us an EXIT frame" (so ExitCode is the real value the process returned, even if the gRPC stream errored out afterwards) from "we never saw an EXIT frame, ExitCode is just its zero value". Without this flag, downstream code can't tell "the command finished with exit 0 right before the stream died" from "we have no idea what the exit code was".

type AgentTaskResult

type AgentTaskResult struct {
	AgentID        string
	AgentSessionID string
	Message        string
	ModelID        string
	Provider       string
	Fork           bool
	Status         TaskStatus
	Report         string
	Error          string
}

AgentTaskResult is the terminal output for one managed subagent run.

type ExecFunc

type ExecFunc func(ctx context.Context, command, workDir string, timeout int32) (*bridge.ExecResult, error)

ExecFunc executes a command in a container and returns the result. This is the signature that bridge.Client.Exec satisfies.

type ManagedRun added in v0.20.0

type ManagedRun func(ctx context.Context, log func(stream, chunk string)) error

ManagedRun is the body of a managed task. Text passed to log is appended to the task output and emitted as an output event, exactly like RecordOutput.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager tracks background tasks and emits live task events.

func New

func New(logger *slog.Logger) *Manager

New creates a new background task Manager.

func (*Manager) AgentTaskStopRequested

func (m *Manager) AgentTaskStopRequested(taskID string) bool

AgentTaskStopRequested reports whether Kill has asked a running managed agent to stop. The status intentionally remains running until its runtime publishes the authoritative terminal outcome.

func (*Manager) Cleanup

func (m *Manager) Cleanup(maxAge time.Duration)

Cleanup removes completed tasks older than the given duration.

func (*Manager) CompleteAgentTask

func (m *Manager) CompleteAgentTask(taskID string, result AgentTaskResult)

CompleteAgentTask finalises a managed agent task. Running task cancellation remains nonterminal until this call records the runtime's resolved outcome.

func (*Manager) CompleteSpawnTask

func (m *Manager) CompleteSpawnTask(taskID string, branches []SpawnBranch)

CompleteSpawnTask finalises a spawn task with its branch outcomes and records the join result. The task is completed when every branch completed and failed when any branch failed. Branch outcomes are recorded even for killed tasks.

func (*Manager) CompleteVideoTask

func (m *Manager) CompleteVideoTask(taskID string, status TaskStatus, result map[string]any, errorMessage string)

CompleteVideoTask finalises a video generation task unless it was killed before the provider goroutine returned.

func (*Manager) Get

func (m *Manager) Get(taskID string) *Task

Get returns a task by ID, or nil if not found.

func (*Manager) GetForSession

func (m *Manager) GetForSession(botID, sessionID, taskID string) *Task

GetForSession returns a task by ID only if it belongs to the provided bot+session.

func (*Manager) Kill

func (m *Manager) Kill(taskID string) error

Kill cancels a running background task. A running agent task records the request without publishing a terminal state: its runtime outcome is the single authority that wakes waiters as killed, failed, or completed.

func (*Manager) KillForSession

func (m *Manager) KillForSession(botID, sessionID, taskID string) error

KillForSession cancels a running background task only when it belongs to the provided bot+session.

func (*Manager) ListForSession

func (m *Manager) ListForSession(botID, sessionID string) []*Task

ListForSession returns all tasks for a given bot+session, most recent first.

func (*Manager) ListSnapshotsForSession

func (m *Manager) ListSnapshotsForSession(botID, sessionID string) []TaskSnapshot

ListSnapshotsForSession returns lock-safe snapshots for all tasks in a bot+session, most recent first.

func (*Manager) MarkAgentTaskRunning

func (m *Manager) MarkAgentTaskRunning(parentCtx context.Context, taskID string) (context.Context, bool, error)

MarkAgentTaskRunning transitions a queued managed agent task into running and returns the cancelable run context. If the task was killed while queued, ok is false and no run should start.

func (*Manager) RecordOutput

func (m *Manager) RecordOutput(taskID, stream, chunk string)

RecordOutput appends live output for a running task and emits a UI event.

func (*Manager) RecordVideoTaskProgress

func (m *Manager) RecordVideoTaskProgress(taskID string, result map[string]any, outputLine string) bool

RecordVideoTaskProgress records the latest provider-visible video job state. The result map is merged into the current task result; outputLine is appended to the compact UI tail when non-empty.

func (*Manager) RunningTasksSummary

func (m *Manager) RunningTasksSummary(botID, sessionID string) string

RunningTasksSummary returns a text summary of currently running tasks for a given bot+session. This is injected into the system prompt so the agent knows about ongoing background work.

func (*Manager) SetEventFunc

func (m *Manager) SetEventFunc(fn func(TaskEvent))

SetEventFunc registers a callback for live background task events.

func (*Manager) Spawn

func (m *Manager) Spawn(
	parentCtx context.Context,
	botID, sessionID, command, workDir, description string,
	execFn ExecFunc,
	writeFn WriteFileFunc,
	readFn ReadFileFunc,
) (taskID, outputFile string)

Spawn starts a command in the background. It returns the task ID immediately. The command runs asynchronously and can be observed through task status tools.

execFn should call bridge.Client.Exec (or equivalent). writeFn should call bridge.Client.WriteFile to persist output logs.

func (*Manager) SpawnAdopt

func (m *Manager) SpawnAdopt(
	parentCtx context.Context,
	botID, sessionID, command, workDir, description, outputDir string,
	resultCh <-chan AdoptResult,
	writeFn WriteFileFunc,
) (taskID, outputFile string)

SpawnAdopt registers a background task for a command that is already running externally (e.g. via ExecStream). Instead of re-executing the command, it waits for the result on the provided channel. This enables "flip to background" where a foreground stream is handed off without killing the process.

func (*Manager) SpawnManaged added in v0.20.0

func (m *Manager) SpawnManaged(parentCtx context.Context, botID, sessionID, description string, run ManagedRun) (taskID string)

SpawnManaged runs an in-process job as a background task. It reuses the Task/TaskEvent lifecycle so the UI can show progress and completion.

The job receives a context detached from parentCtx (a finished request must not abort it) and cancelled by Kill. The operation owns its deadline (for dependency scripts this is the confirmed manifest timeout), so an unrelated background exec limit cannot interrupt a valid longer install. A nil error completes the task; a non-nil error (or a panic) fails it and stores the error text on the task.

func (*Manager) StartAgentTask

func (m *Manager) StartAgentTask(parentCtx context.Context, botID, sessionID, agentID, agentSessionID, message, description string, queued bool) (string, context.Context, error)

StartAgentTask registers a managed subagent task. Queued tasks are visible to background task status immediately but do not get a cancelable run context until MarkAgentTaskRunning is called.

func (*Manager) StartCleanupLoop

func (m *Manager) StartCleanupLoop(done <-chan struct{}, interval, maxAge time.Duration)

StartCleanupLoop periodically removes old completed tasks until done is closed.

func (*Manager) StartSpawnTask

func (m *Manager) StartSpawnTask(parentCtx context.Context, botID, sessionID, description string) (string, context.Context, error)

StartSpawnTask registers a background task for a spawn (subagent batch) whose execution is driven by the spawn tool. It returns the task ID and a detached, cancelable context that subagent branches must derive from so Kill can stop in-flight work.

func (*Manager) StartVideoTask

func (m *Manager) StartVideoTask(parentCtx context.Context, botID, sessionID, description string) (string, context.Context, error)

StartVideoTask registers an asynchronous video generation task and returns a detached, cancelable context for the provider polling goroutine.

func (*Manager) WaitForSessionTask

func (m *Manager) WaitForSessionTask(ctx context.Context, botID, sessionID, taskID string, idleThreshold time.Duration) (TaskSnapshot, WaitOutcome, error)

WaitForSessionTask waits until a task reaches a terminal state or needs attention, and reports why the wait ended. Besides completed/failed/killed/ stalled, a running exec task whose output stays quiet for idleThreshold returns WaitIdle — the signal that a server-style command has settled and its ready banner is in the output tail. idleThreshold <= 0 disables idle wake-ups; non-exec kinds (agent, spawn, video) have no output stream, so idle never applies to them.

type ReadFileFunc

type ReadFileFunc func(ctx context.Context, path string) ([]byte, error)

ReadFileFunc reads content from a file in the container.

type SpawnBranch

type SpawnBranch struct {
	Task           string
	ChildSessionID string
	Status         TaskStatus
	Report         string
	Error          string
}

SpawnBranch is the join-record entry for one subagent in a spawn batch. ChildSessionID points at the persisted subagent session so the parent agent can read the full transcript via history tools when needed.

type Task

type Task struct {
	ID             string
	Kind           TaskKind
	BotID          string
	SessionID      string
	Command        string
	Description    string
	AgentID        string
	AgentSessionID string
	AgentMessage   string
	AgentReport    string
	AgentError     string
	AgentModelID   string
	AgentProvider  string
	AgentFork      bool
	WorkDir        string
	Status         TaskStatus
	ExitCode       int32
	OutputFile     string // path inside the workspace where output is being written
	Result         map[string]any
	Error          string
	StartedAt      time.Time
	CompletedAt    time.Time
	// contains filtered or unexported fields
}

Task represents a single background task (a container command execution or a spawn subagent batch, per Kind).

func (*Task) AppendOutput

func (t *Task) AppendOutput(s string)

AppendOutput appends text to the buffered output tail. Only the last maxTailBytes are kept.

func (*Task) Cancel

func (t *Task) Cancel()

Cancel requests cancellation of the task's context.

func (*Task) MarkStalled

func (t *Task) MarkStalled() bool

MarkStalled atomically marks the task as stalled and wakes any waiters.

func (*Task) OutputTail

func (t *Task) OutputTail() string

OutputTail returns the last portion of collected output.

func (*Task) Snapshot

func (t *Task) Snapshot() TaskSnapshot

Snapshot returns a consistent view of the task without exposing its mutex.

type TaskEvent

type TaskEvent struct {
	Event          TaskEventType `json:"event"`
	TaskID         string        `json:"task_id"`
	Kind           TaskKind      `json:"kind,omitempty"`
	BotID          string        `json:"bot_id,omitempty"`
	SessionID      string        `json:"session_id,omitempty"`
	Command        string        `json:"command,omitempty"`
	AgentID        string        `json:"agent_id,omitempty"`
	AgentSessionID string        `json:"agent_session_id,omitempty"`
	Status         TaskStatus    `json:"status,omitempty"`
	Stream         string        `json:"stream,omitempty"`
	Chunk          string        `json:"chunk,omitempty"`
	Tail           string        `json:"tail,omitempty"`
	OutputFile     string        `json:"output_file,omitempty"`
	ExitCode       int32         `json:"exit_code,omitempty"`
	Duration       string        `json:"duration,omitempty"`
	Stalled        bool          `json:"stalled,omitempty"`
}

TaskEvent is emitted for live UI updates. Output events are intentionally lightweight and non-persistent; task snapshots remain the source of truth for tool-visible state.

type TaskEventType

type TaskEventType string

TaskEventType identifies a UI-facing background task event.

const (
	TaskEventQueued    TaskEventType = "queued"
	TaskEventStarted   TaskEventType = "started"
	TaskEventOutput    TaskEventType = "output"
	TaskEventCompleted TaskEventType = "completed"
	TaskEventFailed    TaskEventType = "failed"
	TaskEventKilled    TaskEventType = "killed"
	TaskEventUnknown   TaskEventType = "unknown"
	TaskEventStalled   TaskEventType = "stalled"
)

type TaskKind

type TaskKind string

TaskKind identifies what kind of work a background task tracks.

const (
	// KindExec is a background container command execution.
	KindExec TaskKind = "exec"
	// KindSpawn is a background subagent batch run by the spawn tool.
	KindSpawn TaskKind = "spawn"
	// KindAgent is a single managed subagent task.
	KindAgent TaskKind = "agent"
	// KindVideo is an asynchronous video generation task.
	KindVideo TaskKind = "video"
	// KindDependency is an in-process workspace dependency install or update
	// started by SpawnManaged.
	KindDependency TaskKind = "dependency"
)

type TaskSnapshot

type TaskSnapshot struct {
	TaskID         string
	Kind           TaskKind
	BotID          string
	SessionID      string
	Command        string
	Description    string
	AgentID        string
	AgentSessionID string
	AgentMessage   string
	AgentReport    string
	AgentError     string
	AgentModelID   string
	AgentProvider  string
	AgentFork      bool
	WorkDir        string
	Status         TaskStatus
	ExitCode       int32
	OutputFile     string
	OutputTail     string
	Result         map[string]any
	Error          string
	Branches       []SpawnBranch
	StartedAt      time.Time
	CompletedAt    time.Time
	LastOutputAt   time.Time
	Duration       time.Duration
	Stalled        bool
}

TaskSnapshot is a lock-safe, immutable view of a task for handler/UI code.

type TaskStatus

type TaskStatus string

TaskStatus represents the lifecycle state of a background task.

const (
	TaskQueued    TaskStatus = "queued"
	TaskRunning   TaskStatus = "running"
	TaskCompleted TaskStatus = "completed"
	TaskFailed    TaskStatus = "failed"
	TaskKilled    TaskStatus = "killed"
	// TaskUnknown means supervision ended without proof of process exit.
	TaskUnknown TaskStatus = "unknown"
)

type WaitOutcome

type WaitOutcome string

WaitOutcome explains why a wait on a task returned.

const (
	WaitCompleted WaitOutcome = "completed"
	WaitFailed    WaitOutcome = "failed"
	WaitKilled    WaitOutcome = "killed"
	WaitUnknown   WaitOutcome = "unknown"
	WaitStalled   WaitOutcome = "stalled"
	// WaitIdle means the command is still running but produced no new output
	// for the idle threshold — for server-style commands this usually means
	// startup is done and the ready banner is already in the output tail.
	WaitIdle WaitOutcome = "idle"
	// WaitTimeout is never returned by the manager itself; the tool layer uses
	// it when the caller-supplied wait budget elapses before any other outcome.
	WaitTimeout WaitOutcome = "timeout"
)

type WriteFileFunc

type WriteFileFunc func(ctx context.Context, path string, data []byte) error

WriteFileFunc writes content to a file in the container.

Jump to

Keyboard shortcuts

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