Skip to main content

Crate basis_tasks

Crate basis_tasks 

Source
Expand description

The durable task layer over basis: a filesystem-coordinated lifecycle for spawned agents, reachable from Rust and not only from a CLI.

ADR-0019 retired the per-workspace daemon. A task is a directory under one global data directory; execution belongs to whichever process holds its attach.lock — one writer, ever; liveness belongs to the OS. No verb here leaves a resident process behind, and no verb takes a lock it does not need: send, cancel, watch, list, and the read-only accessors never attach at all. Tasks is the crate’s front door; open one and see its docs for what each verb does and costs.

Every rule ADR-0017 states is enforced exactly as it was inside the CLI this crate was extracted from, unchanged: 16 messages to a durable inbox, 4 KiB bounded summaries, a finite deadline on every unattended task, downward-only cancellation, and the wait-edge policy that keeps a basis wait-shaped call from ever cycling (a descendant or an independent root is a safe edge; an ancestor or a peer is not).

§The environment protocol

A task’s runtime sets three environment variables for every command its turns run (ADR-0018), and this is the one place their names and meanings are declared — they are read by tools this crate never sees run, so this is a protocol this crate publishes rather than an implementation detail it could rename:

  • BASIS_TASK_ID — this task’s own handle. A tool that wants to send or ask its own task — a subagent reporting progress upward, for one — reads this rather than being told.
  • BASIS_DATA_DIR — the data directory root Tasks::open would resolve to on its own, named explicitly so a recursive basis (or any other binary built on this crate) invoked from inside a turn resolves the same data directory its parent did, rather than rediscovering one from a possibly different environment.
  • BASIS_PARENT_TASK_ID — the parent’s handle, present only when the task has one. What lets a deeply nested spawn still name its whole ownership chain without walking meta.json files to find it.

current_task reads the first of these back, for a host that wants to know whether it is running as a task’s tool call.

Structs§

Error
An error from a basis-tasks operation: what went wrong, in one line.
EventCursor
A cursor over one task’s event journal.
Reply
What Tasks::ask enqueued and what came of waiting for it.
RunSpec
One task’s worth of spawn request: basis::RunSpec’s per-run intent, plus the workspace-level overrides a basis::Workspace normally fixes once, and the facts a task that may run unattended, in another process, additionally needs recorded.
TaskHandle
A durable task’s handle: <16 lowercase hex>/<32 lowercase hex>.
TaskSummary
One task, as Tasks::list reports it and as a --continue-shaped continuation picks from it.
Tasks
One workspace’s durable tasks.
WatchRecord
One journal record: the flat EventLine shape exactly as written — {"seq":N,"type":...}, whichever vintage of journal it came from (events::EventTail already normalizes the pre-0.6 nested wrapper) — with basis’s typed basis::Event alongside it, when this build recognizes the type it names.

Enums§

Approve
Every consequential call is put to this, exactly once per task: Always allows, Never refuses, Prompt asks whoever is executing the task’s current turn.
Continuation
Which conversation a spawned task picks up, if any.
Hint
A next step this error names unambiguously enough for a host to build its own hint text from, without parsing this error’s message. Not every error carries one: an ordinary operational failure has no next step this crate can name from here, and stays None.
WaitOutcome
The current attach’s outcome, or a bounded timeout — see attach::WaitOutcome, which this re-exports as the return type of Tasks::wait and Tasks::wait_message. What a bounded wait produced: the settled payload, or a timeout with enough said about it to retry sensibly.

Constants§

BASIS_DATA_DIR
The data directory root this task’s runtime resolved — see BASIS_DATA_DIR.
BASIS_PARENT_TASK_ID
This task’s parent, when it has one — see BASIS_PARENT_TASK_ID.
BASIS_TASK_ID
This task’s own handle — see BASIS_TASK_ID.
DEFAULT_DEADLINE
The default deadline an unattended task is given when nothing else names one: 30 minutes. A spawned task may never be waited on by an attentive caller, so — unlike an attended one-shot, which is unbounded unless asked — it always gets a finite service bound (with_deadline narrows it).
MAX_TASKS
How many tasks one workspace may hold at once. spawn refuses past it (archiving an old agent directory is the way out); the ownership-policy walks in policy and attach::cancel_tree are bounded by it too, so corrupt or cyclic metadata cannot loop forever.
POLL
The polling cadence everything waits at: terminal records, contended locks, child settling. Bounded CPU, honest tail latency. Public so a host composing its own loop around crate::EventCursorbasis watch’s own loop, for one — polls at the same cadence this crate’s own waits do.

Traits§

LiveSink
Shown a task’s events as they are journaled. basis-cli’s own terminal renderer is the first implementation, but the trait carries no opinion about a terminal — a host could log, forward over a socket, or update a UI just as well.
PromptHost
How a process answers Approve::Prompt while it executes a task’s turns, and whether there is anyone there to ask at all.

Functions§

current_task
This process’s own task, if it is itself running as one — read from BASIS_TASK_ID. None for a value that is absent, blank, or does not fit the task-handle grammar: basis’s own runtime always sets a well-formed one, so a malformed value is not basis’s, and treating it as “no current task” is the safer reading of somebody else’s environment variable.
now_ms
Milliseconds since the epoch, clamped rather than panicking on a clock before it. The one clock every durable record here is stamped with — created_ms, updated_ms, a cancel marker’s requested_ms, a message’s created_ms — and what a host rendering one of those alongside its own notion of “now” (basis list’s own age column, for one) should read now from, so the two clocks cannot disagree about the epoch.
probe_state
The task’s honest state while unfinished: running only when a live executor observably holds the attach lock, resumable otherwise. The same two-fact derivation Tasks::wait and Tasks::watch settle a timeout’s attached field with, so all three answer “what is this task doing” the same way.
validate_approval
Prompt is answerable exactly when a process is driving the task and has somewhere to put the question — see PromptHost. Always and Never ask nobody, so they need no host at all.