Documentation
¶
Index ¶
- Variables
- func BuildExecJobSpec(input SessionJobInput, argv []string, interactive bool) (*orchestrator.JobSpec, error)
- func BuildInitJobSpec(in InitJobInput) *orchestrator.JobSpec
- func BuildSandboxSpec(spec *orchestrator.JobSpec, rt SandboxRuntimeInfo) (sandbox.Spec, error)
- func BuildSessionJobSpec(input SessionJobInput) (*orchestrator.JobSpec, error)
- func CaptureUpstreamURL(dir string) (string, error)
- func CreateJob(dbtx db.DBTX, j *Job) error
- func FindDaemonShutdownAbortedTasks(conn *sql.DB) ([]string, error)
- func GenerateKey() []byte
- func GitOriginURL(dir string) (string, error)
- 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 NormalizeOriginURL(raw string) (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 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 PeerAdvertise
- 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, ...) (jobID string, dispatchErr 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 TerminalSize
- type WireConfig
- type WorkspaceLookup
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, error)
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. Propagates any error from BuildSessionJobSpec (in particular a session clone-declaration failure — see buildSessionCloneDeclaration).
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, error)
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().
Returns an error when the sandbox-internal clone declaration cannot be built for a non-empty ProjectWorkDir — see buildSessionCloneDeclaration's doc comment. The caller (WebUI POST /sessions, `boid exec` CLI) must surface that as a user-visible error rather than silently degrading, since the cutover contract (docs/plans/git-gateway-cutover.md PR6) requires a clone-based dispatch for every project-visible job.
func CaptureUpstreamURL ¶ added in v0.0.10
CaptureUpstreamURL reads dir's `git config --get remote.origin.url` and normalizes it to an HTTPS URL suitable for a project's upstream_url. Returns an error if dir has no git repository, no origin remote is configured, or the origin URL is in an unrecognized form — the caller (project registration / reload / startup backfill) decides how to react.
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 GitOriginURL ¶ added in v0.0.9
GitOriginURL returns the `git config --get remote.origin.url` value for dir, or an error if git is missing, dir is not a repo, or no origin is configured. It deliberately uses cmd.Dir rather than `git -C dir`: this repo's sandbox git wrapper rejects `-C`, and cmd.Dir works everywhere (production and sandboxed callers alike).
This is the production getOriginURL implementation passed to ResolveHostCommands; it is exported so callers outside this package (internal/server/api_store.go) can pass it too.
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 NormalizeOriginURL ¶ added in v0.0.10
NormalizeOriginURL converts a git remote origin URL into the HTTPS form used as a project's upstream_url (docs/plans/git-gateway-cutover.md PR2: "project → 上流 URL の明示マッピング"). HTTPS URLs are returned unchanged ("既に HTTPS URL ならそのまま"); scp-like SSH (`git@host:owner/repo.git`) and `ssh://` URLs are rewritten to `https://host/owner/repo.git` (`http://` is likewise upgraded to `https://`, reusing the same host/path extraction). Returns an error for an empty or unrecognized URL form.
This is a pure function so it can be unit tested without a real git repository; CaptureUpstreamURL below composes it with the actual `git config` read.
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), getOriginURL 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.
The names "boid", "git", and "fetch" are excluded, each for a different reason: "boid" has a dedicated bind mount + builtin policy elsewhere; "fetch" is a broker builtin (`FetchRequest`) without a host binary at all; "git" is neither a broker builtin nor a shim — it's a real binary reached via the base rbind of /usr, but the name is reserved here so a user `host_commands.git:` entry doesn't try to overlay a shim onto that path and break the sandbox-side git that the git gateway clone flow depends on.
`projectDir` is used to resolve relative paths declared in host_commands.<name>.path, and as the working directory for the origin URL lookup that expands `${boid:repo_slug}` in Env values (see docs/plans/host-command-contract.md item 3). `lookPath` and `getOriginURL` are parameterized for tests; production callers pass exec.LookPath and GitOriginURL. There are only two production call sites (runner.go, api_store.go), which is few enough that threading a parameter through them (matching the existing lookPath convention) is simpler than a package-level var seam.
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 EnvironmentInput ¶ added in v0.0.6
type EnvironmentInput struct {
Visibility orchestrator.Visibility
WorkspacePeers map[string]string
// WorkspacePeerAdvertise, when non-nil, drives the workspace_projects
// listing (docs/plans/git-gateway-cutover.md PR6 cutover). See
// SandboxRuntimeInfo.WorkspacePeerAdvertise's doc comment.
WorkspacePeerAdvertise map[string]PeerAdvertise
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
// CloneDir is this job's own project's absolute sandbox-internal clone
// directory, e.g. "/workspace/bm-next" (workspace 親化リファクタリング,
// nose 2026-07-13 decision). Empty unless Visibility.Clone is set — the
// `filesystem.project_dir` field already carries the *host* path for
// descriptive purposes, but under clone-mode dispatch the host path is
// not actually visible inside the sandbox at all, so agents need this
// separate field to know where their own project's working tree
// actually lives.
CloneDir 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 PeerAdvertise ¶ added in v0.0.10
type PeerAdvertise struct {
// Name is the peer's repo name (the last segment of its upstream_url's
// host/owner/repo form), used purely for display/discoverability.
Name string
// CloneURL is the full gateway clone URL for this peer, scoped fetch-only
// to this job's gateway token (docs/plans/container-based-boid.md
// 「workspace peer プロジェクト」: peers are fetch-only; writing to a peer
// means a cross-project child task instead).
CloneURL string
// ReferencePath is the sandbox-internal RO bind-mount path of the peer's
// `.git` (sandboxClonePeerReferenceDirFmt), usable as `git clone
// --reference` when an agent does clone the peer.
ReferencePath string
// CloneDir is the suggested absolute sandbox-internal directory for this
// peer, e.g. "/workspace/bm-next-lp" (workspace 親化リファクタリング,
// nose 2026-07-13 decision). It is only a suggestion — nothing enforces
// an agent actually clones the peer here — but using the same leaf name
// projectDirName would resolve for the peer's own project (were it
// dispatching as self) keeps the directory name stable regardless of
// which project happens to be the one dispatching, and keeps it off
// $HOME/tmp (both tmpfs, RAM-backed).
CloneDir string
}
PeerAdvertise is the {name, clone URL, reference path} view of a workspace peer project exposed via environment.yaml (docs/plans/git-gateway-cutover.md PR6 cutover 「5. peer advertise の変更」). Built by Runner.buildPeerAdvertise from the peer's captured upstream_url + this job's gateway token; it intentionally carries no host filesystem path — clone-mode jobs have no host path visible for a peer project any more, only the sandbox-internal RO reference dir (ReferencePath) and the gateway clone URL an agent would `git clone` from if it wants to see the peer's working tree.
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
Projects ProjectLookup
// Hydrator optionally resolves a project's workspace-hydrated
// ProjectMeta (project.yaml `meta.name` plus workspace merge) by project
// ID. It is used only for workspace-peer name resolution in
// buildPeerAdvertise — the self project's name is already resolved at
// JobSpec-build time via Visibility.ProjectName and does not need this.
// nil (test wiring, or a daemon build that doesn't wire it) makes
// buildPeerAdvertise degrade to the pre-existing basename fallback, same
// as orchestrator.DispatchPlanner.Hydrator's nil behavior.
Hydrator orchestrator.MetaHydrator
// 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
// GitGateway is the git gateway's job-token registry
// (docs/plans/git-gateway-cutover.md PR4: gateway lifecycle + dispatch
// wiring). nil disables gateway token registration entirely — Dispatch
// and UnregisterJob treat that as a no-op rather than panicking (test
// wiring, or a daemon build without the gateway constructed). PR4 is
// inert: registration happens, but nothing inside the sandbox talks to
// the gateway yet (that's PR5/PR6).
GitGateway *gitgateway.Registry
// GatewayURL points at the daemon's own gateway listener address string,
// filled in by Server.Start once the gateway's TCP listener is bound —
// the same late-binding-via-pointer pattern as ProxyPort, since the
// gateway (like the default proxy listener) is only known once Start
// has run. nil disables gateway URL propagation into SandboxRuntimeInfo.
GatewayURL *string
// 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) (jobID string, dispatchErr 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 and the git gateway job 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
// StdinForward requests a dedicated stdin pipe for a non-interactive
// (Interactive=false) session, so a later Attach's RuntimeAttachRequest.Input
// can feed real bytes to the child process — `boid exec` piped from a
// non-TTY stdin (e.g. `echo hi | boid exec cat`) needs this; a hook job
// never does. False (the default, every hook job) keeps stdin on the null
// device exactly as before: a hook script that probes stdin must keep
// seeing an immediate EOF, not block forever waiting for a forwarder that
// will never attach. Ignored when Interactive is true — PTY sessions
// always support input via the PTY master, forwarding or not.
StdinForward 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
// 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
// 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
// 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 capabilities.docker is
// declared in project.yaml.
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
// GatewayURL is the git gateway's sandbox-facing base URL
// (http://10.0.2.2:<port>), set by Runner from the daemon's own
// gateway listener (docs/plans/git-gateway-cutover.md PR4: gateway
// lifecycle + dispatch wiring). Empty when the gateway isn't wired.
//
// PR4 is inert: BuildSandboxSpec does not thread this into env or mounts
// yet — nothing inside the sandbox reads it. The env var advertise
// (e.g. GIT_HTTP_GATEWAY_URL) is explicitly deferred to the cutover PR
// (PR6); the runner clone sequence that would consume it is PR5.
GatewayURL string
// GatewayJobToken is this job's git gateway token, registered against
// the gateway's Registry at dispatch time (self project fetch/fetch+push,
// workspace peers and workspace extra_repos fetch-only) and unregistered
// when the job completes (see Runner.registerGatewayToken /
// Runner.UnregisterJob). Empty when the gateway isn't wired.
//
// Same PR4-is-inert caveat as GatewayURL: carried here for PR5/PR6 to
// consume, not yet used by BuildSandboxSpec.
GatewayJobToken string
// GatewayCloneURL is the full gateway clone URL for spec's own project
// (GatewayURL + "/j/" + GatewayJobToken + "/<host>/<owner>/<repo>.git"),
// built by Runner.buildGatewayCloneURL (docs/plans/git-gateway-cutover.md
// PR5). Empty unless spec.Visibility.Clone is non-nil (the opt-in
// sandbox-clone path) — computing it is otherwise wasted work, since
// nothing would consume it. BuildSandboxSpec only reads this when
// spec.Visibility.Clone != nil.
GatewayCloneURL string
// WorkspacePeerAdvertise is the {name, clone URL, reference path} view of
// WorkspacePeers exposed to the agent via environment.yaml's
// `workspace_projects` (docs/plans/git-gateway-cutover.md PR6 cutover
// 「5. peer advertise の変更」 — replaces the pre-cutover host path
// enumeration). Built by Runner.buildPeerAdvertise, keyed by peer project
// ID; nil when the gateway isn't wired or no peer has a resolvable
// upstream_url. Distribution stays file-based (environment.yaml) for now
// — RPC-based advertise is a later container-migration step (「タスクコン
// テキストの伝搬」), out of scope here.
WorkspacePeerAdvertise map[string]PeerAdvertise
// CloneWorkspaceDir is the host-side runtime dir path
// (`<RuntimesDir>/<runtime_id>/workspace`) that BuildSandboxSpec bind-
// mounts at the sandbox-internal clone target (/workspace/<name>) when
// spec.Visibility.Clone is set (docs/plans/git-gateway-cutover.md PR6
// cutover — 「一時領域の実体はホスト側 runtime dir の bind mount を既定と
// する」, 2026-07-08 decision in container-based-boid.md). Allocated and
// mkdir'd by Runner.Dispatch before BuildSandboxSpec runs, the same way
// startDockerProxy pre-creates its runtime dir. Empty when RuntimesDir is
// unset (e.g. minimal test wiring) — cloneMounts then skips the bind and
// the clone lands on the sandbox's own tmpfs root instead, a safe but
// non-default degrade (working tree + build artifacts in RAM).
CloneWorkspaceDir 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, 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
// ProjectName is project.yaml's `meta.name` (see
// orchestrator.Visibility.ProjectName's doc comment). The caller fills
// this from the same workspace-hydrated ProjectMeta it already reads
// Env/HostCommands/AdditionalBindings/KitRoots from.
ProjectName 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 TerminalSize ¶
type WireConfig ¶
type WireConfig struct {
DB *sql.DB
Runtime JobRuntime
Broker CommandBroker
Sandbox SandboxPreparer
SecretStore *SecretStore
Projects ProjectLookup
// Hydrator is optional workspace-hydrated ProjectMeta lookup, threaded
// straight to Runner.Hydrator (see its doc comment). nil disables
// workspace-peer meta.name resolution; buildPeerAdvertise falls back to
// filepath.Base(WorkDir).
Hydrator orchestrator.MetaHydrator
// 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
// GitGateway is the git gateway's job-token registry
// (docs/plans/git-gateway-cutover.md PR4). nil disables gateway token
// registration entirely.
GitGateway *gitgateway.Registry
// GatewayURL points at the daemon's own gateway listener address string,
// filled in by Server.Start once the gateway's TCP listener is bound
// (same late-binding pattern as ProxyPort). nil disables gateway URL
// propagation into SandboxRuntimeInfo.
GatewayURL *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.
Source Files
¶
- broker.go
- gitgateway_wire.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
- upstream_url.go
- wire.go