# p0151a: wire LoopTraceCollector via decorators, enable RunCommand in Plan phase, prepend SourceAnchoringPreamble

- [Architecture] Decorator pattern for trace wiring instead of changing `RetryCoordinator`'s callback signature. `TracingChatClient` wraps the `IChatClient` and records each `GetResponseAsync` (model + tokens + duration) on the active `LoopTraceCollector`. `TracingAIFunction` wraps each `AIFunction` and records every tool invocation. `SkillCallRuntime` composes both wrappers per-call before constructing `ChatOptions`. No changes to the public `RetryCoordinator.InvokeAsync` signature — existing callers (and tests) keep working untouched.
- [Architecture] `LoopTraceCollector.ReadSet` is a property on the existing collector, not a separate service. It is populated only by `TracingAIFunction` when the wrapped function's name is `read_file` — the only tool whose first-class output is a source citation. Other tool calls (grep, list_files, run_command) record on the trace via `AppendToolCall` but do not contribute to ReadSet. p0151b consumes this set as the gate for `AnalyzedFromSource` observations.
- [Architecture] No new `BashToolHost` class. `FilesystemToolHost.RunCommand` (shipped in p0145) is unchanged; the only delta is the per-phase filter — `SkillExecutionPhase.Plan` now resolves to `InvestigatorSet()` instead of `ReadOnlySet()` so the existing tool surfaces in Plan-phase recon rounds. A new host would have duplicated the `_runner.RunAsync` plumbing in `SandboxStepRunner` and required its own DI registration — both unnecessary given the existing sandbox boundary.
- [Architecture] `SourceAnchoringPreamble` is a trivial transient service that produces a fixed paragraph. Injected into `PromptComposer` and prepended to the final `(SystemPrompt, UserPrefix, UserSuffix)` triple in both `ComposeDiscussion` and `ComposeStructured`. One place to update the cross-cutting source-anchoring rule; no per-skill prompt boilerplate — see the umbrella's "no new SKILL.md format" decision.
- [Implementation] `TracingChatClient.Dispose` delegates to the wrapped client. `SkillCallRuntime` constructs the wrapper inside `TryInvokeAsync` (per-skill-call scope), so the wrapper's lifetime matches the call's `LoopTraceCollector`. No DI registration for either tracing wrapper — they are local composition, not services.
- [Implementation] `TracingAIFunction.InvokeCoreAsync` calls `inner.InvokeAsync` (the public entry) rather than `inner.InvokeCoreAsync` (protected, inaccessible to a sibling type). The argument serialization uses `JsonSerializer.Serialize(arguments, JsonSerializerOptions)` and falls back to `"{}"` on failure — the args summary is for human readability, never load-bearing.
- [Implementation] The `ReadSet` is case-insensitive (`StringComparer.OrdinalIgnoreCase`) and de-duplicates on insert. Different-case spellings of the same path are folded together; downstream validators do not need to canonicalize before checking membership.
- [TradeOff] `TracingChatClient` records token counts from `ChatResponse.Usage` and falls back to zero when absent. This means a chat client that returns responses without usage info produces an entry with `0/0t` — visible but incomplete. Acceptable because production clients (OpenAI, Anthropic) always populate Usage; the zero-fallback only surfaces in test fixtures or misconfigured clients.
- [TradeOff] `TracingAIFunction.SerializeArgs` swallows any serialization exception and returns `"{}"`. The args summary is diagnostic, never load-bearing for the trace's primary signal (success/failure + duration). A serialization failure should not abort the tool call.
- [TradeOff] Existing tests asserted Plan-phase = ReadOnly set (no `RunCommand`). Four tests across `ToolKitTests`, `ToolKitCompositionTests`, `FilesystemToolHostTests`, and `StructuredRoundToolPolicyTests` were updated to reflect the new contract; method names renamed where appropriate to document the change. The `ApiSecurityPassiveMode_NoProbe` test was renamed to `ApiSecurityPassiveMode_KeepsToolsuiteIntact` — the "no probe" semantics live in the prompt-strategy layer (the `{"probe": ...}` JSON-block contract is suppressed in passive mode), not in the tool layer. Tool surface is uniform regardless of active/passive.
