@jmanuelcorral/openteam 0.18.1 → 0.19.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 (111) hide show
  1. package/.opencode/openteam.example.json +23 -14
  2. package/AGENTS.md +16 -2
  3. package/README.es.md +291 -208
  4. package/README.md +292 -138
  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.js +1348 -593
  10. package/dist/commands/agents.d.ts +16 -4
  11. package/dist/commands/agents.d.ts.map +1 -1
  12. package/dist/commands/dispatch.d.ts.map +1 -1
  13. package/dist/commands/doctor.d.ts.map +1 -1
  14. package/dist/commands/local.d.ts +4 -5
  15. package/dist/commands/local.d.ts.map +1 -1
  16. package/dist/commands/report.d.ts +6 -6
  17. package/dist/commands/report.d.ts.map +1 -1
  18. package/dist/commands/rolesInit.d.ts +10 -12
  19. package/dist/commands/rolesInit.d.ts.map +1 -1
  20. package/dist/commands/setup.d.ts +5 -13
  21. package/dist/commands/setup.d.ts.map +1 -1
  22. package/dist/commands/yolo.d.ts +2 -4
  23. package/dist/commands/yolo.d.ts.map +1 -1
  24. package/dist/config/errors.d.ts.map +1 -1
  25. package/dist/config/executionPolicyDomains.d.ts +18 -0
  26. package/dist/config/executionPolicyDomains.d.ts.map +1 -0
  27. package/dist/config/migrations.d.ts +9 -2
  28. package/dist/config/migrations.d.ts.map +1 -1
  29. package/dist/config/persist.d.ts.map +1 -1
  30. package/dist/config/runtime.d.ts +2 -0
  31. package/dist/config/runtime.d.ts.map +1 -1
  32. package/dist/config/schema.d.ts +97 -12
  33. package/dist/config/schema.d.ts.map +1 -1
  34. package/dist/console/configView.d.ts +2 -7
  35. package/dist/console/configView.d.ts.map +1 -1
  36. package/dist/console/render.d.ts.map +1 -1
  37. package/dist/console/types.d.ts +2 -2
  38. package/dist/console/types.d.ts.map +1 -1
  39. package/dist/context/schema.d.ts +13 -7
  40. package/dist/context/schema.d.ts.map +1 -1
  41. package/dist/graph/certificate.d.ts.map +1 -1
  42. package/dist/graph/langgraph/adapter.d.ts +7 -8
  43. package/dist/graph/langgraph/adapter.d.ts.map +1 -1
  44. package/dist/graph/langgraph/certification.d.ts +5 -0
  45. package/dist/graph/langgraph/certification.d.ts.map +1 -1
  46. package/dist/graph/langgraph/checkpointReplay.d.ts.map +1 -1
  47. package/dist/graph/langgraph/engine.d.ts +2 -2
  48. package/dist/graph/langgraph/engine.d.ts.map +1 -1
  49. package/dist/graph/langgraph/types.d.ts +11 -6
  50. package/dist/graph/langgraph/types.d.ts.map +1 -1
  51. package/dist/graph/privacyPolicy.d.ts +6 -30
  52. package/dist/graph/privacyPolicy.d.ts.map +1 -1
  53. package/dist/index.d.ts +1 -9
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +3136 -1122
  56. package/dist/mcp/teamMemory.d.ts +2 -2
  57. package/dist/mcp/teamMemory.d.ts.map +1 -1
  58. package/dist/messages/commands.d.ts +22 -35
  59. package/dist/messages/commands.d.ts.map +1 -1
  60. package/dist/messages/executionSetup.d.ts +116 -0
  61. package/dist/messages/executionSetup.d.ts.map +1 -0
  62. package/dist/messages/index.d.ts +1 -0
  63. package/dist/messages/index.d.ts.map +1 -1
  64. package/dist/messages/memoryTool.d.ts +17 -0
  65. package/dist/messages/memoryTool.d.ts.map +1 -0
  66. package/dist/messages/orchestration.d.ts +16 -0
  67. package/dist/messages/orchestration.d.ts.map +1 -0
  68. package/dist/messages/routing.d.ts +9 -0
  69. package/dist/messages/routing.d.ts.map +1 -0
  70. package/dist/opencodeArtifacts/orchestratorAgent.d.ts +3 -5
  71. package/dist/opencodeArtifacts/orchestratorAgent.d.ts.map +1 -1
  72. package/dist/orchestrator/coordinator.d.ts +19 -12
  73. package/dist/orchestrator/coordinator.d.ts.map +1 -1
  74. package/dist/orchestrator/graphEffects.d.ts +10 -11
  75. package/dist/orchestrator/graphEffects.d.ts.map +1 -1
  76. package/dist/orchestrator/outputContract.d.ts +5 -6
  77. package/dist/orchestrator/outputContract.d.ts.map +1 -1
  78. package/dist/orchestrator/roles.d.ts +25 -2
  79. package/dist/orchestrator/roles.d.ts.map +1 -1
  80. package/dist/orchestrator/runtimeSelection.d.ts.map +1 -1
  81. package/dist/orchestrator/subsessions.d.ts +49 -1
  82. package/dist/orchestrator/subsessions.d.ts.map +1 -1
  83. package/dist/plugin/availability.d.ts +15 -2
  84. package/dist/plugin/availability.d.ts.map +1 -1
  85. package/dist/plugin/diagnostics.d.ts +1 -0
  86. package/dist/plugin/diagnostics.d.ts.map +1 -1
  87. package/dist/plugin/hooks.d.ts +21 -2
  88. package/dist/plugin/hooks.d.ts.map +1 -1
  89. package/dist/plugin/memoryTool.d.ts +3 -3
  90. package/dist/plugin/memoryTool.d.ts.map +1 -1
  91. package/dist/plugin/modelOwnership.d.ts +15 -0
  92. package/dist/plugin/modelOwnership.d.ts.map +1 -0
  93. package/dist/plugin/orchestrateTool.d.ts +50 -3
  94. package/dist/plugin/orchestrateTool.d.ts.map +1 -1
  95. package/dist/router/chooseModel.d.ts.map +1 -1
  96. package/dist/router/cost.d.ts +1 -1
  97. package/dist/router/cost.d.ts.map +1 -1
  98. package/dist/router/executionPolicy.d.ts +44 -0
  99. package/dist/router/executionPolicy.d.ts.map +1 -0
  100. package/dist/router/frontierBaseline.d.ts.map +1 -1
  101. package/dist/router/types.d.ts +10 -4
  102. package/dist/router/types.d.ts.map +1 -1
  103. package/dist/telemetry/aggregate.d.ts +4 -4
  104. package/dist/telemetry/aggregate.d.ts.map +1 -1
  105. package/dist/telemetry/diagnostics.d.ts.map +1 -1
  106. package/dist/telemetry/events.d.ts +6 -6
  107. package/dist/telemetry/otel.d.ts.map +1 -1
  108. package/dist/telemetry/types.d.ts +3 -3
  109. package/dist/web/configApi.d.ts +3 -1
  110. package/dist/web/configApi.d.ts.map +1 -1
  111. 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,20 +35,25 @@
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.
53
58
  - A local web **Console** aggregating every opencode session (cost, tokens, tool calls, routing, decisions) — redacted, loopback-only.
54
59
  - A read-only **MCP server** exposing aggregated team memory, plus opt-in semantic recall.
@@ -89,46 +94,16 @@ For direct opencode loading, reference the package name:
89
94
  "$schema": "https://opencode.ai/config.json",
90
95
  "model": "anthropic/claude-sonnet-4-5",
91
96
  "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
- ]
97
+ "plugin": ["@jmanuelcorral/openteam"]
128
98
  }
129
99
  ```
130
100
 
131
- The fastest way to get a repository ready is the interactive `openteam setup` command (below), which generates this configuration for you.
101
+ Those model identifiers must exist in your configured opencode providers;
102
+ replace them with available equivalents when needed.
103
+
104
+ Keep operator routing policy in `.opencode/openteam.json`, not in the plugin
105
+ declaration. The fastest way to create both files is the interactive
106
+ `openteam setup` command below.
132
107
 
133
108
  ### Option B: development path
134
109
 
@@ -137,33 +112,29 @@ During development you can load a local plugin from `.opencode/plugins/openteam.
137
112
  ```jsonc
138
113
  {
139
114
  "$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
- ]
115
+ "plugin": ["./.opencode/plugins/openteam.ts"]
157
116
  }
158
117
  ```
159
118
 
160
- ## Quickstart: `openteam setup`
119
+ Development-path loading does not change the configuration boundary: keep
120
+ operator policy in `.opencode/openteam.json`.
161
121
 
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:
122
+ ## Quickstart: `openteam setup`
163
123
 
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).
124
+ `openteam setup` detects the installed local runtimes (Ollama, LM Studio,
125
+ Lemonade Server, Foundry Local — all optional), lets you pick local models and
126
+ the **frontier baseline** (sorted cheapest-capable first), and writes the
127
+ project configuration and generated agent/command artifacts:
128
+
129
+ - `.opencode/opencode.json` for a new project, or the existing
130
+ `.opencode/opencode.json` / root `opencode.json`: declares the plugin, local
131
+ providers, frontier `model`, and local `small_model`.
132
+ - `.opencode/openteam.json`: openteam's validated runtime configuration
133
+ (execution domain, local runtimes, primary/role model policies, budgets, and
134
+ bounded retries/handoffs).
166
135
  - `.opencode/agent/openteam.md`: the **primary `openteam` agent**, visible in the Tab switcher.
136
+ - `.opencode/agent/<role>.md` for the standard dispatch targets, plus generated
137
+ `.opencode/command/*.md` entries and the local `.opencode/.gitignore`.
167
138
 
168
139
  ```powershell
169
140
  # One-off, without a global install
@@ -176,25 +147,66 @@ openteam setup
176
147
 
177
148
  The wizard:
178
149
 
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`.
150
+ 1. Probes local runtimes, then first asks which ones to enable. Choosing
151
+ **None** selects global `frontier` directly; it does not ask a redundant
152
+ three-way mode question and proceeds to frontier model selection.
153
+ 2. For each enabled runtime, choose this machine or another LAN host, its
154
+ default/thinking/coder models, and concurrency. Multiple instances retain
155
+ distinct runtime and provider identities.
156
+ 3. With at least one local runtime, choose the team execution mode (default
157
+ `mixed`). `local` pins the primary to the first enabled runtime's thinking
158
+ model or default; `frontier` asks for a frontier model; `mixed` asks whether
159
+ the primary uses that local model or the selected frontier model. Frontier
160
+ choices come from Models.dev cheapest-capable ordering, with browse/custom
161
+ and curated-offline fallbacks.
162
+ 4. Setup writes an exact primary policy with `fallbacks: []`, persists each
163
+ worker as the compact partial policy `{ "model": "auto" }`, and writes the
164
+ `2`/`2` retry/model limits. Primary `"auto"` and primary fallbacks are
165
+ supported by the schema and manual config, but setup does not prompt for
166
+ them.
167
+ 5. Choose whether to enable **YOLO mode**. Existing config files require
168
+ overwrite confirmation.
169
+
170
+ After `setup`, authenticate a frontier provider if the configured policies allow
171
+ frontier, make sure each configured local runtime is reachable, and open
172
+ opencode in the repo. Re-tune the baseline later with
173
+ `openteam baseline set <provider/model>` or `openteam baseline auto`.
174
+
175
+ > **Upgrade note:** `privacyMode` is deprecated, ignored metadata and no longer prevents
176
+ > frontier execution. Convert any required restriction to
177
+ > `router.executionMode: "local"` or an agent `executionMode: "local"` under
178
+ > global `mixed`.
186
179
 
187
180
  ## The `openteam` agent
188
181
 
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).)
182
+ `openteam setup` generates a single **primary agent** named `openteam`. Press
183
+ **Tab** in opencode to select it: you explicitly know you are in openteam mode.
184
+ Its model follows the resolved global and primary-agent policy; the prompt does
185
+ not select a domain by being judged sensitive or complex.
186
+
187
+ The generated primary frontmatter and `opencode.json.model` use the same exact
188
+ bootstrap model. Generated workers intentionally omit frontmatter `model`; their
189
+ policies in `.opencode/openteam.json` are authoritative. Guardian, scribe, and
190
+ ralph are not forced local by name.
191
+
192
+ That orchestrator ships **no project-specific pre-created subagents**. On every
193
+ request it checks whether a team already exists
194
+ (`.opencode/openteam/roster.md` + subagents in `.opencode/agent/`). If needed,
195
+ it designs the minimal team and creates it on demand, reusing existing agents
196
+ first. Model placement comes from the global and per-agent policies, not from
197
+ automatic sensitivity classification. The unified router may still derive
198
+ capability and complexity requirements inside the permitted domain. (This
199
+ on-demand, themed-team model is inspired by
200
+ [Squad](https://bradygaster.github.io/squad/) — see
201
+ [Acknowledgements](#acknowledgements).)
192
202
 
193
203
  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
204
 
195
- - **scribe** — silent memory: logs decisions and learnings to `.opencode/openteam/decisions.md` without touching code (local model).
205
+ - **scribe** — silent memory: logs decisions and learnings to `.opencode/openteam/decisions.md` without touching code.
196
206
  - **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`.
207
+ - **guardian** — security review and operator-policy advice. It is not an
208
+ automatic pre-frontier content gate and has no built-in local requirement.
209
+ Configure `executionMode: "local"` explicitly if the role must stay local.
198
210
 
199
211
  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
212
 
@@ -230,7 +242,9 @@ To install and try openteam on another machine that already has [opencode](https
230
242
 
231
243
  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
244
 
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.
245
+ 5. **Open opencode in the repo**, press **Tab** and pick the `openteam` agent.
246
+ Give it the first prompt: it creates the subagent team on demand and runs
247
+ each agent within the configured execution domain.
234
248
 
235
249
  Check the status with `openteam doctor` (runtimes + config) and the savings with `openteam report`.
236
250
 
@@ -260,11 +274,19 @@ Notes when updating:
260
274
 
261
275
  ## Configuration
262
276
 
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`.
277
+ The canonical runtime configuration lives in `.opencode/openteam.json`; this
278
+ repo ships `.opencode/openteam.example.json` as a complete minimal v2 example,
279
+ not as a byte-for-byte contract for every setup choice. Canonical v2
280
+ serialization is compact: it omits default legacy heuristic values, retains
281
+ non-default heuristic overrides for policy-less compatibility, and compacts
282
+ resolved role profiles to partial policies relative to their built-in or
283
+ synthesized baselines. The Zod-validated shape is in `src/config/schema.ts`.
284
+ `telemetry` is a plugin option read by `src/index.ts`, not part of the persisted
285
+ `OpenTeamConfig`.
264
286
 
265
287
  Config files are strict: unknown keys fail closed in both the CLI and plugin
266
288
  instead of being silently ignored. Errors name each rejected path (for example
267
- `router.trivialPromptMxChars`) and include a nearest-key hint when available.
289
+ `router.executionMod`) and include a nearest-key hint when available.
268
290
  This strict validation applies to the config file on both paths; programmatic
269
291
  plugin options are a separate surface and are not yet strictly validated (#102).
270
292
  Existing configs without `configVersion` remain valid because openteam migrates
@@ -310,13 +332,15 @@ configured" and should `cd` to the project root first.
310
332
 
311
333
  | Key | Type | Default | Description |
312
334
  | --- | --- | --- | --- |
335
+ | `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
336
  | `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
337
  | `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. |
338
+ | `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. |
339
+ | `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`. |
340
+ | `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. |
341
+ | `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. |
342
+ | `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
343
  | `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
344
  | `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
345
  | `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
346
  | `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 +353,118 @@ configured" and should `cd` to the project root first.
329
353
  | `budgets.sessionUSD` | positive number, optional | unlimited | Frontier budget per session. |
330
354
  | `budgets.monthlyUSD` | positive number, optional | unlimited | Monthly frontier budget. |
331
355
  | `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. |
356
+ | `budgets.hardStopOnBudgetExhaustion` | `boolean` | `false` | Controls the configured budget action. It never authorizes leaving the execution domain. |
357
+ | `orchestrator` | object, optional | absent | Holds primary/role policy and pass limits. |
358
+ | `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. |
359
+ | `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. |
360
+ | `orchestrator.primary` | partial `ExecutionPolicy`, optional | absent | Policy for the primary coordinator. Every field is optional. |
361
+ | `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
362
  | `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. |
363
+ | `orchestrator.primary.executionMode` / `orchestrator.roles.<role>.executionMode` | `"local" \| "frontier" \| "mixed"`, optional | inherited | May narrow global `mixed`; cannot contradict global `local` or `frontier`. |
364
+ | `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. |
365
+ | `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. |
366
+ | `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
367
  | `telemetry.enabled` | `boolean` (plugin option) | `true` | Enable/disable JSONL writing. |
343
368
  | `telemetry.path` | `string` (plugin option) | `.opencode/openteam-local/telemetry.jsonl` | Path of the `CostRecord` JSONL log. |
344
369
 
345
- Example custom privacy/security auditor:
370
+ Legacy `router.localOnly` and `router.frontierOnly` remain optional parse
371
+ aliases so old files load; migration and canonical writes replace them with
372
+ `executionMode`. `TaskSignals.privacySensitive?` likewise remains an optional
373
+ historical caller field, but classification, scoring, rationale, and routing
374
+ ignore it.
375
+
376
+ Example explicit coordinator and worker policies:
377
+
378
+ Exact policy models are always objects such as
379
+ `{ "providerID": "anthropic", "modelID": "claude-sonnet-4-5" }`, never a
380
+ `"provider/model"` scalar. The slash/CLI baseline command and opencode's own
381
+ `model` field use the scalar notation instead. Every example model must already
382
+ exist in the operator's configured providers. A runtime instance ID such as
383
+ `lmstudio-b` and a provider ID such as `lmstudio` are distinct concepts; use
384
+ the provider identity configured for opencode.
346
385
 
347
386
  ```jsonc
348
387
  {
388
+ "configVersion": 2,
389
+ "router": { "executionMode": "mixed" },
349
390
  "orchestrator": {
391
+ "maxRetries": 2,
392
+ "maxModelsPerNode": 2,
393
+ "primary": {
394
+ "executionMode": "frontier",
395
+ "model": "auto"
396
+ },
350
397
  "roles": {
351
- "galadriel": {
352
- "opencodeAgent": "galadriel",
353
- "defaultTier": "moderate",
354
- "minReasoningTier": 3,
355
- "minCodeQualityTier": 3,
356
- "needsToolCallingByDefault": true,
357
- "preferredFrontierModels": [],
358
- "localFirst": true
398
+ "scribe": {
399
+ "executionMode": "local",
400
+ "model": { "providerID": "ollama", "modelID": "qwen3:8b" }
401
+ },
402
+ "integration": {
403
+ "executionMode": "frontier",
404
+ "model": { "providerID": "anthropic", "modelID": "claude-sonnet-4-5" },
405
+ "fallbacks": [
406
+ { "providerID": "google", "modelID": "gemini-3.5-flash" }
407
+ ]
359
408
  }
360
409
  }
361
410
  }
362
411
  }
363
412
  ```
364
413
 
414
+ The resolved `routeKind` is always concrete `local`, `frontier`, or `blocked`,
415
+ never `mixed`. Only the selected model and returned fallback chain may execute.
416
+ A blocked decision does not dispatch; its selected identity is a cost/reference
417
+ value only. Budget narrowing cannot substitute an unapproved local model: if no
418
+ authorized candidate remains, routing blocks.
419
+
365
420
  ## Usage
366
421
 
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`.
422
+ On each turn, openteam resolves the global and agent execution policy, filters
423
+ available models by domain, tool support and context, ranks `"auto"` candidates
424
+ by cost against the unified router's capability/complexity requirements, and
425
+ assigns `output.message.model`. Prompt sensitivity and Graph privacy metadata
426
+ are not routing inputs.
368
427
 
369
428
  Multi-agent orchestration uses static roles and opencode subsessions:
370
429
 
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. |
430
+ For coordinator-owned work, the role's `defaultTier` and capability minima are
431
+ floors. Ordinary task capability/complexity classification may raise the
432
+ effective requirement for harder work, but it cannot weaken the role
433
+ requirement. The effective tier drives routing and telemetry. Sensitivity
434
+ vocabulary and Graph privacy metadata have no routing effect.
379
435
 
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`.
436
+ | Role | opencode agent | Typical policy |
437
+ | --- | --- | --- | --- |
438
+ | `rusty` | `architect` | Configure `"auto"` or an exact in-domain model. |
439
+ | `livingston` | `integration` | Usually requires tool support; domain is explicit. |
440
+ | `yen` | `local-runtime` | May be narrowed to `local` under global `mixed`. |
441
+ | `basher` | `routing-cost` | Auto selection may optimize cost inside the domain. |
442
+ | `scribe` | `scribe` | No built-in local restriction; set one explicitly if required. |
443
+ | `linus` | `tester` | Configure models with the required tools/context. |
444
+
445
+ `runRoleTask` runs a single subsession; `runRoleTasks` runs several with a
446
+ `batchID`. Direct and batched attempts use the same stable logical report path
447
+ and must prove a current, changed-when-required, substantive durable report
448
+ before success. Permission and review policy remain separate from model
449
+ placement.
450
+
451
+ SDK child dispatch uses a plugin-scoped, one-shot model authorization keyed by
452
+ the exact child session, agent alias, and model. When the public SDK prompt
453
+ re-enters `chat.message`, only that matching claim preserves the
454
+ coordinator-authorized worker model; the authorization is released when the
455
+ prompt settles. Unmatched and user-initiated messages still follow normal
456
+ routing. This mechanism cannot broaden an execution domain or authorize a
457
+ model outside `selected` plus `fallbackChain`.
458
+
459
+ To inspect actual model use, handoff reasons, and cost/savings estimates when
460
+ available, read the telemetry JSONL. Key fields include `promptHash`,
461
+ `promptChars`, `routeKind`, `selected`, `rationale`, `estimatedCostUSD`,
462
+ `baselineCostUSD`, `estimatedSavingsUSD`, `budgetAction`, `tokensIn`, and
463
+ `tokensOut`. Unknown model costs remain `null`/unknown rather than becoming
464
+ free; savings are unknown when either required estimate is unavailable. For
465
+ coordinator auto routing, the effective capability requirement—the role floor
466
+ plus any upward task classification—determines both candidate capability and
467
+ the capable frontier baseline used for cost comparison.
383
468
 
384
469
  ## Runtime commands
385
470
 
@@ -392,8 +477,9 @@ The plugin registers the `openteam` tool. Ask the agent to use it, or install th
392
477
  - `show` — show the effective baseline.
393
478
  - `set` (with `model` as `provider/model`) — pin the baseline (`pinned` mode).
394
479
  - `auto` — return to cheapest-capable (`auto` mode).
395
- - `doctor` — diagnostics for local runtimes, privacy and telemetry.
396
- - `report` — cost and savings summary from telemetry.
480
+ - `doctor` — diagnostics for execution policy, model availability, and telemetry.
481
+ - `report` — available cost and savings estimates from telemetry; unknown
482
+ values remain unknown.
397
483
 
398
484
  `set` / `auto` changes are persisted to `.opencode/openteam.json` and apply on the next plugin start.
399
485
 
@@ -420,7 +506,7 @@ The package exposes the `openteam` binary. With a global install:
420
506
  ```powershell
421
507
  bun add --global @jmanuelcorral/openteam@latest
422
508
  openteam baseline show
423
- openteam baseline set openai/gpt-5-mini
509
+ openteam baseline set anthropic/claude-sonnet-4-5
424
510
  openteam doctor --config .opencode/openteam.json
425
511
  openteam agents
426
512
  openteam console
@@ -438,7 +524,11 @@ Flags: `--config <path>` and `--telemetry <path>` override the default paths; `-
438
524
 
439
525
  ### See each agent's LLM: `openteam agents`
440
526
 
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).
527
+ Lists the agents in `.opencode/agent/`, showing their configured model and
528
+ provider. Agents without their own `model` inherit the `opencode.json` default.
529
+ That inherited/default model is not permission to escape the resolved
530
+ execution domain; effective primary/role policies in `.opencode/openteam.json`
531
+ remain authoritative.
442
532
 
443
533
  ### YOLO mode (auto-approve permissions)
444
534
 
@@ -452,27 +542,32 @@ openteam yolo off # disable YOLO
452
542
 
453
543
  > **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
544
 
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.
545
+ > **YOLO does not change the execution domain.** It only affects opencode
546
+ > permission approvals, and it does not add a sensitivity screen. In
547
+ > `frontier` or `mixed`, prompt content may be sent to frontier. Use it only in
548
+ > trusted environments.
456
549
 
457
550
  ### Routing mode: `openteam local`
458
551
 
459
- Use `openteam local` to switch between the three global routing modes without
552
+ Use `openteam local` to switch between the three global execution domains without
460
553
  re-running setup:
461
554
 
462
555
  ```powershell
463
- openteam local status # local-first, local-only, or frontier-only? + configured runtimes
464
- openteam local off # frontier-only (disable local-first routing)
556
+ openteam local status # mixed, local, or frontier? + configured runtimes
557
+ openteam local off # frontier
465
558
  openteam local only # local-only (never escalate to frontier)
466
- openteam local on # back to local-first routing
559
+ openteam local on # mixed
467
560
  ```
468
561
 
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.
562
+ The commands persist `router.executionMode`. `local only` is a hard upper
563
+ bound: no primary/worker pin or fallback can reach frontier, and the command is
564
+ rejected without an enabled local runtime. `local off` needs no local runtime.
565
+ `local on` allows both domains, and each agent may narrow itself to one of them.
566
+ Before any mode change is written, exact primary, role, and fallback policies
567
+ are validated against the proposed domain; a conflict rejects the command and
568
+ leaves config unchanged.
569
+ Every write removes legacy `localOnly`, `frontierOnly`, and root `privacyMode`;
570
+ `local status` shows the effective mode and enabled runtime identities.
476
571
 
477
572
  ### Updating the plugin and clearing the cache: `openteam clear-cache`
478
573
 
@@ -500,13 +595,57 @@ explanation of the freeze mechanism and a step-by-step update procedure.
500
595
 
501
596
  ### Iterative work under `openteam-orchestrate` 🎯
502
597
 
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.
598
+ Unattended, backlog-style work is driven entirely from orchestration. The
599
+ current model gets `orchestrator.maxRetries` extra passes (effective runtime
600
+ default `2`, so up to three passes per model). Persistent incompleteness uses
601
+ that model's pass budget before handing the **same node** to the next authorized
602
+ model. A recognized retry-safe prompt transport/capacity failure
603
+ (`prompt-timeout`, `prompt-rate-limited`, `prompt-connection`,
604
+ `prompt-server`, or `prompt-context-length`) is retried on that same model
605
+ within its pass allowance; create-stage retry recognizes the same transport
606
+ classes except context length. `maxRetries` is an upper bound, not a required
607
+ repeat floor: after confirmed stop, an eligible transport failure may consume
608
+ the remaining same-model allowance or hand off earlier to the next authorized
609
+ candidate. Auth,
610
+ content-filter, validation/invalid-model, generic rejection, and missing-session
611
+ or unclassified failures are terminal. A candidate found unavailable before
612
+ dispatch may be skipped because no attempt started. `maxModelsPerNode` has the
613
+ effective runtime default `2`; together the defaults cap a node at up to six
614
+ openteam passes. Neither default is materialized by the schema. This is not a
615
+ guaranteed pass/HTTP-call count or a statement about provider-internal SDK
616
+ retries.
617
+ The batch, objective, report path, and durable work state are preserved. An
618
+ exact pin without `fallbacks` authorizes no second model.
619
+
620
+ Every pass, including a single pass with `maxRetries: 0`, needs a standalone
621
+ `openteam-status: done` declaration **and** a durable report with at least 40
622
+ substantive body characters after actual declaration lines are removed. The
623
+ last standalone declaration outside fenced code wins; quoted or fenced examples
624
+ remain body text and cannot declare completion. A marker alone is not
625
+ completion. The coordinator reads the report text only to parse that status and
626
+ test substance; it does not copy the body into role results, routing telemetry,
627
+ diagnostics, or the reference-only projection.
628
+
629
+ After a timed-out or otherwise thrown/uncertain prompt dispatch, the prior SDK
630
+ request must settle and server stop must be confirmed before any same-model
631
+ retry or model handoff. After a successful abort, bounded polling confirms stop
632
+ when the exact session is `idle` or is absent from a valid sparse status
633
+ snapshot; malformed/missing status payloads and sessions that remain `busy` or
634
+ `retry` block continuation. Status evidence alone does not replace request
635
+ settlement. Exhaustion is terminal failure/incomplete, and dependents do not
636
+ run. See
637
+ **[docs/guide/commands.md](docs/guide/commands.md)**.
504
638
 
505
639
  ### Multi-session web Console: `openteam console`
506
640
 
507
641
  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
642
 
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).
643
+ It shows live: **recorded token usage and cost estimates per message**
644
+ (input/output/reasoning/cache), **tool calls with duration** and ok/fail, recent
645
+ routing (cost/savings and chosen model — **prompt hashes only, never text**),
646
+ agent decisions and meetings, orchestration progress, the team, and commits.
647
+ These controls and estimates are finite/auditable, but they are not provider
648
+ invoice accounting.
510
649
 
511
650
  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
651
 
@@ -549,7 +688,7 @@ block):
549
688
 
550
689
  ### Team memory over MCP (read-only): `openteam mcp`
551
690
 
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).
691
+ `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
692
 
554
693
  Register it in `opencode.json` like any local MCP server:
555
694
 
@@ -561,6 +700,12 @@ Register it in `opencode.json` like any local MCP server:
561
700
  }
562
701
  ```
563
702
 
703
+ The plugin's **`openteam-memory` write tool** is separate from this read-only
704
+ MCP server and from the general scribe role's model policy. Its extraction
705
+ transport is explicitly local-only and requires a reachable configured local
706
+ runtime. Global `frontier` execution refuses the memory-model call; the tool
707
+ does not add a frontier memory path or silently fall back to frontier.
708
+
564
709
  #### Semantic recall (opt-in): `recall_facts`, `recall_preferences`, `similar_tasks`
565
710
 
566
711
  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 +837,24 @@ and remove its inherited GitHub Actions secret.
692
837
 
693
838
  ## Privacy
694
839
 
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`.
840
+ openteam does **not** detect prompt sensitivity or promise to intercept sensitive
841
+ content before a frontier call. `privacyMode` is deprecated and has no routing
842
+ authority. `frontier` and `mixed` deliberately permit frontier egress under the
843
+ configured agent policy. To prohibit it, configure
844
+ `router.executionMode: "local"` globally, or narrow the relevant agent to
845
+ `local` under global `mixed`.
696
846
 
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.
847
+ Credential redaction, prompt-hash telemetry, reference-only shared storage,
848
+ loopback/access controls, and finite budget/model/pass limits remain in force.
849
+ Telemetry uses `hashPrompt` (currently `fnv1a32:<hash>`) and never serializes the
850
+ full prompt. See [SECURITY.md](SECURITY.md) for vulnerability reporting.
698
851
 
699
852
  ## Quality
700
853
 
701
854
  Non-negotiable gates (see [docs/testing-and-quality.md](docs/testing-and-quality.md)):
702
855
 
703
856
  - `bun test`
704
- - Global coverage: lines `>= 85%`, branches `>= 80%`.
857
+ - Global coverage: lines/functions `>= 85%`.
705
858
  - Coverage of `src/router/**` and `src/memory/**`: `100%` lines and functions (enforced by `bun run coverage:check`).
706
859
  - `bun run typecheck`
707
860
  - `biome check .` (or `bun run lint` + `bun run format:check`)
@@ -714,7 +867,7 @@ See [docs/compatibility.md](docs/compatibility.md). Verified summary as of `2026
714
867
 
715
868
  | Surface | Version / contract |
716
869
  | --- | --- |
717
- | openteam | `0.18.1`; current package version |
870
+ | openteam | `0.19.0`; current package version |
718
871
  | `@opencode-ai/plugin` | `1.18.19` |
719
872
  | `@opencode-ai/sdk` | `1.18.19` |
720
873
  | Routing hook | `chat.message`; not `chat.params` for model changes |
@@ -729,12 +882,13 @@ Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) for th
729
882
  | Phase | Status | Deliverables |
730
883
  | --- | --- | --- |
731
884
  | 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`. |
885
+ | 1. Local registry + router | Done | Ollama/LM Studio/Lemonade/Foundry, probes and deterministic `chooseModel`. |
886
+ | 2. Profiles + budget + telemetry | Done | `ModelCapabilityProfile`, budgets and `CostRecord`. |
734
887
  | 3. Multi-agent orchestration | Done | Coordinator, roles, child sessions and permissions. |
735
888
  | 4. npm packaging + docs | Done | Exportable package, README, Windows examples and compatibility matrix. |
736
889
  | 5. Runtime commands | Done | `baseline show/set/auto`, `doctor` and `report` via tool, `/openteam` and CLI `bin`. |
737
890
  | 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. |
891
+ | 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
892
 
739
893
  ## License
740
894