<!-- task=m-iop-owned-single-request-agent-execution/12+05,06,08,11_internal_tool_loop plan=0 tag=API milestone-task=tool-loop -->

# Coordinator-owned Internal Workspace Tool Loop

## For the Implementing Agent

Do not start until packets 05, 06, 08, and 11 each have `complete.log`. Implement the coordinator/tool continuation exactly within the listed boundary, run all verification, fill `CODE_REVIEW-cloud-G10.md`, and leave finalization to official review. Do not reuse caller continuation or activate an unplanned production stage driver.

## Background

The coordinator recognizes an `internal_tool` detour and the Node can execute tools, but no owner validates model tool calls, sends them over the admitted wire, and returns results to the same internal execution without exposing `tool_use`. This packet closes that loop as an injectable service capability; plan/work/review provider drivers remain their own Epic.

## Analysis

### Files Read

- `AGENTS.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-owned-single-request-agent-execution.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`
- `agent-task/m-iop-owned-single-request-agent-execution/03+02_single_request_coordinator/PLAN-local-G07.md`
- `agent-task/m-iop-owned-single-request-agent-execution/05+03_single_ingress/PLAN-cloud-G09.md`
- `agent-task/m-iop-owned-single-request-agent-execution/06+05_stream_terminal/PLAN-cloud-G09.md`
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/openai/server.go`
- `apps/edge/internal/openai/anthropic_handler.go`
- `apps/edge/internal/openai/anthropic_types.go`
- `apps/edge/internal/openai/workspace_tool_binding.go`
- `apps/edge/internal/openai/workspace_tool_codec.go`
- `agent-contract/outer/anthropic-compatible-api.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-spec/runtime/edge-node-execution.md`

### SDD Criteria

- S06 requires multiple internal model tool call/result round trips, zero Claude-facing `tool_use` terminal, and no second HTTP request.
- `internal_tool` resumes only the saved active stage. Stage/request iteration, output, and deadline limits are immutable and exhaustion fails closed.
- D04/D10 keep provider reasoning/tool protocol private; public progress/final output is owned by packets 05/06.

### Verification Context

- Packet 03 defines coordinator envelopes and terminal ownership; packet 05 proves one real marked POST; packet 06 owns the shared outer-contract/input-spec write boundary; packet 08 supplies immutable workspace admission; packet 11 supplies all canonical Node operations.
- These APIs do not exist at starting HEAD, so dependency completion and exact post-implementation interfaces are mandatory preflight.
- Service race tests plus packet 05's real HTTP test are the deterministic oracle; no real provider or Mac runner is needed.

### State and Concurrency Findings

- Tool calls are ordered workspace effects; execute sequentially unless a later SDD explicitly adds parallel semantics.
- A tool result must correlate request, stage, tool id, workspace ref, Node generation, and exactly one pending call.
- Duplicate/malformed/unknown/capability-denied calls, repeated ids, stale result, budget exhaustion, and cancel fail the request without an external continuation.

### Test Coverage Gaps

- The current caller-owned workspace codecs translate public tool schemas and must not be reused as ownership.
- No service capability consumes a canonical internal call and supplies its result back to the same executor handle.

### Symbol References

- Extend packet 03's executor/handle via a separate optional continuation interface so existing fakes remain compatible.
- Packet 05's separate `singleRequestService` branch remains the only HTTP branch; do not widen `runService`.
- Existing route-01 `workspace_tool_*` code stays unchanged and caller-owned.

### Split Judgment

- The decode/correlate/wire/result/resume invariant is atomic and independently PASS-capable with fake executor plus net-pipe Node.
- Packet 06 is an ordering-only predecessor: it must complete before this packet updates the shared Anthropic contract and input spec, but its streaming implementation is not consumed by this non-stream tool-loop evidence.
- Provider-specific plan/work/review prompts and repair policy are excluded and consume this port later.

### Scope Rationale

- Include canonical internal schemas, strict decoding, ordered loop, workspace open, continuation correlation, budget/cancel, service and HTTP evidence, contract/spec.
- Exclude provider drivers, stage prompts, cleanup effects, streaming progress, observation metrics, and real Claude smoke.

### Final Routing

- `evaluation_mode=first-pass`; build closures true, scores 2/2/2/1/2 = G09.
- Finalizer route `grade-boundary`, lane `cloud`, filename `PLAN-cloud-G09.md`; all five loop-risk signatures are positive.
- Review scores 2/2/2/2/2 = G10; official filename `CODE_REVIEW-cloud-G10.md`; no capability/recovery gap.

## Dependencies and Execution Order

1. Require packet 05 for the marked HTTP branch/evidence.
2. Require packet 06 to serialize the shared Anthropic contract and input-spec write set.
3. Require packet 08 for immutable workspace identity/capabilities.
4. Require packet 11 for complete file/command/cancel execution.
5. Define schemas and continuation interface, implement the loop, then extend real-POST evidence/docs.

## Implementation Checklist

- [ ] Define closed canonical internal workspace calls/results and strict per-operation decoding independent of caller-facing tool codecs.
- [ ] Execute ordered calls through the admitted generation, correlate exactly one pending call/result, resume only the saved stage, and enforce immutable iteration/output/deadline budgets.
- [ ] Propagate cancellation and every malformed/stale/denied/exhausted outcome internally with no external continuation or fallback/reselection.
- [ ] Prove a real marked Anthropic POST performs multiple Node round trips yet emits no public tool protocol or second ingress, then synchronize contracts/specs.
- [ ] Run all dependency, focused race, endpoint, package, vet, documentation, and whitespace verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

## Implementation Plan

### [API-1] Define canonical internal tool continuation

**Problem**

- Packet 03 plans `internal_tool` as a state detour but its executor port has no concrete Node tool result continuation.
- `apps/edge/internal/openai/workspace_tool_codec.go:1` belongs to caller-facing route-01 behavior and cannot own S06.

**Solution**

Add service-owned types, with no OpenAI/Anthropic import:

```go
type InternalWorkspaceToolCall struct {
	RequestID, StageID, ToolCallID, Name string
	Arguments json.RawMessage
}

type InternalWorkspaceToolResult struct {
	RequestID, StageID, ToolCallID string
	Status, ErrorCode string
	// bounded typed result fields
}

type SingleRequestToolContinuation interface {
	ContinueInternalTool(context.Context, InternalWorkspaceToolResult) error
}
```

Use exactly `workspace_read`, `workspace_list`, `workspace_write`, `workspace_delete`, and `workspace_command`. Decode each with `json.Decoder.DisallowUnknownFields`, reject trailing data/unknown fields/empty identity/invalid combinations, and map to packet 09 typed inputs. Command accepts only command id and allowlisted env values, never executable/argv.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/service/single_request_tool_types.go` — canonical calls/results, strict decoders, clone/redaction helpers.
- [ ] `apps/edge/internal/service/single_request_tool_types_test.go` — valid operation table and malformed/unknown/trailing/identity/capability cases.

**Test Strategy**

- Table-test each schema, malicious path/command shapes, duplicate ids, raw sentinel redaction, and deep-copy behavior.

**Verification**

- `go test ./apps/edge/internal/service -run 'TestInternalWorkspaceTool(Call|Decode)' -count=1`
- Expected: only the closed canonical schema reaches wire DTOs and failures expose no raw arguments.

### [API-2] Execute and resume the saved stage internally

**Problem**

- Packet 03's planned `StartSingleRequest` validates state but does not own workspace open/tool/result delivery.
- Packet 09's wire methods are not connected to coordinator envelopes.

**Solution**

Before (predecessor state contract, `03+02_single_request_coordinator/PLAN-local-G07.md:160`):

```go
// Typed internal envelopes carry request/stage identity.
// internal_tool returns only to its saved active stage.
```

After, add a request-local tool loop that opens the frozen workspace once on first call, validates identity/capability, sends one ordered tool at a time, converts the bounded typed response, and calls only the emitting execution's `ContinueInternalTool`. Track pending tool id and iteration/output/deadline budget under the coordinator lock/state discipline. Duplicate/stale continuation is rejected; context cancellation sends typed cancel. Tool envelopes never enter the surface progress/terminal channel.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/service/service.go` — configure/snapshot the optional internal tool executor without endpoint coupling.
- [ ] `apps/edge/internal/service/single_request.go` — intercept `internal_tool`, preserve saved stage, and resume through the optional continuation interface.
- [ ] `apps/edge/internal/service/single_request_tool_loop.go` — own open/call/result correlation, budgets, cancellation, and generation-fenced wire use.
- [ ] `apps/edge/internal/service/single_request_tool_loop_test.go` — fake executor plus net-pipe Node for multi-tool, identity, denial, malformed, stale, budget, cancel, and one terminal races.

**Test Strategy**

- Drive read→write→command calls from one fake execution, assert ordered Node requests/results, saved-stage resume, and only one sanitized service terminal.

**Verification**

- `go test -race ./apps/edge/internal/service -run 'TestSingleRequestInternalToolLoop' -count=1`
- Expected: multiple correlated tools complete internally; all invalid/racing cases fail closed without exposed tool events.

### [API-3] Prove no external continuation at the HTTP boundary

**Problem**

- Packet 05's planned real-POST test proves one ingress with a fake multi-stage executor, but not an actual internal Node tool round trip.

**Solution**

Extend the completed packet 05 fixture with packet 12's service tool loop and a net-pipe workspace handler. Send one real marked POST, make the fake executor emit at least two internal tool calls and accept results, then assert ingress counter delta `+1`, one terminal, no `tool_use`/`tool_result`/private sentinel, and no additional HTTP request. Do not change handler production code unless required by a verified integration defect; any such need is outside this write boundary and must be recorded as a blocker for official review.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/openai/single_request_handler_test.go` — add real-POST multi-tool/zero-public-continuation integration evidence.
- [ ] `agent-contract/outer/anthropic-compatible-api.md` — mark internal tool continuation implemented and private.
- [ ] `agent-spec/input/openai-compatible-surface.md` — link the real-POST multi-tool evidence.
- [ ] `agent-spec/runtime/edge-node-execution.md` — document the service loop, identity/budget/cancel behavior, and provider-driver deferral.

**Test Strategy**

- Add `TestAnthropicSingleRequestInternalToolsStayPrivate`; docs use this and API-2 tests as executable oracle.

**Verification**

- `go test ./apps/edge/internal/openai -run 'TestAnthropicSingleRequest(UsesOnePost|InternalToolsStayPrivate)' -count=1`
- `rg --sort path -n 'internal tool|tool_use|second|workspace|defer' agent-contract/outer/anthropic-compatible-api.md agent-spec/input/openai-compatible-surface.md agent-spec/runtime/edge-node-execution.md`
- Expected: one real POST performs multiple internal wire calls and public output contains no continuation protocol.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/edge/internal/service/single_request_tool_types.go` | API-1 |
| `apps/edge/internal/service/single_request_tool_types_test.go` | API-1 |
| `apps/edge/internal/service/service.go` | API-2 |
| `apps/edge/internal/service/single_request.go` | API-2 |
| `apps/edge/internal/service/single_request_tool_loop.go` | API-2 |
| `apps/edge/internal/service/single_request_tool_loop_test.go` | API-2 |
| `apps/edge/internal/openai/single_request_handler_test.go` | API-3 |
| `agent-contract/outer/anthropic-compatible-api.md` | API-3 |
| `agent-spec/input/openai-compatible-surface.md` | API-3 |
| `agent-spec/runtime/edge-node-execution.md` | API-3 |
| `agent-task/m-iop-owned-single-request-agent-execution/12+05,06,08,11_internal_tool_loop/CODE_REVIEW-cloud-G10.md` | API-1, API-2, API-3 |

## Final Verification

1. `test -f agent-task/m-iop-owned-single-request-agent-execution/05+03_single_ingress/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/05+03_single_ingress/complete.log' | wc -l)" -eq 1`
2. `test -f agent-task/m-iop-owned-single-request-agent-execution/06+05_stream_terminal/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/06+05_stream_terminal/complete.log' | wc -l)" -eq 1`
3. `test -f agent-task/m-iop-owned-single-request-agent-execution/08+03,07_workspace_admission/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/08+03,07_workspace_admission/complete.log' | wc -l)" -eq 1`
4. `test -f agent-task/m-iop-owned-single-request-agent-execution/11+10_workspace_command/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/11+10_workspace_command/complete.log' | wc -l)" -eq 1`
5. `go test -race ./apps/edge/internal/service -run 'Test(InternalWorkspaceTool|SingleRequestInternalToolLoop)' -count=1`
6. `go test ./apps/edge/internal/openai -run 'TestAnthropicSingleRequest(UsesOnePost|InternalToolsStayPrivate)' -count=1`
7. `go test ./apps/edge/internal/service ./apps/edge/internal/openai -count=1`
8. `go vet ./apps/edge/internal/service ./apps/edge/internal/openai`
9. `rg --sort path -n 'internal tool|tool_use|second|workspace|defer' agent-contract/outer/anthropic-compatible-api.md agent-spec/input/openai-compatible-surface.md agent-spec/runtime/edge-node-execution.md`
10. `git diff --check`

Expected: all four predecessors are uniquely complete; shared documentation writes are serialized after packet 06; multi-tool flow stays internal and ordered under race; one real POST yields one private-free terminal; package checks pass. Cached tests are not acceptable.

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