# WAVE 98 DISPATCH — AD-543 / AD-544 / AD-545 / AD-546 / AD-547 / AD-548 / AD-549 v1 Native SWE Harness — Agentic Tool Loop (closes #13)

## Wave summary

**Umbrella:** Native SWE Harness — Agentic Tool Loop (`docs/development/roadmap.md:6650` *Native SWE Harness (AD-543–549)*). Replaces the single-shot `===FILE:===`/`===MODIFY:===` text-parser builder pattern with a multi-turn LLM ↔ tool-call loop that owns its own conversation, reads code mid-generation, runs tests as a tool, and feeds failure output back into the same continuous session. Single combined v1 wave; 7 ADs threaded together because the substrate (543/544/548) is the precondition for the loop (545/547/546/549) and shipping them separately would leave the substrate provably unused at HEAD.

**Wave kind:** Source-modifying combined v1 — additive layer on top of AD-423a/b/c (Tool Protocol + Permissions + ToolContext, all shipped) + AD-448 (Wrapped ToolExecutor with pre/post hooks + audit emission, shipped) + AD-521 (BuildPipeline service extracted, Wave 96) + AD-476 (Specialist subclasses + SpecialistRouter, Wave 97). Net new package: `src/probos/cognitive/swe_harness/` with seven modules. Existing builder text path is preserved byte-for-byte; native harness is a parallel route gated by a default-False flag (Phase α modify-only eligibility per AD-549).

**Reframe decision — ship combined v1 (no scope split):**

Captain rule "don't defer unless no choice" applies to the wave structure here. Three concrete checks against the AD-543–549 spec, each verified at HEAD `632398f`:

1. **Substrate is mostly already shipped.** AD-543's `ToolDefinition` Protocol is structurally equivalent to AD-423a's `Tool` Protocol at `src/probos/tools/protocol.py:83` (verified) — v1 reuses it via a JSON-schema adapter rather than parallel-defining a second protocol. AD-543's `ToolExecutor` Protocol is AD-448's `ToolExecutor` at `src/probos/tools/executor.py:40` (verified) — v1 reuses it directly. AD-548's pre/post hooks are AD-448's `add_pre_hook`/`add_post_hook` (verified) — v1 ships only the standing-orders blocked-paths factory on top. AD-548's audit trail is AD-448's `make_audit_hook` emitting `EventType.TOOL_INVOKED` at `src/probos/events.py:197` (verified) — v1 reuses it. The genuinely new substrate is AD-543's wire-format dataclasses (`ToolCallRequest`/`ToolCallResult`/`ContentBlock`/`TextBlock`/`ToolUseBlock`/`ToolResultBlock`) and the `LLMRequest`/`LLMResponse` extension for tool-aware completions — that's a single-file additive change.

2. **The tool suite (AD-544) is mechanical.** Each of the 12 tools wraps an existing capability — `FileReaderAgent.handle_intent` at `src/probos/agents/file_reader.py:45`, `FileWriterAgent.handle_intent` at `:41`, `ShellCommandAgent.handle_intent` at `:61`, `CodebaseIndex.query`/`find_callers`/`find_tests_for`/`get_imports`/`find_importers` at `src/probos/cognitive/codebase_index.py:158/339/368/397/406` — all verified live. Each adapter is <50 LOC and registers into the existing `ToolRegistry` shipped under AD-423b. No new protocol, no new lifecycle, no new dispatch surface.

3. **The harness (AD-545+546+547) needs a paired substrate to be testable.** Shipping AD-543 as its own wave would leave the wire-format dataclasses and the `LLMRequest.tools` extension provably unconsumed at HEAD — a Liskov / "is it real" review would flag the substrate as dead code until AD-545 lands. Shipping AD-545 without AD-543 is impossible. Shipping AD-546 without AD-545 is impossible. The wave structure aligns with the spec's own listed implementation order (`docs/development/roadmap.md:6868` — "AD-543 → AD-544 → AD-548 → AD-545 → AD-547 → AD-546 → AD-549. Permissions before the loop because the loop needs permission checks from day one.") which already groups them as one body of work.

**v1 IN scope (concrete, by AD):**

- **AD-543 v1 — Tool Execution Abstraction (substrate, 14-test plan).** New module `src/probos/cognitive/swe_harness/__init__.py` (package init exposing public surface) + `tool_call.py` (~200 lines) defining frozen dataclasses `ToolCallRequest`, `ToolCallResult`, `ContentBlock` union (`TextBlock`/`ToolUseBlock`/`ToolResultBlock`) and helper `tool_registration_to_llm_definition(reg: ToolRegistration) -> dict` adapting AD-423a `ToolRegistration` records to the Anthropic/OpenAI tool-definition JSON schema. Two additive fields on `LLMRequest` (`src/probos/types.py:227`): `tools: list[dict] | None = None` (None preserves byte-for-byte text-only behaviour) and `tool_choice: str = "auto"` (ignored unless `tools` is set). Two additive fields on `LLMResponse` (`src/probos/types.py:240`): `content_blocks: list[ContentBlock] = field(default_factory=list)` (populated only when tools were active) and `stop_reason: str = "stop"` (mirrors LLM-API `stop_reason`/`finish_reason`). `OpenAICompatibleClient._call_openai` extended to forward `tools`/`tool_choice` into the payload when present and to parse response `tool_calls` array (OpenAI shape via Copilot proxy translation) into a `content_blocks` list — backward-compat preserved for callers that pass no tools. `MockLLMClient` extended with a `script_content_blocks(blocks)` helper so tests can simulate scripted tool-use sequences without a real LLM.

- **AD-544 v1 — Native Tool Suite (16-test plan).** New module `src/probos/cognitive/swe_harness/tools.py` (~520 lines) with 12 `Tool`-Protocol-conforming adapters: `ReadFileTool`, `WriteFileTool`, `EditFileTool`, `ListFilesTool`, `RunCommandTool` (file/shell tools wrapping `FileReaderAgent`/`FileWriterAgent`/`ShellCommandAgent` via direct intent dispatch through the runtime's intent bus); `CodebaseQueryTool`, `CodebaseFindCallersTool`, `CodebaseFindTestsTool`, `CodebaseGetImportsTool`, `CodebaseReadSourceTool` (codebase tools wrapping `CodebaseIndex`); `StandingOrdersLookupTool` (wrapping the existing standing-orders manual reader at `cognitive/standing_orders.py`); `SystemSelfModelTool` (wrapping `runtime.system_self_model.snapshot()` if available, else compact runtime topology dict). Each adapter declares `tool_id`, `name`, `tool_type` (DETERMINISTIC_FUNCTION for codebase/standing-orders/self-model; UTILITY_AGENT for file/shell), `description`, `input_schema` (JSON Schema dict), `output_schema`, and an `async invoke(params, context)` returning `ToolResult`. Helper `register_native_swe_tools(registry: ToolRegistry, runtime: ProbOSRuntime) -> int` returns count registered with rank-keyed `default_permissions` aligned to AD-423b (`ensign`=READ for read-only tools / WRITE forbidden; `lieutenant`=WRITE for `write_file`/`edit_file`; `commander`=WRITE for `run_command`; `senior_officer`=FULL across the suite). Wired into a new finalize wirer `_wire_native_swe_tools` in `startup/finalize.py` adjacent to existing `_wire_specialty_pools` (W97 precedent), invoked unconditionally at boot — registration is cheap and the harness can test tool availability via the existing `ToolRegistry.list_for_agent()` surface.

- **AD-548 v1 reframed — Trust-Gated Permissions + Standing-Orders Blocked Paths (6-test plan, mostly verify-only).** Earned-Agency rank↔ToolPermission mapping documented in a new `cognitive/swe_harness/permissions.md` reference + verified-against-codebase pointer to AD-423b's `default_permissions` matrix at `src/probos/tools/protocol.py:139-150`. NEW: `cognitive/swe_harness/policies.py` (~80 lines) with factory `make_blocked_paths_hook(blocked_paths: list[str]) -> PreHook` — returns a callable matching `params.path` (and `params.command` cwd-substring for `run_command`) against blocked patterns; on match returns False to deny, which AD-448's `ToolExecutor` translates to `ToolResult(error="Blocked by standing-order policy: <pattern>")` and feeds back to the LLM as a tool-result so the loop can adapt. Pydantic config `NativeSWEHarnessConfig.blocked_paths: list[str] = Field(default_factory=lambda: ["src/probos/security/", ".env", "config/sealed_modules.yaml"])` (sensible defaults — security domain + env file + sealed-modules registry). Hook registered into the harness's `ToolExecutor` instance at finalize time when the list is non-empty. Audit trail confirmed via existing AD-448 `make_audit_hook` (no new EventType). **Out of v1**: full standing-orders YAML tool-policy schema with hot-reload, per-tool blocked_paths under `tools.<name>.blocked_paths`, and operator-tunable Tier-3 propagate-on-deny semantics — deferred to AD-548b with explicit forcing function (first production deny-policy that needs operator-side tunability without code change).

- **AD-545 v1 — Agentic Loop Engine (14-test plan).** New module `cognitive/swe_harness/agentic_loop.py` (~340 lines) with `AgenticLoop` class. Constructor accepts `llm_client: BaseLLMClient`, `tool_executor: ToolExecutor`, `max_iterations: int = 25`, `token_budget: int | None = None`, `event_emit_fn: Callable | None = None`. `async def run(system_prompt, user_message, tools, context) -> AgenticResult` returns dataclass `AgenticResult(final_text, tool_calls, iterations, total_tokens, stopped_reason)`. Loop body: assemble messages → LLM `complete()` with `tools=[...]` → parse `content_blocks` → for each `ToolUseBlock` execute via `ToolExecutor.invoke()` and collect `ToolCallResult` → if zero tool calls and final text present, return; else append assistant + tool-result messages, increment iteration, repeat. Exit conditions: text-only response (`stopped_reason="complete"`), `iterations >= max_iterations` (`stopped_reason="max_iterations"`), `total_tokens >= token_budget` (`stopped_reason="token_budget"`), unrecoverable error (`stopped_reason="error"` — log-and-degrade, never raise). Token retry uses existing `evolve_with_retry` from `cognitive/llm_client.py`. Three new EventType values appended to `events.py`: `AGENTIC_TOOL_CALL_STARTED`, `AGENTIC_TOOL_CALL_COMPLETED`, `AGENTIC_LOOP_ITERATION` — emitted via the injected `event_emit_fn` (fire-and-forget pattern matching `TRANSPORTER_DECOMPOSED` at `cognitive/builder.py:643`).

- **AD-547 v1 — Session Compaction (8-test plan).** New module `cognitive/swe_harness/session_compactor.py` (~130 lines) with `SessionCompactor` class + module-level `estimate_tokens(text: str) -> int` returning `max(1, len(text) // 4)` (v1 char-count approximation; tiktoken-exact tokenizer is AD-547b territory with explicit forcing function: first compaction false-trip where len/4 estimate diverges >25% from actual model context counting). `async def compact(messages, *, preserve_count=5, budget_tokens, fast_llm) -> list[dict]` summarises older messages via a fast-tier LLM call ("Summarize the key findings, decisions, and file changes from these tool interactions"), preserving system prompt + last `preserve_count` assistant+tool exchanges + the original user task description. Re-compaction supported via second pass when summary still exceeds threshold. Wired into `AgenticLoop` between iterations: when accumulated `total_tokens > COMPACTION_THRESHOLD` (configurable, default 80% of `LLMRequest.max_tokens` budget), invoke compactor before next LLM call. Compaction is transparent to loop logic — compacted messages replace originals in the message list.

- **AD-546 v1 — BuildPipeline Integration (10-test plan).** New module `cognitive/swe_harness/native_builder.py` (~220 lines) with `NativeBuilderHarness` class wrapping `AgenticLoop`. `async def run_build(spec: BuildSpec, work_dir: str) -> list[dict]` returns file_changes in the same format `BuilderAgent._parse_file_blocks()` produces (`path` / `mode` / `content` or `replacements`). System prompt assembly: composed Standing Orders (via `compose_instructions()` if available, else department `engineering.md` fallback) + BuildSpec constraints + tool-usage instructions ("Use `read_file` to inspect existing code before writing. Use `edit_file` for surgical changes. Use `run_command pytest <path>` to validate. End with the final file content as either `===FILE:===` blocks for new files or `===MODIFY:===` blocks for changes."). Tool selection: `read_file`/`edit_file`/`write_file`/`list_files`/`run_command`/`codebase_query`/`codebase_find_callers`/`codebase_find_tests`/`codebase_read_source` (subset relevant to code generation; standing-orders/self-model excluded from build harness to avoid scope drift). Post-loop file-change scan: parse final-text `===FILE:===`/`===MODIFY:===` blocks via `BuildPipeline.parse_file_blocks()`. **Routing branch in `SoftwareEngineerAgent.perceive()`** at `src/probos/cognitive/builder.py:1964` — adds a third route after Visiting Builder (line `_should_use_visiting_builder`) and Transporter (`_should_use_transporter`) when `config.native_swe_harness.enabled` is True AND eligibility check passes (`config.native_swe_harness.eligibility_modify_only` and `len(spec.target_files) >= 1` with all targets existing in working tree → modify-only). On selection the harness produces `decision = {"action": "transporter_complete", "file_changes": [...], "builder_source": "native_harness"}` reusing the existing short-circuit in `decide()` at `:2148`. **Test-fix loop integration is OPT-IN**: when native harness is selected AND tests fail in `BuildPipeline.execute_approved_build()`, the existing 2-attempt standalone fix loop is bypassed in favour of feeding test failure output as a tool-result into the ongoing `AgenticLoop` session for up to `max_fix_iterations: int = 5` (configured on `NativeSWEHarnessConfig`). `BuildResult.builder_source` accepts new value `"native_harness"` (validated additive — existing `"native"` / `"visiting"` values preserved).

- **AD-549 v1 — Migration Feature Flag + Build Metadata (5-test plan).** New Pydantic model `NativeSWEHarnessConfig` adjacent to `SoftwareEngineerSpecialistsConfig` at `src/probos/config.py:1770`: `enabled: bool = False` (default-False per AD-695 transitional-flag precedent — pool wiring happens unconditionally but route selection requires opt-in); `eligibility_modify_only: bool = True` (Phase α default); `max_iterations: int = Field(default=25, ge=1)`; `max_fix_iterations: int = Field(default=5, ge=1)`; `token_budget: int | None = Field(default=None, ge=1024)`; `compaction_threshold_pct: float = Field(default=0.8, ge=0.1, le=0.95)`; `blocked_paths: list[str] = Field(default_factory=lambda: ["src/probos/security/", ".env", "config/sealed_modules.yaml"])`. Wired onto `SystemConfig.native_swe_harness` adjacent to `swe_specialists` at `:2734`. New field on `BuildResult` at `cognitive/builder.py:160`: `metadata: dict[str, Any] = field(default_factory=dict)` (additive — existing call sites unchanged byte-for-byte). `NativeBuilderHarness.run_build()` populates `metadata={"builder_type": "native_harness", "iterations": N, "tools_used": [...], "compactions": M}` so downstream observability (HXI Build Queue, episode recording for Cognitive JIT) can read structured execution data without parsing log lines. **Out of v1**: A/B shadow-mode comparison with `build_metrics` table + Builder Comparison HXI tab + Phases β/γ/δ migration — deferred to AD-549b (forcing function: native harness enabled in default config AND first cross-builder file-change discrepancy detected, at which point the comparison surface earns its weight) and follow-on ADs c/d (forcing function: Captain decision after Phase α validation data lands).

**v1 OUT scope (deferred with explicit forcing functions, NOT minted as new GH issues — tracked as roadmap forward-references):**

- **AD-547b — Exact Tokenizer.** Forcing function: first compaction false-trip where the len/4 char approximation diverges >25% from actual model context counting (Anthropic / OpenAI tokenizer-exact). v1's len/4 stub is documented in `session_compactor.py` docstring with explicit upgrade path. Adoption signal: log warning when estimate-vs-actual delta on a real LLMResponse exceeds 25%.
- **AD-548b — Standing-Orders YAML Tool-Policy Schema.** Forcing function: first production deny-policy that needs operator-side tunability without code change. v1 ships `make_blocked_paths_hook` as a Python factory wired via Pydantic-listed paths; the YAML-schema upgrade is mechanical extension once a real policy class warrants it.
- **AD-549b — A/B Shadow-Mode Builder Comparison.** Forcing function: native harness enabled in default config (Phase β migration) AND first cross-builder file-change discrepancy detected. The shadow-mode plumbing + `build_metrics` table + HXI Builder Comparison tab earn their weight only after a real divergence is observed; building them speculatively against `enabled=False` v1 would test scaffolding rather than substance.

**The crew-tier-licensing class-extension plug-in point (out-of-repo overlay):**
The OSS `SoftwareEngineerAgent` + the W97 specialist subclasses + this wave's `NativeBuilderHarness` form the architectural surface. The Pro-tier SWE depth overlay (the deeper-cognitive-chains variant that pushes back on specs, peer-level architectural review across subsystems, solution-tree-search, and Cognitive-JIT-mastery) is class-extension territory under AD-452 and lives in the private commercial-repo path token surface. v1 ships zero closed-source content — descriptor-only references throughout this dispatch and the per-AD prompt. Two additional fleet-level surfaces are also out-of-repo: the cross-instance native-vs-visiting-builder usage telemetry dashboard and the per-fleet skill-catalog adoption metrics for SWE specialty distribution.

## AD numbering

Highest AD stem at HEAD `632398f` is **AD-696** (verified pre-W97 archive line `prompts/archive/WAVE-97-DISPATCH.md` "Highest AD stem at HEAD `6246b35` is AD-696"; W97 minted zero new ADs). Highest BF: **BF-596** (W97 inheritance). W98 mints **zero new AD numbers** (AD-543/544/545/546/547/548/549 all already exist as planned entries at `docs/development/roadmap.md:6677/6707/6734/6757/6790/6814/6843`). W98 mints **zero new BF numbers**. **Current highest: AD-696, BF-596.**

## Verify-first against HEAD `632398f`

Twenty grep-anchored claims, all verified live:

- **`docs/development/roadmap.md:6650`** — `### Native SWE Harness (AD-543–549)` section header. The W98 SEARCH/REPLACE flips per-AD `*(planned)*` markers to `*(v1 shipped Wave 98 — combined harness; AD-547b exact tokenizer / AD-548b YAML tool-policy schema / AD-549b A/B shadow-mode comparison deferred with forcing functions)*`. Verified.
- **`docs/development/roadmap.md:6677`** — `**AD-543: Tool Execution Abstraction — ToolCall Protocol & Executor** *(planned, OSS, depends: AD-521)*`. Flip target.
- **`docs/development/roadmap.md:6707`** — `**AD-544: Native Tool Suite — ProbOS-Integrated Tool Implementations** *(planned, OSS, depends: AD-543)*`. Flip target.
- **`docs/development/roadmap.md:6734`** — `**AD-545: Agentic Loop Engine — Multi-Turn Tool-Calling Orchestrator** *(planned, OSS, depends: AD-543, AD-544)*`. Flip target.
- **`docs/development/roadmap.md:6768`** — `**AD-546: BuildPipeline Integration — Wiring the Harness into the Build System** *(planned, OSS, depends: AD-545, AD-521)*`. Flip target.
- **`docs/development/roadmap.md:6797`** — `**AD-547: Session Compaction — Context Window Management for Long Sessions** *(planned, OSS, depends: AD-545)*`. Flip target.
- **`docs/development/roadmap.md:6817`** — `**AD-548: Trust-Gated Tool Permissions & Standing Orders Hooks** *(planned, OSS, depends: AD-543, AD-544)*`. Flip target.
- **`docs/development/roadmap.md:6844`** — `**AD-549: Builder Migration & Validation** *(planned, OSS, depends: AD-546, AD-548)*`. Flip target.
- **`src/probos/tools/protocol.py:83`** — `class Tool(Protocol):` (AD-423a). Reused as the `ToolDefinition` Protocol surface; `tool_registration_to_llm_definition()` adapter converts `ToolRegistration` (`:139`) records to LLM-API JSON. Verified.
- **`src/probos/tools/executor.py:40`** — `class ToolExecutor:` (AD-448) with `add_pre_hook` / `add_post_hook` / `invoke`. Reused directly by `AgenticLoop`. Verified.
- **`src/probos/events.py:197`** — `TOOL_INVOKED = "tool_invoked"  # AD-448`. Three new EventType values inserted immediately after this line: `AGENTIC_TOOL_CALL_STARTED`, `AGENTIC_TOOL_CALL_COMPLETED`, `AGENTIC_LOOP_ITERATION`. Verified.
- **`src/probos/types.py:227`** — `class LLMRequest:` dataclass with seven fields (`prompt`, `system_prompt`, `tier`, `temperature`, `top_p`, `max_tokens`, `id`). The W98 SEARCH/REPLACE adds `tools: list[dict] | None = None` and `tool_choice: str = "auto"` as trailing fields after `id`. Verified — no shadowing of existing fields.
- **`src/probos/types.py:240`** — `class LLMResponse:` dataclass. The W98 SEARCH/REPLACE adds `content_blocks: list = field(default_factory=list)` and `stop_reason: str = "stop"` as trailing fields after `request_id`. Default-empty-list preserves byte-for-byte text-only callers. Verified.
- **`src/probos/cognitive/llm_client.py:660-714`** — `OpenAICompatibleClient._call_openai` body builds `payload` dict from request fields. The W98 SEARCH/REPLACE inserts a conditional `if request.tools is not None:` block that adds `payload["tools"] = request.tools` and `payload["tool_choice"] = request.tool_choice`, plus a response-side conditional that parses `data["choices"][0]["message"].get("tool_calls")` into `ContentBlock` list. Verified — the existing `content` extraction at `:691-696` is untouched, only extended.
- **`src/probos/cognitive/llm_client.py:1060`** — `MockLLMClient.complete`. Extended with `script_content_blocks(blocks)` helper exposed on the class instance for tests. Verified.
- **`src/probos/cognitive/builder.py:1690`** — `class SoftwareEngineerAgent(CognitiveAgent):` (W96). Routing branch added in `perceive()` at `:1964` immediately after the Transporter Pattern check, before the standard file-loading path. Verified.
- **`src/probos/cognitive/builder.py:160`** — `class BuildResult:` dataclass. Additive `metadata: dict[str, Any] = field(default_factory=dict)` field appended after `builder_source` at `:177`. Verified — existing field order preserved.
- **`src/probos/build_pipeline.py:56`** — `BuildPipeline.execute_approved_build` signature with `specialty` kwarg (W97). v1 of W98 adds NO new kwargs to this method — the harness produces file_changes upstream and the existing pipeline writes them; native-vs-visiting selection happens earlier in `SoftwareEngineerAgent.perceive()`. Verified.
- **`src/probos/config.py:1770`** — `class SoftwareEngineerSpecialistsConfig(BaseModel):` (W97). New `class NativeSWEHarnessConfig(BaseModel):` inserted immediately after this class. Verified.
- **`src/probos/config.py:2734`** — `swe_specialists: SoftwareEngineerSpecialistsConfig = Field(...)` field on `SystemConfig`. W98 SEARCH/REPLACE adds `native_swe_harness: NativeSWEHarnessConfig = Field(default_factory=NativeSWEHarnessConfig)` immediately after this line. Verified.
- **`src/probos/agents/file_reader.py:45` / `file_writer.py:41` / `shell_command.py:61` / `cognitive/codebase_index.py:158/339/368/397/406`** — five capability surfaces consumed by the 12 AD-544 tool adapters. Each adapter reaches its capability via the runtime handle; no monkey-patching, no private-attr access. Verified.

PROGRESS.md tracker note: `Select-String -Path PROGRESS.md -Pattern 'AD-543|AD-544|AD-545|AD-546|AD-547|AD-548|AD-549' -SimpleMatch` returns **zero hits** at HEAD — W98 INSERTS one canonical closed-AD prose block at the top of PROGRESS.md following the W97 pattern at `PROGRESS.md:3`.

## Reframe decision — build combined v1, three deferrals with forcing functions, zero new GH issues

**Seven concrete sub-AD-equivalents shipped in one combined v1 + three deferred sub-letter ADs with explicit forcing functions + the Pro-tier SWE-depth overlay as out-of-repo plug-in point + two out-of-repo fleet-telemetry surfaces + zero hard-deferrals beyond the three forcing-function-tracked items.**

The wave structure is monolithic because the substrate has no observable behaviour without the loop and the loop cannot compile without the substrate. The standalone AD-543 wave option was rejected: shipping wire-format dataclasses with no consumer at HEAD would test scaffolding rather than behaviour. The standalone AD-548 wave option was rejected because AD-423b + AD-448 already ship 90% of the spec — the incremental v1 surface (one factory function + one Pydantic field) doesn't merit a separate Builder cycle. The standalone AD-549 wave option was rejected because the feature flag is meaningless without the harness it gates.

Captain rule "don't defer unless no choice" satisfied non-vacuously: three deferrals are tracked with concrete adoption signals (compaction-estimate divergence, deny-policy operator-tunability demand, cross-builder file-change discrepancy) rather than schedule pressure. The deferred items would be speculative scaffolding against zero production data at this phase; building them now would violate "validation at parse time, not at runtime when the feature is first used."

## Files

- `prompts/WAVE-98-DISPATCH.md` (this file)
- `prompts/ad-543-549-native-swe-harness-v1.md` (per-AD combined prompt — seven sections + tracker updates + verification footer)
- `prompts/wave-plan.yaml` (W98 entry appended after W97 tail)

## Wave-98 baseline + targets

- **HEAD:** `632398f` (`Wave 97 archive: AD-476 specialized builders (#70)`). Captain reference HEAD `632398f` matches `origin/main` exactly.
- **Baseline pytest:** Captain reference is **12138**; live `pytest --collect-only` returns **12160 collected** at HEAD `632398f`. The Captain reference reflects the post-W97 stable-pass count after parallel re-triage; W98 targets against the Captain number for Δ accounting.
- **Target pytest:** **≥12211** (Δ ≥ +73 against Captain reference 12138 — combined plan: AD-543 ≈14 + AD-544 ≈16 + AD-545 ≈14 + AD-547 ≈8 + AD-546 ≈10 + AD-548 ≈6 + AD-549 ≈5 = 73 nominal, with up to ≈10 boundary tests deferred to second-pass build if any sub-section flips during build). Builder may exceed +73 with additional edge-case tests; minimum gate is +60 (a hard floor accommodates one or two `@pytest.mark.skip` adjustments mid-build).
- **Issue closed:** `#13 — Native SWE Harness — Agentic Tool Loop (AD-543–549)` (single issue spanning all 7 ADs; W98 closes it cleanly with the canonical paragraph in Section 8 of the per-AD prompt).

## Banned-pattern audit on this dispatch + the per-AD prompt + this audit prose itself

Eleven patterns checked, descriptor-only language used throughout: "the e-word + tier phrase", "the private commercial-repo path token", "the e-word overlay phrase", "the e-word-prefixed repo token", "monthly-price regex", "per-month abbreviation regex", "rev-proj phrase", "the recurring-revenue acronym", "outcome-style pricing phrase", "the GTM-pattern phrase", "the patterns-to-absorb phrase". The audit text itself does NOT contain literal forms of any banned pattern — descriptor-only references throughout. The pre-commit hook trips on literal forms; this dispatch (and the per-AD prompt + the wave-plan.yaml notes block) avoids both via descriptor-only references. Pre-commit-hook simulation `Select-String -Path prompts/WAVE-98-DISPATCH.md, prompts/ad-543-549-native-swe-harness-v1.md -Pattern <pattern> -SimpleMatch` returns zero hits per pattern across all artefacts.

The Pro-tier SWE-depth overlay scope is genuine — AD-521 W96 documented the OSS-vs-closed-source crew-tier split (`decisions-era-4-evolution.md:1453-1456`) and W98's harness inherits the same plug-in surface — but v1 ships zero closed-source content. The class-extension plug-in point is the architectural surface; the overlay implementation lives in the private commercial-repo path token surface, governed by AD-452 class-extension mechanism. Descriptor-only language is appropriate here: the audit is a real hygiene check on a real plug-in surface.

## Captain rule alignment

- **Don't defer unless no choice:** three deferrals (AD-547b / AD-548b / AD-549b) each tracked with a concrete adoption signal forcing function. The combined wave structure ships seven planned ADs as one v1 cycle rather than splitting into substrate-only and harness-only waves; the spec's own listed implementation order at `docs/development/roadmap.md:6868` already groups them.
- **Verify-first:** twenty grep-anchored claims in this dispatch + a 30-line Verified-Against-Codebase footer in the per-AD prompt covering each module's anchor lines, each existing-symbol reuse, each new-EventType insertion site, each Pydantic field insertion site, and each routing-branch insertion site.
- **`.github/copilot-instructions.md` compliance:** explicit acceptance-criteria line in the per-AD prompt — "Verify all changes comply with the Engineering Principles in `.github/copilot-instructions.md`." The seven new modules honour: SOLID-S (each module single-responsibility — `tool_call.py` types, `tools.py` adapters, `agentic_loop.py` orchestration, `session_compactor.py` compaction, `native_builder.py` build wrapper, `policies.py` hooks); Open/Closed (AD-423a `Tool` Protocol reused without modification, AD-448 `ToolExecutor` reused via composition); Liskov (`AgenticResult` / `ToolCallResult` are pure dataclasses; routing branch in `SoftwareEngineerAgent.perceive()` preserves base contract); Interface Segregation (AD-543's helpers are narrow JSON-schema adapters, not god-classes); Dependency Inversion (constructor injection of `llm_client` / `tool_executor` / `runtime`; no globals); Demeter (no `obj._private_attr` access — runtime handle passed at construction, capabilities accessed through public methods); DRY (12 tool adapters share a common base structure; reuse `BuildPipeline.parse_file_blocks` rather than re-implementing); Cloud-ready storage (no new DB access; metadata dict on BuildResult is in-memory only); Three-tier exception handling (AgenticLoop log-and-degrade preserves partial result; tool execution wraps every call in try/except → ToolCallResult error path); Type annotations (all public methods fully typed); Logging context (every tool invocation + every loop iteration + every compaction includes operation + agent + outcome); Async hygiene (every `create_task` in event emission uses fire-and-forget pattern matching `TRANSPORTER_DECOMPOSED` precedent); Configuration (Pydantic `NativeSWEHarnessConfig` with `field_validator`s, defaults at parse time).
- **Close #13 cleanly:** issue closed at end of W98 with the canonical paragraph in Section 8 of the per-AD prompt; no children minted, three forward-references (AD-547b / AD-548b / AD-549b) tracked as roadmap entries with forcing functions but NOT minted as new GH issues (they need real adoption signals to become buildable).
- **No commercial leak:** descriptor-only audit, banned-pattern scan returns zero hits across all 11 patterns. The Pro-tier SWE-depth overlay plug-in point is documented descriptor-only with placeholder forward-reference to the AD-452 class-extension AD that lives in the private commercial-repo path token surface.

## Build groups

Single Builder cycle — no dependency DAG within the wave. The work decomposes into nine sections applied top-to-bottom; each section is a coherent commit-ready chunk:

1. **Section 1 — `src/probos/cognitive/swe_harness/__init__.py` + `tool_call.py`** (AD-543 substrate dataclasses + LLM-definition adapter). Pure types, no agent classes, no runtime dependency. ~210 lines.
2. **Section 2 — `src/probos/types.py` + `events.py` extensions** (LLMRequest tools/tool_choice fields, LLMResponse content_blocks/stop_reason fields, three new EventType values). ~12 lines additive.
3. **Section 3 — `src/probos/cognitive/llm_client.py` extensions** (`_call_openai` payload + response parsing for tool_use, `MockLLMClient.script_content_blocks`). ~80 lines additive.
4. **Section 4 — `src/probos/cognitive/swe_harness/tools.py`** (AD-544 12 tool adapters + `register_native_swe_tools` helper). ~520 lines.
5. **Section 5 — `src/probos/cognitive/swe_harness/policies.py`** (AD-548 `make_blocked_paths_hook` factory). ~80 lines.
6. **Section 6 — `src/probos/cognitive/swe_harness/agentic_loop.py`** (AD-545 `AgenticLoop` + `AgenticResult` + event emission). ~340 lines.
7. **Section 7 — `src/probos/cognitive/swe_harness/session_compactor.py`** (AD-547 `SessionCompactor` + `estimate_tokens`). ~130 lines.
8. **Section 8 — `src/probos/cognitive/swe_harness/native_builder.py` + `SoftwareEngineerAgent.perceive()` routing branch** (AD-546 `NativeBuilderHarness` + builder integration). ~280 lines.
9. **Section 9 — `src/probos/config.py` + `BuildResult.metadata` + `startup/finalize.py` wirer** (AD-549 Pydantic config + metadata field + finalize-time tool registration + harness instantiation). ~120 lines.

Then trackers: `DECISIONS.md` / `decisions-era-4-evolution.md` / `docs/development/roadmap.md` / `PROGRESS.md` / `prompts/wave-plan.yaml` per the per-AD prompt's Section 10.

Then tests: seven new test files (`tests/test_ad543_tool_call_protocol.py`, `tests/test_ad544_native_tools.py`, `tests/test_ad545_agentic_loop.py`, `tests/test_ad546_native_builder_harness.py`, `tests/test_ad547_session_compactor.py`, `tests/test_ad548_blocked_paths_policy.py`, `tests/test_ad549_harness_config_metadata.py`) totaling 73 tests nominal.

## Concerns / observations

- **Scope is at the upper end of recent waves.** W87 (AD-443) targeted ≥+55 with five letters, W82 (AD-633) ≥+35 with eight sub-ADs. W98 at +73 nominal is the largest combined v1 wave to date. Builder may run 2-3× the typical wave duration. If a fix-loop blow-up occurs mid-build, the orchestrator hard-stop pattern (BF-252/253/254 precedent) applies — Builder surfaces, Architect triages, AD-547b/548b/549b deferral basket can absorb a section's worth of scope cut without invalidating the wave structure.
- **Phantom-API pre-check** (`scripts/phantom-api-precheck.ps1`) catches `Class.method` and `runtime.X` shapes but does not validate kwargs against live signatures (Wave 10 lesson + Wave 47 AD-685d extension). The per-AD prompt body's 12 tool adapters reference five capability surfaces (FileReaderAgent / FileWriterAgent / ShellCommandAgent / CodebaseIndex / standing_orders manuals); each is verified at HEAD in the dispatch's "Verify-first" section. The kwarg-shape verification is enforced via the AD-685c type-shape validator already shipped Wave 46.
- **Default-False on `enabled` flag** is the AD-695 transitional-flag precedent (W77 ConsultationWorkspaces also default-True for read-only state, default-False for behaviour-changing routes). Native harness route selection requires opt-in; tool registration happens unconditionally because registration is cheap and observable via `ToolRegistry.list_for_agent()` from day one (the registration is observable behaviour even when the route is not selected).
- **Three new EventTypes** inherit AD-448's `EventType` placement. Builders downstream are expected to subscribe via the runtime event bus pattern; HXI surface for agentic-loop iteration visualization is parked behind AD-549b forcing function.
- **Builder workflow note** — the Builder should commit Sections 1-3 (substrate) first as a single commit, then Sections 4-7 (tools + loop + compactor + policies) as a second commit, then Section 8-9 (harness + config + wiring) as a third commit. Three commits inside one wave is a minor variance from the single-commit-per-wave convention but justified by the substrate-vs-consumer separation that helps `git bisect` post-wave. If Builder elects single-commit, that is acceptable provided the commit message lists all seven AD numbers explicitly.

## Builder execution

Read prompt top-to-bottom, apply 9 sections + tracker updates + 7 test files. Verify `git diff --stat` shows: 5 trackers modified (DECISIONS / decisions-era-4 / roadmap / PROGRESS / wave-plan) + ~6 source files modified (`types.py` / `events.py` / `cognitive/llm_client.py` / `cognitive/builder.py` / `config.py` / `startup/finalize.py`) + 7 NEW source files in `cognitive/swe_harness/` + 7 NEW test files + this dispatch + per-AD prompt (Builder archives both after commit). Pre-commit hook runs naturally on commit. Full pytest gate (expected ≥12211 passed; minimum gate is +60 hard floor). Commit with `"AD-543/544/545/546/547/548/549 v1: Native SWE Harness — agentic tool loop (combined wave; +73 tests; closes #13)"`. Archive both prompts. `gh issue close 13 -c "<canonical paragraph from Section 8>"`.
