digital-roster 0.3.2

Rent the intelligence, own the governance — a control plane for workers: software colleagues whose every action passes through a gateway you control (default-deny egress, injected credentials, budgets, approval gates, audit).
# Work: the task management system

All work a worker does is a **task** in its partition of the task management
system (TMS) — one document per worker (`data/workers/<name>/tasks/
tasks.json`) holding pending tasks and recurring templates, plus an
append-only journal of everything finished. The dispatch loop inside
`roster server start` is a dumb executor over it: each tick it asks the TMS
who is due and runs one governed box per claimable task. Nothing works
inline, and the TMS is the only path into a container. (Chat conversations
take a warm shortcut — see [channels.md](channels.md) — but everything else
is a task.)

## How work arrives

- **You file it**: `roster worker task add dobby "…"`. Owner-standing work
  always runs, budget or no budget.
- **The heartbeat fires.** Every worker has a system recurring template —
  on by default, tuned with `heartbeat = "30m"` in its spec (`"off"`
  disables) — that wakes it to curate its task list and do what's due.
- **A recurring template fires.** Templates live in the partition itself
  (5-field cron, host-local time, optional window) and spawn ordinary
  pending tasks. The worker creates and retires its own via `set_tasks`;
  cursors persist, so a restart neither double-fires nor silently skips.
- **A message arrives.** Chat listeners file relay tasks framed as untrusted
  content — the message directs attention; it never commands (see
  [channels.md]channels.md). `roster worker task relay` does the same by
  hand.
- **The worker files it.** The `file_task` action adds one task (optionally
  scheduled with `at`); the `set_tasks` action reshapes the whole partition
  — reorder, reschedule, chain with `depends_on`, cancel, recur. Standing
  follows the room: filed at a trusted operator's ask → owner (always
  runs); autonomous or untrusted-context filings → proactive, paced by
  budget.
- **A gate resolves.** If the action's grant sets `wake_on_resolve`,
  resolution files a continuation task, and the next run starts briefed with
  the outcome.

`roster worker run` is the one exception: a governed session right now,
bypassing the TMS, for ad-hoc work and testing. Same box, same gateway,
same rules.

## Task lifecycle

```
pending → claimed → completed | failed | needs-review → journal
```

- Eligibility is derived, never stored: a pending task is claimable once
  its `scheduled_at` (if any) has passed and everything in `depends_on`
  completed. A dependency that failed or was canceled blocks its
  dependents until someone curates the list.
- **`needs-review`** — the box finished but left a gate pending; when the
  last gate resolves, the task completes.
- Over-budget proactive work is simply **late**: it stays claimable and
  runs when the window clears — there is no parked state.
- **`roster worker task requeue`** puts a claimed (dead-box) or
  needs-review task back to `pending`. A completed, failed, or canceled
  task lives in the journal; requeueing it re-files the same task as
  `pending` — the natural retry after fixing what broke it (a missing
  credential, a bad repo path).
- Every state change is host-attested — the worker never marks its own
  work done. It does *report*: a task run ends with `task_complete`, or
  `task_fail` and the reason. The report is evidence, not the verdict:
  a crash, a nonzero exit, or the ceiling is failure no matter what was
  claimed, and a run that ends silently after refused gateway calls (a
  spent budget, a revoked credential, a denied host) is attested failed
  with that evidence — a blocked worker's no-op never shows up as
  "completed".

Dispatch polls every couple of seconds and runs up to `--cap` boxes at once
(default 3). On startup it reclaims honestly: `claimed` tasks whose
container actually died go back to pending; live ones are left alone.
Restarting the daemon mid-flight loses nothing. With no model credential in
reach (the vault, a host pi login, or `ANTHROPIC_API_KEY`), dispatch holds —
tasks stay `pending` instead of failing on arrival — and resumes on its own
once a credential appears.

Every run has a wall-clock ceiling — the container is killed when it
expires. Defaults: 30 minutes for filed tasks and ad-hoc runs, 20 for
recurring templates, 15 for relays; override per task with `--ceiling`.

## The worker's own view

Every run mounts the partition read-only at `$HOME/self/schedule.json`
(`$ROSTER_TASKS_FILE`) — a live view the host rewrites on every change.
The worker reads it freely; writes go through `set_tasks` with optimistic
concurrency (send back the `version` you read; on conflict re-read and
retry). The heartbeat template is `system: true` and host-owned — the one
entry the worker cannot touch.

## Code tasks

Code work rides the same connections as everything else — no special task
kind. A repo on a gateway-reachable remote with a push grant: the worker
clones into `workspace/`, edits, and pushes a branch through the gateway
(keep `main` protected so review stays the merge gate). A repo on this
host: grant it as a gated host-repo connection and changes land through
the validated `repo_push` action ([repos.md](repos.md)) — the box never
holds a push credential either way.

## Reorganization tasks

```bash
roster worker task add dobby "reorganize the knowledge repo around topics"
```

Restructuring knowledge is an ordinary task now: every clean run gets its
own branch of the knowledge repo and full agency over the layout; pushes
land serialized, fast-forward only. See [repos.md](repos.md).

## Runs: the permanent record

Every session — queued, ad-hoc, or chat — is a **run** with a durable
record. Run ids are timestamps with a random suffix
(`2026-07-10-21-51-17-a3b3`); any unique prefix works in commands.

```bash
roster server runs ls --worker dobby
roster server runs show 2026-07-10       # transcript, journal, knowledge
                                          # commits, files, how it ended
roster server runs context <run>         # the exact compiled prompts
roster server runs recall <run>          # why each memory was included
```

Each worker mounts its own complete run history read-only at
`$HOME/self/runs/` — transcripts, compiled prompts, outcomes, raw — so
"what did I do last week" is a question a worker can answer itself.

A run record answers how the session ended (`done`, `ceiling`, `error`,
`idle`), its knowledge access and what commit it pushed, what it
proposed, and what everything cost. The per-worker journal threads the story
across runs: what the box did, what was gated, who decided, what the
executor reported. Nothing here is model-written; it's all computed from
what actually happened.