docs: add Trellis planning and project specs

This commit is contained in:
2026-07-01 06:27:55 -07:00
parent 7f227c5f2a
commit aafd9caaac
349 changed files with 55801 additions and 0 deletions

View File

@@ -0,0 +1,480 @@
# Command Reference
Authoritative current command reference for `trellis channel` subcommands,
validated against the source in `packages/cli/src/commands/channel/`
(`index.ts` Commander wiring and each subcommand handler).
Every subcommand accepts `--scope <project|global>` unless noted; `project`
is the default and resolves against the current cwd's project bucket.
## Top-level
```
trellis channel <subcommand>
```
> Multi-agent collaboration runtime — spawn / coordinate / interrupt worker
> agents through a shared event log.
---
## Create / List
### `create <name>`
```bash
trellis channel create <name>
[--scope project|global] # default: project
[--type chat|forum] # default: chat
[--task <path>] # associated Trellis task dir
[--project <slug>]
[--labels a,b,c]
[--description <text>] # stable channel description
[--context-file <abs-path>] ... # repeatable
[--context-raw <text>] ... # repeatable
[--linked-context-file <abs-path>] # [deprecated alias]
[--linked-context-raw <text>] # [deprecated alias]
[--cwd <path>] # recorded in create event
[--by <agent>] # default: main
[--force] # overwrite existing channel
[--ephemeral] # hide from default list, prunable
```
Behavior:
- Appends a `create` event; immutable `type` (cannot mutate forum↔chat after).
- `--ephemeral` channels are hidden from `channel list` by default and are
the sweep target for `channel prune --ephemeral`.
- `--linked-context-*` are folded into `--context-*`; emit a deprecation
notice when used.
### `list`
```bash
trellis channel list
[--scope project|global]
[--json]
[--project <slug>] # substring match on task field
[--all] # include ephemeral (suffix '*')
[--all-projects] # scan every project bucket
```
Behavior:
- Default scope: current cwd's project. `--all-projects` scans every bucket.
- Pretty mode prints `NAME WORKERS EVENTS LAST KIND TYPE TASK`, sorted by
recency, with a footer noting hidden ephemeral count.
- `--json` switches to a JSON array.
---
## Chat Messages
### `send <name> [text]`
```bash
trellis channel send <name> [text]
--as <agent> # REQUIRED — author
[--scope project|global]
[--to <agents,csv>] # default: broadcast
[--stdin | --text-file <path>] # body from stdin or file
[--delivery-mode appendOnly|requireKnownWorker|requireRunningWorker]
```
Behavior:
- Body precedence: positional `[text]``--stdin``--text-file`.
- `--to` with one entry stores a string; multiple stores an array; omitted
means broadcast.
- `--delivery-mode` selects targeted-delivery validation:
- `appendOnly` (default-ish — just record),
- `requireKnownWorker` (the named target must have a `spawned` event),
- `requireRunningWorker` (the worker must currently be live).
- Prints the appended event as one JSON line on stdout.
> **Note:** `send` has **no** `--tag` and **no** `--kind` flag. See
> [`tag-vs-kind`](#tag-vs-kind--how-event-shape-is-actually-controlled) below.
### `messages <name>`
```bash
trellis channel messages <name>
[--scope project|global]
[--raw] # one JSON event per line
[--follow] # stream new events
[--last <N>] # last N matching events
[--since <seq>] # seq > N
[--kind <kind>] # one of CHANNEL_EVENT_KINDS
[--from <csv>] # author filter
[--to <target>] # routing target filter
[--thread <key>] # forum-only
[--action <thread-action>] # forum-only
[--no-progress] # hide progress events
```
Behavior:
- Auto-detects forum channels: with no filters it renders the thread board
instead of the event stream. `--thread` / `--action` are forum-only and
error against chat channels.
- `--kind` is validated against `CHANNEL_EVENT_KINDS` (single value, not
CSV — that's the `wait` side).
### `wait <name>`
```bash
trellis channel wait <name>
--as <agent> # REQUIRED — self for filter ctx
[--scope project|global]
[--timeout <Ns|Nm|Nh|Nms>] # parsed by parseDuration
[--from <a,b>] # author CSV
[--kind <k1,k2>] # CSV, OR semantics
[--thread <key>] # forum filter
[--action <thread-action>] # forum filter
[--to <target>] # default: own agent (broadcast + me)
[--include-progress] # also wake on progress events
[--all] # require every --from to match
```
Behavior:
- Streams matching events as JSON, one per line.
- Default `--to` filter is the caller's own agent (broadcast events still
match — broadcast + explicit-to-me).
- `--all` requires `--from` and blocks until every listed agent has produced
a matching event.
- **Timeout exits 124** and prints `timeout: still waiting on ...` to stderr
when `--all` was in play.
---
## tag-vs-kind — how event shape is actually controlled
There is **no `--tag` flag** anywhere in the v0.6.0 channel CLI; `--kind` is
not a legacy alias for any `--tag` flag.
Concrete model in the current source:
- `--kind` is the only event-type filter, and it is constrained to the
trellis-emitted whitelist (`CHANNEL_EVENT_KINDS` in
`packages/core/src/channel/internal/store/events.ts`):
- `create`, `join`, `leave`, `message`, `thread`, `context`, `channel`,
`spawned`, `killed`, `respawned`, `progress`, `done`, `error`,
`waiting`, `awake`, `undeliverable`, `interrupt_requested`,
`turn_started`, `turn_finished`, `interrupted`, `supervisor_warning`
- Passing anything else throws
`Invalid --kind '<x>'. Must be one of: …`.
- `--kind` lives on `wait` (CSV, OR semantics) and `messages` (single
value). `send` and `run` cannot emit a custom kind — every `send` writes
a `message` event.
- Mid-turn worker abort is **not** a tag. It is the dedicated
`channel interrupt` command, which appends an `interrupt_requested` /
`interrupted` pair and provider-level interrupts the worker.
Practical rule for dispatchers waiting on workers:
- Use `--kind done,turn_finished` for "worker finished a turn" — these are
system events that the supervisor fires automatically. Do not depend on
the worker LLM remembering to emit any custom signal.
- Use `trellis channel interrupt` (the command) only when you actually want
mid-turn abort behavior.
- Do **not** invent user-side tags as completion signals. There is no
`--tag` filter; a worker writing a custom string into its final message
is just text inside a `message` event and cannot be matched by `wait`.
Long bodies always go through stdin or a file:
```bash
trellis channel send T --as A --stdin < /tmp/message.md
trellis channel send T --as A --text-file /tmp/message.md
```
---
## Interrupt
### `interrupt <name> [text]`
```bash
trellis channel interrupt <name> [text]
--as <agent> # REQUIRED — caller
--to <agent> # REQUIRED — target worker
[--scope project|global]
[--stdin | --text-file <path>]
```
Behavior:
- Appends an `interrupt` event with `reason: "user"` and a replacement
instruction body; supervisor performs provider-level interrupt where
supported (Claude `/interrupt`, Codex turn cancel).
- Prints the appended event JSON on stdout.
---
## Workers
### `spawn <name>`
```bash
trellis channel spawn <name>
[--scope project|global]
[--agent <agent-name>] # loads .trellis/agents/<name>.md
[--provider claude|codex] # overrides agent file
[--as <worker-name>] # default: agent name
[--cwd <path>]
[--model <id>]
[--resume <id>] # session/thread id resume
[--timeout <Ns|Nm|Nh>] # auto-kill after duration
[--warn-before <Ns|Nm|Nh>] # supervisor_warning lead time
# default 5m, 0ms disables
[--file <path>] ... # glob, repeatable; inject content
[--jsonl <path>] ... # Trellis manifest, repeatable
[--by <agent>] # spawn-event author
# default: TRELLIS_CHANNEL_AS env or 'main'
[--inbox-policy explicitOnly|broadcastAndExplicit]
# default explicitOnly
[--idle-timeout <Ns|Nm|Nh>] # OOM-guard idle TTL
# default 5m, 0 disables
[--max-live-workers <n>] # spawn-time live-worker budget
# default 6, 0 disables
```
Behavior:
- Provider is validated against the adapter registry
(`packages/cli/src/commands/channel/adapters/`); current: `claude`,
`codex`.
- Worker stays inbox-idle until the first `send --to <worker>`.
- Records a `spawned` event with `pid`, `provider`, `agent`, `files`,
`manifests`.
- OOM-guard precedence: CLI flag → env var
(`TRELLIS_CHANNEL_WORKER_IDLE_TIMEOUT`,
`TRELLIS_CHANNEL_MAX_LIVE_WORKERS`) →
`.trellis/config.yaml#channel.worker_guard` → built-in defaults.
### `run [name]`
```bash
trellis channel run [name?]
[--agent <name>]
[--provider claude|codex]
[--as <worker-name>]
[--cwd <path>]
[--model <id>]
[--file <path>] ... # repeatable, glob
[--jsonl <path>] ... # repeatable
[--message <text> | --message-file <path> | --stdin]
[--timeout <Ns|Nm|Nh>] # default 5m
```
Behavior:
- One-shot. Auto-generates `run-<hex>` if `name` omitted.
- Creates an ephemeral channel (`createMode=run`), spawns a single worker,
sends the prompt, waits for `done`, prints the final assistant text to
stdout, then removes the channel on success. On failure the channel is
kept for inspection and exit code is 1.
> `run` has **no** `--tag` flag. Completion is detected via the `done`
> event the supervisor emits.
### `kill <name>`
```bash
trellis channel kill <name>
--as <agent> # REQUIRED — worker agent name
[--scope project|global]
[--force] # SIGKILL immediately
```
Behavior:
- Default path: SIGTERM → 8 s grace → SIGKILL escalation; the CLI writes a
`killed` event when SIGKILL was needed so the log stays truthful.
- Cleans `pid`, `worker-pid`, `config`, `spawnlock` sidecar files; keeps
`log`, `session-id`, `thread-id` for forensics / resume.
### `rm <name>`
```bash
trellis channel rm <name>
[--scope project|global]
```
Behavior:
- Kills any live workers, then deletes the entire channel directory.
- Prints `Removed channel '<name>'`.
### `prune`
```bash
trellis channel prune
[--scope project|global] # omitted: scan every project
[--all | --empty | --idle <Ns|Nm|Nh|Nd> | --ephemeral] # mutually exclusive
[--yes] # actually delete (default: dry-run)
[--dry-run] # default true; redundant with default
[--keep <names,csv>] # exclusion list
```
Behavior:
- Filter flags are mutually exclusive — error otherwise.
- Default is dry-run; `--yes` flips to real delete.
- Without `--scope`, scans **every** project bucket (intentional, repo-wide
cleanup); with `--scope project|global`, limited to that bucket.
- Live-worker channels are always skipped regardless of filter.
- Output: per-candidate line `name last-ts (reason)` plus a final summary.
---
## Forum Channels
### `post <name> <action>`
```bash
trellis channel post <name> <action>
--as <agent> # REQUIRED
[--scope project|global]
[--thread <key>] # required except action=opened
[--title <text>]
[--text <text> | --stdin | --text-file <path>]
[--description <text>] # stable thread description
[--status <status>]
[--labels a,b] # REPLACES thread labels
[--assignees a,b] # REPLACES assignees
[--summary <text>]
[--context-file <abs-path>] ...
[--context-raw <text>] ...
[--linked-context-file <abs-path>] # [deprecated alias]
[--linked-context-raw <text>] # [deprecated alias]
```
Behavior:
- `<action>` is free-form on the CLI surface; conventional values include
`opened`, `comment`, `status`, `labels`, `assignees`, `summary`,
`processed`.
- `action=rename` is rejected — use `thread rename` instead.
- `--labels` / `--assignees` are replace-semantics, not append.
- Output: appended event JSON on stdout.
### `forum <name>`
```bash
trellis channel forum <name>
[--scope project|global]
[--status <status>]
[--raw]
```
Behavior:
- Lists threads (reduced state). `--status` filters by current thread
status. `--raw` prints one JSON per thread.
### `thread <name> <thread>` / `thread rename`
```bash
trellis channel thread <name> <thread-key>
[--scope project|global]
[--raw]
trellis channel thread rename <name> <old-thread> <new-thread>
--as <agent> # REQUIRED
[--scope project|global]
```
Behavior:
- `thread <name> <key>` shows one thread's timeline:
header `<thread> [<status>] <title>`, then description / labels /
assignees / summary / timeline lines. `--raw` switches to raw events.
- `thread rename` is the only mutation; `post --action rename` is rejected.
---
## Context / Title
### `context add` / `context delete` / `context list`
```bash
trellis channel context add <name>
[--as <agent>] # default: main
[--scope project|global]
[--thread <key>] # thread-level instead of channel-level
[--file <abs-path>] ... # repeatable
[--raw <text>] ... # repeatable
# at least one of --file or --raw
trellis channel context delete <name>
[--as <agent>] # default: main
[--scope project|global]
[--thread <key>]
[--file <abs-path>] ...
[--raw <text>] ...
trellis channel context list <name>
[--scope project|global]
[--thread <key>]
[--raw] # one JSON entry per line
```
Behavior:
- `add` / `delete` append a `context` event and print the event JSON.
- `list` projects current context entries; pretty output is
`file <path>` / `raw <truncated text>` lines, `(no context)` when empty.
### `title set <name>` / `title clear <name>`
```bash
trellis channel title set <name>
--title <text> # REQUIRED
[--as <agent>] # default: main
[--scope project|global]
trellis channel title clear <name>
[--as <agent>] # default: main
[--scope project|global]
```
Behavior:
- Appends a `title` event projecting a stable display title onto the
channel. Output: event JSON.
---
## Hidden / Internal
| Command | Purpose |
|---|---|
| `channel __supervisor <channel> <worker> <config>` | Forked entry point invoked by `spawn`. Do not invoke directly. |
| `channel __parse-trace <adapter> <file>` | Dev helper — replays a recorded stream-json / wire trace through the matching adapter and prints the resulting channel events. Adapter is validated against the provider registry. |
---
## Event Model
`CHANNEL_EVENT_KINDS` (whitelist enforced by `parseChannelKind`):
`create`, `join`, `leave`, `message`, `thread`, `context`, `channel`,
`spawned`, `killed`, `respawned`, `progress`, `done`, `error`, `waiting`,
`awake`, `undeliverable`, `interrupt_requested`, `turn_started`,
`turn_finished`, `interrupted`, `supervisor_warning`.
`MEANINGFUL_EVENT_KINDS` (default-visible subset used by `wait` /
`messages` when no explicit `--kind` is given):
`create`, `join`, `leave`, `message`, `thread`, `context`, `channel`,
`spawned`, `killed`, `respawned`, `done`, `error`.
Non-meaningful kinds (e.g. `progress`, `waiting`, `awake`,
`supervisor_warning`, the `turn_*` / `interrupt*` set) still flow through
the store; opt in via `--kind` or `--include-progress`.
Forum channels are event-sourced; use the CLI reducers
(`forum`, `thread`, `context list`) for state projection.
---
## Output Conventions
- **Mutations** (`send`, `interrupt`, `post`, `context add/delete`,
`title set/clear`, `thread rename`) print the appended event as one JSON
line on **stdout**.
- **Streaming reads** (`wait`, `messages --follow`) print one JSON event
per line on stdout.
- **Pretty reads** (`list`, `messages`, `forum`, `thread`, `context list`)
print colored, padded tables / timelines.
- **`run`** prints only the final assistant text on stdout (so callers can
pipe); diagnostic notes go to stderr.
- **Errors** go through `chalk.red("Error:")` to stderr and `exit 1`.
- **`wait` timeout** specifically exits **124**.

View File

@@ -0,0 +1,233 @@
# Forum Channels
Forum channels are durable, topic-style channels. They are created with
`--type forum` at channel-creation time and are immutable after that. They are
not normal chat streams: the default read path is
**forum summary -> one thread timeline -> current context**.
## Forum vs Regular Channel
A channel's type is set with `--type` on `channel create` and never changes:
- `chat` (default) — flat message timeline. `channel messages` always renders
the event stream. Forum-only flags such as `--thread` and `--action` are
rejected here.
- `forum` — thread-oriented. `channel messages` without filters renders a
thread-board summary instead of raw events. The `post`, `forum`, `thread`,
and `thread rename` subcommands only apply to forum channels.
Both types share the same scope model (`--scope project` is the default;
`--scope global` puts the channel in the cross-project bucket).
## Create A Forum Channel
```bash
trellis channel create design-feedback \
--type forum \
--scope global \
--description "Cross-project design feedback board." \
--context-raw "One thread per design topic; close when resolved." \
--by main
```
Use `--scope project` for a board scoped to one repo, `--scope global` for a
cross-project board.
## Threads: Open, Comment, Status, Summary
Threads live inside a forum channel. Each thread is identified by a stable
`--thread <key>` (lowercase kebab-case is conventional). The first action on
a thread is `opened`; everything afterwards uses the same `--thread` key.
```bash
trellis channel post design-feedback opened \
--scope global \
--as main \
--thread login-empty-state \
--title "Empty state on the login screen" \
--description "Track design feedback for the new login empty state." \
--labels design,login \
--context-raw "Spotted during the 0.4 release review." \
--text-file /tmp/thread-open.md
trellis channel post design-feedback comment \
--scope global \
--as reviewer \
--thread login-empty-state \
--text-file /tmp/review.md
trellis channel post design-feedback status \
--scope global \
--as main \
--thread login-empty-state \
--status closed
trellis channel post design-feedback summary \
--scope global \
--as main \
--thread login-empty-state \
--summary "Adopted the option-B layout; ticket TRELLIS-123 owns the fix."
```
Key distinctions:
- `--description` is the **durable** thread description (the answer to "what
is this thread about?"). It is set on `opened` and edited by re-running
`post` with `--description`.
- `--text` / `--stdin` / `--text-file` is the **event body** — the comment or
payload attached to this specific timeline entry.
- `--labels` and `--assignees` are CSV and **replace** the current value; they
do not append.
- `--summary` is the rolling thread summary. Setting it on `status closed` is
the standard way to mark a thread resolved with context.
`--thread` is required for every action except `opened` (where it is also
required in practice — there is no anonymous thread).
## Read A Forum
```bash
trellis channel messages design-feedback --scope global
trellis channel forum design-feedback --scope global --status open
trellis channel thread design-feedback login-empty-state --scope global
trellis channel messages design-feedback --scope global --raw --thread login-empty-state
```
If a peer says "I commented on the forum", run `channel forum` first to see
which thread changed, then drill into that thread with `channel thread <name>
<thread>`. Do not jump straight to ad-hoc `events.jsonl` parsing.
## Context
Context entries are durable background that should always be in scope when
reading a channel or a thread. They are **not** timeline events; they are
projected separately and replayed for every reader.
Use the `context` subcommands. The legacy `--linked-context-file` /
`--linked-context-raw` flags on `create` and `post` are deprecated aliases
that fold into the canonical `--context-file` / `--context-raw`.
### Add Context
```bash
# Channel-level context (whole forum)
trellis channel context add design-feedback \
--scope global \
--raw "Upstream feedback board; please link tasks before opening threads."
# Thread-level context (one thread)
trellis channel context add design-feedback \
--scope global \
--thread login-empty-state \
--file "$PWD/.trellis/tasks/05-13-login-redesign/design.md"
```
- `--thread <key>` switches between channel-level and thread-level context.
- `--file` paths **must be absolute**; relative paths are rejected.
- `--raw` is plain text inline content.
- Both flags are repeatable; at least one is required for `add` / `delete`.
- `--as <agent>` records authorship; defaults to `main`.
### List Context
```bash
trellis channel context list design-feedback --scope global
trellis channel context list design-feedback --scope global --thread login-empty-state --raw
```
`--raw` on `list` emits one JSON entry per line (useful for piping); without
it you get a human-readable `file <path>` / `raw <truncated text>` listing.
An empty store prints `(no context)`.
### Delete Context
```bash
trellis channel context delete design-feedback \
--scope global \
--thread login-empty-state \
--raw "stale note"
```
You delete by **value**, not by id: pass the same `--file` or `--raw` value
that was added. Repeat the flag to delete multiple entries in one call.
### Reading Order
When reading a thread, work top-down:
1. Thread `description` (the durable "what is this about").
2. Context entries (channel-level + thread-level).
3. Timeline (`opened`, `comment`, `status`, `summary`).
If a context file is missing or unreadable, state that explicitly and
continue with the remaining data — do not fabricate the content.
## Title Projection
`title` projects a stable display title onto the channel without renaming the
storage address. The channel `name` you pass to every command stays the same.
```bash
trellis channel title set design-feedback \
--scope global \
--title "Design feedback board"
trellis channel title clear design-feedback --scope global
```
- `title set` requires `--title`.
- `--as <agent>` records authorship; defaults to `main`.
- This is a presentation-layer change. Tooling and scripts keep using the
original channel name.
## Thread Rename
`thread rename` is the correction path when a thread was opened with the
wrong key (typo, wrong slug convention, etc.). Threads do not support hard
deletion — rename is the supported corrective action.
```bash
trellis channel thread rename design-feedback old-key new-key \
--scope global \
--as main
```
- `--as <agent>` is **required**.
- `post <name> rename` is rejected — you must use `thread rename`.
## Deletion Discipline
Do not model single-comment deletion or hard thread deletion as normal
workflow. Forum threads are append-only collaboration history. To correct
state, use:
- `post ... status` to mark a thread closed / blocked / etc.
- `post ... summary` to record the resolution.
- `post ... --labels` to re-label (replaces the set).
- `thread rename` to correct a bad thread key.
## Internal Changelog Pattern
A common use of a global forum channel is an internal release / runtime
changelog. One thread per notable change keeps history searchable:
```bash
trellis channel create release-notes \
--type forum \
--scope global \
--description "Internal release and runtime changelog." \
--context-raw "One thread per notable change; close when shipped." \
--by main
trellis channel post release-notes opened \
--scope global \
--as main \
--thread release-2026-q1 \
--title "Channel threads and forum UX in 0.6" \
--description "Forum channel UX shipped in the 0.6 line." \
--labels channel,release \
--text-file /tmp/release-notes.md
```
Use stable, descriptive thread keys (e.g. `release-2026-q1`,
`runtime-event-schema-change`) so later readers can find them by name.

View File

@@ -0,0 +1,226 @@
# Progress And Debugging
Pretty output is for operators. Raw output is the audit log. Subcommands
(`forum`, `thread`, `messages`, `context`) are the audit *interface* — reach
for them before grepping `events.jsonl` by hand.
## Pretty vs `--raw`
`trellis channel messages <channel>` renders a compact, human-readable view:
timestamps, identities, kind, and a short body. It is meant for operators
scanning a channel, not for diagnostics.
Pretty output can and will truncate:
- long progress deltas (`text_delta`, partial tool args)
- tool names and command lines
- multi-line status fields and structured `detail` blobs
- forum thread titles past the column budget
When something looks "off" — a worker appears stuck, a progress line ends
mid-word, an action field shows `...` — switch to `--raw`. Raw mode emits
one JSON event per line exactly as it lives in `events.jsonl`, so nothing
is dropped.
```bash
# Pretty (operator view)
trellis channel messages <channel> --kind done --last 10
trellis channel messages <channel> --kind error --last 10
# Raw (diagnostic view) — one JSON per line
trellis channel messages <channel> --raw --kind progress --last 20
trellis channel messages <channel> --raw --last 50
```
Rule of thumb: never diagnose a worker from a truncated progress line.
### Rebuild Streaming Text
To reconstruct what a model actually streamed during a turn, concatenate
`detail.text_delta` from progress events:
```bash
trellis channel messages <channel> --raw --kind progress --last 80 \
| python3 -c 'import json,sys; [print((json.loads(l).get("detail") or {}).get("text_delta",""), end="") for l in sys.stdin if l.strip()]'
```
## Stalled Worker Diagnosis
Symptom: `trellis channel list` shows the worker as running, but no new
events appear in `messages` and `wait` keeps timing out.
Triage order:
1. **Locate the channel files.** Use `list --all --all-projects` if you are
not sure which bucket the channel lives in.
```bash
trellis channel list --all --all-projects
CHAN=~/.trellis/channels/<bucket>/<channel>
```
2. **Confirm the supervisor and worker PIDs are alive.**
```bash
cat "$CHAN/<worker>.pid" # supervisor PID
cat "$CHAN/<worker>.worker-pid" # actual CLI subprocess PID
ps -p "$(cat "$CHAN/<worker>.pid")"
ps -p "$(cat "$CHAN/<worker>.worker-pid")"
```
If the supervisor PID is gone but the channel still lists the worker,
you have a ghost entry — clean it with
`trellis channel kill <name> --as <worker> --force`.
3. **Tail the worker log.** This is the canonical place to see provider /
MCP / tool startup output that never makes it onto the channel.
```bash
tail -f "$CHAN/<worker>.log"
```
4. **Check the last raw events.** A worker that emitted `progress` but no
`message`/`done` is usually mid-stream or blocked on a tool call:
```bash
trellis channel messages <channel> --raw --last 50
```
Common "alive but silent" causes:
- Provider cold start before the first token (long, but eventually moves).
- A blocking MCP server during startup — visible in the worker log.
- Worker is waiting for a tool result whose subprocess hung.
- Prompt is huge / model is rate-limited; check provider-side errors in the
worker log.
## Progress Event Interpretation
A `progress` event represents an in-flight piece of work. Its shape varies
by `action` field, but the load-bearing fields are always under `detail`:
- `detail.text_delta` — incremental model output (concatenate across events
to rebuild the streamed reply).
- `detail.tool_name`, `detail.tool_input` — tool call about to run or
currently running.
- `detail.status` — short string used by long-running actions
(`starting`, `running`, `flushing`, `done`).
- `detail.action` — semantic label (e.g. `status` for thread heartbeats).
Progress events are **noisy** by design. `wait` ignores them unless you
pass `--include-progress`. When you do want to see them, prefer:
```bash
trellis channel messages <channel> --raw --kind progress --last 80
```
A stream that emits progress at a steady cadence but never closes with
`done`/`error`/`message` is the classic shape of a hung tool call —
inspect the worker log for the subprocess.
## Wait Semantics (Quick Reference)
`channel wait` watches `events.jsonl` from EOF and wakes on:
- `message`
- `done`
- `error`
- `killed`
- `progress` only with `--include-progress`
Useful filters:
```bash
trellis channel wait T --as main --from check --kind done --timeout 15m
trellis channel wait T --as main --from check,check-cx --kind done --all --timeout 15m
trellis channel wait T --as worker --tag interrupt --timeout 1h
trellis channel wait T --as main --thread release-note --action status --timeout 10m
```
Exit codes: `0` matched, `124` timeout, `1`/`2` errors. On `wait --all`
timeout, stderr names the workers still missing.
## Auditing `events.jsonl` — Use Subcommands, Not `grep`
Every channel persists its full history at `$CHAN/events.jsonl`. It is
tempting to `tail` / `grep` / `jq` this file directly during debugging.
Don't make it a habit, and **never** do it for forum channels.
Why subcommands first:
- `messages` already replays the file with filters (`--kind`, `--from`,
`--last`, `--tag`, `--thread`, `--action`) and gives you `--raw` for the
exact JSON. Anything you would write a one-liner for, `messages` already
does.
- `wait` consumes the same file with EOF semantics — re-implementing that
with `tail -f | jq` will drop events under load and misorder them under
rotation.
- `context` materializes a worker's inbox view, including cursor state.
Hand-rolled filters do not respect `<worker>.inbox-cursor`.
### Forum channels: never parse `events.jsonl` directly
Forum channels multiplex many logical threads onto a single `events.jsonl`.
Each event carries `thread`, `action`, and tag fields that the forum
subcommands know how to fold together. Parsing the file by hand will:
- Mix threads together and make a thread look incoherent.
- Miss thread lifecycle events (open / status / close) that change how
later events should be interpreted.
- Ignore worker inbox cursors, so you will "see" events a worker has
already consumed and assume they are pending.
Use the forum-aware views instead:
```bash
# List logical threads inside the forum channel
trellis channel forum list <channel>
# Inspect one thread end-to-end
trellis channel thread show <channel> <thread>
# Replay messages for a thread (supports --raw, --kind, --last)
trellis channel messages <channel> --thread <thread> --raw --last 100
# What a specific worker still has pending
trellis channel context <channel> --as <worker>
```
Direct reads of `events.jsonl` are reserved for the case where the CLI
itself is suspect — e.g. confirming an event was actually persisted, or
diffing against `<worker>.inbox-cursor` while debugging the supervisor.
## Common Failures
| Symptom | Cause | Fix |
|---|---|---|
| `trellis: command not found` | CLI not installed globally | `npm install -g @mindfoldhq/trellis` |
| `wait` exits immediately | wrong filter or identity collision | use distinct `--as`, inspect raw messages |
| zsh errors on message text | shell interpreted punctuation | use `--stdin` or `--text-file` |
| progress line is cut off | pretty output truncation | use `messages --raw --kind progress` |
| worker never speaks | provider startup / prompt / MCP delay | inspect `<worker>.log`, `ps`, raw events |
| channel not found in another cwd | project bucket mismatch | `cd` to project, use `--scope global`, or `list --all-projects` |
| ghost worker in list | supervisor died without cleanup | `trellis channel kill <name> --as <worker> --force` |
| forum thread looks scrambled | parsed `events.jsonl` directly | use `forum`, `thread`, `messages --thread` |
## Storage Layout
```text
~/.trellis/channels/
└── <bucket>/
└── <channel-name>/
├── events.jsonl
├── <channel>.lock
├── <worker>.log
├── <worker>.pid
├── <worker>.worker-pid
├── <worker>.config
├── <worker>.session-id
├── <worker>.thread-id
├── <worker>.inbox-cursor
└── <worker>.spawnlock
```
Agents normally use the CLI, not direct file reads. Direct file reads are
for debugging when CLI views are insufficient — and even then, never on a
forum channel's `events.jsonl`.

View File

@@ -0,0 +1,276 @@
# Workers And Agent Cards
Use workers when a peer agent should execute independently and report back
through the channel event log. A worker is a registered child process (claude
or codex) attached to a channel; the supervisor forwards inbox messages to it
and translates its output back into channel events.
## Spawn
```bash
trellis channel create impl-task --by dispatcher --cwd /path/to/repo
trellis channel spawn impl-task --provider codex --as codex-impl --timeout 30m
echo "Implement the schema for table X per .trellis/.../prd.md" \
| trellis channel send impl-task --as dispatcher --to codex-impl --stdin
trellis channel wait impl-task --as dispatcher --from codex-impl --kind done --timeout 30m
```
`spawn` forks a `channel __supervisor` worker that emits `spawned`, streams
`progress`, and should end with `done`, `error`, or `killed`. Workers stay
inbox-idle until a `send --to <worker>` (or a broadcast when
`--inbox-policy broadcastAndExplicit` is set) wakes them.
Key `spawn` flags:
- `--agent <name>` — load `.trellis/agents/<name>.md` (provider/model/as/system prompt defaults).
- `--provider <claude|codex>` — overrides the agent card; validated against the adapter registry.
- `--as <name>` — channel worker handle; defaults to the agent name.
- `--cwd <path>` — worker working directory (also the jail root for `--file`/`--jsonl`).
- `--model <id>` — model override.
- `--resume <id>` — resume an existing claude session / codex thread.
- `--timeout <duration>` — auto-kill after `30s` / `2m` / `1h`.
- `--warn-before <duration>` — supervisor_warning lead time (default `5m`; `0ms` disables).
- `--file <path>` (repeatable, glob-supported) — inject file content into the system prompt.
- `--jsonl <path>` (repeatable) — Trellis jsonl manifest (`{file, reason}` per line).
- `--by <agent>` — author of the `spawned` event (defaults to `$TRELLIS_CHANNEL_AS` or `main`).
- `--inbox-policy <explicitOnly|broadcastAndExplicit>` — default `explicitOnly`.
- `--idle-timeout <duration>` — OOM guard idle TTL (default `5m`; `0` disables).
- `--max-live-workers <n>` — spawn-time live-worker budget (default `6`; `0` disables).
The success event `spawned` records `pid`, `provider`, `agent`, the injected
`files`, and the resolved `manifests` so later spectators can audit context.
## Agent Cards
`--agent <name>` resolves to `.trellis/agents/<name>.md`. The card name must
match `[A-Za-z0-9._-]+`. The default Trellis install ships two cards:
- `.trellis/agents/check.md` — code-quality reviewer.
- `.trellis/agents/implement.md` — coding worker for implementation runs.
```yaml
---
name: check
description: Code quality check expert.
provider: claude
---
```
Frontmatter fields populate `spawn` defaults (provider, model, `as`); the
markdown body becomes the worker's system-prompt role. Cards do **not**
auto-attach task files — context must be injected explicitly per spawn (see
below).
Always inspect project cards before spawning a named agent:
```bash
ls .trellis/agents
sed -n '1,100p' .trellis/agents/check.md
```
## Context Injection
Two flags inject content into the worker's system prompt under a
`# CONTEXT FILES` block, assembled by `context-loader`:
- `--file <path>` — repeatable, glob-supported (`*`, `**`). Each match is
read and concatenated.
- `--jsonl <path>` — repeatable Trellis manifest where every line is
`{"file":"<path>","reason":"<why>"}`. The reason is preserved as a header
comment above each file's content.
Limits enforced by the loader:
- 1 MB hard cap per file (oversize → error).
- 200 KB per-file warning to stderr.
- 500 KB total assembled-context warning to stderr.
- Path-traversal jail: all resolved paths must stay under `--cwd`.
Example spawning a check agent against a task directory:
```bash
TASK=.trellis/tasks/05-13-example
trellis channel spawn cr-example --agent check --provider codex --as check-cx \
--file "$TASK/prd.md" \
--file "$TASK/design.md" \
--file "$TASK/implement.md" \
--jsonl "$TASK/check.jsonl" \
--cwd "$PWD" --timeout 30m
```
The `spawned` event records both the literal `files` array and any `manifests`
expanded from `--jsonl`, so the audit trail captures whatever the worker was
actually shown.
## Names And Routing
`--as` has two meanings:
- `send` / `wait` / `interrupt`: speaker identity (author of the resulting event).
- `spawn`: the worker handle that other agents address with `--to`.
Use explicit names when multiple workers or providers participate in one
channel:
```bash
trellis channel spawn cr-feature --agent check --as check-claude
trellis channel spawn cr-feature --agent check --provider codex --as check-cx
trellis channel wait cr-feature --as main \
--from check-claude,check-cx --kind done --all --timeout 15m
```
`--all` requires `--from` and blocks until every listed worker has produced a
matching event; timeout exits with code **124** and prints
`timeout: still waiting on ...` to stderr.
## Soft Interrupt — `interrupt`
`channel interrupt` is the cooperative redirect: it appends an `interrupt`
event (reason `"user"`) and, where the adapter supports it, issues a
provider-level turn interrupt with a replacement instruction. Use it when the
worker should drop its current turn and act on new input immediately, without
losing its session.
```bash
echo "Stop refactoring the parser — switch to fixing the failing test in src/foo.ts" \
| trellis channel interrupt impl-task --as dispatcher --to codex-impl --stdin
```
Flags:
- `--as <agent>` **(required)** — caller identity.
- `--to <agent>` **(required)** — target worker.
- `--scope <project|global>` — channel scope.
- `--stdin` / `--text-file <path>` / `[text]` — replacement instruction body.
The appended event has `kind: "interrupt"` — downstream `wait` / `messages`
filters can subscribe with `--kind interrupt` to react to redirections (e.g.
to log the rerouting, or to gate other workers behind a coordinator's
correction).
For low-priority hints that should wait for the worker's next turn, send a
plain tagged message instead:
```bash
echo "Check this when you reach the next turn." \
| trellis channel send impl-task --as dispatcher --to codex-impl \
--stdin --tag question
```
## Hard Interrupt — `kill` + `--resume`
Use `kill` when the worker must stop **now** (e.g. runaway loop, bad
instructions already in flight, or `interrupt` is not honored by the
adapter). The supervisor escalates SIGTERM → 8 s grace → SIGKILL; the CLI
writes a `killed` event when SIGKILL is needed so the event log stays
truthful.
```bash
trellis channel kill impl-task --as codex-impl
trellis channel spawn impl-task --as codex-impl --provider codex \
--resume "$(cat ~/.trellis/channels/<bucket>/impl-task/worker.session-id)"
echo "STOP — new instructions: ..." \
| trellis channel send impl-task --as dispatcher --to codex-impl --stdin
```
`kill` flags:
- `--as <agent>` **(required)** — names the worker (positional `<name>` is the channel).
- `--scope <project|global>`.
- `--force` — SIGKILL immediately (also kills the inner worker pid).
Side effects: cleans `pid`, `worker-pid`, `config`, `spawnlock` sidecar
files; keeps `log`, `session-id`, `thread-id` for forensics and resume.
When `interrupt` will not converge, kill + `--resume` is the guaranteed
redirection path.
## Worker OOM Guard
The OOM guard prevents orphaned/idle workers from accumulating and exhausting
host resources. It runs at every `spawn` and enforces two policies per
project bucket:
- **Idle TTL** — sweep workers whose last activity is older than the
configured threshold (default `5m`; `0` disables).
- **Live-worker budget** — refuse the new spawn if more than N workers are
already alive in the same project bucket (default `6`; `0` disables).
Precedence (highest first):
1. CLI flags: `--idle-timeout`, `--max-live-workers` on `spawn`.
2. Environment variables: `TRELLIS_CHANNEL_WORKER_IDLE_TIMEOUT`,
`TRELLIS_CHANNEL_MAX_LIVE_WORKERS`.
3. `.trellis/config.yaml` under `channel.worker_guard`.
4. Built-in defaults (`5m`, `6`).
Cleanup notices are written to stderr at spawn time so operators can see which
idle workers were swept and why a new spawn was rejected. The guard does not
touch ephemeral / `channel run` workers any differently — they are subject to
the same idle TTL and budget.
To audit current state, list workers via `channel list` (the `WORKERS`
column) and inspect per-channel `pid` / `worker-pid` sidecar files under
`~/.trellis/channels/<bucket>/<channel>/`.
## Worker Inbox APIs
The inbox is the channel surface workers wake on. Routing is controlled by
two knobs:
- **Inbox policy** (`spawn --inbox-policy`):
- `explicitOnly` (default) — worker only wakes on `send --to <worker>` or
`interrupt --to <worker>`.
- `broadcastAndExplicit` — also wakes on broadcasts (`send` with no `--to`).
- **Delivery mode** (`send --delivery-mode`):
- `appendOnly` — append the event regardless of worker state.
- `requireKnownWorker` — fail if no worker named in `--to` was ever spawned.
- `requireRunningWorker` — fail if the named worker is not currently alive.
Stricter delivery modes prevent silent message loss when callers expect a
running peer.
Inbox-relevant subcommands:
- `send <channel> [text]` — append a `message` event.
- `--as <agent>` **(required)** — author.
- `--to <agents>` — CSV; one → string, many → array; broadcast if omitted.
- `--stdin` / `--text-file <path>` / `[text]` — body source.
- `--delivery-mode <appendOnly|requireKnownWorker|requireRunningWorker>`.
- `interrupt <channel> [text]` — soft-interrupt redirect (see above).
- `wait <channel>` — block until matching events arrive.
- `--as <agent>` **(required)** — `self` for filter context.
- `--from <agents>` — CSV authors.
- `--kind <kind[,kind...]>` — CSV (OR semantics); supports `interrupt`,
`done`, `progress`, etc.
- `--to <target>` — defaults to own agent (broadcast + explicit-to-me).
- `--include-progress` — also wake on progress events.
- `--all` — require every `--from` agent to match (timeout → exit **124**).
- `--timeout <duration>``30s` / `2m` / `1h` / `1000ms`.
- `messages <channel>` — view / filter / follow the event stream.
- `--follow` to tail, `--kind` / `--from` / `--to` to filter, `--raw` for
JSON-per-line, `--no-progress` to hide progress noise.
A typical dispatcher loop:
```bash
# 1. Wake the worker.
echo "Run the failing test and report." \
| trellis channel send impl-task --as dispatcher --to codex-impl --stdin \
--delivery-mode requireRunningWorker
# 2. Block until it finishes.
trellis channel wait impl-task --as dispatcher \
--from codex-impl --kind done,error --timeout 30m
# 3. Read the final answer.
trellis channel messages impl-task --from codex-impl --last 1 --raw
```
All event-emitting subcommands (`send`, `interrupt`, `post`, `context add` /
`delete`, `title set` / `clear`, `thread rename`) print the appended event as
a single JSON line on stdout, making the inbox layer easy to script against.

View File

@@ -0,0 +1,128 @@
# Workflows
Use these patterns by intent. Prefer durable channels for multi-round work and
`channel run` for one-shot questions.
## Pattern A: Multi-round Brainstorm
Use when the user says "和 codex/claude 讨论一下", "brainstorm", or "拉一个 agent
进来一起看".
```bash
trellis channel create brainstorm-storage-layer --by main \
--task .trellis/tasks/05-XX-storage-adapter
trellis channel spawn brainstorm-storage-layer \
--agent architect --provider codex \
--file .trellis/tasks/05-XX-storage-adapter/prd.md \
--file .trellis/tasks/05-XX-storage-adapter/design.md \
--as cx-arch --timeout 30m
trellis channel send brainstorm-storage-layer \
--as main --to cx-arch --text-file /tmp/brainstorm-r1.md
trellis channel wait brainstorm-storage-layer \
--as main --kind done --from cx-arch --timeout 10m
```
Do not stop after one answer. Read the answer, identify vague areas, send a
new probe, and repeat until the result is executable.
Minimum round structure:
1. Direction split: should this live in an existing mechanism or a new one?
2. MVP boundary: v1, v2, and what would force v2 back into v1.
3. Data contract: events, schema, metadata, state source of truth, compatibility.
4. CLI / UX contract: command names, flags, errors, defaults, ambiguity.
5. Cross-layer risk and tests: shared helpers, drift points, release-blocking tests.
Optional rounds:
- Operations: logs, debugging, stuck workers, kill/restart, recovery.
- Migration/release: breaking status, manifest, changelog, docs-site.
- Opposition review: ask the peer agent to argue against the current plan.
Every probe should request concrete file paths, commands, schema, rejected
alternatives, and release-blocking issues. Reject hedging when a decision is
needed.
## Pattern B: Implement / Check Agent
Use when the user asks to dispatch implementation or review work.
```bash
TASK=.trellis/tasks/05-12-foo
trellis channel create cr-foo --task "$TASK" --by main
trellis channel spawn cr-foo \
--agent check \
--jsonl "$TASK/check.jsonl" \
--file "$TASK/prd.md" \
--file "$TASK/design.md" \
--file "$TASK/implement.md" \
--cwd "$PWD" --timeout 15m
trellis channel send cr-foo --as main --to check --text-file /tmp/cr-brief.md
trellis channel wait cr-foo --as main --kind done --from check --timeout 15m
trellis channel messages cr-foo --kind message --from check --tag final_answer
```
For implement work, use `--agent implement` and send an implementation brief.
For check work, include the exact diff scope, relevant specs, and validation
already run.
## Pattern C: Parallel Reviewers
Use one channel and distinct worker names.
```bash
trellis channel create cr-feature --by main --ephemeral
trellis channel spawn cr-feature --agent check \
--jsonl "$TASK/check.jsonl" --file "$TASK/prd.md" --file "$TASK/design.md" \
--timeout 15m
trellis channel spawn cr-feature --agent check --provider codex --as check-cx \
--jsonl "$TASK/check.jsonl" --file "$TASK/prd.md" --file "$TASK/design.md" \
--timeout 15m
trellis channel send cr-feature --as main --to check --text-file /tmp/cr-brief.md
trellis channel send cr-feature --as main --to check-cx --text-file /tmp/cr-brief.md
trellis channel wait cr-feature --as main --kind done --from check,check-cx --all --timeout 15m
```
`--all` means every listed worker must emit a matching event.
## Pattern D: One-shot Worker
```bash
trellis channel run --provider codex --message "say hi in 3 words" --timeout 1m
trellis channel run --agent plan --message-file /tmp/plan-question.md --timeout 10m
```
On success, `run` removes the ephemeral channel. On error/timeout/killed, it
keeps the channel and prints the path for inspection.
## Pattern E: Forum Channel
Use for issue forums, topic-style feedback, release todos, agent findings, and
internal changelogs. Read `forum.md` for the full model.
## Pattern F: Take Over Existing Thread
If the user gives a forum/thread name, restore context yourself:
```bash
trellis channel forum <board> --scope global
trellis channel thread <board> <thread> --scope global --raw
trellis channel context list <board> --scope global --thread <thread>
trellis channel messages <board> --scope global --raw --thread <thread>
```
Output a constraint summary, not a transcript dump:
- user-level problem
- context files that affect this repo
- current-version versus future-version requirements
- whether current code/design satisfies it
- next action or comment to append