@loicngr/kobo 1.11.45 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/AGENTS.md +17 -2
  2. package/CHANGELOG.md +12 -0
  3. package/CODE_OF_CONDUCT.md +16 -0
  4. package/CONFIGURATION.md +1968 -0
  5. package/CONTRIBUTING.md +83 -0
  6. package/DESIGN.md +296 -0
  7. package/README.md +35 -7
  8. package/SECURITY.md +54 -0
  9. package/THIRD_PARTY_NOTICES.md +20 -0
  10. package/dist/server/db/migrations.js +31 -0
  11. package/dist/server/db/schema.js +4 -0
  12. package/dist/server/index.js +6 -0
  13. package/dist/server/routes/environment.js +18 -0
  14. package/dist/server/routes/health.js +17 -2
  15. package/dist/server/routes/integrations.js +29 -0
  16. package/dist/server/routes/settings.js +5 -0
  17. package/dist/server/routes/sounds.js +60 -0
  18. package/dist/server/routes/usage.js +28 -2
  19. package/dist/server/routes/workspaces.js +46 -12
  20. package/dist/server/services/agent/engines/claude-code/engine.js +37 -4
  21. package/dist/server/services/agent/engines/codex/options-builder.js +1 -0
  22. package/dist/server/services/agent/engines/codex/spawn.js +2 -2
  23. package/dist/server/services/agent/integration-mcp.js +49 -0
  24. package/dist/server/services/agent/orchestrator.js +6 -2
  25. package/dist/server/services/auto-loop-service.js +45 -25
  26. package/dist/server/services/custom-sound-service.js +0 -0
  27. package/dist/server/services/environment-check-service.js +125 -0
  28. package/dist/server/services/initial-prompt-template-service.js +8 -21
  29. package/dist/server/services/integration-config-service.js +65 -0
  30. package/dist/server/services/legacy-public-prompts.js +112 -0
  31. package/dist/server/services/notion-service.js +25 -61
  32. package/dist/server/services/sentry-service.js +17 -12
  33. package/dist/server/services/settings-defaults.js +5 -0
  34. package/dist/server/services/settings-service.js +112 -23
  35. package/dist/server/services/skill-suite-prompts.js +29 -22
  36. package/dist/server/services/templates-service.js +31 -8
  37. package/dist/server/services/terminal-service.js +16 -0
  38. package/dist/server/services/usage/reliability.js +24 -0
  39. package/dist/server/services/wakeup-service.js +7 -5
  40. package/dist/server/services/workspace-service.js +25 -4
  41. package/dist/server/services/worktree-service.js +3 -1
  42. package/dist/server/utils/git-repo-lock.js +7 -3
  43. package/dist/server/utils/mcp-client.js +26 -12
  44. package/dist/server/utils/mcp-process.js +25 -0
  45. package/dist/server/utils/node-pty-spawn-helper.js +31 -0
  46. package/dist/server/utils/paths.js +7 -0
  47. package/dist/shared/auto-loop-prompts.js +6 -2
  48. package/dist/shared/automatic-admission.js +1 -0
  49. package/dist/shared/environment.js +1 -0
  50. package/dist/shared/notification-assets.js +73 -0
  51. package/dist/shared/skill-suite-prompts.js +6 -3
  52. package/dist/shared/workflow-policy.js +25 -0
  53. package/docs/getting-started.md +47 -0
  54. package/docs/troubleshooting.md +49 -0
  55. package/package.json +19 -7
  56. package/src/client/dist/pwa/assets/ActivityFeed-DeJ0V6CW.js +8 -0
  57. package/src/client/dist/pwa/assets/{ChangelogPage-CnXTnFhe.js → ChangelogPage-OoOnn4q9.js} +1 -1
  58. package/src/client/dist/pwa/assets/{ComparisonPanel-CKylN45q.js → ComparisonPanel-BYpe6Nch.js} +2 -2
  59. package/src/client/dist/pwa/assets/CreatePage-B9C9J9Pw.css +1 -0
  60. package/src/client/dist/pwa/assets/CreatePage-COQwHyqL.js +2 -0
  61. package/src/client/dist/pwa/assets/DashboardPage-CZ2036MC.js +1 -0
  62. package/src/client/dist/pwa/assets/DashboardPage-XiCj24i2.css +1 -0
  63. package/src/client/dist/pwa/assets/DiffViewer-CXBmMV9R.js +8 -0
  64. package/src/client/dist/pwa/assets/{DocumentsPanel-BeCttB-x.js → DocumentsPanel-mLGU5aTb.js} +1 -1
  65. package/src/client/dist/pwa/assets/{DrawerToggleButton-BJBBYeOc.js → DrawerToggleButton-C39pfWBD.js} +1 -1
  66. package/src/client/dist/pwa/assets/GitPanel-BQk7kJO-.js +3 -0
  67. package/src/client/dist/pwa/assets/{GitPanel-BFCnyhE2.css → GitPanel-C4wOcZV2.css} +1 -1
  68. package/src/client/dist/pwa/assets/{HealthPage-l88TEk-k.js → HealthPage-CnJWXfa8.js} +1 -1
  69. package/src/client/dist/pwa/assets/MainLayout-Cc4XzCAy.css +1 -0
  70. package/src/client/dist/pwa/assets/MainLayout-D3zfT61Y.js +2 -0
  71. package/src/client/dist/pwa/assets/{QBtnDropdown-44Mj-y_1.js → QBtnDropdown-DGLLlBf3.js} +1 -1
  72. package/src/client/dist/pwa/assets/{QExpansionItem-Bok1m8Wi.js → QExpansionItem-CQ2LzM8P.js} +1 -1
  73. package/src/client/dist/pwa/assets/QSelect-DoTWgQAO.js +1 -0
  74. package/src/client/dist/pwa/assets/{QTooltip-BDihn2aY.js → QTooltip-DKvUeiR_.js} +1 -1
  75. package/src/client/dist/pwa/assets/SchedulePanel-BYKSFoFj.css +1 -0
  76. package/src/client/dist/pwa/assets/SchedulePanel-DIMzNIO3.js +1 -0
  77. package/src/client/dist/pwa/assets/{SearchPage-BwZ2TX1G.js → SearchPage-COWnMxRZ.js} +1 -1
  78. package/src/client/dist/pwa/assets/SettingsPage-B9cLhFnO.js +9 -0
  79. package/src/client/dist/pwa/assets/{SettingsPage-W6N01tGz.css → SettingsPage-BYdLXJEJ.css} +1 -1
  80. package/src/client/dist/pwa/assets/SplitWorkspacePage-BJKssJyI.js +1 -0
  81. package/src/client/dist/pwa/assets/{TerminalPanel-Cq-FR7A7.js → TerminalPanel-C_HNpN1X.js} +4 -4
  82. package/src/client/dist/pwa/assets/{TouchPan-BQ1cvzRC.js → TouchPan-CCllyaAF.js} +1 -1
  83. package/src/client/dist/pwa/assets/{TourReplayButton-Dz1yEz_z.js → TourReplayButton-Cp2UnxWa.js} +1 -1
  84. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-B-SuQuS_.js +1 -0
  85. package/src/client/dist/pwa/assets/{WorkspacePage-BbUvR1Vf.css → WorkspacePage--5HsnMQ1.css} +1 -1
  86. package/src/client/dist/pwa/assets/WorkspacePage-6lN4iF5W.js +4 -0
  87. package/src/client/dist/pwa/assets/{browser-BmWgBx9M.js → browser-uy3hmwKo.js} +1 -1
  88. package/src/client/dist/pwa/assets/{build-path-tree-6EjBmYYY.js → build-path-tree-DZrp6Aji.js} +1 -1
  89. package/src/client/dist/pwa/assets/custom-sounds-Du8ReMlU.js +1 -0
  90. package/src/client/dist/pwa/assets/de-CTE3Xb7Z.js +17 -0
  91. package/src/client/dist/pwa/assets/{documents-BZ8bwcjr.js → documents-BtNrB6d0.js} +1 -1
  92. package/src/client/dist/pwa/assets/{editor.api-CK7--ckY.js → editor.api-P-eJRcVK.js} +1 -1
  93. package/src/client/dist/pwa/assets/efforts-ChUC4xtk.js +9 -0
  94. package/src/client/dist/pwa/assets/engineFeatures-D-0NPZBE.js +1 -0
  95. package/src/client/dist/pwa/assets/es-CZxg1xjh.js +17 -0
  96. package/src/client/dist/pwa/assets/{expand-template-C8VyjASo.js → expand-template-ByoC_kxX.js} +1 -1
  97. package/src/client/dist/pwa/assets/{format-LtTLLitJ.js → format-CeidAPFn.js} +7 -7
  98. package/src/client/dist/pwa/assets/{formatters-Bl9zA7Ai.js → formatters-BtG6lb4d.js} +1 -1
  99. package/src/client/dist/pwa/assets/fr-C7HCclxj.js +17 -0
  100. package/src/client/dist/pwa/assets/{freemarker2-DxezMaje.js → freemarker2-CDYXi2tK.js} +1 -1
  101. package/src/client/dist/pwa/assets/{handlebars-DK_Bj6Rx.js → handlebars-AVkJuN-_.js} +1 -1
  102. package/src/client/dist/pwa/assets/{html-DFfsfStD.js → html-CpvEZlmO.js} +1 -1
  103. package/src/client/dist/pwa/assets/i18n-KVkTJG-4.js +1 -0
  104. package/src/client/dist/pwa/assets/index-DsaWUT_Z.js +20 -0
  105. package/src/client/dist/pwa/assets/it-DoOWwqZz.js +17 -0
  106. package/src/client/dist/pwa/assets/{javascript-DuNbcaL7.js → javascript-BYnnBLH9.js} +1 -1
  107. package/src/client/dist/pwa/assets/{liquid-CM3JKyhv.js → liquid-CzqlB3Um.js} +1 -1
  108. package/src/client/dist/pwa/assets/{mdx-BG3zYwNF.js → mdx-hrYFnJVZ.js} +1 -1
  109. package/src/client/dist/pwa/assets/{monaco-contributions-DLscZGH-.js → monaco-contributions-D2YXqNaH.js} +1 -1
  110. package/src/client/dist/pwa/assets/{monaco.contribution-DnhZHwXT.js → monaco.contribution-Dl22KqTL.js} +2 -2
  111. package/src/client/dist/pwa/assets/network-auth-Dq0I5kjv.js +1 -0
  112. package/src/client/dist/pwa/assets/notify-theme-vV3Rm4_v.js +1 -0
  113. package/src/client/dist/pwa/assets/{pending-diff-open-dYIFdS4r.js → pending-diff-open-BJYqBOUG.js} +1 -1
  114. package/src/client/dist/pwa/assets/permissionModes-B-ciuUbz.js +36 -0
  115. package/src/client/dist/pwa/assets/{project-color-R_1je4vW.js → project-color-W5mMgIKK.js} +1 -1
  116. package/src/client/dist/pwa/assets/{python-COs6pHk2.js → python-DafgCozG.js} +1 -1
  117. package/src/client/dist/pwa/assets/{razor-Jl1VqMsk.js → razor-CWYrTLCm.js} +1 -1
  118. package/src/client/dist/pwa/assets/render-chat-markdown-CE5RIL0s.js +69 -0
  119. package/src/client/dist/pwa/assets/{typescript-BU_TUuAM.js → typescript-DLSIuW9u.js} +1 -1
  120. package/src/client/dist/pwa/assets/use-tours-CDdnvIaG.js +2 -0
  121. package/src/client/dist/pwa/assets/workspace-templates-C88VmFdQ.js +1 -0
  122. package/src/client/dist/pwa/assets/{xml-gFFsobxE.js → xml-BAmdYarn.js} +1 -1
  123. package/src/client/dist/pwa/assets/{yaml-DaXaqaGD.js → yaml-RQqNMXWh.js} +1 -1
  124. package/src/client/dist/pwa/index.html +1 -1
  125. package/src/client/dist/pwa/sounds/neutral.wav +0 -0
  126. package/src/client/dist/pwa/sounds/ready.wav +0 -0
  127. package/src/client/dist/pwa/sw.js +1 -1
  128. package/src/client/dist/pwa/assets/ActivityFeed-CLXjdqS8.js +0 -8
  129. package/src/client/dist/pwa/assets/CreatePage-SoCcvxuD.js +0 -2
  130. package/src/client/dist/pwa/assets/CreatePage-TY9g3hZb.css +0 -1
  131. package/src/client/dist/pwa/assets/DashboardPage-CZQLfoV1.js +0 -1
  132. package/src/client/dist/pwa/assets/DashboardPage-D8g7_r4S.css +0 -1
  133. package/src/client/dist/pwa/assets/DiffViewer-Csf3WmLc.js +0 -8
  134. package/src/client/dist/pwa/assets/GitPanel-B-ZHgghQ.js +0 -3
  135. package/src/client/dist/pwa/assets/MainLayout-BAAQLq95.css +0 -1
  136. package/src/client/dist/pwa/assets/MainLayout-Cf4Q9x1u.js +0 -2
  137. package/src/client/dist/pwa/assets/QDrawer-B4qHw8wt.js +0 -1
  138. package/src/client/dist/pwa/assets/QSelect-BbtD5Iks.js +0 -1
  139. package/src/client/dist/pwa/assets/SchedulePanel-BAA4Aajn.css +0 -1
  140. package/src/client/dist/pwa/assets/SchedulePanel-DiWCDNZ8.js +0 -1
  141. package/src/client/dist/pwa/assets/SettingsPage-BUh0WfNf.js +0 -9
  142. package/src/client/dist/pwa/assets/SplitWorkspacePage-Bg6nKBcA.js +0 -1
  143. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BCVOP8vp.js +0 -1
  144. package/src/client/dist/pwa/assets/WorkspacePage-Cl8gap4b.js +0 -4
  145. package/src/client/dist/pwa/assets/de-COObokpu.js +0 -17
  146. package/src/client/dist/pwa/assets/efforts-Bw0o2Bud.js +0 -9
  147. package/src/client/dist/pwa/assets/engineFeatures-DQO9HHXI.js +0 -1
  148. package/src/client/dist/pwa/assets/es-By2Q9Tca.js +0 -17
  149. package/src/client/dist/pwa/assets/fouet-ahh-DdxrUXRj.mp3 +0 -0
  150. package/src/client/dist/pwa/assets/fr-D-sd0ddn.js +0 -17
  151. package/src/client/dist/pwa/assets/i18n-aDWy4KHt.js +0 -1
  152. package/src/client/dist/pwa/assets/index-BLd8DAzg.js +0 -20
  153. package/src/client/dist/pwa/assets/it-CeK5lyIA.js +0 -17
  154. package/src/client/dist/pwa/assets/network-auth-BJiPbO5l.js +0 -1
  155. package/src/client/dist/pwa/assets/notify-theme-D4p3qPkp.js +0 -1
  156. package/src/client/dist/pwa/assets/permissionModes-B7fuUegU.js +0 -36
  157. package/src/client/dist/pwa/assets/render-chat-markdown-AaOo0Tgq.js +0 -67
  158. package/src/client/dist/pwa/assets/use-tours-CVn874kD.js +0 -2
  159. package/src/client/dist/pwa/assets/workspace-templates-BCKR8TXr.js +0 -1
  160. package/src/client/dist/pwa/sounds/7eme-compagnie-03.mp3 +0 -0
  161. package/src/client/dist/pwa/sounds/aller-ftg.mp3 +0 -0
  162. package/src/client/dist/pwa/sounds/arrete-de-mentir.mp3 +0 -0
  163. package/src/client/dist/pwa/sounds/arretez-les-messages.mp3 +0 -0
  164. package/src/client/dist/pwa/sounds/bah-alors-on-est-nul.mp3 +0 -0
  165. package/src/client/dist/pwa/sounds/basic-notification.mp3 +0 -0
  166. package/src/client/dist/pwa/sounds/ca_va_peter.mp3 +0 -0
  167. package/src/client/dist/pwa/sounds/dry-fart.mp3 +0 -0
  168. package/src/client/dist/pwa/sounds/faaah.mp3 +0 -0
  169. package/src/client/dist/pwa/sounds/for-shure.mp3 +0 -0
  170. package/src/client/dist/pwa/sounds/gta-v-death.mp3 +0 -0
  171. package/src/client/dist/pwa/sounds/hey.mp3 +0 -0
  172. package/src/client/dist/pwa/sounds/mais-laisse-moi-dormir-zebi.mp3 +0 -0
  173. package/src/client/dist/pwa/sounds/nan-tu-degages.mp3 +0 -0
  174. package/src/client/dist/pwa/sounds/nan-wallah-pardon.mp3 +0 -0
  175. package/src/client/dist/pwa/sounds/ouais-cest-greg.mp3 +0 -0
  176. package/src/client/dist/pwa/sounds/pas-ca-zinedine.mp3 +0 -0
  177. package/src/client/dist/pwa/sounds/ta-gueule.mp3 +0 -0
  178. package/src/client/dist/pwa/sounds/travail_termine.mp3 +0 -0
  179. package/src/client/dist/pwa/sounds/tu-vas-la-fermer.mp3 +0 -0
  180. package/src/client/dist/pwa/sounds/warcraft-3-humain-travail.mp3 +0 -0
  181. package/src/client/dist/pwa/sounds/yaaa.mp3 +0 -0
@@ -0,0 +1,1968 @@
1
+ # Configuration
2
+
3
+ Reference for Kōbō configuration and external integrations. The tables cover the main settings; feature sections document additional keys and behavior. For a quick start see the [README](./README.md); for architecture, conventions, and contribution rules see [`AGENTS.md`](./AGENTS.md).
4
+
5
+ ## Contents
6
+
7
+ - [Storage layout](#storage-layout)
8
+ - [Environment variables](#environment-variables)
9
+ - [Settings UI](#settings-ui)
10
+ - [Settings navigation](#settings-navigation)
11
+ - [Fresh defaults and Git preferences](#first-run-defaults-and-git-workflow-preferences)
12
+ - [Managed MCPs in agent sessions](#managed-integration-mcps-in-agent-sessions)
13
+ - [Progressive Web App](#progressive-web-app)
14
+ - [Custom change-source-branch script](#custom-change-source-branch-script)
15
+ - [Lifecycle hooks](#lifecycle-hooks)
16
+ - [Unanswered question reminder](#unanswered-question-reminder)
17
+ - [Imported notification sounds](#imported-notification-sounds)
18
+ - [Comparing two engines on one task](#comparing-two-engines-on-one-task)
19
+ - [Workspace templates and duplication](#workspace-templates-and-duplication)
20
+ - [Auto-purge worktree on PR merged](#auto-purge-worktree-on-pr-merged)
21
+ - [Conversation history retention](#conversation-history-retention-opt-in)
22
+ - [Concurrent agents](#concurrent-agents)
23
+ - [Network access](#network-access)
24
+ - [Reachable addresses](#reachable-addresses)
25
+ - [External LLM access through MCP](#external-llm-access-through-mcp)
26
+ - [Kōbō update checks](#kōbō-update-checks)
27
+ - [Docker deployment](#docker-deployment)
28
+ - [Choosing a compose file](#choosing-a-compose-file)
29
+ - [The image](#the-image)
30
+ - [Environment variables (Docker)](#environment-variables-docker)
31
+ - [Volumes](#volumes)
32
+ - [Ports](#ports)
33
+ - [Token / login flow](#token--login-flow)
34
+ - [Quick local test — docker-compose.local.yml](#quick-local-test--docker-composelocalyml)
35
+ - [Rehearsing the reverse-proxy stack — docker-compose.local-traefikyml](#rehearsing-the-reverse-proxy-stack--docker-composelocal-traefikyml)
36
+ - [Real deployment — docker-compose.exampleyml](#real-deployment--docker-composeexampleyml)
37
+ - [Troubleshooting](#troubleshooting)
38
+ - [Agent runtimes](#agent-runtimes)
39
+ - [Claude Code](#claude-code)
40
+ - [OpenAI Codex](#openai-codex)
41
+ - [Permission modes](#permission-modes)
42
+ - [Dev server](#dev-server)
43
+ - [Forge integration](#forge-integration)
44
+ - [Notion integration](#notion-integration)
45
+ - [Sentry integration](#sentry-integration)
46
+ - [Voice transcription](#voice-transcription)
47
+ - [Skill suites](#skill-suites)
48
+ - [superpowers](#superpowers)
49
+ - [gstack](#gstack)
50
+ - [superpowers + gstack](#superpowers--gstack)
51
+ - [custom](#custom)
52
+ - [gbrain (companion MCP)](#gbrain-companion-mcp)
53
+ - [Workspace sorting, split view and absence digest](#workspace-sorting-split-view-and-absence-digest)
54
+ - [Conversation search index](#conversation-search-index)
55
+ - [Audit compatibility notes](#audit-compatibility-notes)
56
+
57
+ ## Storage layout
58
+
59
+ Kōbō persists all state under a single home directory, resolved in this order:
60
+
61
+ 1. `KOBO_HOME` env var (relative paths resolve against the working directory)
62
+ 2. `$XDG_CONFIG_HOME/kobo/`
63
+ 3. `~/.config/kobo/`
64
+
65
+ Contents:
66
+
67
+ ```
68
+ $KOBO_HOME/
69
+ ├── kobo.db # SQLite (WAL mode). Forward-only migrations.
70
+ ├── kobo.db.backup-<ISO>-<seq> # Daily WAL-safe backup (seven most recent kept).
71
+ ├── kobo.db.premigration-* # Separate snapshot before schema changes (five kept).
72
+ ├── settings.json # Global and per-project settings.
73
+ ├── integrations.json # Private MCP configuration (0600); excluded from settings exports.
74
+ ├── sounds/ # Imported notification sounds plus their sounds.json manifest.
75
+ ├── templates.json # Prompt templates.
76
+ ├── workspace-templates.json # Saved presets for the create form.
77
+ ├── skills.json # Cached Claude slash-command catalogue; suite settings live in settings.json.
78
+ └── voice/
79
+ └── models/whisper/ # Downloaded ggml-*.bin models.
80
+ ```
81
+
82
+ A production-installed Kōbō (`npx @loicngr/kobo`) and a dev server can run side by side: `npm run dev` forces `KOBO_HOME=./data` so dev never touches your real config.
83
+
84
+ ## Environment variables
85
+
86
+ | Variable | Default | Purpose |
87
+ |---|---|---|
88
+ | `PORT` | `3000` | HTTP / WebSocket server port. Overridden by `SERVER_PORT` when both are set. |
89
+ | `SERVER_PORT` | — | Takes precedence over `PORT`. Useful for stacking Kōbō next to other tools that also honour `PORT`. |
90
+ | `KOBO_VERBOSE` | — | Set to `1` to show the data directory and successful database backup/rotation details in the terminal. Errors always remain visible. |
91
+ | `NO_COLOR` | — | When present, disables startup banner colors. Redirected output is always plain text. |
92
+ | `KOBO_HOME` | `~/.config/kobo` | Override the storage directory. |
93
+ | `KOBO_ENFORCE_LOCAL_HOME` | — | When set, refuses any `KOBO_HOME` that resolves outside the current directory. Used by `npm run dev` to guarantee dev data lives in `./data`. |
94
+ | `KOBO_MCP_INIT_TIMEOUT_MS` | `30000` | Handshake timeout for the Notion and Sentry MCP servers. Bump it if cold `npx` fetches are slow on your network. |
95
+ | `XDG_CONFIG_HOME` | — | Standard XDG override consulted before `~/.config`. |
96
+ | `NOTION_API_TOKEN` | — | Notion integration token (preferred over `NOTION_TOKEN`). |
97
+ | `NOTION_TOKEN` | — | Fallback Notion token. |
98
+ | `NOTION_MCP_COMMAND` | `npx` | Binary used to launch the Notion MCP server. |
99
+ | `NOTION_MCP_ARGS` | `-y @notionhq/notion-mcp-server` | Space-separated arguments passed to `NOTION_MCP_COMMAND`. |
100
+ | `OPENAI_API_KEY` | — | Codex engine credential. Alternative to `codex login`. |
101
+ | `ANTHROPIC_API_KEY` | — | Claude credential. Alternative to `claude /login`. |
102
+ | `CLAUDE_CONFIG_DIR` | `~/.claude` | Where the Claude SDK reads its auth and MCP config. |
103
+ | `WHISPER_CPP_COMMAND` | `whisper-cli` | Override the whisper.cpp binary if it's not in `PATH` and you don't want to set it via Settings. |
104
+ | `DEBUG_MCP_STDERR` | — | When set, pipe spawned MCP servers' stderr to the Kōbō log. Useful for debugging Notion/Sentry MCP issues. |
105
+ | `KOBO_NETWORK_ACCESS_ENABLED` | — | Set `true` to enable [network access](#network-access) at boot, without going through Settings. Mainly useful for headless/Docker deployments. |
106
+ | `KOBO_NETWORK_ACCESS_BEHIND_PROXY` | — | Set `true` to enable [behind a reverse proxy](#behind-a-reverse-proxy) mode at boot (disables the loopback bypass entirely). Requires `KOBO_NETWORK_ACCESS_ENABLED=true`. |
107
+ | `KOBO_NETWORK_ACCESS_PROXY_HOST` | — | Domain your proxy serves Kōbō under (`kobo.example.com`). Makes the [host check](#reachable-addresses) enforce that name in behind-a-reverse-proxy mode instead of accepting any. A scheme and port are ignored. |
108
+ | `KOBO_DEV_CLIENT_ORIGIN` | — | Origin the Quasar dev server is served from (`http://localhost:8080`), so its page counts as Kōbō's own UI despite the proxy rewriting the `Host`. Set by the `dev` npm script; never set in a published build. |
109
+ | `GH_TOKEN` | — | Non-interactive auth for the `gh` CLI (used by the [forge integration](#forge-integration)). Needed in headless containers where `gh auth login`'s browser flow isn't practical. |
110
+ | `GITLAB_TOKEN` | — | Non-interactive auth for the `glab` CLI, same rationale as `GH_TOKEN`. |
111
+ | `KOBO_WORKTREE_CLEANUP_IMAGE` | `alpine` | Docker image used to reclaim ownership of root-owned files when removing a worktree fails on a permission error. Override with a locally cached image to avoid a pull. |
112
+
113
+ ## Settings UI
114
+
115
+ Settings are managed live from the **Settings** page in the UI and persisted to `$KOBO_HOME/settings.json`. The file is structured as:
116
+
117
+ ```jsonc
118
+ {
119
+ "global": { /* defaults applied to every project */ },
120
+ "projects": [
121
+ { "path": "/abs/path/to/repo", /* per-project overrides */ }
122
+ ]
123
+ }
124
+ ```
125
+
126
+ The current [navigation map](#settings-navigation) explains where each group lives.
127
+ Workflow and integration drafts survive tab changes; leaving with unsaved changes
128
+ asks for confirmation. Ordinary settings use the main Save action. Direct connection
129
+ credentials use their separate **Replace connection** action and are excluded from
130
+ settings exports.
131
+
132
+ ### Global settings
133
+
134
+ | Key | Type | Purpose |
135
+ |---|---|---|
136
+ | `defaultModel` | `string` | Fallback model when no engine-specific default is set. |
137
+ | `defaultModelByEngine` | `Record<engine, string>` | Per-engine default model (`claude-code` / `codex`). Fresh installs use `auto` for both engines: the runtime chooses its default model. Historical migrations and existing user choices are preserved. The actual model is reported by the engine when the session starts; an Auto configuration label is not a model name. |
138
+ | `workflowPolicy` | `{ commit, push, publish }` | Independent `manual` / `automatic` preferences, snapshotted at workspace creation. Fresh defaults are manual. See [Git preferences](#first-run-defaults-and-git-workflow-preferences). |
139
+ | `skillSuite` | `string` | Standard, an external suite/combination, or custom instructions. Fresh default `standard`; see [Skill suites](#skill-suites). |
140
+ | `defaultPermissionModeByEngine` | `Record<engine, mode>` | Default permission mode per engine. See [Permission modes](#permission-modes). |
141
+ | `dangerouslySkipPermissions` | `boolean` | Disable all approval prompts. **Use with care.** |
142
+ | `prPromptTemplate` | `string` | Template rendered by the `/open-pr` endpoint. Supports `{{pr_number}}`, `{{pr_url}}`, `{{branch_name}}`, `{{diff_stats}}`, `{{commits}}`, etc. |
143
+ | `reviewPromptTemplate` | `string` | Template for the review prompt action. |
144
+ | `notionInitialPromptTemplate` | `string` | Template injected as the first user message when a workspace is created from a Notion page. |
145
+ | `sentryInitialPromptTemplate` | `string` | Template injected as the first user message when a workspace is created from a Sentry issue. |
146
+ | `gitConventions` | `string` | Markdown block written to `.ai/.git-conventions.md` inside every workspace. |
147
+ | `setupScript` | `string` | Shell script run in a worktree after it is created, before the agent starts. Empty disables the setup script. |
148
+ | `cleanupScript` | `string` | Shell script run after a session ends. Empty disables. See `cleanupScriptMode`. |
149
+ | `cleanupScriptMode` | `'idle' \| 'no-tasks'` | When the cleanup script fires: after every session (`idle`) or only when no Kōbō task remains (`no-tasks`). In auto-loop it runs only once every task is done. |
150
+ | `cleanupScriptOnlyOnChanges` | `boolean` | Run the cleanup script only when the worktree has uncommitted changes (modified / added / deleted / untracked files). |
151
+ | `archiveScript` | `string` | Shell script run server-side when a workspace is archived. Empty disables the archive script. |
152
+ | `changeSourceBranchScript` | `string` | Shell script that **replaces** the built-in change-source-branch logic. The global default is Kōbō's bundled script; clearing the effective script hides the UI action. The backend retains a built-in fallback. See [Custom change-source-branch script](#custom-change-source-branch-script). |
153
+ | `editorCommand` | `string` | Command used by the "Open in editor" action (e.g. `code`, `phpstorm`, `cursor`). The worktree path is appended as the last argument. |
154
+ | `browserNotifications` | `boolean` | Trigger Web Notifications when an agent finishes a turn. |
155
+ | `audioNotifications` | `boolean` | Play a sound when an agent finishes a turn. |
156
+ | `whipEnabled` | `boolean` | Enable the interactive whip. Default `false`. |
157
+ | `whipShortcut` | `string` | Portable whip shortcut. Default `mod+shift+x`. |
158
+ | `whipVolume` | `number` | Independent whip volume from `0` to `1`. Default `1`; master `audioNotifications` still mutes playback. |
159
+ | `audioNotificationSound` | `string` | Identifier of the chosen sound. |
160
+ | `audioQuestionSound` | `string` | Sound used when an agent asks a question. |
161
+ | `audioPrCiFailedSound` | `string` | Sound used when CI enters `FAILURE`. |
162
+ | `audioPrCiFailedEnabled` | `boolean` | Enable audio when CI enters `FAILURE`. |
163
+ | `audioPrCiFailedVolume` | `number` | CI-failure sound volume from `0` to `1`. |
164
+ | `audioPrCiRecoveredSound` | `string` | Sound used when CI moves from `FAILURE` to `SUCCESS`. |
165
+ | `audioPrCiRecoveredEnabled` | `boolean` | Enable audio when CI recovers. |
166
+ | `audioPrCiRecoveredVolume` | `number` | CI-recovery sound volume from `0` to `1`. |
167
+ | `audioPrChangesRequestedSound` | `string` | Sound used when a review requests changes. |
168
+ | `audioPrChangesRequestedEnabled` | `boolean` | Enable audio when changes are requested. |
169
+ | `audioPrChangesRequestedVolume` | `number` | Changes-requested sound volume from `0` to `1`. |
170
+ | `audioPrApprovedSound` | `string` | Sound used when a PR becomes approved. |
171
+ | `audioPrApprovedEnabled` | `boolean` | Enable audio when a PR becomes approved. |
172
+ | `audioPrApprovedVolume` | `number` | PR-approved sound volume from `0` to `1`. |
173
+ | `audioPrMergeConflictSound` | `string` | Sound used when a merge conflict appears. |
174
+ | `audioPrMergeConflictEnabled` | `boolean` | Enable audio when a merge conflict appears. |
175
+ | `audioPrMergeConflictVolume` | `number` | Merge-conflict sound volume from `0` to `1`. |
176
+ | `audioPrReadyToMergeSound` | `string` | Sound used when an idle workspace's PR becomes ready to merge. |
177
+ | `audioPrReadyToMergeEnabled` | `boolean` | Enable audio when a PR becomes ready to merge. |
178
+ | `audioPrReadyToMergeVolume` | `number` | Ready-to-merge sound volume from `0` to `1`. |
179
+ | `audioPrMergedSound` | `string` | Sound used when an open PR becomes merged. |
180
+ | `audioPrMergedEnabled` | `boolean` | Enable audio when a PR becomes merged. |
181
+ | `audioPrMergedVolume` | `number` | PR-merged sound volume from `0` to `1`. |
182
+ | `audioNotificationVolume` | `number` | General notification sound volume from `0` to `1`. |
183
+ | `notionMcpKey` | `string` | Override the `~/.claude.json` MCP key used for Notion (defaults to the enabled entry named exactly `notion`). |
184
+ | `sentryMcpKey` | `string` | Override the Sentry MCP key (defaults to the first enabled entry whose key contains `sentry`, case-insensitive). |
185
+ | `notionEnabled` | `boolean` | Enable Notion imports and automations. Defaults to `true`; disabling preserves its configuration and existing workspace links. |
186
+ | `sentryEnabled` | `boolean` | Enable Sentry imports and automations. Defaults to `true`; disabling preserves its configuration and existing workspace links. |
187
+ | `showThinkingBlocks` | `boolean` | Display the latest agent reasoning panel immediately above the chat input. Defaults to `true`. |
188
+ | `notionStatusProperty` | `string` | Notion DB property updated to `notionInProgressStatus` when a workspace starts work on a Notion-backed mission. Empty disables the feature. |
189
+ | `notionInProgressStatus` | `string` | Value written to `notionStatusProperty`. |
190
+ | `notionAssigneeProperty` | `string` | Notion People property to auto-assign to the authenticated user. |
191
+ | `notionUserId` | `string` | Notion user UUID assigned via `notionAssigneeProperty`. |
192
+ | `tags` | `string[]` | Global tag catalogue exposed in the sidebar filters and the workspace-tag picker. |
193
+ | `branchPrefixes` | `string[]` | Git branch prefixes offered on the workspace creation page (stored without the trailing `/`). The first entry is the default selection. |
194
+ | `worktreesPath` | `string` | Root directory where new worktrees are created. Defaults to `.worktrees` (resolved relative to the project). Absolute, `$HOME`, `~`, and `%USERPROFILE%` are accepted; `..` and drive-relative Windows paths (`C:foo`) are rejected. |
195
+ | `worktreesPrefixByProject` | `boolean` | Nest each worktree under a project-named subfolder, preventing collisions when multiple projects share the same `worktreesPath`. |
196
+ | `autoPurgeOnPrMerged` | `boolean` | When a PR transitions to MERGED, also remove the worktree from disk (in addition to archiving). Chat history and PR metadata are preserved. Default `false`. See [Auto-purge worktree on PR merged](#auto-purge-worktree-on-pr-merged). |
197
+ | `voiceEnabled` | `boolean` | Enable local push-to-talk transcription. |
198
+ | `voicePttKey` | `'alt' \| 'ctrl+space'` | Hotkey held to record. |
199
+ | `voiceLanguage` | `string` | `auto` or a 2-letter language code. |
200
+ | `voiceModel` | `string \| null` | Active model name (e.g. `base`). |
201
+ | `voiceCommandPath` | `string` | Override `whisper-cli` path. Empty falls back to `WHISPER_CPP_COMMAND` then `PATH`. |
202
+ | `voiceFfmpegPath` | `string` | Override `ffmpeg` path. Empty falls back to `PATH`. |
203
+ | `voiceTemperature` | `number` | Decoding temperature, `0`–`1`. |
204
+ | `voicePrompt` | `string` | Initial prompt to bias transcription (custom vocabulary, names). |
205
+
206
+ PR/MR event sound settings accept `inherit` (the default, uses
207
+ `audioNotificationSound`) or a bundled sound filename from **Settings →
208
+ Notifications**. Each event has an independent `Enabled` toggle and `Volume`.
209
+ Migration v45 converts a previous `none` selection into a disabled card and
210
+ preserves the effective general volume. Review-decision sounds currently
211
+ require GitHub because the GitLab provider does not expose approval decisions.
212
+ When multiple states change in one poll, every enabled sound is queued and
213
+ played in order.
214
+
215
+ ### Per-project settings
216
+
217
+ Projects override a subset of global settings. Anything you set here takes precedence for workspaces created under that project path.
218
+
219
+ | Key | Type | Purpose |
220
+ |---|---|---|
221
+ | `path` | `string` | Absolute project path (primary key). |
222
+ | `displayName` | `string` | Override the auto-derived name. |
223
+ | `color` | `ProjectColor \| null` | Sidebar accent. |
224
+ | `defaultSourceBranch` | `string` | Source branch new workspaces branch from (e.g. `develop`). |
225
+ | `defaultModel` | `string` | Project-scoped model default. |
226
+ | `dangerouslySkipPermissions` | `boolean` | Project-scoped override. |
227
+ | `prPromptTemplate`, `reviewPromptTemplate`, `notionInitialPromptTemplate`, `sentryInitialPromptTemplate`, `gitConventions` | `string` | Per-project versions of the global templates. Empty inherits the global value. |
228
+ | `setupScript`, `cleanupScript`, `archiveScript` | `string` | Per-project versions of the global lifecycle scripts. Empty inherits the global value. |
229
+ | `changeSourceBranchScript` | `string` | Per-project version of the custom change-source-branch script. Empty inherits the global value. See [Custom change-source-branch script](#custom-change-source-branch-script). |
230
+ | `cleanupScriptMode` | `'' \| 'idle' \| 'no-tasks'` | Per-project override of the cleanup trigger mode. Empty inherits the global mode. |
231
+ | `taskPromptTemplate` | `string` | Prompt used to prefill the mission description when this project is selected. Empty leaves the description without a project-provided prompt. |
232
+ | `forge` | `'auto' \| 'github' \| 'gitlab' \| 'bitbucket-community' \| 'none'` | Which forge provides PR/MR features. `auto` detects from the git remote URL. See [Forge integration](#forge-integration). |
233
+ | `devServer.startCommand` / `stopCommand` | `string` | Per-workspace dev server commands. Docker, npm, or any shell-startable process. See [Dev server](#dev-server) for the status/URL contract. |
234
+ | `e2e.framework` | `'cypress' \| 'playwright' \| 'jest' \| 'vitest' \| 'other' \| ''` | E2E framework auto-loop grooming should target. |
235
+ | `e2e.skill` | `string` | Optional skill name injected into the E2E grooming prompt. |
236
+ | `e2e.prompt` | `string` | Free-form prompt appended to every `[E2E] ` sub-task. |
237
+ | `finalization.prompt` | `string` | Runs as the very last auto-loop iteration (`[FINAL]`-prefixed task). Empty uses the built-in mandatory final verification. |
238
+
239
+ ## Progressive Web App
240
+
241
+ Production builds (`npm run build`, `npm start`, or `npx @loicngr/kobo`) serve
242
+ the Quasar PWA build. Open Kōbō in a browser and use **Install app** or **Add
243
+ to Home Screen** to install it as a desktop or mobile application.
244
+
245
+ The PWA caches the application shell for fast reopening, but agent sessions,
246
+ chat history, HTTP requests, and WebSocket updates still require a reachable
247
+ Kōbō server. Kōbō shows an offline notice and offers an explicit reload when a
248
+ new service worker is ready; it never reloads an active page automatically.
249
+ The development client (`npm run dev:client`) intentionally uses Quasar's
250
+ normal dev server and is not installable.
251
+
252
+ When working from source, `npm run install-all` installs the root, client, and
253
+ `src/client/src-pwa` dependency trees. The third tree contains Quasar's
254
+ build-only Workbox dependencies and is required by `npm run build`, but not by
255
+ the normal development client.
256
+
257
+ ## Custom change-source-branch script
258
+
259
+ The **Change source branch** action uses the effective `changeSourceBranchScript`:
260
+ a non-empty project value overrides the global script. New installations and the
261
+ settings migration seed Kōbō's bundled script as the global default; **Reset to Kōbō
262
+ default** restores it. Clearing the effective script hides the UI action. The
263
+ backend still has a built-in cherry-pick fallback for requests made without a
264
+ custom script. A configured script **replaces** that built-in flow.
265
+
266
+ ### Contract
267
+
268
+ When the script is set, Kōbō:
269
+
270
+ - still refuses if the agent is running (corruption risk, non-negotiable);
271
+ - spawns `bash -c "<your script>"` from the worktree as cwd;
272
+ - waits up to 5 minutes;
273
+ - on exit `0` → updates the workspace's `source_branch` metadata to the new
274
+ base the user typed, and refreshes the UI;
275
+ - on non-zero exit (or timeout) → surfaces the script's stderr as the toast.
276
+
277
+ The script owns everything else: the git reconstruction (cherry-pick, rebase,
278
+ or reset, your choice), the conflict resolution, the PR base change (`gh pr edit`
279
+ / `glab mr update`), and the force-push (`git push --force-with-lease`). The
280
+ built-in features that only apply to the cherry-pick path stay **inactive** on
281
+ the custom path: the backup branch, the `cancel-source-change` recovery, the
282
+ force-push confirmation prompt, and the agent-driven conflict resolution.
283
+
284
+ ### Environment variables
285
+
286
+ | Variable | Value |
287
+ |---|---|
288
+ | `KOBO_NEW_BASE` | the new source branch the user typed in the dialog |
289
+ | `KOBO_OLD_BASE` | the workspace's previous `sourceBranch` |
290
+ | `KOBO_WORKING_BRANCH` | the workspace's working branch |
291
+ | `KOBO_WORKTREE_PATH` | absolute path of the worktree (also the script's cwd) |
292
+ | `KOBO_PROJECT_PATH` | absolute path of the main project repo |
293
+ | `KOBO_PROJECT_NAME` | project directory name (basename of `KOBO_PROJECT_PATH`); handy for log lines or notifications |
294
+ | `KOBO_WORKSPACE_ID` | Kōbō workspace id (stable across renames); useful for backup branch naming, idempotency keys, etc. |
295
+ | `KOBO_WORKSPACE_NAME` | workspace display name as shown in the Kōbō UI |
296
+ | `KOBO_FORGE` | resolved forge id for this project (`github`, `gitlab`, `bitbucket-community` or `none`); use it to pick the appropriate CLI cleanly instead of probing them all |
297
+ | `KOBO_PR_NUMBER` | number of the PR / MR open on the resolved forge for the working branch. Empty when none is open, when the forge is `none`, or when its CLI cannot resolve it. |
298
+
299
+ The standard process env (`PATH`, `HOME`, etc.) is forwarded unchanged.
300
+
301
+ ### Example — sekur-style cherry-pick + PR re-target + force-push
302
+
303
+ ```bash
304
+ #!/usr/bin/env bash
305
+ set -euo pipefail
306
+
307
+ git fetch origin
308
+ COMMITS=$(git log --reverse --format=%H "$KOBO_WORKING_BRANCH" \
309
+ --not "origin/$KOBO_NEW_BASE" "origin/$KOBO_OLD_BASE")
310
+ COUNT=$(printf '%s\n' "$COMMITS" | grep -c '^[0-9a-f]' || true)
311
+
312
+ if [ "$COUNT" -eq 0 ]; then
313
+ git reset --hard "origin/$KOBO_NEW_BASE"
314
+ elif [ "$COUNT" -gt 50 ]; then
315
+ echo "Too many proper commits ($COUNT) — rebase manually" >&2
316
+ exit 1
317
+ else
318
+ git branch "kobo-backup/${KOBO_WORKING_BRANCH}-$(date +%s)" "$KOBO_WORKING_BRANCH"
319
+ git reset --hard "origin/$KOBO_NEW_BASE"
320
+ printf '%s\n' "$COMMITS" | xargs git cherry-pick
321
+ fi
322
+
323
+ # Re-target the PR / MR — use the forge Kōbō resolved for this project.
324
+ # `command -v` makes the script degrade gracefully if `gh` / `glab` is missing.
325
+ case "${KOBO_FORGE:-none}" in
326
+ github)
327
+ if command -v gh >/dev/null 2>&1; then
328
+ gh pr edit --base "$KOBO_NEW_BASE" 2>/dev/null || true
329
+ else
330
+ echo "warn: 'gh' CLI not installed — skipping PR base update" >&2
331
+ fi
332
+ ;;
333
+ gitlab)
334
+ if command -v glab >/dev/null 2>&1; then
335
+ glab mr update --target-branch "$KOBO_NEW_BASE" 2>/dev/null || true
336
+ else
337
+ echo "warn: 'glab' CLI not installed — skipping MR base update" >&2
338
+ fi
339
+ ;;
340
+ *) : ;; # no forge configured — skip
341
+ esac
342
+
343
+ # Force-push if the branch is tracked upstream.
344
+ if git rev-parse --abbrev-ref "@{upstream}" >/dev/null 2>&1; then
345
+ git push --force-with-lease origin "$KOBO_WORKING_BRANCH"
346
+ fi
347
+ ```
348
+
349
+ ### Trust model
350
+
351
+ The script can do anything: `git reset --hard`, force-push, delete files. The
352
+ trust model is identical to `setupScript` / `cleanupScript` / `archiveScript`.
353
+ Kōbō is a local single-user dev tool and the script is your own code.
354
+
355
+ ## Lifecycle hooks
356
+
357
+ Three moments in a workspace's life can trigger a shell script of your own:
358
+ a session ending, a PR being merged, auto-loop turning itself off. They join
359
+ the scripts Kōbō already ran on setup, cleanup, archive and
360
+ change-source-branch, and work exactly the same way.
361
+
362
+ Each hook is **empty by default, which means disabled** — a hook runs arbitrary
363
+ shell on your machine, so it only ever exists because you wrote it. Configure
364
+ them in **Settings → Scripts**, globally or per project; an empty per-project
365
+ value inherits the global one, like every other script.
366
+
367
+ | Hook | Fires when | Settings key |
368
+ |---|---|---|
369
+ | Session ended | An agent session ends, for any reason: clean finish, error, a session that failed to start, the watchdog finding the engine dead, or you stopping it | `sessionEndedScript` |
370
+ | PR merged | The PR watcher sees a PR go to MERGED | `prMergedScript` |
371
+ | Auto-loop stopped | Auto-loop is disabled, by Kōbō **or by you** | `autoLoopDisabledScript` |
372
+
373
+ Two ends never run the `Session ended` hook: a session replaced by a newer one
374
+ on the same workspace (it did not end, it was superseded), and a session
375
+ stopped by a **delete** or a **purge**, since the worktree the script would run
376
+ in is being removed at that very moment.
377
+
378
+ ### Hook contract
379
+
380
+ Each hook is spawned with `bash`, cwd = the workspace worktree, a **5 minute**
381
+ timeout, and its output streamed live into the workspace feed under
382
+ `hook:<event>:*`. The exit code is recorded but **never blocks the lifecycle
383
+ transition** that triggered it: a failing hook is reported, not fatal. If the
384
+ worktree is no longer on disk (purged, deleted by hand), the hook is skipped
385
+ silently.
386
+
387
+ ### Hook environment variables
388
+
389
+ Every hook gets the standard set: `WORKSPACE_ID`, `WORKSPACE_NAME`,
390
+ `BRANCH_NAME`, `SOURCE_BRANCH`, `PROJECT_PATH`, plus `KOBO_HOOK_EVENT` naming
391
+ the event. On top of that:
392
+
393
+ | Hook | Extra variables |
394
+ |---|---|
395
+ | Session ended | `KOBO_SESSION_ID`, `KOBO_SESSION_END_REASON` (`completed` / `error` / `killed` / `watchdog`), `KOBO_SESSION_EXIT_CODE` (empty when the engine reports none), `KOBO_SESSION_STOP_CAUSE` (`user`, `replacement`, `setup`, `archive`, or `shutdown`; empty for a natural end; superseded sessions and delete/purge stops skip the hook), `KOBO_AUTOLOOP_ACTIVE` (`1` when auto-loop is enabled, so a test run may overlap with the next agent's edits) |
396
+ | PR merged | `KOBO_PR_NUMBER`, `KOBO_PR_URL` |
397
+ | Auto-loop stopped | `KOBO_AUTOLOOP_REASON` (`completed` / `user-action`; blocked missions retain their intent and do not fire this hook), `KOBO_TASKS_PENDING` |
398
+
399
+ Only `KOBO_`-prefixed variables are passed through from the event; a hook
400
+ payload can never replace `PATH`, `HOME` or the identity variables above.
401
+
402
+ ### Hook examples
403
+
404
+ ```bash
405
+ # Session ended — run the suite, but only after a clean finish, and not
406
+ # while auto-loop is already starting the next iteration in this worktree.
407
+ [ "$KOBO_SESSION_END_REASON" = "completed" ] || exit 0
408
+ [ "$KOBO_AUTOLOOP_ACTIVE" = "1" ] && exit 0
409
+ npm test
410
+ ```
411
+
412
+ ```bash
413
+ # PR merged — notify a chat channel.
414
+ curl -fsS -X POST "$MY_WEBHOOK" \
415
+ -d "text=PR #${KOBO_PR_NUMBER} merged on ${WORKSPACE_NAME}: ${KOBO_PR_URL}"
416
+ ```
417
+
418
+ ```bash
419
+ # Auto-loop stopped — notify when all work has been verified.
420
+ [ "$KOBO_AUTOLOOP_REASON" = "completed" ] || exit 0
421
+ notify-send "Kobo" "$WORKSPACE_NAME completed"
422
+ ```
423
+
424
+ ### Ordering with archive and auto-purge
425
+
426
+ The `PR merged` hook **starts while the worktree is still on disk**, before the
427
+ workspace is archived. Archiving does not wait for it (archiving keeps the
428
+ worktree, so there is nothing to protect); [auto-purge](#auto-purge-worktree-on-pr-merged)
429
+ does wait, so a hook that deploys or tags from the worktree will not have the
430
+ ground removed under it — at the cost of delaying the purge by however long the
431
+ hook takes (capped by the 5 minute timeout), and of holding one of the PR
432
+ watcher's four concurrent slots for that long.
433
+
434
+ The hook also runs when an agent is still active in that workspace (the PR
435
+ merged while it was working). A hook that deploys from the worktree may then
436
+ pick up the agent's uncommitted changes; check `git status` first if that
437
+ matters to you.
438
+
439
+ ### Hook trust model
440
+
441
+ Identical to the other scripts: the hook can do anything your user can. Kōbō is
442
+ a local single-user dev tool and the script is your own code.
443
+
444
+ ## Imported notification sounds
445
+
446
+ Kōbō ships two original tones, `neutral.wav` and `ready.wav`. **Settings →
447
+ Notifications → Imported sounds** adds your own: pick a file, and it becomes
448
+ selectable in every sound selector on that page, including the seven PR events.
449
+
450
+ - Formats: `.wav`, `.mp3`, `.ogg`, `.webm` — what every targeted browser decodes.
451
+ The extension decides, because browsers label audio MIME types inconsistently.
452
+ - Limits: 2 MiB per file, 20 files. A notification is short; the bundled sounds
453
+ removed in the public-readiness pass ranged from 1.7 KB to 207 KB.
454
+ - Storage: `<KOBO_HOME>/sounds/`. Each file is saved as `<id><extension>` with a
455
+ server-generated id; the uploaded filename is kept only as a display label and
456
+ never becomes a path. The `sounds.json` manifest holds
457
+ `{ id, name, extension, size, createdAt }` per entry.
458
+ - Settings store the reference `custom:<id>`. Deleting a sound that a setting
459
+ still points at never fails: playback falls back to `neutral.wav`, exactly as
460
+ it does for any unknown identifier.
461
+ - The catalogue is loaded when the app starts, so a sound plays for a notification
462
+ that arrives before you ever open Settings. Playback goes through a local blob
463
+ URL, which is what makes an imported sound audible over network access and
464
+ behind a reverse proxy.
465
+ - The files live in the Kōbō home, not in the published package, so they survive
466
+ an upgrade and are reachable from a phone over network access. They are not
467
+ part of a settings export — back up `<KOBO_HOME>/sounds/` with the rest of the
468
+ data directory.
469
+
470
+ HTTP: `GET /api/sounds` lists the catalogue, `POST /api/sounds` takes one
471
+ multipart `sound` field, `DELETE /api/sounds/:id` removes one, and
472
+ `GET /api/sounds/:id/file` serves the audio. All four sit behind the same
473
+ Host/Origin and network-token gates as the rest of the API.
474
+
475
+ ## Unanswered question reminder
476
+
477
+ A workspace that asks you something sits in `awaiting-user` until you answer.
478
+ The badge in the drawer says so, but only if you are looking at Kōbō. This
479
+ reminds you.
480
+
481
+ **Settings → Notifications → Unanswered question reminder**, in minutes.
482
+ **`0` is the default and means off** — it is your attention being spent.
483
+
484
+ With a value of `10`, a workspace that has been waiting ten minutes triggers a
485
+ reminder, then another ten minutes after each reminder until you answer.
486
+ Changing the value applies from the next reminder. Answering (or the workspace
487
+ leaving `awaiting-user` for any other reason) resets the clock. The loop looks
488
+ once a minute, so a reminder lands within a minute of being due.
489
+
490
+ The reminder is a browser notification plus the question sound, the same pair
491
+ used when the question is first asked, and it is **broadcast to every open
492
+ Kōbō client** rather than only the tab showing that workspace — the point is to
493
+ reach you while you are looking at something else. As with any browser
494
+ notification, it needs the tab to be open (though not focused) and the
495
+ permission granted.
496
+
497
+ Two consequences worth knowing:
498
+
499
+ - The waiting clock lives in server memory, so restarting the server restarts
500
+ it. You get a later reminder, never a lost one.
501
+ - However far behind the loop falls (a laptop waking from sleep, say), it sends
502
+ **one** reminder per pass, not one per missed interval.
503
+
504
+ ## Attachments in the creation description
505
+
506
+ On the create page, paste or drop files into the mission description, or use
507
+ **Attach files** to choose them. Images show a preview; documents show their
508
+ filename and an icon. Remove any attachment before creating the workspace.
509
+
510
+ Supported formats:
511
+
512
+ - Images: PNG, JPEG, GIF and WebP.
513
+ - Documents: PDF, Markdown (`.md`, `.markdown`), plain text (`.txt`, `.text`),
514
+ CSV, TSV, JSON, JSONL, YAML, YML, XML, LOG, RST and TOML.
515
+
516
+ The shared limit is 10 files, 50 MiB per file and 50 MiB total, counting images
517
+ and documents together. An attachment can provide the mission context without
518
+ description text; the workspace still needs a name.
519
+
520
+ Files stay in the browser until you press **Create**. Kōbō then copies images
521
+ into `.ai/images/` and documents into `.ai/attachments/` in the new or reused
522
+ worktree before starting the agent. The initial prompt includes the paths and
523
+ document names so the agent can read them. PDFs are copied as supplied; reading
524
+ or extracting their content uses the selected agent's available tools. Kōbō does
525
+ not perform PDF extraction or OCR during creation.
526
+
527
+ Comparing two engines copies the same attachments into each worktree
528
+ independently. If creation fails without creating a workspace, the form keeps
529
+ the attachments for retry. If only the setup script fails, the workspace keeps
530
+ the files and initial prompt for resuming later.
531
+
532
+ Selected files are not saved in workspace templates or copied by **Duplicate**.
533
+ Reloading the create page discards files that have not been submitted.
534
+
535
+ ## Attachments in workspace chat
536
+
537
+ Existing workspaces accept the same images and documents as the creation form,
538
+ with the same limits: 50 MiB per file, 50 MiB of newly uploaded files per draft,
539
+ and 10 files maximum. Use **Attach files**, drag files into the chat input, or
540
+ paste files from the clipboard. Ordinary pasted text keeps its usual behavior.
541
+
542
+ Uploads show a pending badge and then the original filename. Sending is blocked
543
+ until every upload succeeds; remove a failed attachment and select it again to
544
+ retry. Removing a draft attachment also removes its uploaded file. Images keep
545
+ their inline conversation previews; document references include their original
546
+ name and worktree path for the agent to read. PDF extraction/OCR is delegated to
547
+ the agent's available tools.
548
+
549
+ Files are kept once the message is sent or queued. A failed send restores the
550
+ draft and its attachment badges while that conversation is still selected.
551
+ Switching workspaces or sessions discards unsent attachments; an upload that
552
+ finishes after the switch is cleaned up in its original workspace. Queued and
553
+ sent attachments are preserved. Documents stored in `.ai/attachments/` are added
554
+ to the worktree's `.gitignore`, including workspaces created before this feature.
555
+
556
+ ## Comparing two engines on one task
557
+
558
+ Kōbō drives two agent runtimes. Which one does better on *your* code is an
559
+ empirical question, and this answers it.
560
+
561
+ On the create page, turn on **Compare two engines** and pick the engine to
562
+ compare against. Creating then produces **two workspaces instead of one**, from
563
+ the same task description, source branch and Notion/Sentry context:
564
+
565
+ - names are suffixed with the engine (`Fix the parser (Claude Code)` and
566
+ `Fix the parser (Codex)`), both halves, so neither reads as the default one;
567
+ - branches are suffixed with the engine id (`feature/fix-the-parser-claude-code`,
568
+ `feature/fix-the-parser-codex`), so each gets its own worktree;
569
+ - each engine runs with the model, reasoning effort and permission mode set in
570
+ its own block (engine B's are pre-filled with that engine's defaults), since
571
+ a Codex model id means nothing to Claude Code and vice versa;
572
+ - with auto-loop on, the brainstorm model and effort pickers are disabled: each
573
+ engine brainstorms on the model and effort of its own block, so both halves
574
+ start from the same footing.
575
+
576
+ The two workspaces are created **one after the other**, not in parallel: each
577
+ one fetches, branches and builds a worktree in the same repository, and two of
578
+ those at once contend on the git index lock. If the second creation fails you
579
+ are told so explicitly — the first workspace exists and is usable on its own.
580
+
581
+ Once both workspaces are created, Kōbō opens them automatically in the side-by-side
582
+ view, with the first engine on the left and the comparison engine on the right.
583
+ If only the first workspace was created, it opens in the usual single-workspace view.
584
+
585
+ ### Reading the result
586
+
587
+ Both workspaces show an **Engine comparison** table at the top of their Git tab:
588
+ engine, model, status, tasks done over tasks planned, commits, files changed,
589
+ diff size, then what it took — agent messages over your messages, questions the
590
+ agent stopped to ask, tool calls, errors, input over output tokens, sessions and
591
+ total time — for each side, with a download button that exports the whole
592
+ table as CSV (one row per engine) and
593
+ a button to jump to the other one. The numbers come from the same git-stats
594
+ cache the rest of the UI uses; the table reads it when it opens and on its
595
+ refresh button, so a dash means "not measured yet", which is not the same claim
596
+ as zero. When one half was never created (or was deleted), the table says so
597
+ instead of showing a single row as if that were the whole comparison.
598
+
599
+ From there, each side's actual diff is one click away in the Git tab it sits
600
+ above.
601
+
602
+ ## Choosing an LLM for code review
603
+
604
+ The workspace's **Start review** dialog offers engine (Claude Code or Codex),
605
+ model, reasoning effort and permission mode using the same catalogues as workspace
606
+ creation. The current workspace settings are selected initially. Changing any of
607
+ these settings automatically enables and locks **New session**; with unchanged
608
+ settings you can continue the current session or start a fresh one explicitly.
609
+
610
+ Enable **Return to the current session after the review** to resume the original
611
+ conversation with its original LLM settings and the reviewer's final summary.
612
+ This option requires an existing resumable session and is off by default. The
613
+ receiving agent summarizes the findings and waits for your instructions before
614
+ applying fixes. The complete review remains in its own session history.
615
+
616
+ With the option off, subsequent conversation continues with the review LLM.
617
+ When automatic return is enabled, stopping the review cancels it. If the review
618
+ fails or Kobo restarts during it, the original settings are restored without launching the
619
+ original agent automatically; review output already recorded remains available.
620
+
621
+ ## Workspace templates and duplication
622
+
623
+ A template is a named preset of the create-workspace form: project (optional),
624
+ source branch, branch type, engine, model, reasoning effort, permission mode,
625
+ auto-loop settings, description, tasks and acceptance criteria. The workspace
626
+ name and working branch are never part of it - they are derived at each
627
+ creation - and neither are Notion / Sentry / PR URLs, which are one-off
628
+ context.
629
+
630
+ On the create page, **From a template** prefills the form with a saved preset:
631
+ only the fields the template carries change, the rest keeps its value. Once
632
+ applied, the select returns to **None**, so picking the same template again
633
+ re-applies it. A template that pins a project is listed only when that project
634
+ is selected.
635
+ **Save as template** captures the current form under a name; saving under an
636
+ existing name asks before overwriting. Templates are managed (rename, delete,
637
+ inspect) in **Settings → Workspace templates**; to edit one, load it on the
638
+ create page, adjust, and save it again under the same name.
639
+
640
+ **Duplicate**, in a workspace's context menu, opens the create page prefilled
641
+ from that workspace - its settings, description, tasks and criteria (all reset
642
+ to "to do") - with the name suffixed "(copy)". Nothing is created until you
643
+ press Create, so the branch and name can still be adjusted. Duplication carries
644
+ the workspace's settings, description, tasks and criteria, but not the
645
+ brainstorm effort nor "skip setup script", which are creation-time choices.
646
+ Archived workspaces can be duplicated too.
647
+
648
+ Templates live in `<KOBO_HOME>/workspace-templates.json` (100 at most). A
649
+ corrupt file is read as empty and reported in the server log, and is only
650
+ rewritten by the next save.
651
+
652
+ ## Auto-purge worktree on PR merged
653
+
654
+ Purging a worktree frees its disk space (often hundreds of MB or GB worth of
655
+ `node_modules`, `vendor`, build artefacts, etc.) while keeping the workspace
656
+ discoverable: chat history and PR metadata are retained in the database, the
657
+ workspace is archived, and the worktree folder is removed from disk.
658
+
659
+ **Manual trigger**: workspace context menu → **Free disk space (delete worktree)**.
660
+ A confirmation dialog warns you before any destructive action.
661
+
662
+ **Automatic trigger**: Settings → Worktrees → **Auto-purge worktree on PR
663
+ merged**. When enabled, the pr-watcher fires `purgeWorktree(id)` on the
664
+ OPEN → MERGED transition, in addition to the existing auto-archive. Disabled by
665
+ default; opt in once you trust the restore flow on your machine.
666
+
667
+ ### Restoring a purged workspace
668
+
669
+ Click **Restore worktree** in the workspace context menu or in the purged-worktree
670
+ banner. Kōbō recreates the checkout at its recorded path, then unarchives the same
671
+ workspace. Its conversation, tasks, sessions and lifecycle status are retained.
672
+ Restoration does not start an agent or dev server, run setup scripts, reinstall
673
+ dependencies, or re-enable auto-loop or cancelled schedules.
674
+
675
+ Recovery uses the first available source:
676
+
677
+ 1. The existing local working branch (including commits made since purge).
678
+ 2. The `headCommitSha` captured at purge time, if the branch was deleted and the
679
+ commit object is still available locally.
680
+ 3. The exact working branch fetched from `origin`.
681
+
682
+ The local path works offline. Older purge records without a saved SHA remain
683
+ supported. Kōbō never substitutes the latest source branch or a merge commit for
684
+ missing work, resets an existing branch, or overwrites an occupied directory.
685
+ If no recovery source remains, the workspace stays purged and the UI explains
686
+ how to proceed. If Git still registers the missing worktree, repair that stale
687
+ registration manually before retrying.
688
+
689
+ **This is not a filesystem backup:** discarded uncommitted changes, ignored files,
690
+ local secrets and installed dependencies are not recovered. Git objects are not
691
+ pinned against garbage collection; restoration cannot recover deleted history
692
+ that no longer exists locally or remotely. `mergeCommitSha` is historical metadata
693
+ and is not currently populated; `headCommitSha` is optional and best-effort.
694
+
695
+ Manual recovery remains available when the working branch was deleted remotely.
696
+ For example, GitHub retains PR head refs:
697
+
698
+ ```bash
699
+ # Run from the project's repository, substituting the saved values.
700
+ git fetch origin "pull/<pr-number>/head:<branch-name>"
701
+ git worktree add "<original-worktree-path>" "<branch-name>"
702
+ ```
703
+
704
+ Use the appropriate provider's recovery flow for a GitLab MR. Once the expected
705
+ worktree is recreated, the PR watcher detects it on its next 30 s tick. It verifies
706
+ the exact root, repository and working branch before clearing both purge fields
707
+ and `archived_at`, and emits `workspace:worktree-restored` to refresh the client.
708
+ Unrelated directories and checkouts are never automatically adopted.
709
+
710
+ ### Permission errors during purge
711
+
712
+ Docker often leaves root-owned files in `node_modules` / `vendor` inside the
713
+ worktree (containers run as `root` by default). When Kōbō tries to remove them
714
+ it hits `EACCES` / `EPERM`. Kōbō now auto-recovers by spawning a throwaway
715
+ Docker container that runs `chown` to reclaim ownership of the worktree, then
716
+ retries the removal; the manual `sudo rm -rf` fallback is only shown when
717
+ Docker is unavailable or the retry still fails. See Settings → Worktrees →
718
+ **How purge works** expansion in the UI for the full guide (manual recovery
719
+ commands for already-broken worktrees, including `setfacl` and `chown -R`
720
+ alternatives).
721
+
722
+ **Prevention summary**:
723
+
724
+ - **Best**: configure your container to run as the host user (`USER`
725
+ directive in `Dockerfile`, or `user: "${UID}:${GID}"` in `docker-compose.yml`
726
+ with `UID`/`GID` exported).
727
+ - **Fallback**: pose a default ACL on the worktrees root so future files
728
+ inherit access for your user: `setfacl -d -m u:$(whoami):rwX <worktrees-root>`.
729
+ Works on ext4 / btrfs / xfs with standard Docker bind mounts; does NOT work
730
+ on named Docker volumes, NTFS / exFAT / tmpfs, strict SELinux with `:Z`, or
731
+ `userns-remap`.
732
+
733
+ ## Conversation history retention (opt-in)
734
+
735
+ Every agent message, tool call and diff Kōbō displays is persisted in the
736
+ `ws_events` table so the activity feed survives a reload. At roughly 25 rows per
737
+ second per running session, that table is the one that grows without bound —
738
+ and SQLite never gives the space back on its own.
739
+
740
+ Kōbō can prune it, but **the feature is off by default and always has to be
741
+ turned on deliberately**: it permanently deletes conversation history, and a
742
+ silent loss of history is worse than a large file. Leave it off and nothing is
743
+ ever deleted, exactly as before.
744
+
745
+ **Settings → Worktrees → Conversation history retention** exposes two values,
746
+ both global (the database is single):
747
+
748
+ | Setting | Default | Meaning |
749
+ |---|---|---|
750
+ | Delete events older than (days) | `0` | Age above which an agent event is deleted. **`0` means retention is disabled — nothing is ever deleted.** |
751
+ | Events always kept per workspace | `0` | The most recent events of each workspace survive whatever their age. Set it (5000 is a good starting point) at the same time you enable retention, so an old but still-open workspace keeps a readable history. |
752
+
753
+ The prune runs **at server startup and every 24 hours** while the backend stays
754
+ running. Each pass reads the current settings and deletes in batches of 5,000
755
+ rows. When enough pages have been freed, a `VACUUM` returns the space to the
756
+ filesystem.
757
+
758
+ **What it deletes:** eligible rows of `ws_events` — the recorded agent output.
759
+ Affected session metrics and the derived search index are reconciled with the
760
+ retained history. External MCP message receipts are kept independently.
761
+ **What it never touches:** workspaces, tasks, sessions, branches, worktrees, or
762
+ any file in your repositories.
763
+
764
+ Enabling retention — or shortening an existing window — asks for confirmation
765
+ first, and the dialog states how many of your recorded events the new window
766
+ would delete. Note that the first run is the largest one: switching a year-old
767
+ database to a 30-day window deletes eleven months in one go. Deleted events
768
+ cannot be recovered, and the server logs how many each prune removed.
769
+
770
+ ## Concurrent agents
771
+
772
+ `maxConcurrentAgents` (global, fresh-install default `2`; `0` = no limit) controls admission of unattended agent sessions. Existing installations retain their configured value.
773
+
774
+ The limit applies to auto-loop iterations, cron, one-shot wakeups and quota retries. Every owned agent controller counts, including starting, manual and closing sessions. Manual starts and chat resumes are exempt from admission; this is not a hard global process limit.
775
+
776
+ An auto-loop workspace that finds no free slot stays enabled and waits. Confirmed
777
+ capacity release and lifecycle-guard release reconsider waiting workspaces.
778
+ It emits `autoloop:waiting-for-slot` so the
779
+ interface can say so rather than looking stalled.
780
+
781
+ The setting lives in `settings.json` under `global` and has no UI control yet:
782
+
783
+ ```json
784
+ { "global": { "maxConcurrentAgents": 3 } }
785
+ ```
786
+
787
+ ## External LLM access through MCP
788
+
789
+ An external MCP client can connect to `http://127.0.0.1:3000/api/mcp` using Streamable HTTP (use your backend's configured port). It can discover workspaces, read their conversations, send messages to their agents and answer pending questions. The existing global stdio MCP server exposes these conversation tools too.
790
+
791
+ Remote HTTP clients follow the [network access](#network-access) settings and supply the Kōbō token as `Authorization: Bearer <token>` or `X-Kobo-Token`. The endpoint uses the same Host/Origin and reverse-proxy policy as the rest of the backend. No network setting is changed automatically.
792
+
793
+ See [Connect an external LLM](src/mcp-server/README.md#connect-an-external-llm) for connection examples, tool arguments, cursor polling and authentication details.
794
+
795
+ The **External LLM access (MCP)** panel within global Network access settings provides copyable URLs and HTTP/stdio configurations for this running installation. Previews use token placeholders; copying a configuration with the real token is a separate action. Set a client name to identify its messages and question answers in workspace history.
796
+
797
+ External senders can supply `idempotency_key` once per intended message and reuse it on retries. The durable receipt survives backend restarts and event retention. An uncertain delivery is explicitly reported and never automatically sent again; see the MCP guide for response codes and limitations.
798
+
799
+ ## Kōbō update checks
800
+
801
+ The backend checks the package's npm `latest` release at startup and then every **20 minutes**, even with no browser open. All connected interfaces receive the result through the existing update banner, without reloading the page. Reconnecting clients retrieve the current snapshot.
802
+
803
+ Checks share one request across browser tabs, have a five-second deadline, and do not interrupt running agents when the registry is unreachable. A transient error preserves the last known valid version until the next check. Dismissing the banner hides that version; a newer release appears again. The post-install What's New dialog remains separate.
804
+
805
+ These checks report availability only: they do not install packages or restart Kōbō automatically.
806
+
807
+ ## Network access
808
+
809
+ By default, Kōbō binds its HTTP and WebSocket server to `127.0.0.1` (localhost
810
+ only). Network access is an **opt-in** feature that re-binds to all interfaces
811
+ (`0.0.0.0`) so you can control Kōbō from another device on the same LAN: a
812
+ phone, a tablet, a second computer.
813
+
814
+ **Default posture: disabled.** Localhost API/WS connections bypass the token gate
815
+ unless **Behind a reverse proxy** is enabled. `/api/health` remains exempt in
816
+ either mode; Host/Origin checks apply separately.
817
+
818
+ ### Production vs. development mode (important)
819
+
820
+ The setting controls the backend, not the separate development server:
821
+
822
+ - **Production** (`npm start`, or `npx @loicngr/kobo`): a single Kōbō process
823
+ serves both the UI and the API on one port, so the bind address follows this
824
+ setting directly: disabled → `127.0.0.1` (invisible on the LAN, connections
825
+ refused), enabled → all interfaces + token gate. Behind-a-reverse-proxy mode
826
+ also removes the loopback exemption.
827
+ - **Development** (`npm run dev:all`): the Quasar/Vite dev server runs as a
828
+ **separate process** (port 8080) and proxies `/api` + `/ws` to the backend over
829
+ `localhost`. The backend toggle does not set Vite's listening address.
830
+ Proxied requests use the loopback token exemption unless proxy mode is enabled;
831
+ Host/Origin checks still apply. To keep development local, explicitly set
832
+ `devServer: { host: '127.0.0.1' }` in `src/client/quasar.config.ts`. Use a
833
+ production build for remote access through Kōbō's normal network settings.
834
+
835
+ ### Enabling network access
836
+
837
+ 1. Open **Settings → General → Network access**.
838
+ 2. Toggle **Enable network access** on.
839
+ 3. A banner appears: **"Kōbō must be restarted to apply this change."** Restart
840
+ Kōbō; the server cannot re-bind a live port without restarting. The banner
841
+ stays visible until the restart happens so you don't miss it.
842
+ 4. After restart, Kōbō listens on all interfaces. Your LAN IP addresses (one
843
+ per network interface) are listed in the same Settings panel.
844
+
845
+ To disable network access, toggle it off and restart again.
846
+
847
+ ### The network access token
848
+
849
+ When network access is enabled, every non-loopback HTTP request and every
850
+ non-loopback WebSocket connection must supply a shared token.
851
+
852
+ - **Auto-generated**: Kōbō generates a random token the first time you enable
853
+ the feature. You never have to type one in.
854
+ - **Where to find it**: Settings → General → Network access → **Token** field
855
+ (always masked; use the copy button).
856
+ - **Regenerate**: the **Regenerate** button creates a new random token and
857
+ invalidates any device already using the old one. Useful when a device is
858
+ lost or you rotate credentials.
859
+ - **Not exported**: the token is excluded from any config export / backup
860
+ bundle, because it is a per-machine secret tied to this installation.
861
+
862
+ ### Connecting a remote device
863
+
864
+ **Option A: QR code (recommended for phones)**
865
+
866
+ The Settings panel shows a QR code encoding
867
+ `http://<lan-ip>:<port>/?token=<token>`. Scan it with your phone's camera
868
+ while on the same Wi-Fi network; the browser opens directly to a pre-authenticated
869
+ session. The token is stored in the browser's `localStorage` under
870
+ `kobo:network-token` so subsequent visits don't require re-scanning.
871
+
872
+ **Option B: manual URL**
873
+
874
+ 1. Copy one of the LAN URLs shown in the Settings panel (format
875
+ `http://<lan-ip>:<port>/`).
876
+ 2. Open it on the remote device.
877
+ 3. A login dialog appears: paste the token from the Settings panel and confirm.
878
+ 4. The token is stored in `localStorage`; subsequent visits auto-authenticate.
879
+
880
+ **Host machine (localhost)**: connect to `http://localhost:<port>` as usual.
881
+ No token is needed unless **Behind a reverse proxy** is enabled.
882
+
883
+ ### Token transport
884
+
885
+ | Connection type | How to supply the token |
886
+ |---|---|
887
+ | HTTP requests | `X-Kobo-Token: <token>` header |
888
+ | MCP HTTP requests (`/api/mcp`) | `X-Kobo-Token` or `Authorization: Bearer <token>` |
889
+ | WebSocket connections | `?token=<token>` query parameter on the upgrade URL |
890
+
891
+ The Kōbō browser client handles both automatically once the token is stored in
892
+ `localStorage`. Third-party scripts or API consumers must supply the header or
893
+ query param manually.
894
+
895
+ The IP address used for the loopback exemption is taken **from the OS socket**,
896
+ never from forwarded headers (`X-Forwarded-For` etc.), so a remote client cannot
897
+ spoof a loopback address to bypass authentication.
898
+
899
+ ### Reachable addresses
900
+
901
+ Kōbō only answers to the names it is legitimately reachable at: `localhost`,
902
+ `127.0.0.1`, `::1`, `0.0.0.0` and — once network access is enabled — its own
903
+ non-internal addresses, IPv4 and IPv6 alike. A request carrying any other
904
+ `Host` gets a **403 `forbidden host`**. Note that Settings lists only the IPv4
905
+ URLs, since those are the ones meant to be read off a screen or scanned; the
906
+ IPv6 addresses of the same machine are accepted too.
907
+
908
+ This is what stops DNS rebinding: a page you visit can make its own domain
909
+ re-resolve to `127.0.0.1`, after which the browser treats Kōbō's responses as
910
+ same-origin and can read them — your settings, your workspaces — and can open
911
+ `/ws/terminal/<id>`, which is a real shell on your machine. The connection
912
+ genuinely comes from loopback, so the token gate lets it through; only the
913
+ `Host` and `Origin` headers tell it apart from your own tab.
914
+
915
+ The `Origin` check closes cross-site writes and the WebSocket terminal. A page
916
+ on another site can reach `http://localhost:3000` with a legitimate `Host`, and
917
+ while it cannot read the reply, the write would otherwise land. The rule is that
918
+ the page must have been served by us: its origin has to match the `Host` of the
919
+ same request, otherwise it gets **403 `forbidden origin`**. That holds however
920
+ you reach Kōbō, including through a remapped port such as `docker -p 3001:3000`
921
+ or an `ssh -L` tunnel. A request carrying no `Origin` at all is allowed:
922
+ browsers always send one on a write or a WebSocket handshake, so its absence
923
+ means a command-line caller.
924
+
925
+ Practical consequences:
926
+
927
+ - **Reaching Kōbō by a custom hostname does not work** (`http://my-nas.local:3000`,
928
+ a `/etc/hosts` alias, a container name). Use the LAN IP shown in Settings, or
929
+ turn on [behind a reverse proxy](#behind-a-reverse-proxy) mode.
930
+ - **In Docker, the LAN IP shown in Settings is the container's**, not the host's,
931
+ because Kōbō reads its own interfaces. Reaching a containerised Kōbō from
932
+ another device on the LAN therefore requires behind-a-reverse-proxy mode,
933
+ which `docker-compose.example.yml` and `docker-compose.local-traefik.yml`
934
+ already set. `docker-compose.local.yml` does not need it: it is opened at
935
+ `http://localhost:3000` from the host, which is allowed as-is.
936
+ - **`/api/health` is exempt**, so a Docker or Compose healthcheck reaching it
937
+ through the service name keeps working. `/api/health/logs` and
938
+ `/api/health/report` are not exempt.
939
+ - **A page served from another loopback port is not trusted.** Being on
940
+ loopback is not enough: Kōbō starts a dev server per workspace, on a loopback
941
+ port, serving whatever code an agent just wrote, and such a page must not
942
+ count as the real interface. In development the Quasar dev server is the one
943
+ exception, since it proxies to the backend and so never matches the `Host`;
944
+ the `dev` npm script declares it through `KOBO_DEV_CLIENT_ORIGIN`.
945
+ - **Behind-a-reverse-proxy mode accepts any host by default**, because only the
946
+ operator knows the expected domain. Declare it with
947
+ `KOBO_NETWORK_ACCESS_PROXY_HOST` (see below) to close that; the bundled
948
+ `docker-compose.example.yml` and `docker-compose.local-traefik.yml` do. That
949
+ mode also disables the loopback exemption, so every `/api/*` request has to
950
+ carry the token — but the SPA itself is served outside `/api/*` and behind no
951
+ token, so without a declared hostname a rebinding page can be served the real
952
+ interface and ask the user to paste their token into it.
953
+
954
+ #### `KOBO_NETWORK_ACCESS_PROXY_HOST`
955
+
956
+ Set it to the domain your proxy serves Kōbō under, and the host check is
957
+ enforced in behind-a-reverse-proxy mode instead of being skipped:
958
+
959
+ ```yaml
960
+ environment:
961
+ - KOBO_NETWORK_ACCESS_ENABLED=true
962
+ - KOBO_NETWORK_ACCESS_BEHIND_PROXY=true
963
+ - KOBO_NETWORK_ACCESS_PROXY_HOST=kobo.example.com
964
+ ```
965
+
966
+ A scheme and a port are accepted and ignored (`https://kobo.example.com:8443`
967
+ reads as `kobo.example.com`). Loopback stays reachable so a local healthcheck
968
+ keeps working. Leaving it unset preserves the previous behaviour.
969
+
970
+ ### Security caveats
971
+
972
+ > **Network access is designed for trusted local networks only.**
973
+
974
+ - **No HTTPS / no TLS.** Kōbō's built-in server serves plain HTTP. The token
975
+ travels in cleartext on the network. Anyone who can observe your LAN traffic
976
+ (e.g. another device on an open Wi-Fi network) can capture it.
977
+ - **Not suitable for internet exposure.** Do not open the Kōbō port on your
978
+ router or firewall. If you need access over the internet, place a terminating
979
+ HTTPS reverse proxy (nginx, Caddy…) or a zero-config VPN (Tailscale, WireGuard)
980
+ in front of Kōbō yourself. Kōbō is not responsible for that layer.
981
+ - **A loopback reverse proxy requires the proxy setting.** Enable
982
+ [Behind a reverse proxy](#behind-a-reverse-proxy) to disable the loopback
983
+ exemption, configure the expected proxy hostname, and terminate TLS at the
984
+ proxy. Without that setting, requests forwarded from loopback bypass the token.
985
+ - **The Vite development server has its own network exposure.** The backend
986
+ toggle does not control its listener or secure its development assets. See
987
+ [Production vs. development mode](#production-vs-development-mode-important)
988
+ above; use production (`npm start`) for the normal remote-access setup.
989
+ - **Single shared token.** All remote devices share the same token. Regenerating
990
+ it disconnects every device at once; there is no per-device revocation.
991
+
992
+ ### Behind a reverse proxy
993
+
994
+ The loopback bypass described above — any request from `127.0.0.1`/`::1` is always allowed,
995
+ token or not — becomes unsafe the moment a reverse proxy (Traefik, nginx, Caddy…) sits in front
996
+ of Kōbō. In a typical Docker Compose deployment, Traefik and Kōbō run as separate containers on a
997
+ shared network; every request Kōbō sees originates from the proxy, and depending on the network
998
+ path it can be indistinguishable from a genuine loopback request. Without this setting, anyone who
999
+ can reach the proxy publicly gets unauthenticated access to Kōbō, silently.
1000
+
1001
+ **Enable it**: Settings → General → Network access → **Behind a reverse proxy** (only visible once
1002
+ Network access itself is enabled). No restart needed — unlike the main Network access toggle, this
1003
+ one only changes the per-request auth decision, not the listening address.
1004
+
1005
+ **What changes**: the loopback bypass is disabled for API requests and WebSocket
1006
+ upgrades, including those from the host itself. For example, requests to
1007
+ `http://localhost:3000/api/workspaces` now need `X-Kobo-Token: <token>`.
1008
+ The application shell remains public so it can display the login dialog, and
1009
+ `/api/health` remains exempt for healthchecks.
1010
+
1011
+ For the concrete Docker setup (which compose file, which env vars, which volumes), see
1012
+ [Docker deployment](#docker-deployment) below.
1013
+
1014
+ ## Docker deployment
1015
+
1016
+ Kōbō ships an official `Dockerfile` and three ready-to-use Compose files, one per use case. All
1017
+ three build from the same image; only the topology and a handful of env vars differ.
1018
+
1019
+ ### Choosing a compose file
1020
+
1021
+ | File | Topology | Use it for |
1022
+ |---|---|---|
1023
+ | [`docker-compose.local.yml`](./docker-compose.local.yml) | Kōbō only, port `3000` published directly. No Traefik, no TLS, no SSH server, no Docker socket. | Trying Kōbō out or poking at a change locally — the fastest path to a running instance. |
1024
+ | [`docker-compose.local-traefik.yml`](./docker-compose.local-traefik.yml) | Traefik in front, Kōbō never on a published port, `kobo.localhost` (auto-resolves to `127.0.0.1`, no `/etc/hosts` editing). | Rehearsing the exact reverse-proxy topology/auth/mount setup locally, before touching a real VPS. |
1025
+ | [`docker-compose.example.yml`](./docker-compose.example.yml) | Traefik + Let's Encrypt (real domain, real TLS), SSH access, optional Docker-socket passthrough. | The reference for an actual production deployment (VPS behind a reverse proxy). |
1026
+
1027
+ All three share the same underlying image (`docker build -t kobo:local .` from the repo root,
1028
+ once) and the same core volume set; they differ in network topology and which optional pieces
1029
+ (SSH server, Docker socket, TLS) are wired in.
1030
+
1031
+ ### The image
1032
+
1033
+ Multi-stage `Dockerfile`, `node:24.19.0-slim` base:
1034
+
1035
+ - **Build stage**: installs root, client, and PWA deps, then runs `npm run build` (client via Quasar, server via `tsc`).
1036
+ - **Runtime stage**: production deps only, plus OS tooling the agents and forge CLIs need — `git`, `openssh-client` + `openssh-server`, `ripgrep`, `fd-find`, `jq`, `curl`, Python + build tools, the Docker CLI (talks to a mounted host socket, see [Volumes](#volumes)), `gh`, `glab`, `tmux`, `vim`/`nano`, and more for interactive shell use.
1037
+ - **Global CLIs**: `@anthropic-ai/claude-code` and `@openai/codex` are installed globally for interactive login (`claude /login`, `codex login` over SSH) — separate from Kōbō's own bundled SDK/subprocess usage, which needs no global install.
1038
+ - **`git config --system --add safe.directory '*'`**: the container runs as root, but bind-mounted project repos keep the *host* user's UID on disk. Git's ownership check (CVE-2022-24765 mitigation) refuses to touch a repo it doesn't "own" unless told otherwise. This line pre-trusts every mounted path — fine for a single-user dev container, not something you'd do on a multi-tenant host.
1039
+ - **`HEALTHCHECK`**: `curl -f http://localhost:$SERVER_PORT/api/health` every 30s. `/api/health` is exempt from the network-access token gate unconditionally (even with `networkAccessBehindProxy` on) — Docker's `HEALTHCHECK` process has no way to supply the runtime-generated token, and without the exemption a "behind a reverse proxy" container never reports healthy, which makes Traefik's Docker provider filter it out of its router table entirely (Traefik drops unhealthy/starting containers from routing).
1040
+ - **`EXPOSE 3000`**, `ENV SERVER_PORT=3000`, entrypoint `docker-entrypoint.sh` → `npm start`.
1041
+
1042
+ Build once: `docker build -t kobo:local .`
1043
+
1044
+ ### Environment variables (Docker)
1045
+
1046
+ Beyond the [general environment variables](#environment-variables), these matter specifically for a container deployment:
1047
+
1048
+ | Variable | Where it's set | Purpose |
1049
+ |---|---|---|
1050
+ | `KOBO_HOME` | all three compose files | Points at the named volume mount (`/root/.config/kobo`) so state survives `docker compose down`. |
1051
+ | `KOBO_NETWORK_ACCESS_ENABLED` | all three | Binds Kōbō to `0.0.0.0` inside the container at boot — required, since a Docker-forwarded or Traefik-proxied connection never looks like true loopback from Kōbō's point of view, even locally. Without it the container's `127.0.0.1`-only default blocks every external route. |
1052
+ | `KOBO_NETWORK_ACCESS_BEHIND_PROXY` | `local-traefik.yml`, `example.yml` | Disables the loopback-trust bypass entirely — needed whenever a reverse proxy sits in front, since every request now looks like it comes from the proxy. Not set in `local.yml`, which publishes Kōbō's port directly with no proxy in between. |
1053
+ | `GH_TOKEN` / `GITLAB_TOKEN` | `example.yml` (bring your own via `.env`) | Non-interactive auth for `gh`/`glab` inside the container — there's no browser to complete an interactive `gh auth login` on a headless VPS. |
1054
+ | `KOBO_PROJECTS_DIR` | host-side, read by all three via `${KOBO_PROJECTS_DIR:-./kobo-projects}` | Not a container env var — a *host* shell variable that picks the bind-mount source for `/projects`. Defaults to `./kobo-projects` next to the compose file. |
1055
+
1056
+ Both `KOBO_NETWORK_ACCESS_ENABLED` and `KOBO_NETWORK_ACCESS_BEHIND_PROXY` are read once at boot —
1057
+ this is what lets the container come up fully configured with no manual Settings-UI step (there's
1058
+ no browser to click through on a fresh headless deploy). The auto-generated token still needs to be
1059
+ retrieved once — see [Token / login flow](#token--login-flow).
1060
+
1061
+ ### Volumes
1062
+
1063
+ | Mount | RO/RW | Purpose |
1064
+ |---|---|---|
1065
+ | `<name>-data:/root/.config/kobo` (named volume) | RW | Kōbō's own state — SQLite DB, `settings.json`, etc. Named (not bind-mounted) so it survives `docker compose down` without `-v`, and so it can be deliberately **shared** across compose files by reusing the same volume name (see the two local files, both `kobo-test-data`). |
1066
+ | `${KOBO_PROJECTS_DIR:-./kobo-projects}:/projects` | RW | The repos you create workspaces against, and their worktrees — all on one filesystem, so a worktree created next to a project lands on the same volume as the project itself. |
1067
+ | `${HOME}/.ssh:/root/.ssh` | RO | Git SSH auth — needed to push/pull over SSH remotes. |
1068
+ | `${HOME}/.gitconfig:/root/.gitconfig` | RO | Commit author identity. Without it, agent-authored commits fail with "please tell me who you are". |
1069
+ | `${HOME}/.claude.json:/root/.claude.json` | RW | Claude Code auth + MCP server registrations. **Must already exist as a file on the host** (run `claude /login` locally first if it doesn't) — Docker silently creates an empty *directory* instead of erroring on a missing bind-mount source, which breaks auth in a confusing way. |
1070
+ | `${HOME}/.claude:/root/.claude` | RW | Claude Code's full state directory (skills/plugins cache, session transcripts, memory) — Claude Code writes into this tree during normal operation. |
1071
+ | `${HOME}/.codex:/root/.codex` | RW | Codex's full state directory (`auth.json`, `config.toml`). Harmless to mount even if you only use Claude Code. |
1072
+ | `/var/run/docker.sock:/var/run/docker.sock` | RW, `example.yml` only | Lets the dev-server panel start/stop/tail logs for your projects' own `docker compose` stacks. **DANGER**: grants the container root-equivalent control over the host — anyone who can run a command inside `kobo` can spawn a privileged container that mounts the host filesystem. Remove this line if you don't need the dev-server panel. |
1073
+
1074
+ ### Ports
1075
+
1076
+ | Port | File(s) | Notes |
1077
+ |---|---|---|
1078
+ | `3000` (published) | `local.yml` only | Kōbō's HTTP/WS port, published directly — no proxy in front. |
1079
+ | `80` | `local-traefik.yml` | Traefik's plain-HTTP entrypoint, routes `kobo.localhost` / `traefik.localhost`. |
1080
+ | `443` | `example.yml` | Traefik's TLS entrypoint (Let's Encrypt via the `le` cert resolver), routes your real domain. |
1081
+ | `2222 → 22` (published) | `example.yml` only | SSH into the container (`ssh -p 2222 root@<host>`), key-based auth only via the same mounted `~/.ssh`, no password login. The one exception to "no published ports" — Traefik proxies HTTP/WS, not raw TCP, so SSH needs its own path. Rebuilding the image regenerates the SSH host keys, so expect a one-time "host key changed" warning from your SSH client after a rebuild. |
1082
+
1083
+ Kōbō itself is **never** published directly in the two Traefik-fronted files — only Traefik is
1084
+ reachable, matching the real production posture.
1085
+
1086
+ ### Token / login flow
1087
+
1088
+ All three files pre-enable network access via env vars, so there's no interactive Settings-UI step.
1089
+ Retrieve the auto-generated token from the container's boot log, then bookmark the token-bearing URL:
1090
+
1091
+ ```bash
1092
+ docker compose -f <compose-file> logs kobo | grep Token
1093
+ ```
1094
+
1095
+ Open `http://<host>/?token=<token>` (exact host depends on the file — see the walkthroughs below).
1096
+ The token is stored in the browser's `localStorage`, so you only paste it once per browser.
1097
+
1098
+ ### Quick local test — `docker-compose.local.yml`
1099
+
1100
+ ```bash
1101
+ docker build -t kobo:local . # once
1102
+ docker compose -f docker-compose.local.yml up -d
1103
+ docker compose -f docker-compose.local.yml logs kobo | grep Token
1104
+ ```
1105
+
1106
+ Open `http://localhost:3000/?token=<token from above>`.
1107
+
1108
+ By default the file bind-mounts `./kobo-projects` (next to it) at `/projects` — put a real git repo
1109
+ in there (or set `KOBO_PROJECTS_DIR` to point elsewhere) before creating a workspace. When you get
1110
+ to the project-path picker in the Create-workspace UI, remember it's browsing the **container's**
1111
+ filesystem, not your host's — your repo shows up under `/projects/<name>`, not wherever it lives on
1112
+ your machine. Kōbō attempts to fetch the source branch from `origin`, then falls
1113
+ back to an existing local source branch if fetch fails. A local-only repository
1114
+ therefore works once it has a commit and the selected source branch; remote
1115
+ push and PR actions still require a configured remote.
1116
+
1117
+ Stop with `docker compose -f docker-compose.local.yml down` (add `-v` to also drop the named volume
1118
+ and lose all Kōbō state).
1119
+
1120
+ ### Rehearsing the reverse-proxy stack — `docker-compose.local-traefik.yml`
1121
+
1122
+ ```bash
1123
+ docker build -t kobo:local . # once, if not already built
1124
+ docker compose -f docker-compose.local-traefik.yml up -d
1125
+ docker compose -f docker-compose.local-traefik.yml logs kobo | grep Token
1126
+ ```
1127
+
1128
+ Open `http://kobo.localhost/?token=<token from above>` — `.localhost` resolves to `127.0.0.1` on
1129
+ most OSes without touching `/etc/hosts`. This exercises the exact routing/auth/mount setup used by
1130
+ `docker-compose.example.yml`, minus a real domain and TLS certificate.
1131
+
1132
+ **Traefik dashboard**: `http://traefik.localhost`, protected by HTTP basic auth (`admin` /
1133
+ `kobo-local` — a local-only throwaway credential). Useful for debugging router/service discovery
1134
+ (`/api/http/routers` shows exactly what Traefik sees). To change the password, generate a new hash
1135
+ and swap it into the `dashboard-auth` middleware label:
1136
+
1137
+ ```bash
1138
+ openssl passwd -apr1 '<new-password>'
1139
+ ```
1140
+
1141
+ The hash contains literal `$` characters, which Compose's own variable interpolation would try to
1142
+ expand — every `$` in the label value must be doubled to `$$` (already done in the shipped file).
1143
+
1144
+ This file intentionally shares its named volume (`kobo-test-data`) with `docker-compose.local.yml`,
1145
+ so a workspace created under one is visible under the other. If you want isolated state instead,
1146
+ rename the volume in one of the two files.
1147
+
1148
+ Stop with `docker compose -f docker-compose.local-traefik.yml down`.
1149
+
1150
+ ### Real deployment — `docker-compose.example.yml`
1151
+
1152
+ Before using it:
1153
+
1154
+ 1. Replace `image: kobo:local` with a real registry tag once one is published, or `docker build -t kobo:local .` from this repo on the target machine.
1155
+ 2. Replace `kobo.example.com` with your real domain, and `you@example.com` with a real email (Let's Encrypt account).
1156
+ 3. Populate a `.env` file next to the compose file with `GH_TOKEN` / `GITLAB_TOKEN` if you use the forge integration.
1157
+ 4. Point every bind mount (`~/.ssh`, `~/.gitconfig`, `~/.claude.json`, `~/.claude`, `~/.codex`) at real paths on the host — see [Volumes](#volumes) for what each one is for, in particular the "`~/.claude.json` must already exist as a file" gotcha.
1158
+ 5. Decide whether you need the Docker-socket passthrough (dev-server panel). If not, delete that volume line — see the DANGER note in [Volumes](#volumes).
1159
+ 6. Network access + behind-a-reverse-proxy are already pre-enabled via the `KOBO_NETWORK_ACCESS_*` env vars in the file — no manual bootstrap step.
1160
+
1161
+ Bring it up, grab the token, bookmark the URL:
1162
+
1163
+ ```bash
1164
+ docker compose -f docker-compose.example.yml up -d
1165
+ docker compose -f docker-compose.example.yml logs kobo | grep Token
1166
+ ```
1167
+
1168
+ Open `https://kobo.example.com/?token=<token>`. SSH access: `ssh -p 2222 root@<host>` (key-based
1169
+ only). See [Ports](#ports) and [Volumes](#volumes) above for the full rationale on the Docker socket
1170
+ and SSH tradeoffs before deploying this to a real machine.
1171
+
1172
+ ### Troubleshooting
1173
+
1174
+ - **`fatal: detected dubious ownership in repository at '/projects/<name>'`** — only happens if you
1175
+ build your own image without the `git config --system --add safe.directory '*'` line described in
1176
+ [The image](#the-image) above; the shipped image already has it.
1177
+ - **A workspace's `git fetch origin` fails with "'origin' does not appear to be a git repository"**
1178
+ — the project under `/projects/<name>` has no `origin` remote (e.g. a plain `git init` test repo).
1179
+ Kōbō falls back to the selected local source branch when it exists. If neither
1180
+ source is available, choose an existing branch or configure a reachable remote.
1181
+ For throwaway testing, a local bare repo works fine:
1182
+ ```bash
1183
+ git init -q --bare /projects/my-repo-remote.git
1184
+ git -C /projects/my-repo push /projects/my-repo-remote.git HEAD
1185
+ git -C /projects/my-repo remote add origin /projects/my-repo-remote.git
1186
+ ```
1187
+ - **The project-path picker in the Create-workspace UI shows unfamiliar folders** — it browses the
1188
+ **container's** filesystem, not your host's. Your repos show up under `/projects/<name>`, not
1189
+ wherever they live on your machine.
1190
+ - **Container stays `unhealthy` / Traefik never routes to it with `KOBO_NETWORK_ACCESS_BEHIND_PROXY=true`**
1191
+ — check `docker inspect <container> --format '{{json .State.Health}}'`. This should not happen on
1192
+ a current image (`/api/health` is exempt from the token gate, see [The image](#the-image)); if you
1193
+ see a 401 in the health log on a custom-built image, you're missing that exemption.
1194
+ - **`docker compose logs kobo | grep Token` returns nothing** — network access wasn't enabled at
1195
+ boot. Confirm `KOBO_NETWORK_ACCESS_ENABLED=true` is actually set in the `environment:` block for
1196
+ the `kobo` service.
1197
+
1198
+ ## Agent runtimes
1199
+
1200
+ Engines are selected per workspace at creation time. Both share the same UI surface (chat feed, task panel, permission modes, sub-agent panel, quota footer, auto-loop).
1201
+
1202
+ ### Claude Code
1203
+
1204
+ Authenticate once:
1205
+
1206
+ ```bash
1207
+ claude /login
1208
+ ```
1209
+
1210
+ Kōbō talks to the embedded [`@anthropic-ai/claude-agent-sdk`](https://github.com/anthropics/claude-agent-sdk-typescript), which reuses the same login. The `claude` CLI is **not** required at runtime; you only need it for `/login`, `mcp add`, and other one-off setup commands. As a fallback you can export `ANTHROPIC_API_KEY` instead.
1211
+
1212
+ The model selector is populated from [`src/shared/models.ts`](./src/shared/models.ts), also used by backend validation. This checkout includes Fable 5.1, Mythos 5, Opus 5, Opus 4.8, Sonnet 5 and older variants. Pick the default in **Settings → Agents**. Catalogue entries describe what Kōbō offers; provider access still depends on your account. `auto` leaves model selection to the runtime.
1213
+
1214
+ ### OpenAI Codex
1215
+
1216
+ > Experimental. The Claude Code engine remains the primary, battle-tested path.
1217
+
1218
+ Authenticate either way:
1219
+
1220
+ ```bash
1221
+ codex login # writes ~/.codex/auth.json
1222
+ # OR
1223
+ OPENAI_API_KEY=sk-… npx @loicngr/kobo
1224
+ ```
1225
+
1226
+ Kōbō spawns a long-lived `codex app-server` subprocess per workspace and bridges its JSON-RPC stream to the same UI. The binary is supplied by the direct `@openai/codex` dependency, so no separate runtime install is required.
1227
+
1228
+ When a Codex session is already working, **Send now** sends a queued chat message through the app-server's `turn/steer` endpoint. The message stays queued until Codex acknowledges the steer request; a failed request is restored to the queue instead of being lost.
1229
+
1230
+ The selector reads [`src/shared/codex-models.ts`](./src/shared/codex-models.ts). This checkout lists GPT-6 Astra, GPT-5.6 Sol/Terra/Luna, GPT-5.5 and older variants; availability depends on your account and authentication. Kōbō exposes effort values `auto`, `none`, `low`, `medium`, `high`, `xhigh`, and `max`, as declared in the engine's capabilities. The runtime must support the selected model/effort combination. Both selectors switch when you change the engine.
1231
+
1232
+ ### Permission modes
1233
+
1234
+ Claude Code uses SDK permission modes; Codex uses sandbox and approval flags. These controls differ by engine. A Git worktree itself is not a security boundary.
1235
+
1236
+ **Claude Code** (controlled via SDK options):
1237
+
1238
+ | Kōbō mode | Effect |
1239
+ |---|---|
1240
+ | `plan` | SDK `plan` permission mode for planning without edits; not an OS sandbox. |
1241
+ | `bypass` | SDK `bypassPermissions`: approvals bypassed, without an OS boundary at the worktree. |
1242
+ | `strict` | SDK `acceptEdits`: file edits accepted; other tools follow approval rules. |
1243
+ | `interactive` | SDK `default`: unapproved operations can request user permission. |
1244
+
1245
+ **OpenAI Codex** (controlled via `sandbox` + `approvalPolicy` + `collaborationMode`):
1246
+
1247
+ | Kōbō mode | Codex sandbox | Codex approval | `collaborationMode` |
1248
+ |---|---|---|---|
1249
+ | `plan` | `read-only` | `never` | `plan` (enables `request_user_input`) |
1250
+ | `bypass` | `danger-full-access` | `never` | `default` |
1251
+ | `strict` | `workspace-write` | `on-request` | `default` |
1252
+ | `interactive` | `workspace-write` | `unless-trusted` | `default` |
1253
+
1254
+ Interactive Q&A (`request_user_input`) is only available in `plan` for Codex, a constraint of Codex itself. In `bypass`, `strict`, and `interactive`, Codex can still ask questions as normal chat text and you can respond in the chat, but it cannot open Kōbō's structured question panel.
1255
+ Linked-worktree Git operations may need access to the repository's shared `.git` directory outside the worktree. Depending on the engine and sandbox, this can require approval or a permission change explicitly chosen by the user. Kōbō does not automatically promote Plan to Bypass.
1256
+
1257
+ When an approval panel appears, **Allow this turn** permits the same tool for
1258
+ the rest of the current session. **Allow this operation** stores a rule for the
1259
+ same workspace, engine, tool, and exact operation payload; **Allow this tool**
1260
+ stores a rule for every invocation of that tool in the workspace. Persistent
1261
+ rules survive restarts and can be removed from the workspace's permission UI.
1262
+
1263
+ ## Dev server
1264
+
1265
+ Each workspace can run its own dev server. The **Tools** panel shows its status, a clickable URL, the live container count, and a logs button. That works only when your project follows a small contract. This section documents the contract so the panel lights up for *your* project, not just Docker-instance projects.
1266
+
1267
+ ### What the panel actually does
1268
+
1269
+ Kōbō treats the dev server in two independent layers:
1270
+
1271
+ | Layer | Driven by | Works for any project? |
1272
+ |---|---|---|
1273
+ | **Start / Stop** | The shell commands you configure | ✅ Yes, any shell-startable process |
1274
+ | **Status badge, URL, container count, logs** | The `.container/instances/*.env` convention + Docker | ❌ No, requires the convention below |
1275
+
1276
+ Configured processes can be started and stopped. Docker status, a URL and logs
1277
+ require a matching instance file and running containers. Without an instance,
1278
+ runtime detection returns `unknown`; the HTTP route may show the saved process
1279
+ status instead, but has no URL or container logs. Without a start command, the
1280
+ route returns `not_configured`.
1281
+
1282
+ > **Known limitation: Docker-coupled, for now.** Status detection, the URL, the container count, and the logs viewer are currently hard-wired to Docker and to the `.container/instances/*.env` convention described below. This is a deliberate, temporary state: it works today for Docker-instance projects, and the convention is documented here so you can adopt it. An **evolution is planned** to abstract this layer, making the URL/port configurable and the running-detection engine-agnostic (e.g. a TCP probe) so the panel works for any project, Docker or not, without imposing this file layout. Until that lands, follow the contract below.
1283
+
1284
+ ### 1. Configure start / stop commands
1285
+
1286
+ In **Settings → (project) → Dev server**, set:
1287
+
1288
+ | Field | Purpose |
1289
+ |---|---|
1290
+ | `devServer.startCommand` | Shell command (or multi-line script) run to start the server. **Required.** |
1291
+ | `devServer.stopCommand` | Shell command run to stop it. Optional. |
1292
+
1293
+ How Kōbō runs them:
1294
+
1295
+ - `startCommand` runs as `bash -c "<command>"`, detached, with the working directory set to the workspace **worktree** (falling back to the project root).
1296
+ - It runs with two extra environment variables injected:
1297
+ - `INSTANCE`: the workspace branch name, sanitized: lowercased, with `/` and `_` replaced by `-`. Example: branch `feature/My_Thing` → `INSTANCE=feature-my-thing`.
1298
+ - `DEV_DOCKER_NO_FOLLOW=1`: a hint that a Docker setup script should exit after starting its containers instead of following logs. A tracked start process reports `starting` when an instance file exists but no matching container is running yet; once containers appear, Docker status takes precedence.
1299
+ - `stopCommand` runs with `INSTANCE` and `PROJECT_NAME` in the environment. After it, Kōbō also unconditionally runs `docker compose -p <PROJECT_NAME> down` if a `PROJECT_NAME` was resolved, so a Docker project does not strictly need a `stopCommand` at all.
1300
+
1301
+ ### 2. Make the status panel light up — the `.container/instances` contract
1302
+
1303
+ For the **Running** badge, the URL, the container count, and the logs button to appear, your **start command must create an instance file**:
1304
+
1305
+ ```
1306
+ <project-root>/.container/instances/<any-name>.env
1307
+ ```
1308
+
1309
+ Notes:
1310
+
1311
+ - The path is relative to the **project root** Kōbō knows (the main repo path), not the per-workspace worktree.
1312
+ - The file name is free; Kōbō scans *every* `*.env` file in that directory.
1313
+ - The file must contain these keys (`KEY=value`, `#` comments and surrounding quotes are tolerated):
1314
+
1315
+ | Key | Used for |
1316
+ |---|---|
1317
+ | `INSTANCE_NAME` | **Matching.** Must equal the sanitized branch name, i.e. the value of the `INSTANCE` env var Kōbō passed you. This is how Kōbō finds *this workspace's* instance among all the `.env` files. |
1318
+ | `HTTP_PORT` | The URL. Kōbō shows `http://localhost:<HTTP_PORT>`. |
1319
+ | `PROJECT_NAME` | **Running detection.** Must equal the Docker Compose project label `com.docker.compose.project`. One or more running containers with that exact label → `running`; custom container names are supported. |
1320
+
1321
+ The simplest correct start script therefore: read `$INSTANCE`, start your containers, then write the `.env` file with `INSTANCE_NAME=$INSTANCE`.
1322
+
1323
+ ### 3. Running detection and logs
1324
+
1325
+ - **Status**: `running` if at least one container has the exact Compose project label; `starting` if an instance file exists and the start process remains tracked but no container is running; `stopped` otherwise; `unknown` if no instance file matched.
1326
+ - **Logs**: the logs button runs `docker logs` on those same matched containers.
1327
+
1328
+ Both are **Docker-only**. A plain Node process (`npm run dev`) does not produce a
1329
+ running Docker status or container logs. Writing an instance file alone is not
1330
+ enough to expose a URL: status responses only include the URL when matching
1331
+ containers are running. Direct processes can still be started and stopped.
1332
+
1333
+ ### Minimal setups
1334
+
1335
+ **Docker project**: full panel support. Follow the contract: the start command writes the `.env` under the main project root and starts Compose with `-p "$PROJECT_NAME"` so its containers carry the matching label.
1336
+
1337
+ **Non-Docker project (e.g. Vite/Node)**: configure a foreground command such as
1338
+ `npm run dev`. Kōbō tracks its process group for shutdown, including children that
1339
+ outlive the shell. Open the project's URL yourself; the Docker status, URL and
1340
+ logs contract does not provide general process health monitoring.
1341
+
1342
+ **No instance file at all**: Start/Stop run your commands; the panel uses the
1343
+ saved status when available, otherwise `unknown`, with no URL or Docker logs.
1344
+
1345
+ ### Example: the full convention end-to-end
1346
+
1347
+ The reference implementation is the `sekur` project. Its Kōbō `startCommand` is effectively `make dev-docker`, where the `Makefile` calls `./sh/dev-docker.sh $(INSTANCE)` and `$(INSTANCE)` reads the `INSTANCE` env var Kōbō injects. The script:
1348
+
1349
+ 1. Picks free ports starting at `8700`.
1350
+ 2. Names the Docker Compose project `sekur-<instance>` and runs `docker compose -p sekur-<instance> up -d`.
1351
+ 3. Writes `<project-root>/.container/instances/sekur-<instance>.env`:
1352
+
1353
+ ```env
1354
+ # Instance: feature-my-thing
1355
+ PROJECT_NAME=sekur-feature-my-thing
1356
+ INSTANCE_NAME=feature-my-thing
1357
+ HTTP_PORT=8700
1358
+ HTTPS_PORT=8701
1359
+ DB_PORT=8702
1360
+ ```
1361
+
1362
+ 4. Honors `DEV_DOCKER_NO_FOLLOW=1` by exiting instead of tailing logs.
1363
+
1364
+ Kōbō then resolves the workspace (branch `feature/my-thing` → sanitized `feature-my-thing` → matches `INSTANCE_NAME`), shows `http://localhost:8700`, and detects the `sekur-feature-my-thing-*` containers as `running`.
1365
+
1366
+ ### A minimal, copy-pasteable start script
1367
+
1368
+ Drop this in your project as `sh/dev-docker.sh`, make it executable (`chmod +x`), and set the Kōbō `startCommand` to `bash sh/dev-docker.sh`. It is a trimmed-down version of the `sekur` script, keeping only what the convention requires.
1369
+
1370
+ ```bash
1371
+ #!/usr/bin/env bash
1372
+ # Starts an isolated Docker instance for the current Kōbō workspace and
1373
+ # writes the .container/instances/<project>.env file Kōbō reads for status.
1374
+ set -e
1375
+
1376
+ # Kōbō injects INSTANCE (sanitized branch name). Fall back to the git branch
1377
+ # so the script also works when run by hand.
1378
+ INSTANCE_NAME="${INSTANCE:-$(git branch --show-current | tr '/_' '-' | tr '[:upper:]' '[:lower:]')}"
1379
+
1380
+ # Docker Compose project name — Compose writes it to each container's
1381
+ # com.docker.compose.project label, which Kōbō matches exactly.
1382
+ APP_NAME="${APP_NAME:-myapp}"
1383
+ PROJECT_NAME="${APP_NAME}-${INSTANCE_NAME}"
1384
+
1385
+ # Pick the first free HTTP port starting at 8700 (10-port stride per instance).
1386
+ HTTP_PORT=8700
1387
+ while nc -zw1 localhost "$HTTP_PORT" 2>/dev/null; do
1388
+ HTTP_PORT=$((HTTP_PORT + 10))
1389
+ done
1390
+
1391
+ # Resolve the main project root even when this script runs in a linked worktree.
1392
+ COMMON_GIT_DIR="$(git rev-parse --path-format=absolute --git-common-dir)"
1393
+ PROJECT_ROOT="$(dirname "$COMMON_GIT_DIR")"
1394
+ INSTANCE_DIR="${PROJECT_ROOT}/.container/instances"
1395
+ mkdir -p "$INSTANCE_DIR"
1396
+ cat > "${INSTANCE_DIR}/${PROJECT_NAME}.env" <<EOF
1397
+ # Instance: ${INSTANCE_NAME}
1398
+ PROJECT_NAME=${PROJECT_NAME}
1399
+ INSTANCE_NAME=${INSTANCE_NAME}
1400
+ HTTP_PORT=${HTTP_PORT}
1401
+ EOF
1402
+
1403
+ # Start the containers. `-p "$PROJECT_NAME"` sets the matching Compose label;
1404
+ # export HTTP_PORT so compose.yaml can bind it.
1405
+ HTTP_PORT="$HTTP_PORT" docker compose -p "$PROJECT_NAME" up -d
1406
+
1407
+ echo "Started ${PROJECT_NAME} on http://localhost:${HTTP_PORT}"
1408
+
1409
+ # Kōbō runs this detached and sets DEV_DOCKER_NO_FOLLOW=1 — exit instead of
1410
+ # leaving an unnecessary log-following process running.
1411
+ if [ "${DEV_DOCKER_NO_FOLLOW:-0}" = "1" ]; then
1412
+ exit 0
1413
+ fi
1414
+ docker compose -p "$PROJECT_NAME" logs -f
1415
+ ```
1416
+
1417
+ Matching `stopCommand` (optional; Kōbō also runs `docker compose -p <PROJECT_NAME> down` itself):
1418
+
1419
+ ```bash
1420
+ docker compose -p "${APP_NAME:-myapp}-${INSTANCE}" down
1421
+ ```
1422
+
1423
+ Adapt the port stride, the `APP_NAME`, and any extra `*_PORT` keys to your stack. The three keys Kōbō reads stay fixed: `INSTANCE_NAME`, `PROJECT_NAME`, `HTTP_PORT`.
1424
+
1425
+ ### Troubleshooting
1426
+
1427
+ | Symptom | Likely cause |
1428
+ |---|---|
1429
+ | Status stuck on `starting` | The start process is still tracked but no matching Docker container is running. Check build/start errors and the Compose project label. |
1430
+ | Status `unknown`, no URL | No `.env` in `.container/instances/`, or its `INSTANCE_NAME` doesn't match the sanitized branch. Check `INSTANCE_NAME` equals `$INSTANCE`. |
1431
+ | Badge says `stopped`, no URL | No running Docker container with the exact Compose project label (normal for non-Docker projects). |
1432
+ | Logs button empty | Same cause: `docker logs` has no matching container. |
1433
+ | Wrong port in URL | `HTTP_PORT` in the `.env` doesn't match the port the server actually bound. |
1434
+
1435
+ ## Forge integration
1436
+
1437
+ Kōbō can open pull requests (GitHub or Bitbucket) or merge requests (GitLab) directly from the Git panel. The **forge** setting controls which service is used for a given project.
1438
+
1439
+ ### Modes
1440
+
1441
+ | Value | Behaviour |
1442
+ |---|---|
1443
+ | `auto` (default) | Kōbō reads the `origin` remote URL: host contains `github.com` → GitHub; host contains `gitlab` → GitLab; host contains `bitbucket` → Bitbucket Community; anything else → `none`. |
1444
+ | `github` | Always use GitHub, regardless of the remote URL. |
1445
+ | `gitlab` | Always use GitLab, regardless of the remote URL. |
1446
+ | `bitbucket-community` | Always use Bitbucket Community (`bkt`), regardless of the remote URL. |
1447
+ | `none` | PR/MR features are disabled. The PR block is hidden in the Git panel. |
1448
+
1449
+ Set the per-project value in **Settings → (project) → Forge**.
1450
+
1451
+ ### Auto-detection
1452
+
1453
+ `auto` is designed to work without any configuration for the common cases:
1454
+
1455
+ - GitHub.com repositories: the remote URL contains `github.com` → GitHub detected automatically.
1456
+ - GitLab.com repositories: the remote URL contains `gitlab` → GitLab detected automatically.
1457
+ - Bitbucket Cloud repositories: the remote URL contains `bitbucket.org` → Bitbucket Community detected automatically.
1458
+ - Self-hosted GitLab on a custom hostname (e.g. `git.mycompany.com`): the hostname does not contain `gitlab`, so `auto` falls back to `none`. Set `forge: gitlab` explicitly for these projects.
1459
+
1460
+ ### CLI prerequisites
1461
+
1462
+ Kōbō delegates PR/MR operations to the forge CLI and ships no credentials of its own. You must install and authenticate the relevant CLI before the PR actions become available:
1463
+
1464
+ **GitHub:**
1465
+
1466
+ ```bash
1467
+ # Install: https://cli.github.com
1468
+ gh auth login
1469
+ ```
1470
+
1471
+ **GitLab (GitLab.com):**
1472
+
1473
+ ```bash
1474
+ # Install: https://gitlab.com/gitlab-org/cli
1475
+ glab auth login
1476
+ ```
1477
+
1478
+ **GitLab (self-hosted):**
1479
+
1480
+ ```bash
1481
+ glab auth login --hostname git.mycompany.com
1482
+ ```
1483
+
1484
+ **Bitbucket Cloud:**
1485
+
1486
+ Install [`bkt`](https://github.com/avivsinai/bitbucket-cli), then open **Settings → Forge** and enter your Atlassian email and a Bitbucket-scoped API token. Kōbō keeps this token locally, excludes it from settings exports, and passes it to `bkt` for Bitbucket workspaces and their agents. No `bkt auth login` context or interactive keyring prompt is required.
1487
+
1488
+ Kōbō only invokes the CLI binary. If the CLI is missing from `PATH` or not authenticated, PR/MR actions are disabled with an explanatory tooltip in the Git panel. Kōbō keeps working; only the PR/MR features are affected.
1489
+
1490
+ ### UI behaviour
1491
+
1492
+ - The Git panel label adapts per forge: **Create PR** on GitHub and Bitbucket, **Create MR** on GitLab.
1493
+ - When `forge` is `none` (or auto-resolves to `none`), the PR/MR block is hidden entirely: no errors, no placeholders.
1494
+ - When the CLI is absent or unauthenticated, the button is disabled with a tooltip explaining the issue instead of surfacing a raw error.
1495
+
1496
+ ### Creating a workspace from a PR/MR
1497
+
1498
+ The **Create workspace** page's **Import from a PR** button (Project Git section) opens a picker listing open pull/merge requests on the resolved forge, with pagination and quick filters (all / mine / review-requested) plus a text search.
1499
+
1500
+ - Requires a forge that supports listing PRs/MRs (GitHub, GitLab, or Bitbucket Community — see [CLI prerequisites](#cli-prerequisites) above); the button is disabled with a tooltip when the project's resolved forge is `none` or the CLI is unauthenticated. Forks are listed but not selectable.
1501
+ - Selecting a PR/MR opens a guided stepper that diagnoses the local state (fetches the branch first) and walks through every conflict it finds one step at a time: an existing Kōbō workspace already tracking the branch (open it, or continue past an archived one), a worktree that already exists at the target path (attach to it or pick another path), local branch divergence from the remote (fast-forward, rebase your local commits on top, discard them, or keep as-is), uncommitted changes, and an in-progress rebase/merge/cherry-pick to resolve or cancel first. A rebase conflict leaves the worktree in that state for you (or the agent, via **Resolve with agent** in the Git panel) to finish.
1502
+ - Once resolved, the worktree-mode toggle, project, and branch fields lock to the imported PR/MR (an **Unlock** control reverts to the normal "new worktree" flow). The PR/MR URL is stored on the workspace and its title/description can seed the mission context. Your own **Description** text, if you've written one, is never overwritten.
1503
+ - **Advanced options → Skip setup script** is enabled by default for a PR/MR import. Turn it off to run the configured setup script before starting the agent. Ordinary attachment of an existing worktree outside PR/MR import still skips setup automatically.
1504
+
1505
+ ## Notion integration
1506
+
1507
+ Pulls the body, title, and checklists of a Notion page to seed a workspace's tasks and acceptance criteria.
1508
+
1509
+ Enable the integration in **Settings → Notion**. Disabling it preserves its
1510
+ credentials and existing workspace links but hides Notion import and automation
1511
+ actions until it is enabled again.
1512
+
1513
+ Use **Test connection** in the same Settings tab to start the configured MCP
1514
+ server, initialise it, and make a lightweight authenticated API request. The
1515
+ result shows the elapsed time or an actionable configuration error.
1516
+
1517
+ ### Setup
1518
+
1519
+ 1. Visit <https://www.notion.so/profile/integrations> and create an internal integration.
1520
+ 2. Grant it at least *Read content*.
1521
+ 3. Copy the token (format `ntn_…` or `secret_…`).
1522
+ 4. Open the target Notion page → **…** → **Connections** → **Add connection** → select the integration. Kōbō can only read pages explicitly shared with the integration.
1523
+
1524
+ ### Token sources
1525
+
1526
+ Resolved in this order:
1527
+
1528
+ 1. `NOTION_API_TOKEN` env var
1529
+ 2. `NOTION_TOKEN` env var
1530
+ 3. The direct Notion connection saved in Settings (`NOTION_TOKEN` or `NOTION_API_TOKEN`)
1531
+ 4. If no direct connection exists, `~/.claude.json` → `mcpServers.notion.env.NOTION_TOKEN` (or `NOTION_API_TOKEN`)
1532
+
1533
+ The default config entry is named exactly `notion` and must be enabled.
1534
+ `notionMcpKey` selects another exact key. An existing `OPENAPI_MCP_HEADERS` value
1535
+ from the environment or selected entry is preserved; otherwise Kōbō constructs
1536
+ the authorization headers from the resolved token.
1537
+
1538
+ For a direct connection, enter command `npx`, arguments `["-y", "@notionhq/notion-mcp-server"]` and an environment object containing your `NOTION_TOKEN`. No Claude installation is required.
1539
+
1540
+ The legacy shared-Claude configuration remains supported:
1541
+
1542
+ ```bash
1543
+ claude mcp add notion -s user -e NOTION_TOKEN=ntn_your_token -- npx -y @notionhq/notion-mcp-server
1544
+ ```
1545
+
1546
+ ### Overriding the MCP command
1547
+
1548
+ The command and arguments come from `NOTION_MCP_COMMAND` / `NOTION_MCP_ARGS`,
1549
+ then the direct saved connection or selected Claude MCP entry, then the bundled `npx -y
1550
+ @notionhq/notion-mcp-server` defaults. Pin a version or use a fork:
1551
+
1552
+ ```bash
1553
+ NOTION_MCP_COMMAND=node \
1554
+ NOTION_MCP_ARGS="./my-fork/dist/server.js" \
1555
+ npx @loicngr/kobo
1556
+ ```
1557
+
1558
+ ### Auto-assign on workspace creation
1559
+
1560
+ When `notionAssigneeProperty` is set, Kōbō updates the named People property on the imported page to `notionUserId`, but only if the property has no assignee yet. Both fields are global settings; leave blank to disable.
1561
+
1562
+ ### Status field updates
1563
+
1564
+ When `notionStatusProperty` is set, Kōbō flips it to `notionInProgressStatus` as soon as the workspace starts work. Useful for keeping a Notion kanban in sync.
1565
+
1566
+ ## Sentry integration
1567
+
1568
+ Turns a Sentry issue URL into a "fix workspace": Kōbō extracts the stacktrace, tags, and offending spans, writes them to `.ai/thoughts/SENTRY-<id>.md`, and primes the agent with a TDD fix workflow plus live access to the Sentry MCP tools.
1569
+
1570
+ Enable the integration in **Settings → Sentry**. Disabling it preserves its
1571
+ configuration and existing workspace links but hides Sentry import and
1572
+ automation actions until it is enabled again.
1573
+
1574
+ Use **Test connection** in the same Settings tab to initialise the configured
1575
+ MCP server and run its identity check. The result shows the elapsed time or an
1576
+ actionable configuration error.
1577
+
1578
+ ### Setup
1579
+
1580
+ 1. Generate a Sentry auth token with at least `project:read`, `event:read`, `org:read`. User tokens (format `sntryu_…`) are simplest for personal use.
1581
+ 2. Register the Sentry MCP in Claude Code:
1582
+
1583
+ ```bash
1584
+ claude mcp add sentry -s user \
1585
+ -e SENTRY_ACCESS_TOKEN=sntryu_your_token \
1586
+ -e SENTRY_HOST=your-org.sentry.io \
1587
+ -- npx -y @sentry/mcp-server@latest
1588
+ ```
1589
+
1590
+ For self-hosted Sentry, set `SENTRY_HOST` to your hostname (e.g. `sentry.mycompany.com`).
1591
+
1592
+ Alternatively, save command `npx`, arguments `["-y", "@sentry/mcp-server"]` and the appropriate environment variables in **Settings → Sentry → Direct connection**. This works without a Claude installation. Direct credentials live in the private `KOBO_HOME/integrations.json` file (mode `0600`) and are excluded from settings exports. Saving replaces the complete connection; only configured/unconfigured status is returned to the browser. Clearing the direct connection restores legacy lookup.
1593
+
1594
+ ### How Kōbō picks the entry
1595
+
1596
+ Uses the direct saved connection when present. Otherwise reads `~/.claude.json` and uses the first entry under `mcpServers` whose key contains `sentry` (case-insensitive) **and is not disabled**. To force a specific entry, set the global `sentryMcpKey` setting to its exact key.
1597
+
1598
+ ### Usage
1599
+
1600
+ 1. Paste a Sentry issue URL in the workspace creation form (**Import Sentry**).
1601
+ 2. Submit. Kōbō extracts the issue, writes `.ai/thoughts/SENTRY-<shortId>.md`, creates a `Fix: <title>` task, and boots the agent with the fix workflow.
1602
+ 3. The Sentry Short-ID (e.g. `ACME-API-3`) becomes the branch prefix (`fix/ACME-API-3--slug`), which means commit messages like `Fixes ACME-API-3` will auto-close the issue on merge.
1603
+
1604
+ When Sentry is active, the workspace description field becomes optional; the extracted context is enough to start work.
1605
+
1606
+ ## Voice transcription
1607
+
1608
+ Local push-to-talk transcription using [`whisper.cpp`](https://github.com/ggml-org/whisper.cpp). Available in the workspace chat input and the workspace creation form.
1609
+
1610
+ ### Requirements
1611
+
1612
+ - `whisper-cli` from whisper.cpp
1613
+ - `ffmpeg`
1614
+ - `cmake` (only to build whisper.cpp from source)
1615
+ - At least one Whisper model downloaded via **Settings → Voice**
1616
+
1617
+ ### Install whisper.cpp (Linux / macOS)
1618
+
1619
+ ```bash
1620
+ git clone https://github.com/ggml-org/whisper.cpp.git
1621
+ cd whisper.cpp
1622
+ cmake -B build
1623
+ cmake --build build -j
1624
+ # Binary lands at build/bin/whisper-cli
1625
+ ```
1626
+
1627
+ Alternatively, grab a prebuilt archive from the [whisper.cpp releases](https://github.com/ggml-org/whisper.cpp/releases) and point Kōbō at the extracted binary.
1628
+
1629
+ ### Install whisper.cpp (Windows)
1630
+
1631
+ 1. Install CMake and Visual Studio Build Tools (C/C++).
1632
+ 2. Build whisper.cpp as above, or use a prebuilt archive.
1633
+ 3. Verify in PowerShell:
1634
+
1635
+ ```powershell
1636
+ where whisper-cli
1637
+ whisper-cli -h
1638
+ ```
1639
+
1640
+ ### Install ffmpeg
1641
+
1642
+ **Ubuntu / Debian:**
1643
+
1644
+ ```bash
1645
+ sudo apt update
1646
+ sudo apt install -y ffmpeg
1647
+ ```
1648
+
1649
+ **macOS:**
1650
+
1651
+ ```bash
1652
+ brew install ffmpeg
1653
+ ```
1654
+
1655
+ **Windows:**
1656
+
1657
+ ```powershell
1658
+ choco install ffmpeg # or: scoop install ffmpeg
1659
+ where ffmpeg
1660
+ ```
1661
+
1662
+ ### Configure in Kōbō
1663
+
1664
+ **Settings → Voice**:
1665
+
1666
+ - Enable voice transcription.
1667
+ - Optionally set the binary paths. Empty falls back to `WHISPER_CPP_COMMAND` env var, then `PATH`, for whisper-cli; `ffmpeg` is looked up in `PATH`.
1668
+ - Download a model from the **Whisper models** section (sizes range from ~75 MB for `tiny` to ~3 GB for `large-v3`). The storage directory is displayed at the top of the section. Downloads are chunked and stream to disk with a live progress bar, and can be cancelled mid-flight.
1669
+ - Pick the active model.
1670
+
1671
+ The Voice panel shows the runtime status for whisper-cli and ffmpeg so misconfigurations are visible immediately.
1672
+
1673
+ ### Model sizes (approximate)
1674
+
1675
+ | Model | Disk | Recommended temperature |
1676
+ |---|---|---|
1677
+ | `tiny` | ~75 MB | `0.1` |
1678
+ | `base` | ~142 MB | `0.1` |
1679
+ | `small` | ~466 MB | `0.2` |
1680
+ | `medium` | ~1.5 GB | `0.2` |
1681
+ | `large-v3` | ~3.1 GB | `0.2` |
1682
+
1683
+ ### Advanced parameters
1684
+
1685
+ - **Temperature** (`0`–`1`): decoding stability vs flexibility.
1686
+ - **Initial prompt**: biases recognition for custom vocabulary, names, or jargon.
1687
+ - **Translate to English**: translate non-English speech rather than transcribing it.
1688
+ - **Suppress non-speech tokens**: reduce non-speech artefacts in the output.
1689
+
1690
+ ## Skill suites
1691
+
1692
+ Kōbō's generated instructions cover reviews, auto-loop grooming, QA and brainstorming. Standard uses neutral instructions; external suites reference their skills by name. The **Settings → Skills → Skill suite** selector picks which ecosystem those prompts target. Seven options are supported:
1693
+
1694
+ | Suite | Auto-prompts cite | Best for |
1695
+ |---|---|---|
1696
+ | `standard` (fresh-install default) | Neutral, engine-independent instructions without named external skills | First use and repositories with their own conventions |
1697
+ | `superpowers` | `superpowers:*` skills (brainstorming, writing-plans, executing-plans, TDD, debugging, requesting-code-review, …) | Plugin-driven workflow inside Claude Code |
1698
+ | `gstack` | gstack slash commands (`/review`, `/ship`, `/qa`, `/browse`, `/design-review`, `/investigate`, `/codex`, …) | Concrete CLI-driven workflows, browser-based QA, opinionated ship/deploy loop |
1699
+ | `superpowers+gstack` | Both, specialised by intent (e.g. `/review` for tactical bug-hunting, `superpowers:requesting-code-review` for principles-level critique) | Users who install both suites and want each used for what it does best |
1700
+ | `ecc` | ECC review, testing, debugging and verification skills referenced by the bundled prompts | Users with ECC skills available to their engine |
1701
+ | `superpowers+gstack+ecc` | Combined instructions for the three installed suites | Users who already maintain all three ecosystems |
1702
+ | `custom` | Whatever you write in the `custom*` fields (Settings → Skills → Custom prompts) | Air-gapped, suite-agnostic, or non-standard setups |
1703
+
1704
+ Switching the selector only changes the prompt text Kōbō emits; it does **not** install or remove anything. Pick whichever matches the skills you have available in your Claude Code / Codex environment.
1705
+
1706
+ ### standard
1707
+
1708
+ The fresh-install default. No external skill package is required. Agents receive
1709
+ neutral instructions and can still follow repository conventions and explicit user
1710
+ requests. Standard does not remove skills already available in the agent runtime.
1711
+ Use the dedicated Skills step in the Settings guided tour for an in-app explanation.
1712
+
1713
+ ### superpowers
1714
+
1715
+ An optional open-source Claude Code plugin covering brainstorming, writing-plans, executing-plans, TDD, systematic-debugging, requesting / receiving-code-review, dispatching-parallel-agents, etc. Kōbō also surfaces specs from `docs/superpowers/specs/` and plans from `docs/superpowers/plans/` in the right-drawer Documents browser.
1716
+
1717
+ Install inside Claude Code:
1718
+
1719
+ ```bash
1720
+ /plugin marketplace add obra/superpowers-marketplace
1721
+ /plugin install superpowers@superpowers-marketplace
1722
+ ```
1723
+
1724
+ Repo: <https://github.com/obra/superpowers>.
1725
+
1726
+ ### gstack
1727
+
1728
+ CLI-driven skill suite with a different philosophy: concrete slash commands for navigation (`/browse`), QA (`/qa`, `/qa-only`), design (`/design-review`, `/plan-design-review`, `/design-shotgun`, `/design-html`), strategy (`/office-hours`, `/plan-ceo-review`, `/plan-eng-review`), debugging (`/investigate`, `/codex`), and the full ship loop (`/ship`, `/land-and-deploy`, `/canary`, `/document-release`). Voice triggers exist for many of the commands.
1729
+
1730
+ When this suite is active, Kōbō's review template tells the agent to invoke `/review`, the auto-loop review gate uses gstack's reviewer, and the QA prompt routes to `/browse` for real-browser smoke tests.
1731
+
1732
+ Install by following the instructions in the [gstack repo](https://github.com/garrytan/gstack), then switch the suite to `gstack` in **Settings → Skills**.
1733
+
1734
+ ### superpowers + gstack
1735
+
1736
+ When both suites are installed, set the selector to `superpowers+gstack`. Kōbō's prompts then cite each suite for what it does best: superpowers for the discipline (TDD loop, plan execution, parallel subagents), gstack for the tactical surface (real-browser QA, design audits, ship pipeline, codex second-opinion).
1737
+
1738
+ ### ECC and combined suites
1739
+
1740
+ Choose `ecc` when the ECC skills referenced by Kōbō are already available to the
1741
+ selected engine. Choose `superpowers+gstack+ecc` only when all three suites are
1742
+ available. Kōbō adapts its instructions but does not install, update or verify the
1743
+ availability of external suites. Suite selection never overrides workflow
1744
+ preferences, engine permissions or narrower instructions from the user.
1745
+
1746
+ ### custom
1747
+
1748
+ Lets you bypass the bundled prompts entirely and write your own. The five `custom*` fields under **Settings → Skills → Custom prompts** are seeded with neutral, suite-free defaults (the *AGNOSTIC* prompt strings in `src/shared/skill-suite-prompts.ts`) so you have a starting point. Useful for air-gapped setups, internal forks, or compliance environments where a third-party plugin is off-limits.
1749
+
1750
+ ## gbrain (companion MCP)
1751
+
1752
+ [`gbrain`](https://github.com/garrytan/gbrain) is a separate companion tool: a per-project knowledge graph and semantic search index, with an MCP server interface. It's **not** a Kōbō skill suite (it doesn't change the auto-generated prompts) but it plugs into Kōbō workspaces transparently via the standard Claude Code MCP config: Claude Code sessions can inherit MCP servers registered in `~/.claude.json`. Codex uses its own native MCP configuration; Kōbō does not copy arbitrary Claude MCP entries into Codex. The managed Notion/Sentry connections described above are supplied explicitly to both engines.
1753
+
1754
+ When gbrain is configured, the agent gets access to tools like:
1755
+
1756
+ - `search`, `query`, `resolve_slugs`: semantic + graph search over the project's indexed content
1757
+ - `get_page`, `put_page`, `delete_page`, `get_chunks`, `get_versions`, `revert_version`: page-level knowledge-graph CRUD
1758
+ - `add_link`, `remove_link`, `get_links`, `get_backlinks`, `traverse_graph`, `find_orphans`: link operations
1759
+ - `add_tag`, `remove_tag`, `get_tags`: tagging
1760
+ - `submit_job`, `list_jobs`, `get_job`, `get_job_progress`, `pause_job`, `resume_job`, `retry_job`, `replay_job`, `cancel_job`: async ingestion jobs
1761
+ - `add_timeline_entry`, `get_timeline`, `log_ingest`, `get_ingest_log`: per-project activity log
1762
+ - `file_list`, `file_upload`, `file_url`: file attachments
1763
+ - `sync_brain`, `get_health`, `get_stats`: administration
1764
+
1765
+ Install the CLI by following the instructions in the [gbrain repo](https://github.com/garrytan/gbrain), then initialise and register it with Claude Code:
1766
+
1767
+ ```bash
1768
+ # 1. Initialise a brain for this project (or use a shared remote brain)
1769
+ cd /path/to/your/project
1770
+ gbrain init
1771
+
1772
+ # 2. Register the MCP server in Claude Code (Kōbō reads the same ~/.claude.json)
1773
+ claude mcp add gbrain -s user -- gbrain mcp
1774
+ ```
1775
+
1776
+ Once registered for the chosen engine, Kōbō workspaces opened on this project can see the `gbrain__*` tools automatically. The `/sync-gbrain` gstack skill keeps the index fresh after large refactors. Repo: <https://github.com/garrytan/gbrain>.
1777
+
1778
+ Pairing notes:
1779
+
1780
+ - gbrain pairs especially well with the `gstack` skill suite (which calls `gbrain search` in several of its skills) and with auto-loop mode (the agent can query past work without re-reading the diff every iteration).
1781
+ - If you don't use gbrain, the agent falls back to `grep` / `Read` for code exploration; no setup is required.
1782
+
1783
+ ## Workspace sorting, split view and absence digest
1784
+
1785
+ The workspace drawer's sort menu offers last activity, creation date, name and
1786
+ attention priority, in either direction. The preference is saved in the browser.
1787
+ Sorting applies within the existing status/project groups, including archives;
1788
+ search relevance remains primary while searching. Last activity uses workspace
1789
+ updates and agent liveness events, never the read marker from opening a workspace.
1790
+
1791
+ **Split view:** choose “Open side by side” in the workspace header’s overflow menu, or “Open beside”
1792
+ in a workspace context menu. Each pane has its own workspace/session selection,
1793
+ conversation, Git/documents panels and terminal. Select workspaces with the two
1794
+ selectors and drag the divider (stacked vertically on small screens). The active
1795
+ pane is marked at the top and the recipient is named explicitly. The URL preserves
1796
+ both workspace choices on reload; the divider position is browser-local. Closing
1797
+ or navigating away checks unsaved files and unsent pane messages. Each pane runs
1798
+ an isolated same-origin client, so split mode uses two additional client instances
1799
+ and WebSocket connections. Browser/audio notifications stay with the host client.
1800
+
1801
+ **Since your last visit:** the entry at the bottom of the workspace drawer lists questions, approval requests,
1802
+ session endings, agent errors and PR changes since the browser's previous visit.
1803
+ The global **Settings → Notifications → Since your last visit** toggle
1804
+ (`activityDigestEnabled`, enabled by default) hides this entry and stops browser
1805
+ visit tracking and new server-side journal entries when switched off and saved.
1806
+ Existing history and browser checkpoints are preserved. Re-enabling resumes
1807
+ tracking; events occurring while disabled are not collected retroactively.
1808
+ Click an entry to open its workspace and, where available, its session or Git panel.
1809
+ “Mark displayed events as read” acknowledges only the loaded entries; older pages
1810
+ can be loaded before acknowledging. No automatic agent or workspace action runs.
1811
+
1812
+ Significant event metadata is retained server-side for 30 days, starting when this
1813
+ version is installed; conversation contents are not copied into the journal.
1814
+ Tracking the last visit is browser-local and only advances while the application
1815
+ is visible and no digest entries remain unread. A first visit establishes a
1816
+ checkpoint rather than presenting historical events. Deleted workspaces and
1817
+ expired activity do not appear. SQLite migration v41 creates the journal without
1818
+ changing existing workspace or conversation records.
1819
+
1820
+ **Unavailable actions:** Tools, Git and development-server controls explain common
1821
+ blocking conditions (archived/purged workspace, running agent or operation, missing
1822
+ configuration). Explanations remain visible and keyboard-accessible even when the
1823
+ button is disabled; setup configuration links directly to the Scripts settings.
1824
+
1825
+ The compact workspace header opens sessions from its title. **Configure** groups
1826
+ model, effort, permissions and auto-loop; the overflow menu holds split view,
1827
+ command palette, panel visibility and the guided tour. Stop remains visible while
1828
+ an agent runs. The active model badge describes a running session only.
1829
+
1830
+ Dismissed agent error banners stay dismissed after a reload in the same browser;
1831
+ the conversation history is preserved and new errors still appear. The Help menu
1832
+ can mark all current tour steps as seen at once, without hiding future additions.
1833
+ Queued messages are shared with split panes through the host client, so they
1834
+ remain visible and cancellable across view changes. Unsent drafts are guarded.
1835
+
1836
+ While the agent compacts its context, the workspace shows **Compacting** in its
1837
+ status indicators. Sending a chat message is temporarily disabled, including
1838
+ keyboard shortcuts; the draft remains editable and is preserved. Sending becomes
1839
+ available again when compaction finishes. This status survives page reloads, and
1840
+ **Stop** remains available if you need to stop the agent.
1841
+
1842
+
1843
+ ### Checkout and process recovery
1844
+
1845
+ PR checkout matches an existing workspace by project and branch. A branch already checked out in a worktree can be attached; creating another worktree on that same branch is disabled. Keeping uncommitted changes cannot be combined with a hard reset: choose stash or commit first, or keep the branch. Checkout diagnosis includes staged, unstaged and untracked contents, including nested Git repositories; changing them invalidates the pending decision. Ignored files are excluded. This check detects changes between diagnosis and resolution; it cannot lock out edits from an unrelated terminal at the instant Git mutates a file.
1846
+
1847
+ Source-branch changes and their rollback require the worktree to be on the workspace's expected branch. If an agent or development server cannot confirm its shutdown, destructive workspace operations stop with an error and leave the worktree in place. Retry after shutdown completes. Starts are refused while an incompatible workspace operation is in progress.
1848
+
1849
+ Stopping an agent also cancels a queued replacement and disables auto-loop for that workspace, including while waiting for a free agent slot. Enable auto-loop again to resume automatic iterations. Internal restarts and application shutdown preserve the auto-loop setting.
1850
+
1851
+ Development-server shutdown waits for the entire process group, including children that outlive their shell. Process-group verification requires the standard POSIX `ps` command. When a custom stop script succeeds but the generic Docker shutdown fails, Kōbō confirms shutdown only if an independent Docker query reports no running containers for the project.
1852
+
1853
+ Docker status and logs identify containers by their exact `com.docker.compose.project` label. Custom container names work; unlabeled containers are not attributed by a name prefix.
1854
+
1855
+ ### Automatic database backups
1856
+
1857
+ Kōbō checks for a daily backup at startup and every hour while running. A new WAL-safe snapshot is created when the latest backup is at least 24 hours old, keeping the seven most recent daily snapshots. Slow backups never overlap. Failures are logged and retried at the next check; graceful shutdown waits for the active backup before closing SQLite. Pre-migration snapshots have their own independent retention.
1858
+
1859
+ ## Conversation search index
1860
+
1861
+ Global search and workspace history search use the same index of complete messages.
1862
+ Streamed fragments are assembled before indexing, so a word can match across token
1863
+ boundaries. Queries are literal substrings, with Unicode normalization and
1864
+ case-insensitive matching; quotes and wildcard characters are treated literally.
1865
+
1866
+ On the first upgrade to database migration 42, Kōbō builds the index in the
1867
+ background. Search remains available with partial results and an indexing progress
1868
+ indicator. The original conversation events are preserved, and indexing resumes
1869
+ from its saved progress after a restart. Existing workspaces and agents remain
1870
+ usable during this operation. Indexing and search run in a dedicated worker;
1871
+ no additional credentials or configuration are required.
1872
+
1873
+ An unavailable index affects search only and is reported explicitly. Restarting
1874
+ Kōbō restarts the worker and resumes pending indexing. The derived index lives in
1875
+ the existing SQLite database and is covered by its normal backups.
1876
+ Temporary SQLite writer contention is retried automatically. Search results link
1877
+ to the fragment containing the match, including within long streamed responses
1878
+ and across Unicode normalization boundaries.
1879
+
1880
+ ## Audit compatibility notes
1881
+
1882
+ - Previously remembered Codex **exact-operation** file-edit approvals are inactive
1883
+ when their old fingerprints did not identify the actual changes. Approve the
1884
+ operation again to create a precise rule. Deliberately remembered whole-tool
1885
+ approvals and Claude approvals retain their existing scope.
1886
+ - Cron schedules accept five fields or `@hourly`, `@daily`, `@weekly`, `@monthly`,
1887
+ and `@yearly`. Older invalid expressions, including six-field schedules with
1888
+ seconds, remain visible as inactive entries; replace them with a valid schedule.
1889
+ - Submitting a review keeps its draft until the server confirms delivery and
1890
+ persistence. Rejections, disconnects, and a 30-second confirmation timeout
1891
+ preserve the draft. If confirmation is missing, inspect the conversation before
1892
+ manually retrying: the agent may already have received the message.
1893
+ - A slow WebSocket connection is disconnected and replays missed events on
1894
+ reconnection. Current questions, agent state, and previously viewed development
1895
+ servers are refreshed after replay.
1896
+ - MCP thought logs use unique filenames. Repeated notes with the same title and
1897
+ tag no longer replace earlier files.
1898
+
1899
+ ## Auto-loop completion and recovery
1900
+
1901
+ Auto-loop remains enabled while work is unfinished. Its panel distinguishes grooming, execution and final verification from waiting, blocked, completed and stopped states. All todos and acceptance criteria must be complete, followed by a successful final verification. The final check runs again after task changes or new instructions. Empty custom finalization settings use the built-in final check.
1902
+
1903
+ Completing an auto-loop task through MCP or HTTP requires structured verification: a non-empty `method`, `summary`, and `checks` array with named checks whose `status` is `passed`. A required test that could not run stays open (`not_run`); writing test source alone is insufficient. These are results reported by the agent, not independently executed by Kōbō.
1904
+
1905
+ After three iterations without a new task milestone, Kōbō runs one diagnostic session and allows two further attempts. Turns dedicated to integrating new instructions preserve these counters, including in continuous-session mode. Persistent failure pauses the mission with its reason and a **Resume** action. The loop's intent is retained. Explicitly stopping, archiving or deleting a workspace disables it. Independently configured cron schedules retain their existing behavior; cancel them separately when no scheduled session should run.
1906
+
1907
+ Quota waits survive restarts and respect known reset dates, even several days away. Unknown resets use the fallback schedule, capped at five hours between attempts. `autoLoopMaxRetries` limits temporary engine/transport failures, not genuine quota waits. Outside auto-loop, an explicitly scheduled wakeup or cron can resume after the quota wait expires; expiry alone never launches a manual workspace. No automatic start may overlap an engine whose shutdown remains unconfirmed.
1908
+
1909
+ Messages sent while auto-loop is enabled are stored for the next iteration, including during grooming. That iteration integrates the instructions into the task list before implementation continues. The panel lists pending messages and lets you cancel them before dispatch. If a crash or interrupted session makes delivery uncertain, the mission pauses: inspect its history, then explicitly acknowledge the instruction as handled or request a retry. A retry can repeat work; it never happens automatically for an uncertain delivery.
1910
+
1911
+ HTTP clients can use `POST /api/workspaces/:id/auto-loop/messages` with `{content, clientMessageId}`, `GET` on the same path to inspect the queue, and `PATCH .../messages/:messageId` with `{action: "cancel" | "acknowledge" | "retry"}`. Keep the same `clientMessageId` when retrying an uncertain HTTP request. Existing immediate message delivery retains its explicit manual semantics.
1912
+
1913
+ ## First-run defaults and Git workflow preferences
1914
+
1915
+ New installations select Standard, Plan, automatic provider model selection, two unattended slots, silent audio and disabled Notion/Sentry. Existing installations skip first-run setup and preserve their own choices. Known shipped prompt text is updated only on exact match; customized text and deleted templates remain unchanged. Retired sound identifiers map to the original neutral tone while preserving volume and enablement.
1916
+
1917
+ `workflowPolicy` contains independent `commit`, `push` and `publish` values (`manual` or `automatic`). Global settings provide defaults; project settings may override individual values; workspace creation snapshots the resolved result into the workspace. Later global/project changes affect future workspaces, not existing snapshots. A workspace policy can be updated through its validated PATCH API. The workspace toolbar's **Configure** menu exposes the three preferences beside the
1918
+ model, effort and permission selectors; they are disabled while an agent is running,
1919
+ for the same reason the API refuses the change. A change returns HTTP 409 while an agent controller is still present (including shutdown) or a workspace lifecycle operation is in progress: stop the agent, wait for the operation to finish, then update the policy. The next session start or resume receives the updated preferences. A PATCH that leaves the effective policy unchanged remains accepted, subject to the existing lifecycle restrictions.
1920
+
1921
+ Fresh defaults are manual. Existing settings/workspaces migrate to the previous automatic behavior for compatibility. Manual requires an explicit user request for that action; automatic supplies the workflow preference for the mission. Narrower user instructions always win. Publish means PR/MR creation, description updates and comments; merging needs separate authorization. Explicit Git-panel actions remain available. These preferences are agent instructions, not a shell-command filter or an OS sandbox.
1922
+
1923
+ Plan is never promoted to Bypass by auto-loop preparation, an agent tool event, or loop enablement. Preparation can run in Plan; execution/finalization blocks with an actionable message until an execution mode is explicitly selected.
1924
+
1925
+ Notion token environment overrides are applied consistently to both token variables passed to the child process. Explicit `OPENAPI_MCP_HEADERS` remain authoritative for compatibility and are not replaced by the resolved token. Direct integration configuration and legacy fallback are only read when an integration is used; opening Settings does not start a Notion users lookup. Connection tests and users refresh are explicit actions.
1926
+
1927
+
1928
+ ### Managed integration MCPs in agent sessions
1929
+
1930
+ **Test connection** uses the saved direct configuration when present, otherwise the
1931
+ selected Claude entry, with the documented provider environment overrides. It does
1932
+ not test unsaved form values. Notion checks authentication with a users lookup;
1933
+ it does not write a page. A successful connection test is separate from an agent
1934
+ actually calling a tool.
1935
+
1936
+ Enabled, configured Notion and Sentry connections are supplied to both Claude Code
1937
+ and Codex at every agent launch or resume. Resolution is shared with connection
1938
+ tests and imports: direct configuration takes priority over selected Claude MCP
1939
+ entries; the documented Notion environment overrides still apply. Optional,
1940
+ unconfigured integrations are omitted and do not prevent ordinary sessions.
1941
+
1942
+ Managed server names begin with `kobo-notion-` or `kobo-sentry-` and carry a fresh
1943
+ launch suffix. The session prompt identifies the current namespaces and asks the
1944
+ agent to use them for the corresponding integration. This prevents Codex's native
1945
+ configuration merge from retaining stale credentials or transport fields under a
1946
+ reused server name. Only connection-specific environment overrides are supplied;
1947
+ the complete backend environment is not serialized into the agent configuration.
1948
+
1949
+ Saving settings does not replace connections in an already-running engine. Stop
1950
+ that engine and resume, or start a fresh session, to apply changes. Disabling an
1951
+ integration removes its Kōbō-managed connection from subsequent launches; it does
1952
+ not remove independent MCP servers configured in Claude or Codex. Integration
1953
+ credentials do not authorize the agent to modify remote data.
1954
+
1955
+
1956
+ ### Settings navigation
1957
+
1958
+ **Git** contains commit/push/publication preferences, Git conventions, branch
1959
+ prefixes. **Prompts** contains instructions sent after opening a PR/MR, as well
1960
+ as review, CI-fix and finalization instructions. **Prompt templates** contains reusable chat text;
1961
+ it is distinct from model selection under **Agents** and workspace presets under
1962
+ **Workspace templates**. Per-project Git overrides remain under **Projects → Git**.
1963
+
1964
+ Automatic retry limits are under **Agents**; awaiting-user reminders are under
1965
+ **Notifications**. **Worktrees** contains paths, purge and retention. **Forge**
1966
+ contains Bitbucket credentials; GitHub/GitLab reuse authenticated local CLIs and
1967
+ the provider is selected per project. These moves do not change stored settings
1968
+ or inheritance. The main save action and unsaved-edit protection still apply.