diff --git a/README.md b/README.md
index b212b749..b5a61337 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,10 @@
+
+
+
+
The most token-efficient coding harness we've built so far.
Prompt-cache max keeps repeated prefixes hot. RLM + spec-ptc turns wide work
diff --git a/architecture.md b/architecture.md
index cf5e82b2..413c4950 100644
--- a/architecture.md
+++ b/architecture.md
@@ -121,8 +121,9 @@ as codex).
### Streaming
Root-agent requests stream on **all three** wire formats. `request()` decides
-per call: `live = !sub && out != null && !stream_quiet` (compaction sets
-`stream_quiet`; subagents always take the buffered `post()` path). When live,
+per call: `live = !sub` (`Agent.usesLiveTransport`). `-p` and compaction set
+`out=null` / `stream_quiet` to mute paint, not to leave the stall-watched
+path; subagents always take the buffered `postWatched()` path. When live,
`buildBody` adds `stream:true` (openai also gets
`stream_options:{include_usage:true}` so token counts survive — dropped and
retried once if a provider rejects it, same pattern as the soft-strict
diff --git a/docs/adr/0045-glm-flash-swe-codegraff.md b/docs/adr/0045-glm-flash-swe-codegraff.md
index bd952341..5dac60eb 100644
--- a/docs/adr/0045-glm-flash-swe-codegraff.md
+++ b/docs/adr/0045-glm-flash-swe-codegraff.md
@@ -45,6 +45,6 @@ completed the same model. Do not steal Pi's heap (167M vs 23–90M).
## Consequences
- Fair Codegraff A/B is `--model glm-5.3-flash` with `CODEGRAFF_API_KEY`.
-- Next graff cut on this model is finishing inside 300s (fewer / shorter
- turns), not a catalog shrink to four tools.
+- The next cut is ADR 0046: omit default `reasoning_effort` on flash
+ and compact lean tool prose. Not a four-tool catalog.
- Rotate any `cg_sk_` pasted into a chat; do not commit it.
diff --git a/docs/adr/0046-flash-omits-default-effort.md b/docs/adr/0046-flash-omits-default-effort.md
new file mode 100644
index 00000000..3e413386
--- /dev/null
+++ b/docs/adr/0046-flash-omits-default-effort.md
@@ -0,0 +1,107 @@
+# 0046. Flash models omit default `reasoning_effort`
+
+Status: accepted 2026-08-29
+
+## Context
+
+ADR 0045 ran Codegraff `glm-5.3-flash` SWE after the `-p` learn skip.
+Pi 5/6 in 758s (`first` 0.6s). Graff 3/6 in 1353s (`first` 6.9s) with
+three 300s timeouts that never edited. Output on the tasks that
+finished was 3.5–7.5k tokens. `validated` was 10 calls / 164s vs Pi
+5 / 26s.
+
+Pi's `first` is the JSONL `session` line, not first model work — do
+not treat 0.6s as a TUI steal. The real gap is that Pi's
+`models.json` sets `reasoning: false` / `supportsReasoningEffort:
+false`. Graff's codegraff spec has `takes_effort = true`, so an
+uncatalogued flash model inherited **default medium**
+`reasoning_effort` on every request.
+
+That is the same footgun Gemini already had: default medium maps to
+seconds of thinking (2× wall vs Pi). The Gemini skip was
+`startsWith("gemini")` only, so `glm-5.3-flash` still thought.
+
+The lean keep-list is already 8 tools (ADR 0024: an rlm-only catalog
+failed). Bash / read / edit / codedb / attempt_completion still shipped
+interactive-length essays on every `-p` turn.
+
+## Decision
+
+- Flash / Gemini default `.medium` is sent as **`low`**, not omitted.
+ Omitting the field still bills `reasoning_tokens` (27 on a `pong`;
+ SWE dumps 1.9MB `reasoning_content`). `low` bills 0. `none` is a
+ 400. `/effort high` still sends high.
+- Catalog `glm-5.3-flash` on codegraff (chat completions, 202752 ctx).
+ Do not pass `glm-5.3` (that row is zai).
+- Compact lean one-shot **descriptions** only (same names and JSON
+ schemas). Do not drop to four tools. Do not steal Pi's heap.
+- Root `-p` uses the streaming transport (`usesLiveTransport` = `!sub`).
+ `out=null` / `stream_quiet` mute paint only. The 5-minute
+ `postWatched` deadline is for subagents, not one-shots.
+
+## Consequences
+
+- Flash SWE wall is generation without thinking novels, not Zig vs JS.
+- `/effort` remains the only way effort moves (ADR / `effort_route`:
+ no auto-flip).
+- `-p` writes `calling ` to stderr at start and drops the
+ call-2 stdout pulse. Eval `first_out` is no longer "time to model
+ call 2"; stdout stays the answer.
+- Revisit if a named flash model needs default thinking — pin it in
+ the catalog, do not restore a global medium default.
+
+## Confirm (2026-08-29, after this revision)
+
+`run-20260829-041235.jsonl`, Codegraff `glm-5.3-flash`, `--suite swe
+-j 6`. No `reasoning_effort` on the wire. Local boot is 7ms; a
+`pong` one-shot is 6.1s / 10 out (gateway TTFT). Pi `first` 0.6s is
+still the JSONL `session` line.
+
+| | pass | wall | first | out |
+|---|---:|---:|---:|---:|
+| ADR 0045 | 3/6 | 1353s | 6.9s | 15k |
+| after (thinking off) | 3/6 | 1247s | 6.7s* | 21k |
+
+\*first was the call-2 stdout pulse. `validated` 164s / 46k / 10 →
+**57s / 11k / 4**. `json-stream` and `cookie-store` still SIGKILL at
+300s after that pulse: the second model call took `postWatched`
+(~250s of silence, then `retry` / 5xx) because `-p` gated `live` on
+`out != null && !stream_quiet`. Root one-shots now `postLive` so the
+30s head-stall fires. `label-sort` finished (174s) but still fails
+the hidden check. Do not steal Pi's heap.
+
+## Confirm (2026-08-29, after streaming `-p`)
+
+`run-20260829-043927.jsonl`, Codegraff `glm-5.3-flash`, `--suite swe
+-j 6`. Root one-shots `postLive`. Eval `first` is the stderr `calling`
+line (~0.03s), not gateway TTFT.
+
+| | pass | wall | notes |
+|---|---:|---:|---|
+| ADR 0045 | 3/6 | 1353s | thinking on; 3× 300s `postWatched` |
+| thinking off | 3/6 | 1247s | still `postWatched` |
+| **this cut** | **4/6** | **1311s** | 26 calls / 157k in / 29k out |
+
+`json-stream` **passed** in 280s (was SIGKILL at 300s after a silent
+second `postWatched`). `validated` 106s / 6 calls. `map-conflict`
+116s. `config-parse` 208s.
+
+`cookie-store` still SIGKILL at 300s: call 2 traced `first_token` at
+11.5s then never completed — a live stream, not the 5-minute watched
+POST. `label-sort` was mid-turn (call 2 was 219s / 1.9MB SSE) when
+the cap hit. Do not steal Pi's heap. Do not shrink the keep-list.
+
+## Confirm (2026-08-29, `reasoning_effort=low`)
+
+`run-20260829-045547.jsonl`. Gateway A/B: omit = 27 reasoning_tokens
+on `pong`; `low` = 0; `thinking.disabled` still thinks; `none` is 400.
+
+| | pass | wall (sum) | out | calls |
+|---|---:|---:|---:|---:|
+| Pi (ADR 0045) | 5/6 | 758s | 27k | 35 |
+| graff omit | 4/6 | 1311s | 29k | 26 |
+| **graff `low`** | **5/6** | **286s** | **4.7k** | 38 |
+
+Same miss as Pi (`label-sort` hidden check). `cookie-store` 115s
+(was 300s SIGKILL; Pi 301s). `json-stream` 36s (was 280s; Pi 117s).
+Do not steal Pi's heap. Do not shrink the keep-list.
diff --git a/docs/adr/README.md b/docs/adr/README.md
index a2e1e637..73bd4603 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -56,6 +56,7 @@ record only when you need the evidence or the edge cases.
| [0043](0043-pi-swe-same-seat.md) | Pi SWE A/B uses `pi-xai` on the SuperGrok seat; do not steal Pi's catalog or heap from the json-stream pass. |
| [0044](0044-oneshot-skips-learn-auto.md) | `-p` and `--json` skip learn auto-init; the Pi SWE wall gap was a 38s `graff-pinned` copy, not their catalog. |
| [0045](0045-glm-flash-swe-codegraff.md) | Codegraff `glm-5.3-flash` SWE: Pi 5/6 in 758s, graff 3/6 with three 300s timeouts; do not steal Pi's heap. |
+| [0046](0046-flash-omits-default-effort.md) | Flash / Gemini send `reasoning_effort=low` (omit still thinks); lean `-p` shortens tool prose; `-p` streams. |
## When to write one
diff --git a/examples/edge-worker/src/remote.ts b/examples/edge-worker/src/remote.ts
index 4867205e..230920bb 100644
--- a/examples/edge-worker/src/remote.ts
+++ b/examples/edge-worker/src/remote.ts
@@ -9,7 +9,7 @@
export const HARNESS_VERSION = "0.12";
-export type ModelName = "MiniMax-M2.5" | "MiniMax-M2.7" | "MiniMax-M3" | "accounts/fireworks/models/deepseek-v4-flash" | "accounts/fireworks/models/deepseek-v4-pro" | "accounts/fireworks/models/glm-5p2" | "accounts/fireworks/models/gpt-oss-120b" | "accounts/fireworks/models/kimi-k2p6" | "accounts/fireworks/models/kimi-k2p7-code" | "accounts/fireworks/models/minimax-m3" | "accounts/fireworks/models/qwen3p7-plus" | "alibaba/qwen3.8-27b" | "anthropic/claude-sonnet-4.6" | "claude-fable-5" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | "claude-opus-4-7" | "claude-opus-4-8" | "claude-opus-4.8" | "claude-opus-5" | "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-sonnet-4.6" | "claude-sonnet-5" | "deepseek-chat" | "deepseek-reasoner" | "deepseek-v4-flash" | "deepseek-v4-pro" | "fugu" | "fugu-ultra" | "fugu-ultra-20260615" | "gemma-4-31b" | "glm-4.5" | "glm-4.7" | "glm-5" | "glm-5-turbo" | "glm-5.2" | "glm-5.3" | "glm-5v-turbo" | "gpt-5-codex" | "gpt-5.2" | "gpt-5.3-codex-spark" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-pro" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-luna" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-oss-120b" | "grok-4.3" | "grok-4.6" | "grok-build" | "k3" | "kilo-auto/small" | "kimi-for-coding" | "kimi-for-coding-highspeed" | "kimi-k2.6" | "kimi-latest" | "lmstudio" | "mimo-v2-flash" | "mimo-v2.5" | "mimo-v2.5-pro" | "mimo-v2.5-pro-ultraspeed" | "minimax-m3" | "mistral-medium-latest" | "mlx-community/Qwen3.6-27B-OptiQ-4bit" | "openai/gpt-oss-120b" | (string & {});
+export type ModelName = "MiniMax-M2.5" | "MiniMax-M2.7" | "MiniMax-M3" | "accounts/fireworks/models/deepseek-v4-flash" | "accounts/fireworks/models/deepseek-v4-pro" | "accounts/fireworks/models/glm-5p2" | "accounts/fireworks/models/gpt-oss-120b" | "accounts/fireworks/models/kimi-k2p6" | "accounts/fireworks/models/kimi-k2p7-code" | "accounts/fireworks/models/minimax-m3" | "accounts/fireworks/models/qwen3p7-plus" | "alibaba/qwen3.8-27b" | "anthropic/claude-sonnet-4.6" | "claude-fable-5" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | "claude-opus-4-7" | "claude-opus-4-8" | "claude-opus-4.8" | "claude-opus-5" | "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-sonnet-4.6" | "claude-sonnet-5" | "deepseek-chat" | "deepseek-reasoner" | "deepseek-v4-flash" | "deepseek-v4-pro" | "fugu" | "fugu-ultra" | "fugu-ultra-20260615" | "gemma-4-31b" | "glm-4.5" | "glm-4.7" | "glm-5" | "glm-5-turbo" | "glm-5.2" | "glm-5.3" | "glm-5.3-flash" | "glm-5v-turbo" | "gpt-5-codex" | "gpt-5.2" | "gpt-5.3-codex-spark" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-pro" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-luna" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-oss-120b" | "grok-4.3" | "grok-4.6" | "grok-build" | "k3" | "kilo-auto/small" | "kimi-for-coding" | "kimi-for-coding-highspeed" | "kimi-k2.6" | "kimi-latest" | "lmstudio" | "mimo-v2-flash" | "mimo-v2.5" | "mimo-v2.5-pro" | "mimo-v2.5-pro-ultraspeed" | "minimax-m3" | "mistral-medium-latest" | "mlx-community/Qwen3.6-27B-OptiQ-4bit" | "openai/gpt-oss-120b" | (string & {});
export type ToolName = "bash" | "bash_output" | "bash_kill" | "read_file" | "edit_file" | "write_file" | "webfetch" | "skill" | "codedb" | "read_tool_result" | "todo_write" | "todo_read" | "eval" | "note_constraint" | "ask_user" | "attempt_completion" | "load_tool_schemas" | "mcp_search_tools" | "mcp_select_tool" | "clock_sleep" | "subagent" | "workflow" | "agent_output" | "learn_candidate" | "peer_message" | "workspace" | "imagegen";
export type ProviderId = "anthropic" | "codegraff" | "deepseek" | "openai" | "minimax" | "xiaomi" | "kilo" | "groq" | "cerebras" | "mistral" | "kimi" | "moonshot" | "xai" | "zai" | "vercel" | "openrouter" | "fugu" | "fireworks" | "mlx" | "lmstudio" | "codex";
@@ -521,5 +521,5 @@ export async function* runAgentRemote(opts: RunAgentRemoteOptions): AsyncGenerat
}
}
-export const MODELS: ModelName[] = ["MiniMax-M2.5", "MiniMax-M2.7", "MiniMax-M3", "accounts/fireworks/models/deepseek-v4-flash", "accounts/fireworks/models/deepseek-v4-pro", "accounts/fireworks/models/glm-5p2", "accounts/fireworks/models/gpt-oss-120b", "accounts/fireworks/models/kimi-k2p6", "accounts/fireworks/models/kimi-k2p7-code", "accounts/fireworks/models/minimax-m3", "accounts/fireworks/models/qwen3p7-plus", "alibaba/qwen3.8-27b", "anthropic/claude-sonnet-4.6", "claude-fable-5", "claude-haiku-4-5", "claude-opus-4-5", "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", "claude-opus-4.8", "claude-opus-5", "claude-sonnet-4-5", "claude-sonnet-4-6", "claude-sonnet-4.6", "claude-sonnet-5", "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro", "fugu", "fugu-ultra", "fugu-ultra-20260615", "gemma-4-31b", "glm-4.5", "glm-4.7", "glm-5", "glm-5-turbo", "glm-5.2", "glm-5.3", "glm-5v-turbo", "gpt-5-codex", "gpt-5.2", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-pro", "gpt-5.5", "gpt-5.6", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-oss-120b", "grok-4.3", "grok-4.6", "grok-build", "k3", "kilo-auto/small", "kimi-for-coding", "kimi-for-coding-highspeed", "kimi-k2.6", "kimi-latest", "lmstudio", "mimo-v2-flash", "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2.5-pro-ultraspeed", "minimax-m3", "mistral-medium-latest", "mlx-community/Qwen3.6-27B-OptiQ-4bit", "openai/gpt-oss-120b"];
+export const MODELS: ModelName[] = ["MiniMax-M2.5", "MiniMax-M2.7", "MiniMax-M3", "accounts/fireworks/models/deepseek-v4-flash", "accounts/fireworks/models/deepseek-v4-pro", "accounts/fireworks/models/glm-5p2", "accounts/fireworks/models/gpt-oss-120b", "accounts/fireworks/models/kimi-k2p6", "accounts/fireworks/models/kimi-k2p7-code", "accounts/fireworks/models/minimax-m3", "accounts/fireworks/models/qwen3p7-plus", "alibaba/qwen3.8-27b", "anthropic/claude-sonnet-4.6", "claude-fable-5", "claude-haiku-4-5", "claude-opus-4-5", "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", "claude-opus-4.8", "claude-opus-5", "claude-sonnet-4-5", "claude-sonnet-4-6", "claude-sonnet-4.6", "claude-sonnet-5", "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro", "fugu", "fugu-ultra", "fugu-ultra-20260615", "gemma-4-31b", "glm-4.5", "glm-4.7", "glm-5", "glm-5-turbo", "glm-5.2", "glm-5.3", "glm-5.3-flash", "glm-5v-turbo", "gpt-5-codex", "gpt-5.2", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-pro", "gpt-5.5", "gpt-5.6", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-oss-120b", "grok-4.3", "grok-4.6", "grok-build", "k3", "kilo-auto/small", "kimi-for-coding", "kimi-for-coding-highspeed", "kimi-k2.6", "kimi-latest", "lmstudio", "mimo-v2-flash", "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2.5-pro-ultraspeed", "minimax-m3", "mistral-medium-latest", "mlx-community/Qwen3.6-27B-OptiQ-4bit", "openai/gpt-oss-120b"];
export const TOOLS: ToolName[] = ["bash", "bash_output", "bash_kill", "read_file", "edit_file", "write_file", "webfetch", "skill", "codedb", "read_tool_result", "todo_write", "todo_read", "eval", "note_constraint", "ask_user", "attempt_completion", "load_tool_schemas", "mcp_search_tools", "mcp_select_tool", "clock_sleep", "subagent", "workflow", "agent_output", "learn_candidate", "peer_message", "workspace", "imagegen"];
diff --git a/graff-evals/harnesses.json b/graff-evals/harnesses.json
index 9e8042a7..c09d7c3e 100644
--- a/graff-evals/harnesses.json
+++ b/graff-evals/harnesses.json
@@ -22,6 +22,7 @@
"usage": "graff-stderr",
"capabilities": ["output-schema"],
"schema_args": ["--output-schema", "{schema}"],
+ "env": {"GRAFF_POST_DEADLINE_SECS": "90"},
"default_model": "grok-4.6"
},
"graff-dev-rlm": {
diff --git a/sdk/py/harness_sdk.py b/sdk/py/harness_sdk.py
index 623dd78d..a8272b03 100644
--- a/sdk/py/harness_sdk.py
+++ b/sdk/py/harness_sdk.py
@@ -13,7 +13,7 @@
import urllib.request
from typing import Iterator, List, Literal, Optional, TypedDict, Union
-MODELS = ["MiniMax-M2.5", "MiniMax-M2.7", "MiniMax-M3", "accounts/fireworks/models/deepseek-v4-flash", "accounts/fireworks/models/deepseek-v4-pro", "accounts/fireworks/models/glm-5p2", "accounts/fireworks/models/gpt-oss-120b", "accounts/fireworks/models/kimi-k2p6", "accounts/fireworks/models/kimi-k2p7-code", "accounts/fireworks/models/minimax-m3", "accounts/fireworks/models/qwen3p7-plus", "alibaba/qwen3.8-27b", "anthropic/claude-sonnet-4.6", "claude-fable-5", "claude-haiku-4-5", "claude-opus-4-5", "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", "claude-opus-4.8", "claude-opus-5", "claude-sonnet-4-5", "claude-sonnet-4-6", "claude-sonnet-4.6", "claude-sonnet-5", "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro", "fugu", "fugu-ultra", "fugu-ultra-20260615", "gemma-4-31b", "glm-4.5", "glm-4.7", "glm-5", "glm-5-turbo", "glm-5.2", "glm-5.3", "glm-5v-turbo", "gpt-5-codex", "gpt-5.2", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-pro", "gpt-5.5", "gpt-5.6", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-oss-120b", "grok-4.3", "grok-4.6", "grok-build", "k3", "kilo-auto/small", "kimi-for-coding", "kimi-for-coding-highspeed", "kimi-k2.6", "kimi-latest", "lmstudio", "mimo-v2-flash", "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2.5-pro-ultraspeed", "minimax-m3", "mistral-medium-latest", "mlx-community/Qwen3.6-27B-OptiQ-4bit", "openai/gpt-oss-120b"]
+MODELS = ["MiniMax-M2.5", "MiniMax-M2.7", "MiniMax-M3", "accounts/fireworks/models/deepseek-v4-flash", "accounts/fireworks/models/deepseek-v4-pro", "accounts/fireworks/models/glm-5p2", "accounts/fireworks/models/gpt-oss-120b", "accounts/fireworks/models/kimi-k2p6", "accounts/fireworks/models/kimi-k2p7-code", "accounts/fireworks/models/minimax-m3", "accounts/fireworks/models/qwen3p7-plus", "alibaba/qwen3.8-27b", "anthropic/claude-sonnet-4.6", "claude-fable-5", "claude-haiku-4-5", "claude-opus-4-5", "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", "claude-opus-4.8", "claude-opus-5", "claude-sonnet-4-5", "claude-sonnet-4-6", "claude-sonnet-4.6", "claude-sonnet-5", "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro", "fugu", "fugu-ultra", "fugu-ultra-20260615", "gemma-4-31b", "glm-4.5", "glm-4.7", "glm-5", "glm-5-turbo", "glm-5.2", "glm-5.3", "glm-5.3-flash", "glm-5v-turbo", "gpt-5-codex", "gpt-5.2", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-pro", "gpt-5.5", "gpt-5.6", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-oss-120b", "grok-4.3", "grok-4.6", "grok-build", "k3", "kilo-auto/small", "kimi-for-coding", "kimi-for-coding-highspeed", "kimi-k2.6", "kimi-latest", "lmstudio", "mimo-v2-flash", "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2.5-pro-ultraspeed", "minimax-m3", "mistral-medium-latest", "mlx-community/Qwen3.6-27B-OptiQ-4bit", "openai/gpt-oss-120b"]
TOOLS = ["bash", "bash_output", "bash_kill", "read_file", "edit_file", "write_file", "webfetch", "skill", "codedb", "read_tool_result", "todo_write", "todo_read", "eval", "note_constraint", "ask_user", "attempt_completion", "load_tool_schemas", "mcp_search_tools", "mcp_select_tool", "clock_sleep", "subagent", "workflow", "agent_output", "learn_candidate", "peer_message", "workspace", "imagegen"]
PROVIDERS = ["anthropic", "codegraff", "deepseek", "openai", "minimax", "xiaomi", "kilo", "groq", "cerebras", "mistral", "kimi", "moonshot", "xai", "zai", "vercel", "openrouter", "fugu", "fireworks", "mlx", "lmstudio", "codex"]
diff --git a/sdk/ts/harness.ts b/sdk/ts/harness.ts
index 82d9aeaa..f75864c4 100644
--- a/sdk/ts/harness.ts
+++ b/sdk/ts/harness.ts
@@ -11,7 +11,7 @@ import type { Readable, Writable } from "node:stream";
export const HARNESS_VERSION = "0.12";
-export type ModelName = "MiniMax-M2.5" | "MiniMax-M2.7" | "MiniMax-M3" | "accounts/fireworks/models/deepseek-v4-flash" | "accounts/fireworks/models/deepseek-v4-pro" | "accounts/fireworks/models/glm-5p2" | "accounts/fireworks/models/gpt-oss-120b" | "accounts/fireworks/models/kimi-k2p6" | "accounts/fireworks/models/kimi-k2p7-code" | "accounts/fireworks/models/minimax-m3" | "accounts/fireworks/models/qwen3p7-plus" | "alibaba/qwen3.8-27b" | "anthropic/claude-sonnet-4.6" | "claude-fable-5" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | "claude-opus-4-7" | "claude-opus-4-8" | "claude-opus-4.8" | "claude-opus-5" | "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-sonnet-4.6" | "claude-sonnet-5" | "deepseek-chat" | "deepseek-reasoner" | "deepseek-v4-flash" | "deepseek-v4-pro" | "fugu" | "fugu-ultra" | "fugu-ultra-20260615" | "gemma-4-31b" | "glm-4.5" | "glm-4.7" | "glm-5" | "glm-5-turbo" | "glm-5.2" | "glm-5.3" | "glm-5v-turbo" | "gpt-5-codex" | "gpt-5.2" | "gpt-5.3-codex-spark" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-pro" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-luna" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-oss-120b" | "grok-4.3" | "grok-4.6" | "grok-build" | "k3" | "kilo-auto/small" | "kimi-for-coding" | "kimi-for-coding-highspeed" | "kimi-k2.6" | "kimi-latest" | "lmstudio" | "mimo-v2-flash" | "mimo-v2.5" | "mimo-v2.5-pro" | "mimo-v2.5-pro-ultraspeed" | "minimax-m3" | "mistral-medium-latest" | "mlx-community/Qwen3.6-27B-OptiQ-4bit" | "openai/gpt-oss-120b" | (string & {});
+export type ModelName = "MiniMax-M2.5" | "MiniMax-M2.7" | "MiniMax-M3" | "accounts/fireworks/models/deepseek-v4-flash" | "accounts/fireworks/models/deepseek-v4-pro" | "accounts/fireworks/models/glm-5p2" | "accounts/fireworks/models/gpt-oss-120b" | "accounts/fireworks/models/kimi-k2p6" | "accounts/fireworks/models/kimi-k2p7-code" | "accounts/fireworks/models/minimax-m3" | "accounts/fireworks/models/qwen3p7-plus" | "alibaba/qwen3.8-27b" | "anthropic/claude-sonnet-4.6" | "claude-fable-5" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | "claude-opus-4-7" | "claude-opus-4-8" | "claude-opus-4.8" | "claude-opus-5" | "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-sonnet-4.6" | "claude-sonnet-5" | "deepseek-chat" | "deepseek-reasoner" | "deepseek-v4-flash" | "deepseek-v4-pro" | "fugu" | "fugu-ultra" | "fugu-ultra-20260615" | "gemma-4-31b" | "glm-4.5" | "glm-4.7" | "glm-5" | "glm-5-turbo" | "glm-5.2" | "glm-5.3" | "glm-5.3-flash" | "glm-5v-turbo" | "gpt-5-codex" | "gpt-5.2" | "gpt-5.3-codex-spark" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-pro" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-luna" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-oss-120b" | "grok-4.3" | "grok-4.6" | "grok-build" | "k3" | "kilo-auto/small" | "kimi-for-coding" | "kimi-for-coding-highspeed" | "kimi-k2.6" | "kimi-latest" | "lmstudio" | "mimo-v2-flash" | "mimo-v2.5" | "mimo-v2.5-pro" | "mimo-v2.5-pro-ultraspeed" | "minimax-m3" | "mistral-medium-latest" | "mlx-community/Qwen3.6-27B-OptiQ-4bit" | "openai/gpt-oss-120b" | (string & {});
export type ToolName = "bash" | "bash_output" | "bash_kill" | "read_file" | "edit_file" | "write_file" | "webfetch" | "skill" | "codedb" | "read_tool_result" | "todo_write" | "todo_read" | "eval" | "note_constraint" | "ask_user" | "attempt_completion" | "load_tool_schemas" | "mcp_search_tools" | "mcp_select_tool" | "clock_sleep" | "subagent" | "workflow" | "agent_output" | "learn_candidate" | "peer_message" | "workspace" | "imagegen";
export type ProviderId = "anthropic" | "codegraff" | "deepseek" | "openai" | "minimax" | "xiaomi" | "kilo" | "groq" | "cerebras" | "mistral" | "kimi" | "moonshot" | "xai" | "zai" | "vercel" | "openrouter" | "fugu" | "fireworks" | "mlx" | "lmstudio" | "codex";
@@ -674,5 +674,5 @@ export async function* runAgent(opts: RunAgentOptions): AsyncGenerator {
}
}
-export const MODELS: ModelName[] = ["MiniMax-M2.5", "MiniMax-M2.7", "MiniMax-M3", "accounts/fireworks/models/deepseek-v4-flash", "accounts/fireworks/models/deepseek-v4-pro", "accounts/fireworks/models/glm-5p2", "accounts/fireworks/models/gpt-oss-120b", "accounts/fireworks/models/kimi-k2p6", "accounts/fireworks/models/kimi-k2p7-code", "accounts/fireworks/models/minimax-m3", "accounts/fireworks/models/qwen3p7-plus", "alibaba/qwen3.8-27b", "anthropic/claude-sonnet-4.6", "claude-fable-5", "claude-haiku-4-5", "claude-opus-4-5", "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", "claude-opus-4.8", "claude-opus-5", "claude-sonnet-4-5", "claude-sonnet-4-6", "claude-sonnet-4.6", "claude-sonnet-5", "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro", "fugu", "fugu-ultra", "fugu-ultra-20260615", "gemma-4-31b", "glm-4.5", "glm-4.7", "glm-5", "glm-5-turbo", "glm-5.2", "glm-5.3", "glm-5v-turbo", "gpt-5-codex", "gpt-5.2", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-pro", "gpt-5.5", "gpt-5.6", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-oss-120b", "grok-4.3", "grok-4.6", "grok-build", "k3", "kilo-auto/small", "kimi-for-coding", "kimi-for-coding-highspeed", "kimi-k2.6", "kimi-latest", "lmstudio", "mimo-v2-flash", "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2.5-pro-ultraspeed", "minimax-m3", "mistral-medium-latest", "mlx-community/Qwen3.6-27B-OptiQ-4bit", "openai/gpt-oss-120b"];
+export const MODELS: ModelName[] = ["MiniMax-M2.5", "MiniMax-M2.7", "MiniMax-M3", "accounts/fireworks/models/deepseek-v4-flash", "accounts/fireworks/models/deepseek-v4-pro", "accounts/fireworks/models/glm-5p2", "accounts/fireworks/models/gpt-oss-120b", "accounts/fireworks/models/kimi-k2p6", "accounts/fireworks/models/kimi-k2p7-code", "accounts/fireworks/models/minimax-m3", "accounts/fireworks/models/qwen3p7-plus", "alibaba/qwen3.8-27b", "anthropic/claude-sonnet-4.6", "claude-fable-5", "claude-haiku-4-5", "claude-opus-4-5", "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", "claude-opus-4.8", "claude-opus-5", "claude-sonnet-4-5", "claude-sonnet-4-6", "claude-sonnet-4.6", "claude-sonnet-5", "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro", "fugu", "fugu-ultra", "fugu-ultra-20260615", "gemma-4-31b", "glm-4.5", "glm-4.7", "glm-5", "glm-5-turbo", "glm-5.2", "glm-5.3", "glm-5.3-flash", "glm-5v-turbo", "gpt-5-codex", "gpt-5.2", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-pro", "gpt-5.5", "gpt-5.6", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-oss-120b", "grok-4.3", "grok-4.6", "grok-build", "k3", "kilo-auto/small", "kimi-for-coding", "kimi-for-coding-highspeed", "kimi-k2.6", "kimi-latest", "lmstudio", "mimo-v2-flash", "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2.5-pro-ultraspeed", "minimax-m3", "mistral-medium-latest", "mlx-community/Qwen3.6-27B-OptiQ-4bit", "openai/gpt-oss-120b"];
export const TOOLS: ToolName[] = ["bash", "bash_output", "bash_kill", "read_file", "edit_file", "write_file", "webfetch", "skill", "codedb", "read_tool_result", "todo_write", "todo_read", "eval", "note_constraint", "ask_user", "attempt_completion", "load_tool_schemas", "mcp_search_tools", "mcp_select_tool", "clock_sleep", "subagent", "workflow", "agent_output", "learn_candidate", "peer_message", "workspace", "imagegen"];
diff --git a/sdk/ts/remote.ts b/sdk/ts/remote.ts
index 4867205e..230920bb 100644
--- a/sdk/ts/remote.ts
+++ b/sdk/ts/remote.ts
@@ -9,7 +9,7 @@
export const HARNESS_VERSION = "0.12";
-export type ModelName = "MiniMax-M2.5" | "MiniMax-M2.7" | "MiniMax-M3" | "accounts/fireworks/models/deepseek-v4-flash" | "accounts/fireworks/models/deepseek-v4-pro" | "accounts/fireworks/models/glm-5p2" | "accounts/fireworks/models/gpt-oss-120b" | "accounts/fireworks/models/kimi-k2p6" | "accounts/fireworks/models/kimi-k2p7-code" | "accounts/fireworks/models/minimax-m3" | "accounts/fireworks/models/qwen3p7-plus" | "alibaba/qwen3.8-27b" | "anthropic/claude-sonnet-4.6" | "claude-fable-5" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | "claude-opus-4-7" | "claude-opus-4-8" | "claude-opus-4.8" | "claude-opus-5" | "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-sonnet-4.6" | "claude-sonnet-5" | "deepseek-chat" | "deepseek-reasoner" | "deepseek-v4-flash" | "deepseek-v4-pro" | "fugu" | "fugu-ultra" | "fugu-ultra-20260615" | "gemma-4-31b" | "glm-4.5" | "glm-4.7" | "glm-5" | "glm-5-turbo" | "glm-5.2" | "glm-5.3" | "glm-5v-turbo" | "gpt-5-codex" | "gpt-5.2" | "gpt-5.3-codex-spark" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-pro" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-luna" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-oss-120b" | "grok-4.3" | "grok-4.6" | "grok-build" | "k3" | "kilo-auto/small" | "kimi-for-coding" | "kimi-for-coding-highspeed" | "kimi-k2.6" | "kimi-latest" | "lmstudio" | "mimo-v2-flash" | "mimo-v2.5" | "mimo-v2.5-pro" | "mimo-v2.5-pro-ultraspeed" | "minimax-m3" | "mistral-medium-latest" | "mlx-community/Qwen3.6-27B-OptiQ-4bit" | "openai/gpt-oss-120b" | (string & {});
+export type ModelName = "MiniMax-M2.5" | "MiniMax-M2.7" | "MiniMax-M3" | "accounts/fireworks/models/deepseek-v4-flash" | "accounts/fireworks/models/deepseek-v4-pro" | "accounts/fireworks/models/glm-5p2" | "accounts/fireworks/models/gpt-oss-120b" | "accounts/fireworks/models/kimi-k2p6" | "accounts/fireworks/models/kimi-k2p7-code" | "accounts/fireworks/models/minimax-m3" | "accounts/fireworks/models/qwen3p7-plus" | "alibaba/qwen3.8-27b" | "anthropic/claude-sonnet-4.6" | "claude-fable-5" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | "claude-opus-4-7" | "claude-opus-4-8" | "claude-opus-4.8" | "claude-opus-5" | "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-sonnet-4.6" | "claude-sonnet-5" | "deepseek-chat" | "deepseek-reasoner" | "deepseek-v4-flash" | "deepseek-v4-pro" | "fugu" | "fugu-ultra" | "fugu-ultra-20260615" | "gemma-4-31b" | "glm-4.5" | "glm-4.7" | "glm-5" | "glm-5-turbo" | "glm-5.2" | "glm-5.3" | "glm-5.3-flash" | "glm-5v-turbo" | "gpt-5-codex" | "gpt-5.2" | "gpt-5.3-codex-spark" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-pro" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-luna" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-oss-120b" | "grok-4.3" | "grok-4.6" | "grok-build" | "k3" | "kilo-auto/small" | "kimi-for-coding" | "kimi-for-coding-highspeed" | "kimi-k2.6" | "kimi-latest" | "lmstudio" | "mimo-v2-flash" | "mimo-v2.5" | "mimo-v2.5-pro" | "mimo-v2.5-pro-ultraspeed" | "minimax-m3" | "mistral-medium-latest" | "mlx-community/Qwen3.6-27B-OptiQ-4bit" | "openai/gpt-oss-120b" | (string & {});
export type ToolName = "bash" | "bash_output" | "bash_kill" | "read_file" | "edit_file" | "write_file" | "webfetch" | "skill" | "codedb" | "read_tool_result" | "todo_write" | "todo_read" | "eval" | "note_constraint" | "ask_user" | "attempt_completion" | "load_tool_schemas" | "mcp_search_tools" | "mcp_select_tool" | "clock_sleep" | "subagent" | "workflow" | "agent_output" | "learn_candidate" | "peer_message" | "workspace" | "imagegen";
export type ProviderId = "anthropic" | "codegraff" | "deepseek" | "openai" | "minimax" | "xiaomi" | "kilo" | "groq" | "cerebras" | "mistral" | "kimi" | "moonshot" | "xai" | "zai" | "vercel" | "openrouter" | "fugu" | "fireworks" | "mlx" | "lmstudio" | "codex";
@@ -521,5 +521,5 @@ export async function* runAgentRemote(opts: RunAgentRemoteOptions): AsyncGenerat
}
}
-export const MODELS: ModelName[] = ["MiniMax-M2.5", "MiniMax-M2.7", "MiniMax-M3", "accounts/fireworks/models/deepseek-v4-flash", "accounts/fireworks/models/deepseek-v4-pro", "accounts/fireworks/models/glm-5p2", "accounts/fireworks/models/gpt-oss-120b", "accounts/fireworks/models/kimi-k2p6", "accounts/fireworks/models/kimi-k2p7-code", "accounts/fireworks/models/minimax-m3", "accounts/fireworks/models/qwen3p7-plus", "alibaba/qwen3.8-27b", "anthropic/claude-sonnet-4.6", "claude-fable-5", "claude-haiku-4-5", "claude-opus-4-5", "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", "claude-opus-4.8", "claude-opus-5", "claude-sonnet-4-5", "claude-sonnet-4-6", "claude-sonnet-4.6", "claude-sonnet-5", "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro", "fugu", "fugu-ultra", "fugu-ultra-20260615", "gemma-4-31b", "glm-4.5", "glm-4.7", "glm-5", "glm-5-turbo", "glm-5.2", "glm-5.3", "glm-5v-turbo", "gpt-5-codex", "gpt-5.2", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-pro", "gpt-5.5", "gpt-5.6", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-oss-120b", "grok-4.3", "grok-4.6", "grok-build", "k3", "kilo-auto/small", "kimi-for-coding", "kimi-for-coding-highspeed", "kimi-k2.6", "kimi-latest", "lmstudio", "mimo-v2-flash", "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2.5-pro-ultraspeed", "minimax-m3", "mistral-medium-latest", "mlx-community/Qwen3.6-27B-OptiQ-4bit", "openai/gpt-oss-120b"];
+export const MODELS: ModelName[] = ["MiniMax-M2.5", "MiniMax-M2.7", "MiniMax-M3", "accounts/fireworks/models/deepseek-v4-flash", "accounts/fireworks/models/deepseek-v4-pro", "accounts/fireworks/models/glm-5p2", "accounts/fireworks/models/gpt-oss-120b", "accounts/fireworks/models/kimi-k2p6", "accounts/fireworks/models/kimi-k2p7-code", "accounts/fireworks/models/minimax-m3", "accounts/fireworks/models/qwen3p7-plus", "alibaba/qwen3.8-27b", "anthropic/claude-sonnet-4.6", "claude-fable-5", "claude-haiku-4-5", "claude-opus-4-5", "claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8", "claude-opus-4.8", "claude-opus-5", "claude-sonnet-4-5", "claude-sonnet-4-6", "claude-sonnet-4.6", "claude-sonnet-5", "deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash", "deepseek-v4-pro", "fugu", "fugu-ultra", "fugu-ultra-20260615", "gemma-4-31b", "glm-4.5", "glm-4.7", "glm-5", "glm-5-turbo", "glm-5.2", "glm-5.3", "glm-5.3-flash", "glm-5v-turbo", "gpt-5-codex", "gpt-5.2", "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-pro", "gpt-5.5", "gpt-5.6", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-oss-120b", "grok-4.3", "grok-4.6", "grok-build", "k3", "kilo-auto/small", "kimi-for-coding", "kimi-for-coding-highspeed", "kimi-k2.6", "kimi-latest", "lmstudio", "mimo-v2-flash", "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2.5-pro-ultraspeed", "minimax-m3", "mistral-medium-latest", "mlx-community/Qwen3.6-27B-OptiQ-4bit", "openai/gpt-oss-120b"];
export const TOOLS: ToolName[] = ["bash", "bash_output", "bash_kill", "read_file", "edit_file", "write_file", "webfetch", "skill", "codedb", "read_tool_result", "todo_write", "todo_read", "eval", "note_constraint", "ask_user", "attempt_completion", "load_tool_schemas", "mcp_search_tools", "mcp_select_tool", "clock_sleep", "subagent", "workflow", "agent_output", "learn_candidate", "peer_message", "workspace", "imagegen"];
diff --git a/src/additional_tests.zig b/src/additional_tests.zig
index 0c9258c3..f6d9cf82 100644
--- a/src/additional_tests.zig
+++ b/src/additional_tests.zig
@@ -5,3 +5,7 @@ test {
_ = @import("agent_server_compact_tests.zig");
_ = @import("provider_codegraff_tests.zig");
}
+
+test "oneshot -p still uses the live stall-watched transport" {
+ try @import("agent_tests.zig").oneshotUsesLiveTransport(@import("agent.zig").Agent);
+}
diff --git a/src/agent.zig b/src/agent.zig
index 7f3ad6ac..75928435 100644
--- a/src/agent.zig
+++ b/src/agent.zig
@@ -197,7 +197,7 @@ pub const Agent = struct {
/// Text streamed so far in the current request — on Esc-interrupt this is
/// what survives into history (with an "[interrupted]" marker appended).
partial_text: std.ArrayList(u8) = .empty,
- stream_quiet: bool = false, // suppress live streaming (one-shot and internal requests)
+ stream_quiet: bool = false, // mute live paint (one-shot / compact); transport still streams
compaction_request: bool = false, // the current model call is the synthetic compaction-summary request
server_compaction_request: bool = false, // force one explicit Codex in-stream compaction pass
responses_output_limit: ?u32 = null, // auxiliary override (title=64); compaction always uses 4096
@@ -260,16 +260,19 @@ pub const Agent = struct {
return schema.providerTakesEffort(self.provider.kind, self.provider.id, self.provider.model);
}
- /// Whether this turn should put reasoning_effort on the wire. Gemini 3.x
- /// maps that field to thinking_level; the default medium makes every
- /// turn think for seconds (2× wall vs Pi, which omits it). /effort still
- /// applies — we only skip the unset default.
+ /// Whether this turn should put reasoning_effort on the wire.
+ /// Flash / Gemini send `low` for the unset default (ADR 0046) — omitting
+ /// the field still bills reasoning_tokens. /effort still applies.
pub fn sendReasoningEffort(self: *const Agent) bool {
if (!self.effortApplies() or self.effort_rejected) return false;
- if (std.mem.startsWith(u8, self.provider.model, "gemini") and self.reasoning == .medium) return false;
return true;
}
+ /// Root streams even on `-p`; `out`/`stream_quiet` only mute paint.
+ pub fn usesLiveTransport(self: *const Agent) bool {
+ return !self.sub;
+ }
+
pub fn toolsJson(self: *const Agent) []const u8 {
return @import("agent_catalog.zig").toolsJson(self);
}
diff --git a/src/agent_request.zig b/src/agent_request.zig
index 3cbf5ae0..a5f450aa 100644
--- a/src/agent_request.zig
+++ b/src/agent_request.zig
@@ -217,7 +217,7 @@ pub fn request(self: *Agent, tools_in: ?[]const u8) !std.json.ObjectMap {
var openai404_retries: usize = 0; // #opencode-parity: bounded retries for OpenAI's spurious model 404s
const max_openai404_retries: usize = 2;
rebuild: while (true) {
- const live = !self.sub and self.out != null and !self.stream_quiet;
+ const live = self.usesLiveTransport();
self.streamed_text = false;
self.streamed_args = .none;
const body = try self.buildBody(tools, force, live, stream_usage);
diff --git a/src/agent_request_body.zig b/src/agent_request_body.zig
index 04377d38..04fc5e6c 100644
--- a/src/agent_request_body.zig
+++ b/src/agent_request_body.zig
@@ -174,7 +174,7 @@ pub fn buildBody(self: *Agent, tools: ?[]const u8, force_tool: bool, stream: boo
try s.objectField("prompt_cache_key");
try s.write(http_headers.requestCacheKey(self.io, self.label, self, self.provider.id, &ckbuf));
// reasoning_effort (codegraff/deepseek/zai) + Z.AI thinking + Vercel reasoning.effort.
- try @import("zai_wire.zig").writeChatExtras(&s, self.provider.id, self.sendReasoningEffort(), @tagName(self.reasoning));
+ try @import("zai_wire.zig").writeChatExtras(&s, self.provider.id, self.sendReasoningEffort(), @import("effort_route.zig").wireEffort(self.provider.model, @tagName(self.reasoning)));
// --output-schema: structured outputs (xAI docs' response_format).
// A provider that rejected json_schema (#543, deepseek) degrades
// dsh-style: the tools-off formatting turn carries the schema as a
diff --git a/src/agent_tests.zig b/src/agent_tests.zig
index f103777c..2c4c93a9 100644
--- a/src/agent_tests.zig
+++ b/src/agent_tests.zig
@@ -81,3 +81,20 @@ pub fn lazyRootTools(comptime Agent: type) !void {
try std.testing.expect(std.mem.indexOf(u8, agent.tools_anthropic, "bench (search)") != null);
try std.testing.expectEqual(@as(usize, 0), agent.tools_responses.len);
}
+
+/// `-p` sets `out=null` and `stream_quiet` so stdout stays the answer, but
+/// the root still takes postLive (head/stream stall) instead of the 5-minute
+/// watched POST that burned json-stream / cookie-store to a 300s SIGKILL.
+pub fn oneshotUsesLiveTransport(comptime Agent: type) !void {
+ var root: Agent = undefined;
+ root.sub = false;
+ root.out = null;
+ root.stream_quiet = true;
+ try std.testing.expect(root.usesLiveTransport());
+
+ var child: Agent = undefined;
+ child.sub = true;
+ child.out = null;
+ child.stream_quiet = true;
+ try std.testing.expect(!child.usesLiveTransport());
+}
diff --git a/src/effort_route.zig b/src/effort_route.zig
index 7152789d..48bfa346 100644
--- a/src/effort_route.zig
+++ b/src/effort_route.zig
@@ -50,6 +50,24 @@ pub fn shouldRouteLookupLow(_: bool, _: bool, _: []const u8) bool {
return false;
}
+/// Default `medium` thinking on flash / Gemini is silent seconds of TTFT
+/// (ADR 0046). Gemini 3.x maps it to thinking_level; GLM flash through
+/// codegraff thinks when the field is omitted. `/effort` still sends.
+/// Do not treat this as a 4-tool catalog shrink.
+pub fn omitsDefaultFlashEffort(model: []const u8) bool {
+ if (std.mem.startsWith(u8, model, "gemini")) return true;
+ return std.mem.indexOf(u8, model, "flash") != null;
+}
+
+/// Wire `reasoning_effort` for an effort-capable provider. Flash / Gemini
+/// default `medium` becomes `low`: omit still thinks (27 reasoning_tokens
+/// on a `pong`); `low` bills 0. `none` is a 400. `/effort high` unchanged.
+pub fn wireEffort(model: []const u8, requested: []const u8) []const u8 {
+ if (std.mem.eql(u8, requested, "medium") and omitsDefaultFlashEffort(model)) return "low";
+ if (std.mem.eql(u8, requested, "ultra")) return "max";
+ return requested;
+}
+
test "lookup prompts route low, mutation prompts and bare commands do not" {
// The three benchmark tasks are the pinned shape: two route low, and the
// one that REGRESSED at low effort (permgate) must not route.
@@ -64,3 +82,17 @@ test "lookup prompts route low, mutation prompts and bare commands do not" {
try std.testing.expect(!shouldRouteLookupLow(false, true, q));
try std.testing.expect(!shouldRouteLookupLow(true, true, q));
}
+
+test "flash and Gemini map default medium to low; grok and glm-5.3 stay medium" {
+ try std.testing.expect(omitsDefaultFlashEffort("glm-5.3-flash"));
+ try std.testing.expect(omitsDefaultFlashEffort("gemini-3.7-flash"));
+ try std.testing.expect(omitsDefaultFlashEffort("gemini-3.7-pro"));
+ try std.testing.expect(omitsDefaultFlashEffort("deepseek-v4-flash"));
+ try std.testing.expect(!omitsDefaultFlashEffort("grok-4.6"));
+ try std.testing.expect(!omitsDefaultFlashEffort("glm-5.3"));
+ try std.testing.expect(!omitsDefaultFlashEffort("deepseek-v4-pro"));
+ try std.testing.expectEqualStrings("low", wireEffort("glm-5.3-flash", "medium"));
+ try std.testing.expectEqualStrings("high", wireEffort("glm-5.3-flash", "high"));
+ try std.testing.expectEqualStrings("medium", wireEffort("grok-4.6", "medium"));
+ try std.testing.expectEqualStrings("max", wireEffort("grok-4.6", "ultra"));
+}
diff --git a/src/http.zig b/src/http.zig
index 2aa9ac13..2d513f03 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -191,9 +191,10 @@ fn post(gpa: Allocator, client: *std.http.Client, provider: Provider, body: []co
/// Hard ceiling on one non-streaming request. The legitimate worst case
/// observed (a 160k-token subagent context) completed in ~134s; anything
/// past this is a hung response, not a slow one. Tunable via
-/// GRAFF_POST_DEADLINE_SECS (#544): non-streaming callers (one-shots,
-/// subagents) have no inter-chunk stall signal, so this deadline is their
-/// ONLY hang detector — a harness with a tighter task timeout lowers it.
+/// GRAFF_POST_DEADLINE_SECS (#544): non-streaming callers (subagents)
+/// have no inter-chunk stall signal, so this deadline is their only hang
+/// detector — a harness with a tighter task timeout lowers it. Root `-p`
+/// streams (ADR 0046); this ceiling is not that path's hang detector.
pub var post_deadline_ms: u64 = 5 * 60 * 1000;
pub const WatchdogFired = enum { esc, deadline };
diff --git a/src/no_local_tools.zig b/src/no_local_tools.zig
index 897cea62..88e1e835 100644
--- a/src/no_local_tools.zig
+++ b/src/no_local_tools.zig
@@ -122,13 +122,32 @@ pub fn filterLeanSpecs(comptime Spec: type, arena: Allocator, specs: []const Spe
/// (evals) can act locally; the old lie forced extra child turns.
pub const lean_subagent_desc = "Spawn a sidecar for a self-contained task. Child has bash, read_file, edit_file, write_file, codedb; it does NOT share your context — the prompt must say everything. Returns the final report. Keep the critical-path next step local; do not hand off the blocker and wait. Do file and command work yourself this turn; spawn only for independent work.";
+/// One-shot tool prose (ADR 0046). Same names and JSON schemas; shorter
+/// descriptions so flash models start and finish instead of rereading
+/// essays. Do not drop to four tools (ADR 0024).
+pub const lean_bash_desc = "Run /bin/sh -c in cwd. Returns stdout, stderr, exit. Foreground still running after 120s (or timeout ms) backgrounds — bash_output(wait_ms>0) waits; do not poll.";
+pub const lean_read_file_desc = "Read a UTF-8 file. Call before editing. contains=exact-key numbered lines; start_line/end_line for a window.";
+pub const lean_edit_file_desc = "Replace exact text. Prefer one batched edits[] over many calls. Prefer over write_file for an existing file.";
+pub const lean_codedb_desc = "Indexed nav: context · around · callpath A B · list_dir · status. Prefer over bash grep/find/ls.";
+pub const lean_attempt_completion_desc = "Task is done. Put the final answer in result.";
+
+fn leanCompactDesc(name: []const u8) ?[]const u8 {
+ if (std.mem.eql(u8, name, "subagent")) return lean_subagent_desc;
+ if (std.mem.eql(u8, name, "bash")) return lean_bash_desc;
+ if (std.mem.eql(u8, name, "read_file")) return lean_read_file_desc;
+ if (std.mem.eql(u8, name, "edit_file")) return lean_edit_file_desc;
+ if (std.mem.eql(u8, name, "codedb")) return lean_codedb_desc;
+ if (std.mem.eql(u8, name, "attempt_completion")) return lean_attempt_completion_desc;
+ return null;
+}
+
/// schema.zig chains this after filterLeanSpecs: identical names and JSON
/// schemas, compact prose. Returns the input untouched when lean is off.
pub fn compactLeanSpecs(comptime Spec: type, arena: Allocator, specs: []const Spec) ![]const Spec {
if (!lean) return specs;
const buf = try arena.dupe(Spec, specs);
for (buf) |*spec| {
- if (std.mem.eql(u8, spec.name, "subagent")) spec.desc = lean_subagent_desc;
+ if (leanCompactDesc(spec.name)) |desc| spec.desc = desc;
}
return buf;
}
@@ -234,16 +253,20 @@ test "lean: the filter keeps exactly the seven one-shot tools, and allocates not
try std.testing.expect(!leanKeeps("workflow"));
try std.testing.expect(!leanKeeps("todo_write"));
try std.testing.expect(!leanKeeps("webfetch"));
- // …and the compact pass shrinks subagent's prose, leaves its name/schema
- // and every other tool alone, and is a no-op when lean is off.
+ // …and the compact pass shrinks one-shot prose, leaves names/schemas,
+ // and is a no-op when lean is off.
lean = false;
try std.testing.expectEqual(specs.len, (try compactLeanSpecs(Spec, arena, &specs)).len);
lean = true;
const compact = try compactLeanSpecs(Spec, arena, &specs);
try std.testing.expectEqual(specs.len, compact.len);
for (compact) |s| {
- if (std.mem.eql(u8, s.name, "subagent")) try std.testing.expectEqualStrings(lean_subagent_desc, s.desc) else try std.testing.expectEqualStrings("", s.desc);
+ if (std.mem.eql(u8, s.name, "subagent")) try std.testing.expectEqualStrings(lean_subagent_desc, s.desc);
+ if (std.mem.eql(u8, s.name, "bash")) try std.testing.expectEqualStrings(lean_bash_desc, s.desc);
+ if (leanCompactDesc(s.name) == null) try std.testing.expectEqualStrings("", s.desc);
}
+ try std.testing.expect(lean_bash_desc.len < 200);
+ try std.testing.expect(lean_attempt_completion_desc.len < 80);
try std.testing.expect(std.mem.indexOf(u8, lean_subagent_desc, "sidecar") != null);
try std.testing.expect(std.mem.indexOf(u8, lean_subagent_desc, "critical-path") != null);
try std.testing.expect(std.mem.indexOf(u8, lean_subagent_desc, "approval-denied") == null); // that fact lives on unattended_note, not here
diff --git a/src/pricing.zig b/src/pricing.zig
index 2760bc66..af22766d 100644
--- a/src/pricing.zig
+++ b/src/pricing.zig
@@ -276,6 +276,7 @@ pub const model_table = [_]ModelInfo{
.{ .provider = "codegraff", .name = "grok-4.6", .context = 500_000 },
.{ .provider = "codegraff", .name = "grok-build", .context = 256_000 },
.{ .provider = "codegraff", .name = "glm-5.2", .context = 204_800 },
+ .{ .provider = "codegraff", .name = "glm-5.3-flash", .context = 202_752 },
.{ .provider = "codegraff", .name = "mimo-v2.5", .context = 128_000 },
.{ .provider = "codegraff", .name = "mimo-v2.5-pro", .context = 128_000 },
// Kimi Code offline fallback. Authenticated startup replaces this slice
diff --git a/src/provider_codegraff.zig b/src/provider_codegraff.zig
index c511e746..ecf6e132 100644
--- a/src/provider_codegraff.zig
+++ b/src/provider_codegraff.zig
@@ -20,7 +20,7 @@ test "Codegraff Responses allowlist matches the public gateway contract" {
for ([_][]const u8{ "gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "grok-4.6" }) |model|
try std.testing.expect(usesResponses("codegraff", model));
- for ([_][]const u8{ "gpt-5.5", "grok-4.3", "claude-opus-4.8", "gemini-3.7-flash" }) |model|
+ for ([_][]const u8{ "gpt-5.5", "grok-4.3", "claude-opus-4.8", "gemini-3.7-flash", "glm-5.3-flash" }) |model|
try std.testing.expect(!usesResponses("codegraff", model));
try std.testing.expect(!usesResponses("openai", "gpt-5.6"));
}
diff --git a/src/provider_codegraff_tests.zig b/src/provider_codegraff_tests.zig
index 27d1fba9..103fb16a 100644
--- a/src/provider_codegraff_tests.zig
+++ b/src/provider_codegraff_tests.zig
@@ -85,7 +85,7 @@ test "Codegraff Responses aliases pin a sticky prompt_cache_key" {
}
}
-test "Codegraff Gemini omits default reasoning_effort; /effort high still sends" {
+test "Codegraff Gemini sends low for default effort; /effort high still sends" {
var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
@@ -109,10 +109,10 @@ test "Codegraff Gemini omits default reasoning_effort; /effort high still sends"
.sys_normal = "system",
};
try std.testing.expect(agent.effortApplies());
- try std.testing.expect(!agent.sendReasoningEffort());
- const omitted = try agent.buildBody(null, false, true, true);
- defer std.testing.allocator.free(omitted);
- try std.testing.expect(std.mem.indexOf(u8, omitted, "reasoning_effort") == null);
+ try std.testing.expect(agent.sendReasoningEffort());
+ const low = try agent.buildBody(null, false, true, true);
+ defer std.testing.allocator.free(low);
+ try std.testing.expect(std.mem.indexOf(u8, low, "\"reasoning_effort\":\"low\"") != null);
agent.reasoning = .high;
try std.testing.expect(agent.sendReasoningEffort());
@@ -128,3 +128,39 @@ test "Codegraff Gemini omits default reasoning_effort; /effort high still sends"
defer std.testing.allocator.free(ds);
try std.testing.expect(std.mem.indexOf(u8, ds, "\"reasoning_effort\":\"medium\"") != null);
}
+
+test "Codegraff glm-5.3-flash sends low for default effort; /effort high still sends" {
+ var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena_state.deinit();
+ const arena = arena_state.allocator();
+ var messages = std.json.Array.init(arena);
+ try messages.append(.{ .object = blk: {
+ var obj: std.json.ObjectMap = .empty;
+ try obj.put(arena, "role", .{ .string = "user" });
+ try obj.put(arena, "content", .{ .string = "hello" });
+ break :blk obj;
+ } });
+ var agent: Agent = .{
+ .gpa = std.testing.allocator,
+ .arena = arena,
+ .io = std.testing.io,
+ .client = undefined,
+ .provider = build("glm-5.3-flash"),
+ .messages = messages,
+ .sub = false,
+ .label = "main",
+ .out = null,
+ .sys_normal = "system",
+ };
+ try std.testing.expect(agent.effortApplies());
+ try std.testing.expect(agent.sendReasoningEffort());
+ const low = try agent.buildBody(null, false, true, true);
+ defer std.testing.allocator.free(low);
+ try std.testing.expect(std.mem.indexOf(u8, low, "\"reasoning_effort\":\"low\"") != null);
+
+ agent.reasoning = .high;
+ try std.testing.expect(agent.sendReasoningEffort());
+ const high = try agent.buildBody(null, false, true, true);
+ defer std.testing.allocator.free(high);
+ try std.testing.expect(std.mem.indexOf(u8, high, "\"reasoning_effort\":\"high\"") != null);
+}
diff --git a/src/session_run.zig b/src/session_run.zig
index acbf4f3b..0ff4c2df 100644
--- a/src/session_run.zig
+++ b/src/session_run.zig
@@ -115,7 +115,7 @@ pub fn runReplCommand(gpa: Allocator, io: Io, environ_map: anytype, root: *agent
/// One-shot print mode (`-p`/bare positional prompt): run the single prompt
/// to completion, print the final text to stdout, exit. Tool progress goes
-/// to stderr (say() with no out writer), streaming stays quiet, and the gate
+/// to stderr (say() with no out writer), paint stays quiet, and the gate
/// denies anything not pre-approved instead of prompting (there's no one to
/// ask). Moved out of main() verbatim (600-line goal); `root`/`tracer` are
/// already stable main()-owned storage by the time this runs.
@@ -125,6 +125,9 @@ pub fn runOneshotPrompt(gpa: Allocator, io: Io, arena: Allocator, root: *agent_m
root.in = null; // gate: deny instead of prompt; ask_user: self-decide
root.out = null; // tool progress → stderr; stdout carries only the answer
root.stream_quiet = true;
+ // stderr only: same class of "process is live" as Pi's JSONL session line.
+ // Eval first_out used to wait for the call-2 stdout pulse (ADR 0046).
+ std.debug.print("calling {s}\n", .{root.provider.model});
const ultracode_msg = try shapes.applyUltracodeSteering(arena, prompt_text, prompt_text, prompts.ultracodeActive(root));
if (ultracode_msg.explicit) {
tracer.note("ultracode", prompt_text[0..@min(prompt_text.len, 120)]);
diff --git a/src/turn_chrome.zig b/src/turn_chrome.zig
index db90078d..d8fa0412 100644
--- a/src/turn_chrome.zig
+++ b/src/turn_chrome.zig
@@ -39,11 +39,16 @@ pub fn emitRetryNotice(io: Io, class: []const u8, attempt: usize, max_attempts:
/// Count this inner-loop request, pulse from call 2, optionally pause if a
/// non-zero cap is set. Returns pause text or null to proceed. Never pauses
-/// when the cap is 0.
+/// when the cap is 0. `-p` / unattended skip the pulse: it was landing on
+/// stdout and eval `first_out` was "time to model call 2", not boot (ADR 0046).
+pub fn shouldPulseTurn(unattended: bool, json_mode: bool, sub: bool, call_n: u64) bool {
+ return call_n >= 2 and !sub and !json_mode and !unattended;
+}
+
pub fn beforeRequest(self: *Agent) !?[]const u8 {
self.model_calls_this_turn += 1;
const cap = max_turn_model_calls;
- if (self.model_calls_this_turn >= 2 and !self.sub and !main_mod.json_mode) {
+ if (shouldPulseTurn(main_mod.unattended, main_mod.json_mode, self.sub, self.model_calls_this_turn)) {
var buf: [120]u8 = undefined;
const text = formatTurnPulse(&buf, self.model_calls_this_turn, cap, self.tool_calls_this_turn);
if (text.len > 0) tool_pulse.emitNotice(self.io, "{s}", .{text});
@@ -78,3 +83,11 @@ test "formatTurnPulse omits a denominator when the cap is unlimited" {
try std.testing.expectEqualStrings("· turn still going · model call 3 · 11 tools", formatTurnPulse(&buf, 3, 0, 11));
try std.testing.expectEqualStrings("· turn still going · model call 3/64 · 11 tools", formatTurnPulse(&buf, 3, 64, 11));
}
+
+test "one-shot and --json do not pulse turn chrome onto stdout" {
+ try std.testing.expect(shouldPulseTurn(false, false, false, 2));
+ try std.testing.expect(!shouldPulseTurn(true, false, false, 2));
+ try std.testing.expect(!shouldPulseTurn(false, true, false, 2));
+ try std.testing.expect(!shouldPulseTurn(false, false, true, 2));
+ try std.testing.expect(!shouldPulseTurn(false, false, false, 1));
+}