@agimon-ai/doompi 0.0.1-alpha.14 → 0.0.1-alpha.15

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 (105) hide show
  1. package/README.md +155 -535
  2. package/dist/adapters/modules/moduleResolution.cjs +1 -1
  3. package/dist/adapters/modules/moduleResolution.cjs.map +1 -1
  4. package/dist/adapters/modules/moduleResolution.d.cts +18 -15
  5. package/dist/adapters/modules/moduleResolution.d.cts.map +1 -1
  6. package/dist/adapters/modules/moduleResolution.d.mts +18 -15
  7. package/dist/adapters/modules/moduleResolution.d.mts.map +1 -1
  8. package/dist/adapters/modules/moduleResolution.mjs +1 -1
  9. package/dist/adapters/modules/moduleResolution.mjs.map +1 -1
  10. package/dist/adapters/piSettings.cjs +1 -1
  11. package/dist/adapters/piSettings.cjs.map +1 -1
  12. package/dist/adapters/piSettings.d.cts +3 -1
  13. package/dist/adapters/piSettings.d.cts.map +1 -1
  14. package/dist/adapters/piSettings.d.mts +3 -1
  15. package/dist/adapters/piSettings.d.mts.map +1 -1
  16. package/dist/adapters/piSettings.mjs +1 -1
  17. package/dist/adapters/piSettings.mjs.map +1 -1
  18. package/dist/adapters/syncState.cjs +1 -1
  19. package/dist/adapters/syncState.cjs.map +1 -1
  20. package/dist/adapters/syncState.d.cts +6 -1
  21. package/dist/adapters/syncState.d.cts.map +1 -1
  22. package/dist/adapters/syncState.d.mts +6 -1
  23. package/dist/adapters/syncState.d.mts.map +1 -1
  24. package/dist/adapters/syncState.mjs +1 -1
  25. package/dist/adapters/syncState.mjs.map +1 -1
  26. package/dist/commands/initCommand.cjs +1 -2
  27. package/dist/commands/initCommand.cjs.map +1 -1
  28. package/dist/commands/initCommand.d.cts +4 -3
  29. package/dist/commands/initCommand.d.cts.map +1 -1
  30. package/dist/commands/initCommand.d.mts +4 -3
  31. package/dist/commands/initCommand.d.mts.map +1 -1
  32. package/dist/commands/initCommand.mjs +1 -2
  33. package/dist/commands/initCommand.mjs.map +1 -1
  34. package/dist/commands/initPresenter.cjs +3 -0
  35. package/dist/commands/initPresenter.cjs.map +1 -0
  36. package/dist/commands/initPresenter.d.cts +8 -0
  37. package/dist/commands/initPresenter.d.cts.map +1 -0
  38. package/dist/commands/initPresenter.d.mts +8 -0
  39. package/dist/commands/initPresenter.d.mts.map +1 -0
  40. package/dist/commands/initPresenter.mjs +3 -0
  41. package/dist/commands/initPresenter.mjs.map +1 -0
  42. package/dist/config/index.cjs +1 -1
  43. package/dist/config/index.d.cts +2 -2
  44. package/dist/config/index.d.mts +2 -2
  45. package/dist/config/index.mjs +1 -1
  46. package/dist/entries/domains.cjs +1 -1
  47. package/dist/entries/domains.d.cts +2 -2
  48. package/dist/entries/domains.d.mts +2 -2
  49. package/dist/entries/domains.mjs +1 -1
  50. package/dist/extensions/entries/domains.cjs +1 -1
  51. package/dist/extensions/entries/domains.cjs.map +1 -1
  52. package/dist/extensions/entries/domains.d.cts +10 -2
  53. package/dist/extensions/entries/domains.d.cts.map +1 -1
  54. package/dist/extensions/entries/domains.d.mts +10 -2
  55. package/dist/extensions/entries/domains.d.mts.map +1 -1
  56. package/dist/extensions/entries/domains.mjs +1 -1
  57. package/dist/extensions/entries/domains.mjs.map +1 -1
  58. package/dist/extensions/entries/modeCatalog.cjs +1 -1
  59. package/dist/extensions/entries/modeCatalog.cjs.map +1 -1
  60. package/dist/extensions/entries/modeCatalog.d.cts.map +1 -1
  61. package/dist/extensions/entries/modeCatalog.d.mts.map +1 -1
  62. package/dist/extensions/entries/modeCatalog.mjs +1 -1
  63. package/dist/extensions/entries/modeCatalog.mjs.map +1 -1
  64. package/dist/extensions/services/domainSwitchHandoff.cjs +2 -0
  65. package/dist/extensions/services/domainSwitchHandoff.cjs.map +1 -0
  66. package/dist/extensions/services/domainSwitchHandoff.mjs +2 -0
  67. package/dist/extensions/services/domainSwitchHandoff.mjs.map +1 -0
  68. package/dist/index.cjs +1 -1
  69. package/dist/index.d.cts +3 -3
  70. package/dist/index.d.mts +3 -3
  71. package/dist/index.mjs +1 -1
  72. package/dist/schemas/domainVoiceTools.cjs +2 -0
  73. package/dist/schemas/domainVoiceTools.cjs.map +1 -0
  74. package/dist/schemas/domainVoiceTools.mjs +2 -0
  75. package/dist/schemas/domainVoiceTools.mjs.map +1 -0
  76. package/dist/services/config/index.d.cts +2 -2
  77. package/dist/services/config/index.d.mts +2 -2
  78. package/dist/services/config/index.mjs +1 -1
  79. package/dist/services/extensionAssembler.cjs +1 -1
  80. package/dist/services/extensionAssembler.cjs.map +1 -1
  81. package/dist/services/extensionAssembler.d.cts.map +1 -1
  82. package/dist/services/extensionAssembler.d.mts.map +1 -1
  83. package/dist/services/extensionAssembler.mjs +1 -1
  84. package/dist/services/extensionAssembler.mjs.map +1 -1
  85. package/dist/services/piSettings.cjs +1 -1
  86. package/dist/services/piSettings.d.cts +2 -2
  87. package/dist/services/piSettings.d.mts +2 -2
  88. package/dist/services/piSettings.mjs +1 -1
  89. package/dist/services/syncState.cjs +1 -1
  90. package/dist/services/syncState.d.cts +2 -2
  91. package/dist/services/syncState.d.mts +2 -2
  92. package/dist/services/syncState.mjs +1 -1
  93. package/dist/utils/index.cjs +1 -1
  94. package/dist/utils/index.d.cts +2 -2
  95. package/dist/utils/index.d.mts +2 -2
  96. package/dist/utils/index.mjs +1 -1
  97. package/dist/utils/moduleResolution.cjs +1 -1
  98. package/dist/utils/moduleResolution.d.cts +2 -2
  99. package/dist/utils/moduleResolution.d.mts +2 -2
  100. package/dist/utils/moduleResolution.mjs +1 -1
  101. package/package.json +20 -18
  102. package/dist/schemas/modeCatalog.cjs +0 -2
  103. package/dist/schemas/modeCatalog.cjs.map +0 -1
  104. package/dist/schemas/modeCatalog.mjs +0 -2
  105. package/dist/schemas/modeCatalog.mjs.map +0 -1
package/README.md CHANGED
@@ -2,13 +2,23 @@
2
2
 
3
3
  **A coding agent that loads only the skills and tools you name.**
4
4
 
5
- Plugin systems scope what an agent knows. Nothing scopes what it can reach. Claude Code
6
- gives you `enableAllProjectMcpServers` and a static denylist, both repository-wide, so
7
- every session pays for every MCP server's tool schemas whether the task is a database
8
- migration or a landing page.
5
+ > It begins with one useful MCP server. Then another. Soon the agent fixing a heading
6
+ > wakes up with database tools, browser controls, and their small novel of schemas. This
7
+ > is our config.
9
8
 
10
- Doompi makes both a declared input. Three YAML files decide what a session loads, and
11
- `--explain` prints the bill before you launch.
9
+ Doompi is a configuration framework for [Pi](https://github.com/earendil-works/pi)
10
+ tailored for people whose agent has one MCP server too many. It turns extensions, skills,
11
+ MCP servers, and system prompts into config instead of background noise.
12
+
13
+ Plugin systems scope what an agent knows. Nothing scopes what it can reach. Claude Code's
14
+ `enableAllProjectMcpServers` and static denylist are repository-wide. Doompi draws that
15
+ boundary around the session. Pick a major mode and some domains; add a profile if you want
16
+ one. Three YAML files decide what loads; `doompi --explain` tells you what got in, why, and
17
+ what it costs before launch.
18
+
19
+ It borrows its shape from [Doom Emacs Core](https://github.com/doomemacs/core): quick to
20
+ start, close to Pi, opinionated where defaults help, and easy to pull apart when they do
21
+ not. Use it as-is, build your own config on top, or raid it for parts.
12
22
 
13
23
  ## Install
14
24
 
@@ -25,8 +35,11 @@ doompi --explain # what would load, and why
25
35
  doompi # start a session
26
36
  ```
27
37
 
28
- `.doom/` is committed to git, so a checkout carries its own agent configuration. Edit a
29
- YAML file and the next launch picks it up.
38
+ `doompi init` is the one machine-wide step. It seeds `~/.pi/.doom`; copy those files into
39
+ a repository when the repository needs its own agent. Commit `.doom/`. Now the config
40
+ follows the code instead of living in someone's shell history.
41
+
42
+ Flags override the defaults for one session:
30
43
 
31
44
  ```bash
32
45
  doompi --major-mode dev --domains development
@@ -34,360 +47,80 @@ doompi --domains marketing --profile marketing
34
47
  doompi --domains analytics --explain
35
48
  ```
36
49
 
37
- ## The three axes
38
-
39
- Doompi wraps [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) and
40
- resolves a declared configuration into a session rather than asking you to wire one up.
41
- The three choices are independent: adding a domain requires no knowledge of major modes,
42
- and swapping a profile changes nothing about either.
43
-
44
- | Choice | Decides | Declared in |
45
- | -------------- | ------------------------------------ | --------------------- |
46
- | **Major mode** | what the agent is wrapped in | `.doom/modes.yaml` |
47
- | **Domains** | what it knows, and what it can reach | `.doom/domains.yaml` |
48
- | **Profile** | who it speaks as | `.doom/profiles.yaml` |
49
-
50
- A domain names plugins and an MCP allowlist together, so `--domains marketing` and
51
- `--domains development` are the same agent with different knowledge and a different reach,
52
- not two different agents. That is the lever for keeping context small, and
53
- [`--explain`](#minimal-context) prices it before you commit.
54
-
55
- ## What is actually different
56
-
57
- **Compaction reads state, it does not summarize prose.** When the context fills, Doompi
58
- does not guess at coordination state from the transcript. It reads the live plan, task
59
- graph, and team snapshot, and commits them next to the summary as authoritative. The agent
60
- comes out the other side knowing what it was doing, what is still running, and who is doing
61
- what. See [Long runs](#long-runs).
62
-
63
- **Spec-driven and meta-prompting systems change what the agent is told. Doompi changes what
64
- it loads.** The two compose fine. This one is about the context window, not the prompt.
65
-
66
- **One configuration serves two readers.** A human gets a keyboard surface that does not
67
- move; an autonomous agent gets a small tool surface and a context window that compacts
68
- itself.
69
-
70
50
  ## Philosophy
71
51
 
72
- **Give the agent fewer things to choose between.** [Major modes, domains, and
73
- profiles](#major-modes-domains-profiles) decide what loads, so a session carries the skills
74
- and MCP servers its domains named and nothing else. Whatever still overflows [compacts in
75
- the background](#long-runs).
76
-
77
- **Shift the work onto deterministic workflows.** Autonomous runs are [GitHub Actions
78
- shaped](#workflows), with jobs, dependencies, steps, timeouts, and declared artifacts, so
79
- the same job resolves the same way every time.
80
-
81
- **Keep the keyboard ergonomic.** `SPC` is the whole surface, Spacemacs and Doom Emacs
82
- style, and [it opens only when the draft is empty](#leader-space), so a half-written prompt
83
- is never a casualty.
84
-
85
- **Batteries included, without the bloat.** Plan mode, tasks, teams, a supervised runner,
86
- voice, and workflows ship in the box, but [core is not a
87
- layer](#core-is-not-a-layer) and every opinion waits behind a layer or a domain.
88
-
89
- **One major mode, several minor modes.** A session runs under exactly one major mode, which
90
- is what selects its layers. [Plan, loop, and workflow](#minor-modes) are minor modes: you
91
- toggle them mid-session, they stack, and they report themselves on a shared status line.
52
+ An agent does not need every tool for every job. Doompi separates the base session from
53
+ the things you switch on for a while: modes choose behavior, domains choose subject
54
+ matter, and profiles choose a point of view.
92
55
 
93
- ## Two ways to run
56
+ ### Major and minor modes
94
57
 
95
- The launcher resolves your three choices per run and spawns Pi with them. Skills, agents,
96
- MCP configs, and the persona prompt are assembled into a temporary directory that is
97
- deleted on exit. Nothing is written back into the repository, and every session pays the
98
- resolution cost.
58
+ A major mode is the base config. It names the extension layers for development, marketing,
59
+ or whatever else you do. Define as many as you like; only one is active at a time, and you
60
+ can switch it without leaving the session.
99
61
 
100
- `doompi sync` is the other way in, and the Doom Emacs one. Resolve once, write the
101
- result where Pi looks, then run `pi` yourself.
62
+ Minor modes are switches inside that base. They start off, stack freely, and bring their
63
+ own tools and instructions when turned on. Doompi ships five:
102
64
 
103
- ```bash
104
- doompi init # seed ~/.pi/.doom, once per machine
105
- doompi sync # resolve the selection and register DoomPi with Pi
106
- doompi build # optional: warm launch and synchronized bundles
107
- pi # auto-build stale output, then start the agent
108
-
109
- doompi sync --check # exit non-zero when the synced config is out of date
110
- ```
65
+ - **Plan mode** — make the repository read-only while you agree on an approach.
66
+ - **Loop mode** — run a prompt now, then run it again on a schedule.
67
+ - **Goal mode** — keep one objective in view until it is done or dismissed.
68
+ - **Workflow mode** — run jobs with dependencies, timeouts, and artifacts.
69
+ - **Voice mode** — replace typing with local speech.
111
70
 
112
- A synced session takes `--major-mode`, `--domains`, `--profile`, and `--mute` the same way
113
- the launcher does, and `/mode`, `/domains`, and `/profile` all switch in place: the
114
- extension set is composed on every load rather than frozen at startup, so a reload is
115
- enough.
116
-
117
- Both paths compose the extension set from the same function, `assembleExtensions`, which
118
- owns load order. Two things differ. A synced session forces `--auto-stop` off, and it
119
- reads mute from `DOOMPI_MUTE` instead of an argument.
120
-
121
- `doompi build` is an optional warm-up step, analogous to `doom build`: it resolves the
122
- selected launcher matrix, compiles its exact extension graph, and—after a sync—warms the
123
- synchronized bootstrap and mode bundles in `.pi/doom/dist`. A sidecar manifest retains
124
- every original extension and exact `SKILL.md` path, so resource discovery never depends
125
- on the bundle's location. Without that command, plain `pi` writes a tiny native ESM
126
- bootstrap instead of running the graph bundler; cache-miss preparation stays under 200 ms
127
- and then loads the package's already-built JavaScript. Run `doompi build` only when you
128
- want the lower steady-state cost of flattened mode bundles.
129
-
130
- Syncing does not disturb the launcher. Pi merges the extensions a project declares with
131
- the ones passed on the command line, so the synced entry stands down whenever it sees the
132
- composed set already there, which is what the launcher and every detached subagent pass.
133
-
134
- ### Two settings scopes, one registration
135
-
136
- Pi reads user settings from `$PI_CODING_AGENT_DIR/settings.json` and project settings from
137
- `<repo>/.pi/settings.json`. Project settings override user settings key by key, but
138
- resource resolution is the exception: Pi collects the `extensions` and `packages` of both
139
- scopes and dedupes only by the resolved file's real path. A repository that registers Doom
140
- Pi as well as the user scope therefore loads two _installs_ of the same package against one
141
- `.pi/doom` state, and two installs at different versions disagree about the state contract.
142
-
143
- Doom Pi settles that in both places. At load time the package entry claims the repository
144
- for the whole process, so the first factory owns the cycle and every later one stands down
145
- without reading, compiling, or writing anything. Pi resolves project resources before user
146
- ones, which makes the owner the repository-local install: the repository wins, as it does
147
- everywhere else. `doompi sync` then removes the redundant registration from
148
- `.pi/settings.json`, keeping the file and every unrelated key, and `doompi sync --check`
149
- and `doompi build` report it until it is gone.
150
-
151
- ## For humans
152
-
153
- ### Leader Space
154
-
155
- `@agimon-ai/doompi-ui` owns the leader state machine, rendering, conflict handling, and the
156
- core bindings.
157
-
158
- Space opens the leader **only when the draft is empty**. With text in the editor, space is
159
- a space. `ctrl+space` opens the leader either way, and the draft survives the sequence, so
160
- you never lose a half-written prompt to a keystroke. That is the space the human keeps.
161
-
162
- Inside a sequence: `escape` cancels, `backspace` pops one segment, and any key that
163
- matches nothing cancels. There is no partial state to get stuck in.
164
-
165
- Core groups. Every level of the map renders in leader-key alphabetical order, so the table
166
- below is the order you see:
167
-
168
- | Key | Group | Bindings |
169
- | --- | --------- | ---------------------------------------- |
170
- | `e` | extension | `e` external editor, `t` tools browser |
171
- | `h` | help | `h` hotkeys, `l` log metrics |
172
- | `m` | models | `m` select, `n` next, `t` thinking level |
173
- | `q` | quit | `q` exit |
174
- | `s` | sessions | `f` fork, `n` new, `r` resume, `t` tree |
175
-
176
- `t` is deliberately left out of core at the root and reserved for doom-task.
177
-
178
- Optional feature extensions own the bindings for their own commands. The UI never
179
- hardcodes a binding for a layer that may not be loaded, so a group appears only while its
180
- extension is loaded:
181
-
182
- | Chord | Source | Opens |
183
- | -------------------- | ----------------------------- | ------------------------------ |
184
- | `SPC a` | `@agimon-ai/doompi-team` | subagent fleet |
185
- | `SPC h l` | `@agimon-ai/doompi-log` | log metrics |
186
- | `SPC l s`, `SPC l l` | `@agimon-ai/doompi-loop` | start loops, list/stop loops |
187
- | `SPC p p/c/d/f` | `@agimon-ai/doompi-plan` | plan normal/cancel/debug/fable |
188
- | `SPC t t` | `@agimon-ai/doompi-task` | tasks |
189
- | `SPC r r` | `@agimon-ai/doompi-runner` | background processes |
190
- | `SPC v v` | `@agimon-ai/doompi-voice` | record or transcribe |
191
- | `SPC w w/l/r` | `@agimon-ai/doompi-workflow` | launch/manage/recover |
192
- | `SPC e f` | `@agimon-ai/doompi-file-edit` | session edits |
193
- | `SPC e s` | `@agimon-ai/doompi` | skills catalog |
194
- | `SPC e c` | `@agimon-ai/doompi-ui` | config panel (core binding) |
195
- | `SPC g s/e/p` | `@agimon-ai/doompi-goal` | start/end/history |
196
-
197
- Contributions go through the public `@agimon-ai/doompi-ui/leader` API:
198
-
199
- ```ts
200
- registerDoomLeaderContribution(pi, {
201
- source: '@agimon-ai/doompi-log',
202
- bindings: [
203
- {
204
- id: 'log.metrics',
205
- path: [
206
- { key: 'h', label: 'help', order: 70 },
207
- { key: 'l', label: 'logs', detail: 'telemetry' },
208
- ],
209
- command: { name: 'log-metrics' },
210
- },
211
- ],
212
- });
213
- ```
71
+ ### Domains
214
72
 
215
- A path excludes the leading `SPC`. The UI turns this into `SPC h l`, dispatches
216
- `/log-metrics` through the normal editor submission path, and preserves the draft. A
217
- binding carries either a `command` descriptor or an `action` name, never both. Commands
218
- stay owned by the extension that registered the slash command; actions route back to the
219
- contributor through `registerDoomLeaderActionHandlers`, which is what doom-plan uses.
220
-
221
- The map is deterministic because the registry is strict:
222
-
223
- - Keys are a single lowercase alphanumeric character, paths are at most four segments.
224
- - Shared group prefixes must agree on label, detail, and order, or the contribution is
225
- rejected.
226
- - Exact chord conflicts are rejected rather than silently overridden. A conflict in a core
227
- binding throws; a conflict from a contributor produces a diagnostic and a warning.
228
- - Re-registering the same `source` replaces that source's complete binding set. Registering
229
- an empty set removes it.
230
- - Rebuilds sort by source name then binding id, so load order does not affect the result.
231
- - Options render in leader-key alphabetical order at every level. A segment's `order` is
232
- group identity that shared prefixes must agree on, not a display position.
233
-
234
- Registration runs over Pi's shared extension event bus with a 250 ms timeout. A timeout is
235
- swallowed, so an extension loaded without the UI degrades quietly instead of failing.
236
-
237
- ### Major modes, domains, profiles
238
-
239
- Three choices, three files, committed to git. They are independent. Adding a domain
240
- requires no knowledge of major modes, and swapping a profile changes nothing about either.
241
-
242
- | Choice | Loads | Declared in |
243
- | --------------- | ------------------------------------- | --------------------- |
244
- | **Major modes** | a named set of layers | `.doom/modes.yaml` |
245
- | **Domains** | plugins, meaning skills and MCP | `.doom/domains.yaml` |
246
- | **Profiles** | a persona and the brand it speaks for | `.doom/profiles.yaml` |
247
-
248
- **Layers are protection and steering.** A layer is a set of Pi extensions plus a set of
249
- hook groups. The extensions add behavior the agent runs with; the hooks fire around its
250
- tool calls and can block it, warn it, or steer it back. You define the set you want, along
251
- the lines of `guardrails`, `lint`, `code-intel`, `team`, `plan-mode`, `runner`, and
252
- `ask-user`. The hooks themselves live in `.doom/hooks.yaml`, one registry shared by every
253
- frontend, where a group is either `core` and always loads, or is pulled in by whichever
254
- layer wants it.
255
-
256
- **A major mode is the one you actually pick.** You do not assemble layers one by one at the
257
- prompt. You select one named major mode with `--major-mode <name>`, and
258
- `.doom/modes.yaml` says which layers it contains. A session has exactly one, the way
259
- an Emacs buffer has exactly one major mode. The file can choose the fallback without
260
- renaming that mode:
73
+ A domain is a named group of Pi plugins. It carries the skills and MCP servers for one
74
+ kind of work, and `/domains` switches it while the session is running.
261
75
 
262
- ```yaml
263
- defaultMajorMode: minimal
264
- majorMode:
265
- minimal: [guardrails, team]
266
- copilot: [guardrails, team, plan-mode, runner]
267
- ```
76
+ A blog is not one task. Research it, draft it, make the assets, then review it. Turn on the
77
+ `visual` domain while making assets; the other three steps have no reason to carry it.
268
78
 
269
- An explicit `--major-mode` wins, then `DOOMPI_MAJOR_MODE`, then
270
- `defaultMajorMode`. Omitting the field preserves the compatible `copilot` fallback.
79
+ ### Profile
271
80
 
272
- The flag is `--major-mode` and not `--mode` because Pi already owns `--mode` for its output
273
- mode (`text`, `json`, `rpc`), and for any other value it consumes the argument and ignores
274
- it without a diagnostic. Use `--output-format` for Pi's output mode.
81
+ An LLM has no house style until you give it one. A profile can supply a narrative, brand
82
+ rules, or a different voice. It is optional; no profile is a perfectly good profile.
275
83
 
276
- **Domains are plugins, and a plugin is skills plus MCP.** Selecting a domain decides which
277
- plugins contribute their skills and subagents, which MCP servers the session can reach,
278
- and whether the always-on shared skills apply. A domain can take a whole plugin or a named
279
- subset of one. This is the choice that decides how much the agent can see, so it is also
280
- the lever for keeping context small. Defaults are plural because domains compose:
84
+ ## What this buys you
281
85
 
282
- ```yaml
283
- defaultDomains: [development, qa]
284
- domains:
285
- development:
286
- plugins: [plugins/development]
287
- qa:
288
- plugins: [plugins/qa]
289
- ```
86
+ Every tool schema and skill name competes for the same context. Loading less has two
87
+ immediate effects:
290
88
 
291
- Explicit `--domain` or `--domains` flags win, then `DOOMPI_DOMAINS`, then
292
- `defaultDomains`.
89
+ 1. You spend fewer tokens before the work begins.
90
+ 2. The model has fewer plausible-but-wrong tools and skills to choose from.
293
91
 
294
- **Profiles are a persona and a brand.** `.doom/profiles.yaml` points at a directory under
295
- `agents/<brand>/<person>/`, and doom concatenates that person's `profile.md`, `SOUL.md`,
296
- and `AGENTS.md` into the system prompt: identity and the brand it represents, then voice,
297
- then role and rules. A profile also carries environment defaults, and nothing else. It
298
- cannot select domains, major modes, models, presets, or policy, and an exported value
299
- always beats a profile default.
92
+ The savings get larger when each workflow job starts with its own config instead of
93
+ inheriting the last job's toolbox.
300
94
 
301
- Switching is live. `/mode`, `/domains`, and `/profile` re-resolve into the running
302
- session and reload. A domain or profile switch always applies in place. A major mode switch
303
- applies in place too unless the new mode changes which extension packages load, since Pi
304
- freezes the `--extension` set at construction; the picker tells you when a relaunch is
305
- needed.
95
+ ### Copilot
306
96
 
307
- ### Minor modes
97
+ I got tired of remembering slash commands, so `SPC` is the map. It opens only when the
98
+ draft is empty; a space in the middle of a prompt remains a space. Press it, read the
99
+ choices, then press the next key.
308
100
 
309
- A major mode is chosen once per session and selects layers. Minor modes are the opposite:
310
- you toggle them while the session runs, several can be on at once, and none of them changes
311
- which extensions are loaded.
101
+ When the keyboard is the wrong tool, autonomous Voice mode keeps the conversation going.
102
+ You can talk to the agent while doing the chores instead of carrying a laptop around the
103
+ house.
312
104
 
313
- | Minor mode | Toggle | Owned by |
314
- | ---------- | -------------------- | ---------------------------- |
315
- | goal | `/goal`, `SPC g` | `@agimon-ai/doompi-goal` |
316
- | plan | `/plan`, `SPC p` | `@agimon-ai/doompi-plan` |
317
- | loop | `/loop`, `SPC l` | `@agimon-ai/doompi-loop` |
318
- | workflow | `/workflow`, `SPC w` | `@agimon-ai/doompi-workflow` |
105
+ ### Autopilot
319
106
 
320
- Goal is assembled in `minimal`, `copilot`, custom major modes, and detached children rather
321
- than selected by a layer. It starts dormant: `/goal` is available, but its tools, system
322
- prompt, automatic continuation, and `GOAL` status stay off until an objective is accepted
323
- or an active Goal is restored. Paused, blocked, limited, and queue-waiting Goals retain the
324
- status item without retaining execution capabilities. Parent hosts load the Doom entry;
325
- detached children load the UI-independent Pi entry.
107
+ Copilot helps while you are present. Loop and Workflow keep work moving when you are not.
108
+ Together they can dispatch structured jobs from one live session.
326
109
 
327
- Each enabled minor mode publishes a label to a shared `MODES` line rather than painting a
328
- row of its own. The registry is `@agimon-ai/doompi-extension-contracts/mode`.
110
+ #### Workflows
329
111
 
330
- Existing `pi-goal.json` files remain readable. Both legacy `toolVisibility` values now mean
331
- that Goal tools are managed only while execution is operational; neither exposes tools in
332
- a dormant session. `experimental.goals` controls queues only.
333
-
334
- If an edited `modes.yaml` still defines or selects a Goal layer, remove that definition and
335
- its major-mode references, then run `doompi sync`. Normal `doompi init` preserves edited
336
- files. `doompi init --force` is a full template reset and may overwrite unrelated edits.
337
-
338
- ## For autonomous agents
339
-
340
- ### Minimal context
341
-
342
- Fewer options, better choices. Domains cut the skill list and the tool list together. A
343
- domain that names three plugins gives the agent those plugins' skills and nothing else,
344
- and one with an `mcp` allowlist reaches only the servers and proxy upstreams it names.
345
-
346
- `--explain` prints what any combination costs before you launch it, and ends with the bill:
347
-
348
- ```
349
- $ doompi --domains development --explain
350
- ...
351
- skills: 24 (from 3 directories)
352
-
353
- context cost (tokens)
354
- skills prompt 3,772 always on
355
- persona 0 always on
356
- startup total 3,772
357
- skill bodies 54,145 read on demand
358
-
359
- Excludes MCP tool schemas, which the servers only report once connected,
360
- and skills contributed by extensions, which register after startup.
361
- ```
362
-
363
- Every figure comes from files on disk, so two runs of the same selection print the same
364
- numbers and you can reproduce them on your own repository rather than trusting these.
365
-
366
- MCP tool schemas are the one cost this cannot price. They exist only after a server
367
- connects, and pricing them would mean spawning every configured server on a command meant
368
- to be instant. Scoping them is still a declared choice; the saving is just not counted here.
369
-
370
- A domain without an `mcp` key admits everything, and one unscoped domain in the selection
371
- unfilters the rest. Scoping applies only when every selected domain declares it.
372
-
373
- A domain can also take a named subset of one plugin's skills, by name or glob.
374
-
375
- ### Workflows
376
-
377
- Long autonomous work runs as workflows, in the shape a GitHub Actions user already knows:
378
- `on:`, `jobs:`, `needs:`, `steps:`, timeouts, and declared artifacts.
379
-
380
- The payoff is that every step names the session it wants. A workflow is where
381
- `--major-mode`, `--domains`, and `--profile` stop being things you type and become part of
382
- the definition, so one run can hand each job exactly the context that job needs and nothing
383
- else:
112
+ GitHub Actions already has a decent vocabulary for long jobs, so Doompi reuses it. Each job
113
+ declares the Doompi session it wants. Here, implementation gets coding tools; the release
114
+ note waits for it and gets marketing context plus a brand voice:
384
115
 
385
116
  ```yaml
117
+ on:
118
+ workflow_dispatch:
119
+
386
120
  jobs:
387
121
  implement:
388
- needs: intake
389
122
  steps:
390
- - name: Implement
123
+ - name: Build the feature
391
124
  timeout-minutes: 180
392
125
  artifacts: [implementation/report.md]
393
126
  interactiveRun:
@@ -395,10 +128,10 @@ jobs:
395
128
  doompi --major-mode dev --domains development --auto-stop \
396
129
  --cwd "$PWD" "$JOB_SYSTEM_PROMPT"
397
130
 
398
- announce:
131
+ release-note:
399
132
  needs: implement
400
133
  steps:
401
- - name: Draft the release note
134
+ - name: Write the release note
402
135
  timeout-minutes: 30
403
136
  artifacts: [marketing/release-note.md]
404
137
  interactiveRun:
@@ -408,220 +141,107 @@ jobs:
408
141
  --cwd "$PWD" "$JOB_SYSTEM_PROMPT"
409
142
  ```
410
143
 
411
- Two jobs, two different agents. `implement` runs under a major mode carrying lint and code
412
- intelligence and a domain carrying coding skills. `announce` drops both, takes a domain
413
- scoped to a handful of MCP servers, and adds a profile, so the release note comes out in a
414
- named persona's voice rather than the agent's own. Neither job can drift into the other's
415
- context, and the same job resolves the same way on every run.
416
-
417
- The one real departure from GitHub Actions is `extends:`, which lets a job inherit shared
418
- setup from a named template rather than repeating it.
419
-
420
- The engine itself is not in this package. `@agimon-ai/doompi-workflow` provides the in-session
421
- surface on `SPC w`, and its dispatcher exposes `list_workflows` to any session but scopes
422
- `launch_workflow` to the root session, so a subagent can look but not spawn. The `workflow`
423
- hook group is `core`, so it loads in any major mode.
424
-
425
- ### Long runs
426
-
427
- Tasks, teams, runners, and compaction share one working state, so a long autonomous run
428
- does not lose its place.
429
-
430
- - **Tasks** (`SPC t`) are a file-backed graph with dependencies and delegation, not a
431
- scratch list.
432
- - **Teams** (`SPC a`) run named subagents asynchronously against that same board,
433
- sequentially or in parallel.
434
- - **Runners** (`SPC r`) replace the bash tool and detach long commands, then reconcile
435
- them afterwards.
436
- - **Compaction** runs on a three-pass ladder in a worker thread, so the session never
437
- blocks on it.
438
-
439
- The integration is the point. When compaction summarizes, it does not guess at
440
- coordination state: it reads the live plan, tasks, and team snapshot, and commits them
441
- next to the summary as authoritative rather than leaving them to be reconstructed from
442
- prose. Detached runners reconcile themselves once the context has been rewritten. The
443
- agent comes out the other side knowing what it was doing, what is still running, and who
444
- is doing what.
445
-
446
- ## Core is not a layer
447
-
448
- Telemetry, workflow orchestration, Goal's dormant runtime, the dispatch loop, and editor
449
- plumbing ship unconditionally and are absent from `modes.yaml` on purpose. They are the
450
- reason the harness exists, so making them optional would only create broken configurations.
451
- Layers are for opinions, not foundations.
452
-
453
- ## Who owns what
454
-
455
- Doom Pi is a meta-package. It depends on the Doom closure (`@agimon-ai/doompi-*`) and composes
456
- it, and it depends on nothing that belongs to the repository consuming it. The Agiflow and
457
- Agent Hooks extensions are consumer-owned: the repository declares them in its own
458
- `.doom/modes.yaml` and installs them itself, and they load after the Doom packages.
459
-
460
- That split decides where a specifier resolves. A package the repository declares resolves
461
- from the repository root, walking its module chain; anything that does not resolve there
462
- falls back to what ships with the installed meta-package. So a consumer can add a layer
463
- without Doom Pi knowing the package exists, and Doom Pi can ship its own closure without
464
- the consumer declaring it.
465
-
466
- Package resources follow the same rule: the UI theme is shipped by `@agimon-ai/doompi-ui`
467
- and the workflow-recovery skill by `@agimon-ai/doompi-workflow`, each declared in its own
468
- manifest, so an installed package is discoverable without this checkout. Every Doom package
469
- publishes through an explicit `files` allowlist, which keeps repository material such as
470
- `docs/ideas/` out of any tarball.
471
-
472
- A repository is recognised by a Doom or trusted Pi marker: a `.doom/` directory, or an
473
- existing `.pi/settings.json`. No Nx, pnpm workspace, or plugins profile is required, so a
474
- plain repository that installs the package can launch it.
475
-
476
- ## Lazy config
477
-
478
- Under the launcher, everything is resolved per run and nothing is written back into the
479
- repository. Editing a YAML file is the entire change.
480
-
481
- `doompi sync` trades that for a pinned setup: the same resolution runs once and lands in
482
- `.pi/doom/`, while Pi's user settings (`$PI_CODING_AGENT_DIR/settings.json`, defaulting to
483
- `~/.pi/agent/settings.json`) keep the stable `@agimon-ai/doompi` extension name. An
484
- internal user-directory alias lets Pi resolve that stable name. The lightweight package
485
- entry validates generated output, writes or refreshes a native bootstrap within a 200 ms
486
- budget, and only then loads the synchronized runtime graph. An explicit `doompi build`
487
- replaces that shim with fingerprinted graph bundles. Edit a YAML file and the next session says the
488
- config changed, the way Doom Emacs asks you to re-run `doom sync`.
489
-
490
- `doompi init` seeds `~/.pi/.doom` with all five file names, but only `config.yaml` from
491
- there is read at runtime. Major modes, domains, profiles, and hooks are always read from the
492
- repository `.doom/`, so the other four seeded files affect nothing beyond the inputs hash.
493
-
494
- Either way the hook registry compiles to the files other frontends read before any harness
495
- code runs, and `doompi sync` regenerates them.
496
-
497
- ## Other frontends
498
-
499
- Pi is the primary frontend. Claude Code, Codex, and Antigravity run through
500
- `doompi compat <provider>` and share the same domain config as far as each can:
501
-
502
- | | Pi | Claude Code | Codex | Antigravity |
503
- | ------------------- | ----------------- | ----------------------------- | ------------------------ | ------------------- |
504
- | domains to plugins | yes | yes | yes | yes |
505
- | hooks | from the registry | generated `settings.json` | generated `hooks.json` | copied `hooks.json` |
506
- | MCP servers | scoped | scoped | unscoped | scoped |
507
- | MCP proxy upstreams | scoped | scoped | scoped | scoped |
508
- | major mode | packages + hooks | shared hook groups | shared hook groups | shared hook groups |
509
- | personas | system prompt | `--append-system-prompt-file` | `developer_instructions` | no |
510
-
511
- Compatibility frontends use the selected major mode for shared hook state. Layer packages
512
- and Pi extensions are loaded only by Pi.
513
-
514
- Antigravity is the odd one. It reads its configuration from the workspace and from the
515
- user's home directory rather than from arguments, so every selection is written to disk
516
- before launch and reverted when it is no longer selected. Everything the harness writes is
517
- tracked in a managed-state file, so a file you created by hand is never silently replaced.
518
- Its hooks are copied from `.antigravity-local/hooks.json`, which is maintained by hand
519
- rather than generated from the registry.
520
-
521
- Where a concept has no equivalent, it is left out rather than approximated.
522
-
523
- ## Files
144
+ #### Loop
524
145
 
525
- All Doompi configuration lives in `.doom/`, committed to git.
146
+ Workflow definitions are exposed like skills, so the agent can choose one for the job. A
147
+ loop can send a subagent to fetch the next task, then dispatch the workflow that matches
148
+ it. One session becomes the dispatcher instead of the place every job has to fit.
526
149
 
527
- ```
528
- .doom/
529
- config.yaml projectTrust, plus the selection sync pins
530
- domains.yaml domains, plus aliases for shorthand bundles
531
- modes.yaml layer definitions and the named major modes built from them
532
- hooks.yaml canonical hooks for all three frontends
533
- profiles.yaml persona and environment profiles
534
-
535
- agents/<product>/<person>/ persona source, referenced never copied
536
- ```
150
+ ## Features
537
151
 
538
- `doompi sync` writes repository state into `.pi/doom/` and registers the stable package
539
- and theme in Pi's user directory. Older project-local Doom registrations are removed while
540
- unrelated project settings are preserved.
152
+ Doompi is a distribution, not one giant extension. Each package owns one job; shared TUI
153
+ and session contracts make them behave like one. Use the defaults together or replace
154
+ them one at a time.
541
155
 
542
- ```
543
- .pi/doom/ generated and gitignored
544
- state.json environment, resolved paths, inputs, and build pointers
545
- cache/ dist/ content-addressed precompile output
546
- mcp.json mcp-extension.ts agents/ persona.md
547
- run/<pid>/ one session's live switches, never the baseline
548
- harness-state.json that session's own state, owned by its process
549
-
550
- ~/.pi/agent/ or $PI_CODING_AGENT_DIR
551
- settings.json stable @agimon-ai/doompi entry; other keys preserved
552
- themes/doom-pi-dark.json synchronized Doom theme
553
- @agimon-ai/doompi internal link to the installed package
554
- ```
156
+ ### Configuration and composition
555
157
 
556
- ### One owner for the state
158
+ `@agimon-ai/doompi` is both an extension and the command-line config compiler. The
159
+ `doompi init` command writes the config; `doompi sync` resolves every major mode and
160
+ domain into a distribution Pi can load quickly. A large major mode with 15 extensions
161
+ adds only 400 ms of code startup time.
557
162
 
558
- The resolved matrix a session runs on is a file, not a set of variables.
559
- `DOOMPI_STATE` points at it, and everything else the harness exports is derived from
560
- it: a projection published for the readers that can only see an environment, which are bash
561
- hooks, the shell launchers, `agent-hooks`, and any process spawned by any of them. Two
562
- fields never appear there at all, because nothing outside `@agimon-ai/doompi-config` reads
563
- them and every hook spawn would otherwise copy them: the plugin hook list and the profile's
564
- environment defaults.
163
+ ### Leader key
565
164
 
566
- Ownership is per process, and that is the point. Doom Team subagents run detached, from an
567
- environment snapshot, and can outlive the parent that spawned them. So a spawner writes the
568
- child a file of its own in that run's directory, and any process that finds a file it does
569
- not own copies it before its first write. A child can neither corrupt its parent's session
570
- nor lose its own when the parent cleans up.
165
+ `@agimon-ai/doompi-ui` turns `SPC` into a map of the available commands. It stays out of
166
+ the way when a draft is not empty, and other packages contribute bindings through one
167
+ leader API instead of hardcoding their own menus.
571
168
 
572
- It also regenerates the two hook files the other frontends read before any harness code
573
- runs, which live outside `.pi/`:
169
+ ### Agent team
574
170
 
575
- ```
576
- .claude/settings.json the "hooks" key only; every other key is preserved
577
- .codex-local/hooks.json the whole file
578
- ```
171
+ `@agimon-ai/doompi-team` runs named subagents asynchronously against a shared task board.
172
+ They can work in parallel, message one another, and use the model policy attached to the
173
+ selected Team package entry. `SPC a l` lists available agents; `SPC a r` opens current-session runs and
174
+ their controls.
579
175
 
580
- Edit `hooks.yaml` and re-run `doompi sync` to regenerate both.
176
+ ### Tasks
581
177
 
582
- ## Selection
178
+ `@agimon-ai/doompi-task` keeps a task graph on disk, not a disposable checklist in the
179
+ transcript. Dependencies and delegation survive compaction, and work can be handed to a
180
+ subagent—including a smaller model when the job does not need the expensive one.
583
181
 
584
- Use singular `--profile <name>` for one persona and its environment defaults. Use
585
- `--domains <name[,name...]>` for plugin bundles. Both `--major-mode` and `--profile` take
586
- exactly one name, reject a comma, and can be given only once. The removed `--layer`,
587
- `--layers`, `--profiles`, and `--target` flags throw and point at the replacement, and so
588
- does the removed `DOOMPI_LAYER` variable: a stale export is worth an error rather than a
589
- session quietly starting on the default.
182
+ ### Auto-compact
590
183
 
591
- Defaults when you name nothing: `defaultMajorMode` from `.doom/modes.yaml` and the
592
- `defaultDomains` list from `.doom/domains.yaml`. Omitting those fields preserves the
593
- compatible fallbacks: major mode `copilot`, plus domain `marketing` for the legacy
594
- `marketing` mode or `default` for every other mode.
184
+ Ordinary compaction waits for one summary to save an overgrown session.
185
+ `@agimon-ai/doompi-autocompact` leaves checkpoints instead:
595
186
 
596
- Environment equivalents are `DOOMPI_MAJOR_MODE`, `DOOMPI_DOMAINS`, and `DOOMPI_PROFILE`.
597
- `DOOMPI_PRESET` and `DOOMPI_ADDITIONAL_DIRS` work the same way for `--preset` and
598
- `--add-dir`. An empty `DOOMPI_DOMAINS` means no domains, not the default. No profile is
599
- selected unless one is named.
187
+ 1. At 50%, it writes the first compact summary.
188
+ 2. Later, it combines that summary with the messages since; the model decides whether the
189
+ result is ready to use.
190
+ 3. On the third pass, it combines them again and forces compaction.
600
191
 
601
- There is one variable per axis. A nested run inherits its launcher's choice because the
602
- launcher writes the resolved value into the child environment under the same name, so
603
- anything you exported yourself is what a top-level run uses and what a spawned run
604
- overrides. These used to be two variables, `DOOM_PI_*` for the selection and
605
- `AGENT_HARNESS_*` for the resolved projection, with the second outranking the first. Any
606
- surviving `AGENT_HARNESS_*` variable now throws and names its replacement, because a stale
607
- export in a shell profile is worth an error.
192
+ The work runs off-thread. Each checkpoint carries the live plan, task graph, team state,
193
+ and user request with it, so coordination does not have to be guessed back out of prose.
608
194
 
609
- `DOOMPI_LAYERS`, plural, is a different variable from `DOOMPI_LAYER`. It carries the
610
- resolved layer components rather than the mode that selected them.
195
+ ### MCP
611
196
 
612
- A repository can also pin the full selection used by a bare `doompi sync` or
613
- `doompi build`. A flag still wins, then an exported variable, then the matching
614
- `selection` field, then `modes.yaml`'s `defaultMajorMode` or `domains.yaml`'s
615
- `defaultDomains`:
197
+ `@agimon-ai/doompi-mcp` is the gate between a session and its servers. It reads `.mcp.json`
198
+ and other common formats, then exposes only the servers and proxy upstreams allowed by the
199
+ selected domains. Switch domains and that boundary reloads with them.
616
200
 
617
- ```yaml
618
- # .doom/config.yaml
619
- selection:
620
- majorMode: dev
621
- domains: [development]
622
- profile: house-voice
623
- ```
201
+ ### Ask user question
202
+
203
+ `@agimon-ai/doompi-user-feedback` gives the agent a structured question that actually
204
+ waits for an answer. In autonomous Voice mode it skips the modal, narrates the choices,
205
+ and accepts the next spoken response as an ordinary user message.
206
+
207
+ ### Logging and telemetry
208
+
209
+ Doompi telemetry records counters and spans, never prompt text or file content, in a local
210
+ SQLite database by default. `SPC h l` opens the metrics, and `@agimon-ai/log-sink-mcp`
211
+ gives the agent CLI tools for inspecting its own runs.
212
+
213
+ ### Plan mode
214
+
215
+ A promise to "only plan" is not a permission boundary. `@agimon-ai/doompi-plan` makes the
216
+ repository read-only while the agent explores, persists the plan, and hands it back for
217
+ approval. Use `SPC p p` for normal planning, `SPC p d` for debug planning, `SPC p f` for
218
+ the Fable flow, and `SPC p e` to exit. Turn it on when the approach should be settled
219
+ before the files move.
220
+
221
+ ### Loop mode
222
+
223
+ `@agimon-ai/doompi-loop` is an in-session scheduler. It runs a prompt immediately and then
224
+ repeats it on an interval; several loops can coexist. Use `SPC l s` to start one and
225
+ `SPC l l` to list or stop them. It is for recurring checks and prompts that belong to the
226
+ current session.
227
+
228
+ ### Goal mode
229
+
230
+ `@agimon-ai/doompi-goal` pins one objective to the session until it completes or you end
231
+ it. Use `SPC g g` for status, `SPC g s` to start, `SPC g e` to end, and `SPC g p` for
232
+ history. Finished goals leave the prompt and tools behind but remain in history when you
233
+ want to restart one.
234
+
235
+ ### Workflow mode
236
+
237
+ `@agimon-ai/doompi-workflow` runs GitHub Actions-style job graphs with dependencies,
238
+ timeouts, artifacts, and a separate Doompi session for each step. Use `SPC w w` to launch,
239
+ `SPC w l` to manage, `SPC w r` to recover a failed run, and `SPC w e` to give the agent
240
+ workflow tools or take them back. It is for work that needs hard job boundaries and
241
+ explicit handoffs rather than one long conversation.
242
+
243
+ ### Voice mode
624
244
 
625
- Pi asks once before it trusts a project and loads `.pi` resources, so a synced session
626
- prompts on first run regardless of `projectTrust`, which reaches Pi as a launcher flag.
627
- Answer yes, or run `/trust`.
245
+ `@agimon-ai/doompi-voice` records and transcribes speech locally; audio stays on the
246
+ machine. Use `SPC v v` for one recording or `SPC v a` to toggle autonomous capture. It
247
+ replaces the keyboard without replacing the work already in progress.