task

package
v0.14.0 Latest Latest
Warning

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

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

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func RenderLine

func RenderLine(a agent.Adapter, line string) string

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

func ResolveCwd(requested, defaultCwd string, allowed []string) (string, error)

ResolveCwd validates and normalizes a requested working directory against the configured default and optional allow-list.

func WorktreeRoot added in v0.12.0

func WorktreeRoot(configured, stateDir string) string

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

type EventSink func(ev agent.Event)

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

func NewManager(maxTasks int) *Manager

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) Cancel

func (m *Manager) Cancel(id string) (Snapshot, error)

Cancel requests cancellation of a running task.

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) Get

func (m *Manager) Get(id string) (*Task, bool)

Get returns a task by id.

func (*Manager) List

func (m *Manager) List() []Snapshot

List returns snapshots of all tasks, newest first.

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

func (m *Manager) RemoveWorktree(ctx context.Context, id string, force bool) (string, error)

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

func (m *Manager) Restore(reg *agent.Registry, foreign *state.Owner) int

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) Running added in v0.11.0

func (m *Manager) Running() int

Running reports how many workers are alive right now.

func (*Manager) SetAudit

func (m *Manager) SetAudit(a *audit.Logger)

SetAudit attaches an audit logger; nil or a disabled logger is fine.

func (*Manager) SetGrants added in v0.11.0

func (m *Manager) SetGrants(g *grants.Store)

SetGrants attaches the store of permissions the user has granted permanently.

func (*Manager) SetMaxConcurrent added in v0.11.0

func (m *Manager) SetMaxConcurrent(n int)

SetMaxConcurrent caps how many workers may run at once; zero disables it.

func (*Manager) SetMaxCostUSD added in v0.11.0

func (m *Manager) SetMaxCostUSD(v float64)

SetMaxCostUSD bounds what a single task may spend; zero disables it.

func (*Manager) SetStore

func (m *Manager) SetStore(s *state.Store)

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

func (m *Manager) SetTaskTimeout(d time.Duration)

SetTaskTimeout sets a per-turn timeout; zero disables it.

func (*Manager) StartTask

func (m *Manager) StartTask(a agent.Adapter, ws Workspace, spec agent.RunSpec, opts Options) (*Task, error)

StartTask creates a task and launches its first turn asynchronously.

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

func (t *Task) BaseCommit() string

BaseCommit reports where the repository stood when this task started.

func (*Task) Output

func (t *Task) Output(since, max int, compact bool) (from, to, total int, text string)

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) Snapshot

func (t *Task) Snapshot() Snapshot

Snapshot returns a thread-safe view of the task.

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.

func (*Task) Workspace added in v0.12.0

func (t *Task) Workspace() Workspace

Workspace reports where this task's worker runs.

type TurnInfo

type TurnInfo struct {
	Prompt    string
	StartLine int
	StartedAt time.Time
}

TurnInfo records one prompt sent within a task.

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 At added in v0.12.0

func At(dir string) Workspace

At returns a plain workspace: the worker runs directly in dir.

func NewWorktree added in v0.12.0

func NewWorktree(ctx context.Context, cwd, root string) (Workspace, error)

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.

func (Workspace) Isolated added in v0.12.0

func (w Workspace) Isolated() bool

Isolated reports whether this workspace is a worktree of its own rather than the caller's directory.

Jump to

Keyboard shortcuts

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