@thoth-agents/pi-background-tasks
Pi extension for durable background shell tasks, watchers, logs, and status inspection.
Package details
Install @thoth-agents/pi-background-tasks from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@thoth-agents/pi-background-tasks- Package
@thoth-agents/pi-background-tasks- Version
0.5.1- Published
- Oct 21, 2026
- Downloads
- 796/mo · 217/wk
- Author
- eremesng
- License
- MIT
- Types
- extension
- Size
- 437.3 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"image": "https://raw.githubusercontent.com/1aboveio/pi-better-harness/main/docs/images/package-gallery/pi-better-background-tasks.png",
"extensions": [
"./dist/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@thoth-agents/pi-background-tasks
Local shell/process jobs for Pi: spawn commands, poll watches, inspect logs and status, stop work, and receive completion callbacks without blocking the foreground turn. The standalone tools are: bg_task_spawn, bg_task_watch, bg_task_list, bg_task_status, bg_task_log, bg_task_stop, and bg_task_clear. The Work panel remains available in interactive sessions. Requires Node >=22.19.0 and Pi SDK/TUI >=0.99.0.
Provenance
Copied from https://github.com/1aboveio/pi-better-harness, directory packages/pi-better-background-tasks, commit 86876e8369a673892a10c4e8df15460386ad0132 (upstream version 0.6.2). MIT; Copyright (c) 2026 1aboveio is retained in LICENSE. Shared libraries are now owned source; no upstream monorepo synchronization scripts are needed.
The manifest follows the existing vendored packages' conventions (scoped name, compiled ./dist/index.ts extension entry, public publish configuration; not marked private). Installing this workspace does not change the operator's Pi settings.
Use
Thoth's complete Pi Install and applied Update install and individually verify
npm:@thoth-agents/pi-background-tasks@>=0.3.0. An existing copy from any source
at or above that floor is preserved without reinstalling; an older copy remains
untouched and blocks completion with manual upgrade guidance. Ambiguous identity
fails closed. Upgrade a local/Git checkout in place, or review ownership before
switching its configured source with Pi's native remove/install commands; verify
the installed manifest and rerun setup. Dry-run performs no mutation.
For a standalone install:
pi install 'npm:@thoth-agents/pi-background-tasks@>=0.3.0'
Restart Pi after installation. The manifest loads the compiled ./dist/index.ts
entry; workspace development builds it with pnpm run build:pi-extensions.
{
"name": "build",
"shell": "none",
"argv": ["node", "build.mjs"],
"timeout_seconds": 1800
}
Pass this to bg_task_spawn. Prefer shell:"none" and argv for literal arguments: the executable runs directly, without shell parsing or MSYS argument rewriting. Commands should be long-running work, not short foreground checks.
Declare the command shell
Spawn and watch accept shell: "bash" | "powershell" | "none", default "bash". New calls reject the old boolean form. Write the syntax of the shell you declare; commands are never translated or silently run in another shell. An unavailable requested shell fails before launch, naming the missing shell, available shells (including PowerShell edition/version), and how to rewrite the call.
bashresolves through Pi's public shell resolver andshellPathsetting, with Pi's arguments (-c). Windows:shellPath→ Git Bash under Program Files →bash.exeon PATH. POSIX:shellPath→PI_BETTER_BACKGROUND_TASKS_SHELLoverride →/bin/bash→ PATH bash. Pi'sshfallback and System32/Sysnative WSL bash are rejected as bash unavailable. Bash command strings retain Pi's MSYS path/argument conversion behavior; usenonefor literal native argv. Git Bash reopens its log streams inside the shell so stdout/stderr are captured.powershellprefers validated PowerShell Core 7+, then Windows PowerShell Desktop 5.1 on Windows; POSIX supports pwsh only.PI_BACKGROUND_TASKS_PWSHreplaces pwsh discovery and may point to either edition; an unusable override still permits the Windows 5.1 candidate, never a different shell. Normal Windows discovery checks PATH pwsh, Program Files/PowerShell/7 and WindowsApps, then System32/WindowsPowerShell/v1.0/powershell.exe. Windows PowerShell 5.1 lacks&&/||: use compatible PowerShell syntax. The encoded wrapper sets UTF-8 before parsing user commands insidetry, suppresses progress, and propagates explicitexit Nand native-command exit codes.nonerunsargvdirectly. No command shell is required, but Windows containment still uses a validated PowerShell helper, preferring 7+ and accepting built-in 5.1. PowerShell 7 is not a prerequisite for Windows jobs of any shell.
Each command tool description discloses shells detected once at registration (paths, PowerShell edition/version and 5.1 limitations). Availability is revalidated at launch. Every spawn/watch result includes the actual shell label in text and details.shell (kind, executable, label, and PowerShell edition/version). Legacy records without launch details report those details as unknown. Reload normalizes legacy metadata: false → none, true/absent → powershell on Windows or bash on POSIX, preserving the shell the old job was written for.
A watch repeatedly executes a command until success_when, failure_when, or timeout matches. interval_seconds defaults to 30. Its first result is returned after at most 15 seconds. Watch timeout defaults to 900 seconds; timeout_seconds:0 disables it. Spawned processes have no default timeout. Logs retain a bounded tail (max_log_bytes, default 4194304 / 4 MiB), and terminal artifacts are retained for seven days. List/status/log default to the current session; all:true explicitly opts into cross-session inspection.
Watch conditions use these shapes; JSON conditions read stdout:
| Type | Fields |
|---|---|
exit_code |
equals: number |
stdout_contains |
value: string |
stderr_contains |
value: string |
json_path_equals |
path: string, value: any |
json_path_exists |
path: string |
JSON paths must start with $, for example $.status or $.steps[0].status;
bare keys and wildcards are unsupported. blind_checks defaults to 3 consecutive
exit-zero checks with stderr and no matched condition before flagging an
actionable possibly-blind incident. The watch continues; no stderr or a matched
condition recovers it. Set blind_checks: 0 to disable this rule.
Spawn/watch also accept operation_id (up to 64 letters, digits or . _ : / -)
to identify a logical operation across retries: a later success with the same id
recovers earlier failures of that operation in the same session.
expected_exit_codes declares up to 16 distinct intentional exit codes (0–255;
0 is already success and ignored). These are recorded as expected failures, not
actionable incidents; signals and timeouts are never expected. Both fields may
be omitted or null.
bg_task_clear dismisses all owned terminal tasks, optionally filtered by status (e.g. ["failed"]), or one task with id (which overrides status); all:true permits another session's task only by ID. Omitted or empty status clears every owned terminal status. Running tasks must be stopped first; dismissal preserves history. max_bytes bounds missing-task and ownership diagnostics; bg_task_stop also accepts history:true for failure history in its result. Output controls use max_bytes and lines (not maxBytes or tail_lines). The former bg_task/bg_status action wrappers are no longer registered.
Output is paged with caller-owned, session-scoped cursor values: replay the
same page/change notice or pass nextCursor to continue; callers are independent.
Status verbose: true pages raw metadata with environment values omitted
(default false). Logs default to 10 trailing display rows; raw: true or
lines: 0 pages raw bytes from the oldest retained offset. Lists default to 10
entries, capped at 100. max_bytes budgets the whole UTF-8 output:
| Output | Default | Cap |
|---|---|---|
| Status | 1 KiB | 2 KiB |
| Log tail / list | 1 KiB | 4 KiB |
| Raw log / verbose metadata | 16 KiB | 64 KiB |
Completion callbacks are session-origin scoped and delivered as Pi follow-ups. Cancelled tasks do not wake the agent. Failed checks remain visible in status and can receive failure-attention callbacks. The Work panel's Background section shows local work and its evidence.
Work panel and history
The Background provider uses pi-core's v2 data-only Work panel contract: rows
carry unstyled text and semantic status, with rendering owned by the host.
pi-core's discovery/action API lists
sources, subscribes to revisions, reads bounded rows and invokes same-session
open/history/close actions. Upgrade pi-subagents, pi-background-tasks and pi-todo
together to >=0.3.0, then /reload: first-owner arbitration hides incompatible
sections in mixed v1/v2 installs.
The Background section shows running work and current-prompt outcomes while the agent is busy (all failures/timeouts and at most three recent completed tasks). When idle with no running tasks, it collapses to one selectable summary with session done/failed totals. This replaces the former 30-second row expiry. Prompt retention uses pi-core's observed-text heuristic: a run must match an interactive/RPC prompt observed while idle; prompts queued while streaming do not advance it.
Press ← from an empty editor to focus the Work panel, ↑/↓ to select, and Enter
to open history (a task row selects that task). /bg opens the same panel; no
default shortcut is added. History lists every retained task from the current
cwd/session, newest first, including dismissed tasks and older outcomes. Task
metadata and retained logs are shown in bounded pages of up to 64 KiB, with
visible capture/retention loss notices. ←/→ selects a task, ↑/↓, PgUp/PgDn,
Home/End and the mouse wheel scroll the current page; [/] pages the log.
Press x twice to stop a running task; Esc, q or Ctrl+C closes history.
The shared pi-core overlay host enables mouse reporting in regular TUI mode and
releases it on close, rejection or disposal; fullscreen-owned tracking is untouched.
Dismissing a terminal Work-panel row does not remove it from history or totals.
Registry retention is unchanged: maintenance removes terminal artifacts after
seven days.
Tool calls/results, completion/failure messages and the Work panel's Background
section render through the theme's Render KIT when present, discovered through
@thoth-agents/pi-core at render time. Without the kit, they keep native Pi
rendering; there is no dependency on @thoth-agents/pi-thoth-theme.
Task summary channel
Publishes complete current cwd/session-origin snapshots on pi-core's v1
thoth:background:state channel, with source
@thoth-agents/pi-background-tasks and envelope
{ v, source, sessionId, at, data } (at is Unix milliseconds). Data contains
only task id/name, kind/status, lifecycle times, exit code/signal and dismissal,
plus status counts; dismissed tasks remain included. The contract permits numeric
or short-text progress (at most 200 UTF-16 code units), currently omitted by this
publisher. Commands, argv, environment, results, stdout/stderr, logs and log paths
are never published.
Subscribe before requesting on thoth:background:state:request with data: {}
and the target session ID in the envelope. Requests before readiness are answered
once by the readiness snapshot. Publications use fresh metadata reads after
registry changes, including filesystem notifications for another process's
writes (no polling timers), and on session start/switch. Watches are disposed and
rebound across session switches and disposed on shutdown. Consumers replace the
full snapshot; this channel does not change work-panel/history providers or
retention. See pi-core's exact contract.
Lifecycle and limits
The registry lives under the OS temporary directory at
pi-better-background-tasks/tasks/<id> (meta.json and retained output.log).
Environment overrides (milliseconds):
| Variable | Default | Purpose |
|---|---|---|
PI_BETTER_STALL_QUIET_MS |
60000 |
Observable-progress age before quiet status. |
PI_BETTER_STALL_MS |
300000 |
Observable-progress age before stalled status; observational, not automatic recovery. |
PI_BETTER_CALLBACK_BATCH_MS |
100 |
Completion callback batching window, clamped to 0–5000 ms. |
A session's /reload suspends that instance's scheduling without stopping its jobs. The next instance adopts work of the same origin, including an in-flight watch poll: it does not start an overlapping poll, and completion is delivered once. Runtime timers, the Work panel Background section and failure-attention state belong to each extension instance; loading or ending a headless child does not change root-session work or UI.
Every other session_shutdown reason (including quit, new, resume and fork) stops the origin's running jobs and in-flight watch commands, subject to the platform guarantees below. Cancelled jobs do not send completion callbacks.
Windows containment: every job and watch poll is created suspended, assigned to its own unnamed Job Object, and resumed only after assignment succeeds. Assignment failure terminates the suspended child without running it. Jobs have KILL_ON_JOB_CLOSE and no breakaway permission; only stdio handles are inherited. Termination uses TerminateJobObject, and ActiveProcesses == 0 must be verified before cancellation, natural-exit finalization, watch-condition evaluation, or the next poll. A short-lived intermediate cannot escape tracking: there is no ancestry census or PID-based tree kill. Opaque launch-time ownership survives same-process reload and is never reconstructed from persisted PIDs. Failed cleanup stays running, owned and retriable.
One hidden PowerShell helper, shared across sessions in the same Pi process, owns the job handles outside those jobs. It waits independently on Pi's process handle. Any Pi process exit, including a crash, closes the jobs and kills their trees on Windows. Helper death also kills its contained jobs; if emptiness cannot be queried, the package conservatively retains unverified ownership instead of declaring success. Child-session teardown stops only its own containers, never the shared helper. This is not a sandbox: work brokered through services, elevation, WSL or remote systems is outside the containment guarantee.
POSIX is best-effort: each launch gets its own detached process group. Cleanup sends TERM, waits up to 500 ms, then KILL when needed, and waits for the group to report ESRCH. No ancestry census or individual-PID fallback is used. A reused group ID after the leader is reaped can be signalled; descendants that leave the group (setsid) can escape; abrupt Pi death does not clean up groups. ESRCH does not prove that escaped descendants exited. Natural exit triggers the same best-effort group cleanup before terminalization or another poll.
Jobs survive only a same-process owning-session /reload, not a Pi restart. During the gap between reload instances, scheduled deadlines are suspended; resumed work enforces overdue deadlines. Child sessions are separate origins and must emit their shutdown event when they end. All package and fixture process launchers explicitly hide Windows consoles.
Development
From the workspace root:
pnpm install --frozen-lockfile
pnpm --filter @thoth-agents/pi-background-tasks run typecheck
pnpm --filter @thoth-agents/pi-background-tasks run test
Tests use private temporary registry roots. Node fixtures cover portable process jobs and local watch sequences; executable-shell incident reproductions have explicit POSIX-only guards on Windows.
