<!-- task=m-iop-hot-path-one-shot-execution/14+13_anthropic_gate plan=3 tag=REVIEW_API milestone-task=terminal-control,anthropic-gate -->

# Preserve empty-input Anthropic tools in live Hot Path decoding

## For the Implementing Agent

Implement every checklist item, run the exact verification commands, fill the implementation-owned sections in `CODE_REVIEW-cloud-G05.md` with actual notes and output, keep the active files in place, and report ready for review. If blocked, record only the exact blocker, attempted commands/output, and resume condition in implementation-owned evidence fields. Do not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive logs, write `complete.log`, or start orchestration; finalization belongs to the code-review skill.

## Background

The repaired Anthropic Hot Path now binds provider identity, enforces provider-token budgets, and releases live SSE correctly, but review found one remaining native-tool loss. A valid streaming `tool_use` whose `content_block_start.input` is `{}` and which has no `input_json_delta` is recorded and then discarded, so direct/light classification and continuation can lose a zero-argument provider tool call. This follow-up preserves exactly one empty argument object at block close without duplicating fragmented non-empty arguments.

## Archive Evidence Snapshot

- `plan_cloud_G09_2.log` contains the completed identity, token-budget, and production live-stage repair whose decoder baseline this follow-up preserves.
- `code_review_cloud_G10_2.log` records `FAIL` with one Required finding in `hot_path_stage_stream.go`: an Anthropic `tool_use` with `input: {}` and no argument delta produces no normalized tool fragment. Reviewer reruns of the targeted race suite, common race suite, formatting check, and `git diff --check` passed; a focused decoder reproducer failed with `empty-input tool_use was dropped: events=[]`.
- The follow-up changes only empty-input tool preservation and its handler-level regression; evidence integrity is trusted.

## Analysis

### Files Read

- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-hot-path-one-shot-execution/SDD.md`
- `agent-contract/outer/anthropic-compatible-api.md`
- `agent-spec/input/openai-compatible-surface.md`
- `apps/edge/internal/openai/anthropic_handler.go`
- `apps/edge/internal/openai/anthropic_stream.go`
- `apps/edge/internal/openai/artifact_pair.go`
- `apps/edge/internal/openai/hot_path_direct.go`
- `apps/edge/internal/openai/hot_path_dispatch.go`
- `apps/edge/internal/openai/hot_path_light.go`
- `apps/edge/internal/openai/hot_path_stage_stream.go`
- `apps/edge/internal/openai/hot_path_terminal_control.go`
- `apps/edge/internal/openai/normalized_sse.go`
- `apps/edge/internal/openai/stream_gate_tunnel_codec.go`
- `apps/edge/internal/openai/hot_path_anthropic_gate_test.go`
- `apps/edge/internal/openai/hot_path_terminal_control_test.go`
- `apps/edge/internal/openai/hot_path_light_test.go`

### SDD Criteria

- Approved SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-hot-path-one-shot-execution/SDD.md`; first-line milestone tasks: `terminal-control,anthropic-gate`.
- S10 requires stable block/tool identity, ordered normalized deltas, and exactly-once outer terminal behavior across stages. Its Evidence Map row requires cross-stage tool-id remap and normalized delta-ordering tests.
- S11 requires native Anthropic `tool_use/tool_result` ordering and connected direct/light continuation. Its Evidence Map row requires fragmented Anthropic SSE/tool-use/error fixtures at handler integration level.
- These rows require the decoder to retain an empty tool argument object as one normalized fragment and require a native handler regression that observes the public tool block and stored continuation mapping without changing terminal ownership.

### Verification Context

- No neutral verification handoff was supplied. Repository-native rules, the approved SDD, the Anthropic outer contract, current source/tests, and prior-loop evidence were used.
- Local preflight was `/config/workspace/iop-s0` with `go version go1.26.2 linux/arm64`; the shared checkout is dirty, so unrelated changes must be preserved.
- Fresh reviewer commands passed: `go test -race -count=1 ./apps/edge/internal/openai -run 'TestHotPathAnthropic|TestHotPathOuterTurn|TestHotPathStage|TestAnthropic(ChatBridge|Native)'`, the common four-package race suite, `gofmt -d` over planned implementation files, and `git diff --check`.
- A temporary focused decoder test reproduced the gap and was removed after it failed with `empty-input tool_use was dropped: events=[]`. The permanent oracle is the handler-level test in this plan.
- No external runner, credential, provider smoke, generated artifact, or cached test output is required. The `-count=1` commands require fresh local execution. Confidence is high because the failure is isolated to one explicit protocol transition and has a deterministic fixture.

### Test Coverage Gaps

- Existing native Anthropic coverage preserves fragmented non-empty `input_json_delta` values after an initial `{}` placeholder.
- No existing test covers a tool block that starts with `input: {}`, receives no argument delta, closes normally, and must remain visible as one zero-argument tool call through the handler and continuation store.

### Symbol References

- None. No public or package-level symbol rename/removal is planned.

### Split Judgment

- Keep one compact plan. Decoder state and the handler regression prove one indivisible invariant: every closed Anthropic `tool_use` yields exactly one logical argument stream, including `{}` when no argument fragment arrived.

### Scope Rationale

- Modify only the Anthropic provider-stage decoder and the existing Anthropic gate test. Exclude the already-repaired response identity, token budgeting, live tunnel release, Chat codec, broader error/cancel matrix, actual-provider smoke owned by S16, contracts/spec documentation, roadmap state, and unrelated dirty-worktree changes.

### Final Routing

- `evaluation_mode=isolated-reassessment`; finalizer=`finalize-task-policy.sh pair`; status=`routed`; missing evidence and blocked reason are empty.
- Build closures: scope/context/verification/evidence/ownership/decision are all true; closure basis is the focused reproducer, current decoder/test paths, approved S10/S11 evidence map, and deterministic local race commands; no capability gap.
- Build scores=`1/1/1/0/1` (G04); base route basis=`local-fit`; route basis=`recovery-boundary`; lane=`cloud`; filename=`PLAN-cloud-G04.md`.
- Build signals: `large_indivisible_context=false`; risks=`temporal_state,boundary_contract,structured_interpretation` (3); `risk_boundary_matched=false`; `review_rework_count=2`; `evidence_integrity_failure=false`; `recovery_boundary_matched=true`.
- Review closures: scope/context/verification/evidence/ownership/decision are all true; closure basis is the exact modified-file pair plus handler and race verification; no capability gap.
- Review scores=`1/1/1/1/1` (G05); route basis=`official-review`; lane=`cloud`; filename=`CODE_REVIEW-cloud-G05.md`; adapter=`codex`; model=`gpt-5.6-sol`; reasoning effort=`xhigh`.

## Implementation Checklist

- [ ] [API-1] Preserve exactly one empty Anthropic tool argument object when a live `tool_use` block closes without an input fragment, and add its native handler regression.
- [ ] Run the targeted and common race suites plus `git diff --check` exactly as listed in Final Verification.
- [ ] Fill implementation-owned sections in `CODE_REVIEW-*-G??.md` with actual implementation notes and verification output.

### [API-1] Empty-input Anthropic tool preservation

**Problem:** `apps/edge/internal/openai/hot_path_stage_stream.go:938-952` stores the tool identity but returns no event for `input: {}` or `null`. `decodeFrame` at lines 868-900 ignores `content_block_stop`, while `decodeBlockDelta` only emits non-empty `input_json_delta`; therefore a valid zero-argument tool disappears from normalized output and never reaches selector classification or caller continuation.

**Solution:** Track, per Anthropic tool block, its identity and whether any argument fragment has been emitted. Mark the block when a non-empty initial input or `input_json_delta` is emitted. Decode `content_block_stop`; for a known tool block with no emitted argument, emit exactly one `ToolCallFragmentEvent` containing `{}`, then remove the block state. A fragmented non-empty tool must not receive an extra `{}` and a repeated/unknown stop must not duplicate a tool.

Before (`apps/edge/internal/openai/hot_path_stage_stream.go:769-780, 868-900, 938-952, 994-1008`):

```go
type anthropicMessagesStageDecoder struct {
	tools map[int]stageToolIdentity
}

case "content_block_delta":
	return d.decodeBlockDelta(data)

case "tool_use":
	d.tools[payload.Index] = stageToolIdentity{id: payload.Block.ID, name: payload.Block.Name}
	if len(payload.Block.Input) == 0 || string(payload.Block.Input) == "{}" || string(payload.Block.Input) == "null" {
		return nil, nil
	}

case "input_json_delta":
	identity := d.tools[payload.Index]
	return []streamgate.NormalizedEvent{ev}, nil
```

After:

```go
type anthropicStageTool struct {
	identity       stageToolIdentity
	inputEmitted   bool
}

type anthropicMessagesStageDecoder struct {
	tools map[int]anthropicStageTool
}

case "content_block_stop":
	return d.decodeBlockStop(data)

case "tool_use":
	d.tools[payload.Index] = anthropicStageTool{
		identity: stageToolIdentity{id: payload.Block.ID, name: payload.Block.Name},
	}
	// Emit and mark only a concrete non-empty initial input here.

case "input_json_delta":
	// Emit the partial JSON and mark this tool as having input.

func (d *anthropicMessagesStageDecoder) decodeBlockStop(data string) ([]streamgate.NormalizedEvent, error) {
	// Emit one "{}" fragment only for a known tool with no prior input, then delete its state.
}
```

**Modified Files and Checklist:**

- [ ] Modify `apps/edge/internal/openai/hot_path_stage_stream.go` to track argument emission, handle `content_block_stop`, emit one `{}` fallback, and delete closed tool state without duplicate fragments.
- [ ] Modify `apps/edge/internal/openai/hot_path_anthropic_gate_test.go` with `TestHotPathAnthropicDirectStreamPreservesEmptyToolInput` (or an equivalently focused handler-level test) using native `message_start -> content_block_start(tool_use,input:{}) -> content_block_stop -> message_delta(tool_use) -> message_stop` frames.
- [ ] Record actual implementation decisions, deviations, and command output in `agent-task/m-iop-hot-path-one-shot-execution/14+13_anthropic_gate/CODE_REVIEW-cloud-G05.md`.

**Test Strategy:** Add the regression in `apps/edge/internal/openai/hot_path_anthropic_gate_test.go`. Drive the real Hot Path handler with an Anthropic native streaming fixture that has no `input_json_delta`; assert one public `tool_use` block, exactly one public input delta whose `partial_json` is `{}`, stable public/provider tool mapping in the waiting continuation, one `message_delta` with `tool_use`, one `message_stop`, and no duplicate argument fragment. Existing fragmented non-empty fixtures must continue to prove that no fallback `{}` is prepended or appended.

**Verification:** Run `go test -race -count=1 ./apps/edge/internal/openai -run 'TestHotPathAnthropic|TestHotPathOuterTurn|TestHotPathStage|TestAnthropic(ChatBridge|Native)'`; it exits 0 and both empty and fragmented tool inputs are preserved exactly once.

## Modified Files Summary

| File | Item |
|---|---|
| `apps/edge/internal/openai/hot_path_stage_stream.go` | API-1 |
| `apps/edge/internal/openai/hot_path_anthropic_gate_test.go` | API-1 |
| `agent-task/m-iop-hot-path-one-shot-execution/14+13_anthropic_gate/CODE_REVIEW-cloud-G05.md` | API-1 evidence |

## Final Verification

```bash
go test -race -count=1 ./apps/edge/internal/openai -run 'TestHotPathAnthropic|TestHotPathOuterTurn|TestHotPathStage|TestAnthropic(ChatBridge|Native)'
go test -race -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai ./apps/edge/internal/service
git diff --check
```

Expected: all commands exit 0 with fresh (`-count=1`) test execution; a native empty-input tool remains exactly once through normalized decoding, public Anthropic SSE, and continuation mapping, while fragmented non-empty tools and single-terminal behavior remain unchanged.

After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
