Documentation
¶
Overview ¶
Package backgroundtask provides a shared lifecycle registry for long-running executions (sub-agents, shell commands, ...) that may outlive the tool call that launched them.
The central type is Manager: a non-generic, in-memory registry that tracks foreground/background/auto-background runs and exposes them via Get/List/ Cancel/Wait/Close. Manager is deliberately non-generic so a single instance can be shared across heterogeneous domains (e.g. agent runs and shell runs) under one unified task-ID space.
What a run actually does is supplied per-call as a WorkFunc passed to Run, which keeps the engine agnostic to what it runs (agents, shell, ...); adapters that produce WorkFunc values live in the consuming packages (subagent, filesystem). A task may carry an output-file path (RunInput.OutputFile) that the launcher writes to; the Manager only records and surfaces the path, never the file.
Manager tracks lifecycle only. Streaming a specific run's events in real time is intentionally out of scope here: the launcher (a domain adapter) already knows the concrete event type, so live streaming, if needed, belongs at that typed layer rather than behind a type-erased registry-wide channel.
Index ¶
- type Config
- type IDGenerator
- type Manager
- func (m *Manager) Cancel(id string) error
- func (m *Manager) Close(ctx context.Context) error
- func (m *Manager) Get(id string) (*Task, bool)
- func (m *Manager) List() []*Task
- func (m *Manager) MarkOutputFileUnreliable(taskID, errMsg string)
- func (m *Manager) Run(ctx context.Context, input *RunInput, work WorkFunc) (*Task, error)
- func (m *Manager) RunStream(ctx context.Context, input *RunInput, work StreamWorkFunc) (*schema.StreamReader[string], error)
- func (m *Manager) Subscribe() <-chan *TaskEvent
- func (m *Manager) Wait(ctx context.Context, id string) (*Task, bool)
- type NoticeInfo
- type RunInput
- type Status
- type StreamWorkFunc
- type Task
- type TaskEvent
- type TaskEventType
- type TaskInfo
- type WorkFunc
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// ForegroundTimeoutMs sets the foreground budget: the time a foreground run is
// allowed to occupy the foreground before its deadline fires.
// When > 0, a foreground run that hasn't completed within this many
// milliseconds reaches its deadline (see ShouldAutoBackground for what happens then).
// When 0, there is no deadline (foreground runs block until completion).
//
// Default: 120000ms (120 seconds).
ForegroundTimeoutMs *int
// ShouldAutoBackground decides, at a foreground run's deadline, whether it may be
// moved to the background (kept running) instead of being canceled. Applications
// can use it to permit long-lived workloads such as servers and watchers while
// timing out commands whose results are no longer useful. The hook receives the
// task, so a host can branch on Task.Type and recover domain parameters from
// Task.Metadata (e.g. the shell command via filesystem.CommandFromTask).
//
// Deciding whether a workload is genuinely long-lived is inherently host- and
// command-specific, so this package ships no built-in policy: the framework
// cannot reliably infer "never exits" from a command string, and a wrong guess
// either kills a useful run or keeps a doomed one. Hosts encode their own rules.
//
// It is consulted ONLY for the auto path — a foreground run that hits its
// deadline. An explicit RunInBackground run always backgrounds immediately,
// regardless of this hook.
//
// When nil (the default), it is treated as always returning false: a run that
// hits its deadline is canceled and reported as timed out, never auto-backgrounded.
ShouldAutoBackground func(ctx context.Context, task *Task) bool
// IDGen, when set, decides the full ID of every task created by this Manager.
// If nil, Manager uses its default task-type-prefixed base62 ID.
//
// IDGen may be called concurrently by concurrent Run / RunStream calls. It
// must return a non-empty ID. The returned ID must be unique among this
// Manager's registered tasks; a duplicate fails task creation.
IDGen IDGenerator
// BackgroundNotice customizes the chunk emitted on a RunStream caller's stream
// when a task starts in the background or is auto-moved there. The Manager owns
// only lifecycle facts (id, type, output file); how a host tells the model to
// retrieve the result is host-specific — one host exposes a task_output tool,
// another points at the output file — so that wording does not belong in this
// type-erased layer.
//
// When nil, defaultBackgroundNotice is used: it announces the background launch
// and, when an output file is reserved, directs the reader to Read that path for
// interim output.
//
// The ctx passed to the hook is the run's context (detached from the caller's
// cancellation, carrying its values); use it only for value lookup, not to gate
// the notice on cancellation.
BackgroundNotice func(ctx context.Context, info NoticeInfo) string
}
Config configures a Manager.
type IDGenerator ¶
IDGenerator returns the complete ID for a new task.
The generator sees the run input before the task is registered and may return a business-side identifier. Manager does not add the task-type prefix when IDGen is configured; callers that want one should include it in the returned ID.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager is a non-generic, in-memory registry that owns the lifecycle of managed executions: creation, foreground/background/auto-background switching, cancellation and terminal-state tracking.
It is intentionally execution-agnostic: it does not know whether a task is an agent or a shell command. Callers launch work via the free function Run, passing a WorkFunc that performs the actual execution. A single Manager can therefore be shared across multiple domains under one task-ID space.
func New ¶
New creates a new Manager. By default, the foreground budget is 120 seconds; set Config.ForegroundTimeoutMs to 0 to remove the deadline (foreground runs block until completion). What happens when the budget is reached is governed by Config.ShouldAutoBackground (default: cancel the run and report it timed out).
func (*Manager) Cancel ¶
Cancel stops a running task. The run's context is canceled and the task transitions to StatusCanceled. Returns an error if the task does not exist or is not running.
func (*Manager) Close ¶
Close performs graceful shutdown. It waits for all running tasks to complete (up to the ctx deadline), then cancels any remaining running tasks. After Close returns, Run will return an error.
func (*Manager) Get ¶
Get returns the current state of a task by ID. Returns (nil, false) if the task does not exist.
func (*Manager) MarkOutputFileUnreliable ¶
MarkOutputFileUnreliable records that a write to the task's output file failed, so the file is known to be incomplete. The launcher that owns writing calls it (with the task id it receives via WorkFunc's TaskInfo) when an append to the file errors.
It sets Task.OutputFileErr on the task with the given id, so consumers stop trusting the partial file. The first failure wins: a later call does not overwrite an existing message, since once the file has a gap it is unreliable regardless of what later writes do. An empty id, an unknown id, or an already-marked task is a no-op, so callers may invoke it unconditionally on write error.
func (*Manager) Run ¶
Run executes work as a managed task on m.
The execution mode depends on input.RunInBackground and the effective foreground budget (input.ForegroundTimeoutMs if set, else the Manager's configured default):
- Foreground (RunInBackground=false, budget<=0): blocks until completion
- Background (RunInBackground=true): returns immediately with StatusRunning
- Deadline (budget>0): runs in foreground up to the budget, then — if still running — consults the Manager's ShouldAutoBackground hook. If it permits, the run is moved to the background (kept running) and Run returns StatusRunning. Otherwise the run is canceled and reported as timed out (StatusFailed).
All runs are tracked in Manager state and visible via Get/List.
func (*Manager) RunStream ¶
func (m *Manager) RunStream(ctx context.Context, input *RunInput, work StreamWorkFunc) (*schema.StreamReader[string], error)
RunStream executes streaming work as a managed task, returning a stream of output chunks to consume in real time.
It mirrors Run's lifecycle (tracking, foreground budget, auto-background) but preserves streaming for the foreground phase:
- Foreground completion: every chunk is forwarded live, then the stream closes.
- Auto-background at the deadline: chunks forwarded so far are kept; the Manager appends a single notice chunk (task id + output file) and closes the caller's stream, while the work keeps running in the background — its remaining output is drained into the task's Result/OutputFile.
- Explicit background (input.RunInBackground): the work runs detached from the start, so no execution chunks reach the caller; the stream carries only the background notice and then closes.
The returned reader is always non-nil on a nil error. The Manager is the sole writer of that stream, so there is never a write race with the work.
func (*Manager) Subscribe ¶
Subscribe returns a channel that receives TaskEvent values whenever the Manager changes a task's lifecycle state.
The stream is forward-only: events generated before the first Subscribe call are not replayed (use Get/List to inspect current state). Multiple calls return the same shared stream, and Close closes it after buffered events are drained. The returned Task values are snapshots; mutating them does not mutate the Manager's registry.
func (*Manager) Wait ¶
Wait blocks until the task with the given id reaches a terminal state, or until ctx is canceled, and returns the task's current snapshot together with whether it actually reached a terminal state. Callers bound the wait with ctx (e.g. context.WithTimeout).
Return values:
- (nil, false): no task with this id exists.
- (task, true): the task reached a terminal state (task.Status is terminal).
- (task, false): ctx was canceled/timed out first; task is the latest (still-running) snapshot.
The wait is per-task: it selects on the task's own done channel rather than the shared condition, so it neither holds m.mu while waiting nor is woken when other tasks finish.
type NoticeInfo ¶
type NoticeInfo struct {
// Task is a snapshot of the task at the moment the notice is emitted, carrying
// ID, Type, and OutputFile. Nil only if the task vanished mid-emit (not expected).
Task *Task
// AutoBackgrounded is false when the run was launched directly in the background
// (RunInBackground), and true when a foreground run was auto-moved to the
// background at its deadline because the ShouldAutoBackground hook permitted it
// (a deadline the hook declines becomes a timeout failure, which never reaches
// this notice). The true case is the same transition reported to subscribers as
// TaskEventBackgrounded.
AutoBackgrounded bool
}
NoticeInfo carries the lifecycle facts a BackgroundNotice hook may use to build the chunk shown when a run goes to the background.
type RunInput ¶
type RunInput struct {
// Description is a short human-readable title for the task, stored in Task.Description.
Description string
// Type is an optional tag for the task (e.g. "bash", "subagent"), stored in
// Task.Type. See Task.Type.
Type string
// ToolUseID is the optional id of the tool call launching this task, stored in
// Task.ToolUseID. See Task.ToolUseID.
ToolUseID string
// RunInBackground controls execution mode: true returns immediately with StatusRunning.
RunInBackground bool
// Metadata is optional caller-supplied data attached to the task's Task.Metadata.
// It is for observers (Get/List, the task_output tool, the host) to correlate or
// label background tasks — e.g. an originating tool-call ID, session, or trace.
// The Manager does not interpret it. It is shallow-copied into the task on creation.
Metadata map[string]any
// OutputFile is the optional path the launcher will write this task's output
// to. When non-empty it is recorded on Task.OutputFile and surfaced in the
// background notice; the Manager itself never writes the file. The launcher
// (a domain adapter) owns writing, so the file may carry interim output while
// the task runs. Empty means the task has no output file.
OutputFile string
// ForegroundTimeoutMs optionally overrides the Manager's foreground budget for
// this run only. When nil, the Manager's configured default applies. When non-nil,
// it bounds how long the run may occupy the foreground before its deadline fires
// (see Config.ShouldAutoBackground for what happens at the deadline). A value <= 0
// removes the deadline for this run (blocks until completion). Ignored when
// RunInBackground is true.
ForegroundTimeoutMs *int
}
RunInput is the execution-agnostic input for Run. Domain-specific parameters (which agent, which command, the prompt) are captured by the WorkFunc closure, not here.
type Status ¶
type Status string
Status represents the lifecycle status of a task.
const ( // StatusRunning indicates the task is currently executing. StatusRunning Status = "running" // StatusCompleted indicates the task finished successfully. StatusCompleted Status = "completed" // StatusFailed indicates the task terminated with an error. StatusFailed Status = "failed" // StatusCanceled indicates the task was stopped by an external request // (Cancel / Close) via context cancellation. StatusCanceled Status = "canceled" )
type StreamWorkFunc ¶
StreamWorkFunc performs a single managed streaming execution. It is the streaming counterpart of WorkFunc: instead of returning the whole result at once, it returns a stream of output chunks. The Manager forwards those chunks to the RunStream caller in real time and, in parallel, accumulates them into the task's final Result (and OutputFile). Chunk semantics (formatting, exit codes) are entirely the caller's concern; the Manager only concatenates.
task behaves exactly as for WorkFunc (see TaskInfo): it carries the task id the work uses to report an output-file write failure.
ctx behaves exactly as for WorkFunc (see WorkFunc): detached from the caller's cancellation, stopped by Cancel/deadline/Close. Work should honor it and close the returned reader when ctx is done.
type Task ¶
type Task struct {
// ID is the unique identifier for this task, generated by Manager.
ID string
// Type is a caller-supplied tag (e.g. "bash", "subagent") identifying what kind
// of work this task runs. The Manager does not interpret it; it lets the
// ShouldAutoBackground hook and observers distinguish domains without parsing
// Description. Empty if the launcher did not set it.
Type string
// ToolUseID is the id of the tool call that launched this task, when known.
// It lets a host correlate a task event back to the originating tool call.
// Empty if the launcher did not set it.
ToolUseID string
// Description is a human-readable summary of what the task does.
Description string
// Status is the current lifecycle status.
Status Status
// Result contains the task output, set when Status is StatusCompleted.
Result string
// OutputFile is the path the task's output is written to, when the launcher
// supplied one via RunInput.OutputFile. Empty otherwise. The Manager only
// records and surfaces this path (in notices and Get/List); it never writes
// the file — the launcher's work owns writing, so it may contain interim
// output while the task is still running. The file outlives the in-memory
// record, so it remains readable after the Manager is rebuilt.
OutputFile string
// OutputFileErr is set by the launcher (via MarkOutputFileUnreliable) when a
// write to OutputFile fails, so the file is known to be incomplete. It is
// empty while the file is trustworthy. When non-empty, neither OutputFile nor
// Result can be treated as the authoritative complete output: the file has a
// gap, and Result is only whatever the worker returned (which may be empty
// while the task is still running, and — depending on the worker — may be a
// partial projection of the file rather than its full content). Consumers
// should report the file's failed state honestly rather than present either
// side as complete. The Manager does not interpret the value beyond emptiness;
// it carries the first failure's message for diagnostics.
OutputFileErr string
// Error contains the error message, set when Status is StatusFailed.
Error string
// RunInBackground indicates whether this task is running (or ran) in the
// background — either launched with RunInBackground, or moved to the background
// after exhausting its foreground budget. It distinguishes background tasks
// from foreground ones when inspecting task state.
RunInBackground bool
// CreatedAt is the time the task was registered.
CreatedAt time.Time
// DoneAt is the time the task reached a terminal state. Nil if still running.
DoneAt *time.Time
// Metadata holds arbitrary extensible fields for future use.
Metadata map[string]any
}
Task represents a single managed execution record.
type TaskEvent ¶
type TaskEvent struct {
// Type is the transition that caused this event.
Type TaskEventType
// Task is the task snapshot immediately after the transition.
Task *Task
}
TaskEvent is a lifecycle event published by Manager.Subscribe.
type TaskEventType ¶
type TaskEventType string
TaskEventType describes the lifecycle transition that caused a task event.
const ( // TaskEventCreated indicates a task was registered in StatusRunning. TaskEventCreated TaskEventType = "created" // TaskEventBackgrounded indicates a foreground task moved to the background. TaskEventBackgrounded TaskEventType = "backgrounded" // TaskEventCompleted indicates a task finished successfully. TaskEventCompleted TaskEventType = "completed" // TaskEventFailed indicates a task finished with an error. TaskEventFailed TaskEventType = "failed" // TaskEventCanceled indicates a task was canceled by Cancel / Close. TaskEventCanceled TaskEventType = "canceled" )
type TaskInfo ¶
type TaskInfo struct {
// ID is the Manager-generated task id. It is the one fact the work cannot
// otherwise obtain: the id is assigned inside createTask, after the work
// closure is already built. The launcher passes it to MarkOutputFileUnreliable
// to report an output-file write failure against this task.
ID string
// Backgrounded is closed when this task moves to background execution: before
// the work starts for an explicit RunInBackground launch, or at the
// auto-background transition (foreground deadline reached and permitted). It
// stays open for a run that completes in the foreground. Work uses it to stop
// side effects that are only valid while the run is foreground — most notably
// forwarding events to the launching turn's live stream, which the turn closes
// once it ends. It is never nil.
Backgrounded <-chan struct{}
}
TaskInfo is a read-only snapshot of the facts the Manager establishes about a task at creation, handed to the WorkFunc when it starts. It is not the live Task record: it carries only identity fixed at creation, never the mutable lifecycle fields (Result/Status/Error/OutputFileErr) the Manager fills later, so work never races on them. The work already holds everything the launcher passed in (Type, OutputFile, Metadata, ...); TaskInfo supplies what only the Manager knows. New fields may be added over time — adding a field is backward compatible, so the WorkFunc signature stays stable.
type WorkFunc ¶
WorkFunc performs a single managed execution. It is supplied by the caller (e.g. a subagent or filesystem adapter); the Manager itself never knows what the work is.
task carries the Manager-assigned facts about this run (see TaskInfo) — most importantly its id, which the work needs to report an output-file write failure via Manager.MarkOutputFileUnreliable.
ctx carries the values of the Run call's context but is detached from its cancellation, so a backgrounded task outlives the turn that launched it. It is canceled when Cancel is invoked for this task, when a foreground deadline or an abandoned foreground wait stops it, or when the Manager is closed. Work should honor it.
The returned result becomes Task.Result; a non-nil err becomes Task.Error and transitions the task to StatusFailed.