# kanban

> File-backed kanban board — tickets, threaded comments, agent assignment, the kanban:change and kanban:comment trigger types, and an injectable console board with agent profiles for planning and review.

| field | value |
|-------|-------|
| version | 0.1.18 |
| type | binary |
| license | Apache-2.0 |
| repo | https://github.com/iii-hq/workers |
| supported_targets | x86_64-apple-darwin, aarch64-apple-darwin, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, armv7-unknown-linux-gnueabihf |
| author | iii |

## installation

```sh
iii trigger compose::add worker=kanban@0.1.18
```

## configuration

```yaml
- agents_path: agents
  columns:
    - id: backlog
      label: Backlog
    - id: todo
      label: To do
    - id: in_progress
      label: In progress
    - id: in_review
      label: In review
    - id: done
      label: Done
  data_path: data/kanban
  default_status: backlog
  id_prefix: KAN
  priorities:
    - low
    - medium
    - high
    - urgent
```

## dependencies

- `configuration` @ `latest`

## readme

# kanban

A file-backed kanban board for an iii project. Tickets, threaded comments,
an activity timeline and agent assignment live in one `board.json`; every
mutation is a `kanban::*` function, and two trigger types (`kanban:change`,
`kanban:comment`) let agents wake on what happens to a ticket instead of
polling. The worker injects a board page and a ticket screen into the
console, and ships five agent profiles — Product Manager, Tech Lead, Backend
Engineer, Frontend Engineer, iii ADE Worker Designer — that plan, dispatch,
build and review work as tickets on this board.

## Table of contents

- [Install](#install)
- [Quickstart](#quickstart)
- [Configuration](#configuration)
- [Custom trigger types](#custom-trigger-types)
- [Console pages](#console-pages)
- [Chat mentions](#chat-mentions)
- [Agent profiles](#agent-profiles)
- [Skills](#skills)
- [Development](#development)

## Install

```bash
iii trigger compose::add worker=kanban
```

`iii trigger compose::add` declares the worker in `worker-compose.yaml` and
starts it as part of the Compose project. The `configuration` worker (built
into the engine) stores the board settings; `iii-directory` is optional and
supplies the agent profiles the board can assign to.

## Quickstart

Create a ticket, claim it, comment, and hand it off. Every ticket has an
internal uuid and a human key (`KAN-1`); both work everywhere a ticket is
addressed, case-insensitively.

```bash
iii trigger kanban::ticket::create title="Search filters persist" priority=high
iii trigger kanban::ticket::update id=KAN-1 assignee=backend-engineer status=in_progress actor=backend-engineer
iii trigger kanban::comment::create ticket_id=KAN-1 body="Pushed the fix; ready for review." author=backend-engineer
iii trigger kanban::ticket::move id=KAN-1 status=in_review actor=backend-engineer
iii trigger kanban::ticket::get id=kan-1
```

From a worker, the same calls go through the SDK:

```rust
use iii_sdk::protocol::TriggerRequest;
use iii_sdk::{InitOptions, register_worker};
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let iii = register_worker("ws://localhost:49134", InitOptions::default());

    let ticket = iii
        .trigger(TriggerRequest {
            function_id: "kanban::ticket::create".into(),
            payload: json!({ "title": "Search filters persist", "priority": "high" }),
            action: None,
            timeout_ms: Some(5_000),
        })
        .await?;
    println!("{}", ticket["key"]); // KAN-1

    let board = iii
        .trigger(TriggerRequest {
            function_id: "kanban::board::get".into(),
            payload: json!({}),
            action: None,
            timeout_ms: Some(5_000),
        })
        .await?;
    println!("{}", board["columns"]);
    Ok(())
}
```

`kanban::ticket::get` returns the ticket with its `comments` and `activity`
inline; `kanban::board::get` returns every configured column with its ticket
summaries (no description or thread). Both list reads take `updated_since`
and `compact`, and the board takes `statuses` and a per-column `limit`, so an
agent reads the active lanes in one small call and the `done` column only for
what changed:

```bash
iii trigger kanban::board::get --json '{"statuses":["todo","in_progress","in_review"],"compact":true}'
iii trigger kanban::ticket::list --json '{"status":"done","updated_since":"2026-09-10T00:00:00.000Z","compact":true}'
```
 Pass `actor` on writes and `author` on comments: the timeline
reads honestly, and the comment filters key on exactly that string. Deleting
a ticket (`kanban::ticket::delete`) is a soft delete — the row stays in the
board file with `deleted_at` set and `kanban::ticket::restore` undoes it —
and it is reserved for a person on the console's ticket screen; agents move
a finished ticket to `in_review` or `done` instead.

The full function list is in
[`skills/SKILL.md`](skills/SKILL.md); request and response schemas come from
`engine::functions::info`.

## Configuration

Settings live in the `configuration` worker under the id `kanban` and are
edited from the console's Kanban settings form (columns, priorities, prefix,
paths) or by writing the YAML file the configuration worker persists. The
worker hot-reloads on `configuration:updated`; no restart is needed.

```yaml
data_path: data/kanban        # folder holding board.json; relative to III_COMPOSE_DIR (or the process cwd)
id_prefix: KAN                # human keys become KAN-1, KAN-2, …  (^[A-Z][A-Z0-9]*$)
default_status: backlog       # column a new ticket lands in when none is given
columns:                      # left to right; `id` is what tickets store, `label` is what the board shows
  - { id: backlog, label: Backlog }
  - { id: todo, label: To do }
  - { id: in_progress, label: In progress }
  - { id: in_review, label: In review }
  - { id: done, label: Done }
priorities: [low, medium, high, urgent]   # offered on a ticket, lowest first; the first one is the default
agents_path: agents           # fallback folder of <id>.md agent profiles when iii-directory is not installed
```

A stored value is repaired rather than refused: an unknown `default_status`
falls to the first column, an invalid prefix falls back to `KAN`, duplicate
column ids collapse, and an empty list falls back to the default. Pass
`--config <file>.yaml` to seed the entry on first boot only; afterwards the
configuration worker is the source of truth. `kanban::config::info` prints
the resolved absolute paths.

## Custom trigger types

| Trigger type | Config | Fires when | Payload to subscribers |
|---|---|---|---|
| `kanban:change` | `{ ticket_id?, events?, ticket?, metadata? }` | After every mutation. `ticket_id` (uuid or key) and `events` narrow it; an empty config fires for everything. | `{ event, ticket_id, ticket_key, ticket, comment, activity, at }` — `event` is one of `ticket.created`, `ticket.updated`, `ticket.moved`, `ticket.deleted`, `ticket.restored`, `comment.created`. |
| `kanban:comment` | `{ ticket_id, author?, exclude_author?, root_only?, ticket? }` | A comment is created on that ticket. `ticket_id` is required; `author` keeps one author, `exclude_author` drops one (your own id), `root_only` ignores replies. | `{ event: "comment.created", ticket_id, ticket_key, ticket, comment, author, at }` |

`ticket` on either type is `full` (default: the ticket with comments and
activity inline) or `summary` (the board-card fields only); an agent that
re-reads the ticket on a wake should pass `summary` so each wake costs a card,
not a thread. `kanban:change` has no author filter, so a feedback loop that
must not wake on its own comments uses `kanban:comment` with `exclude_author`:

```json
engine::register_trigger {
  "trigger_type": "kanban:comment",
  "config": { "ticket_id": "KAN-7", "exclude_author": "backend-engineer", "ticket": "summary" },
  "once": false,
  "lifecycle": { "max_fires": 20, "expires_in_ms": 3600000 }
}
```

Bindings live in the worker's in-process map and die with the process;
`kanban::config::info` reports how many are held under `subscribers`. Pair a
standing wake with a `cron` check-in over `kanban::comment::list`, which
takes the same filters plus `since`.

## Console pages

While the worker is connected it injects two pages into the console:

- **Kanban** (page `kanban-board`) — the board: one lane per configured
  column, drag and drop between lanes, a new-ticket dialog, and a lane picker
  when the pane is narrow. Clicking a card opens the ticket beside the
  board; the card's **Open in this tab** action keeps it in the current pane
  instead (the console reuses any tab that already shows a ticket pane), and
  the inline view offers **Open in its own pane** to go the other way.
- **Ticket** (page `kanban-ticket`) — one ticket: status, priority and
  assignee (from `kanban::agent::list`), a Markdown description with an
  editor, and the activity timeline with threaded comments. Delete and
  restore live here, behind a confirmation.

Both pages stay live over a `kanban:change` binding, and the Kanban
settings form edits the configuration above. Chat renders `kanban::*`
results as ticket cards and `kanban:*` trigger activity with the ticket and
comment it carried. Command palette rows: New ticket, Refresh board, Refresh
ticket, Edit description, Write a comment, Back to the board.

## Chat mentions

Tickets can be mentioned in a chat as `@kanban(id="<uuid>")`. In the
console composer, type `@kanban`, press Tab and search by key (`KAN-12`,
`12`) or title words; the ticket shows as a pill that previews on hover as
its ticket card (the one ticket function results show) and opens the ticket
screen on click.
Agents get the ticket's one-line summary with the message and read the whole
ticket through `kanban::ticket::get`.

Two internal functions provide this (see
[`crates/mention-contract`](../crates/mention-contract/README.md)):
`kanban::mention::search` (live tickets ranked by key, then title, then
description and labels) and `kanban::mention::get` (the view, including a
soft-deleted ticket marked as such), which declares the provider.

## Agent profiles

`agents/` ships five profiles. Each is a system prompt with preloaded
`skills` and `functions`; the harness freezes both into every session that
runs as that profile, and the board is the only channel between them.

| Profile id | Role | Preloaded skills |
|---|---|---|
| `product-manager` | Interviews the user, writes tickets whose descriptions carry observable acceptance criteria, and gates `in_review` → `done` (any caveat goes back to `in_progress`). | `kanban/tickets/feature-planning`, `kanban/tickets/acceptance-review`, `kanban/tickets/kanban-tickets` |
| `tech-lead` | Splits one feature on the seam between backend and frontend, dispatches each ticket with `harness::spawn`, stays reachable on comment wakes, verifies the seam. | `kanban/tickets/ticket-orchestration`, `kanban/tickets/kanban-tickets`, `kanban/tickets/acceptance-review` |
| `backend-engineer` | Builds workers, functions, triggers and configuration; verifies with a real call; reports on the ticket. | `kanban/iii-node/index`, `kanban/iii-node/configuration`, `kanban/tickets/ticket-worker` |
| `frontend-engineer` | Builds the browser app (Vite, React, TanStack, iii-browser-sdk); verifies in a real browser; reports with screenshots on the ticket. | `kanban/frontend/iii-browser-sdk`, `kanban/frontend/react`, `kanban/frontend/vite`, `kanban/frontend/tanstack-router`, `kanban/frontend/tanstack-query`, `kanban/frontend/web-accessibility`, `kanban/frontend/web-performance`, `kanban/frontend/frontend-testing`, `kanban/tickets/ticket-worker` |
| `ade-worker-designer` | Designs and builds the UI a worker injects into the ADE console — pages, renderers, configuration forms, scoped styles — against `@iii-dev/console-ui` and the iii Schematic design system; verifies in the running console. | `kanban/ade-worker-design/index`, `kanban/ade-worker-design/console-injectable-ui`, `kanban/ade-worker-design/patterns`, `kanban/ade-worker-design/console-design`, `kanban/iii-node/configuration`, `kanban/tickets/ticket-worker` |

All four extend the bundled `iii-minimal` identity and use their profile id
as `actor` / `author` / `exclude_author`, so the loop in
[`skills/kanban-tickets.md`](skills/kanban-tickets.md) closes.

### Where they land

- **Registry install.** `iii trigger compose::add worker=kanban` publishes
  the profiles into `<agents_folder>/<id>.md` and the skills under
  `<skills_folder>/kanban/…`, which is why the skill ids above are prefixed
  `kanban/`.
- **Local `path://` install.** Copy `agents/*.md` into the project's
  `agents/` folder and `skills/**` into `skills/kanban/` (keeping the same
  relative layout) so the ids the profiles preload resolve.

`directory::agents::list` shows them; `kanban::agent::list` is what the board
assigns from.

### Picking a model per profile

The profiles ship without a `model`, so each session takes the model of the
send (the console's model picker or `harness::send { options: { model } }`).
To pin one, add `model` and optionally `reasoning_effort` to the
frontmatter as `provider::model`. `router::models::list` prints the catalog
for the providers you installed (its ids read `provider/model`; the profile
spells the same key with `::`). The examples below use the `claude-code`
provider; with the API-key provider the same models are `anthropic::…`.

```yaml
---
name: Tech Lead
model: claude-code::claude-fable-5-1
reasoning_effort: high
extends: iii-minimal
---
```

`model` inherits through `extends`, so setting it on a shared base profile
pins every child. A useful split, from most to least demanding:

| Profile | What the work needs | Pick |
|---|---|---|
| `tech-lead` | Decomposition, contract design, judging a seam that "passes" but does not work; the costliest mistakes happen here. | The strongest model you run: `claude-code::claude-fable-5-1` (or `claude-code::claude-opus-5`) with `reasoning_effort: high`. |
| `product-manager` | Interviewing, writing precise acceptance criteria, refusing caveats at the gate. Long context, careful language, few calls. | `claude-code::claude-opus-5` at medium effort; `claude-code::claude-sonnet-5` is the economy choice. |
| `backend-engineer` | Many tool calls, real verification, Rust/Node code. Gains more from a strong coder than from deep deliberation. | `claude-code::claude-sonnet-5` (or an `openai-codex::…` model if Codex is your coding provider); raise to Opus for protocol or migration work. |
| `frontend-engineer` | Same shape as backend plus browser verification and screenshots; visual judgement matters. | `claude-code::claude-sonnet-5`; Opus when the ticket is a design pass. |
| `ade-worker-designer` | Long design-system and contract skills preloaded (about 40k tokens), visual judgement against screenshots, strict adherence to `index.d.ts`. | `claude-code::claude-opus-5`; Sonnet for small renderer or form tickets. |

Edit the file in `agents/` (or `directory::agents::update { id, … }`) and
the next session that runs as that profile picks it up; sessions already
running keep the model they started with.

## Skills

`skills/SKILL.md` is the worker overview agents read first. The rest is the
working doctrine the profiles preload, one folder per concern (each folder's
`index.md` maps its files):

- `tickets/` — how work moves through the board: `kanban-tickets` (the
  shared loop, the comment wake, review vs done, never delete),
  `feature-planning` and `acceptance-review` (the Product Manager's halves),
  `ticket-orchestration` and `ticket-worker` (the dispatcher's and the
  dispatched engineer's halves).
- `iii-node/` — building a Node worker (package shape, functions, triggers,
  asset delivery, compose wiring) with its bundled `configuration` reference.
- `ade-worker-design/` — the designer's half: the injectable UI contract
  (`console-injectable-ui`), the iii Schematic design system
  (`console-design`) and the record-shaped interaction recipes (`patterns`).
- `frontend/` — the Frontend Engineer's browser stack: `iii-browser-sdk`,
  `react`, `vite`, `tanstack-router`, `tanstack-query`, `web-accessibility`,
  `web-performance`, `frontend-testing`.

Fetch any of them with `directory::skills::get { "id": "kanban/<folder>/<name>" }`.

## Development

```bash
# UI (esbuild; react and @iii-dev/console-ui stay external)
pnpm --dir kanban/ui build
# worker
cd kanban
cargo fmt --all -- --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked --all-features
```

`build.rs` runs the UI build when `ui/dist` is missing or stale; set
`SKIP_UI_BUILD=1` to embed the existing output. For the hot-reload loop run
`pnpm --dir kanban/ui watch` in one terminal and
`III_KANBAN_UI_WATCH=1 cargo run --bin kanban` in another; every open
console tab swaps the asset on rebuild. The worker connects to `III_URL`
(default `ws://127.0.0.1:49134`) and resolves `data_path` and `agents_path`
against `III_COMPOSE_DIR`.

## skills

This version ships 27 skill files for agents, also browsable on the worker page:

- [`SKILL.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=SKILL.md&version=0.1.18)
- [`agents/ade-worker-designer.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=agents%2Fade-worker-designer.md&version=0.1.18)
- [`agents/backend-engineer.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=agents%2Fbackend-engineer.md&version=0.1.18)
- [`agents/frontend-engineer.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=agents%2Ffrontend-engineer.md&version=0.1.18)
- [`agents/product-manager.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=agents%2Fproduct-manager.md&version=0.1.18)
- [`agents/tech-lead.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=agents%2Ftech-lead.md&version=0.1.18)
- [`skills/ade-worker-design/console-design.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Fade-worker-design%2Fconsole-design.md&version=0.1.18)
- [`skills/ade-worker-design/console-injectable-ui.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Fade-worker-design%2Fconsole-injectable-ui.md&version=0.1.18)
- [`skills/ade-worker-design/index.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Fade-worker-design%2Findex.md&version=0.1.18)
- [`skills/ade-worker-design/patterns.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Fade-worker-design%2Fpatterns.md&version=0.1.18)
- [`skills/frontend/frontend-testing.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ffrontend%2Ffrontend-testing.md&version=0.1.18)
- [`skills/frontend/iii-browser-sdk.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ffrontend%2Fiii-browser-sdk.md&version=0.1.18)
- [`skills/frontend/index.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ffrontend%2Findex.md&version=0.1.18)
- [`skills/frontend/react.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ffrontend%2Freact.md&version=0.1.18)
- [`skills/frontend/tanstack-query.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ffrontend%2Ftanstack-query.md&version=0.1.18)
- [`skills/frontend/tanstack-router.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ffrontend%2Ftanstack-router.md&version=0.1.18)
- [`skills/frontend/vite.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ffrontend%2Fvite.md&version=0.1.18)
- [`skills/frontend/web-accessibility.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ffrontend%2Fweb-accessibility.md&version=0.1.18)
- [`skills/frontend/web-performance.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ffrontend%2Fweb-performance.md&version=0.1.18)
- [`skills/iii-node/configuration.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Fiii-node%2Fconfiguration.md&version=0.1.18)
- [`skills/iii-node/index.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Fiii-node%2Findex.md&version=0.1.18)
- [`skills/tickets/acceptance-review.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ftickets%2Facceptance-review.md&version=0.1.18)
- [`skills/tickets/feature-planning.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ftickets%2Ffeature-planning.md&version=0.1.18)
- [`skills/tickets/index.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ftickets%2Findex.md&version=0.1.18)
- [`skills/tickets/kanban-tickets.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ftickets%2Fkanban-tickets.md&version=0.1.18)
- [`skills/tickets/ticket-orchestration.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ftickets%2Fticket-orchestration.md&version=0.1.18)
- [`skills/tickets/ticket-worker.md`](https://workers.iii.dev/workers/kanban?tab=skills&skill=skills%2Ftickets%2Fticket-worker.md&version=0.1.18)

### SKILL.md

# kanban

The `kanban` worker owns one board file (`<data_path>/board.json`) holding
every ticket with its comments and activity timeline inline. Every ticket
carries an internal uuid and a human key (`KAN-7`, case-insensitive); both
work everywhere a ticket is addressed. Columns, priorities and the key prefix
are configuration (`kanban::config::info` prints the live values), and the
default board is `backlog`, `todo`, `in_progress`, `in_review`, `done`.

Tickets are never removed by an agent: a finished ticket is moved to
`in_review` or `done`, and a human soft-deletes from the console's ticket
screen (the row stays on disk with `deleted_at` set). Writes take an `actor`
and comments an `author`; pass your own agent-profile id in both, because the
comment filters below key on exactly that string. The worker also injects a
board page and a ticket page into the console.

## When to Use

- A feature is being planned, dispatched to other agent profiles, or reviewed,
  and the plan must outlive the chat session.
- You were dispatched onto a ticket and must claim it, report on it and hand
  it off where the reviewer will see it.
- You need to react to a human's or another agent's comment on one ticket
  without polling.
- The console should show a board or a ticket beside the conversation.

## Boundaries

- Not a general task queue or a durable job store; use `queue` for work that
  must be executed, and the board for work that must be planned and reviewed.
- No author filter on `kanban:change`; use `kanban:comment` with
  `exclude_author` for a feedback loop that must not wake on your own posts.
- Deleting and restoring tickets is the human's action; agents move tickets.
- Agent profiles are resolved through `iii-directory` (with the configured
  agents folder as the fallback); this worker does not create profiles.

## Functions

- `kanban::config::info` — resolved settings: board file path, columns, priorities, key prefix, agents folder.
- `kanban::board::get` — every column with its ticket summaries; `statuses`, `updated_since`, `limit` and `compact` keep the read small.
- `kanban::ticket::create` — create a ticket (title, description, status, priority, assignee, labels, actor).
- `kanban::ticket::get` — one ticket with comments and activity, by uuid or key.
- `kanban::ticket::list` — ticket summaries narrowed by column, assignee or `updated_since`; `compact` rows for catch-up reads.
- `kanban::ticket::update` — change title, description, status, priority, assignee or labels.
- `kanban::ticket::move` — move a ticket to a column and position (the board drag and drop).
- `kanban::ticket::delete` / `kanban::ticket::restore` — soft delete and undo; human actions.
- `kanban::comment::create` — comment or reply (`parent_id`); replies stay one level deep.
- `kanban::comment::list` — comments filtered by author, excluded author, root only, or since a timestamp.
- `kanban::activity::list` — the ticket's timeline, oldest first, optionally narrowed by type or time.
- `kanban::agent::list` — assignable agent profiles and their ids.

A user may mention a ticket in chat as `@kanban(id="<uuid>")`; the message
then arrives with a `<mentions>` block carrying the ticket's summary and the
pre-verified `kanban::ticket::get` call. To point the user at a ticket in a
reply, write `@kanban(id="<uuid>")` with the `id` a function returned — the
console renders it as a ticket pill. (`kanban::mention::search` and
`kanban::mention::get` serve the composer and that block; they are internal.)

## Reactive triggers

Register a `kanban:comment` trigger when a session or function should run
every time someone comments on one ticket — the reviewer's answer after a
hand-off, a human's reply, another agent's note — without re-reading the
board.

Reach for it when:

- You reported on a ticket and moved it to `in_review`, and the rejection or
  acceptance must reach you after your turn ended.
- You dispatched a ticket to another profile and its report arrives as a
  comment.

If you only need the comment you just wrote, use the return value of
`kanban::comment::create`; register a trigger only when a *different* author
should wake you. `kanban:change` is the board-wide sibling: it fires on every
event (`ticket.created`, `ticket.updated`, `ticket.moved`, `ticket.deleted`,
`ticket.restored`, `comment.created`) with the whole ticket, filtered by
ticket and event name only, and is the right primitive for keeping a view in
sync or for a done-watch on `ticket.moved`.

### How to bind

1. Register a handler, or omit `function_id` from an agent session to be woken.
2. Register the trigger:

```json
engine::register_trigger {
  "trigger_type": "kanban:comment",
  "config": { "ticket_id": "KAN-7", "exclude_author": "<your profile id>", "ticket": "summary" },
  "once": false,
  "lifecycle": { "max_fires": 20, "expires_in_ms": 3600000 }
}
```

`ticket_id` is required and matches the uuid or the key; `author`,
`exclude_author` and `root_only` narrow further. `"ticket": "summary"` on
either trigger type delivers the board-card fields instead of the whole
ticket with its thread — the right choice for an agent that re-reads the
ticket on a wake. Mutations fire triggers; reads do not. The worker delivers only to bindings in its own in-process map,
so pair a standing wake with a `cron` check-in over `kanban::comment::list`.
For the payload shape call `get function info` on the trigger type.

## api reference

```json
{
  "functions": [
    {
      "description": "Read a ticket's activity timeline: field changes, lane moves, deletion and comment events, oldest first.",
      "metadata": {},
      "name": "kanban::activity::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "limit": {
            "default": null,
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "since": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "ticket_id": {
            "description": "Ticket uuid or human-readable key (KAN-1, case-insensitive).",
            "type": "string"
          },
          "types": {
            "default": null,
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          }
        },
        "required": [
          "ticket_id"
        ],
        "title": "ActivityListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Activity": {
            "properties": {
              "actor": {
                "type": "string"
              },
              "at": {
                "type": "string"
              },
              "changes": {
                "items": {
                  "$ref": "#/definitions/Change"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "comment_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "seq": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "ticket_id": {
                "type": "string"
              },
              "type": {
                "description": "ticket.created | ticket.updated | ticket.moved | ticket.deleted | ticket.restored | comment.created",
                "type": "string"
              }
            },
            "required": [
              "actor",
              "at",
              "id",
              "seq",
              "ticket_id",
              "type"
            ],
            "type": "object"
          },
          "Change": {
            "properties": {
              "field": {
                "type": "string"
              },
              "from": true,
              "to": true
            },
            "required": [
              "field",
              "from",
              "to"
            ],
            "type": "object"
          }
        },
        "properties": {
          "activity": {
            "items": {
              "$ref": "#/definitions/Activity"
            },
            "type": "array"
          },
          "count": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "activity",
          "count"
        ],
        "title": "ActivityList",
        "type": "object"
      }
    },
    {
      "description": "List assignable agent profiles, resolved through iii-directory with a fallback to the configured agents folder.",
      "metadata": {},
      "name": "kanban::agent::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "Empty",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "AgentProfile": {
            "properties": {
              "color": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "icon": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "description": "Agent profile id used as the ticket assignee.",
                "type": "string"
              },
              "logo": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "model": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ],
            "type": "object"
          }
        },
        "properties": {
          "agents": {
            "items": {
              "$ref": "#/definitions/AgentProfile"
            },
            "type": "array"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "description": "directory | agents-folder | unavailable",
            "type": "string"
          }
        },
        "required": [
          "agents",
          "source"
        ],
        "title": "AgentList",
        "type": "object"
      }
    },
    {
      "description": "Read the board: every configured column with its tickets, plus the resolved paths and priorities. Narrow it with statuses, updated_since and limit, and ask for compact rows to keep the response small.",
      "metadata": {},
      "name": "kanban::board::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "compact": {
            "default": false,
            "description": "Return the compact row (key, title, status, priority, assignee, comment_count) instead of the full summary.",
            "type": "boolean"
          },
          "include_deleted": {
            "default": false,
            "description": "Include soft-deleted tickets. Default false.",
            "type": "boolean"
          },
          "limit": {
            "default": null,
            "description": "At most this many tickets per column, from the top of the column.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "statuses": {
            "default": null,
            "description": "Only these column ids (board order kept). Omit for every column.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "updated_since": {
            "default": null,
            "description": "Only tickets updated after this ISO timestamp — what changed since your last read.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BoardGetInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "BoardColumnView": {
            "properties": {
              "id": {
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "tickets": {
                "items": {
                  "$ref": "#/definitions/TicketRow"
                },
                "type": "array"
              }
            },
            "required": [
              "id",
              "label",
              "tickets"
            ],
            "type": "object"
          },
          "TicketBrief": {
            "description": "The compact projection: what an agent needs to recognise a ticket in a list (`compact: true` on `kanban::board::get` / `kanban::ticket::list`). About a third of a summary's size — no uuid, timestamps, labels or order.",
            "properties": {
              "assignee": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comment_count": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "key": {
                "type": "string"
              },
              "priority": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "title": {
                "type": "string"
              }
            },
            "required": [
              "comment_count",
              "key",
              "priority",
              "status",
              "title"
            ],
            "type": "object"
          },
          "TicketRow": {
            "anyOf": [
              {
                "$ref": "#/definitions/TicketSummary"
              },
              {
                "$ref": "#/definitions/TicketBrief"
              }
            ],
            "description": "One list row: the summary by default, the brief when `compact` was asked."
          },
          "TicketSummary": {
            "description": "The board-card projection of a ticket.",
            "properties": {
              "assignee": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comment_count": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "created_at": {
                "type": "string"
              },
              "deleted_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "key": {
                "type": "string"
              },
              "labels": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "order": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "priority": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "updated_at": {
                "type": "string"
              }
            },
            "required": [
              "comment_count",
              "created_at",
              "id",
              "key",
              "labels",
              "order",
              "priority",
              "status",
              "title",
              "updated_at"
            ],
            "type": "object"
          }
        },
        "properties": {
          "board_file": {
            "type": "string"
          },
          "columns": {
            "description": "The columns asked for (all by default), each with the tickets the filters kept.",
            "items": {
              "$ref": "#/definitions/BoardColumnView"
            },
            "type": "array"
          },
          "data_path_resolved": {
            "type": "string"
          },
          "default_status": {
            "type": "string"
          },
          "deleted_count": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "id_prefix": {
            "type": "string"
          },
          "priorities": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "ticket_count": {
            "description": "Every live ticket on the board, regardless of the filters above.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "board_file",
          "columns",
          "data_path_resolved",
          "default_status",
          "deleted_count",
          "id_prefix",
          "priorities",
          "ticket_count",
          "updated_at"
        ],
        "title": "BoardView",
        "type": "object"
      }
    },
    {
      "description": "Comment on a ticket, or reply to a comment by passing parent_id. Returns the new comment and the ticket.",
      "metadata": {},
      "name": "kanban::comment::create",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "author": {
            "default": null,
            "description": "Agent profile id, or `user`. Defaults to `user`.",
            "type": [
              "string",
              "null"
            ]
          },
          "body": {
            "description": "Comment body. Markdown is rendered in the console.",
            "type": "string"
          },
          "parent_id": {
            "default": null,
            "description": "Comment id to reply to; replies stay one level deep.",
            "type": [
              "string",
              "null"
            ]
          },
          "ticket_id": {
            "description": "Ticket uuid or human-readable key (KAN-1, case-insensitive).",
            "type": "string"
          }
        },
        "required": [
          "body",
          "ticket_id"
        ],
        "title": "CommentCreateInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Activity": {
            "properties": {
              "actor": {
                "type": "string"
              },
              "at": {
                "type": "string"
              },
              "changes": {
                "items": {
                  "$ref": "#/definitions/Change"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "comment_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "seq": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "ticket_id": {
                "type": "string"
              },
              "type": {
                "description": "ticket.created | ticket.updated | ticket.moved | ticket.deleted | ticket.restored | comment.created",
                "type": "string"
              }
            },
            "required": [
              "actor",
              "at",
              "id",
              "seq",
              "ticket_id",
              "type"
            ],
            "type": "object"
          },
          "Change": {
            "properties": {
              "field": {
                "type": "string"
              },
              "from": true,
              "to": true
            },
            "required": [
              "field",
              "from",
              "to"
            ],
            "type": "object"
          },
          "Comment": {
            "properties": {
              "author": {
                "description": "Agent profile id, or `user`.",
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "id": {
                "type": "string"
              },
              "parent_id": {
                "default": null,
                "description": "Set for a reply; replies stay one level deep.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ticket_id": {
                "type": "string"
              }
            },
            "required": [
              "author",
              "body",
              "created_at",
              "id",
              "ticket_id"
            ],
            "type": "object"
          },
          "Ticket": {
            "properties": {
              "activity": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/Activity"
                },
                "type": "array"
              },
              "assignee": {
                "default": null,
                "description": "Agent profile id.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "comments": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/Comment"
                },
                "type": "array"
              },
              "created_at": {
                "type": "string"
              },
              "created_by": {
                "default": "user",
                "type": "string"
              },
              "deleted_at": {
                "default": null,
                "description": "Set when soft-deleted; the row stays on disk.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "description": {
                "default": "",
                "type": "string"
              },
              "id": {
                "description": "Internal uuid.",
                "type": "string"
              },
              "key": {
                "description": "Human-readable id, e.g. KAN-1.",
                "type": "string"
              },
              "labels": {
                "default": [],
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "order": {
                "default": 0,
                "description": "Position inside its column.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "priority": {
                "type": "string"
              },
              "status": {
                "description": "Column id.",
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "updated_at": {
                "type": "string"
              }
            },
            "required": [
              "created_at",
              "id",
              "key",
              "priority",
              "status",
              "title",
              "updated_at"
            ],
            "type": "object"
          }
        },
        "properties": {
          "comment": {
            "$ref": "#/definitions/Comment"
          },
          "ticket": {
            "$ref": "#/definitions/Ticket"
          }
        },
        "required": [
          "comment",
          "ticket"
        ],
        "title": "CommentCreated",
        "type": "object"
      }
    },
    {
      "description": "Read a ticket's comments with filtering, mirroring the kanban:comment trigger: by author, excluding an author, roots only, or after a timestamp.",
      "metadata": {},
      "name": "kanban::comment::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "author": {
            "default": null,
            "description": "Only comments by exactly this author.",
            "type": [
              "string",
              "null"
            ]
          },
          "exclude_author": {
            "default": null,
            "description": "Skip comments by this author — e.g. your own.",
            "type": [
              "string",
              "null"
            ]
          },
          "include_deleted": {
            "default": false,
            "type": "boolean"
          },
          "limit": {
            "default": null,
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "parent_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "root_only": {
            "default": false,
            "type": "boolean"
          },
          "since": {
            "default": null,
            "description": "ISO timestamp; only comments created after it.",
            "type": [
              "string",
              "null"
            ]
          },
          "ticket_id": {
            "description": "Ticket uuid or human-readable key (KAN-1, case-insensitive).",
            "type": "string"
          }
        },
        "required": [
          "ticket_id"
        ],
        "title": "CommentListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Comment": {
            "properties": {
              "author": {
                "description": "Agent profile id, or `user`.",
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "id": {
                "type": "string"
              },
              "parent_id": {
                "default": null,
                "description": "Set for a reply; replies stay one level deep.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ticket_id": {
                "type": "string"
              }
            },
            "required": [
              "author",
              "body",
              "created_at",
              "id",
              "ticket_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "comments": {
            "items": {
              "$ref": "#/definitions/Comment"
            },
            "type": "array"
          },
          "count": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "comments",
          "count"
        ],
        "title": "CommentList",
        "type": "object"
      }
    },
    {
      "description": "Resolved kanban settings: the folder the board file lives in, the columns, and the numbering prefix.",
      "metadata": {},
      "name": "kanban::config::info",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "Empty",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Column": {
            "properties": {
              "id": {
                "description": "Stable column id stored on tickets.",
                "type": "string"
              },
              "label": {
                "description": "Name shown on the board.",
                "type": "string"
              }
            },
            "required": [
              "id",
              "label"
            ],
            "type": "object"
          },
          "SubscriberCounts": {
            "properties": {
              "change": {
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              },
              "comment": {
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "change",
              "comment"
            ],
            "type": "object"
          }
        },
        "properties": {
          "agents_path": {
            "type": "string"
          },
          "agents_path_resolved": {
            "type": "string"
          },
          "board_file": {
            "description": "Absolute path of board.json.",
            "type": "string"
          },
          "columns": {
            "description": "Board columns left to right.",
            "items": {
              "$ref": "#/definitions/Column"
            },
            "type": "array"
          },
          "configuration_id": {
            "type": "string"
          },
          "data_path": {
            "description": "Configured path, as written.",
            "type": "string"
          },
          "data_path_resolved": {
            "description": "Absolute folder the board file is read from.",
            "type": "string"
          },
          "default_status": {
            "type": "string"
          },
          "id_prefix": {
            "type": "string"
          },
          "priorities": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "project_root": {
            "type": "string"
          },
          "subscribers": {
            "$ref": "#/definitions/SubscriberCounts"
          }
        },
        "required": [
          "agents_path",
          "agents_path_resolved",
          "board_file",
          "columns",
          "configuration_id",
          "data_path",
          "data_path_resolved",
          "default_status",
          "id_prefix",
          "priorities",
          "project_root",
          "subscribers"
        ],
        "title": "ConfigInfo",
        "type": "object"
      }
    },
    {
      "description": "Resolve a @kanban(id=…) chat mention to its pill, preview card and agent summary; null for an unknown id. Declares the kanban mention provider.",
      "metadata": {
        "internal": true,
        "mention": {
          "color": "blue",
          "description": "A kanban ticket, by uuid or human key (KAN-12)",
          "details": {
            "function_id": "kanban::ticket::get",
            "id_field": "id"
          },
          "icon": "ticket",
          "label": "Tickets",
          "name": "kanban",
          "search": "kanban::mention::search",
          "v": 1
        },
        "trace_hidden": true
      },
      "name": "kanban::mention::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Input of a provider's get function.",
        "properties": {
          "id": {
            "description": "The id from the token.",
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "title": "MentionGetRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "anyOf": [
          {
            "$ref": "#/definitions/MentionView"
          },
          {
            "type": "null"
          }
        ],
        "definitions": {
          "MentionField": {
            "description": "One labelled value of a preview card.",
            "properties": {
              "label": {
                "type": "string"
              },
              "tone": {
                "description": "`neutral`, `info`, `success`, `warning` or `danger`.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "label",
              "value"
            ],
            "type": "object"
          },
          "MentionOpen": {
            "anyOf": [
              {
                "properties": {
                  "context": true,
                  "page": {
                    "description": "Console page id (as registered with `host.pages`).",
                    "type": "string"
                  }
                },
                "required": [
                  "page"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "session": {
                    "description": "Session id to select.",
                    "type": "string"
                  }
                },
                "required": [
                  "session"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "url": {
                    "type": "string"
                  }
                },
                "required": [
                  "url"
                ],
                "type": "object"
              }
            ],
            "description": "What clicking a mention opens: an injected console page, a chat session, or an http(s) URL."
          },
          "MentionView": {
            "description": "What a chat surface renders for one mention, and what an agent reads. A get function answers `null` for an id it does not know.",
            "properties": {
              "color": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "data": {
                "description": "The domain object, for a worker's own preview renderer."
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "fields": {
                "description": "Labelled values for the preview card (status, assignee, …).",
                "items": {
                  "$ref": "#/definitions/MentionField"
                },
                "type": "array"
              },
              "hint": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "icon": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "description": "The canonical id (may differ from the one asked for, e.g. a key resolved to a uuid).",
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "open": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/MentionOpen"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "What clicking the mention opens."
              },
              "summary": {
                "description": "One line for an agent: what the item is and its state. Defaults to the label, hint, description and fields.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "updated_at": {
                "description": "RFC 3339 time of the item's last change.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "label"
            ],
            "type": "object"
          }
        },
        "title": "Nullable_MentionView"
      }
    },
    {
      "description": "Search live tickets for the chat @kanban mention menu, by key (KAN-12, 12) or title words; best match first.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "kanban::mention::search",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "MentionSearchContext": {
            "description": "The chat a search was asked from.",
            "properties": {
              "session_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "working_dir": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          }
        },
        "description": "Input of a provider's search function.",
        "properties": {
          "context": {
            "anyOf": [
              {
                "$ref": "#/definitions/MentionSearchContext"
              },
              {
                "type": "null"
              }
            ],
            "description": "Where the search was asked from, for providers that rank by it."
          },
          "limit": {
            "description": "Maximum rows (default 8, at most 50).",
            "format": "uint",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "query": {
            "default": "",
            "description": "What the user typed after `@<name>:`. Empty asks for the most relevant recent items.",
            "type": "string"
          }
        },
        "title": "MentionSearchRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "MentionItem": {
            "description": "One search row.",
            "properties": {
              "color": {
                "description": "Overrides the provider's color for this row.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "description": {
                "description": "One line after the label (\"in progress · high\").",
                "type": [
                  "string",
                  "null"
                ]
              },
              "hint": {
                "description": "A short human handle shown before the label (\"KAN-12\").",
                "type": [
                  "string",
                  "null"
                ]
              },
              "icon": {
                "description": "Overrides the provider's icon for this row.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "description": "The id written into the token; stable for the item's lifetime.",
                "type": "string"
              },
              "label": {
                "description": "The item's name (\"Fix login redirect\").",
                "type": "string"
              }
            },
            "required": [
              "id",
              "label"
            ],
            "type": "object"
          }
        },
        "description": "Output of a provider's search function, best match first.",
        "properties": {
          "items": {
            "items": {
              "$ref": "#/definitions/MentionItem"
            },
            "type": "array"
          }
        },
        "required": [
          "items"
        ],
        "title": "MentionSearchResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal: re-read the kanban configuration after an operator change.",
      "metadata": {
        "internal": true
      },
      "name": "kanban::on-config-change",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Trigger payload for `<worker>::on-config-change`. Advisory only: handlers re-fetch the authoritative value and ignore it, so a direct call can never inject config.",
        "properties": {
          "id": {
            "default": null,
            "description": "Configuration id that changed.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "OnConfigChangeEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Ack returned by the internal `<worker>::on-config-change` handler.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "OnConfigChangeResponse",
        "type": "object"
      }
    },
    {
      "description": "Create a ticket on the board and return it.",
      "metadata": {},
      "name": "kanban::ticket::create",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "actor": {
            "default": null,
            "description": "Who is making the change. Defaults to `user`.",
            "type": [
              "string",
              "null"
            ]
          },
          "assignee": {
            "default": null,
            "description": "Agent profile id.",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "labels": {
            "default": null,
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "priority": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "default": null,
            "description": "Column id; defaults to the configured default_status.",
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "description": "Required.",
            "type": "string"
          }
        },
        "required": [
          "title"
        ],
        "title": "TicketCreateInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Activity": {
            "properties": {
              "actor": {
                "type": "string"
              },
              "at": {
                "type": "string"
              },
              "changes": {
                "items": {
                  "$ref": "#/definitions/Change"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "comment_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "seq": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "ticket_id": {
                "type": "string"
              },
              "type": {
                "description": "ticket.created | ticket.updated | ticket.moved | ticket.deleted | ticket.restored | comment.created",
                "type": "string"
              }
            },
            "required": [
              "actor",
              "at",
              "id",
              "seq",
              "ticket_id",
              "type"
            ],
            "type": "object"
          },
          "Change": {
            "properties": {
              "field": {
                "type": "string"
              },
              "from": true,
              "to": true
            },
            "required": [
              "field",
              "from",
              "to"
            ],
            "type": "object"
          },
          "Comment": {
            "properties": {
              "author": {
                "description": "Agent profile id, or `user`.",
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "id": {
                "type": "string"
              },
              "parent_id": {
                "default": null,
                "description": "Set for a reply; replies stay one level deep.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ticket_id": {
                "type": "string"
              }
            },
            "required": [
              "author",
              "body",
              "created_at",
              "id",
              "ticket_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "activity": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Activity"
            },
            "type": "array"
          },
          "assignee": {
            "default": null,
            "description": "Agent profile id.",
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Comment"
            },
            "type": "array"
          },
          "created_at": {
            "type": "string"
          },
          "created_by": {
            "default": "user",
            "type": "string"
          },
          "deleted_at": {
            "default": null,
            "description": "Set when soft-deleted; the row stays on disk.",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "default": "",
            "type": "string"
          },
          "id": {
            "description": "Internal uuid.",
            "type": "string"
          },
          "key": {
            "description": "Human-readable id, e.g. KAN-1.",
            "type": "string"
          },
          "labels": {
            "default": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "order": {
            "default": 0,
            "description": "Position inside its column.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "priority": {
            "type": "string"
          },
          "status": {
            "description": "Column id.",
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "id",
          "key",
          "priority",
          "status",
          "title",
          "updated_at"
        ],
        "title": "Ticket",
        "type": "object"
      }
    },
    {
      "description": "Soft-delete a ticket: it leaves the board but stays in the board file.",
      "metadata": {},
      "name": "kanban::ticket::delete",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "actor": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "description": "Ticket uuid or human-readable key (KAN-1, case-insensitive).",
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "title": "TicketRefInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Activity": {
            "properties": {
              "actor": {
                "type": "string"
              },
              "at": {
                "type": "string"
              },
              "changes": {
                "items": {
                  "$ref": "#/definitions/Change"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "comment_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "seq": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "ticket_id": {
                "type": "string"
              },
              "type": {
                "description": "ticket.created | ticket.updated | ticket.moved | ticket.deleted | ticket.restored | comment.created",
                "type": "string"
              }
            },
            "required": [
              "actor",
              "at",
              "id",
              "seq",
              "ticket_id",
              "type"
            ],
            "type": "object"
          },
          "Change": {
            "properties": {
              "field": {
                "type": "string"
              },
              "from": true,
              "to": true
            },
            "required": [
              "field",
              "from",
              "to"
            ],
            "type": "object"
          },
          "Comment": {
            "properties": {
              "author": {
                "description": "Agent profile id, or `user`.",
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "id": {
                "type": "string"
              },
              "parent_id": {
                "default": null,
                "description": "Set for a reply; replies stay one level deep.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ticket_id": {
                "type": "string"
              }
            },
            "required": [
              "author",
              "body",
              "created_at",
              "id",
              "ticket_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "activity": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Activity"
            },
            "type": "array"
          },
          "assignee": {
            "default": null,
            "description": "Agent profile id.",
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Comment"
            },
            "type": "array"
          },
          "created_at": {
            "type": "string"
          },
          "created_by": {
            "default": "user",
            "type": "string"
          },
          "deleted_at": {
            "default": null,
            "description": "Set when soft-deleted; the row stays on disk.",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "default": "",
            "type": "string"
          },
          "id": {
            "description": "Internal uuid.",
            "type": "string"
          },
          "key": {
            "description": "Human-readable id, e.g. KAN-1.",
            "type": "string"
          },
          "labels": {
            "default": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "order": {
            "default": 0,
            "description": "Position inside its column.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "priority": {
            "type": "string"
          },
          "status": {
            "description": "Column id.",
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "id",
          "key",
          "priority",
          "status",
          "title",
          "updated_at"
        ],
        "title": "Ticket",
        "type": "object"
      }
    },
    {
      "description": "Read one ticket with its comments and activity. The id accepts the internal uuid or the human-readable key.",
      "metadata": {},
      "name": "kanban::ticket::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "id": {
            "description": "Ticket uuid or human-readable key (KAN-1, case-insensitive).",
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "title": "TicketGetInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Activity": {
            "properties": {
              "actor": {
                "type": "string"
              },
              "at": {
                "type": "string"
              },
              "changes": {
                "items": {
                  "$ref": "#/definitions/Change"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "comment_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "seq": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "ticket_id": {
                "type": "string"
              },
              "type": {
                "description": "ticket.created | ticket.updated | ticket.moved | ticket.deleted | ticket.restored | comment.created",
                "type": "string"
              }
            },
            "required": [
              "actor",
              "at",
              "id",
              "seq",
              "ticket_id",
              "type"
            ],
            "type": "object"
          },
          "Change": {
            "properties": {
              "field": {
                "type": "string"
              },
              "from": true,
              "to": true
            },
            "required": [
              "field",
              "from",
              "to"
            ],
            "type": "object"
          },
          "Comment": {
            "properties": {
              "author": {
                "description": "Agent profile id, or `user`.",
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "id": {
                "type": "string"
              },
              "parent_id": {
                "default": null,
                "description": "Set for a reply; replies stay one level deep.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ticket_id": {
                "type": "string"
              }
            },
            "required": [
              "author",
              "body",
              "created_at",
              "id",
              "ticket_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "activity": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Activity"
            },
            "type": "array"
          },
          "assignee": {
            "default": null,
            "description": "Agent profile id.",
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Comment"
            },
            "type": "array"
          },
          "created_at": {
            "type": "string"
          },
          "created_by": {
            "default": "user",
            "type": "string"
          },
          "deleted_at": {
            "default": null,
            "description": "Set when soft-deleted; the row stays on disk.",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "default": "",
            "type": "string"
          },
          "id": {
            "description": "Internal uuid.",
            "type": "string"
          },
          "key": {
            "description": "Human-readable id, e.g. KAN-1.",
            "type": "string"
          },
          "labels": {
            "default": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "order": {
            "default": 0,
            "description": "Position inside its column.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "priority": {
            "type": "string"
          },
          "status": {
            "description": "Column id.",
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "id",
          "key",
          "priority",
          "status",
          "title",
          "updated_at"
        ],
        "title": "Ticket",
        "type": "object"
      }
    },
    {
      "description": "List tickets, optionally narrowed to a column, an assignee or an updated_since timestamp; compact rows carry only key, title, status, priority, assignee and comment_count.",
      "metadata": {},
      "name": "kanban::ticket::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "assignee": {
            "default": null,
            "description": "Agent profile id; empty string matches unassigned.",
            "type": [
              "string",
              "null"
            ]
          },
          "compact": {
            "default": false,
            "description": "Return the compact row (key, title, status, priority, assignee, comment_count) instead of the full summary.",
            "type": "boolean"
          },
          "include_deleted": {
            "default": false,
            "type": "boolean"
          },
          "limit": {
            "default": null,
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "default": null,
            "description": "Column id.",
            "type": [
              "string",
              "null"
            ]
          },
          "updated_since": {
            "default": null,
            "description": "Only tickets updated after this ISO timestamp.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "TicketListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "TicketBrief": {
            "description": "The compact projection: what an agent needs to recognise a ticket in a list (`compact: true` on `kanban::board::get` / `kanban::ticket::list`). About a third of a summary's size — no uuid, timestamps, labels or order.",
            "properties": {
              "assignee": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comment_count": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "key": {
                "type": "string"
              },
              "priority": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "title": {
                "type": "string"
              }
            },
            "required": [
              "comment_count",
              "key",
              "priority",
              "status",
              "title"
            ],
            "type": "object"
          },
          "TicketRow": {
            "anyOf": [
              {
                "$ref": "#/definitions/TicketSummary"
              },
              {
                "$ref": "#/definitions/TicketBrief"
              }
            ],
            "description": "One list row: the summary by default, the brief when `compact` was asked."
          },
          "TicketSummary": {
            "description": "The board-card projection of a ticket.",
            "properties": {
              "assignee": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comment_count": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "created_at": {
                "type": "string"
              },
              "deleted_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "key": {
                "type": "string"
              },
              "labels": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "order": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "priority": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "updated_at": {
                "type": "string"
              }
            },
            "required": [
              "comment_count",
              "created_at",
              "id",
              "key",
              "labels",
              "order",
              "priority",
              "status",
              "title",
              "updated_at"
            ],
            "type": "object"
          }
        },
        "properties": {
          "count": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "tickets": {
            "items": {
              "$ref": "#/definitions/TicketRow"
            },
            "type": "array"
          }
        },
        "required": [
          "count",
          "tickets"
        ],
        "title": "TicketList",
        "type": "object"
      }
    },
    {
      "description": "Move a ticket to a column (and position inside it); used by the board drag and drop.",
      "metadata": {},
      "name": "kanban::ticket::move",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "actor": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "description": "Ticket uuid or human-readable key (KAN-1, case-insensitive).",
            "type": "string"
          },
          "index": {
            "default": null,
            "description": "Zero-based position in the target column.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "description": "Target column id.",
            "type": "string"
          }
        },
        "required": [
          "id",
          "status"
        ],
        "title": "TicketMoveInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Activity": {
            "properties": {
              "actor": {
                "type": "string"
              },
              "at": {
                "type": "string"
              },
              "changes": {
                "items": {
                  "$ref": "#/definitions/Change"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "comment_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "seq": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "ticket_id": {
                "type": "string"
              },
              "type": {
                "description": "ticket.created | ticket.updated | ticket.moved | ticket.deleted | ticket.restored | comment.created",
                "type": "string"
              }
            },
            "required": [
              "actor",
              "at",
              "id",
              "seq",
              "ticket_id",
              "type"
            ],
            "type": "object"
          },
          "Change": {
            "properties": {
              "field": {
                "type": "string"
              },
              "from": true,
              "to": true
            },
            "required": [
              "field",
              "from",
              "to"
            ],
            "type": "object"
          },
          "Comment": {
            "properties": {
              "author": {
                "description": "Agent profile id, or `user`.",
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "id": {
                "type": "string"
              },
              "parent_id": {
                "default": null,
                "description": "Set for a reply; replies stay one level deep.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ticket_id": {
                "type": "string"
              }
            },
            "required": [
              "author",
              "body",
              "created_at",
              "id",
              "ticket_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "activity": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Activity"
            },
            "type": "array"
          },
          "assignee": {
            "default": null,
            "description": "Agent profile id.",
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Comment"
            },
            "type": "array"
          },
          "created_at": {
            "type": "string"
          },
          "created_by": {
            "default": "user",
            "type": "string"
          },
          "deleted_at": {
            "default": null,
            "description": "Set when soft-deleted; the row stays on disk.",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "default": "",
            "type": "string"
          },
          "id": {
            "description": "Internal uuid.",
            "type": "string"
          },
          "key": {
            "description": "Human-readable id, e.g. KAN-1.",
            "type": "string"
          },
          "labels": {
            "default": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "order": {
            "default": 0,
            "description": "Position inside its column.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "priority": {
            "type": "string"
          },
          "status": {
            "description": "Column id.",
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "id",
          "key",
          "priority",
          "status",
          "title",
          "updated_at"
        ],
        "title": "Ticket",
        "type": "object"
      }
    },
    {
      "description": "Put a soft-deleted ticket back on the board.",
      "metadata": {},
      "name": "kanban::ticket::restore",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "actor": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "description": "Ticket uuid or human-readable key (KAN-1, case-insensitive).",
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "title": "TicketRefInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Activity": {
            "properties": {
              "actor": {
                "type": "string"
              },
              "at": {
                "type": "string"
              },
              "changes": {
                "items": {
                  "$ref": "#/definitions/Change"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "comment_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "seq": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "ticket_id": {
                "type": "string"
              },
              "type": {
                "description": "ticket.created | ticket.updated | ticket.moved | ticket.deleted | ticket.restored | comment.created",
                "type": "string"
              }
            },
            "required": [
              "actor",
              "at",
              "id",
              "seq",
              "ticket_id",
              "type"
            ],
            "type": "object"
          },
          "Change": {
            "properties": {
              "field": {
                "type": "string"
              },
              "from": true,
              "to": true
            },
            "required": [
              "field",
              "from",
              "to"
            ],
            "type": "object"
          },
          "Comment": {
            "properties": {
              "author": {
                "description": "Agent profile id, or `user`.",
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "id": {
                "type": "string"
              },
              "parent_id": {
                "default": null,
                "description": "Set for a reply; replies stay one level deep.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ticket_id": {
                "type": "string"
              }
            },
            "required": [
              "author",
              "body",
              "created_at",
              "id",
              "ticket_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "activity": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Activity"
            },
            "type": "array"
          },
          "assignee": {
            "default": null,
            "description": "Agent profile id.",
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Comment"
            },
            "type": "array"
          },
          "created_at": {
            "type": "string"
          },
          "created_by": {
            "default": "user",
            "type": "string"
          },
          "deleted_at": {
            "default": null,
            "description": "Set when soft-deleted; the row stays on disk.",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "default": "",
            "type": "string"
          },
          "id": {
            "description": "Internal uuid.",
            "type": "string"
          },
          "key": {
            "description": "Human-readable id, e.g. KAN-1.",
            "type": "string"
          },
          "labels": {
            "default": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "order": {
            "default": 0,
            "description": "Position inside its column.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "priority": {
            "type": "string"
          },
          "status": {
            "description": "Column id.",
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "id",
          "key",
          "priority",
          "status",
          "title",
          "updated_at"
        ],
        "title": "Ticket",
        "type": "object"
      }
    },
    {
      "description": "Update a ticket field (title, description, status, priority, assignee, labels) and return the ticket.",
      "metadata": {},
      "name": "kanban::ticket::update",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "actor": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "assignee": {
            "default": null,
            "description": "Agent profile id, or null to unassign.",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "description": "Ticket uuid or human-readable key (KAN-1, case-insensitive).",
            "type": "string"
          },
          "labels": {
            "default": null,
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "priority": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id"
        ],
        "title": "TicketUpdateInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Activity": {
            "properties": {
              "actor": {
                "type": "string"
              },
              "at": {
                "type": "string"
              },
              "changes": {
                "items": {
                  "$ref": "#/definitions/Change"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "comment_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "seq": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "ticket_id": {
                "type": "string"
              },
              "type": {
                "description": "ticket.created | ticket.updated | ticket.moved | ticket.deleted | ticket.restored | comment.created",
                "type": "string"
              }
            },
            "required": [
              "actor",
              "at",
              "id",
              "seq",
              "ticket_id",
              "type"
            ],
            "type": "object"
          },
          "Change": {
            "properties": {
              "field": {
                "type": "string"
              },
              "from": true,
              "to": true
            },
            "required": [
              "field",
              "from",
              "to"
            ],
            "type": "object"
          },
          "Comment": {
            "properties": {
              "author": {
                "description": "Agent profile id, or `user`.",
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "id": {
                "type": "string"
              },
              "parent_id": {
                "default": null,
                "description": "Set for a reply; replies stay one level deep.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ticket_id": {
                "type": "string"
              }
            },
            "required": [
              "author",
              "body",
              "created_at",
              "id",
              "ticket_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "activity": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Activity"
            },
            "type": "array"
          },
          "assignee": {
            "default": null,
            "description": "Agent profile id.",
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "default": [],
            "items": {
              "$ref": "#/definitions/Comment"
            },
            "type": "array"
          },
          "created_at": {
            "type": "string"
          },
          "created_by": {
            "default": "user",
            "type": "string"
          },
          "deleted_at": {
            "default": null,
            "description": "Set when soft-deleted; the row stays on disk.",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "default": "",
            "type": "string"
          },
          "id": {
            "description": "Internal uuid.",
            "type": "string"
          },
          "key": {
            "description": "Human-readable id, e.g. KAN-1.",
            "type": "string"
          },
          "labels": {
            "default": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "order": {
            "default": 0,
            "description": "Position inside its column.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "priority": {
            "type": "string"
          },
          "status": {
            "description": "Column id.",
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "id",
          "key",
          "priority",
          "status",
          "title",
          "updated_at"
        ],
        "title": "Ticket",
        "type": "object"
      }
    },
    {
      "description": "Serve the kanban worker's injected console UI assets (content function for its console:script / console:style / console:module triggers).",
      "metadata": {
        "internal": true
      },
      "name": "kanban::ui-content",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Input of the content function: the console asks for one asset by path.",
        "properties": {
          "path": {
            "description": "The asset path from the trigger config (e.g. `state/page.js`).",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "UiContentInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Output of the content function.",
        "properties": {
          "content": {
            "description": "The asset source, verbatim.",
            "type": "string"
          },
          "content_type": {
            "description": "MIME type the console should serve the asset with.",
            "type": "string"
          }
        },
        "required": [
          "content",
          "content_type"
        ],
        "title": "UiContentResult",
        "type": "object"
      }
    }
  ],
  "triggers": [
    {
      "description": "Fires after every kanban mutation with the whole ticket. Config: { ticket_id?, events?, metadata? } — ticket_id matches the uuid or the human key.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "TicketDetail": {
            "description": "How much of the ticket a delivery carries. `full` is the ticket with its comments and activity inline; `summary` is the board-card projection (key, title, status, priority, assignee, labels, order, timestamps, comment_count) — the right choice for an agent that re-reads the ticket with `kanban::ticket::get` anyway.",
            "enum": [
              "full",
              "summary"
            ],
            "type": "string"
          }
        },
        "properties": {
          "events": {
            "description": "Event names to fire for (ticket.created, ticket.updated, ticket.moved, ticket.deleted, ticket.restored, comment.created). Omit for all.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "metadata": {
            "description": "Metadata attached to every invocation this binding receives."
          },
          "ticket": {
            "anyOf": [
              {
                "$ref": "#/definitions/TicketDetail"
              },
              {
                "type": "null"
              }
            ],
            "description": "`full` (default) delivers the whole ticket; `summary` only its board-card fields."
          },
          "ticket_id": {
            "description": "Ticket uuid or human key. Omit to fire for every ticket.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "ChangeConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "kanban:change",
      "return_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Activity": {
            "properties": {
              "actor": {
                "type": "string"
              },
              "at": {
                "type": "string"
              },
              "changes": {
                "items": {
                  "$ref": "#/definitions/Change"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "comment_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "seq": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "ticket_id": {
                "type": "string"
              },
              "type": {
                "description": "ticket.created | ticket.updated | ticket.moved | ticket.deleted | ticket.restored | comment.created",
                "type": "string"
              }
            },
            "required": [
              "actor",
              "at",
              "id",
              "seq",
              "ticket_id",
              "type"
            ],
            "type": "object"
          },
          "Change": {
            "properties": {
              "field": {
                "type": "string"
              },
              "from": true,
              "to": true
            },
            "required": [
              "field",
              "from",
              "to"
            ],
            "type": "object"
          },
          "Comment": {
            "properties": {
              "author": {
                "description": "Agent profile id, or `user`.",
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "id": {
                "type": "string"
              },
              "parent_id": {
                "default": null,
                "description": "Set for a reply; replies stay one level deep.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ticket_id": {
                "type": "string"
              }
            },
            "required": [
              "author",
              "body",
              "created_at",
              "id",
              "ticket_id"
            ],
            "type": "object"
          },
          "Ticket": {
            "properties": {
              "activity": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/Activity"
                },
                "type": "array"
              },
              "assignee": {
                "default": null,
                "description": "Agent profile id.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "comments": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/Comment"
                },
                "type": "array"
              },
              "created_at": {
                "type": "string"
              },
              "created_by": {
                "default": "user",
                "type": "string"
              },
              "deleted_at": {
                "default": null,
                "description": "Set when soft-deleted; the row stays on disk.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "description": {
                "default": "",
                "type": "string"
              },
              "id": {
                "description": "Internal uuid.",
                "type": "string"
              },
              "key": {
                "description": "Human-readable id, e.g. KAN-1.",
                "type": "string"
              },
              "labels": {
                "default": [],
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "order": {
                "default": 0,
                "description": "Position inside its column.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "priority": {
                "type": "string"
              },
              "status": {
                "description": "Column id.",
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "updated_at": {
                "type": "string"
              }
            },
            "required": [
              "created_at",
              "id",
              "key",
              "priority",
              "status",
              "title",
              "updated_at"
            ],
            "type": "object"
          },
          "TicketPayload": {
            "anyOf": [
              {
                "$ref": "#/definitions/Ticket"
              },
              {
                "$ref": "#/definitions/TicketSummary"
              }
            ],
            "description": "The `ticket` field of a delivery: the whole ticket, or its summary when the binding asked for `ticket: summary`."
          },
          "TicketSummary": {
            "description": "The board-card projection of a ticket.",
            "properties": {
              "assignee": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comment_count": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "created_at": {
                "type": "string"
              },
              "deleted_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "key": {
                "type": "string"
              },
              "labels": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "order": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "priority": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "updated_at": {
                "type": "string"
              }
            },
            "required": [
              "comment_count",
              "created_at",
              "id",
              "key",
              "labels",
              "order",
              "priority",
              "status",
              "title",
              "updated_at"
            ],
            "type": "object"
          }
        },
        "properties": {
          "activity": {
            "anyOf": [
              {
                "$ref": "#/definitions/Activity"
              },
              {
                "type": "null"
              }
            ]
          },
          "at": {
            "type": "string"
          },
          "comment": {
            "anyOf": [
              {
                "$ref": "#/definitions/Comment"
              },
              {
                "type": "null"
              }
            ]
          },
          "event": {
            "type": "string"
          },
          "ticket": {
            "$ref": "#/definitions/TicketPayload"
          },
          "ticket_id": {
            "type": "string"
          },
          "ticket_key": {
            "type": "string"
          }
        },
        "required": [
          "at",
          "event",
          "ticket",
          "ticket_id",
          "ticket_key"
        ],
        "title": "ChangeEvent",
        "type": "object"
      }
    },
    {
      "description": "Fires when a comment is added to one ticket, optionally filtered by author. Config: { ticket_id, author?, exclude_author?, root_only? }.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "TicketDetail": {
            "description": "How much of the ticket a delivery carries. `full` is the ticket with its comments and activity inline; `summary` is the board-card projection (key, title, status, priority, assignee, labels, order, timestamps, comment_count) — the right choice for an agent that re-reads the ticket with `kanban::ticket::get` anyway.",
            "enum": [
              "full",
              "summary"
            ],
            "type": "string"
          }
        },
        "properties": {
          "author": {
            "description": "Fire only for comments by exactly this author.",
            "type": [
              "string",
              "null"
            ]
          },
          "exclude_author": {
            "description": "Fire for every author except this one — pass your own profile id.",
            "type": [
              "string",
              "null"
            ]
          },
          "root_only": {
            "description": "Ignore replies; fire only for top-level comments.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "ticket": {
            "anyOf": [
              {
                "$ref": "#/definitions/TicketDetail"
              },
              {
                "type": "null"
              }
            ],
            "description": "`full` (default) delivers the whole ticket; `summary` only its board-card fields."
          },
          "ticket_id": {
            "description": "Required: ticket uuid or human key. A binding without it never fires.",
            "type": "string"
          }
        },
        "required": [
          "ticket_id"
        ],
        "title": "CommentConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "kanban:comment",
      "return_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Activity": {
            "properties": {
              "actor": {
                "type": "string"
              },
              "at": {
                "type": "string"
              },
              "changes": {
                "items": {
                  "$ref": "#/definitions/Change"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "comment_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "seq": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "ticket_id": {
                "type": "string"
              },
              "type": {
                "description": "ticket.created | ticket.updated | ticket.moved | ticket.deleted | ticket.restored | comment.created",
                "type": "string"
              }
            },
            "required": [
              "actor",
              "at",
              "id",
              "seq",
              "ticket_id",
              "type"
            ],
            "type": "object"
          },
          "Change": {
            "properties": {
              "field": {
                "type": "string"
              },
              "from": true,
              "to": true
            },
            "required": [
              "field",
              "from",
              "to"
            ],
            "type": "object"
          },
          "Comment": {
            "properties": {
              "author": {
                "description": "Agent profile id, or `user`.",
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "id": {
                "type": "string"
              },
              "parent_id": {
                "default": null,
                "description": "Set for a reply; replies stay one level deep.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "ticket_id": {
                "type": "string"
              }
            },
            "required": [
              "author",
              "body",
              "created_at",
              "id",
              "ticket_id"
            ],
            "type": "object"
          },
          "Ticket": {
            "properties": {
              "activity": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/Activity"
                },
                "type": "array"
              },
              "assignee": {
                "default": null,
                "description": "Agent profile id.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "comments": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/Comment"
                },
                "type": "array"
              },
              "created_at": {
                "type": "string"
              },
              "created_by": {
                "default": "user",
                "type": "string"
              },
              "deleted_at": {
                "default": null,
                "description": "Set when soft-deleted; the row stays on disk.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "description": {
                "default": "",
                "type": "string"
              },
              "id": {
                "description": "Internal uuid.",
                "type": "string"
              },
              "key": {
                "description": "Human-readable id, e.g. KAN-1.",
                "type": "string"
              },
              "labels": {
                "default": [],
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "order": {
                "default": 0,
                "description": "Position inside its column.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "priority": {
                "type": "string"
              },
              "status": {
                "description": "Column id.",
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "updated_at": {
                "type": "string"
              }
            },
            "required": [
              "created_at",
              "id",
              "key",
              "priority",
              "status",
              "title",
              "updated_at"
            ],
            "type": "object"
          },
          "TicketPayload": {
            "anyOf": [
              {
                "$ref": "#/definitions/Ticket"
              },
              {
                "$ref": "#/definitions/TicketSummary"
              }
            ],
            "description": "The `ticket` field of a delivery: the whole ticket, or its summary when the binding asked for `ticket: summary`."
          },
          "TicketSummary": {
            "description": "The board-card projection of a ticket.",
            "properties": {
              "assignee": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comment_count": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "created_at": {
                "type": "string"
              },
              "deleted_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "key": {
                "type": "string"
              },
              "labels": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "order": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "priority": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "updated_at": {
                "type": "string"
              }
            },
            "required": [
              "comment_count",
              "created_at",
              "id",
              "key",
              "labels",
              "order",
              "priority",
              "status",
              "title",
              "updated_at"
            ],
            "type": "object"
          }
        },
        "properties": {
          "at": {
            "type": "string"
          },
          "author": {
            "type": "string"
          },
          "comment": {
            "$ref": "#/definitions/Comment"
          },
          "event": {
            "description": "Always `comment.created`.",
            "type": "string"
          },
          "ticket": {
            "$ref": "#/definitions/TicketPayload"
          },
          "ticket_id": {
            "type": "string"
          },
          "ticket_key": {
            "type": "string"
          }
        },
        "required": [
          "at",
          "author",
          "comment",
          "event",
          "ticket",
          "ticket_id",
          "ticket_key"
        ],
        "title": "CommentEvent",
        "type": "object"
      }
    }
  ]
}
```
