Documentation
¶
Overview ¶
Package jobs is memcode's background-job layer — the async extension of the shell lanes. A dev server / watcher / `docker compose up` never exits, so it can't run through the blocking bash path; it runs here: started detached, output captured to a capped ring buffer, status tracked, killable. Doctrine: foreground answers now; background jobs maintain temporal state; monitors turn waiting into memory.
Concurrency note: the registry is the runtime's first concurrent subsystem. All mutable Job state is guarded by Registry.mu; callers only ever see immutable View snapshots. Jobs use a process GROUP (Setpgid) so killing one reaps the whole tree (npm → node → …), and they run under a background context so they outlive the turn.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RunForeground ¶
RunForeground runs cmd to completion in its OWN process group, killing the whole group if ctx is cancelled or times out — so a child (e.g. node under npm) can't hold the output pipe open and hang cmd.Wait() after the kill. Use this instead of exec.CommandContext + cmd.Run() for any foreground command that may spawn children: CommandContext kills only the direct child, which is why an un-grouped `next dev` made the agent turn unkillable (Esc/Ctrl-C/timeout couldn't free it). Returns ctx.Err() on cancel, else the command's exit error.
Types ¶
type ForegroundOutcome ¶
type ForegroundOutcome struct {
Done bool // ran to completion within the time budget
Killed bool // turnCtx was cancelled (Esc/Ctrl-C) — the whole group was reaped
Exit int // exit code when Done (-1 if it couldn't start)
Stdout string // captured stdout when Done
Stderr string // captured stderr when Done
Promoted *View // non-nil: still running at the deadline, handed to a background report-back job
}
ForegroundOutcome is the result of RunForegroundOrPromote.
func RunForegroundOrPromote ¶
func RunForegroundOrPromote(r *Registry, bgCtx, turnCtx context.Context, timeout time.Duration, command, cwd string) ForegroundOutcome
RunForegroundOrPromote runs command in its OWN process group and waits up to `timeout` for it to finish. Three outcomes:
- it finishes → Done, with Exit + Stdout + Stderr (the old blocking bash path);
- turnCtx is cancelled first (Esc/Ctrl-C) → Killed, the group reaped;
- it's STILL running at the deadline → it is NOT killed. It's promoted to a tracked background job (watched under bgCtx, reporting its result back on completion) and returned via Promoted, so the turn stops blocking on it.
bgCtx MUST be the long-lived session context so a promoted job outlives the turn; turnCtx is the per-turn context that carries interrupt.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry owns the live background jobs for a session.
func (*Registry) DrainReports ¶
func (r *Registry) DrainReports() []ShellReport
DrainReports returns and clears the finished report-back shell jobs (promoted commands), each exactly once. Called from the UI poll to hand results back to the model.
func (*Registry) Kill ¶
Kill terminates a running job's process group. Returns whether a running job was killed.
func (*Registry) KillAll ¶
func (r *Registry) KillAll()
KillAll terminates every running job — call on session end so nothing orphans.
type ShellReport ¶
ShellReport is a finished report-back shell job — a background shell or a promoted foreground command — owed back to the model. Drained by the UI poll and injected as a new turn.
type SyncBuf ¶
type SyncBuf struct {
// contains filtered or unexported fields
}
SyncBuf is a thread-safe output sink: unbounded while a command runs in the foreground (so a normal bash call's full output is captured, as the old strings.Builder did), then capped to its tail via Cap() if the command is promoted to a long-running background job.
type View ¶
View is an immutable snapshot of a job for callers (footer, /jobs, /tail).
func Start ¶
Start launches command (via the platform shell) in cwd as a detached background job and returns its View. ctx should be a LONG-LIVED context (session/background) — never a turn context, or the job dies when the turn ends. Every started job reports back on its own exit (reportBack=true): a finished poller hands its result to the model, and a dev server that exits did so unexpectedly — exactly when you want to hear about it. Jobs WE kill (/kill, session end) stay silent.