Documentation
¶
Overview ¶
Package task runs CLI-agent turns as background jobs and tracks their state.
A Task owns one agent *session*: the first turn starts it, and follow-up turns resume it (so a delegating model can hold a multi-turn conversation with the worker agent). Each turn spawns the agent headless; the process inherits the server's environment, so whatever the host machine can reach (VPN routes, an SSH agent, credentials) is available to the worker with zero extra wiring.
Index ¶
- func RenderLine(a agent.Adapter, line string) string
- func ResolveCwd(requested, defaultCwd string, allowed []string) (string, error)
- func WorktreeRoot(configured, stateDir string) string
- type Approver
- type EventSink
- type Manager
- func (m *Manager) AnswerPermission(taskID, id string, ans PermissionAnswer) (*PermissionRequest, error)
- func (m *Manager) AskPermission(ctx context.Context, taskID, tool, detail, command string, wait time.Duration) PermissionAnswer
- func (m *Manager) Cancel(id string) (Snapshot, error)
- func (m *Manager) Followup(id, prompt string, allowedTools, extraArgs []string, opts Options) (*Task, error)
- func (m *Manager) FollowupStreaming(ctx context.Context, id, prompt string, allowedTools, extraArgs []string, ...) (*Task, bool, error)
- func (m *Manager) Get(id string) (*Task, bool)
- func (m *Manager) List() []Snapshot
- func (m *Manager) PendingPermissions() []PermissionRequest
- func (m *Manager) RemoveWorktree(ctx context.Context, id string, force bool) (string, error)
- func (m *Manager) Restore(reg *agent.Registry, foreign *state.Owner) int
- func (m *Manager) RunTaskStreaming(ctx context.Context, a agent.Adapter, ws Workspace, spec agent.RunSpec, ...) (t *Task, finished bool, err error)
- func (m *Manager) Running() int
- func (m *Manager) SetAudit(a *audit.Logger)
- func (m *Manager) SetGrants(g *grants.Store)
- func (m *Manager) SetMaxConcurrent(n int)
- func (m *Manager) SetMaxCostUSD(v float64)
- func (m *Manager) SetStore(s *state.Store)
- func (m *Manager) SetTaskTimeout(d time.Duration)
- func (m *Manager) StartTask(a agent.Adapter, ws Workspace, spec agent.RunSpec, opts Options) (*Task, error)
- type Options
- type PermissionAnswer
- type PermissionRequest
- type Snapshot
- type Status
- type Task
- func (t *Task) BaseCommit() string
- func (t *Task) Output(since, max int, compact bool) (from, to, total int, text string)
- func (t *Task) Snapshot() Snapshot
- func (t *Task) WatchFrom(ctx context.Context, since int, timeout time.Duration, compact bool) (text string, newSince, total int, status Status, running bool)
- func (t *Task) Workspace() Workspace
- type TurnInfo
- type Workspace
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RenderLine ¶
RenderLine produces the compact, human-facing form of one stored transcript line — the same text agent_get_output and the board show for it.
It is exported so a reader outside the server process (the `logs` command, the local web viewer) renders a transcript identically without keeping a second copy of the rules. A nil adapter means the agent that produced the line is no longer configured here; the raw line is returned rather than nothing.
func ResolveCwd ¶
ResolveCwd validates and normalizes a requested working directory against the configured default and optional allow-list.
func WorktreeRoot ¶ added in v0.12.0
WorktreeRoot is where isolated checkouts live when the operator names no directory: alongside the task records, so everything this server creates on disk is in one place.
Types ¶
type Approver ¶ added in v0.11.0
type Approver interface {
// Grant issues per-run approval for a task. ok is false when the run should
// proceed without it — because the orchestrating client cannot ask, or
// because the operator turned it off. release must be called when the turn
// ends, and revokes the grant.
Grant(taskID string) (configPath, toolName string, release func(), ok bool)
}
Approver supplies the wiring that lets a worker ask a human for permission mid-run, rather than stalling on a prompt nobody is there to answer.
type EventSink ¶
EventSink receives each parsed stdout event of a running turn, in order. It is used by the streaming ("run") tools to forward live progress to the MCP client. It is always called off the task lock, so it may call back into the task safely.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns all tasks.
func NewManager ¶
NewManager builds a task manager retaining up to maxTasks tasks.
func (*Manager) AnswerPermission ¶ added in v0.11.0
func (m *Manager) AnswerPermission(taskID, id string, ans PermissionAnswer) (*PermissionRequest, error)
AnswerPermission releases a parked request. When id is empty it answers the task's current one, which is what a caller relaying a conversation has.
func (*Manager) AskPermission ¶ added in v0.11.0
func (m *Manager) AskPermission(ctx context.Context, taskID, tool, detail, command string, wait time.Duration) PermissionAnswer
AskPermission parks a request against a task and waits for an answer.
It returns the answer, or a refusal when the wait runs out or the task goes away. Every path that is not an explicit yes is a no: nobody said this could run.
func (*Manager) Followup ¶
func (m *Manager) Followup(id, prompt string, allowedTools, extraArgs []string, opts Options) (*Task, error)
Followup resumes a task's session with a new prompt, asynchronously.
func (*Manager) FollowupStreaming ¶
func (m *Manager) FollowupStreaming(ctx context.Context, id, prompt string, allowedTools, extraArgs []string, opts Options) (*Task, bool, error)
FollowupStreaming resumes a finished task's session with a new prompt, waiting on it under the same rules as RunTaskStreaming.
func (*Manager) PendingPermissions ¶ added in v0.11.0
func (m *Manager) PendingPermissions() []PermissionRequest
PendingPermissions lists every parked request, oldest first.
func (*Manager) RemoveWorktree ¶ added in v0.12.0
RemoveWorktree tears down a task's isolated checkout.
It refuses a worktree that still holds uncommitted work unless force is set: that work is the entire product of the task, and it exists nowhere else.
func (*Manager) Restore ¶
Restore rebuilds the tasks left behind by a previous server process.
A record still marked running belonged to a process that is not this one, so its real outcome is unknowable from here: the worker may have finished, or may still be going under the old instance. Calling it "running" would invite agent_watch to block forever on something this process can never see finish, so it becomes StatusOrphaned instead — readable, honest, and clearly not something to wait on.
Restored tasks reattach to their adapter by name, so one that captured a session id can still be resumed with a follow-up. foreign is the previous owner Store.Acquire reported: another server process that is still alive and running tasks of its own. It is passed in rather than read here because Acquire has already overwritten the lock with this process's pid by the time Restore runs — asking the store afterwards returns us.
func (*Manager) RunTaskStreaming ¶
func (m *Manager) RunTaskStreaming(ctx context.Context, a agent.Adapter, ws Workspace, spec agent.RunSpec, opts Options) (t *Task, finished bool, err error)
RunTaskStreaming creates a task and runs its first turn, forwarding each event to opts.Sink as it arrives. It waits for the turn under the rules in runDetached and reports whether it finished; either way the turn keeps running. Used by the streaming "run" tools so the MCP client sees live progress.
func (*Manager) SetGrants ¶ added in v0.11.0
SetGrants attaches the store of permissions the user has granted permanently.
func (*Manager) SetMaxConcurrent ¶ added in v0.11.0
SetMaxConcurrent caps how many workers may run at once; zero disables it.
func (*Manager) SetMaxCostUSD ¶ added in v0.11.0
SetMaxCostUSD bounds what a single task may spend; zero disables it.
func (*Manager) SetStore ¶
SetStore attaches durable storage. Without one the manager behaves exactly as it did before, keeping everything in memory; every persistence call below is a no-op on a nil store.
func (*Manager) SetTaskTimeout ¶
SetTaskTimeout sets a per-turn timeout; zero disables it.
type Options ¶ added in v0.11.0
type Options struct {
// Sink receives each event as it arrives, for callers streaming progress.
Sink EventSink
// Window bounds how long a blocking call waits before handing back a task
// id. Zero means wait until the turn ends or the caller goes away.
Window time.Duration
// Approver, when set, lets the worker ask for permission during the run.
Approver Approver
}
Options are the per-call knobs shared by every way of starting a turn.
type PermissionAnswer ¶ added in v0.11.0
type PermissionAnswer struct {
Allow bool
Remember bool
Message string // shown to the agent when refused
}
PermissionAnswer is what a person decided.
type PermissionRequest ¶ added in v0.11.0
type PermissionRequest struct {
ID string `json:"id"`
TaskID string `json:"task_id"`
Tool string `json:"tool"`
Detail string `json:"detail,omitempty"` // the command, path or URL at stake
Command string `json:"command,omitempty"` // the program being run, for remembering
AskedAt time.Time `json:"asked_at"`
// contains filtered or unexported fields
}
PermissionRequest is one parked question.
func (*PermissionRequest) Age ¶ added in v0.11.0
func (r *PermissionRequest) Age() time.Duration
Age reports how long this request has been waiting.
type Snapshot ¶
type Snapshot struct {
ID string `json:"task_id"`
Agent string `json:"agent"`
Cwd string `json:"cwd"`
Model string `json:"model,omitempty"`
Status Status `json:"status"`
SessionID string `json:"session_id,omitempty"`
Result string `json:"result,omitempty"`
IsError bool `json:"is_error"`
ExitCode *int `json:"exit_code,omitempty"`
Error string `json:"error,omitempty"`
StartedAt string `json:"started_at"`
EndedAt string `json:"ended_at,omitempty"`
TotalLines int `json:"total_output_lines"`
Turns int `json:"turns"`
Prompts []string `json:"prompts,omitempty"`
// Pending is set while the worker is blocked waiting for someone to allow a
// tool call. A task in that state is running and getting nowhere, which is
// indistinguishable from a slow one unless it is said outright.
Pending *PermissionRequest `json:"pending_permission,omitempty"`
// ModelUsed is what the agent reported it actually ran, which is the only
// way to know when the caller requested no particular model.
ModelUsed string `json:"model_used,omitempty"`
// Usage accumulates every turn's accounting. Nil when the agent reported
// none, so a caller can tell "free" from "not measured".
Usage *agent.Usage `json:"usage,omitempty"`
// BaseCommit is where the repository stood when the task started, so its
// changes can still be reviewed after the worker has committed them.
BaseCommit string `json:"base_commit,omitempty"`
// Worktree, Repo and Branch are set only when the task ran isolated in a
// checkout of its own. They are what tells a reader that the work is not in
// the directory they asked about, and where it is instead.
Worktree string `json:"worktree,omitempty"`
Repo string `json:"repo,omitempty"`
Branch string `json:"branch,omitempty"`
}
Snapshot is an immutable view of a Task for serialization.
type Status ¶
type Status string
Status is the lifecycle state of a task's most recent turn.
const ( StatusRunning Status = "running" StatusDone Status = "done" StatusFailed Status = "failed" StatusCanceled Status = "canceled" // StatusOrphaned marks a task restored from disk that a previous server // process was still running. This process cannot watch it, cancel it, or // learn how it ended — the worker may have finished long ago or may still be // going under the old instance. Reporting it as "running" would be a lie // that makes agent_watch block forever. StatusOrphaned Status = "orphaned" )
type Task ¶
type Task struct {
ID string
AgentName string
Cwd string
Model string
// contains filtered or unexported fields
}
Task is a single delegated job. All fields are guarded by mu.
func (*Task) BaseCommit ¶ added in v0.12.0
BaseCommit reports where the repository stood when this task started.
func (*Task) Output ¶
Output returns lines[since:since+max]. since is 0-based; max<=0 means "all". When compact, noisy lines are dropped and each is rendered human-readably; `since`/`total` always index the raw stream so the contract is stable.
func (*Task) WatchFrom ¶
func (t *Task) WatchFrom(ctx context.Context, since int, timeout time.Duration, compact bool) (text string, newSince, total int, status Status, running bool)
WatchFrom blocks until new output appears past `since`, the task finishes, or timeout elapses (or ctx is cancelled) — then returns the new lines and state. It is the primitive for supervised "director" mode: an orchestrator watches a backgrounded task in near-real-time and decides whether to let it continue or cancel it, without busy-polling.
type Workspace ¶ added in v0.12.0
type Workspace struct {
// Path is the directory the worker runs in.
Path string
// Repo and Branch are set only for a worktree: the repository it was cut
// from, and the branch created for it. Repo is what worktree removal has to
// be run from, since the worktree itself is what is being removed.
Repo string
Branch string
}
Workspace is where a task's worker actually runs.
Most of the time it is just the requested directory. When a task asks to be isolated it is a git worktree instead: a checkout of its own, on a branch of its own, sharing the repository's history. That distinction matters as soon as more than one worker is running, because agents edit files — two of them in one checkout overwrite each other and produce a diff neither intended.
func NewWorktree ¶ added in v0.12.0
NewWorktree cuts an isolated checkout of the repository containing cwd.
The worktree is created under root rather than inside the repository, so it never shows up as untracked clutter in the very diff it exists to produce.
It names itself rather than taking the task id, because the worktree has to exist before the task does — the task records where its worker runs, and that is decided here. The name is carried on the snapshot either way.