feat(config): narrate_tool_calls knob for CLI agent transcript - #210
Conversation
Users can now calibrate how much the CLI agent (cc/codex/agy/opencode)
prefaces each tool call in the transcript. Prior behavior often
collapsed into `🔧 Bash 🔧 Bash 🔧 Bash` with only end-summary prose,
forcing the user to open the diff to see what happened.
New string field on Config:
narrate_tool_calls → "off" | "brief" (default) | "full"
- off = no preface; chained tool-call icons with only end-summary prose
- brief = one-line preface quoting the command or snippet (default)
- full = preface + quoted key output line after each non-trivial call
Injected into the turn-1 system prompt via new narrationDirective() sibling
of the existing effortDirective / outputStyleDirective helpers, so all four
subprocess backends adhere consistently. OpenCode reads it from the *Config
already threaded through NewOpenCodeAgent; CC / Codex / Antigravity get a
new positional string param mirroring outputVerbose.
Setter registered under /set narrate_tool_calls off|brief|full with
common synonyms (none, silent, default, verbose, detailed) accepted.
QualityMax ReviewVerdict: COMMENT · Confidence: evidence-backed scan Files eligible: 14 · Files reviewed: 14 · Files with findings: 0 · Findings: 0 · Inline cards: 0 Priority findings
Review gates
Important files
Review lifecycleUse the inline cards to inspect evidence and suggested remediation. Re-run the QualityMax review after pushing a fix; unchanged cards are identified by their stable finding marker. Dismiss with a reason through the existing QualityMax/GitHub review feedback flow. 0 prior card(s) are stale/resolved on this head. Proof legend: VERIFIED independently judged patch · REPRODUCED verified finding · GROUNDED deterministic evidence · MODEL-ONLY model judgment. QualityMax project results are available in the configured project. Receipt · commit |
|
| Gate | Result |
|---|---|
| 🔍 AI diff review | ✅ Clean · gemini-3.1-flash-lite · completed · 14 eligible / 14 reviewed · gemini-3.1-flash-lite |
| 🔍 SAST | completed · 14 eligible / 14 reviewed · claude-haiku-4-5-20251001 |
| 🔍 Canonical PR review delivery | completed · 0 eligible / 0 reviewed · exact-head review #5326971206 and overview #5843683082 confirmed |
| 🧪 Repo Tests | ✅ 842/842 passed (go) |
Powered by QualityMax — AI-Powered Test Automation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Narration may expose sensitive output, off mode semantics are inconsistent, and backend prompt wiring lacks sufficient coverage.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Adds configurable off/brief/full tool-call narration across CLI agent backends, with config handling and tests.
Changes:
- Adds
narrate_tool_callsnormalization, aliases, and config commands. - Threads narration directives through CC, Codex, Agy, and OpenCode.
- Updates constructors and related tests.
| File | Summary |
|---|---|
main.go |
Passes narration configuration to agents. |
internal/repl/repl.go |
Threads configuration through backend switching. |
internal/api/config.go |
Defines and normalizes the setting. |
internal/agent/opencode_agent.go |
Applies narration configuration. |
internal/agent/mcp_reconnect_test.go |
Updates agent construction tests. |
internal/agent/conversation_test.go |
Updates backend construction tests. |
internal/agent/codex_agent.go |
Adds Codex narration directives. |
internal/agent/codex_agent_continuity_test.go |
Updates Codex tests. |
internal/agent/cc_agent.go |
Adds CC narration directives. |
internal/agent/cc_agent_session_test.go |
Tests narration configuration. |
internal/agent/agy_agent.go |
Adds Agy narration directives. |
internal/agent/agy_agent_test.go |
Updates Agy tests. |
config_command.go |
Supports setting and displaying the setting. |
config_command_test.go |
Tests configuration handling. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Updated narration guidelines to include redaction of commands and key outputs. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…210) (#211) Copilot flagged HIGH-severity credential-exposure risk on PR #210: the `full` narration directive asked the subprocess agent to echo commands and output verbatim, which can carry API keys (argv `--token=`), bearer tokens (`Authorization:` headers), env-var values from a `printenv` call, connection strings, private keys, etc. Every mode (off/brief/full — including the "bogus"→brief fallback) now carries a shared REDACTION clause that: - names the secret classes to substitute (API keys, bearer/OAuth/session tokens, passwords, private keys, Authorization/Cookie header values, connection strings, --token=/--api-key=/--password=/--secret= flag values, AWS_*/GITHUB_TOKEN/ANTHROPIC_API_KEY/*_KEY/*_TOKEN/*_SECRET env-var values, webhook signing secrets), - specifies the substitution (`<REDACTED>`, not omission), - forbids quoting `env`/`printenv`/`railway variables` output or `.env*` file contents in the narration channel, - covers `off` mode too because "only narrate on failure" is exactly where secrets tend to appear (401 responses, curl -v dumps). Subprocess agents (Codex CLI, Antigravity, opencode-selected models) do not inherit qmax-code's own secret-handling rules from CLAUDE.md, so the guard is spelled out in the injected system prompt. TestNarrationDirectiveCarriesRedactionClause asserts each mode names the key secret classes by string — a future edit that thins the directive without replacing the guard fails loudly.

Summary
🔧 Bash 🔧 Bash 🔧 Bashchains with only end-summary prose, forcing users to open the diff to see what actually ran.Config
New field on `api.Config`:
```go
NarrateToolCalls string `json:"narrate_tool_calls,omitempty"` // "off" | "brief" (default) | "full"
```
Set via:
```bash
qmax-code config set narrate_tool_calls full
qmax-code config set narrate_tool_calls off
```
Synonyms accepted: `none`/`silent` → `off`, `default` → `brief`, `verbose`/`detailed` → `full`. Empty/unknown → `brief` on read.
Directive snippet
```go
// internal/agent/cc_agent.go
func narrationDirective(mode string) string {
switch mode {
case "off": return "\n\nTOOL NARRATION: OFF — Chain tool calls without prose ..."
case "full": return "\n\nTOOL NARRATION: FULL — Before each non-trivial tool call, ..."
default: return "\n\nTOOL NARRATION: BRIEF — Before each non-trivial tool call, ..."
}
}
```
Threading
Tests
```bash
go build -o qmax-code .
go test ./... # all packages ok (agent 6.878s, api cached, main 10.9s)
```
Test plan
Notes