@jmanuelcorral/openteam 0.18.1 → 0.20.0

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 (162) hide show
  1. package/.opencode/openteam.example.json +23 -14
  2. package/AGENTS.md +16 -2
  3. package/README.es.md +300 -209
  4. package/README.md +300 -139
  5. package/dist/capabilities/classify.d.ts.map +1 -1
  6. package/dist/capabilities/normalize.d.ts.map +1 -1
  7. package/dist/certificates/graph-release-certificate.json +2 -2
  8. package/dist/certificates/graph-shadow-certificate.json +2 -2
  9. package/dist/cli/consoleServe.d.ts +16 -0
  10. package/dist/cli/consoleServe.d.ts.map +1 -1
  11. package/dist/cli/graphOperator.d.ts +10 -1
  12. package/dist/cli/graphOperator.d.ts.map +1 -1
  13. package/dist/cli/lifecycleHistoryProvider.d.ts +21 -0
  14. package/dist/cli/lifecycleHistoryProvider.d.ts.map +1 -0
  15. package/dist/cli.d.ts +3 -1
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +6841 -1555
  18. package/dist/commands/agents.d.ts +16 -4
  19. package/dist/commands/agents.d.ts.map +1 -1
  20. package/dist/commands/dispatch.d.ts.map +1 -1
  21. package/dist/commands/doctor.d.ts.map +1 -1
  22. package/dist/commands/local.d.ts +4 -5
  23. package/dist/commands/local.d.ts.map +1 -1
  24. package/dist/commands/report.d.ts +6 -6
  25. package/dist/commands/report.d.ts.map +1 -1
  26. package/dist/commands/rolesInit.d.ts +10 -12
  27. package/dist/commands/rolesInit.d.ts.map +1 -1
  28. package/dist/commands/setup.d.ts +5 -13
  29. package/dist/commands/setup.d.ts.map +1 -1
  30. package/dist/commands/yolo.d.ts +2 -4
  31. package/dist/commands/yolo.d.ts.map +1 -1
  32. package/dist/config/errors.d.ts.map +1 -1
  33. package/dist/config/executionPolicyDomains.d.ts +18 -0
  34. package/dist/config/executionPolicyDomains.d.ts.map +1 -0
  35. package/dist/config/migrations.d.ts +9 -2
  36. package/dist/config/migrations.d.ts.map +1 -1
  37. package/dist/config/persist.d.ts.map +1 -1
  38. package/dist/config/runtime.d.ts +2 -0
  39. package/dist/config/runtime.d.ts.map +1 -1
  40. package/dist/config/schema.d.ts +128 -13
  41. package/dist/config/schema.d.ts.map +1 -1
  42. package/dist/console/assets.d.ts +22 -1
  43. package/dist/console/assets.d.ts.map +1 -1
  44. package/dist/console/configView.d.ts +2 -7
  45. package/dist/console/configView.d.ts.map +1 -1
  46. package/dist/console/render.d.ts +12 -0
  47. package/dist/console/render.d.ts.map +1 -1
  48. package/dist/console/types.d.ts +2 -2
  49. package/dist/console/types.d.ts.map +1 -1
  50. package/dist/context/schema.d.ts +13 -7
  51. package/dist/context/schema.d.ts.map +1 -1
  52. package/dist/graph/certificate.d.ts.map +1 -1
  53. package/dist/graph/langgraph/adapter.d.ts +7 -8
  54. package/dist/graph/langgraph/adapter.d.ts.map +1 -1
  55. package/dist/graph/langgraph/certification.d.ts +5 -0
  56. package/dist/graph/langgraph/certification.d.ts.map +1 -1
  57. package/dist/graph/langgraph/checkpointReplay.d.ts.map +1 -1
  58. package/dist/graph/langgraph/engine.d.ts +2 -2
  59. package/dist/graph/langgraph/engine.d.ts.map +1 -1
  60. package/dist/graph/langgraph/types.d.ts +11 -6
  61. package/dist/graph/langgraph/types.d.ts.map +1 -1
  62. package/dist/graph/privacyPolicy.d.ts +6 -30
  63. package/dist/graph/privacyPolicy.d.ts.map +1 -1
  64. package/dist/index.d.ts +1 -9
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +5992 -1390
  67. package/dist/lifecycle/contracts.d.ts +618 -0
  68. package/dist/lifecycle/contracts.d.ts.map +1 -0
  69. package/dist/lifecycle/historyReader.d.ts +11 -0
  70. package/dist/lifecycle/historyReader.d.ts.map +1 -0
  71. package/dist/lifecycle/historySourceError.d.ts +7 -0
  72. package/dist/lifecycle/historySourceError.d.ts.map +1 -0
  73. package/dist/lifecycle/project.d.ts +3 -0
  74. package/dist/lifecycle/project.d.ts.map +1 -0
  75. package/dist/lifecycle/root.d.ts +3 -0
  76. package/dist/lifecycle/root.d.ts.map +1 -0
  77. package/dist/lifecycle/runtimeRecorder.d.ts +6 -0
  78. package/dist/lifecycle/runtimeRecorder.d.ts.map +1 -0
  79. package/dist/lifecycle/schema.d.ts +487 -0
  80. package/dist/lifecycle/schema.d.ts.map +1 -0
  81. package/dist/mcp/teamMemory.d.ts +2 -2
  82. package/dist/mcp/teamMemory.d.ts.map +1 -1
  83. package/dist/messages/commands.d.ts +22 -35
  84. package/dist/messages/commands.d.ts.map +1 -1
  85. package/dist/messages/executionSetup.d.ts +116 -0
  86. package/dist/messages/executionSetup.d.ts.map +1 -0
  87. package/dist/messages/index.d.ts +2 -0
  88. package/dist/messages/index.d.ts.map +1 -1
  89. package/dist/messages/lifecycle.d.ts +95 -0
  90. package/dist/messages/lifecycle.d.ts.map +1 -0
  91. package/dist/messages/lifecycleRuntime.d.ts +4 -0
  92. package/dist/messages/lifecycleRuntime.d.ts.map +1 -0
  93. package/dist/messages/lifecycleStorage.d.ts +4 -0
  94. package/dist/messages/lifecycleStorage.d.ts.map +1 -0
  95. package/dist/messages/memoryTool.d.ts +17 -0
  96. package/dist/messages/memoryTool.d.ts.map +1 -0
  97. package/dist/messages/orchestration.d.ts +16 -0
  98. package/dist/messages/orchestration.d.ts.map +1 -0
  99. package/dist/messages/routing.d.ts +9 -0
  100. package/dist/messages/routing.d.ts.map +1 -0
  101. package/dist/opencodeArtifacts/orchestratorAgent.d.ts +3 -5
  102. package/dist/opencodeArtifacts/orchestratorAgent.d.ts.map +1 -1
  103. package/dist/orchestrator/coordinator.d.ts +30 -13
  104. package/dist/orchestrator/coordinator.d.ts.map +1 -1
  105. package/dist/orchestrator/graphEffects.d.ts +10 -11
  106. package/dist/orchestrator/graphEffects.d.ts.map +1 -1
  107. package/dist/orchestrator/graphIngress.d.ts +5 -0
  108. package/dist/orchestrator/graphIngress.d.ts.map +1 -1
  109. package/dist/orchestrator/graphRuntime.d.ts +13 -1
  110. package/dist/orchestrator/graphRuntime.d.ts.map +1 -1
  111. package/dist/orchestrator/outputContract.d.ts +5 -6
  112. package/dist/orchestrator/outputContract.d.ts.map +1 -1
  113. package/dist/orchestrator/roles.d.ts +25 -2
  114. package/dist/orchestrator/roles.d.ts.map +1 -1
  115. package/dist/orchestrator/runtimeSelection.d.ts.map +1 -1
  116. package/dist/orchestrator/subsessions.d.ts +52 -1
  117. package/dist/orchestrator/subsessions.d.ts.map +1 -1
  118. package/dist/plugin/availability.d.ts +15 -2
  119. package/dist/plugin/availability.d.ts.map +1 -1
  120. package/dist/plugin/diagnostics.d.ts +1 -0
  121. package/dist/plugin/diagnostics.d.ts.map +1 -1
  122. package/dist/plugin/hooks.d.ts +21 -2
  123. package/dist/plugin/hooks.d.ts.map +1 -1
  124. package/dist/plugin/memoryTool.d.ts +3 -3
  125. package/dist/plugin/memoryTool.d.ts.map +1 -1
  126. package/dist/plugin/modelOwnership.d.ts +15 -0
  127. package/dist/plugin/modelOwnership.d.ts.map +1 -0
  128. package/dist/plugin/orchestrateTool.d.ts +53 -3
  129. package/dist/plugin/orchestrateTool.d.ts.map +1 -1
  130. package/dist/router/chooseModel.d.ts.map +1 -1
  131. package/dist/router/cost.d.ts +1 -1
  132. package/dist/router/cost.d.ts.map +1 -1
  133. package/dist/router/executionPolicy.d.ts +44 -0
  134. package/dist/router/executionPolicy.d.ts.map +1 -0
  135. package/dist/router/frontierBaseline.d.ts.map +1 -1
  136. package/dist/router/types.d.ts +10 -4
  137. package/dist/router/types.d.ts.map +1 -1
  138. package/dist/storage/lifecycle/codec.d.ts +19 -0
  139. package/dist/storage/lifecycle/codec.d.ts.map +1 -0
  140. package/dist/storage/lifecycle/fsLifecycleJournal.d.ts +3 -0
  141. package/dist/storage/lifecycle/fsLifecycleJournal.d.ts.map +1 -0
  142. package/dist/storage/lifecycle/fsLifecycleSupport.d.ts +17 -0
  143. package/dist/storage/lifecycle/fsLifecycleSupport.d.ts.map +1 -0
  144. package/dist/storage/lifecycle/fsLifecycleWriterLivenessProbe.d.ts +3 -0
  145. package/dist/storage/lifecycle/fsLifecycleWriterLivenessProbe.d.ts.map +1 -0
  146. package/dist/telemetry/aggregate.d.ts +4 -4
  147. package/dist/telemetry/aggregate.d.ts.map +1 -1
  148. package/dist/telemetry/diagnostics.d.ts.map +1 -1
  149. package/dist/telemetry/events.d.ts +6 -6
  150. package/dist/telemetry/otel.d.ts.map +1 -1
  151. package/dist/telemetry/types.d.ts +3 -3
  152. package/dist/web/configApi.d.ts +3 -1
  153. package/dist/web/configApi.d.ts.map +1 -1
  154. package/dist/web/graphRoute.d.ts +39 -1
  155. package/dist/web/graphRoute.d.ts.map +1 -1
  156. package/dist/web/server.d.ts +19 -0
  157. package/dist/web/server.d.ts.map +1 -1
  158. package/dist/web/start.d.ts +11 -0
  159. package/dist/web/start.d.ts.map +1 -1
  160. package/dist/web/watch.d.ts +17 -2
  161. package/dist/web/watch.d.ts.map +1 -1
  162. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 🌐 **Language / Idioma:** **English** (this document) · [Español](README.es.md)
4
4
 
5
- **openteam — a cost-aware, local-first routing plugin for [opencode](https://opencode.ai) with cheapest-capable FRONTIER fallback and on-demand multi-agent orchestration.**
5
+ **openteam — operator-controlled local, frontier, and mixed model execution for [opencode](https://opencode.ai), with cheapest-capable automatic selection and on-demand multi-agent orchestration.**
6
6
 
7
7
  [![CI](https://github.com/jmanuelcorral/openteam/actions/workflows/ci.yml/badge.svg)](https://github.com/jmanuelcorral/openteam/actions/workflows/ci.yml)
8
8
  [![npm](https://img.shields.io/npm/v/@jmanuelcorral/openteam.svg)](https://www.npmjs.com/package/@jmanuelcorral/openteam)
@@ -35,21 +35,29 @@
35
35
 
36
36
  ## What it is and why it exists
37
37
 
38
- openteam adds a routing and orchestration layer to opencode to **save frontier tokens and cost without sacrificing quality or privacy**. The default strategy is **local-first**: trivial tasks, summaries, simple docs and small diffs try local runtimes first; architecture, security, multi-file refactors, critical tool calling and ambiguous debugging escalate to **cheapest-capable FRONTIER**.
38
+ openteam adds a routing and orchestration layer to opencode. The operator chooses
39
+ a global **local**, **frontier**, or **mixed** execution domain; primary and
40
+ worker policies can select an exact model or use cheapest-capable `"auto"`
41
+ selection inside that domain.
39
42
 
40
- The router keeps a privacy guarantee: with `privacyMode: "forceLocalOnSensitive"`, a task flagged as `privacySensitive` never escalates to frontier.
43
+ openteam does **not** inspect prompt content to infer sensitivity before a
44
+ frontier call. Frontier egress is controlled explicitly: use `local` globally,
45
+ or narrow an agent to `local` under global `mixed`, when frontier is prohibited.
41
46
 
42
47
  ## Features
43
48
 
44
49
  - Per-turn routing from the `chat.message` hook, mutating `output.message.model` before opencode persists the message.
45
50
  - Registration of OpenAI-compatible local runtimes: **Ollama**, **LM Studio**, **Lemonade Server** and **Foundry Local**.
46
- - Local JSON classifier with a deterministic heuristic fallback.
47
51
  - Capability profiles from [Models.dev](https://models.dev), with cache and a curated fallback when the catalog is unavailable.
48
- - **Cheapest-capable** frontier selection by blended cost `0.75 * inputUSD + 0.25 * outputUSD`.
52
+ - **Cheapest-capable** automatic selection by cost, availability, tool support, and context requirements inside the allowed domain.
49
53
  - Budgets per session, per month and per frontier tokens per session.
50
54
  - Hash-only `CostRecord` JSONL telemetry: stores `promptHash`, cost/decision metrics — **never raw prompts**.
51
- - Multi-agent orchestration with `runRoleTask` / `runRoleTasks`, SDK child sessions and an explicit model in `client.session.prompt({ body: { model } })`.
55
+ - Per-agent exact pins or `"auto"`, optional ordered fallbacks, and bounded cross-model handoff for an incomplete node.
56
+ - Multi-agent orchestration with SDK child sessions and an explicit model in `client.session.prompt({ body: { model } })`.
52
57
  - Local availability refresh at startup and on `session.created` / `session.idle` events.
58
+ - A metadata-only [agent timeline](docs/guide/agent-timeline.md) with durable
59
+ coordinator/attempt history, historical cursor inspection, and live
60
+ invalidation hints.
53
61
  - A local web **Console** aggregating every opencode session (cost, tokens, tool calls, routing, decisions) — redacted, loopback-only.
54
62
  - A read-only **MCP server** exposing aggregated team memory, plus opt-in semantic recall.
55
63
 
@@ -89,46 +97,16 @@ For direct opencode loading, reference the package name:
89
97
  "$schema": "https://opencode.ai/config.json",
90
98
  "model": "anthropic/claude-sonnet-4-5",
91
99
  "small_model": "ollama/qwen3:8b",
92
- "plugin": [
93
- [
94
- "@jmanuelcorral/openteam",
95
- {
96
- "baseline": {
97
- "mode": "auto",
98
- "pinnedModel": null,
99
- "hardDefault": { "providerID": "anthropic", "modelID": "claude-sonnet-4-5" }
100
- },
101
- "router": {
102
- "mode": "balanced",
103
- "localDefault": { "providerID": "ollama", "modelID": "qwen3:8b" },
104
- "trivialPromptMaxChars": 280,
105
- "frontierPromptMinChars": 2000
106
- },
107
- "local": {
108
- "runtimes": [
109
- {
110
- "id": "ollama",
111
- "enabled": true,
112
- "baseURL": "http://localhost:11434/v1",
113
- "defaultModel": { "providerID": "ollama", "modelID": "qwen3:8b" }
114
- }
115
- ]
116
- },
117
- "budgets": {
118
- "sessionUSD": 2,
119
- "monthlyUSD": 30,
120
- "frontierTokensPerSession": 250000,
121
- "hardStopOnBudgetExhaustion": false
122
- },
123
- "privacyMode": "forceLocalOnSensitive",
124
- "telemetry": { "enabled": true, "path": ".opencode/openteam-local/telemetry.jsonl" }
125
- }
126
- ]
127
- ]
100
+ "plugin": ["@jmanuelcorral/openteam"]
128
101
  }
129
102
  ```
130
103
 
131
- The fastest way to get a repository ready is the interactive `openteam setup` command (below), which generates this configuration for you.
104
+ Those model identifiers must exist in your configured opencode providers;
105
+ replace them with available equivalents when needed.
106
+
107
+ Keep operator routing policy in `.opencode/openteam.json`, not in the plugin
108
+ declaration. The fastest way to create both files is the interactive
109
+ `openteam setup` command below.
132
110
 
133
111
  ### Option B: development path
134
112
 
@@ -137,33 +115,29 @@ During development you can load a local plugin from `.opencode/plugins/openteam.
137
115
  ```jsonc
138
116
  {
139
117
  "$schema": "https://opencode.ai/config.json",
140
- "plugin": [
141
- [
142
- "./.opencode/plugins/openteam.ts",
143
- {
144
- "router": {
145
- "mode": "balanced",
146
- "localDefault": {
147
- "providerID": "ollama",
148
- "modelID": "qwen3:8b"
149
- }
150
- },
151
- "telemetry": {
152
- "path": ".opencode/openteam-local/telemetry.jsonl"
153
- }
154
- }
155
- ]
156
- ]
118
+ "plugin": ["./.opencode/plugins/openteam.ts"]
157
119
  }
158
120
  ```
159
121
 
160
- ## Quickstart: `openteam setup`
122
+ Development-path loading does not change the configuration boundary: keep
123
+ operator policy in `.opencode/openteam.json`.
161
124
 
162
- `openteam setup` detects the installed local runtimes (Ollama, LM Studio, Lemonade Server, Foundry Local — all optional), lets you pick the local models and the **frontier baseline** model (sorted cheapest-capable first), and writes three files:
125
+ ## Quickstart: `openteam setup`
163
126
 
164
- - `opencode.json` at the repo root: declares the `@jmanuelcorral/openteam` plugin, the OpenAI-compatible local `provider`s, the frontier `model` and the local `small_model`.
165
- - `.opencode/openteam.json`: openteam's validated runtime configuration (baseline, routing, local runtimes, orchestrator roles and privacy).
127
+ `openteam setup` detects the installed local runtimes (Ollama, LM Studio,
128
+ Lemonade Server, Foundry Local — all optional), lets you pick local models and
129
+ the **frontier baseline** (sorted cheapest-capable first), and writes the
130
+ project configuration and generated agent/command artifacts:
131
+
132
+ - `.opencode/opencode.json` for a new project, or the existing
133
+ `.opencode/opencode.json` / root `opencode.json`: declares the plugin, local
134
+ providers, frontier `model`, and local `small_model`.
135
+ - `.opencode/openteam.json`: openteam's validated runtime configuration
136
+ (execution domain, local runtimes, primary/role model policies, budgets, and
137
+ bounded retries/handoffs).
166
138
  - `.opencode/agent/openteam.md`: the **primary `openteam` agent**, visible in the Tab switcher.
139
+ - `.opencode/agent/<role>.md` for the standard dispatch targets, plus generated
140
+ `.opencode/command/*.md` entries and the local `.opencode/.gitignore`.
167
141
 
168
142
  ```powershell
169
143
  # One-off, without a global install
@@ -176,25 +150,66 @@ openteam setup
176
150
 
177
151
  The wizard:
178
152
 
179
- 1. Probes local runtimes and marks the detected ones.
180
- 2. Lets you enable runtimes (or **none** — frontier-only) and, per enabled runtime, choose whether it runs **on this machine** or **on another machine on the LAN** (by entering its URL, e.g. `http://192.168.1.50:11434/v1`), then pick each default model. If you run **more than one machine of the same kind** (e.g. two LM Studio hosts), it declares each as a separate instance with its own id (`lmstudio-a`, `lmstudio-b`), URL and models. For every instance it also asks how many **concurrent generation slots** the runtime has (defaulting to `4`), so you can leave a slot free for interactive work instead of saturating the server.
181
- 3. Offers the frontier baseline from the **full [Models.dev](https://models.dev) catalog** that openteam integrates: a quick list with the **cheapest-capable first** (including **free** models, tagged `free`), a "Browse all models by provider…" option, and "Other (custom)" to type a `provider/model`. If the catalog can't be loaded (offline), it falls back to the curated list.
182
- 4. Asks for the routing mode (`balanced` / `economy` / `quality`), the privacy policy, and whether to enable **YOLO mode** (auto-approve opencode permissions). If you enable no local runtime, it warns you are in **frontier-only mode** and recommends `consentBeforeFrontier`.
183
- 5. If `opencode.json` or `.opencode/openteam.json` already exist, it asks for confirmation before overwriting.
184
-
185
- After `setup`, authenticate the frontier provider (`opencode auth login`), make sure your local runtime is reachable if you enabled one, and open opencode in the repo: openteam routes local-first automatically. Re-tune the baseline later with `openteam baseline set <provider/model>` or `openteam baseline auto`.
153
+ 1. Probes local runtimes, then first asks which ones to enable. Choosing
154
+ **None** selects global `frontier` directly; it does not ask a redundant
155
+ three-way mode question and proceeds to frontier model selection.
156
+ 2. For each enabled runtime, choose this machine or another LAN host, its
157
+ default/thinking/coder models, and concurrency. Multiple instances retain
158
+ distinct runtime and provider identities.
159
+ 3. With at least one local runtime, choose the team execution mode (default
160
+ `mixed`). `local` pins the primary to the first enabled runtime's thinking
161
+ model or default; `frontier` asks for a frontier model; `mixed` asks whether
162
+ the primary uses that local model or the selected frontier model. Frontier
163
+ choices come from Models.dev cheapest-capable ordering, with browse/custom
164
+ and curated-offline fallbacks.
165
+ 4. Setup writes an exact primary policy with `fallbacks: []`, persists each
166
+ worker as the compact partial policy `{ "model": "auto" }`, and writes the
167
+ `2`/`2` retry/model limits. Primary `"auto"` and primary fallbacks are
168
+ supported by the schema and manual config, but setup does not prompt for
169
+ them.
170
+ 5. Choose whether to enable **YOLO mode**. Existing config files require
171
+ overwrite confirmation.
172
+
173
+ After `setup`, authenticate a frontier provider if the configured policies allow
174
+ frontier, make sure each configured local runtime is reachable, and open
175
+ opencode in the repo. Re-tune the baseline later with
176
+ `openteam baseline set <provider/model>` or `openteam baseline auto`.
177
+
178
+ > **Upgrade note:** `privacyMode` is deprecated, ignored metadata and no longer prevents
179
+ > frontier execution. Convert any required restriction to
180
+ > `router.executionMode: "local"` or an agent `executionMode: "local"` under
181
+ > global `mixed`.
186
182
 
187
183
  ## The `openteam` agent
188
184
 
189
- `openteam setup` generates a single **primary agent** named `openteam`. Press **Tab** in opencode to select it: you explicitly know you are in openteam mode while the plugin keeps optimizing the model underneath.
190
-
191
- That orchestrator ships **no pre-created subagents**. On **every request** its first action is to check whether a team already exists (`.opencode/openteam/roster.md` + subagents in `.opencode/agent/`). If it does, it hands work to the right specialists; if not, it understands the tasks your request implies, designs the minimal team and **creates it on demand** (writing `.opencode/agent/<name>.md` with `mode: subagent`), reusing existing agents before creating new ones. Mechanical/bulk roles are left to route to local models; only architecture, security or ambiguous debugging escalate to frontier. (This on-demand, themed-team model is inspired by [Squad](https://bradygaster.github.io/squad/) — see [Acknowledgements](#acknowledgements).)
185
+ `openteam setup` generates a single **primary agent** named `openteam`. Press
186
+ **Tab** in opencode to select it: you explicitly know you are in openteam mode.
187
+ Its model follows the resolved global and primary-agent policy; the prompt does
188
+ not select a domain by being judged sensitive or complex.
189
+
190
+ The generated primary frontmatter and `opencode.json.model` use the same exact
191
+ bootstrap model. Generated workers intentionally omit frontmatter `model`; their
192
+ policies in `.opencode/openteam.json` are authoritative. Guardian, scribe, and
193
+ ralph are not forced local by name.
194
+
195
+ That orchestrator ships **no project-specific pre-created subagents**. On every
196
+ request it checks whether a team already exists
197
+ (`.opencode/openteam/roster.md` + subagents in `.opencode/agent/`). If needed,
198
+ it designs the minimal team and creates it on demand, reusing existing agents
199
+ first. Model placement comes from the global and per-agent policies, not from
200
+ automatic sensitivity classification. The unified router may still derive
201
+ capability and complexity requirements inside the permitted domain. (This
202
+ on-demand, themed-team model is inspired by
203
+ [Squad](https://bradygaster.github.io/squad/) — see
204
+ [Acknowledgements](#acknowledgements).)
192
205
 
193
206
  The team has a **themed identity**: the orchestrator picks (or asks for) a **universe** and names each agent after a character, recording the cast in `.opencode/openteam/roster.md` so names **persist** across sessions. Besides project-specific roles, it plans three **standard roles** created on demand:
194
207
 
195
- - **scribe** — silent memory: logs decisions and learnings to `.opencode/openteam/decisions.md` without touching code (local model).
208
+ - **scribe** — silent memory: logs decisions and learnings to `.opencode/openteam/decisions.md` without touching code.
196
209
  - **ralph** — automation and triage: monitors the backlog, prioritizes, coordinates execution and **escalates to the human** on blockers or approvals.
197
- - **guardian** — security and privacy: **detects and masks sensitive data** (PII, secrets, API keys, tokens) before it reaches frontier; always runs locally and respects `forceLocalOnSensitive`.
210
+ - **guardian** — security review and operator-policy advice. It is not an
211
+ automatic pre-frontier content gate and has no built-in local requirement.
212
+ Configure `executionMode: "local"` explicitly if the role must stay local.
198
213
 
199
214
  Before delegating, the orchestrator **plans dependencies and parallelizes**: it splits the request into tasks and, since opencode runs all `task` calls emitted in a single turn in parallel (Vercel AI SDK), it launches independent tasks together and sequences only the dependent ones into waves. Work that collides on the same files is separated into distinct waves to avoid conflicts. In the **todo list** each entry shows the responsible agent via a name prefix, e.g. `[@Basher] Implement the router`.
200
215
 
@@ -230,7 +245,9 @@ To install and try openteam on another machine that already has [opencode](https
230
245
 
231
246
  4. **Start your local runtime** if you enabled one and it was not already up (e.g. `ollama serve`, or open LM Studio / Foundry Local). If you pointed at a runtime on **another machine on the LAN**, make sure it is reachable from this machine (host exposed and port open); for Ollama this usually means starting it with `OLLAMA_HOST=0.0.0.0 ollama serve` on the remote machine. If you configured **frontier-only**, skip this step.
232
247
 
233
- 5. **Open opencode in the repo**, press **Tab** and pick the `openteam` agent. Give it the first prompt: it will create the subagent team on demand and route local-first automatically.
248
+ 5. **Open opencode in the repo**, press **Tab** and pick the `openteam` agent.
249
+ Give it the first prompt: it creates the subagent team on demand and runs
250
+ each agent within the configured execution domain.
234
251
 
235
252
  Check the status with `openteam doctor` (runtimes + config) and the savings with `openteam report`.
236
253
 
@@ -260,11 +277,19 @@ Notes when updating:
260
277
 
261
278
  ## Configuration
262
279
 
263
- The canonical runtime configuration lives in `.opencode/openteam.json`; this repo ships `.opencode/openteam.example.json` as a complete example. The Zod-validated shape is in `src/config/schema.ts`. `telemetry` is a plugin option read by `src/index.ts`, not part of the persisted `OpenTeamConfig`.
280
+ The canonical runtime configuration lives in `.opencode/openteam.json`; this
281
+ repo ships `.opencode/openteam.example.json` as a complete minimal v2 example,
282
+ not as a byte-for-byte contract for every setup choice. Canonical v2
283
+ serialization is compact: it omits default legacy heuristic values, retains
284
+ non-default heuristic overrides for policy-less compatibility, and compacts
285
+ resolved role profiles to partial policies relative to their built-in or
286
+ synthesized baselines. The Zod-validated shape is in `src/config/schema.ts`.
287
+ `telemetry` is a plugin option read by `src/index.ts`, not part of the persisted
288
+ `OpenTeamConfig`.
264
289
 
265
290
  Config files are strict: unknown keys fail closed in both the CLI and plugin
266
291
  instead of being silently ignored. Errors name each rejected path (for example
267
- `router.trivialPromptMxChars`) and include a nearest-key hint when available.
292
+ `router.executionMod`) and include a nearest-key hint when available.
268
293
  This strict validation applies to the config file on both paths; programmatic
269
294
  plugin options are a separate surface and are not yet strictly validated (#102).
270
295
  Existing configs without `configVersion` remain valid because openteam migrates
@@ -310,13 +335,15 @@ configured" and should `cd` to the project root first.
310
335
 
311
336
  | Key | Type | Default | Description |
312
337
  | --- | --- | --- | --- |
338
+ | `configVersion` | `2`, optional input | canonical `2` | Missing means v1 and migrates before validation. Non-integer or below `1` is unknown; above `2` is unsupported. |
313
339
  | `baseline.mode` | `"auto" \| "pinned"` | `"auto"` | In `auto`, uses cheapest-capable frontier profiles when passed to the router; in `pinned`, uses `baseline.pinnedModel` if set. |
314
340
  | `baseline.pinnedModel` | `ModelRef \| null` | `null` | Explicitly pinned frontier model. `ModelRef` is `{ "providerID": string, "modelID": string }`. |
315
- | `baseline.hardDefault` | `ModelRef` | `{ "providerID": "anthropic", "modelID": "claude-sonnet-4-5" }` | Frontier fallback when there are no cheapest-capable profiles or a valid pin. |
316
- | `router.mode` | `"economy" \| "balanced" \| "quality"` | `"balanced"` | Validated routing mode reserved for policy. |
341
+ | `baseline.hardDefault` | `ModelRef` | `{ "providerID": "google", "modelID": "gemini-3.5-flash" }` | Operator-configurable offline/curated fallback when no usable live or catalog profile drives automatic selection. It is not preferred over supplied profiles; `setup` may write the selected frontier model, and explicit per-agent pins remain allowed. |
342
+ | `router.executionMode` | `"local" \| "frontier" \| "mixed"`, optional | effective `"mixed"` | Hard global execution-domain upper bound. Canonical serialization writes `"mixed"` when omitted. Agent policy may narrow `mixed`, never broaden `local` or `frontier`. |
343
+ | `router.mode` | `"economy" \| "balanced" \| "quality"` | `"balanced"` | Canonical legacy heuristic setting for callers with no v2 agent policy. It is not per-agent model selection and never overrides `executionMode` or adds sensitivity routing. |
344
+ | `router.trivialPromptMaxChars` | positive integer | `280` | Legacy policy-less mixed-routing threshold. Retained for compatible callers that supply no v2 agent policy; generated v2 policies do not rely on it. |
345
+ | `router.frontierPromptMinChars` | positive integer | `2000` | Legacy policy-less mixed-routing threshold. Retained for compatible callers that supply no v2 agent policy; generated v2 policies do not rely on it. |
317
346
  | `router.localDefault` | `ModelRef \| null` | `null` | Primary local model. Not set by default — no local model is routed until you configure one via `openteam setup` or by hand. |
318
- | `router.trivialPromptMaxChars` | positive integer | `280` | Code-free prompts up to this size prefer local. |
319
- | `router.frontierPromptMinChars` | positive integer | `2000` | Prompts from this size are frontier candidates if no guards apply. |
320
347
  | `local.runtimes` | `LocalRuntime[]` | `[]` | Local runtimes to probe. Empty by default — no runtime is configured until you run `openteam setup` or add an entry by hand. |
321
348
  | `local.runtimes[].id` | string | required | Instance identity, unique within the array. Free-form (`lmstudio-a`, `lemonade-lan`) so two machines of the same kind are distinct; when `kind` is omitted it must itself be a runtime kind (back-compat). |
322
349
  | `local.runtimes[].kind` | `"ollama" \| "lmstudio" \| "foundry-local" \| "lemonade"` optional | migrates from `id` | Runtime kind selecting the adapter. Optional: when absent, `id` is treated as the kind, so pre-existing configs keep working unchanged. |
@@ -329,57 +356,118 @@ configured" and should `cd` to the project root first.
329
356
  | `budgets.sessionUSD` | positive number, optional | unlimited | Frontier budget per session. |
330
357
  | `budgets.monthlyUSD` | positive number, optional | unlimited | Monthly frontier budget. |
331
358
  | `budgets.frontierTokensPerSession` | positive integer, optional | unlimited | Frontier token limit per session. |
332
- | `budgets.hardStopOnBudgetExhaustion` | `boolean` | `false` | If `true`, an exhausted budget returns `blockFrontier`; if `false`, it forces local. |
333
- | `privacyMode` | `"forceLocalOnSensitive" \| "consentBeforeFrontier" \| "off"` | `"forceLocalOnSensitive"` | Privacy policy for sensitive tasks. |
334
- | `orchestrator.roles` | `Record<string, AgentRoleProfile>` | `{}` | Custom or overridden role profiles keyed by role ID. Config entries are validated with the same strict role profile schema as built-in roles and take precedence over `TEAM_ROLES`; if an entry omits `roleID`, the map key is used. Unknown roles not declared here still use the neutral fallback and warn once. |
359
+ | `budgets.hardStopOnBudgetExhaustion` | `boolean` | `false` | Controls the configured budget action. It never authorizes leaving the execution domain. |
360
+ | `orchestrator` | object, optional | absent | Holds primary/role policy and pass limits. |
361
+ | `orchestrator.maxRetries` | non-negative integer, optional | effective `2` | Upper bound of extra openteam passes per authorized model; effective `2` permits up to three passes. The default is applied at runtime, not materialized by the schema. |
362
+ | `orchestrator.maxModelsPerNode` | positive integer, optional | effective `2` | Total model candidates used by one node: the selected model plus at most one fallback by default. The runtime default is not materialized; with retry defaults it permits at most six attempts. |
363
+ | `orchestrator.primary` | partial `ExecutionPolicy`, optional | absent | Policy for the primary coordinator. Every field is optional. |
364
+ | `orchestrator.roles` | raw `Record<string, partial AgentRoleProfile>` | `{}` when `orchestrator` exists | Raw entries may be partial. Loaded entries are fully merged and validated: built-in profile first, otherwise a synthesized profile, then the raw override. |
335
365
  | `orchestrator.roles.<role>.opencodeAgent` | non-empty string, optional | role ID | opencode agent name to dispatch for that role. |
336
- | `orchestrator.roles.<role>.defaultTier` | `"trivial" \| "simple" \| "moderate" \| "hard"` | required | Baseline complexity tier used before heuristic and permission elevation. |
337
- | `orchestrator.roles.<role>.minReasoningTier` | `1`–`5` | required | Minimum reasoning capability for frontier candidates. |
338
- | `orchestrator.roles.<role>.minCodeQualityTier` | `1`–`5` | required | Minimum code-quality capability for frontier candidates. |
339
- | `orchestrator.roles.<role>.needsToolCallingByDefault` | `boolean` | required | Whether this role requires tool-calling capability unless the task says otherwise. |
340
- | `orchestrator.roles.<role>.preferredFrontierModels` | `Array<ModelRef \| "auto">` | required | Preferred frontier models; use `[]` for a local-first role with no explicit frontier preference. |
341
- | `orchestrator.roles.<role>.localFirst` | `boolean` | required | Whether the role should keep the normal local-first posture instead of forcing hard frontier routing. |
366
+ | `orchestrator.primary.executionMode` / `orchestrator.roles.<role>.executionMode` | `"local" \| "frontier" \| "mixed"`, optional | inherited | May narrow global `mixed`; cannot contradict global `local` or `frontier`. |
367
+ | `orchestrator.primary.model` / `orchestrator.roles.<role>.model` | `ModelRef \| "auto"`, optional | omitted means `"auto"` | Exact selection or deterministic capable/available in-domain automatic selection. Mixed mode is local-first where capable, then cheapest-capable when catalog cost exists. Static unconfigured, out-of-domain, or known-incapable pins fail validation; transiently offline candidates are skipped only at runtime. Missing costs are not treated as zero. |
368
+ | `orchestrator.primary.fallbacks` / `orchestrator.roles.<role>.fallbacks` | `ModelRef[]`, optional | derived for auto; none for an exact pin | An explicit list replaces derived auto fallbacks and retains declared order after de-duplication and skipping the selected model. Every entry receives static identity/domain/capability validation before capping. A temporarily offline backup does not invalidate a healthy primary; it is skipped in favor of the next authorized candidate. |
369
+ | `privacyMode` | legacy/deprecated | none | Accepted only as ignored compatibility metadata and removed on canonical persistence. Loading it emits one `privacy-mode-ignored` migration warning explaining frontier-egress risk and the `executionMode: "local"` remedy; the notice never changes routing. |
342
370
  | `telemetry.enabled` | `boolean` (plugin option) | `true` | Enable/disable JSONL writing. |
343
371
  | `telemetry.path` | `string` (plugin option) | `.opencode/openteam-local/telemetry.jsonl` | Path of the `CostRecord` JSONL log. |
344
372
 
345
- Example custom privacy/security auditor:
373
+ Legacy `router.localOnly` and `router.frontierOnly` remain optional parse
374
+ aliases so old files load; migration and canonical writes replace them with
375
+ `executionMode`. `TaskSignals.privacySensitive?` likewise remains an optional
376
+ historical caller field, but classification, scoring, rationale, and routing
377
+ ignore it.
378
+
379
+ Example explicit coordinator and worker policies:
380
+
381
+ Exact policy models are always objects such as
382
+ `{ "providerID": "anthropic", "modelID": "claude-sonnet-4-5" }`, never a
383
+ `"provider/model"` scalar. The slash/CLI baseline command and opencode's own
384
+ `model` field use the scalar notation instead. Every example model must already
385
+ exist in the operator's configured providers. A runtime instance ID such as
386
+ `lmstudio-b` and a provider ID such as `lmstudio` are distinct concepts; use
387
+ the provider identity configured for opencode.
346
388
 
347
389
  ```jsonc
348
390
  {
391
+ "configVersion": 2,
392
+ "router": { "executionMode": "mixed" },
349
393
  "orchestrator": {
394
+ "maxRetries": 2,
395
+ "maxModelsPerNode": 2,
396
+ "primary": {
397
+ "executionMode": "frontier",
398
+ "model": "auto"
399
+ },
350
400
  "roles": {
351
- "galadriel": {
352
- "opencodeAgent": "galadriel",
353
- "defaultTier": "moderate",
354
- "minReasoningTier": 3,
355
- "minCodeQualityTier": 3,
356
- "needsToolCallingByDefault": true,
357
- "preferredFrontierModels": [],
358
- "localFirst": true
401
+ "scribe": {
402
+ "executionMode": "local",
403
+ "model": { "providerID": "ollama", "modelID": "qwen3:8b" }
404
+ },
405
+ "integration": {
406
+ "executionMode": "frontier",
407
+ "model": { "providerID": "anthropic", "modelID": "claude-sonnet-4-5" },
408
+ "fallbacks": [
409
+ { "providerID": "google", "modelID": "gemini-3.5-flash" }
410
+ ]
359
411
  }
360
412
  }
361
413
  }
362
414
  }
363
415
  ```
364
416
 
417
+ The resolved `routeKind` is always concrete `local`, `frontier`, or `blocked`,
418
+ never `mixed`. Only the selected model and returned fallback chain may execute.
419
+ A blocked decision does not dispatch; its selected identity is a cost/reference
420
+ value only. Budget narrowing cannot substitute an unapproved local model: if no
421
+ authorized candidate remains, routing blocks.
422
+
365
423
  ## Usage
366
424
 
367
- On each turn, openteam derives signals from the prompt (`promptChars`, `hasCode`, `privacySensitive`, `requiresTools` and a model override if present), runs `chooseModel` and assigns `output.message.model`.
425
+ On each turn, openteam resolves the global and agent execution policy, filters
426
+ available models by domain, tool support and context, ranks `"auto"` candidates
427
+ by cost against the unified router's capability/complexity requirements, and
428
+ assigns `output.message.model`. Prompt sensitivity and Graph privacy metadata
429
+ are not routing inputs.
368
430
 
369
431
  Multi-agent orchestration uses static roles and opencode subsessions:
370
432
 
371
- | Role | opencode agent | Base tier | Policy |
372
- | --- | --- | --- | --- |
373
- | `rusty` | `architect` | `hard` | Architecture and high-risk decisions; frontier auto. |
374
- | `livingston` | `integration` | `moderate` | TS/opencode integration; tool calling by default. |
375
- | `yen` | `local-runtime` | `moderate` | Local runtimes; local-first with frontier fallback. |
376
- | `basher` | `routing-cost` | `moderate` | Router, cost and budget; local-first. |
377
- | `scribe` | `scribe` | `trivial` | Documentation and summaries; local-first. |
378
- | `linus` | `tester` | `simple` | Tests and contracts; local-first with tools. |
433
+ For coordinator-owned work, the role's `defaultTier` and capability minima are
434
+ floors. Ordinary task capability/complexity classification may raise the
435
+ effective requirement for harder work, but it cannot weaken the role
436
+ requirement. The effective tier drives routing and telemetry. Sensitivity
437
+ vocabulary and Graph privacy metadata have no routing effect.
379
438
 
380
- `runRoleTask` runs a single subsession; `runRoleTasks` runs several with a `batchID`. Roles with `destructive` or `multiFileEdit` elevate the tier before choosing a model.
381
-
382
- To see savings and decisions, read the telemetry JSONL. Key fields: `promptHash`, `promptChars`, `tier`, `routeKind`, `selected`, `rationale`, `estimatedCostUSD`, `baselineCostUSD`, `estimatedSavingsUSD`, `budgetAction`, `tokensIn` and `tokensOut`.
439
+ | Role | opencode agent | Typical policy |
440
+ | --- | --- | --- | --- |
441
+ | `rusty` | `architect` | Configure `"auto"` or an exact in-domain model. |
442
+ | `livingston` | `integration` | Usually requires tool support; domain is explicit. |
443
+ | `yen` | `local-runtime` | May be narrowed to `local` under global `mixed`. |
444
+ | `basher` | `routing-cost` | Auto selection may optimize cost inside the domain. |
445
+ | `scribe` | `scribe` | No built-in local restriction; set one explicitly if required. |
446
+ | `linus` | `tester` | Configure models with the required tools/context. |
447
+
448
+ `runRoleTask` runs a single subsession; `runRoleTasks` runs several with a
449
+ `batchID`. Direct and batched attempts use the same stable logical report path
450
+ and must prove a current, changed-when-required, substantive durable report
451
+ before success. Permission and review policy remain separate from model
452
+ placement.
453
+
454
+ SDK child dispatch uses a plugin-scoped, one-shot model authorization keyed by
455
+ the exact child session, agent alias, and model. When the public SDK prompt
456
+ re-enters `chat.message`, only that matching claim preserves the
457
+ coordinator-authorized worker model; the authorization is released when the
458
+ prompt settles. Unmatched and user-initiated messages still follow normal
459
+ routing. This mechanism cannot broaden an execution domain or authorize a
460
+ model outside `selected` plus `fallbackChain`.
461
+
462
+ To inspect actual model use, handoff reasons, and cost/savings estimates when
463
+ available, read the telemetry JSONL. Key fields include `promptHash`,
464
+ `promptChars`, `routeKind`, `selected`, `rationale`, `estimatedCostUSD`,
465
+ `baselineCostUSD`, `estimatedSavingsUSD`, `budgetAction`, `tokensIn`, and
466
+ `tokensOut`. Unknown model costs remain `null`/unknown rather than becoming
467
+ free; savings are unknown when either required estimate is unavailable. For
468
+ coordinator auto routing, the effective capability requirement—the role floor
469
+ plus any upward task classification—determines both candidate capability and
470
+ the capable frontier baseline used for cost comparison.
383
471
 
384
472
  ## Runtime commands
385
473
 
@@ -392,8 +480,9 @@ The plugin registers the `openteam` tool. Ask the agent to use it, or install th
392
480
  - `show` — show the effective baseline.
393
481
  - `set` (with `model` as `provider/model`) — pin the baseline (`pinned` mode).
394
482
  - `auto` — return to cheapest-capable (`auto` mode).
395
- - `doctor` — diagnostics for local runtimes, privacy and telemetry.
396
- - `report` — cost and savings summary from telemetry.
483
+ - `doctor` — diagnostics for execution policy, model availability, and telemetry.
484
+ - `report` — available cost and savings estimates from telemetry; unknown
485
+ values remain unknown.
397
486
 
398
487
  `set` / `auto` changes are persisted to `.opencode/openteam.json` and apply on the next plugin start.
399
488
 
@@ -420,7 +509,7 @@ The package exposes the `openteam` binary. With a global install:
420
509
  ```powershell
421
510
  bun add --global @jmanuelcorral/openteam@latest
422
511
  openteam baseline show
423
- openteam baseline set openai/gpt-5-mini
512
+ openteam baseline set anthropic/claude-sonnet-4-5
424
513
  openteam doctor --config .opencode/openteam.json
425
514
  openteam agents
426
515
  openteam console
@@ -438,7 +527,11 @@ Flags: `--config <path>` and `--telemetry <path>` override the default paths; `-
438
527
 
439
528
  ### See each agent's LLM: `openteam agents`
440
529
 
441
- Lists the agents in `.opencode/agent/`, showing **which LLM each one uses**: whether it is **local** (Ollama/LM Studio/Lemonade/Foundry, no token cost) or **frontier** (consumes your subscription), and the provider. Agents without their own `model` **inherit** the `opencode.json` default (which openteam routes local-first per message).
530
+ Lists the agents in `.opencode/agent/`, showing their configured model and
531
+ provider. Agents without their own `model` inherit the `opencode.json` default.
532
+ That inherited/default model is not permission to escape the resolved
533
+ execution domain; effective primary/role policies in `.opencode/openteam.json`
534
+ remain authoritative.
442
535
 
443
536
  ### YOLO mode (auto-approve permissions)
444
537
 
@@ -452,27 +545,32 @@ openteam yolo off # disable YOLO
452
545
 
453
546
  > **The agent permission overrides the global one.** opencode merges each agent's frontmatter permission **after** the global one and the last matching rule wins, so `openteam yolo on/off` regenerates `.opencode/agent/openteam.md` to keep its `permission` in sync. After changing mode, **restart or reload opencode**.
454
547
 
455
- > **YOLO does not disable openteam privacy.** It only affects opencode permission approvals; with `privacyMode: "forceLocalOnSensitive"`, sensitive prompts still never leave for frontier. Use it only in trusted environments.
548
+ > **YOLO does not change the execution domain.** It only affects opencode
549
+ > permission approvals, and it does not add a sensitivity screen. In
550
+ > `frontier` or `mixed`, prompt content may be sent to frontier. Use it only in
551
+ > trusted environments.
456
552
 
457
553
  ### Routing mode: `openteam local`
458
554
 
459
- Use `openteam local` to switch between the three global routing modes without
555
+ Use `openteam local` to switch between the three global execution domains without
460
556
  re-running setup:
461
557
 
462
558
  ```powershell
463
- openteam local status # local-first, local-only, or frontier-only? + configured runtimes
464
- openteam local off # frontier-only (disable local-first routing)
559
+ openteam local status # mixed, local, or frontier? + configured runtimes
560
+ openteam local off # frontier
465
561
  openteam local only # local-only (never escalate to frontier)
466
- openteam local on # back to local-first routing
562
+ openteam local on # mixed
467
563
  ```
468
564
 
469
- `local off` persists `router.frontierOnly=true`: the router picks the
470
- **cheapest-capable frontier**, but still respects privacy, budgets and explicit
471
- per-task overrides. Sensitive prompts with `forceLocalOnSensitive` don't leave
472
- for frontier; `local off` degrades that policy to `consentBeforeFrontier` (local
473
- can't be forced without a runtime) and tells you about the change. `local only`
474
- persists `router.localOnly=true` and clears `frontierOnly`; it refuses to write
475
- the config unless at least one local runtime is enabled.
565
+ The commands persist `router.executionMode`. `local only` is a hard upper
566
+ bound: no primary/worker pin or fallback can reach frontier, and the command is
567
+ rejected without an enabled local runtime. `local off` needs no local runtime.
568
+ `local on` allows both domains, and each agent may narrow itself to one of them.
569
+ Before any mode change is written, exact primary, role, and fallback policies
570
+ are validated against the proposed domain; a conflict rejects the command and
571
+ leaves config unchanged.
572
+ Every write removes legacy `localOnly`, `frontierOnly`, and root `privacyMode`;
573
+ `local status` shows the effective mode and enabled runtime identities.
476
574
 
477
575
  ### Updating the plugin and clearing the cache: `openteam clear-cache`
478
576
 
@@ -500,13 +598,57 @@ explanation of the freeze mechanism and a step-by-step update procedure.
500
598
 
501
599
  ### Iterative work under `openteam-orchestrate` 🎯
502
600
 
503
- Unattended, backlog-style work is driven **entirely from orchestration** — iteration is a property of the graph, not a separate command. When the coordinator dispatches a role, that graph node keeps working across successive passes until it declares `openteam-status: done` in its report artifact (the **output contract**) or it reaches its retry bound. The bound is configurable via `orchestrator.maxRetries` in `.opencode/openteam.json`: it counts retries, so the default `2` means **three passes per node** and `0` disables iteration (a single pass). A node that never declares completion is re-dispatched up to the bound rather than assumed finished, and a **durability gate** verifies in code that each role actually persisted a non-empty report — so no pass silently discards work. Wave-level concurrency and dependency cancellation are unchanged. See **[docs/guide/commands.md](docs/guide/commands.md)** for the full contract.
601
+ Unattended, backlog-style work is driven entirely from orchestration. The
602
+ current model gets `orchestrator.maxRetries` extra passes (effective runtime
603
+ default `2`, so up to three passes per model). Persistent incompleteness uses
604
+ that model's pass budget before handing the **same node** to the next authorized
605
+ model. A recognized retry-safe prompt transport/capacity failure
606
+ (`prompt-timeout`, `prompt-rate-limited`, `prompt-connection`,
607
+ `prompt-server`, or `prompt-context-length`) is retried on that same model
608
+ within its pass allowance; create-stage retry recognizes the same transport
609
+ classes except context length. `maxRetries` is an upper bound, not a required
610
+ repeat floor: after confirmed stop, an eligible transport failure may consume
611
+ the remaining same-model allowance or hand off earlier to the next authorized
612
+ candidate. Auth,
613
+ content-filter, validation/invalid-model, generic rejection, and missing-session
614
+ or unclassified failures are terminal. A candidate found unavailable before
615
+ dispatch may be skipped because no attempt started. `maxModelsPerNode` has the
616
+ effective runtime default `2`; together the defaults cap a node at up to six
617
+ openteam passes. Neither default is materialized by the schema. This is not a
618
+ guaranteed pass/HTTP-call count or a statement about provider-internal SDK
619
+ retries.
620
+ The batch, objective, report path, and durable work state are preserved. An
621
+ exact pin without `fallbacks` authorizes no second model.
622
+
623
+ Every pass, including a single pass with `maxRetries: 0`, needs a standalone
624
+ `openteam-status: done` declaration **and** a durable report with at least 40
625
+ substantive body characters after actual declaration lines are removed. The
626
+ last standalone declaration outside fenced code wins; quoted or fenced examples
627
+ remain body text and cannot declare completion. A marker alone is not
628
+ completion. The coordinator reads the report text only to parse that status and
629
+ test substance; it does not copy the body into role results, routing telemetry,
630
+ diagnostics, or the reference-only projection.
631
+
632
+ After a timed-out or otherwise thrown/uncertain prompt dispatch, the prior SDK
633
+ request must settle and server stop must be confirmed before any same-model
634
+ retry or model handoff. After a successful abort, bounded polling confirms stop
635
+ when the exact session is `idle` or is absent from a valid sparse status
636
+ snapshot; malformed/missing status payloads and sessions that remain `busy` or
637
+ `retry` block continuation. Status evidence alone does not replace request
638
+ settlement. Exhaustion is terminal failure/incomplete, and dependents do not
639
+ run. See
640
+ **[docs/guide/commands.md](docs/guide/commands.md)**.
504
641
 
505
642
  ### Multi-session web Console: `openteam console`
506
643
 
507
644
  openteam exposes a **local web Console** you launch from the CLI with `openteam console`. It aggregates **all** opencode sessions at once (one tab per session, plus "All"), because each plugin **writes redacted events** to per-session JSONL logs that the Console watches (`fs.watch`) and aggregates.
508
645
 
509
- It shows live: **real cost and tokens per message** (input/output/reasoning/cache), **tool calls with duration** and ok/fail, recent routing (cost/savings and chosen model — **prompt hashes only, never text**), agent **decisions** (scribe) and **meetings** (multi-agent batches), orchestration and iteration progress, the agent team and latest commits. It updates over **SSE** (with polling fallback).
646
+ It shows live: **recorded token usage and cost estimates per message**
647
+ (input/output/reasoning/cache), **tool calls with duration** and ok/fail, recent
648
+ routing (cost/savings and chosen model — **prompt hashes only, never text**),
649
+ agent decisions and meetings, orchestration progress, the team, and commits.
650
+ These controls and estimates are finite/auditable, but they are not provider
651
+ invoice accounting.
510
652
 
511
653
  Each session tab also includes a **"Session" view**: a console where you can **watch the live output** (SSE) and **send prompts** or **answer permissions** for that opencode session, authenticated with an **ephemeral token** per launch. Optionally, with the `terminal.pty` flag, a **"Terminal" view** opens a **real shell** in the workspace via opencode's PTY API (no `node-pty`).
512
654
 
@@ -541,7 +683,11 @@ block):
541
683
  }
542
684
  ```
543
685
 
544
- **Live agent-graph view.** The Console can also render a live **agent graph** of a Graph run; it is **on by default in shadow mode** and can be hidden with `console.graphView.enabled: false` — see [Graph observability web](docs/guide/graph-observability-web.md) for setup and the full requirements.
686
+ **Agent tree and timeline.** `console.graphView.enabled` defaults to `true`.
687
+ Native lifecycle history is composed independently of Graph execution
688
+ authority; the separate Graph DAG follows the effective `graph.mode`. See
689
+ [Agent timeline](docs/guide/agent-timeline.md) and
690
+ [Graph observability web](docs/guide/graph-observability-web.md).
545
691
 
546
692
  > **Security**: the server listens **on loopback only** (`127.0.0.1`/`localhost`, Zod-validated) and **never exposes prompts** (only `promptHash`), tool-call args or message content — only counters, costs and redacted summaries. The "Session"/"Terminal" view requires the ephemeral token. If the port is busy and `autoPortFallback` is `true`, it tries the next free one. Remote access via `openteam tunnel` was **removed from the MVP** (2026-08-31) and will be redefined later; the Console has no built-in remote-exposure path.
547
693
 
@@ -549,7 +695,7 @@ block):
549
695
 
550
696
  ### Team memory over MCP (read-only): `openteam mcp`
551
697
 
552
- `openteam mcp` starts a **read-only MCP server** (JSON-RPC 2.0 over stdio, no dependencies) that exposes the team memory aggregated from per-session events. It uses the same `StorageProvider` as the Console and **never** exposes raw prompts. Tools: `list_decisions` (filterable by `agent`/`tag`), `list_meetings`, `list_sessions` and `cost_summary` (real cost/tokens + estimated savings vs the all-frontier baseline).
698
+ `openteam mcp` starts a **read-only MCP server** (JSON-RPC 2.0 over stdio, no dependencies) that exposes the team memory aggregated from per-session events. It uses the same `StorageProvider` as the Console and **never** exposes raw prompts. Tools: `list_decisions` (filterable by `agent`/`tag`), `list_meetings`, `list_sessions` and `cost_summary` (recorded usage and available cost/savings estimates against the all-frontier baseline, with unknown costs retained as unknown; not an invoice).
553
699
 
554
700
  Register it in `opencode.json` like any local MCP server:
555
701
 
@@ -561,6 +707,12 @@ Register it in `opencode.json` like any local MCP server:
561
707
  }
562
708
  ```
563
709
 
710
+ The plugin's **`openteam-memory` write tool** is separate from this read-only
711
+ MCP server and from the general scribe role's model policy. Its extraction
712
+ transport is explicitly local-only and requires a reachable configured local
713
+ runtime. Global `frontier` execution refuses the memory-model call; the tool
714
+ does not add a frontier memory path or silently fall back to frontier.
715
+
564
716
  #### Semantic recall (opt-in): `recall_facts`, `recall_preferences`, `similar_tasks`
565
717
 
566
718
  Beyond team memory, `openteam mcp` can expose **semantic recall** of the long-term knowledge memory (facts, preferences, similar tasks). It is **disabled by default**; enable it with the `memory.semantic` block in `.opencode/openteam.json`:
@@ -692,16 +844,24 @@ and remove its inherited GitHub Actions secret.
692
844
 
693
845
  ## Privacy
694
846
 
695
- With `privacyMode: "forceLocalOnSensitive"`, if `TaskSignals.privacySensitive` is `true`, `chooseModel` uses local and does not allow frontier fallback. If no usable local runtime exists, the decision keeps the local route and records the reason in `rationale`.
847
+ openteam does **not** detect prompt sensitivity or promise to intercept sensitive
848
+ content before a frontier call. `privacyMode` is deprecated and has no routing
849
+ authority. `frontier` and `mixed` deliberately permit frontier egress under the
850
+ configured agent policy. To prohibit it, configure
851
+ `router.executionMode: "local"` globally, or narrow the relevant agent to
852
+ `local` under global `mixed`.
696
853
 
697
- Telemetry uses `hashPrompt` (currently `fnv1a32:<hash>`) and never serializes the full prompt. See [SECURITY.md](SECURITY.md) for the full privacy stance and how to report vulnerabilities.
854
+ Credential redaction, prompt-hash telemetry, reference-only shared storage,
855
+ loopback/access controls, and finite budget/model/pass limits remain in force.
856
+ Telemetry uses `hashPrompt` (currently `fnv1a32:<hash>`) and never serializes the
857
+ full prompt. See [SECURITY.md](SECURITY.md) for vulnerability reporting.
698
858
 
699
859
  ## Quality
700
860
 
701
861
  Non-negotiable gates (see [docs/testing-and-quality.md](docs/testing-and-quality.md)):
702
862
 
703
863
  - `bun test`
704
- - Global coverage: lines `>= 85%`, branches `>= 80%`.
864
+ - Global coverage: lines/functions `>= 85%`.
705
865
  - Coverage of `src/router/**` and `src/memory/**`: `100%` lines and functions (enforced by `bun run coverage:check`).
706
866
  - `bun run typecheck`
707
867
  - `biome check .` (or `bun run lint` + `bun run format:check`)
@@ -714,7 +874,7 @@ See [docs/compatibility.md](docs/compatibility.md). Verified summary as of `2026
714
874
 
715
875
  | Surface | Version / contract |
716
876
  | --- | --- |
717
- | openteam | `0.18.1`; current package version |
877
+ | openteam | `0.20.0`; current package version |
718
878
  | `@opencode-ai/plugin` | `1.18.19` |
719
879
  | `@opencode-ai/sdk` | `1.18.19` |
720
880
  | Routing hook | `chat.message`; not `chat.params` for model changes |
@@ -729,12 +889,13 @@ Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) for th
729
889
  | Phase | Status | Deliverables |
730
890
  | --- | --- | --- |
731
891
  | 0. Scaffold / PoC | Done | TS/Bun, minimal plugin, `chat.message` hook. |
732
- | 1. Local registry + heuristic router | Done | Ollama/LM Studio/Lemonade/Foundry, probes and deterministic `chooseModel`. |
733
- | 2. Classifier + profiles + budget + telemetry | Done | Local classifier, `ModelCapabilityProfile`, budgets and `CostRecord`. |
892
+ | 1. Local registry + router | Done | Ollama/LM Studio/Lemonade/Foundry, probes and deterministic `chooseModel`. |
893
+ | 2. Profiles + budget + telemetry | Done | `ModelCapabilityProfile`, budgets and `CostRecord`. |
734
894
  | 3. Multi-agent orchestration | Done | Coordinator, roles, child sessions and permissions. |
735
895
  | 4. npm packaging + docs | Done | Exportable package, README, Windows examples and compatibility matrix. |
736
896
  | 5. Runtime commands | Done | `baseline show/set/auto`, `doctor` and `report` via tool, `/openteam` and CLI `bin`. |
737
897
  | 6. Web Console + MCP memory | Done | The multi-session Console is loopback-only and gate-covered; read-only MCP server and semantic recall are stable. |
898
+ | Agent-stack MVP | In progress | Config v2 execution domains, per-agent model policies, bounded model handoff, removal of sensitivity routing, and retirement of duplicate Graph-local policy resolution. Unified router capability/complexity routing remains. LangGraph migration issues remain separate and pending. |
738
899
 
739
900
  ## License
740
901