# Project documentation index — Single entry point

**Project:** Kronn — Self-hosted CLI + web UI for managing AI coding agents (Claude Code, Codex, Vibe, Gemini CLI, Kiro) across git repositories. Unified workflow engine for cron, multi-step pipelines, tracker-driven automation, and manual triggers.

> **All files under `docs/` are in English by default.** Project documentation must be written in English.
> **ATTENTION — This is the reference file for all AI agents.**
> Read this file first, then follow the context loading strategy below.
> Do not read the other config files (.cursorrules, copilot-instructions, etc.) — they redirect here.

> **CRITICAL — Never hallucinate.**
> - **Never invent information** (tech stack, conventions, architecture, file paths...).
> - If you are unsure about something: **check the `docs/` documentation first**.
> - If you still don't find the answer: **ask the user** rather than guessing.
> - After getting the answer: **update the relevant `docs/` file** so the knowledge is captured.
> - Getting it right matters more than answering fast — hallucinations waste everyone's time.

> **CRITICAL — MCP tool usage.**
> Before calling any MCP tool, **read the matching context file** in `docs/operations/mcp-servers/<mcp-name>.md` **if it exists**.
> These files contain project-specific rules, constraints, and examples that prevent hallucinations and misuse.
> If no context file exists for an MCP, **proceed normally** — do not block on a missing doc.
> (Each MCP tool ships its own `description` field via JSON-RPC `tools/list` ; that's the in-band contract. Context files are an additional, optional layer of project-specific guidance.)

**Unknown term?** → `docs/glossary.md` first.

This folder (`docs/`) contains structured project context (for both humans and AI agents). Use paths relative to repo root.

---

## 1. Entry procedure (mandatory)

### Tiered context loading strategy

#### Tier 1 — Always read
- `docs/AGENTS.md` (this file)

**Trivial tasks** (typos, config tweaks, simple style fixes): Tier 1 may suffice.

#### Common tasks — load exactly these files

| Task | Files to load |
|------|---------------|
| Backend API changes | `docs/repo-map.md`, `docs/coding-rules.md` |
| Frontend UI changes | `docs/repo-map.md`, `docs/coding-rules.md` |
| Add new API endpoint | `docs/repo-map.md`, `docs/architecture/overview.md` |
| Workflow engine work | `docs/architecture/overview.md`, `docs/inconsistencies-tech-debt.md`, `docs/coding-rules.md` |
| Docker / deployment | `docs/operations/debug-operations.md` |
| Secret themes / unlock-gated features | `docs/operations/secret-themes.md` |
| **Désagentification / `ApiCall` step** (workflow engine calls APIs directly, zero tokens) — incl. AI helper bubble | `docs/operations/deagent-apicall.md` |
| Fix known issue | `docs/inconsistencies-tech-debt.md` |

#### Tier 2 — For needs not covered above (max 3 files)

| Need | File |
|------|------|
| repo structure / code placement | `docs/repo-map.md` |
| testing / quality | `docs/testing-quality.md` |
| coding rules | `docs/coding-rules.md` |
| known issues / tech debt | `docs/inconsistencies-tech-debt.md` |
| Architecture decisions | `docs/decisions.md` |
| term definitions / project jargon | `docs/glossary.md` |

#### Tier 3 — Escalation
Only if Tier 1 + 2 are insufficient: state which file you need and why, read it, or ask the user.
Never load everything "just in case".
- Architecture overview → `docs/architecture/overview.md`

---

## 2. Prerequisites before running commands

- **Docker + Docker Compose** required for running the full stack.
- **Windows**: WSL2 is mandatory — Kronn mounts host binaries, Unix sockets, and Linux paths. Docker Desktop is not required; Docker Engine inside WSL works. Install WSL: `wsl --install`.
- Start: `./kronn start` or `make start` (builds and runs all services).
- Stop: `./kronn stop` or `make stop`.
- Restart: `./kronn restart`.
- Logs: `./kronn logs` or `make logs`.
- Dev backend only: `make dev-backend` (cargo watch with auto-reload).
- Dev frontend only: `make dev-frontend` (Vite dev server on :5173).
- After changing Rust models with `#[derive(TS)]`: run `make typegen` to regenerate `frontend/src/types/generated.ts`.
- After `docker compose build`, always restart the gateway: `docker compose restart gateway` or `docker compose down && docker compose up -d`.

---

## 3. DO NOT (common mistakes)

- Do **not** guess when information is missing — ask the user.
- Do **not** load all Tier 2 files at once — pick up to 3 max.
- Do **not** modify business code when the task is only about project documentation — edit `docs/` only.
- Do **not** edit `frontend/src/types/generated.ts` by hand — run `make typegen`.
- Do **not** register two `.route()` calls with the same path in axum — chain methods: `.route("/path", get(h1).post(h2))`.
- Do **not** forget `#[derive(PartialEq)]` on enums used in comparisons (`AgentType`, `MessageRole`).
- Do **not** use the old axum 0.7 path-param syntax `:id` — axum 0.8 expects `{id}`. Failure to migrate means the route panics on registration.
- Do **not** wrap an extractor in `Option<Query<…>>` or `Option<ConnectInfo<…>>` — axum 0.8 dropped `OptionalFromRequestParts` for those. Use the concrete extractor with a sentinel default (`Query<…>` with `page=0` "no pagination") or wrap via `Option<Extension<…>>`.
- Do **not** slice `&str` with a hard-coded byte index (`&s[..N]`) when truncating user/agent text — UTF-8 (French, emoji, accented filenames) panics at non-boundary bytes. Use `s.chars().take(N).collect::<String>()` instead. Pattern documented in `feedback_rust_str_slicing` memory.
- Do **not** rely on `disabled={state}` alone to gate an async button handler — React's state update is async, so two synchronous clicks read the stale closure and fire two API calls. Use `useRef` + check at top of handler. Helper: `useAsyncGuard` in `frontend/src/hooks/useAsyncGuard.ts`.
- Do **not** nest a `<button>` inside another `<button>` — invalid HTML and produces a React dev warning. Convert the outer to `<div role="button" tabIndex={0}>` with explicit `onKeyDown` for Enter/Space.

---

## 4. Development constraints

- **Docker-first**: the full app runs via `docker compose`. Backend, frontend, and gateway are separate services.
- **Quality is mandatory**: `cargo clippy -- -D warnings` must pass. Frontend: `npx tsc --noEmit` + `pnpm test`. Shell: `make test-shell`. E2E: `pnpm test:e2e` (Playwright; backend must be running, Vite is auto-spawned). The `frontend/e2e/` tree has its own README — fixtures + page objects + 35+ specs covering the smoke surface, the wizard, the guided tour, MCP host-discovery, and the QP launch double-click race.
- **Type generation**: Rust models are the source of truth. TypeScript types are auto-generated via `ts-rs`.
- If stdout/stderr is missing: ask the user to copy/paste the full output.

### Testing rule (mandatory)

**Every code change MUST include tests.** This is not optional — tests are the primary defense against regressions and AI hallucinations.

| Change type | Required tests |
|-------------|---------------|
| New API endpoint | Integration test in `backend/tests/api_tests.rs` (HTTP request → response assertion) |
| New backend function | Unit test in same file (`#[cfg(test)] mod tests`) |
| Bug fix | Regression test proving the bug is fixed (test fails without fix, passes with) |
| New frontend component | Test file in `__tests__/` (render + key interactions) |
| Frontend behavior change | Update existing tests + add edge case coverage |
| Database migration | Verify migration applies cleanly in existing DB tests |

**Test quality rules:**
- Test **behavior**, not implementation details.
- Include **edge cases**: empty input, large input, unicode, error paths.
- Assertions must be **meaningful** — not just "renders without crashing".
- Mocks must match **real API shapes** (check `types/generated.ts`).
- Run `cargo test` (backend) and `npx vitest run` (frontend) **before declaring a task done**.
- If a test is flaky, fix the root cause — do not add retries or sleeps.

**Why this matters:** A failing test catches a bug in seconds. Without tests, bugs surface in production, require debugging, and cost 10-100x more tokens to fix. Tests also prove to the user that the code works — "all 500 tests pass" is more convincing than "I think it's correct".

---

## 5. Source of truth

- Project documentation: `docs/AGENTS.md` (this file) + `~/.kronn/user-context/` for personal/role-specific overrides. The legacy `ai/` directory was migrated to `docs/` in 0.7.1 (kept here as historical note; tooling no longer reads from `ai/`).
- Rust data models: `backend/src/models/mod.rs`.
- TypeScript types: `frontend/src/types/generated.ts` (auto-generated from Rust).
- API routes: `backend/src/lib.rs` (router definition in `build_router()`).
- Database schema: `backend/src/db/sql/001_initial.sql` (+ migrations 002-019).
- Docker config: `docker-compose.yml`.

---

## 6. Code placement

Use `docs/repo-map.md` to decide.
- New API endpoints: add handler in `backend/src/api/<domain>.rs`, register route in `backend/src/lib.rs` (`build_router()`).
- Workflow engine code: `backend/src/workflows/`.
- New frontend pages: `frontend/src/pages/`.
- New hooks: `frontend/src/hooks/`.
- API client functions: `frontend/src/lib/api.ts`.
- Data models: `backend/src/models/mod.rs` (+ `make typegen`).

---

## 7. Code generation (critical behavior)

- Search the repo for similar implementations before writing.
- Use `docs/repo-map.md` to decide where code goes.
- If info is missing or ambiguous: ask questions; do not guess.
- If a "logical fix" requires a large/risky refactor: add an entry to `docs/inconsistencies-tech-debt.md`.
- **Write tests for every change** — see § 4 Testing rule. No exceptions.

### Project documentation maintenance rule
After completing a task: if you discovered something non-obvious (a gotcha, a missing pattern, an outdated doc), update the relevant `docs/` file before closing. Keep entries factual and concise.

---

## 8. Stack (facts)

| Layer | Technology |
|-------|------------|
| Backend | Rust (axum 0.8, tokio 1.x, serde 1, anyhow 1, rusqlite 0.39, ts-rs 12, reqwest 0.13, calamine 0.34, pdf-extract 0.10) |
| Frontend | React 19 + TypeScript 6 (Vite 8 / rolldown, Lucide icons 1.x, Node >= 24 LTS) |
| Styling | CSS tokens + utility classes + component classes (`src/styles/`). Inline `style={{}}` only for dynamic values. No CSS framework |
| i18n | Custom lightweight system (fr/en/es), localStorage, no external lib |
| Type bridge | ts-rs (Rust → TypeScript) |
| Database | SQLite (`kronn.db`, WAL mode, foreign keys) |
| Streaming | SSE (Server-Sent Events) for agent responses and workflow run updates |
| Container | Docker Compose (backend + frontend + nginx gateway) |
| Agents | Claude Code CLI, OpenAI Codex CLI, Vibe (Mistral), Gemini CLI (Google), Kiro (Amazon), **Ollama (local, v0.4.0)** — HTTP API streaming `/api/chat` with system/user role separation, zero cost. Planned: OpenCode, DeepSeek |
| MCP sync | 7 formats: `.mcp.json` (Claude), `.kiro/settings/mcp.json` (Kiro), `.docs/mcp/mcp.json` (Kiro new), `.gemini/settings.json` (Gemini CLI), `.vibe/config.toml` (Vibe), `~/.codex/config.toml` (Codex), `~/.copilot/mcp-config.json` (Copilot CLI). Also syncs Claude Code's `.claude/settings.local.json` `enabledMcpjsonServers` whitelist |
| Skills sync | Native SKILL.md files written to `.claude/skills/`, `.agents/skills/` (Codex), `.gemini/skills/` for progressive agent discovery. Profiles synced as agent files (`.claude/agents/`, `.gemini/agents/`, `.codex/agents/`, `.copilot/agents/`). Vibe/Kiro: prompt injection fallback |
| API keys | Multi-key per provider (named keys, active selection), stored in `config.toml` as `[[tokens.keys]]` array. Agent auth files synced (e.g. `~/.codex/auth.json`). Override toggle per provider without deleting keys. |
| Token tracking | Per-message `tokens_used` + `auth_mode` (override/local). Codex: parsed from stderr. Claude Code: `--output-format stream-json --verbose --include-partial-messages` (tokens from `result` event and `message_delta`). Ollama: `prompt_eval_count` + `eval_count` from streaming JSON `done` chunk (cost: $0). Gemini/Vibe: TODO. |

---

## 9. UI structure

Dashboard tabs (current / planned):

| Tab | Status | Content |
|-----|--------|---------|
| Projets | Done | Project list, AI audit pipeline (template → audit → validation), project bootstrap (create from scratch), MCP overview, per-project workflows/skills/doc viewer |
| Discussions | Done | Single/multi-agent chat, @mentions, orchestration, global discussions, archive/unarchive (swipe gestures), inline title editing, disabled agent detection. **⏹ Stop agent** button (CancellationToken via `AppState.cancel_registry`, CancelGuard RAII). **Partial response recovery (0.3.5)**: agent output checkpointed every ~30s/~100 chunks into `discussions.partial_response` (+ `partial_response_started_at` for chronological order). Backend restart converts dangling partials into Agent messages with "⚠️ Réflexion interrompue" footer + broadcasts `WsMessage::PartialResponseRecovered`. `POST /api/discussions/:id/dismiss-partial` for manual recovery. `send_message` refuses a new run while a partial is pending (`partial_pending` SSE error → frontend waits or dismisses). **Structured agent questions (0.3.5)**: `{{var}}: question` patterns in agent messages auto-render a mini-form (`AgentQuestionForm`) above ChatInput. **0.5.0 — Test mode (worktree swap-in-main)**: `🧪 Tester cette version` CTA in the ChatHeader swaps the main repo to the discussion's branch, global banner stays pinned while active, single-click exit restores previous branch + pops auto-stash + re-creates the worktree. Triple preflight (worktree dirty, main dirty, detached HEAD) with a dedicated modal for the MainDirty case (stash-and-proceed / commit-first / cancel). `POST /api/discussions/:id/test-mode/{enter,exit}` return tagged envelopes. Persistent across reboots via migration 034. **0.5.0 — Decoder-loop detection**: agent streams now kill the child after 50 consecutive identical non-whitespace deltas (fixes Claude Opus extended-thinking `</thinking>`-loop leaking 76 KB into one response on EW-7189). Parser-level strip of literal `<thinking>` tags is the first line of defense. **0.5.0 — Prompt over stdin for Claude Code**: `start_agent_with_config` writes the prompt to `stdin` instead of argv, bypassing Linux `ARG_MAX` (~128 KiB). `--append-system-prompt` still travels via argv but truncates at 100 KiB with a clear marker. **Split into 8 components**: DiscussionsPage (orchestrator) + ChatHeader, ChatInput, DiscussionSidebar, NewDiscussionForm, MessageBubble, SwipeableDiscItem, AgentQuestionForm — plus 2 test-mode components (`TestModeBanner`, `TestModeModal`) in 0.5.0 |
| Plugins | Done | Plugin registry — card grid + category pills, inline detail panel, per-project navigation. **0.5.0 — plugin kind: MCP \| API \| hybrid** (see §12). **56 plugins: 53 MCPs + 3 API plugins** (Chartbeat `apikey` query, Adobe Analytics OAuth2 S2S, Google Search `apikey` query). Per-card badges `🔌 MCP` / `🌐 API` / `MCP + API`. Kind-filter pills `All \| MCP \| API` on top. Publisher origin badges (official/community). Per-project MCP load indicator (green/orange/red). Env placeholders with realistic hints (sourced from `api_spec.config_keys` for API plugins, else static map). Eye toggle on add form; API plugins auto-expose non-secret fields as plain text with inline description. OAuth2 plugins get Kronn-managed bearer refresh transparent to the agent. Recent additions: MongoDB, Kubernetes, Qdrant, Perplexity, Microsoft 365, Chartbeat, Adobe Analytics, Google Search. Puppeteer removed (use Playwright). |
| Automatisation | Done | Two tabs: **Workflows** + **Quick Prompts**. Workflows: list (grouped by project), creation wizard (simple 3-step + advanced 5-step), detail + runs with live SSE, manual trigger, run deletion. MCP-based suggestions (10 templates). Structured inter-step contracts. AI Architect ("Create with AI" → discussion → `KRONN:WORKFLOW_READY`). Test step (dry-run + live streaming, state survives tab switches via module-level tracker). Starter templates (6 examples). Raw cron editor. **⏹ Cancel run** with cascade to child batch discussions via `parent_run_id`. **Notify step (0.3.5)**: `StepType::Notify` with webhook support (POST/PUT/GET), zero tokens, template rendering in URL + body. **Quick Prompts**: reusable prompt templates with `{{variables}}` and conditional sections `{{#var}}text{{/var}}`. Launch creates a discussion with rendered prompt and dynamic title. **Batch Quick Prompts (0.3.5)**: fan-out to N items (tickets / list / resolved template), each child gets its own discussion + optional worktree, aggregated in sidebar groups. Dry-run preview with per-item rendered prompt + per-item test button. **Active-runs popover (0.5.1)**: `ActiveRunsPopover` hijacks the nav-icon click when `runningWorkflows > 0` — renders a tray listing every live run (workflow name + project + live elapsed timer + one-click `⏹ Arrêter`) without leaving the current page. Inline `⏹ Stop` button also present on every `.wf-card` whose `last_run.status === Running \| Pending` (`stopPropagation` guards against opening the detail panel). `onMouseDown` stop on the nav button prevents the outside-click-closes-popover race. **0.6.0 — Workflow Engine 0.7.x step types**: `StepType::Gate` (human approval, `WaitingApproval` run status, `POST /api/workflows/.../decide`), `StepType::Exec` (allowlisted binary, argv literal, no shell), plus `WorkflowGuards` (timeout / max LLM calls / loop revisits → `StoppedByGuard`), artifacts (`---ARTIFACT:name---` → workspace files), durable run state (`---STATE:k=v---` + `{{state.X}}` + `{{iter.X}}`), `ConditionAction::Goto` loops, `on_failure` rollback steps, per-item Workflow + QP Export/Import. See § Workflow Engine 0.7.x features in section 9 below. **`StepType::ApiCall` (0.5.1 — désagentification)**: workflow step that hits an API plugin directly from the Rust engine (0 tokens), extracts JSON via `serde_json_path` (RFC 9535), pipes to the next step. Full wizard card (`ApiCallStepCard.tsx`) with plugin+endpoint pickers, Test button, clickable JSON tree for click-to-pick path generation, live preview debounced 150 ms via `/api/workflow-steps/test-extract`, 3 example-path buttons, next-step-batch compatibility banner, advanced options collapsible (timeout/retries/output_var/fail_on_empty). Starter template "Chartbeat top 5 → Agent résumé → Slack" cloneable from the wizard. Security triple-guard: SSRF host allowlist, DNS rebind block, `ResolvedAuth` redact (Bearer / api-keys / OAuth2). Auto-pagination shape detection (Jira offset / CF cursor / Stripe has_more / Jira v3 nextPageToken). See [`docs/operations/deagent-apicall.md`](operations/deagent-apicall.md). |
| Config | Done | Multi-key API management (incl. Mistral/Vibe API keys), token usage tracking, language, agent detection + permissions, agent usage dashboard links, Directives CRUD with live cards, DB management (**export ZIP** with data.json + config.toml, **import ZIP/JSON** with config merge + path remapping). **Global context (0.3.5)**: markdown textarea + mode dropdown (always/no_project/never), injected into agent prompts via `ServerConfig.global_context`. Skills/Profiles are now managed per-project on the Project page. **Skill auto-trigger opt-out (0.5.1)**: per-skill toggle backed by `auto_triggers` table — disable a skill from contributing to prompt injection without removing it. **RTK integration (0.5.1)** in the Agents section: `<CompressionSection />` card at the top with 3 activation states, one-click "Activate on all compatible agents" CTA, install modal with copy-paste curl when the binary is absent, live savings counter + 3-card expand (tokens / ratio / samples), sobriety (?) tooltip nuancing the "eco mode" label. Per-agent badge inline next to the version: 🟢 `RTK actif` / 🟡 hook missing / ⚪ not installed / italic `Non pris en charge par RTK` (Kiro, Copilot CLI, Vibe). See §13. |

Note: the old "Agents" tab has been merged into Config. Nav order: Projets → Discussions → Plugins → Workflows → Config. **"?" button** in nav replays the guided tour.

**Guided tour (0.3.6)**: 17-step interactive onboarding auto-launched on first visit. 5 acts (Projets → Plugins → Discussions → Automatisation → Config). 4 interactive steps with `waitForClick` (user must click the real UI element — pulse animation, "Next" blocked). Spotlight via box-shadow cutout, tooltip auto-positioned. Ends on Discussions page. Persistence: `kronn:tour-completed` in localStorage. Components: `TourProvider` (context + state machine), `TourOverlay` (portal), `tourSteps.ts` (declarative step definitions), `useTourPositioning.ts` (placement + MutationObserver).

### Project Bootstrap (create from scratch)

`POST /api/projects/bootstrap` — creates a new project directory, initializes git, installs AI template, creates a bootstrap discussion with architect + product-owner + entrepreneur profiles (3 profiles). **Bootstrap++**: skill `bootstrap-architect` auto-injected for gated validation flow:
1. Agent reads uploaded context files (architecture docs, specs, PRDs) → produces architecture summary → `KRONN:ARCHITECTURE_READY` → CTA validates
2. Agent generates project plan (epics, stories, estimates) → `KRONN:PLAN_READY` → CTA validates
3. Agent creates issues on tracker via MCP → `KRONN:ISSUES_CREATED` → CTA navigates to project

Frontend modal includes **drag & drop file upload** for documents. Files uploaded as context files after discussion creation. `BootstrapProjectRequest` accepts `skill_ids` for skill injection.

### Pre-audit briefing (optional)

`POST /api/projects/:id/start-briefing` — creates a briefing discussion where the AI asks 5 quick questions (project purpose, stack, team, conventions, watch points). The agent writes `docs/briefing.md` and emits `KRONN:BRIEFING_COMPLETE`. The briefing content is injected into each audit step via `PROMPT_PREAMBLE`. Agents without filesystem access (Vibe) are excluded from briefing/audit.

### CI pipeline

GitHub Actions workflow (`.github/workflows/ci-test.yml`) triggered on push to `main` + all PRs:
- `test-backend`: cargo clippy + cargo test (with sccache)
- `test-frontend`: tsc --noEmit + pnpm test (Node 24 LTS)
- `test-shell`: make test-shell (bats)
- `security-scan`: cargo audit + pnpm audit

### AI audit pipeline (4-state badge system)

Projects display 3 badges next to the title: `[FileCode] Project docs`, `[Cpu] AI audit`, `[ShieldCheck] Validated`.

| State | Project docs | AI audit | Validated | Meaning |
|-------|-------------|----------|-----------|---------|
| NoTemplate | gray | gray | hidden | No `docs/` directory |
| TemplateInstalled | green | orange | gray | Template copied, audit pending |
| Audited | green | green | gray | 10-step audit completed |
| Validated | green | green | green | Validation discussion resolved all TODOs |

- **Template install**: copies `docs/` skeleton + redirector files (CLAUDE.md, .cursorrules, etc.) + injects bootstrap prompt
- **AI audit**: 10-step SSE streaming, ~20 min. **Token cost: ~50K–150K tokens per audit** (depends on project size and agent model). With Claude Sonnet API pricing, expect ~$0.50–$2.00 per audit. Fills all `docs/` files.
- **Validation**: opens a prefilled discussion (locked title/prompt) where the AI asks questions about ambiguities. AI updates `docs/` files after each answer. Project page shows "validation en cours" + link to discussion (no validate button on project page).
- When the AI finishes all questions, it includes `KRONN:VALIDATION_COMPLETE` in its last message. This triggers a green banner in the discussion with a "Marquer l'audit comme valide" button. Similarly, `KRONN:BRIEFING_COMPLETE` signals the end of a pre-audit briefing discussion. `KRONN:WORKFLOW_READY` signals the AI Architect has produced a deployable workflow JSON (extracted from ```json block → one-click creation). **Bootstrap++ signals**: `KRONN:ARCHITECTURE_READY` → validate architecture, `KRONN:PLAN_READY` → validate plan, `KRONN:ISSUES_CREATED` → view project. Each gate sends a user message to continue the agent.
- **Mark as validated**: injects `<!-- KRONN:VALIDATED:date -->` marker into `docs/AGENTS.md`.
- AI config file badges (CLAUDE.md, .cursorrules, etc.) shown on a second line below the status badges.

### Audit drift detection

`GET /api/projects/:id/drift` — compares source file checksums against `docs/checksums.json` (generated during audit). Returns stale sections without consuming tokens. `POST /api/projects/:id/partial-audit` re-runs only stale steps (~3-5K tokens vs ~20K for full audit). UI shows an amber badge on stale projects with a "Mettre à jour" button.

**MCP drift auto-detection**: adding/removing/relinking a plugin on an audited project automatically invalidates the `.mcp.json` checksum, flagging drift for step 8 (MCP introspection) re-run.

### Workflow suggestions

`GET /api/projects/:id/workflow-suggestions` — matches installed MCPs against a hardcoded catalogue of 10 workflow templates. Returns suggestions with multi-step prompts, pre-filled triggers, and audience tags (dev/pm/ops). Suggestions use structured inter-step contracts for reliable data passing between collection and synthesis steps.

### Structured inter-step contract (canonical Kronn envelope, 0.8.5+)

**Every envelope-producing step type emits the same byte-for-byte shape via `workflows/step_output_format.rs::format_step_output`:**

```
[optional human-readable prefix line(s)]
---STEP_OUTPUT---
{"data": <any JSON>, "status": "OK|NO_RESULTS|ERROR|PARTIAL|PENDING|…", "summary": "<one line>"}
---END_STEP_OUTPUT---
[SIGNAL: <primary>]
[SIGNAL: <optional secondary>]
```

| Step type | Emits canonical envelope | Primary signal |
|---|---|---|
| `ApiCall`, `BatchApiCall` | yes | `OK` / `NO_RESULTS` / `ERROR` (+ `http_<code>` on HTTP errors) |
| `Exec` | yes | `OK` / `ERROR` + `exit_<code>` |
| `JsonData` | yes | `OK` |
| `Notify` | yes (0.8.5+) | `OK` / `ERROR` (0.8.5+, pre-fix none) |
| `BatchQuickPrompt` | yes (0.8.5+) | `OK` / `PARTIAL` / `ERROR` / `PENDING` (0.8.5+, pre-fix none) |
| `Agent` (Structured / TypedSchema) | yes — prompt template emits markers | whatever the prompt instructs |
| `Agent` (FreeText) | **no** — raw text only, consumers read `.output` only | whatever the agent prints |
| `Gate` | **no** — output is the rendered `gate_message`, has no semantic data | none — Gate is a pause, branch via `request_changes_target` |

For Agent steps with `output_format: Structured` or `TypedSchema`, the engine auto-injects the envelope instructions into the prompt, and `extract_step_envelope` parses the result via marker-delimited strategy-1 (preferred) or last-bare-JSON-with-`data`+`status` strategy-2 (legacy back-compat for pre-0.8.5 run records). For all other step types, the runner writes the canonical envelope directly. `TypedSchema { schema, on_invalid: Continue|Fail }` adds JSON-Schema validation on top of the same envelope — failures fall back to a repair prompt with the schema diagnostic, then optionally fail the step.

Downstream consumers read the same access patterns regardless of producer:
- `{{steps.X.data}}` — JSON payload (compact for objects/arrays, string for scalars)
- `{{steps.X.data.<path>}}` — nested traversal (dot-separated, numeric segments index arrays). Missing fields leave the placeholder literal AND `find_unresolved_critical_refs` fails the consuming step with an actionable error.
- `{{steps.X.summary}}` / `{{steps.X.status}}` — the one-line summary / status string
- `{{steps.X.data_json}}` — always-serialized JSON, useful for piping into a downstream HTTP body
- `{{steps.X.output}}` — raw output (every step type, always available — fallback for Gate / FreeText consumers)

Cross-step transmission is pinned by the comprehensive matrix in `backend/src/workflows/template.rs::cross_step_transmission` (17 tests) — any step type that regresses its emitted shape fails one localised test instead of silently breaking every consumer.

See `backend/src/workflows/step_output_format.rs` (the single emitter), `backend/src/workflows/template.rs` (the extractor + ctx), and `backend/src/models/mod.rs::StepOutputFormat` for the Agent-side variants.

### Workflow Engine 0.7.x features (shipped in 0.6.0)

These features are tagged `0.7.0 Phase X` in the source but ship as part of release 0.6.0. They extend the workflow engine beyond pure agent-step pipelines.

**1. Guards — execution-bound limits.** `WorkflowGuards { timeout_seconds, max_llm_calls, loop_detection_max_revisits }` on `Workflow`. The runner aborts with a dedicated terminal status `RunStatus::StoppedByGuard` (rendered orange in the UI, distinct from `Failed` / `Cancelled`). The `loop_detection_max_revisits` field is a per-step counter consumed by `ConditionAction::Goto`, defaulting to 10. Migration `039_workflow_guards.sql`. Models in `backend/src/models/mod.rs` (search `WorkflowGuards`, `StoppedByGuard`).

**2. Artifacts — persisted step outputs.** `Workflow.artifacts: HashMap<String, ArtifactSpec>` declares typed artifacts. Agents emit `---ARTIFACT:name---...---END_ARTIFACT---` blocks; the runner extracts them, validates against `ArtifactSpec`, and writes them to disk in the run's workspace. Migration `040_workflow_artifacts.sql`. Implementation in `backend/src/workflows/steps.rs` (extraction) and `backend/src/workflows/runner.rs` (persistence).

**3. Gate — human-in-the-loop step.** `StepType::Gate` pauses the run with `RunStatus::WaitingApproval`. Workspace + git worktree are preserved during the pause. `POST /api/workflows/:id/runs/:run_id/decide` accepts `{ decision: "approve" | "request_changes" | "reject", comment? }` (`backend/src/api/workflows.rs::decide_run`). Optional `WorkflowStep.gate_notify_url` fires a best-effort webhook on entering pause (POST). Implementation: `backend/src/workflows/gate_step.rs`. Frontend RunDetail surfaces a "À VALIDER" badge + "en pause depuis Xh" counter on `WaitingApproval` runs.

**4. Exec — direct shell binary, zero tokens.** `StepType::Exec` runs a binary listed in `Workflow.exec_allowlist: Vec<String>` (exact-match allowlist; empty list = Exec disabled). Security invariants: never `sh -c`, args passed as separate argv literals (no shell interpolation), allowlist matched on the binary basename. Migration `043_workflow_exec_allowlist.sql`. Implementation: `backend/src/workflows/exec_step.rs` (header comment documents the threat model).

**5. Loops + run state.** `ConditionAction::Goto { step_name, max_iterations }` enables backward jumps from `on_result` rules. `WorkflowRun.state: HashMap<String,String>` (migration `042_workflow_run_state.sql`) is the durable scratchpad: agents write `---STATE:k=v---` lines, runner persists on the run row. Templates: `{{iter.<step_name>}}` (per-step revisit counter from the loop guard) and `{{state.<key>}}`. The `loop_detection_max_revisits` guard fires `StoppedByGuard` when the per-step iter exceeds the limit. Template plumbing in `backend/src/workflows/template.rs`.

**6. Rollback / `on_failure`.** `Workflow.on_failure: Vec<WorkflowStep>` — a separate step list that fires **only** when the run terminates with `RunStatus::Failed`. Skipped on `Cancelled`, `StoppedByGuard`, and Gate `reject`. Templates `{{failed_step.name}}` and `{{failed_step.output}}` are exposed inside on_failure prompts. Migration `041_workflow_on_failure.sql`. The wizard rejects `Gate` inside the rollback list (Notify + Agent + ApiCall accepted).

**7. Per-item Export / Import (Workflow + Quick Prompt).** Self-contained envelope JSON with `kind` (`"kronn-workflow"` or `"kronn-quick-prompt"`), `version`, `exported_at`. Endpoints:
- `GET /api/workflows/:id/export` + `POST /api/workflows/import` (`backend/src/api/workflows.rs::export_workflow` / `import_workflow`)
- `GET /api/quick-prompts/:id/export` + `POST /api/quick-prompts/import` (`backend/src/api/quick_prompts.rs::export_qp` / `import_qp`)

Workflow export bundles all Quick Prompts referenced by `BatchQuickPrompt` steps in the same envelope so a workflow round-trips standalone. Import strips per-user fields (e.g. `gate_notify_url`) so a shared workflow doesn't leak the previous owner's webhook URLs.

**8. Launch variables (manual trigger forms).** `Workflow.variables: Vec<PromptVariable>` (migration `044_workflow_variables.sql`) mirrors `QuickPrompt.variables`. When a user clicks "Lancer" on a Manual-trigger workflow with non-empty `variables`, the page shows a launch modal asking for one value per declared variable; values are merged into the run's `trigger_context` so `{{var_name}}` resolves in step prompts (existing `inject_trigger_context` handles the path). `POST /api/workflows/:id/trigger` accepts an optional `Json<TriggerWorkflowRequest>` body with `{ variables: HashMap<String,String> }`; missing/empty required variables produce an SSE error before the run starts. Legacy callers (no body) keep working — `Option<Json<...>>` → `None` → no variables. Frontend scanner (`frontend/src/lib/scanUndeclaredVars.ts`) runs live in the wizard and warns when a step prompt references a `{{var}}` that doesn't match any earlier step / state / iter / artifact / declared variable, with a 1-click "add to launch variables" affordance.

**Wizard 0.6.0 additions** (`frontend/src/lib/workflow-templates/v07-presets.ts`):
- 3 non-trivial presets — `AUTO_DEV` (auto-dev with tests), `PR_GATE` (PR creation with Gate approval), `DEPLOY_ROLLBACK` (deploy with on_failure rollback).
- STATE pedagogy chips on Agent step cards when an `on_result: Goto` is detected (hint at `{{state.X}}` write/read pattern).
- Rollback wizard accepts Notify + Agent + ApiCall, explicitly rejects Gate.

### Ticket Autopilot — preset 0.7+ (Sprint 1)

`TICKET_TO_PR` dans `frontend/src/lib/workflow-templates/v07-presets.ts` — workflow opinionated qui prend un ticket en entrée et drive jusqu'à la PR prête au merge. 9 steps + on_failure rollback :

```
fetch_issue (JsonData fixture, swappable→ApiCall)
  → analyze (Agent + writing-plans + brainstorming + verification)
  → plan_gate (humain valide le plan)
  → implement (Agent + tdd + debugging + verification + receiving-code-review)
  → run_tests (Exec, Goto implement on ERROR max 5)
  → review (Agent + requesting-code-review + verification, Goto implement on NEEDS_CHANGES)
  → create_pr (Agent + finishing-a-development-branch + verification)
  → ready_gate (humain valide la PR)
  → notify_done (Notify webhook)
```

Limites assumées Sprint 1 : pas de `Wait`/Poll CI auto (Sprint 3), pas de `skip_if` ni "Ask Human" mid-run dynamique (Sprint 2), pas de webhook receiver pour reprendre sur retours review humaine (Sprint 4), pas d'auto-merge ApiCall (v2).

Doc utilisateur complète : [`docs/ticket-autopilot.md`](../docs/ticket-autopilot.md).

### Vendored external skills (0.7+)

`backend/src/skills/external/` contient 8 skills méthodologiques **vendored** depuis [`obra/superpowers`](https://github.com/obra/superpowers) (MIT, commit `e7a2d164`, imported 2026-05-04) :

| Skill | Use case |
|-------|----------|
| `test-driven-development` | rituel red-green-refactor strict |
| `systematic-debugging` | root-cause à 4 phases |
| `writing-plans` | structurer un plan multi-step |
| `brainstorming` | explorer intent + design avant code |
| `verification-before-completion` | anti "done = compiled" — evidence avant claim |
| `requesting-code-review` | structurer une review |
| `receiving-code-review` | technique pour appliquer feedback review |
| `finishing-a-development-branch` | créer PR avec verify-tests-first |

Toutes auto-loadées par `BUILTIN_SKILLS` dans `core/skills.rs`. Frontmatter étendu avec `external: true`, `source_url`, `source_path`, `source_commit`, `imported_at`. Le pattern d'attribution (Caveman directive) est appliqué : description suffix `Adapted from <url> (<license>).` séparé visuellement (italique + opacité 70%) dans la skill card. Lien `🔗 Source` cliquable + badge `🔗 External` rendus par `SettingsPage::AttributedDescription`.

Pas de migration SQL : les skills sont embeddées au compile-time via `include_str!()`. Bump backend version → restart pour les voir.

Catalog des sources + licences : [`THIRD_PARTY_SKILLS.md`](../THIRD_PARTY_SKILLS.md). Update process documenté.

### Modularité unitaire des workflows (0.7+)

Trois briques pour factoriser les workflows et coller au use-case "remplacer un step mécanique par un appel direct" :

**1. `quick_api_id` étendu à `StepType::ApiCall` single** (était 0.6.0 pour `BatchApiCall` uniquement). Quand set, le runner charge le `QuickApi` référencé via `quick_api_hydrate::hydrate_step_from_quick_api` et hydrate les fields `api_*` manquants (per-field override : le step gagne quand non-vide). Pattern : factoriser un appel canonique en QA, le réutiliser dans N workflows. Wizard `ApiCallStepCard` expose un picker "Depuis un Quick API existant" + bandeau "🔗 Hérité de {QA}".

**2. `quick_prompt_id` sur `StepType::Agent`** — symétrique. Helper `quick_prompt_hydrate::hydrate_step_from_quick_prompt` injecte `prompt_template`, `tier`, et `skill_ids` du QP au run-time si le step les a vides. Pas de variables au niveau step : les `{{var}}` du QP sont résolus avec le `TemplateContext` du workflow (launch variables / state / previous_step / steps.X). Wizard expose un picker dans la step Agent card.

**3. `StepType::JsonData`** (`json_data_step.rs`) — source de données déterministe. Émet le payload littéral stocké dans `json_data_payload` sous forme d'envelope Structured. Zéro token, zéro réseau. Use case canonique : alimenter un `BatchQuickPrompt`/`BatchApiCall` sur une liste figée (10 hosts hardcodés, 5 régions, 3 environnements) sans monter d'API. Aussi : fixture de dev — on construit le pipeline sur du JsonData puis on remplace par un `ApiCall` quand la vraie source est prête. Validation au save : payload JSON non-null + ≤ 1 MiB. Wizard rend un textarea JSON avec parser live + counter d'items.

**Wizard preset bonus** : `daily-host-audit` démontre la combo `JsonData → BatchQuickPrompt → Notify` (5 hosts pré-câblés, l'utilisateur édite la liste + picke le QP audit).

Pas de migration SQL : steps en JSON dans `workflows.steps_json`, les nouveaux champs sont `Option<...>` avec `serde(default)`, donc compatibilité ascendante intacte.

### Desktop app (Tauri)

- **System tray**: closing the window hides to tray, backend + scheduler keep running. Tray menu: "Ouvrir Kronn" / "Quitter". Double-click to reopen.
- **Wake lock**: when cron workflows are active, prevents OS sleep (Windows: `SetThreadExecutionState`, macOS: `caffeinate -w`). Auto-releases when no cron workflows remain.
- **PATH enrichment**: GUI apps on macOS inherit minimal PATH. `enrich_path()` at startup adds homebrew, npm global, cargo, nvm, fnm, bun, uv directories if they exist.
- **Ad-hoc codesigning**: macOS builds are signed with `codesign --force --deep -s -` when no Apple Developer certificate is configured. Users may need `xattr -cr /Applications/Kronn.app`.
- **Agent detection**: uses native PATH (enriched) + npx probe fallback. Agents found via npx only show as "npx" (orange badge) not "installed" (green badge).

### Plugin kind: MCP | API | hybrid (0.5.0)

Kronn plugins expose capabilities to agents in two ways:

| Kind | `mcp_servers.transport` | `mcp_servers.api_spec_json` | How agents use it |
|------|-------------------------|----------------------------|-------------------|
| **MCP** | `Stdio \| Sse \| Streamable` | NULL | Synced to `.mcp.json` / Vibe / Kiro / Gemini configs. Agents discover tools via `mcp__<server>__<tool>` naming. |
| **API** | `ApiOnly` | `{...}` | Skipped in `.mcp.json`. Capability surfaces via prompt injection (`## REST APIs available` section with curl examples + auth). Agents call via Bash `curl`. |
| **Hybrid** | any MCP variant | `{...}` | Both of the above. Agent picks the right approach. (e.g. Jira has both an MCP server and a REST API.) |

**Plumbing**:
- Rust models in `backend/src/models/mod.rs`: `McpTransport::ApiOnly`, `ApiSpec { base_url, auth, endpoints, docs_url, config_keys }`, `ApiAuthKind::{ApiKeyQuery, ApiKeyHeader, Bearer, OAuth2ClientCredentials, None}`, `ApiEndpoint`, `ApiConfigKey`, `OAuth2ExtraHeader`.
- Migration 035 adds `api_spec_json` (nullable) to `mcp_servers`. Zero impact on existing rows.
- `build_api_context_block()` in `core/mcp_scanner.rs` emits the API section from `(server, decrypted_env)` pairs. Called from `make_agent_stream` only for project discussions with at least one active API plugin. Disk MCP context is concatenated when both are present.
- `sync_project_mcps_to_disk()` matches on `transport` — `ApiOnly` is a silent skip.
- The `collect_active_api_plugins()` helper fetches + decrypts active API configs per project.
- `config_keys` lets a plugin declare non-secret parameters (e.g. Chartbeat's `host`, Adobe's `company_id`). The UI renders them as plain inputs with the provided `label` + `placeholder` + `description`; the prompt injection surfaces them alongside the auth so the agent has enough to build a full URL.
- `{ENV_KEY}` templating works in `ApiSpec.base_url` AND `OAuth2ExtraHeader.value_template`. Missing keys render as `<NOT_CONFIGURED:KEY>`.
- Default context (`default_context` on the registry entry) is auto-written to `docs/operations/mcp-servers/<slug>.md` at install time — for API plugins too.

**OAuth2 client-credentials (`ApiAuthKind::OAuth2ClientCredentials`)**:
- New module `backend/src/core/oauth2_cache.rs` — in-memory `HashMap<config_id, CachedToken>` on `AppState.oauth2_cache` (Tokio `Mutex`, `tokio::sync::Mutex`). Thread-safe refresh: concurrent discussion starts on the same plugin share one HTTPS exchange.
- Exchange flow: `POST <token_url>` with `grant_type=client_credentials&client_id=…&client_secret=…&scope=…` (form-urlencoded). Parses `access_token` + `expires_in` from the JSON response. `refresh_at = now + expires_in - 30s` safety margin.
- Async resolver in `make_agent_stream` runs BEFORE `build_api_context_block`: for every plugin with `OAuth2ClientCredentials`, calls `resolve_token()` and injects the result into the plugin's env map under virtual keys `__access_token__` (success) or `__token_error__` (failure). The sync context builder reads those without knowing the auth flow.
- Error-transparency: on token-exchange failure the context block shows *"TOKEN UNAVAILABLE — <reason>"* so the agent stops rather than firing unauthenticated requests.
- On backend restart the cache is empty; one HTTPS round-trip per active OAuth2 plugin on first use, no user-visible impact.

**Plugins shipped**:
- `api-chartbeat` — `https://api.chartbeat.com`, `apikey` query param. 21 endpoints: Live (sync GETs) + Historical (async `submit` → `status` → `fetch`, with `X-CB-AK` header). `CHARTBEAT_HOST` is a `config_key`.
- `api-adobe-analytics` — `https://analytics.adobe.io/api/{ADOBE_COMPANY_ID}` (path interpolation). OAuth2 client-credentials against Adobe IMS `/ims/token/v3`. 7 endpoints: `POST /reports`, `POST /reports/realtime`, `GET /dimensions`, `GET /metrics`, `GET /segments`, `GET /calculatedmetrics`, `GET /users/me`. Required extra headers via `OAuth2ExtraHeader.value_template`: `x-api-key: {ADOBE_CLIENT_ID}` + `x-proxy-global-company-id: {ADOBE_COMPANY_ID}`. Config keys: `ADOBE_COMPANY_ID`, `ADOBE_ORG_ID`, `ADOBE_RSID` (non-secret).
- `api-google-search` — `https://www.googleapis.com/customsearch/v1`, `apikey=` query auth. One endpoint, rich param matrix (`q`, `num`, `start`, `dateRestrict`, `siteSearch`, `searchType`, `lr`, `gl`). `GOOGLE_SEARCH_CX` exposed as config_key — duplicate the plugin per Programmable Search Engine (site-scoped vs whole-web). 100 queries/day free; default_context documents quota + SEO use-cases (rank check, 7-day news, site search).

**Roadmap**:
- ~~New workflow step type `ApiCall`~~ — **shipped** in 0.5.1, extended to `quick_api_id` reference in 0.7+ (see "Modularité unitaire des workflows" above).
- Next OAuth2 plugins candidates: Google Analytics 4 Data API, Salesforce REST — same `OAuth2ClientCredentials` variant, different `token_url` + scopes + extra headers.

### Host MCP sync — bidirectional CLI integration (0.6.0 in development)

Kronn-managed MCPs are now also written into the user's local CLI config files (`~/.claude.json`, `~/.gemini/settings.json`, `~/.codex/config.toml`, `~/.copilot/mcp-config.json`) so that running `claude`, `gemini`, `codex`, or `copilot` **outside** Kronn surfaces the same MCPs. Three-phase architecture; both inbound (read-only audit) and outbound (write) flows.

**Phase 1 — Inbound discovery** (`backend/src/core/host_mcp_discovery.rs`): scans the 4 host config files and surfaces every MCP entry found, with ownership classification (`NotManaged` / `ManagedByMarker(config_id)` / `ManagedByHash(config_id)`). Read-only — never mutates disk. Endpoint `GET /api/mcps/host-discovery`. Frontend section in Settings → "MCPs externes détectés".

**Phase 2 — Adopt** (`POST /api/mcps/host-discovery/adopt`): converts a non-Kronn-managed entry into a Kronn `McpConfig`. Defaults to `host_sync = GlobalOnly` + `is_global = false` + `project_ids = []`. Source classified as `McpSource::HostImported` (separate from `Detected` which is project-`.mcp.json`-scoped). Idempotent (hash dedup). Source file is **never modified** during adopt — that's the user's existing config; Kronn just registers a parallel record.

**Phase 3 — Outbound sync** (4 functions in `backend/src/core/mcp_scanner.rs`):
| Function | Target | Routing |
|----------|--------|---------|
| `sync_codex_global_config` | `~/.codex/config.toml` `[mcp_servers.*]` | top-level only (Codex has no native per-project scope) |
| `sync_copilot_global_config` | `~/.copilot/mcp-config.json` | top-level only |
| `sync_gemini_global_config` | `~/.gemini/settings.json` `mcpServers` | top-level only — Gemini's `httpUrl` used for Streamable HTTP |
| `sync_claude_global_config` | `~/.claude.json` | **scope-aware**: `is_global=true` → top-level `mcpServers`; `is_global=false + project_ids` → `projects[<host-path>].mcpServers` for each project; `project_ids=[]` → top-level fallback |

All four filter `host_sync ∈ {GlobalOnly, MirrorAll}` (= "synced to host"), skip `ApiOnly` transport, and use the same defensive merge pattern: `_kronn` marker on each managed entry (`{managed: true, config_id: <uuid>}`), tree-wide cleanup of orphaned Kronn entries, atomic write (`tmp+rename`), `chmod 0600` on Unix, abort+`.kronn-backup` on parse failure (data preservation).

**Data model (`backend/src/models/mod.rs`)**:
- `HostSyncMode` enum: `None | GlobalOnly | MirrorAll` — column on `mcp_configs` (migration 036). `MirrorAll` is a legacy value the codebase still reads but never writes (collapsed to `GlobalOnly` by migration 038).
- `McpSource::HostImported` — new variant for entries adopted from host files (vs `Registry` / `Detected` / `Manual`).

**Migrations**:
- `036_mcp_host_sync.sql` — adds `host_sync TEXT NOT NULL DEFAULT 'None'` to `mcp_configs`.
- `037_mcp_host_sync_backfill.sql` — sets `host_sync = 'MirrorAll'` on every existing config so the upgrade preserves the pre-0.6.0 behavior (Codex/Copilot were already global-by-default).
- `038_mcp_host_sync_collapse.sql` — converts `MirrorAll` rows into the orthogonal pair `is_global = 1 + host_sync = 'GlobalOnly'`. The 3-mode UI was found to be confusing (override of project_ids), so the column is now binary in practice and `is_global` carries the "applied to all Kronn projects" semantics alone.

**UX (Plugins page is the canonical editor — see `feedback_mcp_single_edit.md` memory)**:
- `HostSyncChip` (`frontend/src/components/HostSyncChip.tsx`) — `🌐 CLI local` badge on every MCP card whose `host_sync !== 'None'`. At-a-glance scan of the fleet.
- Drawer `Scope` section: project toggles + `is_global` toggle (existing) + checkbox "Aussi disponible dans mes CLIs locaux" (new).
- `HostSyncPreview` (`frontend/src/components/HostSyncPreview.tsx`) — dynamic preview under the checkbox listing the exact destination per CLI based on current scope (e.g. `Claude Code → ~/.claude.json › projects[/home/me/Repos/APP_ANDROID]`, `Gemini → ~/.gemini/settings.json (top-level — scope projet non supporté)`). Renders the asymmetry (Claude scope-aware vs others top-level only) before the user clicks Save.
- Empty-state banner on Plugins page when no config has `host_sync !== 'None'` (one-click jump to first config). Coach mark "✨ Nouveau" on the checkbox section, one-shot localStorage flag.

**Host-sync resilience** (resolved 0.6.0 / 2026-05-07 — kept here as
historical context for code reviewers):
- Trait abstraction: `HostMcpSync` + `run_host_sync` driver.
  Adding a 5th CLI = 1 struct + 1 entry in the registry slice.
- Concurrent-write guard: `atomic_write_checked` snapshots mtime
  before read and aborts the rename if a third party (Claude Code,
  Gemini CLI, …) bumped it.
- Workflow-run gate: `db::workflows::has_running_run` short-circuits
  `sync_affected_projects` when an agent is mid-spawn.
- Backup rotation: `rotate_backup(path, 5)` keeps `.1` (newest) →
  `.5` (oldest) on each parse-fail; oldest dropped automatically.

### Document generation — Kronn Docs (0.5.1)

Agents produce 5 file formats (PDF / DOCX / XLSX / CSV / PPTX) without the user installing anything.

**Sidecar architecture** (`backend/sidecars/docs/`): Python FastAPI + uvicorn on a random loopback port, started by backend during boot. Deterministic startup via `KRONN_DOCS_READY <port>` stdout marker. Dependencies: WeasyPrint (PDF), python-docx + BeautifulSoup (DOCX, HTML→Word mapping), XlsxWriter (XLSX), stdlib `csv` (CSV), python-pptx (PPTX). Setup is opt-in via `make docs-setup` — missing venv degrades to a clear "Document sidecar unavailable" error instead of a hard failure.

**Rust proxy** (`backend/src/api/docs.rs` + `backend/src/core/docs_sidecar.rs`): 5 endpoints `POST /api/docs/{pdf,docx,xlsx,csv,pptx}` + `GET /api/docs/file/:discussion_id/:filename`. All five POST handlers funnel through a single `proxy_to_sidecar()` helper — adding a format = one arm. Output files land in `~/.kronn/generated/<discussion_id>/`. Filename sanitization (alphanumerics + `-_ ` only, UUID suffix, extension forced) + canonicalize check in `download_file` guard against path traversal.

**Agent contract** — skill `kronn-docs.md` ships two fence conventions:
- ```` ```kronn-doc-preview ```` — HTML body used for PDF + DOCX. Frontend renders a sandboxed iframe (`sandbox=""`) + two export buttons.
- ```` ```kronn-doc-data ```` — JSON payload `{format, ...}` for structured formats (XLSX / CSV / PPTX). No preview; compact card with summary (row count, sheet count, slide count) + single export button.

Auto-activation: the skill carries `auto_triggers.common/fr/en/es` regex buckets — "génère un rapport PDF", "create a presentation", "exporta hoja xlsx" etc. Matched skills auto-inject into the system prompt. Per-skill opt-out via Settings (`auto_triggers` table).

**Frontend** — `frontend/src/components/DocPreview.tsx` (HTML formats) + `DocDataExport.tsx` (structured). Both detected in `MarkdownContent` (`MessageBubble.tsx`) by the fence's `language-kronn-doc-*` class. Malformed JSON / unknown format discriminator falls back to a normal `<pre>` so a bad agent message can't blow up the chat.

---

## 10. RTK integration (0.5.1) — "Mode économique"

RTK (Rust Token Killer, `github.com/rtk-docs/rtk`) is a Rust shell-output compressor that intercepts commands like `git`, `cargo`, `ls`, test runners and rewrites their output before it reaches the LLM. Measured ~89% compression on the author's local fleet. Kronn ships a first-class integration covering detection, activation, and savings readout.

**Scope — what Kronn does NOT do**: we do not wrap or intercept agent shell calls ourselves. The agent CLI (Claude Code, Codex, Gemini CLI) is the process that executes Bash tool calls, and it runs its *own* `Command::new("bash")`. RTK installs per-agent hooks that those CLIs invoke; Kronn detects + activates + observes, never proxies. This is the correct separation of concerns: RTK owns the format of each hook, Kronn owns the UX.

### What's wired

- **`backend/src/core/rtk_detect.rs`** — read-only scan of the host.
  - `rtk_binary_available()` → `which rtk`
  - `rtk_hook_configured_for(agent_type)` → scans the per-agent config file:
    - Claude Code → `~/.claude/settings.json`
    - Codex → `~/.codex/AGENTS.md` (NOT `config.toml` — RTK injects into the AGENTS.md preamble)
    - Gemini CLI → shell rc (bash/zsh/fish/profile) — `gemini_shell_rc_mentions_rtk()`
    - Kiro, Copilot CLI, Vibe, Ollama → `None` (not in RTK's supported list, or no shell to hook)
  - HOME resolution uses `$HOME` (NOT `KRONN_HOST_HOME`). In Docker `/home/kronn/.claude` etc. are bind-mounted rw from the host, so reading through the container HOME lands on the real host file. Overriding with `KRONN_HOST_HOME` (the host path, e.g. `/home/priol`) points at a path that doesn't exist inside the container and silently returns false everywhere.

- **`backend/src/models/mod.rs`** — `AgentDetection` gets `rtk_available: bool` and `rtk_hook_configured: bool` alongside `runtime_available`. Both have `#[serde(default)]` so older configs deserialize cleanly.

- **`backend/src/api/rtk.rs`** — two endpoints:
  - `POST /api/rtk/activate` — body `{ agents: AgentType[] }`. Filters to RTK-supported agents (Claude Code / Codex / Gemini CLI), spawns one `rtk init` per agent with the right flag matrix:
    - Claude Code: `rtk init -g --auto-patch --hook-only`
    - Codex: `rtk init -g --codex --auto-patch` (no `--hook-only` — incompatible with `--codex`, same for `--gemini`)
    - Gemini: `rtk init -g --gemini --auto-patch`
    `--auto-patch` is mandatory for non-interactive. Returns `{ success, stdout, stderr, per_agent: RtkAgentActivation[] }` with stdout/stderr concatenated by agent for toast display.
  - `GET /api/rtk/savings` — reads `rtk gain --all --format json`, navigates to `summary.{total_saved, avg_savings_pct, total_commands}` (RTK 0.37 shape, validated by a test). Returns `{ available, total_tokens_saved, ratio_percent, sample_count }`. `available: false` when anything fails so the UI hides cleanly.

- **Pre-flight** — `POST /api/rtk/activate` calls `std::fs::create_dir_all($HOME/.config/rtk)` before spawning, because in Docker the chain `~/.config/rtk/` may not exist and RTK errors out with "Permission denied" when mkdir crosses a uid boundary. The Dockerfile also pre-creates `/home/kronn/.config` and `chown`s it to the app user.

- **`backend/Dockerfile`** — RTK 0.37.1 pinned, curl-installed, `dpkg --print-architecture` pattern shared with `glab`, `bun`, `uv`. arm64 target (`aarch64-unknown-linux-musl`) already wired even though compose publishes x86_64 today.

- **`docker-compose.yml`** — bind mounts `~/.config/rtk` and `~/.local/share/rtk` rw. Without these the `rtk gain` call inside the container reads an empty SQLite while the user's real stats live on the host.

- **Frontend** (`frontend/src/components/settings/CompressionSection.tsx`) — single card at the top of AgentsSection. States by `configured/applicable` count (0/N amber, partial neutral, N/N green). Savings counter shows only when RTK reports `available: true` with `total > 0`. Details expand renders 3 stat cards. Install modal (when `!rtk_available`) shows the curl command + GitHub link for the user to pass to their tech colleague. Attribution always visible: "Propulsé par RTK (open source)".

- **Per-agent badge** (`frontend/src/components/settings/AgentsSection.tsx`) — rendered inline next to each agent version. 3 states for RTK-applicable agents, italic "Non pris en charge par RTK" for the rest.

- **Sobriety tooltip** — (?) button next to the "Mode économique" title reveals a paragraph: *"L'usage le plus sobre reste de ne pas utiliser d'IA. Si vous en utilisez, RTK compresse..."*. Acknowledges the eco-mode wording could oversell.

### Bug history worth remembering

Documented so future iterations don't rediscover them:

1. `rtk init -g` alone → only wires Claude Code. Need per-agent flags (`--codex`, `--gemini`).
2. No `--auto-patch` → prompt waits forever, exits 0, nothing happened ("RTK activated" lying toast).
3. `HOME=$KRONN_HOST_HOME` override → tries `mkdir /home/priol/.claude` inside the container, "failed to create directory". Leave HOME alone.
4. `--hook-only` with `--codex`/`--gemini` → "cannot be combined". The hook IS the flow for non-default agents.
5. Codex detection path `.codex/config.toml` → wrong, RTK writes to `.codex/AGENTS.md`.
6. `rtk gain` parser looking at top-level keys → wrong, RTK 0.37 nests under `summary.*`.
7. Docker container's `rtk` reads `/home/kronn/.config/rtk/` → without bind mount, disjoint from host SQLite, savings counter always zero.

Regression tests in `backend/src/core/rtk_detect.rs::tests` and `backend/src/api/rtk.rs::tests` (incl. embedded real-payload JSON). Don't remove them without a very good reason.

### Out of scope (intentionally)

- **RTK timeseries / sparkline** — `daily[]`/`weekly[]`/`monthly[]` arrays are available in `rtk gain` JSON but the UI only surfaces `summary.*`. Reason: RTK is 0.x (breaking changes unannounced), and the counter covers 80% of the perceived value. Revisit when RTK hits 1.0.
- **Scoping RTK savings by discussion / agent** — RTK's SQLite is global, Kronn doesn't instrument per-message. "Compression par discussion Kronn" requires a `ContextCompressor` trait that intercepts at the MCP output / workflow-step output level (the "désagentification" vision). See `docs/decisions.md` for the longer note.

---

## 11. Multi-agent configuration

Redirectors to this file: `CLAUDE.md`, `GEMINI.md`, `AGENTS.md`, `.kiro/steering/instructions.md`, `.vibe/instructions.md`, `.cursorrules`, `.cursor/rules/repo-instructions.mdc`, `.github/copilot-instructions.md`, `.windsurfrules`, `.clinerules`.

**Maintenance rule**: all content lives in `docs/`. Redirectors contain a summary of critical rules + pointer to `docs/AGENTS.md` as source of truth.

---

## 12. Last updated

Project documentation last reviewed: **2026-05-03** (v0.6.0 final — **Modularité unitaire des workflows** ajoutée à la 0.6.0 : 3 nouvelles briques pour factoriser : `quick_api_id` étendu à `StepType::ApiCall` single (était batch-only initialement), nouveau `quick_prompt_id` sur `StepType::Agent` (helper `quick_prompt_hydrate.rs`, charge `prompt_template`/`tier`/`skill_ids` du QP au run-time, per-field override), nouveau `StepType::JsonData` (zéro token / zéro réseau, payload JSON littéral validé ≤ 1 MiB, valide pour alimenter un Batch sur liste figée ou comme fixture de dev), nouveau préset `daily-host-audit` (JsonData → BatchQuickPrompt → Notify). Refactor : helper partagé `quick_api_hydrate.rs` factorisé depuis `batch_apicall_step.rs`. Pas de migration SQL (steps en JSON, `serde(default)` sur les nouveaux fields). Tests : 1369 backend / 934 frontend. v0.6.0 also includes — bidirectional host MCP sync, **Workflow Engine 0.7.x features**: `WorkflowGuards` with `RunStatus::StoppedByGuard` (migration 039), `StepOutputFormat::TypedSchema` JSON-schema-validated step outputs, `Workflow.artifacts` + `---ARTIFACT:name---` envelope persisted to workspace (migration 040), `StepType::Gate` human-in-the-loop with `RunStatus::WaitingApproval` + `POST /api/workflows/.../decide` + optional `gate_notify_url` webhook, `StepType::Exec` direct binary execution gated by `Workflow.exec_allowlist` (no `sh -c`, argv literal, migration 043), `ConditionAction::Goto { max_iterations }` loops + `WorkflowRun.state` durable scratchpad with `---STATE:k=v---` agent emit + `{{iter.<step>}}` / `{{state.<key>}}` template vars (migration 042), `Workflow.on_failure` rollback-only-on-Failed with `{{failed_step.*}}` templates (migration 041), self-contained per-item Export/Import for Workflows + Quick Prompts with bundled QP references and per-user-field stripping. Wizard adds 3 non-trivial presets (`v07-presets.ts`: AUTO_DEV / PR_GATE / DEPLOY_ROLLBACK), STATE pedagogy chips on Agent steps when `Goto` detected, RunDetail "À VALIDER" badge + "en pause depuis Xh" counter on `WaitingApproval` runs, rollback wizard accepts Notify+Agent+ApiCall (rejects Gate). Tests: 1490 backend + 908 frontend. **Previous v0.6.0 highlights** (kept for context): bidirectional host MCP sync (Phase 1 inbound discovery + Phase 2 adopt + Phase 3 outbound write to `~/.claude.json` & friends), scope-aware Claude routing, `_kronn` marker + tree-wide orphan cleanup, `HostSyncMode` model + migrations 036/037/038, `McpSource::HostImported`, `HostSyncChip` + `HostSyncPreview`, single-source-of-edit pattern (Plugins drawer canonical). v0.5.1 baseline kept for context: Kronn Docs sidecar, RTK integration, `StepType::ApiCall`, ActiveRunsPopover + inline Stop, secret themes.
