Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cron/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ Isolation: **board** is the hard boundary — workers get `HERMES_KANBAN_BOARD`
cannot see other boards; **tenant** is a soft namespace within a board (workspace-path + memory-key
isolation, one fleet serving several businesses). After `kanban.failure_limit` consecutive
non-success attempts on a task (default 2) the dispatcher auto-blocks it to stop spin loops.
`kanban.max_in_progress` limits concurrent `running` tasks. Its value is coerced to an integer;
failed conversions or results below 1 are ignored (`0` does not pause spawning). An unset or
ignored value uses a memory-derived default of 2–8 workers when total host memory is known,
otherwise no derived cap. Explicit positive values override that default.
Process-identity note: `kanban --preserve-cache` contains "serve" — never classify processes by argv
substring (root).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ sessions still have zero `kanban_*` schema footprint unless configured.
promotes ready tasks, atomically claims, spawns assigned profiles.
Auto-blocks a task after `failure_limit` consecutive spawn failures
(default 2; configurable via `kanban.failure_limit` or per-task
`max_retries`).
`max_retries`). `kanban.max_in_progress` caps concurrent `running` tasks.
Values are coerced to integers; failed conversions or results below 1
are ignored, so `0` does not pause spawning. When unset or ignored, the
default is 2–8 workers based on total host memory, or no derived cap
when that memory information is unavailable. An explicit positive value
overrides the derived default.
- **Isolation:** board is the hard boundary (workers get
`HERMES_KANBAN_BOARD` pinned in env); tenant is a soft namespace
within a board for workspace-path + memory-key isolation.
Expand Down
4 changes: 2 additions & 2 deletions website/docs/user-guide/features/kanban.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ They coexist: a kanban worker may call `delegate_task` internally during its run
- `scratch` (default) — fresh tmp dir under `~/.hermes/kanban/workspaces/<id>/` (or `~/.hermes/kanban/boards/<slug>/workspaces/<id>/` on non-default boards). **Deleted when the task completes** — scratch is ephemeral by design. Files explicitly declared through `kanban_complete(artifacts=[...])` are copied into durable per-task attachment storage before cleanup; existing deliverable paths in legacy completion summaries receive the same treatment. Other scratch files are removed. A missing declared scratch artifact keeps the task in-flight so the worker can correct the path and retry. Use `worktree:` or `dir:<path>` when the whole workspace should remain available. The first time a scratch workspace is created on an install, the dispatcher logs a warning and emits a `tip_scratch_workspace` event on the task (visible via `hermes kanban show <id>`).
- `dir:<path>` — an existing shared directory (Obsidian vault, mail ops dir, per-account folder). **Must be an absolute path.** Relative paths like `dir:../tenants/foo/` are rejected at dispatch because they'd resolve against whatever CWD the dispatcher happens to be in, which is ambiguous and a confused-deputy escape vector. The path is otherwise trusted — it's your box, your filesystem, the worker runs with your uid. This is the trusted-local-user threat model; kanban is single-host by design. **Preserved on completion.**
- `worktree` — a git worktree under `.worktrees/<id>/` for coding tasks. Use `worktree:<path>` to pin the exact target path. Worker-side `git worktree add` creates it, using `--branch` when provided. **Preserved on completion.**
- **Dispatcher** — a long-lived loop that, every N seconds (default 60): reclaims stale claims, reclaims crashed workers (PID gone but TTL not yet expired), promotes ready tasks, atomically claims, spawns assigned profiles. Runs **inside the gateway** by default (`kanban.dispatch_in_gateway: true`). One dispatcher sweeps all boards per tick; workers are spawned with `HERMES_KANBAN_BOARD` pinned so they can't see other boards. After `kanban.failure_limit` consecutive spawn failures on the same task (default: 2) the dispatcher auto-blocks it with the last error as the reason — prevents thrashing on tasks whose profile doesn't exist, workspace can't mount, etc.
- **Dispatcher** — a long-lived loop that, every N seconds (default 60): reclaims stale claims, reclaims crashed workers (PID gone but TTL not yet expired), promotes ready tasks, atomically claims, spawns assigned profiles. Runs **inside the gateway** by default (`kanban.dispatch_in_gateway: true`). One dispatcher sweeps all boards per tick; workers are spawned with `HERMES_KANBAN_BOARD` pinned so they can't see other boards. After `kanban.failure_limit` consecutive spawn failures on the same task (default: 2) the dispatcher auto-blocks it with the last error as the reason — prevents thrashing on tasks whose profile doesn't exist, workspace can't mount, etc. Use `kanban.max_in_progress` to limit concurrent `running` tasks; the configuration details and default are in [Concurrency, scheduling, and child promotion config](#concurrency-scheduling-and-child-promotion-config).
- **Tenant** — optional string namespace *within* a board. One specialist fleet can serve multiple businesses (`--tenant business-a`) with data isolation by workspace path and memory key prefix. Tenants are a soft filter; boards are the hard isolation boundary.

## Boards (multi-project)
Expand Down Expand Up @@ -854,7 +854,7 @@ All commands are also available as a slash command in the interactive CLI and in

| Config key | Default | What it does |
|------------|---------|--------------|
| `kanban.max_in_progress` | unset (unlimited) | Caps the number of simultaneously running tasks. When the board already has N running, the dispatcher skips spawning more — useful for slow workers (local LLMs, resource-constrained hosts) so they finish what they have before more pile up and time out. Invalid or below-1 values log a warning and behave as unlimited. |
| `kanban.max_in_progress` | memory-derived when available | Caps concurrent `running` tasks. Values are coerced to integers; failed conversions or results below 1 are ignored, so `0` does not pause spawning. When unset or ignored, the default is total host memory divided by 512 MiB per worker, rounded down and clamped to 2–8 workers. If total memory is unavailable, there is no derived cap. An explicit positive value overrides this default. |
| `kanban.max_in_progress_per_profile` | unset (unlimited) | Per-profile variant of `max_in_progress` — caps how many tasks any single assignee profile may run concurrently. Useful when one profile is slow or rate-limited but others should keep flowing. Applies alongside the board-wide `max_in_progress`; both must allow a spawn for it to proceed. |
| `kanban.auto_promote_children` | `true` | After `decompose_triage_task()` produces children with no parent-blocker dependencies, they're automatically promoted to `ready` so the dispatcher can pick them up. Set to `false` to require manual review — children stay in `todo` until you promote them. |
| `kanban.default_workdir` | unset | Board-level default working directory applied to new tasks when neither `--workspace` nor the task itself overrides it. Per-task `workspace:` still wins. |
Expand Down
Loading