Documentation
¶
Index ¶
- Variables
- func BuildExecJobSpec(input SessionJobInput, argv []string, interactive bool) *orchestrator.JobSpec
- func BuildInitJobSpec(in InitJobInput) *orchestrator.JobSpec
- func BuildSandboxSpec(spec *orchestrator.JobSpec, rt SandboxRuntimeInfo) (sandbox.Spec, error)
- func BuildSessionJobSpec(input SessionJobInput) *orchestrator.JobSpec
- func ClearWorktreeCleaned(dbtx db.DBTX, taskID string) error
- func CreateJob(dbtx db.DBTX, j *Job) error
- func CreateWorktree(dbtx db.DBTX, w *Worktree) error
- func FindDaemonShutdownAbortedTasks(conn *sql.DB) ([]string, error)
- func GenerateKey() []byte
- func LoadOrCreateKey(path string) ([]byte, error)
- func MarkStaleAwaitingTasksAborted(conn *sql.DB) (int, error)
- func MarkStaleExecutingTasksAborted(conn *sql.DB) (int, error)
- func MarkStaleJobsFailed(dbtx db.DBTX) error
- func MarkWorktreeCleaned(dbtx db.DBTX, taskID string) error
- func PoliciesToSandbox(in map[string]orchestrator.BuiltinPolicy) map[string]sandbox.BuiltinPolicy
- func ReadTranscript(rootDir, runtimeID string) ([]byte, error)
- func ResolveHostCommands(builtins []string, hostCommands map[string]orchestrator.CommandDef, ...) (map[string]orchestrator.CommandDef, error)
- func StatTranscript(rootDir, runtimeID string) (os.FileInfo, error)
- func UpdateJob(dbtx db.DBTX, j *Job) error
- type CommandBroker
- type CreateOpts
- type EnvironmentInput
- type InitJobInput
- type Job
- type JobCompletionResult
- type JobEventSink
- type JobFilter
- type JobRepository
- func (r *JobRepository) CreateJob(job *Job) error
- func (r *JobRepository) GetJob(id string) (*Job, error)
- func (r *JobRepository) ListJobsByTask(taskID string) ([]*Job, error)
- func (r *JobRepository) ListJobsFiltered(filter JobFilter) ([]*Job, error)
- func (r *JobRepository) UpdateJob(job *Job) error
- type JobRuntime
- type JobStatus
- type LocalRuntime
- func (r *LocalRuntime) Attach(ctx context.Context, runtimeID string, req RuntimeAttachRequest) error
- func (r *LocalRuntime) Resize(_ context.Context, runtimeID string, size TerminalSize) error
- func (r *LocalRuntime) Signal(_ context.Context, runtimeID string, sig syscall.Signal) error
- func (r *LocalRuntime) Start(_ context.Context, spec RuntimeStartSpec) (*RuntimeHandle, error)
- func (r *LocalRuntime) Stop(ctx context.Context, runtimeID string) error
- func (r *LocalRuntime) SubscribeRuntime(runtimeID string) ([]byte, <-chan []byte, func(), bool)
- func (r *LocalRuntime) SupportsAttach(runtimeID string) bool
- func (r *LocalRuntime) Wait(ctx context.Context, runtimeID string) (RuntimeExit, error)
- func (r *LocalRuntime) WriteInputRuntime(runtimeID string, data []byte) error
- type OrchestratorAdapter
- type PreparedSandbox
- type ProjectLookup
- type ProxyAllocator
- type Runner
- func (r *Runner) CleanupTaskWindow(taskID string)
- func (r *Runner) CompleteJob(jobID string, result JobCompletionResult)
- func (r *Runner) Dispatch(ctx context.Context, spec *orchestrator.JobSpec, ...) (string, error)
- func (r *Runner) ResizeRuntime(jobID string, size TerminalSize) error
- func (r *Runner) SignalJobRuntime(runtimeID string, sig syscall.Signal)
- func (r *Runner) StopJobRuntime(runtimeID string)
- func (r *Runner) Subscribe(jobID string) (snapshot []byte, ch <-chan []byte, cancel func(), ok bool)
- func (r *Runner) UnregisterJob(jobID string)
- func (r *Runner) WaitForJob(jobID string) <-chan JobCompletionResult
- func (r *Runner) WaitForJobCtx(ctx context.Context, jobID string) (JobCompletionResult, error)
- func (r *Runner) WriteInput(jobID string, data []byte) error
- type RuntimeAttachRequest
- type RuntimeExit
- type RuntimeHandle
- type RuntimeInputWriter
- type RuntimeStartSpec
- type RuntimeSubscriber
- type SandboxPreparer
- type SandboxRuntimeInfo
- type SecretResolver
- type SecretStore
- type SessionJobInput
- type TaskLookup
- type TerminalSize
- type WireConfig
- type WorkspaceLookup
- type Worktree
- type WorktreeManager
- func (m *WorktreeManager) CleanOrphaned(resolve func(taskID, projectID string) (string, string, error)) error
- func (m *WorktreeManager) CleanupForTask(taskID, projectDir, newStatus string) error
- func (m *WorktreeManager) Create(projectDir, projectID, taskID, baseBranch string, opts CreateOpts) (*Worktree, error)
- func (m *WorktreeManager) EnforceHeadOnBaseBranch(projectDir, baseBranch string) error
- func (m *WorktreeManager) EnsureBindingTargets(worktreePath string, bindings []orchestrator.BindMount, projectDir string) error
- func (m *WorktreeManager) Get(taskID string) (*Worktree, error)
- func (m *WorktreeManager) Recreate(projectDir string, taskID string) (*Worktree, error)
- func (m *WorktreeManager) Remove(projectDir, taskID string, deleteBranch bool) error
- func (m *WorktreeManager) SweepChildBranches(projectDir string, taskIDs []string) error
- type WorktreeRepository
Constants ¶
This section is empty.
Variables ¶
var ErrRuntimeUnsupported = errors.New("job runtime operation is not supported")
Functions ¶
func BuildExecJobSpec ¶ added in v0.0.6
func BuildExecJobSpec(input SessionJobInput, argv []string, interactive bool) *orchestrator.JobSpec
BuildExecJobSpec is the shell-harness variant of BuildSessionJobSpec used by `boid exec` to run a user-supplied argv inside the project sandbox. It reuses BuildSessionJobSpec for project trait inheritance and overrides the result:
- Kind = JobKindExec (TUI displays an "exec" badge instead of "session")
- Argv = the user's argv (runner-inner-child hands this to the shell adapter)
- Interactive = caller's tty detection (sessions are always PTY-attached; exec may be piped from a non-TTY stdin)
- DisplayName falls back to argv[0] when the caller leaves it empty
HarnessType in input is ignored and forced to "shell"; argv must be non-empty.
func BuildInitJobSpec ¶ added in v0.0.8
func BuildInitJobSpec(in InitJobInput) *orchestrator.JobSpec
BuildInitJobSpec converts an InitJobInput into a JobSpec suitable for ProfileInit sandbox dispatch. It does not touch broker state, host-command registration, or the task state machine — those are all skipped for init-style jobs (see sandbox_builder.go:257-264 for the ServerSocket guard, and runner.go:183 for the broker-registration ProfileInit guard).
The returned JobSpec is passed to BuildSandboxSpec + NewSandboxPreparer to produce the launch artefacts, then handed to runner-outer via syscall.Exec (foreground mode, same as boid exec).
func BuildSandboxSpec ¶
func BuildSandboxSpec(spec *orchestrator.JobSpec, rt SandboxRuntimeInfo) (sandbox.Spec, error)
BuildSandboxSpec turns a business-level JobSpec and dispatcher-side runtime facts into a primitive sandbox.Spec. It contains no role-aware switch: the mount set and environment are derived purely from JobSpec.Visibility, HostCommands, Instruction and Argv.
func BuildSessionJobSpec ¶ added in v0.0.6
func BuildSessionJobSpec(input SessionJobInput) *orchestrator.JobSpec
BuildSessionJobSpec converts a resolved SessionJobInput into a JobSpec (JobKindSession, adapter-bound HarnessType). The result is fed straight to dispatcher.Runner which builds the sandbox and hands the agent process to adapter.Run().
func ClearWorktreeCleaned ¶
ClearWorktreeCleaned sets cleaned_at to NULL for the given task's worktree.
func FindDaemonShutdownAbortedTasks ¶
FindDaemonShutdownAbortedTasks returns IDs of tasks currently in aborted status whose most recent aborted-transition action carries payload.code == "daemon_shutdown". Use this on daemon startup to auto-reopen tasks that were interrupted by the previous shutdown.
"Most recent" means the latest action with to_status='aborted' for that task (ordered by created_at desc). If a task was aborted by daemon_shutdown and then aborted again later for another reason, the later code wins and the task is NOT returned — matching the intuition that only freshly-shutdown tasks deserve auto-reopen.
func LoadOrCreateKey ¶
LoadOrCreateKey loads the master key from the given path, or creates a new one if it doesn't exist.
func MarkStaleAwaitingTasksAborted ¶ added in v0.0.7
MarkStaleAwaitingTasksAborted does the same for tasks left in "awaiting" status from a previous crash or restart. After a restart no agent is parked in the (purely in-memory) BlockingAskRegistry, so every awaiting task is a zombie with no live agent behind it — reclaim it. It carries the same daemon_shutdown code as the executing path, so the startup auto-reopen sweep (FindDaemonShutdownAbortedTasks) restarts it and the agent re-asks if needed.
func MarkStaleExecutingTasksAborted ¶
MarkStaleExecutingTasksAborted transitions all tasks in "executing" status to "aborted" and records a daemon_shutdown abort action for each. Call this on server startup after MarkStaleJobsFailed. Returns the number of tasks transitioned.
func MarkStaleJobsFailed ¶
MarkStaleJobsFailed marks all running jobs as failed. Call this on server startup to clean up jobs left in running state from a previous crash or restart.
func PoliciesToSandbox ¶
func PoliciesToSandbox(in map[string]orchestrator.BuiltinPolicy) map[string]sandbox.BuiltinPolicy
PoliciesToSandbox converts the orchestrator-owned neutral BuiltinPolicy representation into the sandbox-layer BuiltinPolicy the broker understands. dispatcher is the only layer allowed to bridge both sides.
func ReadTranscript ¶
ReadTranscript reads the transcript.log for the given runtimeID from rootDir. Returns os.ErrNotExist if the transcript file does not exist (e.g. runtime was gc'd).
func ResolveHostCommands ¶
func ResolveHostCommands( builtins []string, hostCommands map[string]orchestrator.CommandDef, projectDir string, lookPath func(string) (string, error), ) (map[string]orchestrator.CommandDef, error)
ResolveHostCommands turns the orchestrator-side host command map (keyed by the user-declared name) into a map keyed by the absolute path that the boid shim will be bind-mounted at inside the sandbox. The absolute path is also written back into each entry's Path so the broker spawns the right binary on the host without a second lookup.
The same map is used both as the broker's policy table and as the source of shim mount targets; sharing a single resolved view guarantees that the `os.Executable()` value the shim sends will match a key the broker holds.
Builtins ("boid", "git") are excluded — they have dedicated mounts and builtin policies elsewhere.
`projectDir` is used to resolve relative paths declared in host_commands.<name>.path. `lookPath` is parameterized for tests; production callers pass exec.LookPath.
func StatTranscript ¶
StatTranscript returns os.FileInfo for the transcript.log of the given runtimeID. Returns os.ErrNotExist if the file does not exist (e.g. runtime was gc'd).
Types ¶
type CommandBroker ¶
type CommandBroker interface {
RegisterCommands(commands map[string]orchestrator.CommandDef, builtinPolicies map[string]sandbox.BuiltinPolicy, ctx sandbox.TokenContext, resolve SecretResolver) string
UnregisterCommandToken(token string)
SocketPath() string
}
CommandBroker is the dispatcher-owned behavior contract for host command brokering. The execution context is the canonical sandbox.TokenContext so adapters do not need to translate between dispatcher- and sandbox-side context shapes.
type CreateOpts ¶
type CreateOpts struct {
// CheckoutBranch, when non-empty, causes Create to check out an existing
// branch directly (git worktree add <path> <branch>) rather than creating
// a new boid/<id8> branch. Used for root tasks (ParentID == "") so the
// worktree HEAD matches task.BaseBranch directly (P2).
CheckoutBranch string
// ForkPoint overrides the start-point used when creating a new boid/<id8>
// branch (CheckoutBranch == ""). Defaults to baseBranch when empty.
// Reserved for P3 (child fork from parent HEAD branch); unused in P2.
ForkPoint string
// BaseBranchForkPoint is the start point used when the requested
// baseBranch does not exist yet (ClassifyBaseBranch case 3) and must
// be created locally. Sourced from ProjectMeta.ForkPoint via
// Visibility.ForkPoint. Empty falls back to refs/remotes/origin/HEAD;
// if that is also unset, Create returns an error.
BaseBranchForkPoint string
}
CreateOpts controls optional worktree creation behaviour.
type EnvironmentInput ¶ added in v0.0.6
type EnvironmentInput struct {
Visibility orchestrator.Visibility
WorkspacePeers map[string]string
BuiltinPolicies map[string]orchestrator.BuiltinPolicy
HostCommands map[string]orchestrator.CommandDef
// Network plumbing. ProxyPort=0 means no proxy (and therefore no egress
// restriction wired by dispatcher). HostGatewayIP is the address agents
// see for the host; combined with ProxyPort it becomes proxy_url.
ProxyPort int
HostGatewayIP string
AllowedDomains []string
// Job category — Kind=Session gates the `session:` block; everything
// else inherits the same layout but without per-session metadata.
Kind orchestrator.JobKind
HarnessType string
DisplayName string
}
EnvironmentInput is the single input bundle for buildEnvironmentYAML. It is derived from JobSpec + dispatcher runtime facts before contextFiles is called. Centralising the inputs in one struct keeps the call sites in BuildSandboxSpec / tests stable as the YAML layout grows new fields.
type InitJobInput ¶ added in v0.0.8
type InitJobInput struct {
// Profile selects the sandbox filesystem layout. Must be sandbox.ProfileInit
// for kit-init / workspace-configure (host root ro-rbind, broker skipped).
Profile sandbox.Profile
// WritableDirs is the list of host directories to bind read-write into the
// sandbox so the agent can write generated yaml files. Each entry must be an
// absolute host path; it is bind-mounted at the same path inside the
// sandbox. The directory must already exist on the host before dispatch
// (caller is responsible for mkdir). Per-file binds are intentionally not
// supported: harness-side file editors do atomic writes (write to
// `<name>.tmp.<pid>.<rand>` in the parent dir, then rename) which would
// fail with EROFS on a single-file IsFile bind and force the skill into
// shell-only writes, breaking harness-agnosticism.
WritableDirs []string
// ReadOnlyBinds is additional host paths to bind read-only into the
// sandbox. Used by workspace-configure to give the agent access to linked
// project directories (package.json / go.mod / hook scripts) without
// granting write access.
ReadOnlyBinds []string
// Argv is the literal program + arguments to exec inside the sandbox.
// For agent harnesses (claude / codex / opencode) the adapter builds its
// own argv from its CLI conventions and may ignore this; it is still
// required so the shell adapter fall-through path works and so the runner
// can record a meaningful command in diagnostics.
Argv []string
// DisplayName is the human-readable label shown in the TUI / Web UI.
DisplayName string
// Env carries additional environment variables to inject into the sandbox
// on top of the standard HOME / PATH / TERM set. Used to pass context like
// BOID_WORKSPACE_SLUG to the skill.
Env map[string]string
// Instruction is the optional bootstrap prompt the agent should pick up
// on launch. Mirrors SessionJobInput.Instruction: when non-empty it is
// delivered through Env (BOID_USER_ANSWER) so the harness adapter
// receives it as the first turn of user input — for ProfileInit jobs
// this is how `boid kit init` / `boid workspace configure` kicks the
// embedded skill ("boid kit init を実行して" etc.) without making the
// user type anything after the harness opens.
Instruction string
// HarnessType selects the agent adapter. Must be one of "claude" /
// "codex" / "opencode" / "shell". Validated by the caller.
HarnessType string
}
InitJobInput carries the resolved data needed to build a sandbox JobSpec for init-style commands (boid kit init, boid workspace configure) that scan the host filesystem and write machine-local yaml files without going through the task state machine or daemon broker.
The shape mirrors SessionJobInput but is distinct by design so exec / session jobs are never accidentally given ProfileInit semantics and vice versa.
type Job ¶
type Job struct {
ID string `json:"id"`
TaskID string `json:"task_id"`
ProjectID string `json:"project_id"`
HandlerID string `json:"handler_id"`
DisplayName string `json:"display_name,omitempty"` // persisted via the jobs.display_name column (migration 0027)
Role string `json:"role"`
RuntimeID string `json:"runtime_id,omitempty"`
Interactive bool `json:"interactive"`
TTY bool `json:"tty"`
Status JobStatus `json:"status"`
ExitCode int `json:"exit_code,omitempty"`
Output string `json:"output,omitempty"`
ExecutionState string `json:"execution_state,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type JobCompletionResult ¶
JobCompletionResult is the result delivered via WaitForJobCtx/CompleteJob.
type JobEventSink ¶
type JobEventSink interface {
JobCreated(taskID, jobID string)
}
JobEventSink lets the runner report job lifecycle events to a subscriber (typically the web SSE hub) without taking a hard dependency on it. All methods are best-effort: implementations should not block or fail the caller — they exist to push UI refresh hints.
type JobFilter ¶
type JobFilter struct {
Status string
Interactive *bool // nil = no filter
TasklessOnly bool // true = only jobs where task_id IS NULL
}
JobFilter specifies optional filters for listing jobs globally.
type JobRepository ¶
type JobRepository struct {
// contains filtered or unexported fields
}
func NewJobRepository ¶
func NewJobRepository(db db.DBTX) *JobRepository
func (*JobRepository) CreateJob ¶
func (r *JobRepository) CreateJob(job *Job) error
func (*JobRepository) ListJobsByTask ¶
func (r *JobRepository) ListJobsByTask(taskID string) ([]*Job, error)
func (*JobRepository) ListJobsFiltered ¶
func (r *JobRepository) ListJobsFiltered(filter JobFilter) ([]*Job, error)
func (*JobRepository) UpdateJob ¶
func (r *JobRepository) UpdateJob(job *Job) error
type JobRuntime ¶
type JobRuntime interface {
Start(ctx context.Context, spec RuntimeStartSpec) (*RuntimeHandle, error)
Attach(ctx context.Context, runtimeID string, req RuntimeAttachRequest) error
Resize(ctx context.Context, runtimeID string, size TerminalSize) error
Wait(ctx context.Context, runtimeID string) (RuntimeExit, error)
Stop(ctx context.Context, runtimeID string) error
// Signal sends a single signal to the runtime's process group without
// any follow-up SIGKILL. Used by NotifyTask to drive an "agent-stop"
// SIGUSR1 to run-agent.py while leaving the runner chain intact: the
// go-native runner subcommands set this signal to SIG_IGN (see
// runner.ignoreStopSignal), which is inherited across execve so pasta and
// the child runners survive while run-agent.py re-installs its own handler.
// Implementations should be no-op when the runtime has already exited.
Signal(ctx context.Context, runtimeID string, sig syscall.Signal) error
}
type LocalRuntime ¶
type LocalRuntime struct {
RootDir string
// contains filtered or unexported fields
}
func (*LocalRuntime) Attach ¶
func (r *LocalRuntime) Attach(ctx context.Context, runtimeID string, req RuntimeAttachRequest) error
func (*LocalRuntime) Resize ¶
func (r *LocalRuntime) Resize(_ context.Context, runtimeID string, size TerminalSize) error
func (*LocalRuntime) Signal ¶
Signal delivers a single signal to the runtime's process group without any SIGKILL follow-up. NotifyTask uses this for SIGUSR1 (agent-stop) — the signal is delivered process-group-wide (kill(-pgid, sig)) and processes configured to ignore it via `trap ” USR1` / SIG_IGN survive unaffected. No-op when the runtime session has already exited.
func (*LocalRuntime) Start ¶
func (r *LocalRuntime) Start(_ context.Context, spec RuntimeStartSpec) (*RuntimeHandle, error)
func (*LocalRuntime) Stop ¶
func (r *LocalRuntime) Stop(ctx context.Context, runtimeID string) error
func (*LocalRuntime) SubscribeRuntime ¶
func (r *LocalRuntime) SubscribeRuntime(runtimeID string) ([]byte, <-chan []byte, func(), bool)
SubscribeRuntime subscribes to live output of the session identified by runtimeID. Returns the current transcript snapshot, a channel of subsequent chunks, a cancel function to unsubscribe, and whether live streaming is available.
func (*LocalRuntime) SupportsAttach ¶
func (r *LocalRuntime) SupportsAttach(runtimeID string) bool
func (*LocalRuntime) Wait ¶
func (r *LocalRuntime) Wait(ctx context.Context, runtimeID string) (RuntimeExit, error)
func (*LocalRuntime) WriteInputRuntime ¶
func (r *LocalRuntime) WriteInputRuntime(runtimeID string, data []byte) error
WriteInputRuntime writes data to the PTY master of the given runtime. Returns nil if the session is not running or has already exited.
type OrchestratorAdapter ¶
type OrchestratorAdapter struct {
// contains filtered or unexported fields
}
OrchestratorAdapter adapts dispatcher execution to orchestrator interfaces.
func NewOrchestratorAdapter ¶
func NewOrchestratorAdapter(dispatcher dispatchBackend, planner *orchestrator.DispatchPlanner) *OrchestratorAdapter
func (*OrchestratorAdapter) ExecuteHook ¶
func (a *OrchestratorAdapter) ExecuteHook(ctx context.Context, event *orchestrator.HookFireEvent) (string, error)
func (*OrchestratorAdapter) WaitForJob ¶
func (a *OrchestratorAdapter) WaitForJob(ctx context.Context, jobID string) (orchestrator.JobCompletion, error)
type PreparedSandbox ¶
PreparedSandbox is the concrete launch artifact returned by a provider. SpecPath is the JSON sandbox spec passed to `boid runner-outer`; StatePath is the runner-state.json diagnostic file (retained on failure). RootDir and StagingDir are populated so the runner can remove them after the sandbox runtime has exited; zero values mean "nothing to clean up here".
type ProjectLookup ¶
type ProjectLookup interface {
GetProject(id string) (*orchestrator.Project, error)
ListProjects() ([]*orchestrator.Project, error)
}
ProjectLookup lets dispatcher resolve ProjectID → WorkspaceID and enumerate workspace peers, so workspace-peer authorization and peer-visibility concerns stay inside dispatcher instead of leaking into JobSpec.
type ProxyAllocator ¶ added in v0.0.8
ProxyAllocator returns the loopback port of an HTTP(S) egress proxy bound to the given workspace, after applying allowed as its allowlist. The listener is long-lived: subsequent calls for the same workspace reuse the port and live-swap the allowlist. Satisfied by *sandbox.ProxyManager.
type Runner ¶
type Runner struct {
DB *sql.DB
Runtime JobRuntime
Broker CommandBroker
Sandbox SandboxPreparer
SecretStore *SecretStore
Worktrees *WorktreeManager
TaskLookup TaskLookup
Projects ProjectLookup
// Workspaces resolves WorkspaceMeta at dispatch time for the workspace
// the dispatched project is linked to. When nil (test wiring, missing
// disk) the runner falls back to the global floor for proxy allowlist
// resolution. Together with ProxyAllocator it implements the
// workspace-scoped proxy egress allowlist (project-workspace-allowed-domains).
Workspaces WorkspaceLookup
ProxyAllocator ProxyAllocator
BoidBinary string
ServerSocket string
// ProxyPort is the default-workspace proxy port (back-compat fallback
// when the per-workspace allocator path isn't wired or returns an
// error). Workspaces with no overrides reuse this port via the
// allocator's GetOrCreate("default", ...) entry.
ProxyPort *int
// AllowedDomains is the daemon-wide proxy egress allowlist (the floor
// from config.yaml sandbox.allowed_domains + boid defaults). Workspace
// overrides are added on top via orchestrator.ResolveAllowedDomains.
AllowedDomains []string
RuntimesDir string
AttachmentsRoot string
JobEvents JobEventSink // optional; nil disables job lifecycle broadcasts
// contains filtered or unexported fields
}
func Wire ¶
func Wire(cfg WireConfig) *Runner
func (*Runner) CleanupTaskWindow ¶
CleanupTaskWindow stops all tracked runtimes associated with a task.
func (*Runner) CompleteJob ¶
func (r *Runner) CompleteJob(jobID string, result JobCompletionResult)
CompleteJob signals the waiting dispatcher that a job has completed.
func (*Runner) Dispatch ¶
func (r *Runner) Dispatch(ctx context.Context, spec *orchestrator.JobSpec, cleanup orchestrator.CleanupFunc) (string, error)
Dispatch launches a sandbox for the given JobSpec. The optional cleanup callback (typically provided by orchestrator's PlanHook for staging dir teardown) runs after the sandbox process has exited.
func (*Runner) ResizeRuntime ¶
func (r *Runner) ResizeRuntime(jobID string, size TerminalSize) error
ResizeRuntime implements RuntimeInputWriter for Runner. It resolves jobID to a runtimeID via the jobs table, then delegates to JobRuntime.Resize.
func (*Runner) SignalJobRuntime ¶
SignalJobRuntime delivers a single signal to the runtime's process group without any SIGKILL follow-up. NotifyTask uses this for SIGUSR1 to ask the agent (run-agent.py) to stop the agent session gracefully — the go-native runner subcommands keep the signal SIG_IGN (inherited across execve), so they survive while run-agent.py acts on it and runner-inner-child still posts `boid job done` through the broker. Best-effort: errors at debug level only.
func (*Runner) StopJobRuntime ¶
StopJobRuntime stops the runtime identified by runtimeID. It is a best-effort operation: errors are logged at debug level only.
func (*Runner) Subscribe ¶
func (r *Runner) Subscribe(jobID string) (snapshot []byte, ch <-chan []byte, cancel func(), ok bool)
Subscribe implements RuntimeSubscriber for Runner. It resolves jobID to a runtimeID via the jobs table, then delegates to LocalRuntime if the runtime supports live streaming.
func (*Runner) UnregisterJob ¶
UnregisterJob removes the broker token associated with the given job.
func (*Runner) WaitForJob ¶
func (r *Runner) WaitForJob(jobID string) <-chan JobCompletionResult
WaitForJob registers a channel that will receive the job completion result.
func (*Runner) WaitForJobCtx ¶
WaitForJobCtx waits for job completion with context cancellation.
A non-zero exit is NOT reported as an error — the caller inspects result.ExitCode. Only true wait-machinery failures (ctx cancel) produce a non-nil error. This lets the orchestrator record `hook_fired` actions for failing hooks the same way as successful ones; prior behavior discarded the partial FiredEvents when any hook exited non-zero.
type RuntimeAttachRequest ¶
type RuntimeExit ¶
type RuntimeHandle ¶
type RuntimeInputWriter ¶
type RuntimeInputWriter interface {
WriteInput(jobID string, data []byte) error
ResizeRuntime(jobID string, size TerminalSize) error
}
RuntimeInputWriter provides write access to a running job's PTY input.
type RuntimeStartSpec ¶
type RuntimeStartSpec struct {
JobID string
TaskID string
ProjectID string
HandlerID string
Role string
Command string
Interactive bool
TTY bool
// DesiredID, when non-empty, asks the runtime to use this UUID as its
// session identifier instead of generating a fresh one. The caller uses
// this to pre-allocate a runtime directory (e.g. for a per-sandbox docker
// proxy socket) before Start is called. The runtime honours the request
// on a best-effort basis: if the directory already exists or the ID is
// otherwise unusable, Start returns an error.
DesiredID string
}
type RuntimeSubscriber ¶
type RuntimeSubscriber interface {
Subscribe(jobID string) (snapshot []byte, ch <-chan []byte, cancel func(), ok bool)
}
RuntimeSubscriber subscribes to live output of a running job identified by jobID.
type SandboxPreparer ¶
type SandboxPreparer interface {
PrepareSandbox(spec sandbox.Spec) (*PreparedSandbox, error)
}
SandboxPreparer prepares concrete launch artifacts from a sandbox.Spec. The orchestrator-owned BuildSandboxSpec builds the spec; dispatcher only serializes it and tracks artifacts.
func NewSandboxPreparer ¶
func NewSandboxPreparer() SandboxPreparer
NewSandboxPreparer returns the sandbox provider adapter. It serializes the sandbox.Spec to a JSON file that the go-native runner (`boid runner-outer`) reads back; all role-aware translation lives in BuildSandboxSpec.
type SandboxRuntimeInfo ¶
type SandboxRuntimeInfo struct {
JobID string
BoidBinary string
ServerSocket string
ProxyPort int
BrokerSocket string
BrokerToken string
// WorktreeDir is set by dispatcher when Visibility.UseWorktree is true,
// having been resolved through its WorktreeManager. Empty otherwise.
WorktreeDir string
// WorkspacePeers maps peer project IDs (same workspace, excluding self) to
// host paths. Dispatcher resolves this from its ProjectLookup so peer
// visibility/authorization does not leak into orchestrator.JobSpec.
WorkspacePeers map[string]string
// StagingDir, when non-empty, is added to CleanupPaths so the sandbox
// setup script removes it on teardown in addition to the caller-supplied
// CleanupFunc.
StagingDir string
// RootDir, when non-empty, overrides the default per-sandbox ROOT.
RootDir string
// Foreground indicates whether the job runs in the foreground (user-facing
// stdout/stderr, no trap-based completion callback). boid exec sets this
// to true; hook/gate jobs leave it false so stdout is captured and a
// `boid job done` trap posts completion back to the daemon.
Foreground bool
// ResolvedHostCommands is the absolute-path-keyed view of spec.HostCommands
// produced by ResolveHostCommands. The same map is registered with the
// broker so the shim's os.Executable() lookup hits a known key. Empty when
// the job declares no host commands.
ResolvedHostCommands map[string]orchestrator.CommandDef
// DockerEnabled, when true, indicates capabilities.docker is declared in
// project.yaml.
DockerEnabled bool
// ProxySocketPath, when non-empty, is the host-side Unix socket path of the
// per-sandbox docker proxy. sandbox_builder bind-mounts it into the sandbox
// at the fixed sandbox path (see dockerProxySandboxSocket) and injects
// DOCKER_HOST / CONTAINER_HOST / TESTCONTAINERS_* env vars.
// Set by the runner before BuildSandboxSpec when DockerEnabled is true.
ProxySocketPath string
// AllowedDomains is the proxy egress allowlist. It is purely informational
// inside the sandbox (the proxy itself enforces it on the host), surfaced
// to the agent via environment.yaml so it knows which hosts are reachable
// without burning a turn on a 403.
AllowedDomains []string
// AttachmentsRoot is the data-home directory under which per-task
// attachments live (`<AttachmentsRoot>/tasks/<task_id>/attachments`). When
// non-empty and the JobSpec has a TaskID, BuildSandboxSpec appends a
// read-only bind to `<homeDir>/.boid/attachments` so the agent can read
// user-attached files via its standard Read tool. The bind source is
// allowed to be missing — the sandbox setup script handles that via the
// Guard expression so attachments are optional per task.
AttachmentsRoot string
}
SandboxRuntimeInfo carries the dispatcher-internal facts that are required to turn an orchestrator.JobSpec into a sandbox.Spec but that orchestrator never needs to know: job id, broker plumbing, proxy port, boid binary location, server socket path, resolved worktree directory, staging dirs.
type SecretResolver ¶
SecretResolver resolves a secret key into its plaintext value.
type SecretStore ¶
type SecretStore struct {
// contains filtered or unexported fields
}
SecretStore provides encrypted secret storage backed by SQLite.
func NewSecretStore ¶
func NewSecretStore(d *sql.DB, key []byte) (*SecretStore, error)
NewSecretStore creates a store with the given database and encryption key.
func (*SecretStore) Delete ¶
func (s *SecretStore) Delete(namespace, key string) error
func (*SecretStore) Set ¶
func (s *SecretStore) Set(namespace, key, value string) error
type SessionJobInput ¶ added in v0.0.6
type SessionJobInput struct {
// ProjectID and ProjectWorkDir locate the host filesystem the sandbox
// will expose; ProjectWorkDir is the cwd seen by the agent.
ProjectID string
ProjectWorkDir string
// HarnessType selects the agent adapter the runner-inner-child will
// dispatch through. Must be one of "claude" / "codex" / "opencode" /
// "shell" (validated by the caller; BuildSessionJobSpec does not police it).
HarnessType string
// Argv is the literal program + arguments the shell adapter consumes.
// The claude / codex / opencode adapters ignore it (they build their
// argv from CLI conventions). Required when HarnessType == "shell";
// ignored otherwise.
Argv []string
// Instruction is the optional bootstrap prompt the agent should pick up
// on launch (e.g. the `--instruction` flag of `boid agent`, or the
// WebUI Session dialog's text field). When non-empty it is plumbed
// through RunContext.UserAnswer so the adapter's existing "user reply"
// path delivers it as the first turn of input. Empty leaves the adapter
// to pick its default bootstrap (no positional for session mode on claude,
// since the /boid-task skill is meaningless without a task.yaml).
Instruction string
// Readonly controls Visibility.Writable. Sessions default to writable
// (interactive use prioritises developer ergonomics over fail-safety)
// so callers must opt into a read-only session explicitly.
Readonly bool
// Model overrides the harness binary's default model selection.
Model string
// Project trait overlay (the session has no behavior to resolve from,
// so the caller fills these directly from ProjectMeta).
Env map[string]string
HostCommands map[string]orchestrator.HostCommandSpec
AdditionalBindings []orchestrator.BindMount
KitRoots []string
SecretNamespace string
DockerEnabled bool
// DisplayName is the human-readable label persisted to jobs.display_name
// (and shown in the TUI / Web UI). Empty falls back to "<harness>
// session" downstream.
DisplayName string
}
SessionJobInput carries the resolved data needed to build a Session (HarnessAdapter-backed, task-less) JobSpec. Phase 3-d (PR1) introduced it as the input shape for both the daemon API (POST /sessions) and the `boid agent` CLI.
session jobs inherit project-level traits only (env / host_commands / additional_bindings / kit_roots / secret_namespace). behavior-level traits are deliberately ignored — sessions are not driven by the task state machine and have no behavior context to resolve.
type TaskLookup ¶
type TaskLookup interface {
GetTask(id string) (*orchestrator.Task, error)
}
TaskLookup mirrors the subset of orchestrator.TaskLookup the dispatcher needs for worktree resolution. Kept as an interface so tests can stub it.
type TerminalSize ¶
type WireConfig ¶
type WireConfig struct {
DB *sql.DB
Runtime JobRuntime
Broker CommandBroker
Sandbox SandboxPreparer
SecretStore *SecretStore
// Worktrees resolves per-task git worktrees when a JobSpec declares
// Visibility.UseWorktree. Pass nil to disable worktree-backed jobs.
Worktrees *WorktreeManager
TaskLookup TaskLookup
Projects ProjectLookup
// BoidBinary is the host path to the boid executable that should be
// bind-mounted into sandboxes.
BoidBinary string
// ServerSocket is the host path to the daemon UNIX socket (for boid exec
// jobs that talk to boid over HTTP from inside the sandbox).
ServerSocket string
// ProxyPort points at the default-workspace proxy port. Used as the
// fallback when ProxyAllocator is not wired (or fails). Sandboxes
// linked to a workspace get a per-workspace port via ProxyAllocator.
ProxyPort *int
// AllowedDomains is the daemon-wide proxy egress allowlist floor
// (config.yaml sandbox.allowed_domains + boid built-in defaults).
// Workspaces add entries on top via workspace.yaml; they cannot remove
// floor entries (orchestrator.ResolveAllowedDomains enforces this).
AllowedDomains []string
// Workspaces is the WorkspaceLookup used at dispatch time to discover
// each workspace's AllowedDomains overrides. nil disables workspace
// hydration and the runner stays on the floor only.
Workspaces WorkspaceLookup
// ProxyAllocator is the per-workspace proxy listener registry. nil
// disables workspace-scoped proxy allocation and the runner serves
// every sandbox via the default-workspace listener.
ProxyAllocator ProxyAllocator
// RuntimesDir is the root directory where per-sandbox runtime directories
// are created. When non-empty and DockerEnabled, the runner pre-allocates a
// runtime directory here to host the per-sandbox docker proxy socket and
// resource ledger.
RuntimesDir string
// AttachmentsRoot is the data-home directory under which per-task
// attachments live (`<root>/tasks/<id>/attachments`). When non-empty the
// runner threads it through SandboxRuntimeInfo so BuildSandboxSpec can
// add the read-only bind to `~/.boid/attachments` for every harness.
AttachmentsRoot string
}
type WorkspaceLookup ¶ added in v0.0.8
type WorkspaceLookup interface {
Load(slug string) (*orchestrator.WorkspaceMeta, error)
}
WorkspaceLookup reads a WorkspaceMeta for a given slug. Satisfied by *orchestrator.WorkspaceStore; kept as an interface so tests can stub it without touching disk. Load is expected to return os.ErrNotExist-wrapped errors when the workspace file is missing — Runner treats that as the "degraded window" and falls back to the global floor.
type Worktree ¶
type Worktree struct {
ID string `json:"id"`
TaskID string `json:"task_id"`
ProjectID string `json:"project_id"`
Path string `json:"path"`
Branch string `json:"branch"`
BaseBranch string `json:"base_branch"`
CreatedAt time.Time `json:"created_at"`
CleanedAt *time.Time `json:"cleaned_at,omitempty"`
}
type WorktreeManager ¶
type WorktreeManager struct {
RootDir string // e.g. ~/.local/share/boid/worktrees
DB *sql.DB
GitBin string // path to git binary; defaults to "git"
}
WorktreeManager handles git worktree lifecycle for task isolation.
func (*WorktreeManager) CleanOrphaned ¶
func (*WorktreeManager) CleanupForTask ¶
func (m *WorktreeManager) CleanupForTask(taskID, projectDir, newStatus string) error
CleanupForTask removes the worktree filesystem for a task that has reached a terminal state (done / aborted). The associated boid/<id8> branch is NOT deleted here: that responsibility is deferred to the parent supervisor's own finalizeTerminal (via SweepChildBranches), so the supervisor can merge the child branch into the base branch in its post-execution phase before the branch is dropped. Root tasks (no parent) run on the base branch directly and have no boid/* branch to delete in the first place, so this method is uniformly safe regardless of whether the task has a parent.
func (*WorktreeManager) Create ¶
func (m *WorktreeManager) Create(projectDir, projectID, taskID, baseBranch string, opts CreateOpts) (*Worktree, error)
func (*WorktreeManager) EnforceHeadOnBaseBranch ¶
func (m *WorktreeManager) EnforceHeadOnBaseBranch(projectDir, baseBranch string) error
EnforceHeadOnBaseBranch is the Phase 2-2 case 1 HEAD guard. Supervisors running in the project dir (worktree=false) require the project HEAD to remain on the resolved baseBranch from task creation time through job dispatch. A mismatch means the user (or another process) has moved the project branch while a supervisor task was queued; running the supervisor against an unexpected branch silently is the foot-gun that this guard rejects.
Returns nil when baseBranch is empty (no expectation to enforce), nil on a successful match, and an error otherwise. Detached HEAD is reported as an error: a case 1 task should never have been classified for a detached project at creation time, so this code path indicates a state divergence that must abort the run.
func (*WorktreeManager) EnsureBindingTargets ¶
func (m *WorktreeManager) EnsureBindingTargets(worktreePath string, bindings []orchestrator.BindMount, projectDir string) error
EnsureBindingTargets pre-creates additional_bindings mount targets that fall under worktreePath, so a subsequent readonly bind-remount of the worktree does not trigger EROFS when the sandbox setup script tries to mkdir those targets. Bindings whose target lives outside the worktree (and bindings that would escape the worktree via path traversal) are skipped — the sandbox layer handles those during normal mount setup.
projectDir is used to expand the ${PROJECT_WORKDIR} token. ${WORKTREE} expands to worktreePath. Other tokens are passed through unchanged (matching expandWorktreeBindings, which keeps them literal for debuggability).
Idempotent: dirs that already exist are left alone (os.MkdirAll). Safe to call after either Create or Recreate.
func (*WorktreeManager) Recreate ¶
func (m *WorktreeManager) Recreate(projectDir string, taskID string) (*Worktree, error)
Recreate reconstructs a previously cleaned worktree by fetching from the remote branch. It reads the existing DB record (even if cleaned_at is set), fetches the remote branch, creates a new worktree, and clears the cleaned_at timestamp.
func (*WorktreeManager) Remove ¶
func (m *WorktreeManager) Remove(projectDir, taskID string, deleteBranch bool) error
func (*WorktreeManager) SweepChildBranches ¶ added in v0.0.6
func (m *WorktreeManager) SweepChildBranches(projectDir string, taskIDs []string) error
SweepChildBranches deletes the boid/<id8> branches associated with the given task IDs. Called after a parent supervisor reaches a terminal state: its children's branches were retained through CleanupForTask so the supervisor could merge them into the base branch; once the supervisor is terminal, the merged refs are safe to drop.
Behaviour:
- Task IDs with no worktree record are silently skipped (e.g. root tasks that ran in the project dir directly).
- Non-boid/* branches are skipped (root-task base branches must never be auto-deleted; the prefix check is a defence-in-depth).
- `git branch -D` failures are logged but not returned: a child branch may have been deleted already (e.g. by an explicit user merge that ran `git branch -d`), in which case the sweep is a no-op.
type WorktreeRepository ¶
type WorktreeRepository struct {
// contains filtered or unexported fields
}
func NewWorktreeRepository ¶
func NewWorktreeRepository(db db.DBTX) *WorktreeRepository
func (*WorktreeRepository) CreateWorktree ¶
func (r *WorktreeRepository) CreateWorktree(worktree *Worktree) error
func (*WorktreeRepository) GetWorktreeByTask ¶
func (r *WorktreeRepository) GetWorktreeByTask(taskID string) (*Worktree, error)
func (*WorktreeRepository) ListActiveWorktrees ¶
func (r *WorktreeRepository) ListActiveWorktrees() ([]*Worktree, error)
func (*WorktreeRepository) MarkWorktreeCleaned ¶
func (r *WorktreeRepository) MarkWorktreeCleaned(taskID string) error
Source Files
¶
- broker.go
- home.go
- host_commands.go
- init_jobspec.go
- model.go
- orchestrator_adapter.go
- policy_translate.go
- preparer.go
- repository.go
- runner.go
- runtime.go
- runtime_local_linux.go
- runtime_subscriber_export.go
- sandbox_builder.go
- sandbox_preparer.go
- secret_keyfile.go
- secret_store.go
- session_job.go
- store.go
- transcript.go
- wire.go
- worktree_manager.go
- worktree_model.go
- worktree_resolver.go
- worktree_store.go