# Delegation and skills — agentic expansion

This document describes how Furoshiki extends its own capabilities through **delegation** (generic agentic work via the external CLI against a **packaged install**: read-only shipped code under `SRC_DIR`, writable instance under `FUROSHIKI_DATA`) and **guarded self-modification** (skill / plugin proposals). It complements `docs/SELF-AWARENESS-DESIGN.md` and the auto-generated architecture snapshot (`furoshiki design snapshot`).

**Runtime plugin contract** (hooks, `PLUGIN.md`, Brain merge for `type: cron`): **`docs/PLUGIN-SPEC.md`**. **Human overview** (Brain vs plugins vs extensions queue): **`https://furoshiki.ai/plugins-extensions.html`**.

## Why this matters

- **Delegation** moves multi-step coding, research, and checks out of a single chat turn into a tracked job with proposals and instance-side artifacts (no git branch required on a packaged install).
- **Skills** add user-visible capabilities (plugins under `~/.furoshiki/plugins/`, slash commands under `~/.claude/commands/`) without editing the core repo in place during drafting.
- Both paths share **`delegation_jobs`** in `~/furoshiki/memory/longmemory.sqlite` and often persist human-readable artifacts under **`~/.furoshiki/proposals/`**.

**Unified operator queue:** **`extension_work_items`** holds one row per trackable unit of “extension” work (delegation and repair). It is listed in the **dashboard → Extensions** page and via **`furoshiki extensions`**. See **`docs/EXTENSIONS-UNIFIED-PIPELINE.md`**.

## Storage and artifacts

| Location | Role |
|----------|------|
| `extension_work_items` | Unified phase/title/summary for delegation + repair; links `delegation_job_id` and/or `repair_job_id` |
| `delegation_jobs` | Queue: `source`, `task_type`, `task_description`, `status`, `branch_name` (legacy / unused in product mode), `proposal_path`, `commit_sha` (optional proposal hash), … |
| `~/.furoshiki/proposals/*.md` | Proposal documents; filenames include `job<delegation_job_id>` after the timestamp (delegation “propose” outcomes and skill review results) |
| `~/.furoshiki/drafts/` | Staging only for self-modification creation pass |
| `mind_queue` | Notifications (`delegation_*`, `skill_build_invite`, `skill_draft_ready`, …) for Voice Dispatcher |
| Shipped `SRC_DIR` + instance `DATA_DIR` | Delegation dispatcher prompts the CLI to **read** bundled docs under `SRC_DIR` and **write** proposals/plugins under `~/.furoshiki` (see `scripts/delegation_dispatcher.py`) |

**Repair jobs** store optional `context_json` with `resolved_path` and `path_kind` (`shipped_core` vs `instance_plugin`) from `scripts/repair_paths.py`. Product-mode repair **never** edits shipped core; it proposes upgrades or fixes instance plugins. Set **`FUROSHIKI_REPAIR_GIT_MODE=1`** for dev clones that still commit to a local git tree.

## CLI backend configuration

Delegation tasks are executed by external CLI tools. Furoshiki supports multiple backends configured in **`~/.furoshiki/config/cli_backends.jsonc`** (auto-created from defaults on first use).

### Supported backends

| Backend | Binary path | Detection | Environment variable |
|---------|-------------|-----------|----------------------|
| `claude` | `~/.local/bin/claude` | `env_present` | `ANTHROPIC_API_KEY` |
| `cursor` | `~/.local/bin/agent` | `env_present` | `CURSOR_API_KEY` |
| `opencode` | `~/.opencode/bin/opencode` | `path_exists` | (none - uses auth.json) |

### Configuration file

The configuration file defines backend settings:
- **`binary`**: Path to CLI executable (supports `~` expansion)
- **`env_key`**: Required environment variable for authentication
- **`detection`**: How to detect availability (`env_present`, `path_exists`, `always_available`)
- **`default_args`**: Always-included command-line arguments
- **`model_arg`**, **`budget_arg`**, **`tools_arg`**: Parameter mapping
- **`enabled`**: Whether backend is active
- **`output_format`**: Output type (`text` or `json`)

### Backend selection priority

1. **`CLI_BACKEND` environment variable**: Explicit override (`claude`, `cursor`, or `opencode`)
2. **`active_backend` from config**: Defaults to `opencode` in shipped configuration
3. **Auto-detection**: Tries backends in `auto_detection_order` from config (default: `["opencode", "cursor", "claude"]`)
4. **Fallback**: If no backend available, uses legacy logic (`CURSOR_API_KEY` → `cursor`, else → `claude`)

### CLI management

```bash
furoshiki agent status           # Show backend status
furoshiki agent set claude       # Switch to Claude backend
furoshiki agent set cursor       # Switch to Cursor backend  
furoshiki agent set opencode     # Switch to opencode backend
```

### Backward compatibility

- Existing `CLI_BACKEND=claude|cursor` continues working
- No `furoshiki init` required - config auto-created on first use
- Delegation prompts maintain same `DELEGATION_RESULT_JSON` output contract across all backends

### Delegation job ID (lifecycle spine)

`create_delegation_job()` assigns **one** integer primary key per `delegation_jobs` row. The **operator-visible unit of work** is **`extension_work_items`**: one dashboard line per intent. Approving a proposal with **`furoshiki extensions jobs apply`** creates a **new** `delegation_jobs` row for implementation but **reuses the same** `extension_work_items` row (`reuse_extension_work_item_id`): `delegation_job_id` moves to the new job, `metadata_json.delegation_chain` lists every job id in order, and `root_delegation_job_id` is the original proposal job. Full history lives in **`extension_events`** (including continuation lines).

- **Database:** `extension_work_items.delegation_job_id` (active worker), `delegation_jobs.id` (each run / proposal file id), `metadata_json.delegation_chain` + `root_delegation_job_id`.
- **Proposal files:** `~/.furoshiki/proposals/<timestamp>-job<NN>-<slug>.md` (`job<NN>` matches the **proposal** delegation id, often the root).
- **Git (legacy):** older docs described branches `delegation/<id>-<slug>`; **packaged installs** do not use git worktrees — use **`furoshiki extensions jobs apply`** to continue work from a proposal.
- **Dashboard:** **Linked job** shows the active `DJ #…` and, when continued, `· from #<root>` for the proposal job.

`repair_jobs` uses a **separate** id space. When both appear on one extension row, **DJ** is the delegation lifecycle id and **RJ** is the repair pipeline id.

## Delegation job statuses

SQLite column **`delegation_jobs.status`** is the source of truth. The dashboard adds two **derived** fields (see `scripts/extension_work.py`, `scripts/dashboard_data.py`):

- **`display_phase`** — pill text on **Extensions**. For **`research`** and **`system_check`**, when status is **`proposed`**, the pill is **`complete`**: the LLM run is done and the **report file** under `~/.furoshiki/proposals/` is the deliverable (not “a proposal you must merge” in the same sense as code). **`code_change`**, **`create_script`**, and **`self_modification`** still show **`proposed`** when status is `proposed`.
- **`delegation_track`** — whether a dispatcher is expected to be active: **`queued`**, **`running`**, **`orphaned`**, **`failed`**, or **`idle`**. **`idle`** means terminal / no worker (e.g. `proposed`, `fixed`, `notified`) — not an error.

### Values of `delegation_jobs.status`

- **`pending`** — row inserted; waiting for a dispatcher run on this attempt  
- **`dispatched`** — worker subprocess in flight (or past SLA → **orphaned** track; see below)  
- **`proposed`** — worker wrote markdown under `~/.furoshiki/proposals/` (often `…-job<N>-….md`). Interpret by **`task_type`**:
  - **`research`** / **`system_check`**: finished **report**; read the file; **Apply** is not the normal next step.  
  - **`code_change`** / **`create_script`**: code change proposal; operator may **`furoshiki extensions jobs apply <id>`**.  
  - **`self_modification`**: skill proposal document (APPROVED / rejected narrative); install via plugins apply when approved.  
- **`fixed`** — auto-fix applied under the instance (e.g. `plugins/`) or stale-log resolution; **no** merge-to-upstream step in product mode  
- **`failed`** — exceeded attempts or unrecoverable error  
- **`notified`** and other terminals — see dispatcher; treated as finished for track purposes  

`self_modification` jobs do not use the **`fixed`** status for core code; they end as **`proposed`** with a proposal file.

### State flow (delegation job spine)

```mermaid
stateDiagram-v2
    [*] --> pending: create_delegation_job
    pending --> dispatched: spawn_delegation_dispatcher
    dispatched --> proposed: wrote proposal or report md
    dispatched --> fixed: auto_fix criteria met
    dispatched --> failed: error or give_up
    proposed --> pending: apply continues work\n(new DJ id, may reuse extension row)
    failed --> pending: redispatch if attempts remain
```

### Finished vs waiting (quick reference)

| Situation | `status` | Dashboard `display_phase` (typical) | `delegation_track` |
|-----------|----------|-------------------------------------|-------------------|
| Research / system_check report done | `proposed` | **`complete`** | **`idle`** |
| Code proposal awaiting you | `proposed` | **`proposed`** | **`idle`** |
| Worker inside SLA | `dispatched` | **`running`** | **`running`** |
| Dispatch timed out | `dispatched` | **`orphaned`** | **`orphaned`** |
| Gave up | `failed` | **`failed`** | **`failed`** |

**Stuck `dispatched`:** The dashboard and CLI derive **orphaned** from `dispatched_at` and the dispatcher CLI timeout (5 minutes) plus a grace window (`scripts/extension_work.py`). Past that, the worker likely exited without finalizing. **`furoshiki extensions jobs redispatch <id>`** sets the row back to `pending`, restores the extension phase, and spawns the dispatcher; for orphaned `dispatched` rows the previous dispatch **does not** consume an attempt slot (`attempt_count` is decremented before re-queue). **`redispatch --force`** bypasses the staleness check for `dispatched` or `failed` only (risk: duplicate worker if the original is still running). Failed jobs are redispatchable while `attempt_count < max_attempts`.

## Telegram → delegation (high level)

1. **Ingress:** `scripts/telegram_listener.py` receives a message.  
2. **Micro-brain (default):** `scripts/micro_brain.py` runs cheap **MODEL_MICRO** decisions through one orchestration contract, `MicroBrainRouter`. If `introspection_skip_delegation` is true, the message is treated as memory/recall introspection — **`classify_delegation_intent()` is not called** (same effect as the old regex pre-filter, but model-based).  
2b. **Explicit delegation wording (default on):** When `FUROSHIKI_DELEGATION_EXPLICIT_ONLY` is not `0`/`false`/`off`, **`classify_delegation_intent()` returns not-a-delegation without calling the FAST model** unless the **current message plus `conversation_excerpt`** matches tight phrases such as *delegate this*, *run it as a delegation*, *open a delegation job*, *dispatch a job*, *send this to the agent*, *put this through delegation*, or *agentic task in your repo*. This prevents architecture / “load your design” chat from being misclassified as delegation. Operators who want the old broad LLM gate set **`FUROSHIKI_DELEGATION_EXPLICIT_ONLY=0`**.  
2c. **FAST classify:** If the explicit gate passes (or explicit-only is off), `classify_delegation_intent()` in `scripts/classify_intent.py` (**FAST** model) decides delegation and `task_type`. Task typing defaults to **Furoshiki-system scope** unless the user explicitly requests an external project; `self_modification` is reserved for new standalone callable plugins, while changes to existing scripts/pipeline map to `code_change` / `create_script`.  
3. **Conversation thread:** Before classify (and before resolve), `build_delegation_transcript()` assembles the last **N** `User` / `Assistant` lines from `conversation_turns` in memory plus the current user line (default **N = 20**, `FUROSHIKI_DELEGATION_CONTEXT_TURNS`; total size capped by `FUROSHIKI_DELEGATION_CONTEXT_MAX_CHARS`). That excerpt is passed as **`conversation_excerpt`** so short replies like “do it” stay tied to a prior brainstorm. Clarifying questions and immediate dispatches **append to `_history` and `conversation_turns`** via `_save_history()` so the DB transcript is not missing those turns.  
4. **Worker brief (MICRO):** On dispatch, router-backed `prepare_delegation_task_for_job()` runs **`synthesize_delegation_task_brief()`** (MODEL_MICRO, `source=micro_brain_delegation_brief`) on the same transcript to produce a single **`task_brief`** string. That becomes the job’s **`task_description`** for the external worker; the FAST classifier’s line is preserved in **`context_json`** (`classifier_task_description`, `synthesis_ok`, `thread_turns_used`, …). Set **`FUROSHIKI_DELEGATION_THREAD_BRIEF=0`** to skip synthesis and use only the classifier description. **`FUROSHIKI_DELEGATION_BRIEF_MAX_TOKENS`** (default **2048**) avoids truncating long JSON.  
5. **Always-ask confirmation gate:** When confidence >= `DELEGATION_ASK_CONFIDENCE` (0.75), Furoshiki **always** asks a direct yes/no question ("Should I [task]?") and sets `_pending_delegation`. There is **no auto-dispatch** from a fresh message regardless of confidence or task type. The classifier's `clarifying_question` field (always present when `is_delegation=true`) is used if available; otherwise `_build_confirmation_question()` generates one from the task description. `_pending_delegation` also stores the transcript excerpt used for classification so Furoshiki can restate concrete requirements before confirmation. Pending state expires (~10 minutes).  
6. **Resolve (MICRO-first):** While pending, if the user asks for details ("what exactly will you build?"), the listener sends a voiced summary generated from the stored transcript/brief and keeps the confirmation gate open (TTL reset). Otherwise the user's answer goes through `classify_delegation_confirmation()` (MODEL_MICRO) first — a binary yes/no classifier with a regex fast-path for obvious answers. If confidence >= `DELEGATION_MICRO_CONFIRM_THRESHOLD` (0.85), the result is accepted immediately (confirmed → dispatch using the cached first-pass intent, denied → fall through). Only when MICRO is uncertain does the system fall back to the FAST `resolve_delegation_intent()` for nuanced re-classification (confidence must then reach `DELEGATION_RESOLVE_MIN_CONFIDENCE` 0.82).  
7. **Dispatch:** `_dispatch_delegation()` calls `create_delegation_job(task_description=…, context_json=…)` + `spawn_delegation_dispatcher()`.  
8. **Worker:** `delegation_dispatcher.py` loads the job; if `task_type == 'self_modification'`, it **re-execs** `self_modification_dispatcher.py` and returns. Otherwise it runs the headless CLI flow with bundled specs + `SRC_DIR`/`DATA_DIR` rules ( **`context_json`** appears under “Additional Context” when set).  
8. **Outcome:** DB updated, `mind_queue` row enqueued, user can use Telegram `/proposals` and `/apply <id>` or the CLI `furoshiki delegate` commands.

**API parity:** `POST /inject/conversation` accepts optional **`history`** (list of `{role, content}`) for the same transcript behavior without Telegram. `POST /api/v1/chat/messages` builds the transcript from **`conversation_turns`** after inserting the user row, or from optional **`history`** on the body if provided.

```mermaid
flowchart TD
  subgraph harness [Telegram harness]
    TL[telegram_listener.py]
  end
  subgraph intent [Intent]
    CD[classify_delegation_intent]
    RV[resolve_delegation_intent]
    PD[_pending_delegation]
  end
  subgraph queue [Queue]
    CJ[create_delegation_job]
    SD[spawn_delegation_dispatcher]
    DJ[(delegation_jobs)]
  end
  subgraph workers [Workers]
    DD[delegation_dispatcher.py]
    SM[self_modification_dispatcher.py]
    WT[CLI: instance + shipped docs]
  end
  subgraph surface [Surface to user]
    MQ[(mind_queue)]
    VP[voice_dispatcher / Telegram]
  end
  TL --> CD
  MB[prepare_delegation_task_for_job]
  CD -->|borderline| PD
  PD --> RV
  CD -->|dispatch| MB
  RV -->|confirmed| MB
  MB --> CJ
  CJ --> DJ
  SD --> DD
  DD -->|self_modification| SM
  DD -->|other task types| WT
  SM --> DJ
  WT --> DJ
  DJ --> MQ
  MQ --> VP
```

### Telegram when a job **finishes** (Voice Dispatcher)

When the worker **finalizes** (`delegation_dispatcher._finalize` or `self_modification_dispatcher._finalize`), Python inserts a **`mind_queue`** row:

| Outcome | `mind_queue.kind` | Typical priority |
|---------|-------------------|------------------|
| Proposal or report written | `delegation_proposed` | `high` |
| Small instance-side auto-fix | `delegation_fixed` | `high` |
| Gave up / error | `delegation_failed` | `high` |
| Inner monologue / deep reflection: offer to start a build | `skill_build_invite` | usually `high` / `normal` |
| Self-modification draft done (proposal file) | `skill_draft_ready` | `high` (approved path) or `normal` (review issues) |

**Not an instant DM.** `scripts/outreach_pulse.py` (cron, typically every ~5 minutes) runs **`voice_dispatcher.py`**, which takes the top pending row, calls the **deep** model with identity shell + session context, and sends **one short in-character Telegram**. Quiet hours, cooldown since the last proactive send, and “conversation too recent” still apply; **extension outcomes** (kinds above) and health/repair alerts get **relaxed gates** after a few minutes so completion is not stuck for hours behind generic queue noise.

Doctor may **restart services** via the repair API when safe; it does not modify shipped Python in site-packages.

**Gaps:** **`dispatched`** jobs that go **orphaned** (worker died or timed out without `_finalize`) **do not** enqueue `mind_queue` — there is no terminal row yet. Use **`furoshiki extensions jobs redispatch`**. If you **delete** a job from the CLI/dashboard, matching pending `mind_queue` rows are removed; there is no separate “cancelled” ping.

## Generic delegation workflow (non–self-modification)

- **Read** shipped documentation and code under `SRC_DIR` (bundled `docs/PLUGIN-SPEC.md`, etc., injected in the dispatcher prompt). **Write** proposals and user plugins under **`FUROSHIKI_DATA`** (`~/.furoshiki/proposals/`, `plugins/`).  
- **Auto-fix** only when strict criteria are met **and** the change targets **instance-writable** paths (e.g. `plugins/`), never shipped core — see `scripts/delegation_dispatcher.py`.  
- Otherwise **propose:** write `~/.furoshiki/proposals/<timestamp>-job<id>-<slug>.md` and set status **`proposed`**.  
- **CLI:** `furoshiki delegate` (list), `view`, `apply` (spawns implementation job or applies skill-style proposal), `merge` (legacy dev workflow where git applies), `delete` (removes job, proposal file, `mind_queue` refs).
- **Dashboard:** The **Delegations** page lists `delegation_jobs` with **View** (job fields + proposal file content when on disk), **Apply** (`POST /dashboard/api/delegations/{id}/apply` → `furoshiki delegate apply`), and **Delete** (`DELETE /dashboard/api/delegations/{id}` → `furoshiki delegate delete`). Same bearer token as the rest of the dashboard API.
- **Dashboard — Extensions:** Lists **`extension_work_items`** (joined with delegation/repair). **View** loads the extension row and merges proposal content from the linked delegation job when present. **Apply** / **Delete** are available when a **`delegation_job_id`** is set (same subprocess contract as Delegations).

## Skill / self-modification creation

Triggered by `furoshiki skills create "…"`, Telegram routing with `task_type=self_modification`, or other callers of `create_delegation_job(..., task_type='self_modification')`.

1. **Backup gate** — must succeed before any Claude call.  
2. **Creation pass** — tools restricted; writes only under `~/.furoshiki/drafts/`.  
3. **Review pass** — independent read-only review; low confidence “approve” → reject.  
4. **Proposal** — Python writes `~/.furoshiki/proposals/…md` with embedded content and status (**APPROVED** / rejected narrative).  
5. **DB** — `delegation_jobs.proposal_path` set; `mind_queue` kind **`skill_draft_ready`**.  
6. **Install** — User runs `furoshiki plugins apply <file>` or `furoshiki delegate apply <job_id>` for approved skill proposals. The operator **dashboard** (Skills page, `GET/DELETE/POST` under `/dashboard/api/skills/*` with the same bearer token as other dashboard JSON) can **view** full proposal text, **delete** a file (same cleanup as `furoshiki skill delete`), or **Apply** (subprocess `furoshiki plugins apply <file>` when the body contains `APPROVED`).

```mermaid
sequenceDiagram
  participant User
  participant CLI as furoshiki CLI
  participant DB as delegation_jobs
  participant DD as delegation_dispatcher
  participant SM as self_modification_dispatcher
  participant Drafts as "~/.furoshiki/drafts"
  participant Prop as "~/.furoshiki/proposals"
  participant MQ as mind_queue
  User->>CLI: skills create description
  CLI->>DB: insert self_modification job
  CLI->>DD: spawn(job_id)
  DD->>SM: subprocess(self_modification_dispatcher)
  SM->>Drafts: creation pass writes draft
  SM->>SM: review pass
  SM->>Prop: write proposal markdown
  SM->>DB: update status + proposal_path
  SM->>MQ: skill_draft_ready
  User->>CLI: plugins apply OR delegate apply
  CLI->>User: install plugin or command
```

### Self-modification: hook types and test examples (Telegram / CLI)

Furoshiki’s plugin system defines several **hook** types in **`docs/PLUGIN-SPEC.md`**. When you start a self-modification job, the worker needs to know which **one** is primary for that run.

| Mechanism | How the hook type is set |
|-----------|-------------------------|
| **Telegram** shortcuts like `/plugin_callable` … | `context_json.plugin_hook_type` is set automatically (see `scripts/telegram_listener.py`). |
| **CLI** | `furoshiki extensions skills create "…" --hook <TYPE>` stores the same key on the job. |
| **Otherwise** | `scripts/plugin_self_mod.py` resolves from task text (e.g. “hook with type: pre_conversation”) or defaults to **`callable`**. |

After upgrading Furoshiki, **restart the Telegram listener** so `/plugin_*` commands and `context_json` match the current code path.

**End-to-end:** create job → `self_modification_dispatcher` drafts under `~/.furoshiki/drafts/` → review → proposal `~/.furoshiki/proposals/*.md` → **you** run **`furoshiki extensions jobs apply <job_id>`** (or `furoshiki extensions plugins apply <file>`) when the proposal is **APPROVED** → plugin installed under `~/.furoshiki/plugins/<name>/` → `furoshiki extensions plugins reload` as needed (cron hooks merge into `memory/schedules.json`). **Do not put API secrets in chat logs**; use placeholders and store keys in instance config the operator controls.

**Telegram — copy/paste (body text only; one command per line):**

```text
/plugin_callable Fetch a one-line joke from a public joke API; return text in the tool result. Use network permission. No API keys.

/plugin_pre_conversation Add a short bullet "weather note" line if the user's message mentions weather; output valid pre_conversation JSON per PLUGIN-SPEC.

/plugin_post_conversation Append a single line to a small log file under the plugin's data dir describing turn count only; keep it tiny; no secrets.

/plugin_post_inbound If the user says "recall mode", set conversation_mode override to chat_recall in the JSON output; otherwise no change.

/plugin_pre_reflection For target deep: add a short "focus topic" string to the pre-reflection JSON content field based on the last user themes; keep it safe and generic.

/plugin_post_reflection For target all: no stdout required; just log a single marker line to plugin logs via PluginClient.log at info level.

/plugin_cron Every day at 12:00 UTC, append one line to a plugin local log file with a heartbeat timestamp; use schedule and entry in PLUGIN.md; standalone script, no --mode.

/plugin_soul For target profile_gap: if candidate field is "favorite_color", suppress it with a short reason; otherwise pass through.

/delegate_skill Propose a minimal callable plugin "echo_ping" that returns { "content": "pong" } on stdout. No network.
```

**Aliases** (same jobs): `/propose_plugin_<name> …` matches `/plugin_*` in `telegram_listener` (e.g. `/propose_plugin_callable`). Long hook names: `/plugin_post_inbound` and `/plugin_post_inbound_signals` → `post_inbound_signals`; `/plugin_soul` and `/plugin_soul_engine` → `soul_engine`.

**CLI — example with explicit hook:**

```bash
furoshiki extensions skills create \
  "Minimal callable plugin: name echo_test, returns JSON label/content/error=null; stdlib only; furoshiki_plugin_sdk for any Furoshiki interaction." \
  --hook callable
```

**After install — verification:**

- **Callable:** `furoshiki extensions skills callable` or Telegram `/callables`.
- **Installed tree:** `furoshiki extensions plugins list` or Telegram `/plugins`.
- **Cron:** `memory/schedules.json` entries with id prefix `plugin__` after reload.

## Callable skills (runtime invocation)

Distinct from **delegation** and from **proposal creation**: once a plugin under `~/.furoshiki/plugins/` declares `type: callable` hooks (each with a unique `name`), Furoshiki can run those tools **during** a normal reply, micro-contemplation, or deep reflection — no `delegation_jobs` row.

1. **`plugin_loader.get_callable_manifest()`** / **`callable_manifest_to_openrouter_tools()`** — tool list for OpenRouter native `tools` (and for non–Telegram `plan_and_invoke` paths).
2. **`llm.call_llm_chat_with_tool_loop()`** (Telegram) — main **DEEP** chat completion includes `tools`; the model returns `tool_calls`, Python runs **`plugin_loader.run_callable_skill()`**, then `role: tool` messages continue the same turn until final text. **Micro-contemplation / deep reflection** still use **`skill_orchestrator.plan_and_invoke()`** — OpenRouter tool loop with **`source=skill_orchestrator_plan`** (or micro-contemplation may supply `skill_calls` directly in JSON, skipping planning).
3. **`plugin_loader.run_callable_skill()`** — subprocess with `--mode callable`, `--args-json`, `--state-json`.
4. **Results** — On Telegram, tool output is in the chat history for the same reply. On other harnesses, `format_skill_results_for_prompt()` may still inject **raw** JSON into a prompt when that path uses it. **`high` / `urgent`** → `results_to_mind_queue` (`kind=skill_result`, neutral `summary`, full `detail`) → **Voice Dispatcher** LLM composes the proactive Telegram.

```mermaid
sequenceDiagram
  participant H as harness
  participant LLM as OpenRouter_chat
  participant PL as plugin_loader
  participant Sub as plugin_subprocess
  participant MQ as mind_queue
  participant VD as voice_dispatcher
  H->>LLM: chat_completions_with_tools
  LLM-->>H: tool_calls_or_text
  loop until final text
    H->>PL: run_callable_skill
    PL->>Sub: subprocess mode_callable
    Sub-->>PL: stdout JSON
    H->>LLM: messages_plus_tool_results
  end
  H->>MQ: results_to_mind_queue if high_or_urgent
  VD->>LLM: separate pulse composes proactive DM from queue
```

## CLI quick reference

| Command | Purpose |
|---------|---------|
| `furoshiki skills` / `skills list` | Numbered list of `~/.furoshiki/proposals/*.md` (default: hide REJECTED/FAILED) |
| `furoshiki skills list --status all` | All proposal files |
| `furoshiki skills callable` | List installed **callable** tools (active plugins, `type: callable` hooks) |
| `furoshiki skill delete <n>` / `skills delete <n>` | Delete row **#n** from that list; removes matching `delegation_jobs` + `mind_queue` rows by `proposal_path` |
| `furoshiki skills create "…"` | New self-modification job |
| `furoshiki skills create "…" --hook <TYPE>` | Same, with `plugin_hook_type` set (e.g. `cron`, `pre_conversation`, `post_inbound_signals`, `callable` — see **PLUGIN-SPEC.md**) |
| `furoshiki delegate …` | Full job DB view and lifecycle actions |
| `furoshiki extensions` / `extensions list` | Unified `extension_work_items` list (delegation + repair tracks) |
| `furoshiki extensions view <id>` | Full extension row + joined delegation/repair fields (`--json`) |

**Index contract:** The **#** column matches `skill delete` / `skills delete` when both use the same `--status` filter.

## Agent tasks (long-horizon callable sequences)

For tasks that require many model rounds, resource locking, and approval gating, see **`docs/AGENT-TASKS.md`**. Key distinction:

| Feature | Callable skills / delegation | Agent tasks |
|---|---|---|
| Completion | Single reply or async subprocess | Many LLM rounds across Brain ticks |
| Persistence | `extension_work` / `repair_jobs` | `agent_tasks` + `agent_task_steps` |
| Concurrency safety | None (fire-and-forget) | `BEGIN IMMEDIATE` resource locks |
| Approval gate | Not built-in | `requires_approval: true` + `request_approval` meta-tool |
| Scheduling | One-shot via Brain task or cron | Resumed each minute by `agent_runner.py` |

## Related source files

- `scripts/telegram_listener.py` — harness, `/proposals`, `/apply`, delegation routing  
- `scripts/classify_intent.py` — `create_delegation_job`, `spawn_delegation_dispatcher`, intent helpers  
- `scripts/delegation_dispatcher.py` — generic delegation (product mode: instance writes + bundled spec injection)  
- `scripts/self_modification_dispatcher.py` — two-pass skill pipeline  
- `scripts/skill_orchestrator.py` — callable skill planning, prompt injection, urgent `mind_queue` rows  
- `scripts/plugin_loader.py` — `get_callable_manifest`, `run_callable_skill`, and all hook runners  
- `scripts/tool_runtime.py` — ToolRuntime: iteration budget, dedup, cooldowns, urgency routing  
- `scripts/resource_registry.py` — canonical resource vocabulary for agent locking  
- `scripts/agent_tasks.py`, `agent_locks.py`, `agent_runner.py` — agent task persistence + locking  
- `bin/furoshiki` — `skills`, `skill`, `skills callable`, `delegate`, `extensions`, `plugins apply`, `agents`  
- `scripts/dashboard_data.py` — `collect_extension_work`, `get_extension_work_detail`  

---

*Last updated: 2026-04-22 — Added self-modification hook-type resolution (`plugin_self_mod`, Telegram `context_json`, `skills create --hook`) and test examples in this file.*
