From a26591a591ec0d15bacd4b8dfc09afcd5a47e96f Mon Sep 17 00:00:00 2001
From: pelikhan <jhalleux@microsoft.com>
Date: Fri, 1 May 2026 18:36:43 -0700
Subject: [PATCH 01/14] docs: add AW harness design plan
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Design plan for the 'aw' engine harness — a Pi SDK-based replacement
for copilot_harness.cjs that adds multi-step DAG orchestration,
multi-agent/multi-model support, context engineering, cost tracking,
steering, session repair, and observability.

Key design decisions:
- Built on Pi agent ecosystem (pi-coding-agent, pi-agent-core, pi-ai)
- All gh-aw capabilities implemented as Pi extensions (ExtensionAPI)
- Model resolution via existing api-proxy (no provider inference)
- TypeScript compiled for Node 24, bundled as single .cjs
- New opt-in engine (engine: aw) — existing engines untouched

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 specs/aw-harness.md | 726 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 726 insertions(+)
 create mode 100644 specs/aw-harness.md

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
new file mode 100644
index 00000000000..0207b30aca4
--- /dev/null
+++ b/specs/aw-harness.md
@@ -0,0 +1,726 @@
+# AW Harness Design Plan
+
+## Problem Statement
+
+Design a **replacement for `copilot_harness.cjs`** — the Node.js wrapper that gh-aw uses to launch coding agent CLIs inside the GitHub Actions container. Today's harness is a thin retry loop around a single CLI invocation. The new harness — called **"aw"** — adds **multi-step orchestration**, **multi-agent**, **multi-model**, **context engineering**, **cost tracking**, and **observability**.
+
+The harness is **built on top of the Pi agent ecosystem** (`@mariozechner/pi-coding-agent`, `pi-agent-core`, `pi-ai`). All gh-aw-specific capabilities — safe outputs, MCP gateway bridging, cost tracking, steering, observability — are implemented as **Pi extensions** using Pi's native extensibility mechanism (`ExtensionAPI`). This means the extensions are reusable with standalone Pi and the OpenClaw ecosystem.
+
+### What this IS
+
+- A new `engine: aw` option (registered via `harness: aw_harness.cjs` in frontmatter)
+- A Pi SDK application with gh-aw-specific Pi extensions
+- Optimized for execution inside the gh-aw Action container (firewall, api-proxy, MCP gateway)
+- TypeScript compiled for Node.js 24, bundled as a single `.cjs` in `actions/setup/js/`
+
+### What this is NOT
+
+- NOT a reimplementation of gh-aw (compilation, triggers, safe-outputs post-processing, threat detection stay as-is)
+- NOT a CLI spawner — no `copilot` / `claude` / `codex` CLI processes are spawned
+- NOT interactive (no TUI — pure headless CI execution via Pi SDK mode)
+- NOT a replacement for existing engines — `engine: copilot`, `engine: claude`, `engine: codex` continue to work via their current harnesses
+
+## Model Resolution: api-proxy Integration
+
+The harness does **not** do provider inference or model routing. That is handled by the **api-proxy** (`gh-aw-firewall/containers/api-proxy/model-resolver.js`), which already runs in the container.
+
+### How model resolution works
+
+The api-proxy accepts model names (aliases or explicit `provider/model` refs) and resolves them against available models per provider:
+
+```
+Harness (Pi SDK) → api-proxy → LLM provider
+  model: "sonnet"           resolves to copilot/claude-sonnet-4.6
+  model: "gpt-5-codex"      resolves to copilot/gpt-5.3-codex
+  model: "copilot/gpt-4.1"  passes through as-is
+```
+
+**Alias config** (via `AWF_MODEL_ALIASES` env var):
+
+```json
+{
+  "models": {
+    "sonnet": ["copilot/*sonnet*", "anthropic/*sonnet*"],
+    "gpt-5-codex": ["copilot/gpt-5*-codex", "openai/gpt-5*-codex"],
+    "": ["sonnet", "gpt-5*-codex"]
+  }
+}
+```
+
+**Key properties**:
+
+- `providerid/modelid` syntax with `*` wildcards
+- Recursive alias resolution with loop detection
+- Case-insensitive matching
+- Semver sorting (highest version first among candidates)
+- Default policy via `""` key (used when no model is specified)
+
+### What this means for the harness
+
+- Pi SDK's `pi-ai` is configured with the api-proxy as an **OpenAI-compatible custom provider** (using `pi.registerProvider()` or `models.json` configuration)
+- The harness just passes model names through — aliases like `"sonnet"` work out of the box
+- No `provider:` field needed in frontmatter — the proxy handles routing
+- Per-step/per-agent model selection is just setting a different model name string
+- The harness inherits the api-proxy's full model catalog without maintaining its own
+
+## Architecture: Pi SDK + Extensions
+
+### Core Principle: Everything is a Pi Extension
+
+The harness is a thin orchestration layer that creates Pi `AgentSession` instances (via `createAgentSession()` from the Pi SDK). All gh-aw-specific capabilities are implemented as **Pi extensions** using the `ExtensionAPI` interface:
+
+```typescript
+// Each gh-aw capability is a Pi extension
+import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
+
+export default function safeOutputsExtension(pi: ExtensionAPI) {
+  // Register safe-output tools the LLM can call
+  pi.registerTool({ name: "create_issue", ... });
+  pi.registerTool({ name: "create_pull_request", ... });
+  pi.registerTool({ name: "add_comment", ... });
+
+  // Hook into agent events for observability
+  pi.on("tool_call", async (event, ctx) => { ... });
+  pi.on("agent_end", async (event, ctx) => { ... });
+}
+```
+
+**Why Pi extensions instead of custom code?**
+
+1. **Reusable** — Extensions work with standalone Pi CLI, OpenClaw, and any Pi SDK app
+2. **Composable** — Users can add their own extensions alongside gh-aw ones
+3. **Standard lifecycle** — Pi handles extension loading, event dispatch, tool registration
+4. **Testable** — Extensions can be tested independently with Pi's SDK test harness
+5. **Ecosystem-compatible** — Can be published as Pi packages (`pi install npm:@github/aw-extensions`)
+
+### How It Fits in the gh-aw Stack
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│  GitHub Actions Job (compiled from .lock.yml by gh-aw)       │
+│                                                              │
+│  ┌──────────────────────────────────────────────────────┐   │
+│  │  Container (firewall, api-proxy, MCP gateway)         │   │
+│  │                                                       │   │
+│  │  ┌─────────────────────────────────────────────────┐ │   │
+│  │  │  aw_harness.cjs (entry point)                   │ │   │
+│  │  │                                                  │ │   │
+│  │  │  1. Reads workflow.md (frontmatter + body)       │ │   │
+│  │  │  2. Parses steps from markdown headings          │ │   │
+│  │  │  3. Builds execution DAG                         │ │   │
+│  │  │  4. For each step: creates Pi AgentSession       │ │   │
+│  │  │     with gh-aw extensions loaded                 │ │   │
+│  │  │  5. session.prompt() → Pi drives the agent       │ │   │
+│  │  │                                                  │ │   │
+│  │  │  ┌──────────────────────────────────────────┐   │ │   │
+│  │  │  │  Pi SDK (createAgentSession)             │   │ │   │
+│  │  │  │  ├─ pi-agent-core (agent loop, events)   │   │ │   │
+│  │  │  │  ├─ pi-ai → api-proxy → LLM providers   │   │ │   │
+│  │  │  │  └─ compaction, steering, auto-retry      │   │ │   │
+│  │  │  └──────────────────────────────────────────┘   │ │   │
+│  │  │  ┌──────────────────────────────────────────┐   │ │   │
+│  │  │  │  gh-aw Pi Extensions (loaded into each   │   │ │   │
+│  │  │  │  AgentSession via ExtensionAPI):          │   │ │   │
+│  │  │  │  ├─ safe-outputs (tools + artifact write) │   │ │   │
+│  │  │  │  ├─ mcp-bridge (gateway tools → Pi tools) │   │ │   │
+│  │  │  │  ├─ cost-tracker (budget gates + events)  │   │ │   │
+│  │  │  │  ├─ steering (time/budget pressure)       │   │ │   │
+│  │  │  │  ├─ repair (broken session recovery)      │   │ │   │
+│  │  │  │  ├─ observability (JSONL + OTel)          │   │ │   │
+│  │  │  │  └─ checkpoint (persist/resume state)     │   │ │   │
+│  │  │  └──────────────────────────────────────────┘   │ │   │
+│  │  │  ┌──────────────────────────────────────────┐   │ │   │
+│  │  │  │  api-proxy (model alias resolution)      │   │ │   │
+│  │  │  │  └─ Recursive alias → provider routing    │   │ │   │
+│  │  │  └──────────────────────────────────────────┘   │ │   │
+│  │  │  ┌──────────────────────────────────────────┐   │ │   │
+│  │  │  │  MCP Gateway (gh-aw, already running)    │   │ │   │
+│  │  │  │  └─ GitHub tools, custom MCP servers      │   │ │   │
+│  │  │  └──────────────────────────────────────────┘   │ │   │
+│  │  └─────────────────────────────────────────────────┘ │   │
+│  └──────────────────────────────────────────────────────┘   │
+│                                                              │
+│  ┌──────────────────────────────────────────────────────┐   │
+│  │  Post-agent jobs (safe-outputs, threat detection)     │   │
+│  │  — unchanged, reads same artifact format              │   │
+│  └──────────────────────────────────────────────────────┘   │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Design Principles
+
+1. **Built on Pi ecosystem** — Uses Pi SDK (`createAgentSession()`, `Agent`, `AgentTool`) as the runtime. All gh-aw capabilities are Pi extensions, not custom plumbing.
+2. **Extensions-first** — Every gh-aw feature is a proper Pi extension using `ExtensionAPI`. Extensions use `pi.registerTool()` for tools, `pi.on()` for events, `pi.registerProvider()` for model routing. This makes them reusable outside gh-aw.
+3. **api-proxy for model resolution** — Pi's `pi-ai` talks to the api-proxy as an OpenAI-compatible provider. Model names (aliases or explicit) pass through — the proxy resolves.
+4. **Optimized for gh-aw container** — Assumes firewall, api-proxy, MCP gateway are running. No redundant auth, no direct LLM API calls, no network configuration.
+5. **TypeScript → Node 24** — Source is TypeScript, compiled to ES2024, bundled via esbuild to a single `.cjs`. Leverages Node 24 features (native fetch, structuredClone, AbortSignal.any).
+6. **Output in `actions/setup/js/`** — The bundled `aw_harness.cjs` lives alongside `copilot_harness.cjs` and `claude_harness.cjs`. Same deployment mechanism, same runtime contract.
+7. **New opt-in engine** — `engine: aw` is a new choice. Existing engines are untouched.
+8. **Markdown-native steps** — `## Heading` = step boundary. HTML comments carry metadata.
+9. **Observable** — JSONL event stream + OTel spans, all via the observability extension.
+
+## Pi Provider Configuration for api-proxy
+
+The harness registers the api-proxy as a custom Pi provider at startup, using Pi's `registerProvider()` mechanism:
+
+```typescript
+export default async function apiProxyProvider(pi: ExtensionAPI) {
+  const proxyUrl = process.env.AWF_API_PROXY_URL || "http://localhost:8080/v1";
+
+  pi.registerProvider("aw-proxy", {
+    baseUrl: proxyUrl,
+    apiKey: "AW_PROXY_API_KEY",   // env var name — Pi resolves it
+    api: "openai-completions",     // api-proxy speaks OpenAI protocol
+    models: await fetchAvailableModels(proxyUrl),
+  });
+}
+```
+
+This means:
+
+- Pi treats the api-proxy as a standard OpenAI-compatible provider
+- Model aliases (`sonnet`, `gpt-5-codex`) are sent as-is to the proxy
+- The proxy resolves aliases, routes to the actual provider, returns the response
+- Pi's token counting, cost tracking, and streaming all work transparently
+
+## Workflow Definition Format
+
+Uses the **existing gh-aw frontmatter** with `engine: aw` and a new optional `harness:` block.
+
+```markdown
+---
+on:
+  schedule:
+    - cron: "0 9 * * 1-5"
+
+engine:
+  id: aw
+  model: sonnet                  # Model alias — api-proxy resolves
+  harness: aw_harness.cjs
+
+permissions:
+  contents: read
+  issues: read
+  pull-requests: read
+
+tools:
+  github:
+    toolsets: [issues, pull_requests, code_search]
+  bash: [grep, find, wc, git, jq]
+
+safe-outputs:
+  create-issue:
+    title-prefix: "[review] "
+    labels: [automated-review]
+    max-count: 3
+
+timeout-minutes: 30
+
+observability:
+  otlp:
+    endpoint: ${{ secrets.OTLP_ENDPOINT }}
+    headers:
+      Authorization: ${{ secrets.OTLP_TOKEN }}
+
+# ── Harness config (optional) ───────────────────────────────
+harness:
+  budget:
+    max-cost-usd: 5.00
+    warn-at-percent: 80
+
+  context:
+    compaction: summarize
+    compaction-threshold: 0.75
+    transcript-mode: summary
+
+  agents:
+    reviewer:
+      model: sonnet
+      system: |
+        You are a senior code reviewer. Focus on correctness, security,
+        and maintainability.
+    scanner:
+      model: gpt-5-codex
+      system: |
+        You are a security specialist. Focus on vulnerabilities,
+        injection risks, and credential exposure.
+    synthesizer:
+      model: copilot/gpt-4.1
+      system: |
+        You synthesize multiple review perspectives into a single,
+        prioritized action list.
+
+  steps:
+    parallel-review:
+      agents: [reviewer, scanner]
+      parallel: true
+    synthesize:
+      agent: synthesizer
+      depends: [parallel-review]
+
+  steering:
+    time-warning-minutes: 5
+    time-critical-minutes: 2
+    budget-warn-percent: 75
+    budget-critical-percent: 90
+
+  checkpoint: true
+---
+
+## Daily Code Review
+
+Review all changes pushed to the default branch in the last 24 hours.
+
+### Gather Changes
+<!-- harness-step: gather -->
+Use `git log --since="24 hours ago"` and `git diff` to collect all
+recent changes. Summarize the scope.
+
+### Parallel Review
+<!-- harness-step: parallel-review -->
+Each reviewer examines the changes independently.
+
+### Synthesize
+<!-- harness-step: synthesize -->
+Read outputs from both reviewers. Produce a prioritized findings list.
+
+### Report
+Create a GitHub issue with the synthesized review.
+```
+
+### How Steps Work
+
+**Step extraction**: Each `## Heading` or `### Heading` is a potential step. Linked to `harness.steps` via `<!-- harness-step: name -->` HTML comments.
+
+**Implicit behavior**:
+
+- No `<!-- harness-step -->` → sequential by default (document order)
+- With annotation → follows `harness.steps` config (parallel, depends, agent)
+- No `harness:` block → entire body = single Pi session prompt
+
+**Step execution**:
+
+```
+For each step (respecting DAG order):
+  1. Build prompt = step markdown + upstream transcripts + system prompt
+  2. Create Pi AgentSession with gh-aw extensions:
+     - api-proxy provider registered
+     - MCP bridge tools registered
+     - Safe-output tools registered
+     - Steering, repair, cost, observability extensions active
+  3. session.prompt() → Pi agent loop runs
+  4. Extensions handle events (cost tracking, steering, observability)
+  5. Capture transcript for downstream steps
+  6. Budget gate check, checkpoint state
+```
+
+## gh-aw Pi Extensions
+
+All gh-aw-specific behavior is packaged as Pi extensions. Each extension is a standalone TypeScript module that exports a default function receiving `ExtensionAPI`.
+
+### Extension 1: api-proxy Provider
+
+Registers the gh-aw api-proxy as a custom Pi provider.
+
+```typescript
+export default async function(pi: ExtensionAPI) {
+  const proxyUrl = process.env.AWF_API_PROXY_URL || "http://localhost:8080/v1";
+  const models = await fetchModelsFromProxy(proxyUrl);
+
+  pi.registerProvider("aw-proxy", {
+    baseUrl: proxyUrl,
+    apiKey: "AWF_API_PROXY_TOKEN",
+    api: "openai-completions",
+    models,
+  });
+}
+```
+
+**Why an extension**: Pi's `registerProvider()` is designed exactly for this. Async factory support means we can fetch the model list at startup before any session begins.
+
+### Extension 2: MCP Gateway Bridge
+
+Bridges gh-aw's MCP gateway tools into Pi's tool system as `AgentTool` instances.
+
+```typescript
+export default function(pi: ExtensionAPI) {
+  const gatewayConfig = loadMCPGatewayConfig();
+
+  for (const [serverName, tools] of Object.entries(gatewayConfig)) {
+    for (const tool of tools) {
+      pi.registerTool({
+        name: `${serverName}_${tool.name}`,
+        description: tool.description,
+        parameters: tool.inputSchema,
+        async execute(toolCallId, params, signal) {
+          return await callMCPGateway(serverName, tool.name, params);
+        },
+      });
+    }
+  }
+}
+```
+
+**Why an extension**: Pi's `pi.registerTool()` is the standard way to add tools. MCP gateway tools become first-class Pi tools — visible in sessions, tracked in events, subject to `beforeToolCall`/`afterToolCall` hooks.
+
+### Extension 3: Safe Outputs
+
+Registers safe-output tools (create-issue, create-pull-request, add-comment, etc.) and writes artifact files in gh-aw's expected format.
+
+```typescript
+export default function(pi: ExtensionAPI) {
+  const config = loadSafeOutputsConfig();
+
+  pi.registerTool({
+    name: "create_issue",
+    description: "Create a GitHub issue (safe output)",
+    parameters: Type.Object({
+      title: Type.String(),
+      body: Type.String(),
+      labels: Type.Optional(Type.Array(Type.String())),
+    }),
+    async execute(toolCallId, params, signal) {
+      const artifact = buildSafeOutputArtifact("create-issue", params, config);
+      await writeSafeOutputArtifact(artifact);
+      return { content: [{ type: "text", text: `Issue queued: ${params.title}` }] };
+    },
+  });
+
+  // ... register other safe-output tools
+
+  pi.on("agent_end", async (event, ctx) => {
+    await finalizeSafeOutputManifest();
+  });
+}
+```
+
+**Why an extension**: Tool registration + lifecycle hooks. The `agent_end` event is the right place to finalize artifacts.
+
+### Extension 4: Cost Tracker
+
+Monitors token usage and cost via Pi's event stream. Enforces budget gates.
+
+```typescript
+export default function(pi: ExtensionAPI) {
+  const budget = loadBudgetConfig();
+  let totalCost = 0;
+
+  pi.on("turn_end", async (event, ctx) => {
+    totalCost += extractCostFromTurn(event);
+
+    const percent = (totalCost / budget.maxCostUsd) * 100;
+    if (percent >= budget.budgetCriticalPercent) {
+      ctx.agent.abort();
+    } else if (percent >= budget.budgetWarnPercent) {
+      ctx.agent.steer({
+        role: "user",
+        content: `⚠️ Budget: ${percent.toFixed(0)}% used. Be concise.`,
+        timestamp: Date.now(),
+      });
+    }
+  });
+
+  pi.on("agent_end", async () => {
+    emitCostSummary(totalCost);
+  });
+}
+```
+
+**Why an extension**: Pi's event stream (`turn_end`) provides token/cost data per turn. The `steer()` API injects budget warnings naturally.
+
+### Extension 5: Steering (Resource Pressure)
+
+Monitors time remaining and budget, injects steering messages via Pi's native `session.steer()`.
+
+```typescript
+export default function(pi: ExtensionAPI) {
+  const config = loadSteeringConfig();
+  let startTime: number;
+
+  pi.on("agent_start", async () => {
+    startTime = Date.now();
+  });
+
+  pi.on("turn_end", async (event, ctx) => {
+    const elapsed = (Date.now() - startTime) / 60000;
+    const remaining = config.timeoutMinutes - elapsed;
+
+    if (remaining <= config.timeCriticalMinutes) {
+      ctx.agent.steer({
+        role: "user",
+        content: `⚠️ CRITICAL: ${remaining.toFixed(0)}min left. Write final output NOW.`,
+        timestamp: Date.now(),
+      });
+    } else if (remaining <= config.timeWarningMinutes) {
+      ctx.agent.steer({
+        role: "user",
+        content: `⚠️ ${remaining.toFixed(0)}min remaining. Wrap up.`,
+        timestamp: Date.now(),
+      });
+    }
+  });
+}
+```
+
+**Why an extension**: Pi's `turn_end` event fires after each tool execution cycle. Perfect timing for resource checks. `steer()` delivers the message before the next LLM call.
+
+### Extension 6: Session Repair
+
+Detects broken tool calls and repairs the session via Pi's message history manipulation.
+
+```typescript
+export default function(pi: ExtensionAPI) {
+  pi.on("tool_result", async (event, ctx) => {
+    if (isCorruptedToolResult(event)) {
+      const messages = ctx.agent.state.messages;
+      const repaired = truncateBrokenMessages(messages);
+      ctx.agent.state.messages = repaired;
+      emitRepairEvent("truncate_and_resume", event.toolName);
+    }
+  });
+
+  pi.on("agent_end", async (event, ctx) => {
+    if (event.error && isRecoverableError(event.error)) {
+      const summary = await summarizeTranscript(ctx.agent.state.messages);
+      ctx.agent.followUp({
+        role: "user",
+        content: `Previous progress: ${summary}\nContinue from here.`,
+        timestamp: Date.now(),
+      });
+    }
+  });
+}
+```
+
+**Why an extension**: Pi exposes `agent.state.messages` for manipulation and `tool_result` events for interception.
+
+### Extension 7: Observability
+
+Emits JSONL events to stderr and generates OTel spans.
+
+```typescript
+export default function(pi: ExtensionAPI) {
+  pi.on("agent_start", async (event) => {
+    emitJsonl({ event: "step_start", step: currentStep, model: currentModel });
+    startOtelSpan(currentStep);
+  });
+
+  pi.on("turn_end", async (event) => {
+    recordOtelAttributes(event);
+  });
+
+  pi.on("tool_execution_end", async (event) => {
+    emitJsonl({ event: "tool_end", tool: event.toolName, duration: event.duration });
+  });
+
+  pi.on("agent_end", async (event) => {
+    emitJsonl({ event: "step_end", step: currentStep, tokens: event.tokens, cost: event.cost });
+    endOtelSpan(currentStep);
+  });
+}
+```
+
+**Why an extension**: Pi's full event stream provides exactly the data needed for JSONL and OTel.
+
+### Extension 8: Checkpoint
+
+Persists run state for long workflows.
+
+```typescript
+export default function(pi: ExtensionAPI) {
+  pi.on("agent_end", async (event, ctx) => {
+    await saveCheckpoint({
+      step: currentStep,
+      status: event.error ? "failed" : "done",
+      transcript: ctx.agent.state.messages,
+      cost: totalCost,
+    });
+  });
+}
+```
+
+## Orchestration Layer (Not an Extension)
+
+The multi-step DAG orchestration is the **harness entry point** — it sits above the Pi SDK and creates/manages multiple `AgentSession` instances. This is NOT a Pi extension because it manages session lifecycles rather than extending a single session.
+
+```typescript
+// index.ts — entry point
+import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
+
+async function main() {
+  const workflow = parseWorkflow(process.argv[2]);
+  const dag = buildDAG(workflow);
+  const extensions = [
+    apiProxyProvider,
+    mcpBridgeExtension,
+    safeOutputsExtension,
+    costTrackerExtension,
+    steeringExtension,
+    repairExtension,
+    observabilityExtension,
+    checkpointExtension,
+  ];
+
+  for (const stepGroup of dag.executionOrder()) {
+    await Promise.all(stepGroup.map(async (step) => {
+      const { session } = await createAgentSession({
+        sessionManager: SessionManager.inMemory(),
+        extensions,
+        model: resolveModel(step.agent?.model || workflow.defaultModel),
+        systemPrompt: buildSystemPrompt(step),
+      });
+
+      const prompt = buildStepPrompt(step, transcripts);
+      await session.prompt(prompt);
+
+      transcripts[step.name] = captureTranscript(session);
+      session.dispose();
+    }));
+  }
+}
+```
+
+## Backwards Compatibility
+
+| Scenario | Behavior |
+|----------|----------|
+| `engine: copilot` (existing) | Uses current `copilot_harness.cjs` — unchanged |
+| `engine: claude` (existing) | Uses current Claude Code flow — unchanged |
+| `engine: codex` (existing) | Uses current Codex flow — unchanged |
+| `engine: aw` without `harness:` block | Single-step: entire body = one Pi session prompt |
+| `engine: aw` with `harness:` block | Multi-step orchestration mode |
+| `engine: aw` with `harness.steps` | Explicit DAG (parallel, depends, agent assignment) |
+| `engine: aw` without `harness.agents` | All steps use `engine.model` |
+
+## Build & Deployment
+
+### TypeScript Configuration
+
+```jsonc
+// tsconfig.json
+{
+  "compilerOptions": {
+    "target": "es2024",           // Node 24 supports ES2024
+    "module": "es2022",
+    "lib": ["es2024"],
+    "moduleResolution": "bundler",
+    "strict": true,
+    "skipLibCheck": true,
+    "outDir": "dist",
+    "declaration": false
+  }
+}
+```
+
+### Bundle Configuration
+
+```typescript
+// build.ts — esbuild config
+import { build } from "esbuild";
+
+await build({
+  entryPoints: ["src/index.ts"],
+  bundle: true,
+  platform: "node",
+  target: "node24",
+  format: "cjs",                  // .cjs required by gh-aw harness validation
+  outfile: "dist/aw_harness.cjs",
+  external: [],                   // Bundle everything (no runtime npm install)
+  minify: false,                  // Keep readable for debugging in Actions logs
+  sourcemap: "inline",            // Debugging in CI
+});
+```
+
+### Output Location
+
+The bundled `aw_harness.cjs` is copied to `actions/setup/js/aw_harness.cjs` — alongside existing harnesses:
+
+```
+actions/setup/js/
+├── copilot_harness.cjs       # Existing
+├── claude_harness.cjs        # Existing
+├── aw_harness.cjs            # NEW — bundled from aw-harness/
+├── *.cjs                     # Other existing action scripts
+└── *.test.cjs                # Tests
+```
+
+### Testing
+
+Tests use the same Vitest setup as the existing `actions/setup/js/` scripts:
+
+- Unit tests for parser, planner, each extension
+- Integration tests with mock Pi sessions (in-memory session manager)
+- Tests co-located: `aw_harness.test.cjs` or in a `test/` subdirectory
+
+## Project Structure
+
+```
+aw-harness/
+├── package.json                  # deps: pi-coding-agent, pi-agent-core, pi-ai
+├── tsconfig.json                 # target: es2024, module: es2022
+├── build.ts                      # esbuild → dist/aw_harness.cjs
+├── src/
+│   ├── index.ts                  # Entry point: parseWorkflow → buildDAG → run sessions
+│   ├── parser.ts                 # Workflow markdown → steps + config
+│   ├── planner.ts                # DAG construction, topological sort
+│   ├── dag-runner.ts             # Orchestrate sessions (sequential + parallel)
+│   ├── transcript.ts             # Inter-step data flow (save/load/summarize)
+│   ├── context.ts                # Prompt assembly, compaction
+│   └── extensions/               # gh-aw Pi extensions
+│       ├── api-proxy-provider.ts # Register api-proxy as Pi provider
+│       ├── mcp-bridge.ts         # MCP gateway tools → Pi AgentTool
+│       ├── safe-outputs.ts       # Safe-output tools + artifact writing
+│       ├── cost-tracker.ts       # Budget gates via turn_end events
+│       ├── steering.ts           # Time/budget pressure via session.steer()
+│       ├── repair.ts             # Broken session recovery
+│       ├── observability.ts      # JSONL events + OTel spans
+│       └── checkpoint.ts         # Persist/resume run state
+├── test/
+│   ├── parser.test.ts
+│   ├── planner.test.ts
+│   ├── extensions/
+│   │   ├── cost-tracker.test.ts
+│   │   ├── steering.test.ts
+│   │   ├── repair.test.ts
+│   │   └── ...
+│   └── integration/
+│       └── dag-runner.test.ts
+└── dist/
+    └── aw_harness.cjs            # → copied to actions/setup/js/
+```
+
+## Todos
+
+1. **Scaffold project** — Initialize TypeScript project in `aw-harness/`. Configure package.json with Pi SDK deps (`@mariozechner/pi-coding-agent`, `pi-agent-core`, `pi-ai`). Set up tsconfig for ES2024/Node 24. Configure esbuild bundle → `dist/aw_harness.cjs`.
+
+2. **Implement api-proxy provider extension** — Pi extension that registers the api-proxy as a custom provider via `pi.registerProvider()`. Async factory fetches available models at startup. All model requests route through the proxy.
+
+3. **Implement parser** — Read workflow markdown, extract frontmatter, parse `harness:` block, extract steps from heading boundaries and `<!-- harness-step -->` annotations. Fall back to single-step mode.
+
+4. **Implement DAG planner** — Topological sort, parallel group detection, sequential fallback. Validate no cycles, all agent/step references resolve.
+
+5. **Implement MCP bridge extension** — Pi extension that reads MCP gateway config and registers each MCP tool as a Pi `AgentTool` via `pi.registerTool()`.
+
+6. **Implement safe-outputs extension** — Pi extension that registers safe-output tools (create-issue, create-pull-request, add-comment, etc.). Uses `pi.on("agent_end")` to finalize artifact manifest.
+
+7. **Implement DAG runner** — Orchestrates multiple `createAgentSession()` calls. Sequential steps + `Promise.all()` for parallel groups. Passes gh-aw extensions to each session. Manages transcript flow between steps.
+
+8. **Implement transcript manager** — Save step output to disk. Load for downstream steps. Support `summary` mode (use a Pi session to summarize) and `full` mode.
+
+9. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
+
+10. **Implement cost tracker extension** — Pi extension that monitors `turn_end` events for token/cost data. Enforces soft (steer warning) and hard (abort) budget gates.
+
+11. **Implement steering extension** — Pi extension that monitors time/budget and injects user messages via `session.steer()` on `turn_end`.
+
+12. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
+
+13. **Implement checkpoint extension** — Pi extension that persists step completion state on `agent_end`. Resume from checkpoint on `--continue`.
+
+14. **Implement observability extension** — Pi extension that emits JSONL to stderr on agent/tool events. Generates OTel spans using `observability.otlp` config.
+
+15. **Write tests** — Unit tests for parser, planner, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
+
+16. **Write example workflows** — 3 examples: single-step, multi-step sequential, multi-agent parallel with different models.
+
+17. **Add build to Makefile** — Add `make aw-harness` target that runs esbuild and copies `aw_harness.cjs` to `actions/setup/js/`.

From 370d10ce505d907ed0d7c9ab470e65001b1dd8dc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 02:11:27 +0000
Subject: [PATCH 02/14] docs: gh-proxy and cli-proxy always enabled for engine:
 aw (Pi SDK no native MCP)

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/01e7b2ba-9d7a-4706-a21a-f8b576ecb523

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 0207b30aca4..4c34fb739c7 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -12,6 +12,7 @@ The harness is **built on top of the Pi agent ecosystem** (`@mariozechner/pi-cod
 - A Pi SDK application with gh-aw-specific Pi extensions
 - Optimized for execution inside the gh-aw Action container (firewall, api-proxy, MCP gateway)
 - TypeScript compiled for Node.js 24, bundled as a single `.cjs` in `actions/setup/js/`
+- **`gh-proxy` and `cli-proxy` are always enabled** — Pi SDK does not support MCP natively; these are mandatory for the MCP bridge to work and cannot be turned off
 
 ### What this is NOT
 
@@ -153,6 +154,7 @@ export default function safeOutputsExtension(pi: ExtensionAPI) {
 2. **Extensions-first** — Every gh-aw feature is a proper Pi extension using `ExtensionAPI`. Extensions use `pi.registerTool()` for tools, `pi.on()` for events, `pi.registerProvider()` for model routing. This makes them reusable outside gh-aw.
 3. **api-proxy for model resolution** — Pi's `pi-ai` talks to the api-proxy as an OpenAI-compatible provider. Model names (aliases or explicit) pass through — the proxy resolves.
 4. **Optimized for gh-aw container** — Assumes firewall, api-proxy, MCP gateway are running. No redundant auth, no direct LLM API calls, no network configuration.
+5. **`gh-proxy` and `cli-proxy` always on** — The Pi SDK does not support MCP natively. GitHub and other MCP server tools are bridged into Pi via the `mcp-bridge` extension, which requires `gh-proxy` (pre-authenticated `gh` CLI in bash) and `cli-proxy` (MCP servers mounted as CLI tools on `PATH`) to be active. These features are **always enabled** when `engine: aw` is selected and **cannot be disabled**.
 5. **TypeScript → Node 24** — Source is TypeScript, compiled to ES2024, bundled via esbuild to a single `.cjs`. Leverages Node 24 features (native fetch, structuredClone, AbortSignal.any).
 6. **Output in `actions/setup/js/`** — The bundled `aw_harness.cjs` lives alongside `copilot_harness.cjs` and `claude_harness.cjs`. Same deployment mechanism, same runtime contract.
 7. **New opt-in engine** — `engine: aw` is a new choice. Existing engines are untouched.
@@ -187,6 +189,8 @@ This means:
 
 Uses the **existing gh-aw frontmatter** with `engine: aw` and a new optional `harness:` block.
 
+> **Constraint:** `gh-proxy` and `cli-proxy` are always enabled for `engine: aw` (Pi SDK does not support MCP natively). They are shown explicitly below but are enforced by the compiler regardless of whether the author includes them.
+
 ```markdown
 ---
 on:
@@ -203,8 +207,14 @@ permissions:
   issues: read
   pull-requests: read
 
+# gh-proxy and cli-proxy are ALWAYS enabled for engine: aw.
+# Pi SDK does not support MCP natively; the mcp-bridge extension
+# requires both features to bridge gateway tools into Pi AgentTools.
+cli-proxy: true
+
 tools:
   github:
+    mode: gh-proxy               # Always gh-proxy for engine: aw
     toolsets: [issues, pull_requests, code_search]
   bash: [grep, find, wc, git, jq]
 
@@ -342,6 +352,8 @@ export default async function(pi: ExtensionAPI) {
 
 Bridges gh-aw's MCP gateway tools into Pi's tool system as `AgentTool` instances.
 
+> **Note:** The Pi SDK does not support MCP natively. `gh-proxy` (pre-authenticated `gh` CLI in bash) and `cli-proxy` (MCP servers mounted as CLI tools on `PATH`) are **always enabled** when `engine: aw` is selected and **cannot be disabled**. These are mandatory prerequisites for the MCP bridge to function.
+
 ```typescript
 export default function(pi: ExtensionAPI) {
   const gatewayConfig = loadMCPGatewayConfig();
@@ -591,6 +603,8 @@ async function main() {
 | `engine: aw` with `harness:` block | Multi-step orchestration mode |
 | `engine: aw` with `harness.steps` | Explicit DAG (parallel, depends, agent assignment) |
 | `engine: aw` without `harness.agents` | All steps use `engine.model` |
+| `engine: aw` + `cli-proxy: false` | **Ignored** — `cli-proxy` is always on for `engine: aw` |
+| `engine: aw` + `tools.github.mode: remote` | **Overridden to `gh-proxy`** — Pi SDK requires `gh-proxy`; `remote` mode is not supported |
 
 ## Build & Deployment
 

From 8f6af71fe5918e885a5579fb71c062d7286a2264 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 02:35:48 +0000
Subject: [PATCH 03/14] docs(specs): rewrite aw-harness.md in W3C specification
 format

- Add W3C-style title block, Abstract, Status of This Document
- Add RFC 2119 Conformance section with keyword table
- Add formal Terminology and Definitions section for all key terms
- Restructure Architecture as normative section with numbered Design Principles
- Add Harness Invocation Contract (entry point, env vars, exit codes, streams)
- Restructure Workflow Definition with formal sub-schemas for each harness.* key
- Add normative Overrides and Fixed Settings table (Section 6.2)
- Add formal DAG Execution Model with normative algorithm (Section 7)
- Restructure all 8 extensions with Requirements + non-normative examples
- Move Model Resolution to its own normative section (Section 9)
- Move Build/Deployment and Implementation Plan to informative section (Section 10)
- Preserve full backwards-compatibility table including gh-proxy override rows
- Add Security Considerations and Privacy Considerations sections
- Add Normative and Informative References section
- Fix duplicate Design Principle number (two items numbered 5)
- Fix env var inconsistency: use AWF_API_PROXY_TOKEN consistently throughout
- Make gh-proxy/cli-proxy always-on constraint a MUST NOT normative requirement
- Frame all code examples as non-normative with >[!NOTE] callouts

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 1433 +++++++++++++++++++++++++++----------------
 1 file changed, 899 insertions(+), 534 deletions(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 4c34fb739c7..7aa9122d204 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -1,100 +1,156 @@
-# AW Harness Design Plan
+# AW Harness Specification
 
-## Problem Statement
+---
 
-Design a **replacement for `copilot_harness.cjs`** — the Node.js wrapper that gh-aw uses to launch coding agent CLIs inside the GitHub Actions container. Today's harness is a thin retry loop around a single CLI invocation. The new harness — called **"aw"** — adds **multi-step orchestration**, **multi-agent**, **multi-model**, **context engineering**, **cost tracking**, and **observability**.
+**Title:** AW Harness — Multi-Step Agentic Workflow Execution Engine
 
-The harness is **built on top of the Pi agent ecosystem** (`@mariozechner/pi-coding-agent`, `pi-agent-core`, `pi-ai`). All gh-aw-specific capabilities — safe outputs, MCP gateway bridging, cost tracking, steering, observability — are implemented as **Pi extensions** using Pi's native extensibility mechanism (`ExtensionAPI`). This means the extensions are reusable with standalone Pi and the OpenClaw ecosystem.
+**Status:** Unofficial Draft
 
-### What this IS
+**Date:** 2025-07-14
 
-- A new `engine: aw` option (registered via `harness: aw_harness.cjs` in frontmatter)
-- A Pi SDK application with gh-aw-specific Pi extensions
-- Optimized for execution inside the gh-aw Action container (firewall, api-proxy, MCP gateway)
-- TypeScript compiled for Node.js 24, bundled as a single `.cjs` in `actions/setup/js/`
-- **`gh-proxy` and `cli-proxy` are always enabled** — Pi SDK does not support MCP natively; these are mandatory for the MCP bridge to work and cannot be turned off
+**Editor:** GitHub gh-aw Team
 
-### What this is NOT
+---
 
-- NOT a reimplementation of gh-aw (compilation, triggers, safe-outputs post-processing, threat detection stay as-is)
-- NOT a CLI spawner — no `copilot` / `claude` / `codex` CLI processes are spawned
-- NOT interactive (no TUI — pure headless CI execution via Pi SDK mode)
-- NOT a replacement for existing engines — `engine: copilot`, `engine: claude`, `engine: codex` continue to work via their current harnesses
+## Abstract
 
-## Model Resolution: api-proxy Integration
+This document specifies the **AW Harness** (`aw_harness.cjs`), a Node.js execution engine for the `engine: aw` mode of GitHub Agentic Workflows (gh-aw). The harness provides multi-step orchestration, multi-agent coordination, context engineering, cost tracking, and observability, built on top of the Pi agent SDK ecosystem. All gh-aw-specific capabilities are implemented as Pi extensions using Pi's native `ExtensionAPI` extensibility mechanism.
 
-The harness does **not** do provider inference or model routing. That is handled by the **api-proxy** (`gh-aw-firewall/containers/api-proxy/model-resolver.js`), which already runs in the container.
+## Status of This Document
 
-### How model resolution works
+This is an internal design specification for the GitHub gh-aw project. It is not a W3C standard, nor is it on the W3C standards track. The document describes the intended architecture, contracts, and implementation plan for `aw_harness.cjs`. Feedback and corrections **SHOULD** be submitted via the project's standard pull request process.
 
-The api-proxy accepts model names (aliases or explicit `provider/model` refs) and resolves them against available models per provider:
+---
 
-```
-Harness (Pi SDK) → api-proxy → LLM provider
-  model: "sonnet"           resolves to copilot/claude-sonnet-4.6
-  model: "gpt-5-codex"      resolves to copilot/gpt-5.3-codex
-  model: "copilot/gpt-4.1"  passes through as-is
-```
+## Table of Contents
+
+1. [Introduction](#1-introduction)
+2. [Conformance](#2-conformance)
+3. [Terminology and Definitions](#3-terminology-and-definitions)
+4. [Architecture](#4-architecture)
+5. [Harness Invocation Contract](#5-harness-invocation-contract)
+6. [Workflow Definition](#6-workflow-definition)
+7. [DAG Execution Model](#7-dag-execution-model)
+8. [Extensions](#8-extensions)
+9. [Model Resolution](#9-model-resolution)
+10. [Build and Deployment](#10-build-and-deployment)
+11. [Security Considerations](#11-security-considerations)
+12. [Privacy Considerations](#12-privacy-considerations)
+13. [References](#13-references)
 
-**Alias config** (via `AWF_MODEL_ALIASES` env var):
+---
 
-```json
-{
-  "models": {
-    "sonnet": ["copilot/*sonnet*", "anthropic/*sonnet*"],
-    "gpt-5-codex": ["copilot/gpt-5*-codex", "openai/gpt-5*-codex"],
-    "": ["sonnet", "gpt-5*-codex"]
-  }
-}
-```
+## 1. Introduction
 
-**Key properties**:
+*(This section is non-normative.)*
 
-- `providerid/modelid` syntax with `*` wildcards
-- Recursive alias resolution with loop detection
-- Case-insensitive matching
-- Semver sorting (highest version first among candidates)
-- Default policy via `""` key (used when no model is specified)
+The existing gh-aw harnesses (`copilot_harness.cjs`, `claude_harness.cjs`) are thin retry loops around a single CLI invocation. As workflow complexity grows, authors need multi-step orchestration, parallel agent execution, per-step model selection, budget management, and structured observability — none of which the current harnesses provide.
 
-### What this means for the harness
+The AW Harness introduces `engine: aw` as a new opt-in execution engine. It does not replace existing engines; `engine: copilot`, `engine: claude`, and `engine: codex` continue to operate unchanged via their current harnesses. The AW Harness is a Pi SDK application: it creates one `AgentSession` per workflow step, loads a fixed set of gh-aw Pi extensions into each session, and orchestrates sessions according to a DAG derived from the workflow's `harness:` frontmatter block and Markdown heading structure.
 
-- Pi SDK's `pi-ai` is configured with the api-proxy as an **OpenAI-compatible custom provider** (using `pi.registerProvider()` or `models.json` configuration)
-- The harness just passes model names through — aliases like `"sonnet"` work out of the box
-- No `provider:` field needed in frontmatter — the proxy handles routing
-- Per-step/per-agent model selection is just setting a different model name string
-- The harness inherits the api-proxy's full model catalog without maintaining its own
+The harness is designed exclusively for the gh-aw Actions container environment. It assumes the firewall, api-proxy, and MCP gateway are already running. It performs no direct LLM API calls and requires no additional authentication setup beyond what the container already provides.
 
-## Architecture: Pi SDK + Extensions
+### 1.1 Scope
 
-### Core Principle: Everything is a Pi Extension
+This specification covers:
 
-The harness is a thin orchestration layer that creates Pi `AgentSession` instances (via `createAgentSession()` from the Pi SDK). All gh-aw-specific capabilities are implemented as **Pi extensions** using the `ExtensionAPI` interface:
+- The entry-point invocation contract for `aw_harness.cjs`.
+- The frontmatter schema for `engine: aw` workflows.
+- The step-extraction and DAG-construction algorithms.
+- The normative requirements for each of the eight gh-aw Pi extensions.
+- The model resolution contract via the api-proxy.
+- The build and deployment configuration.
 
-```typescript
-// Each gh-aw capability is a Pi extension
-import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
+This specification does not cover:
 
-export default function safeOutputsExtension(pi: ExtensionAPI) {
-  // Register safe-output tools the LLM can call
-  pi.registerTool({ name: "create_issue", ... });
-  pi.registerTool({ name: "create_pull_request", ... });
-  pi.registerTool({ name: "add_comment", ... });
+- The compilation of workflow Markdown to GitHub Actions YAML (handled by `gh-aw` proper).
+- Safe-outputs post-processing and threat detection (handled by post-agent jobs, unchanged).
+- The Pi SDK internals (`pi-agent-core`, `pi-ai`).
+- The api-proxy internals (`model-resolver.js`).
 
-  // Hook into agent events for observability
-  pi.on("tool_call", async (event, ctx) => { ... });
-  pi.on("agent_end", async (event, ctx) => { ... });
-}
-```
+### 1.2 Background and Motivation
+
+The Pi agent ecosystem (`@mariozechner/pi-coding-agent`, `pi-agent-core`, `pi-ai`) provides a composable, extension-based SDK for building agentic applications. By implementing all gh-aw-specific capabilities as Pi extensions, those extensions become:
+
+- **Reusable** — They work with standalone Pi CLI and any Pi SDK application.
+- **Composable** — Users can add their own extensions alongside the provided set.
+- **Ecosystem-compatible** — They can be published as Pi packages.
+
+---
+
+## 2. Conformance
+
+The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** in this document are to be interpreted as described in [RFC 2119].
+
+| Keyword | Meaning |
+|---------|---------|
+| **MUST** / **REQUIRED** / **SHALL** | Absolute requirement |
+| **MUST NOT** / **SHALL NOT** | Absolute prohibition |
+| **SHOULD** / **RECOMMENDED** | Strong recommendation; deviation requires documented justification |
+| **SHOULD NOT** | Strong recommendation against; deviation requires documented justification |
+| **MAY** / **OPTIONAL** | Permitted but not required |
+
+A **conforming implementation** is one that satisfies all **MUST** and **MUST NOT** requirements in this specification.
+
+---
+
+## 3. Terminology and Definitions
+
+**AW Harness**
+: The execution engine implemented in `aw_harness.cjs`, invoked when a workflow declares `engine: aw`. It is responsible for parsing the workflow, constructing the DAG, and orchestrating one `AgentSession` per step.
+
+**AgentSession**
+: A Pi SDK session object, obtained via `createAgentSession()`, that manages a single agent's message loop, tool calls, and event stream.
+
+**api-proxy**
+: A sidecar process (`gh-aw-firewall/containers/api-proxy/model-resolver.js`) running in the gh-aw container. It accepts OpenAI-compatible API requests, resolves model aliases, and routes calls to the appropriate LLM provider. The harness communicates with it at `http://localhost:8080/v1` by default.
+
+**cli-proxy**
+: A feature that mounts MCP servers as CLI tools on `PATH`, making them callable as ordinary shell commands within agent sessions.
+
+**DAG (Directed Acyclic Graph)**
+: The execution graph derived from the workflow's `harness.steps` declarations and implicit document-order dependencies. Nodes are steps; directed edges encode `depends` relationships. Cycles are prohibited.
+
+**ExtensionAPI**
+: The Pi SDK interface (`ExtensionAPI` from `@mariozechner/pi-coding-agent`) that a Pi extension receives as its sole argument. Provides `pi.registerTool()`, `pi.registerProvider()`, and `pi.on()`.
+
+**gh-proxy**
+: A feature that provides a pre-authenticated `gh` CLI binary in the agent's bash environment, enabling direct GitHub API access without separate token management.
+
+**harness step**
+: A unit of work within an `engine: aw` workflow. Each step is assigned one `AgentSession`, a prompt derived from the corresponding Markdown section, and optionally a named agent definition.
+
+**MCP Gateway**
+: The gh-aw MCP gateway process that exposes GitHub tools and custom MCP server tools. It runs independently of the harness in the same container.
+
+**MCP bridge**
+: The `mcp-bridge` Pi extension (Extension 2) that translates MCP gateway tool definitions into Pi `AgentTool` instances, making them available to agent sessions without native MCP support in the Pi SDK.
+
+**model alias**
+: A short name (e.g., `"sonnet"`, `"gpt-5-codex"`) resolved by the api-proxy to a fully-qualified `provider/model` string. The harness passes aliases through without resolution.
+
+**Pi extension**
+: A TypeScript module that exports a default function with signature `(pi: ExtensionAPI) => void | Promise<void>`. Loaded into an `AgentSession` to register tools, subscribe to events, or register providers.
+
+**safe output**
+: A deferred GitHub action (create issue, create pull request, add comment, etc.) expressed as an artifact file written during agent execution and processed by the post-agent job.
+
+**step annotation**
+: An HTML comment of the form `<!-- harness-step: <name> -->` embedded in a Markdown section to associate that section with a named entry in `harness.steps`.
+
+**transcript**
+: The complete message history of a completed `AgentSession`, optionally summarized, passed as context to downstream steps.
+
+**workflow document**
+: A Markdown file with YAML frontmatter that declares an `engine: aw` workflow. The frontmatter **MUST** conform to the schema in [Section 6](#6-workflow-definition).
+
+---
 
-**Why Pi extensions instead of custom code?**
+## 4. Architecture
 
-1. **Reusable** — Extensions work with standalone Pi CLI, OpenClaw, and any Pi SDK app
-2. **Composable** — Users can add their own extensions alongside gh-aw ones
-3. **Standard lifecycle** — Pi handles extension loading, event dispatch, tool registration
-4. **Testable** — Extensions can be tested independently with Pi's SDK test harness
-5. **Ecosystem-compatible** — Can be published as Pi packages (`pi install npm:@github/aw-extensions`)
+### 4.1 Stack Overview
 
-### How It Fits in the gh-aw Stack
+The AW Harness is the topmost layer within the gh-aw container. The following ASCII diagram illustrates the component relationships.
 
 ```
 ┌─────────────────────────────────────────────────────────────┐
@@ -148,506 +204,736 @@ export default function safeOutputsExtension(pi: ExtensionAPI) {
 └─────────────────────────────────────────────────────────────┘
 ```
 
-### Design Principles
-
-1. **Built on Pi ecosystem** — Uses Pi SDK (`createAgentSession()`, `Agent`, `AgentTool`) as the runtime. All gh-aw capabilities are Pi extensions, not custom plumbing.
-2. **Extensions-first** — Every gh-aw feature is a proper Pi extension using `ExtensionAPI`. Extensions use `pi.registerTool()` for tools, `pi.on()` for events, `pi.registerProvider()` for model routing. This makes them reusable outside gh-aw.
-3. **api-proxy for model resolution** — Pi's `pi-ai` talks to the api-proxy as an OpenAI-compatible provider. Model names (aliases or explicit) pass through — the proxy resolves.
-4. **Optimized for gh-aw container** — Assumes firewall, api-proxy, MCP gateway are running. No redundant auth, no direct LLM API calls, no network configuration.
-5. **`gh-proxy` and `cli-proxy` always on** — The Pi SDK does not support MCP natively. GitHub and other MCP server tools are bridged into Pi via the `mcp-bridge` extension, which requires `gh-proxy` (pre-authenticated `gh` CLI in bash) and `cli-proxy` (MCP servers mounted as CLI tools on `PATH`) to be active. These features are **always enabled** when `engine: aw` is selected and **cannot be disabled**.
-5. **TypeScript → Node 24** — Source is TypeScript, compiled to ES2024, bundled via esbuild to a single `.cjs`. Leverages Node 24 features (native fetch, structuredClone, AbortSignal.any).
-6. **Output in `actions/setup/js/`** — The bundled `aw_harness.cjs` lives alongside `copilot_harness.cjs` and `claude_harness.cjs`. Same deployment mechanism, same runtime contract.
-7. **New opt-in engine** — `engine: aw` is a new choice. Existing engines are untouched.
-8. **Markdown-native steps** — `## Heading` = step boundary. HTML comments carry metadata.
-9. **Observable** — JSONL event stream + OTel spans, all via the observability extension.
-
-## Pi Provider Configuration for api-proxy
-
-The harness registers the api-proxy as a custom Pi provider at startup, using Pi's `registerProvider()` mechanism:
-
-```typescript
-export default async function apiProxyProvider(pi: ExtensionAPI) {
-  const proxyUrl = process.env.AWF_API_PROXY_URL || "http://localhost:8080/v1";
-
-  pi.registerProvider("aw-proxy", {
-    baseUrl: proxyUrl,
-    apiKey: "AW_PROXY_API_KEY",   // env var name — Pi resolves it
-    api: "openai-completions",     // api-proxy speaks OpenAI protocol
-    models: await fetchAvailableModels(proxyUrl),
-  });
-}
-```
-
-This means:
-
-- Pi treats the api-proxy as a standard OpenAI-compatible provider
-- Model aliases (`sonnet`, `gpt-5-codex`) are sent as-is to the proxy
-- The proxy resolves aliases, routes to the actual provider, returns the response
-- Pi's token counting, cost tracking, and streaming all work transparently
+### 4.2 Design Principles
 
-## Workflow Definition Format
+1. **Built on Pi ecosystem.** A conforming implementation **MUST** use the Pi SDK (`createAgentSession()`, `Agent`, `AgentTool`) as the agent runtime. All gh-aw capabilities **MUST** be Pi extensions, not custom plumbing.
 
-Uses the **existing gh-aw frontmatter** with `engine: aw` and a new optional `harness:` block.
+2. **Extensions-first.** Every gh-aw feature **MUST** be implemented as a proper Pi extension using `ExtensionAPI`. Extensions **MUST** use `pi.registerTool()` for tools, `pi.on()` for events, and `pi.registerProvider()` for model routing, making them reusable outside gh-aw.
 
-> **Constraint:** `gh-proxy` and `cli-proxy` are always enabled for `engine: aw` (Pi SDK does not support MCP natively). They are shown explicitly below but are enforced by the compiler regardless of whether the author includes them.
+3. **api-proxy for model resolution.** Pi's `pi-ai` **MUST** be configured to communicate with the api-proxy as an OpenAI-compatible provider. Model names (aliases or explicit) **MUST** be passed through to the proxy without local resolution.
 
-```markdown
----
-on:
-  schedule:
-    - cron: "0 9 * * 1-5"
-
-engine:
-  id: aw
-  model: sonnet                  # Model alias — api-proxy resolves
-  harness: aw_harness.cjs
-
-permissions:
-  contents: read
-  issues: read
-  pull-requests: read
-
-# gh-proxy and cli-proxy are ALWAYS enabled for engine: aw.
-# Pi SDK does not support MCP natively; the mcp-bridge extension
-# requires both features to bridge gateway tools into Pi AgentTools.
-cli-proxy: true
-
-tools:
-  github:
-    mode: gh-proxy               # Always gh-proxy for engine: aw
-    toolsets: [issues, pull_requests, code_search]
-  bash: [grep, find, wc, git, jq]
-
-safe-outputs:
-  create-issue:
-    title-prefix: "[review] "
-    labels: [automated-review]
-    max-count: 3
-
-timeout-minutes: 30
-
-observability:
-  otlp:
-    endpoint: ${{ secrets.OTLP_ENDPOINT }}
-    headers:
-      Authorization: ${{ secrets.OTLP_TOKEN }}
-
-# ── Harness config (optional) ───────────────────────────────
-harness:
-  budget:
-    max-cost-usd: 5.00
-    warn-at-percent: 80
-
-  context:
-    compaction: summarize
-    compaction-threshold: 0.75
-    transcript-mode: summary
-
-  agents:
-    reviewer:
-      model: sonnet
-      system: |
-        You are a senior code reviewer. Focus on correctness, security,
-        and maintainability.
-    scanner:
-      model: gpt-5-codex
-      system: |
-        You are a security specialist. Focus on vulnerabilities,
-        injection risks, and credential exposure.
-    synthesizer:
-      model: copilot/gpt-4.1
-      system: |
-        You synthesize multiple review perspectives into a single,
-        prioritized action list.
-
-  steps:
-    parallel-review:
-      agents: [reviewer, scanner]
-      parallel: true
-    synthesize:
-      agent: synthesizer
-      depends: [parallel-review]
-
-  steering:
-    time-warning-minutes: 5
-    time-critical-minutes: 2
-    budget-warn-percent: 75
-    budget-critical-percent: 90
-
-  checkpoint: true
----
+4. **Optimized for gh-aw container.** The harness **MUST** assume that the firewall, api-proxy, and MCP gateway are already running. It **MUST NOT** perform direct LLM API calls, redundant authentication, or network configuration.
 
-## Daily Code Review
+5. **`gh-proxy` and `cli-proxy` always on.** The Pi SDK does not support MCP natively. GitHub and other MCP server tools are bridged into Pi via the `mcp-bridge` extension. This bridge **REQUIRES** both `gh-proxy` (pre-authenticated `gh` CLI in bash) and `cli-proxy` (MCP servers mounted as CLI tools on `PATH`). A conforming implementation **MUST** enable both `gh-proxy` and `cli-proxy` when `engine: aw` is selected. An implementation **MUST NOT** allow these features to be disabled for `engine: aw`, regardless of the values specified in the workflow frontmatter (see [Section 6.2](#62-overrides-and-fixed-settings)).
 
-Review all changes pushed to the default branch in the last 24 hours.
+6. **TypeScript → Node 24.** Source **MUST** be TypeScript, compiled to ES2024, bundled via esbuild to a single `.cjs`. Leverages Node 24 features (native fetch, `structuredClone`, `AbortSignal.any`).
 
-### Gather Changes
-<!-- harness-step: gather -->
-Use `git log --since="24 hours ago"` and `git diff` to collect all
-recent changes. Summarize the scope.
+7. **Output in `actions/setup/js/`.** The bundled `aw_harness.cjs` **MUST** be placed in `actions/setup/js/aw_harness.cjs`, alongside `copilot_harness.cjs` and `claude_harness.cjs`. The same deployment mechanism and runtime contract apply.
 
-### Parallel Review
-<!-- harness-step: parallel-review -->
-Each reviewer examines the changes independently.
+8. **New opt-in engine.** `engine: aw` is an independent opt-in. Existing engines **MUST** be untouched.
 
-### Synthesize
-<!-- harness-step: synthesize -->
-Read outputs from both reviewers. Produce a prioritized findings list.
+9. **Markdown-native steps.** `## Heading` or `### Heading` elements **MUST** be recognized as step boundaries. HTML comments carry step metadata.
 
-### Report
-Create a GitHub issue with the synthesized review.
-```
+10. **Observable.** All implementations **MUST** emit a JSONL event stream to stderr and **SHOULD** generate OTel spans when an OTLP endpoint is configured.
 
-### How Steps Work
-
-**Step extraction**: Each `## Heading` or `### Heading` is a potential step. Linked to `harness.steps` via `<!-- harness-step: name -->` HTML comments.
+---
 
-**Implicit behavior**:
+## 5. Harness Invocation Contract
 
-- No `<!-- harness-step -->` → sequential by default (document order)
-- With annotation → follows `harness.steps` config (parallel, depends, agent)
-- No `harness:` block → entire body = single Pi session prompt
+### 5.1 Entry Point
 
-**Step execution**:
+The AW Harness **MUST** be invocable as a Node.js CommonJS module from the command line. A conforming invocation has the form:
 
 ```
-For each step (respecting DAG order):
-  1. Build prompt = step markdown + upstream transcripts + system prompt
-  2. Create Pi AgentSession with gh-aw extensions:
-     - api-proxy provider registered
-     - MCP bridge tools registered
-     - Safe-output tools registered
-     - Steering, repair, cost, observability extensions active
-  3. session.prompt() → Pi agent loop runs
-  4. Extensions handle events (cost tracking, steering, observability)
-  5. Capture transcript for downstream steps
-  6. Budget gate check, checkpoint state
+node aw_harness.cjs <workflow-path>
 ```
 
-## gh-aw Pi Extensions
+where `<workflow-path>` is the absolute or relative path to the workflow Markdown file.
 
-All gh-aw-specific behavior is packaged as Pi extensions. Each extension is a standalone TypeScript module that exports a default function receiving `ExtensionAPI`.
+### 5.2 Environment Variables
 
-### Extension 1: api-proxy Provider
+A conforming implementation **MUST** read the following environment variables:
 
-Registers the gh-aw api-proxy as a custom Pi provider.
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `AWF_API_PROXY_URL` | Base URL of the api-proxy OpenAI-compatible endpoint | `http://localhost:8080/v1` |
+| `AWF_API_PROXY_TOKEN` | Bearer token for api-proxy authentication | *(required; no default)* |
+| `AWF_MODEL_ALIASES` | JSON string containing model alias configuration | *(empty; aliases resolved by proxy)* |
 
-```typescript
-export default async function(pi: ExtensionAPI) {
-  const proxyUrl = process.env.AWF_API_PROXY_URL || "http://localhost:8080/v1";
-  const models = await fetchModelsFromProxy(proxyUrl);
+A conforming implementation **SHOULD** read additional standard GitHub Actions environment variables (`GITHUB_REPOSITORY`, `GITHUB_RUN_ID`, etc.) for use in observability spans and checkpoint keys.
 
-  pi.registerProvider("aw-proxy", {
-    baseUrl: proxyUrl,
-    apiKey: "AWF_API_PROXY_TOKEN",
-    api: "openai-completions",
-    models,
-  });
-}
-```
+### 5.3 Exit Codes
 
-**Why an extension**: Pi's `registerProvider()` is designed exactly for this. Async factory support means we can fetch the model list at startup before any session begins.
+| Code | Meaning |
+|------|---------|
+| `0` | All steps completed successfully |
+| `1` | One or more steps failed (non-recoverable error) |
+| `2` | Invocation error (missing workflow path, invalid frontmatter) |
 
-### Extension 2: MCP Gateway Bridge
+A conforming implementation **MUST** exit with code `0` if and only if all DAG steps complete without error. It **MUST** exit with a non-zero code on any unrecovered failure.
 
-Bridges gh-aw's MCP gateway tools into Pi's tool system as `AgentTool` instances.
+### 5.4 Standard Streams
 
-> **Note:** The Pi SDK does not support MCP natively. `gh-proxy` (pre-authenticated `gh` CLI in bash) and `cli-proxy` (MCP servers mounted as CLI tools on `PATH`) are **always enabled** when `engine: aw` is selected and **cannot be disabled**. These are mandatory prerequisites for the MCP bridge to function.
+- **stdout**: Reserved for structured output (e.g., JSON summaries). A conforming implementation **SHOULD NOT** write diagnostic messages to stdout.
+- **stderr**: All diagnostic messages, JSONL event stream, and debug output **MUST** be written to stderr.
 
-```typescript
-export default function(pi: ExtensionAPI) {
-  const gatewayConfig = loadMCPGatewayConfig();
-
-  for (const [serverName, tools] of Object.entries(gatewayConfig)) {
-    for (const tool of tools) {
-      pi.registerTool({
-        name: `${serverName}_${tool.name}`,
-        description: tool.description,
-        parameters: tool.inputSchema,
-        async execute(toolCallId, params, signal) {
-          return await callMCPGateway(serverName, tool.name, params);
-        },
-      });
-    }
-  }
-}
-```
-
-**Why an extension**: Pi's `pi.registerTool()` is the standard way to add tools. MCP gateway tools become first-class Pi tools — visible in sessions, tracked in events, subject to `beforeToolCall`/`afterToolCall` hooks.
-
-### Extension 3: Safe Outputs
-
-Registers safe-output tools (create-issue, create-pull-request, add-comment, etc.) and writes artifact files in gh-aw's expected format.
-
-```typescript
-export default function(pi: ExtensionAPI) {
-  const config = loadSafeOutputsConfig();
-
-  pi.registerTool({
-    name: "create_issue",
-    description: "Create a GitHub issue (safe output)",
-    parameters: Type.Object({
-      title: Type.String(),
-      body: Type.String(),
-      labels: Type.Optional(Type.Array(Type.String())),
-    }),
-    async execute(toolCallId, params, signal) {
-      const artifact = buildSafeOutputArtifact("create-issue", params, config);
-      await writeSafeOutputArtifact(artifact);
-      return { content: [{ type: "text", text: `Issue queued: ${params.title}` }] };
-    },
-  });
-
-  // ... register other safe-output tools
-
-  pi.on("agent_end", async (event, ctx) => {
-    await finalizeSafeOutputManifest();
-  });
-}
-```
-
-**Why an extension**: Tool registration + lifecycle hooks. The `agent_end` event is the right place to finalize artifacts.
-
-### Extension 4: Cost Tracker
-
-Monitors token usage and cost via Pi's event stream. Enforces budget gates.
+---
 
-```typescript
-export default function(pi: ExtensionAPI) {
-  const budget = loadBudgetConfig();
-  let totalCost = 0;
+## 6. Workflow Definition
+
+### 6.1 Frontmatter Schema
+
+An `engine: aw` workflow document **MUST** include a YAML frontmatter block conforming to the existing gh-aw frontmatter schema, extended with the optional `harness:` key described below.
+
+> [!NOTE] Non-normative example.
+>
+> The following is a complete example of an `engine: aw` workflow document illustrating all supported frontmatter keys.
+>
+> ```markdown
+> ---
+> on:
+>   schedule:
+>     - cron: "0 9 * * 1-5"
+>
+> engine:
+>   id: aw
+>   model: sonnet                  # Model alias — api-proxy resolves
+>   harness: aw_harness.cjs
+>
+> permissions:
+>   contents: read
+>   issues: read
+>   pull-requests: read
+>
+> # gh-proxy and cli-proxy are ALWAYS enabled for engine: aw.
+> # Pi SDK does not support MCP natively; the mcp-bridge extension
+> # requires both features to bridge gateway tools into Pi AgentTools.
+> cli-proxy: true
+>
+> tools:
+>   github:
+>     mode: gh-proxy               # Always gh-proxy for engine: aw
+>     toolsets: [issues, pull_requests, code_search]
+>   bash: [grep, find, wc, git, jq]
+>
+> safe-outputs:
+>   create-issue:
+>     title-prefix: "[review] "
+>     labels: [automated-review]
+>     max-count: 3
+>
+> timeout-minutes: 30
+>
+> observability:
+>   otlp:
+>     endpoint: ${{ secrets.OTLP_ENDPOINT }}
+>     headers:
+>       Authorization: ${{ secrets.OTLP_TOKEN }}
+>
+> # ── Harness config (optional) ───────────────────────────────
+> harness:
+>   budget:
+>     max-cost-usd: 5.00
+>     warn-at-percent: 80
+>
+>   context:
+>     compaction: summarize
+>     compaction-threshold: 0.75
+>     transcript-mode: summary
+>
+>   agents:
+>     reviewer:
+>       model: sonnet
+>       system: |
+>         You are a senior code reviewer. Focus on correctness, security,
+>         and maintainability.
+>     scanner:
+>       model: gpt-5-codex
+>       system: |
+>         You are a security specialist. Focus on vulnerabilities,
+>         injection risks, and credential exposure.
+>     synthesizer:
+>       model: copilot/gpt-4.1
+>       system: |
+>         You synthesize multiple review perspectives into a single,
+>         prioritized action list.
+>
+>   steps:
+>     parallel-review:
+>       agents: [reviewer, scanner]
+>       parallel: true
+>     synthesize:
+>       agent: synthesizer
+>       depends: [parallel-review]
+>
+>   steering:
+>     time-warning-minutes: 5
+>     time-critical-minutes: 2
+>     budget-warn-percent: 75
+>     budget-critical-percent: 90
+>
+>   checkpoint: true
+> ---
+>
+> ## Daily Code Review
+>
+> Review all changes pushed to the default branch in the last 24 hours.
+>
+> ### Gather Changes
+> <!-- harness-step: gather -->
+> Use `git log --since="24 hours ago"` and `git diff` to collect all
+> recent changes. Summarize the scope.
+>
+> ### Parallel Review
+> <!-- harness-step: parallel-review -->
+> Each reviewer examines the changes independently.
+>
+> ### Synthesize
+> <!-- harness-step: synthesize -->
+> Read outputs from both reviewers. Produce a prioritized findings list.
+>
+> ### Report
+> Create a GitHub issue with the synthesized review.
+> ```
+
+#### 6.1.1 `harness.budget`
+
+The `harness.budget` key is **OPTIONAL**. When present, it **MUST** contain:
+
+- `max-cost-usd` (number): Maximum total cost in USD for the run. The cost-tracker extension **MUST** abort the current session if this limit is exceeded.
+- `warn-at-percent` (number, 0–100): Percentage of `max-cost-usd` at which a steering warning **MUST** be injected.
+
+#### 6.1.2 `harness.context`
+
+The `harness.context` key is **OPTIONAL**. When present, it **MAY** contain:
+
+- `compaction` (string): One of `none`, `sliding-window`, or `summarize`. Default: `none`.
+- `compaction-threshold` (number, 0–1): Context fill fraction at which compaction triggers. Default: `0.75`.
+- `transcript-mode` (string): One of `full` or `summary`. Controls how upstream step transcripts are included in downstream prompts. Default: `full`.
+
+#### 6.1.3 `harness.agents`
+
+The `harness.agents` key is **OPTIONAL**. Each entry defines a named agent with:
+
+- `model` (string, **REQUIRED**): Model alias or fully-qualified `provider/model` string.
+- `system` (string, **OPTIONAL**): System prompt override for this agent.
+
+#### 6.1.4 `harness.steps`
+
+The `harness.steps` key is **OPTIONAL**. Each entry defines a named step with:
+
+- `agent` (string, **OPTIONAL**): Name of an agent defined in `harness.agents`. Mutually exclusive with `agents`.
+- `agents` (array of strings, **OPTIONAL**): Names of agents to run in parallel for this step. Mutually exclusive with `agent`.
+- `parallel` (boolean, **OPTIONAL**): When `true` and `agents` is provided, all listed agents execute in parallel. Default: `false`.
+- `depends` (array of strings, **OPTIONAL**): Names of steps that **MUST** complete before this step begins.
+
+#### 6.1.5 `harness.steering`
+
+The `harness.steering` key is **OPTIONAL**. When present, it **MAY** contain:
+
+- `time-warning-minutes` (number): Minutes before timeout at which a warning **SHOULD** be injected. Default: `5`.
+- `time-critical-minutes` (number): Minutes before timeout at which a critical message **MUST** be injected. Default: `2`.
+- `budget-warn-percent` (number): Budget percentage at which a warning **SHOULD** be injected. Default: `75`.
+- `budget-critical-percent` (number): Budget percentage at which the session **MUST** be aborted. Default: `90`.
+
+#### 6.1.6 `harness.checkpoint`
+
+The `harness.checkpoint` key is **OPTIONAL**. When set to `true`, the checkpoint extension **MUST** persist step state on `agent_end`.
+
+### 6.2 Overrides and Fixed Settings
+
+A conforming implementation **MUST** apply the following overrides regardless of values specified in the workflow frontmatter:
+
+| Setting | Enforced value | Reason |
+|---------|----------------|--------|
+| `cli-proxy` | `true` | Required for MCP bridge functionality |
+| `tools.github.mode` | `gh-proxy` | Pi SDK requires `gh-proxy`; `remote` mode is not supported |
+
+A conforming implementation **MUST NOT** honor `cli-proxy: false` or `tools.github.mode: remote` when `engine: aw` is active. These settings **MUST** be silently overridden.
+
+### 6.3 Step Extraction Algorithm
+
+A conforming implementation **MUST** extract steps from the workflow document body using the following algorithm:
+
+1. Split the document body on ATX heading boundaries (`##` or `###` level).
+2. For each section, scan for an HTML comment matching `<!-- harness-step: <name> -->`. If found, record `name` as the step annotation.
+3. If a step annotation matches a key in `harness.steps`, apply the corresponding step configuration (agent, parallel, depends).
+4. Steps without a `<!-- harness-step -->` annotation are treated as sequential steps in document order.
+5. If no `harness:` block is present in the frontmatter, the entire document body **MUST** be treated as a single step with no explicit agent or dependency.
 
-  pi.on("turn_end", async (event, ctx) => {
-    totalCost += extractCostFromTurn(event);
+---
 
-    const percent = (totalCost / budget.maxCostUsd) * 100;
-    if (percent >= budget.budgetCriticalPercent) {
-      ctx.agent.abort();
-    } else if (percent >= budget.budgetWarnPercent) {
-      ctx.agent.steer({
-        role: "user",
-        content: `⚠️ Budget: ${percent.toFixed(0)}% used. Be concise.`,
-        timestamp: Date.now(),
-      });
-    }
-  });
+## 7. DAG Execution Model
+
+### 7.1 DAG Construction
+
+A conforming implementation **MUST** construct a DAG from the extracted steps as follows:
+
+1. Create one node per extracted step.
+2. For each step with a `depends` list, add a directed edge from each named dependency to that step.
+3. Perform a cycle check. If a cycle is detected, the implementation **MUST** abort with exit code `2` and emit a diagnostic to stderr identifying the cycle.
+4. Compute a topological order. Steps at the same depth **MAY** be executed in parallel.
+
+### 7.2 Execution Algorithm
+
+A conforming implementation **MUST** execute the DAG as follows:
+
+> [!NOTE] Non-normative example illustrating the orchestration entry point.
+>
+> ```typescript
+> // index.ts — entry point
+> import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
+>
+> async function main() {
+>   const workflow = parseWorkflow(process.argv[2]);
+>   const dag = buildDAG(workflow);
+>   const extensions = [
+>     apiProxyProvider,
+>     mcpBridgeExtension,
+>     safeOutputsExtension,
+>     costTrackerExtension,
+>     steeringExtension,
+>     repairExtension,
+>     observabilityExtension,
+>     checkpointExtension,
+>   ];
+>
+>   for (const stepGroup of dag.executionOrder()) {
+>     await Promise.all(stepGroup.map(async (step) => {
+>       const { session } = await createAgentSession({
+>         sessionManager: SessionManager.inMemory(),
+>         extensions,
+>         model: resolveModel(step.agent?.model || workflow.defaultModel),
+>         systemPrompt: buildSystemPrompt(step),
+>       });
+>
+>       const prompt = buildStepPrompt(step, transcripts);
+>       await session.prompt(prompt);
+>
+>       transcripts[step.name] = captureTranscript(session);
+>       session.dispose();
+>     }));
+>   }
+> }
+> ```
+
+For each execution group in topological order:
+
+1. The implementation **MUST** invoke `createAgentSession()` once per step (or once per agent for steps with `agents: [...]`).
+2. The prompt passed to `session.prompt()` **MUST** be assembled from: (a) the step's Markdown body, (b) transcripts from all upstream steps, and (c) the agent's system prompt, if defined.
+3. The implementation **MUST** load all eight gh-aw Pi extensions (see [Section 8](#8-extensions)) into each session.
+4. Steps within the same parallel group **MUST** be executed concurrently using `Promise.all()`.
+5. After each session completes, the implementation **MUST** capture the session transcript for use by downstream steps.
+6. After capturing the transcript, the implementation **MUST** call `session.dispose()`.
+7. If the budget gate has been triggered (via the cost-tracker extension), the implementation **MUST NOT** launch further sessions and **MUST** exit with code `1`.
+
+### 7.3 Step Execution Summary
+
+The per-step execution sequence is:
 
-  pi.on("agent_end", async () => {
-    emitCostSummary(totalCost);
-  });
-}
 ```
-
-**Why an extension**: Pi's event stream (`turn_end`) provides token/cost data per turn. The `steer()` API injects budget warnings naturally.
-
-### Extension 5: Steering (Resource Pressure)
-
-Monitors time remaining and budget, injects steering messages via Pi's native `session.steer()`.
-
-```typescript
-export default function(pi: ExtensionAPI) {
-  const config = loadSteeringConfig();
-  let startTime: number;
-
-  pi.on("agent_start", async () => {
-    startTime = Date.now();
-  });
-
-  pi.on("turn_end", async (event, ctx) => {
-    const elapsed = (Date.now() - startTime) / 60000;
-    const remaining = config.timeoutMinutes - elapsed;
-
-    if (remaining <= config.timeCriticalMinutes) {
-      ctx.agent.steer({
-        role: "user",
-        content: `⚠️ CRITICAL: ${remaining.toFixed(0)}min left. Write final output NOW.`,
-        timestamp: Date.now(),
-      });
-    } else if (remaining <= config.timeWarningMinutes) {
-      ctx.agent.steer({
-        role: "user",
-        content: `⚠️ ${remaining.toFixed(0)}min remaining. Wrap up.`,
-        timestamp: Date.now(),
-      });
-    }
-  });
-}
-```
-
-**Why an extension**: Pi's `turn_end` event fires after each tool execution cycle. Perfect timing for resource checks. `steer()` delivers the message before the next LLM call.
-
-### Extension 6: Session Repair
-
-Detects broken tool calls and repairs the session via Pi's message history manipulation.
-
-```typescript
-export default function(pi: ExtensionAPI) {
-  pi.on("tool_result", async (event, ctx) => {
-    if (isCorruptedToolResult(event)) {
-      const messages = ctx.agent.state.messages;
-      const repaired = truncateBrokenMessages(messages);
-      ctx.agent.state.messages = repaired;
-      emitRepairEvent("truncate_and_resume", event.toolName);
-    }
-  });
-
-  pi.on("agent_end", async (event, ctx) => {
-    if (event.error && isRecoverableError(event.error)) {
-      const summary = await summarizeTranscript(ctx.agent.state.messages);
-      ctx.agent.followUp({
-        role: "user",
-        content: `Previous progress: ${summary}\nContinue from here.`,
-        timestamp: Date.now(),
-      });
-    }
-  });
-}
+For each step (respecting DAG order):
+  1. Build prompt = step markdown + upstream transcripts + system prompt
+  2. Create Pi AgentSession with all gh-aw extensions:
+     - api-proxy provider registered
+     - MCP bridge tools registered
+     - Safe-output tools registered
+     - Steering, repair, cost, observability extensions active
+  3. session.prompt() → Pi agent loop runs
+  4. Extensions handle events (cost tracking, steering, observability)
+  5. Capture transcript for downstream steps
+  6. Budget gate check, checkpoint state
 ```
 
-**Why an extension**: Pi exposes `agent.state.messages` for manipulation and `tool_result` events for interception.
-
-### Extension 7: Observability
-
-Emits JSONL events to stderr and generates OTel spans.
-
-```typescript
-export default function(pi: ExtensionAPI) {
-  pi.on("agent_start", async (event) => {
-    emitJsonl({ event: "step_start", step: currentStep, model: currentModel });
-    startOtelSpan(currentStep);
-  });
-
-  pi.on("turn_end", async (event) => {
-    recordOtelAttributes(event);
-  });
+---
 
-  pi.on("tool_execution_end", async (event) => {
-    emitJsonl({ event: "tool_end", tool: event.toolName, duration: event.duration });
-  });
+## 8. Extensions
+
+All gh-aw-specific behavior **MUST** be packaged as Pi extensions. Each extension **MUST** be a standalone TypeScript module that exports a default function with signature `(pi: ExtensionAPI) => void | Promise<void>`.
+
+The following eight extensions **MUST** be loaded into every `AgentSession` created by the harness.
+
+### 8.1 Extension 1: api-proxy Provider
+
+**Purpose:** Registers the gh-aw api-proxy as a custom Pi provider.
+
+**Requirements:**
+
+- The extension **MUST** call `pi.registerProvider()` with the api-proxy base URL and the `AWF_API_PROXY_TOKEN` environment variable as the API key reference.
+- The extension **MUST** fetch the available model list from the api-proxy at startup, before any session begins.
+- The extension **MUST** use `"openai-completions"` as the API type, as the api-proxy speaks the OpenAI completions protocol.
+- The `AWF_API_PROXY_URL` environment variable **MUST** be used as the base URL, defaulting to `http://localhost:8080/v1`.
+
+> [!NOTE] Non-normative example.
+>
+> ```typescript
+> export default async function(pi: ExtensionAPI) {
+>   const proxyUrl = process.env.AWF_API_PROXY_URL || "http://localhost:8080/v1";
+>   const models = await fetchModelsFromProxy(proxyUrl);
+>
+>   pi.registerProvider("aw-proxy", {
+>     baseUrl: proxyUrl,
+>     apiKey: "AWF_API_PROXY_TOKEN",
+>     api: "openai-completions",
+>     models,
+>   });
+> }
+> ```
+
+### 8.2 Extension 2: MCP Gateway Bridge
+
+**Purpose:** Bridges gh-aw's MCP gateway tools into Pi's tool system as `AgentTool` instances.
+
+**Requirements:**
+
+- The extension **MUST** read the MCP gateway configuration and register each MCP tool as a Pi `AgentTool` via `pi.registerTool()`.
+- Tool names **MUST** be namespaced as `<serverName>_<toolName>` to avoid collisions.
+- The `execute` handler **MUST** delegate to the MCP gateway via the existing gateway IPC mechanism.
+- `gh-proxy` and `cli-proxy` **MUST** be active in the container when this extension executes. The extension **MAY** assert their presence at startup and **MUST** fail with a descriptive error if they are absent.
+
+> [!NOTE] Non-normative example.
+>
+> ```typescript
+> export default function(pi: ExtensionAPI) {
+>   const gatewayConfig = loadMCPGatewayConfig();
+>
+>   for (const [serverName, tools] of Object.entries(gatewayConfig)) {
+>     for (const tool of tools) {
+>       pi.registerTool({
+>         name: `${serverName}_${tool.name}`,
+>         description: tool.description,
+>         parameters: tool.inputSchema,
+>         async execute(toolCallId, params, signal) {
+>           return await callMCPGateway(serverName, tool.name, params);
+>         },
+>       });
+>     }
+>   }
+> }
+> ```
+
+### 8.3 Extension 3: Safe Outputs
+
+**Purpose:** Registers safe-output tools (create-issue, create-pull-request, add-comment, etc.) and writes artifact files in the gh-aw safe-outputs format.
+
+**Requirements:**
+
+- The extension **MUST** register at minimum the `create_issue`, `create_pull_request`, and `add_comment` tools via `pi.registerTool()`.
+- Each `execute` handler **MUST** write a safe-output artifact file in the format expected by the post-agent job, without performing any live GitHub API calls.
+- The extension **MUST** subscribe to the `agent_end` Pi event to finalize the safe-output manifest.
+- The extension **MUST** enforce any `max-count` limit declared in the workflow's `safe-outputs` frontmatter.
+
+> [!NOTE] Non-normative example.
+>
+> ```typescript
+> export default function(pi: ExtensionAPI) {
+>   const config = loadSafeOutputsConfig();
+>
+>   pi.registerTool({
+>     name: "create_issue",
+>     description: "Create a GitHub issue (safe output)",
+>     parameters: Type.Object({
+>       title: Type.String(),
+>       body: Type.String(),
+>       labels: Type.Optional(Type.Array(Type.String())),
+>     }),
+>     async execute(toolCallId, params, signal) {
+>       const artifact = buildSafeOutputArtifact("create-issue", params, config);
+>       await writeSafeOutputArtifact(artifact);
+>       return { content: [{ type: "text", text: `Issue queued: ${params.title}` }] };
+>     },
+>   });
+>
+>   // ... register other safe-output tools
+>
+>   pi.on("agent_end", async (event, ctx) => {
+>     await finalizeSafeOutputManifest();
+>   });
+> }
+> ```
+
+### 8.4 Extension 4: Cost Tracker
+
+**Purpose:** Monitors token usage and cost via Pi's event stream and enforces budget gates.
+
+**Requirements:**
+
+- The extension **MUST** subscribe to `turn_end` events and accumulate total cost from each turn.
+- When accumulated cost reaches or exceeds `harness.budget.warn-at-percent`, the extension **MUST** inject a steering message via `ctx.agent.steer()` warning the agent to be concise.
+- When accumulated cost reaches or exceeds the value corresponding to `harness.steering.budget-critical-percent`, the extension **MUST** call `ctx.agent.abort()`.
+- The extension **MUST** subscribe to `agent_end` and emit a cost summary to the JSONL event stream.
+
+> [!NOTE] Non-normative example.
+>
+> ```typescript
+> export default function(pi: ExtensionAPI) {
+>   const budget = loadBudgetConfig();
+>   let totalCost = 0;
+>
+>   pi.on("turn_end", async (event, ctx) => {
+>     totalCost += extractCostFromTurn(event);
+>
+>     const percent = (totalCost / budget.maxCostUsd) * 100;
+>     if (percent >= budget.budgetCriticalPercent) {
+>       ctx.agent.abort();
+>     } else if (percent >= budget.budgetWarnPercent) {
+>       ctx.agent.steer({
+>         role: "user",
+>         content: `⚠️ Budget: ${percent.toFixed(0)}% used. Be concise.`,
+>         timestamp: Date.now(),
+>       });
+>     }
+>   });
+>
+>   pi.on("agent_end", async () => {
+>     emitCostSummary(totalCost);
+>   });
+> }
+> ```
+
+### 8.5 Extension 5: Steering (Resource Pressure)
+
+**Purpose:** Monitors time remaining and budget, and injects steering messages via Pi's native `session.steer()`.
+
+**Requirements:**
+
+- The extension **MUST** subscribe to `agent_start` to record the session start time.
+- The extension **MUST** subscribe to `turn_end` and compute elapsed time after each turn.
+- When time remaining falls below `harness.steering.time-warning-minutes`, the extension **MUST** inject a warning steering message.
+- When time remaining falls below `harness.steering.time-critical-minutes`, the extension **MUST** inject a critical steering message directing the agent to produce final output immediately.
+
+> [!NOTE] Non-normative example.
+>
+> ```typescript
+> export default function(pi: ExtensionAPI) {
+>   const config = loadSteeringConfig();
+>   let startTime: number;
+>
+>   pi.on("agent_start", async () => {
+>     startTime = Date.now();
+>   });
+>
+>   pi.on("turn_end", async (event, ctx) => {
+>     const elapsed = (Date.now() - startTime) / 60000;
+>     const remaining = config.timeoutMinutes - elapsed;
+>
+>     if (remaining <= config.timeCriticalMinutes) {
+>       ctx.agent.steer({
+>         role: "user",
+>         content: `⚠️ CRITICAL: ${remaining.toFixed(0)}min left. Write final output NOW.`,
+>         timestamp: Date.now(),
+>       });
+>     } else if (remaining <= config.timeWarningMinutes) {
+>       ctx.agent.steer({
+>         role: "user",
+>         content: `⚠️ ${remaining.toFixed(0)}min remaining. Wrap up.`,
+>         timestamp: Date.now(),
+>       });
+>     }
+>   });
+> }
+> ```
+
+### 8.6 Extension 6: Session Repair
+
+**Purpose:** Detects broken tool calls and repairs the session via Pi's message history manipulation.
+
+**Requirements:**
+
+- The extension **MUST** subscribe to `tool_result` events and inspect results for corruption indicators.
+- On detection of a corrupted tool result, the extension **MUST** truncate the broken messages from `ctx.agent.state.messages` and emit a repair event to the JSONL stream.
+- The extension **MUST** subscribe to `agent_end` and attempt recovery if the error is classified as recoverable, by injecting a follow-up message containing a summary of prior progress.
+
+> [!NOTE] Non-normative example.
+>
+> ```typescript
+> export default function(pi: ExtensionAPI) {
+>   pi.on("tool_result", async (event, ctx) => {
+>     if (isCorruptedToolResult(event)) {
+>       const messages = ctx.agent.state.messages;
+>       const repaired = truncateBrokenMessages(messages);
+>       ctx.agent.state.messages = repaired;
+>       emitRepairEvent("truncate_and_resume", event.toolName);
+>     }
+>   });
+>
+>   pi.on("agent_end", async (event, ctx) => {
+>     if (event.error && isRecoverableError(event.error)) {
+>       const summary = await summarizeTranscript(ctx.agent.state.messages);
+>       ctx.agent.followUp({
+>         role: "user",
+>         content: `Previous progress: ${summary}\nContinue from here.`,
+>         timestamp: Date.now(),
+>       });
+>     }
+>   });
+> }
+> ```
+
+### 8.7 Extension 7: Observability
+
+**Purpose:** Emits JSONL events to stderr and generates OTel spans.
+
+**Requirements:**
+
+- The extension **MUST** subscribe to `agent_start`, `turn_end`, `tool_execution_end`, and `agent_end` events.
+- On each event, the extension **MUST** emit a corresponding JSONL record to stderr.
+- If `observability.otlp.endpoint` is configured in the workflow frontmatter, the extension **MUST** create and close OTel spans for each step.
+- OTel span attributes **MUST** include at minimum: step name, model, token counts, and cost.
+
+> [!NOTE] Non-normative example.
+>
+> ```typescript
+> export default function(pi: ExtensionAPI) {
+>   pi.on("agent_start", async (event) => {
+>     emitJsonl({ event: "step_start", step: currentStep, model: currentModel });
+>     startOtelSpan(currentStep);
+>   });
+>
+>   pi.on("turn_end", async (event) => {
+>     recordOtelAttributes(event);
+>   });
+>
+>   pi.on("tool_execution_end", async (event) => {
+>     emitJsonl({ event: "tool_end", tool: event.toolName, duration: event.duration });
+>   });
+>
+>   pi.on("agent_end", async (event) => {
+>     emitJsonl({ event: "step_end", step: currentStep, tokens: event.tokens, cost: event.cost });
+>     endOtelSpan(currentStep);
+>   });
+> }
+> ```
+
+### 8.8 Extension 8: Checkpoint
+
+**Purpose:** Persists run state for long workflows, enabling resume from a prior checkpoint.
+
+**Requirements:**
+
+- When `harness.checkpoint: true` is set, the extension **MUST** subscribe to `agent_end` and persist the step name, completion status, session transcript, and accumulated cost.
+- Checkpoint data **MUST** be stored in a location accessible across job retries (e.g., a Actions cache or artifact).
+- An implementation **SHOULD** support a `--continue` invocation flag that resumes from the last successful checkpoint, skipping already-completed steps.
+
+> [!NOTE] Non-normative example.
+>
+> ```typescript
+> export default function(pi: ExtensionAPI) {
+>   pi.on("agent_end", async (event, ctx) => {
+>     await saveCheckpoint({
+>       step: currentStep,
+>       status: event.error ? "failed" : "done",
+>       transcript: ctx.agent.state.messages,
+>       cost: totalCost,
+>     });
+>   });
+> }
+> ```
 
-  pi.on("agent_end", async (event) => {
-    emitJsonl({ event: "step_end", step: currentStep, tokens: event.tokens, cost: event.cost });
-    endOtelSpan(currentStep);
-  });
-}
-```
+---
 
-**Why an extension**: Pi's full event stream provides exactly the data needed for JSONL and OTel.
+## 9. Model Resolution
 
-### Extension 8: Checkpoint
+*(This section is non-normative.)*
 
-Persists run state for long workflows.
+The harness does not perform provider inference or model routing. That responsibility belongs to the api-proxy (`gh-aw-firewall/containers/api-proxy/model-resolver.js`), which runs as a sidecar in the gh-aw container.
 
-```typescript
-export default function(pi: ExtensionAPI) {
-  pi.on("agent_end", async (event, ctx) => {
-    await saveCheckpoint({
-      step: currentStep,
-      status: event.error ? "failed" : "done",
-      transcript: ctx.agent.state.messages,
-      cost: totalCost,
-    });
-  });
-}
-```
+### 9.1 Alias Resolution Flow
 
-## Orchestration Layer (Not an Extension)
-
-The multi-step DAG orchestration is the **harness entry point** — it sits above the Pi SDK and creates/manages multiple `AgentSession` instances. This is NOT a Pi extension because it manages session lifecycles rather than extending a single session.
-
-```typescript
-// index.ts — entry point
-import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
-
-async function main() {
-  const workflow = parseWorkflow(process.argv[2]);
-  const dag = buildDAG(workflow);
-  const extensions = [
-    apiProxyProvider,
-    mcpBridgeExtension,
-    safeOutputsExtension,
-    costTrackerExtension,
-    steeringExtension,
-    repairExtension,
-    observabilityExtension,
-    checkpointExtension,
-  ];
-
-  for (const stepGroup of dag.executionOrder()) {
-    await Promise.all(stepGroup.map(async (step) => {
-      const { session } = await createAgentSession({
-        sessionManager: SessionManager.inMemory(),
-        extensions,
-        model: resolveModel(step.agent?.model || workflow.defaultModel),
-        systemPrompt: buildSystemPrompt(step),
-      });
-
-      const prompt = buildStepPrompt(step, transcripts);
-      await session.prompt(prompt);
-
-      transcripts[step.name] = captureTranscript(session);
-      session.dispose();
-    }));
-  }
-}
 ```
-
-## Backwards Compatibility
-
-| Scenario | Behavior |
-|----------|----------|
-| `engine: copilot` (existing) | Uses current `copilot_harness.cjs` — unchanged |
-| `engine: claude` (existing) | Uses current Claude Code flow — unchanged |
-| `engine: codex` (existing) | Uses current Codex flow — unchanged |
-| `engine: aw` without `harness:` block | Single-step: entire body = one Pi session prompt |
-| `engine: aw` with `harness:` block | Multi-step orchestration mode |
-| `engine: aw` with `harness.steps` | Explicit DAG (parallel, depends, agent assignment) |
-| `engine: aw` without `harness.agents` | All steps use `engine.model` |
-| `engine: aw` + `cli-proxy: false` | **Ignored** — `cli-proxy` is always on for `engine: aw` |
-| `engine: aw` + `tools.github.mode: remote` | **Overridden to `gh-proxy`** — Pi SDK requires `gh-proxy`; `remote` mode is not supported |
-
-## Build & Deployment
-
-### TypeScript Configuration
-
-```jsonc
-// tsconfig.json
-{
-  "compilerOptions": {
-    "target": "es2024",           // Node 24 supports ES2024
-    "module": "es2022",
-    "lib": ["es2024"],
-    "moduleResolution": "bundler",
-    "strict": true,
-    "skipLibCheck": true,
-    "outDir": "dist",
-    "declaration": false
-  }
-}
+Harness (Pi SDK) → api-proxy → LLM provider
+  model: "sonnet"           resolves to copilot/claude-sonnet-4.6
+  model: "gpt-5-codex"      resolves to copilot/gpt-5.3-codex
+  model: "copilot/gpt-4.1"  passes through as-is
 ```
 
-### Bundle Configuration
-
-```typescript
-// build.ts — esbuild config
-import { build } from "esbuild";
-
-await build({
-  entryPoints: ["src/index.ts"],
-  bundle: true,
-  platform: "node",
-  target: "node24",
-  format: "cjs",                  // .cjs required by gh-aw harness validation
-  outfile: "dist/aw_harness.cjs",
-  external: [],                   // Bundle everything (no runtime npm install)
-  minify: false,                  // Keep readable for debugging in Actions logs
-  sourcemap: "inline",            // Debugging in CI
-});
-```
+### 9.2 Alias Configuration
+
+Model aliases are configured in the api-proxy via the `AWF_MODEL_ALIASES` environment variable.
+
+> [!NOTE] Non-normative example of alias configuration JSON.
+>
+> ```json
+> {
+>   "models": {
+>     "sonnet": ["copilot/*sonnet*", "anthropic/*sonnet*"],
+>     "gpt-5-codex": ["copilot/gpt-5*-codex", "openai/gpt-5*-codex"],
+>     "": ["sonnet", "gpt-5*-codex"]
+>   }
+> }
+> ```
+
+**Key properties of the alias resolver:**
+
+- `providerid/modelid` syntax with `*` wildcards.
+- Recursive alias resolution with loop detection.
+- Case-insensitive matching.
+- Semver sorting (highest version first among candidates).
+- Default policy via `""` key (applied when no model is specified in the workflow).
+
+### 9.3 Implications for the Harness
+
+- Pi SDK's `pi-ai` is configured with the api-proxy as an OpenAI-compatible custom provider (via `pi.registerProvider()` in Extension 1).
+- The harness passes model name strings through as-is — aliases such as `"sonnet"` work without any harness-side resolution.
+- No `provider:` field is needed in frontmatter — the proxy handles all routing.
+- Per-step and per-agent model selection is accomplished by passing a different model name string to `createAgentSession()`.
+- The harness inherits the api-proxy's full model catalog without maintaining its own.
+
+> [!NOTE] Non-normative example of provider registration.
+>
+> ```typescript
+> export default async function apiProxyProvider(pi: ExtensionAPI) {
+>   const proxyUrl = process.env.AWF_API_PROXY_URL || "http://localhost:8080/v1";
+>
+>   pi.registerProvider("aw-proxy", {
+>     baseUrl: proxyUrl,
+>     apiKey: "AWF_API_PROXY_TOKEN",
+>     api: "openai-completions",
+>     models: await fetchAvailableModels(proxyUrl),
+>   });
+> }
+> ```
 
-### Output Location
+---
 
-The bundled `aw_harness.cjs` is copied to `actions/setup/js/aw_harness.cjs` — alongside existing harnesses:
+## 10. Build and Deployment
+
+*(This section is non-normative.)*
+
+### 10.1 TypeScript Configuration
+
+> [!NOTE] Non-normative example.
+>
+> ```jsonc
+> // tsconfig.json
+> {
+>   "compilerOptions": {
+>     "target": "es2024",           // Node 24 supports ES2024
+>     "module": "es2022",
+>     "lib": ["es2024"],
+>     "moduleResolution": "bundler",
+>     "strict": true,
+>     "skipLibCheck": true,
+>     "outDir": "dist",
+>     "declaration": false
+>   }
+> }
+> ```
+
+### 10.2 Bundle Configuration
+
+> [!NOTE] Non-normative example.
+>
+> ```typescript
+> // build.ts — esbuild config
+> import { build } from "esbuild";
+>
+> await build({
+>   entryPoints: ["src/index.ts"],
+>   bundle: true,
+>   platform: "node",
+>   target: "node24",
+>   format: "cjs",                  // .cjs required by gh-aw harness validation
+>   outfile: "dist/aw_harness.cjs",
+>   external: [],                   // Bundle everything (no runtime npm install)
+>   minify: false,                  // Keep readable for debugging in Actions logs
+>   sourcemap: "inline",            // Debugging in CI
+> });
+> ```
+
+### 10.3 Output Location
+
+The bundled `aw_harness.cjs` is placed in `actions/setup/js/aw_harness.cjs`, alongside existing harnesses:
 
 ```
 actions/setup/js/
@@ -658,15 +944,7 @@ actions/setup/js/
 └── *.test.cjs                # Tests
 ```
 
-### Testing
-
-Tests use the same Vitest setup as the existing `actions/setup/js/` scripts:
-
-- Unit tests for parser, planner, each extension
-- Integration tests with mock Pi sessions (in-memory session manager)
-- Tests co-located: `aw_harness.test.cjs` or in a `test/` subdirectory
-
-## Project Structure
+### 10.4 Project Structure
 
 ```
 aw-harness/
@@ -703,7 +981,35 @@ aw-harness/
     └── aw_harness.cjs            # → copied to actions/setup/js/
 ```
 
-## Todos
+### 10.5 Testing
+
+Tests use the same Vitest setup as the existing `actions/setup/js/` scripts:
+
+- Unit tests for parser, planner, and each extension.
+- Integration tests with mock Pi sessions (`SessionManager.inMemory()`).
+- Tests co-located: `aw_harness.test.cjs` or in a `test/` subdirectory.
+
+### 10.6 Build Integration
+
+A `make aw-harness` Makefile target **SHOULD** be added that runs esbuild and copies the output to `actions/setup/js/aw_harness.cjs`.
+
+### 10.7 Backwards Compatibility
+
+| Scenario | Behavior |
+|----------|----------|
+| `engine: copilot` (existing) | Uses current `copilot_harness.cjs` — unchanged |
+| `engine: claude` (existing) | Uses current Claude Code flow — unchanged |
+| `engine: codex` (existing) | Uses current Codex flow — unchanged |
+| `engine: aw` without `harness:` block | Single-step: entire body = one Pi session prompt |
+| `engine: aw` with `harness:` block | Multi-step orchestration mode |
+| `engine: aw` with `harness.steps` | Explicit DAG (parallel, depends, agent assignment) |
+| `engine: aw` without `harness.agents` | All steps use `engine.model` |
+| `engine: aw` + `cli-proxy: false` | **Ignored** — `cli-proxy` is always on for `engine: aw` |
+| `engine: aw` + `tools.github.mode: remote` | **Overridden to `gh-proxy`** — Pi SDK requires `gh-proxy`; `remote` mode is not supported |
+
+### 10.8 Implementation Plan
+
+The following ordered work items describe the implementation sequence:
 
 1. **Scaffold project** — Initialize TypeScript project in `aw-harness/`. Configure package.json with Pi SDK deps (`@mariozechner/pi-coding-agent`, `pi-agent-core`, `pi-ai`). Set up tsconfig for ES2024/Node 24. Configure esbuild bundle → `dist/aw_harness.cjs`.
 
@@ -735,6 +1041,65 @@ aw-harness/
 
 15. **Write tests** — Unit tests for parser, planner, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
 
-16. **Write example workflows** — 3 examples: single-step, multi-step sequential, multi-agent parallel with different models.
+16. **Write example workflows** — Three examples: single-step, multi-step sequential, multi-agent parallel with different models.
 
 17. **Add build to Makefile** — Add `make aw-harness` target that runs esbuild and copies `aw_harness.cjs` to `actions/setup/js/`.
+
+---
+
+## 11. Security Considerations
+
+**Mandatory proxy features.** The `gh-proxy` and `cli-proxy` features **MUST** always be active for `engine: aw`. Disabling them would leave MCP gateway tools inaccessible to agent sessions, and any attempt by a workflow author to disable them **MUST** be silently overridden (see [Section 6.2](#62-overrides-and-fixed-settings)).
+
+**No direct LLM access.** The harness **MUST NOT** make direct calls to LLM provider APIs. All model requests **MUST** pass through the api-proxy, which enforces rate limits, budget caps, and access controls at the container boundary.
+
+**Safe outputs isolation.** The safe-outputs extension **MUST NOT** perform live GitHub API calls during agent execution. All GitHub mutations **MUST** be expressed as artifact files processed by the post-agent job, which applies threat detection and validation before acting.
+
+**Budget enforcement.** The cost-tracker extension provides a hard budget gate. A conforming implementation **MUST** abort the session if the cost exceeds the configured maximum, preventing runaway spending from misbehaving agents.
+
+**Transcript confidentiality.** Transcripts captured for inter-step context **SHOULD** be stored only in memory or in ephemeral container storage. Implementations **SHOULD NOT** persist transcripts to external storage unless checkpointing is explicitly enabled.
+
+**Token and secret handling.** The `AWF_API_PROXY_TOKEN` value **MUST NOT** be logged to stderr or embedded in JSONL events. Implementations **MUST** treat it as an opaque secret.
+
+---
+
+## 12. Privacy Considerations
+
+*(This section is non-normative.)*
+
+**Data residency.** All agent execution occurs within the gh-aw Actions container. No workflow content, prompts, or transcripts leave the container except via the api-proxy to the configured LLM provider endpoint, or via OTLP to the configured telemetry endpoint.
+
+**Transcript retention.** Step transcripts held in memory for inter-step context are discarded when the harness process exits. If checkpointing is enabled, transcript data may be persisted to GitHub Actions artifacts; workflow authors **SHOULD** evaluate the sensitivity of transcript content before enabling checkpointing.
+
+**Telemetry scope.** When `observability.otlp` is configured, OTel spans contain step names, model names, token counts, and cost data. They **SHOULD NOT** contain raw prompt or response text. Implementations **SHOULD** redact sensitive content from span attributes.
+
+**Model provider data handling.** Prompt content is transmitted to the LLM provider as configured in the api-proxy. Workflow authors are responsible for ensuring that content transmitted to LLM providers complies with applicable data handling policies.
+
+---
+
+## 13. References
+
+### 13.1 Normative References
+
+**[RFC 2119]**
+Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997. <https://www.rfc-editor.org/rfc/rfc2119>
+
+### 13.2 Informative References
+
+**[Pi SDK]**
+`@mariozechner/pi-coding-agent` — Pi agent SDK providing `createAgentSession()`, `Agent`, `AgentTool`, and `ExtensionAPI`.
+
+**[pi-agent-core]**
+Core agent loop, event dispatch, and message history management for Pi SDK.
+
+**[pi-ai]**
+Pi AI provider abstraction layer, supporting OpenAI-compatible backends.
+
+**[esbuild]**
+JavaScript/TypeScript bundler. <https://esbuild.github.io>
+
+**[OpenTelemetry]**
+OpenTelemetry specification for distributed tracing. <https://opentelemetry.io/docs/>
+
+**[gh-aw]**
+GitHub Agentic Workflows — the gh-aw CLI extension that compiles Markdown workflow files to GitHub Actions YAML. <https://github.com/github/gh-aw>

From 678615c870e4122e67dfbec403ad91c366e32344 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 02:36:42 +0000
Subject: [PATCH 04/14] docs(specs): address review feedback on aw-harness W3C
 spec

- Clarify MUST NOT wording: 'MUST NOT honor attempts to disable'
- Add normative requirement to emit a stderr warning when cli-proxy
  or tools.github.mode overrides are applied, aiding author debugging

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 7aa9122d204..65a49f6f1f8 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -214,7 +214,7 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 
 4. **Optimized for gh-aw container.** The harness **MUST** assume that the firewall, api-proxy, and MCP gateway are already running. It **MUST NOT** perform direct LLM API calls, redundant authentication, or network configuration.
 
-5. **`gh-proxy` and `cli-proxy` always on.** The Pi SDK does not support MCP natively. GitHub and other MCP server tools are bridged into Pi via the `mcp-bridge` extension. This bridge **REQUIRES** both `gh-proxy` (pre-authenticated `gh` CLI in bash) and `cli-proxy` (MCP servers mounted as CLI tools on `PATH`). A conforming implementation **MUST** enable both `gh-proxy` and `cli-proxy` when `engine: aw` is selected. An implementation **MUST NOT** allow these features to be disabled for `engine: aw`, regardless of the values specified in the workflow frontmatter (see [Section 6.2](#62-overrides-and-fixed-settings)).
+5. **`gh-proxy` and `cli-proxy` always on.** The Pi SDK does not support MCP natively. GitHub and other MCP server tools are bridged into Pi via the `mcp-bridge` extension. This bridge **REQUIRES** both `gh-proxy` (pre-authenticated `gh` CLI in bash) and `cli-proxy` (MCP servers mounted as CLI tools on `PATH`). A conforming implementation **MUST** enable both `gh-proxy` and `cli-proxy` when `engine: aw` is selected. A conforming implementation **MUST NOT** honor attempts to disable these features for `engine: aw`, regardless of the values specified in the workflow frontmatter (see [Section 6.2](#62-overrides-and-fixed-settings)).
 
 6. **TypeScript → Node 24.** Source **MUST** be TypeScript, compiled to ES2024, bundled via esbuild to a single `.cjs`. Leverages Node 24 features (native fetch, `structuredClone`, `AbortSignal.any`).
 
@@ -439,7 +439,7 @@ A conforming implementation **MUST** apply the following overrides regardless of
 | `cli-proxy` | `true` | Required for MCP bridge functionality |
 | `tools.github.mode` | `gh-proxy` | Pi SDK requires `gh-proxy`; `remote` mode is not supported |
 
-A conforming implementation **MUST NOT** honor `cli-proxy: false` or `tools.github.mode: remote` when `engine: aw` is active. These settings **MUST** be silently overridden.
+A conforming implementation **MUST NOT** honor attempts to disable `cli-proxy` or set `tools.github.mode: remote` when `engine: aw` is active. These settings **MUST** be overridden. A conforming implementation **MUST** emit a warning to stderr when either override is applied, so that workflow authors can diagnose unexpected configuration behaviour.
 
 ### 6.3 Step Extraction Algorithm
 

From fe1e2a2f0e311e18c5be5a48baf6f88cf03c1796 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 02:47:28 +0000
Subject: [PATCH 05/14] =?UTF-8?q?docs:=20add=20=C2=A76.4=20initial=20promp?=
 =?UTF-8?q?t=20context=20=E2=80=94=20no=20predefined=20context,=20all=20im?=
 =?UTF-8?q?ports=20must=20be=20declared?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/8df5106a-c84d-4c72-b43d-280243d2ebae

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 65a49f6f1f8..61455b21be4 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -451,6 +451,22 @@ A conforming implementation **MUST** extract steps from the workflow document bo
 4. Steps without a `<!-- harness-step -->` annotation are treated as sequential steps in document order.
 5. If no `harness:` block is present in the frontmatter, the entire document body **MUST** be treated as a single step with no explicit agent or dependency.
 
+### 6.4 Initial Prompt Context
+
+The AW Harness **MUST NOT** inject any predefined or ambient context into agent sessions. There are no implicit files, skills, or instruction documents automatically added to a session's initial prompt.
+
+A conforming implementation **MUST** source every item included in a session's initial prompt from one of the following explicitly declared origins:
+
+- The step's own Markdown body (extracted per [Section 6.3](#63-step-extraction-algorithm)).
+- Transcripts from upstream steps (passed via the DAG execution model in [Section 7](#7-dag-execution-model)).
+- The agent's `system` prompt as declared under `harness.agents` in the frontmatter.
+- Files or sub-workflows declared via the standard `imports:` frontmatter key.
+
+A conforming implementation **MUST NOT** automatically load AGENTS.md files, `.github/agents/` entries, skills directories, or any other ambient repository files unless they are explicitly listed in `imports:`. This behavior is a deliberate divergence from engines such as `engine: copilot` that inject ambient context automatically.
+
+> [!IMPORTANT]
+> Workflow authors **MUST** explicitly declare every file or skill they wish the agent to reference using the `imports:` frontmatter key. Relying on ambient context that is auto-injected by other engines will produce a missing-context failure when running with `engine: aw`.
+
 ---
 
 ## 7. DAG Execution Model

From 945545f082089997dde214921b35e5f4533cc9b3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 03:05:19 +0000
Subject: [PATCH 06/14] docs: remove mcp-bridge extension; harness reads
 compiler-generated config.json+prompt.txt

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/c4564ad2-de2d-4e3d-8a0f-da65187abf59

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 151 +++++++++++++++++---------------------------
 1 file changed, 58 insertions(+), 93 deletions(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 61455b21be4..22d30d6174a 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -121,10 +121,7 @@ A **conforming implementation** is one that satisfies all **MUST** and **MUST NO
 : A unit of work within an `engine: aw` workflow. Each step is assigned one `AgentSession`, a prompt derived from the corresponding Markdown section, and optionally a named agent definition.
 
 **MCP Gateway**
-: The gh-aw MCP gateway process that exposes GitHub tools and custom MCP server tools. It runs independently of the harness in the same container.
-
-**MCP bridge**
-: The `mcp-bridge` Pi extension (Extension 2) that translates MCP gateway tool definitions into Pi `AgentTool` instances, making them available to agent sessions without native MCP support in the Pi SDK.
+: The gh-aw MCP gateway process that exposes GitHub tools and custom MCP server tools as CLI commands (via `cli-proxy`) in the agent's bash environment. It runs independently of the harness in the same container.
 
 **model alias**
 : A short name (e.g., `"sonnet"`, `"gpt-5-codex"`) resolved by the api-proxy to a fully-qualified `provider/model` string. The harness passes aliases through without resolution.
@@ -142,7 +139,7 @@ A **conforming implementation** is one that satisfies all **MUST** and **MUST NO
 : The complete message history of a completed `AgentSession`, optionally summarized, passed as context to downstream steps.
 
 **workflow document**
-: A Markdown file with YAML frontmatter that declares an `engine: aw` workflow. The frontmatter **MUST** conform to the schema in [Section 6](#6-workflow-definition).
+: A Markdown file with YAML frontmatter that declares an `engine: aw` workflow. The frontmatter is parsed by the gh-aw compiler at compile time; the harness itself never reads the raw Markdown file. Instead, the compiler provides the harness with pre-processed inputs: `config.json` (harness configuration), `prompt.txt` (extracted prompt body), and any referenced agent files.
 
 ---
 
@@ -162,12 +159,12 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 │  │  ┌─────────────────────────────────────────────────┐ │   │
 │  │  │  aw_harness.cjs (entry point)                   │ │   │
 │  │  │                                                  │ │   │
-│  │  │  1. Reads workflow.md (frontmatter + body)       │ │   │
-│  │  │  2. Parses steps from markdown headings          │ │   │
-│  │  │  3. Builds execution DAG                         │ │   │
-│  │  │  4. For each step: creates Pi AgentSession       │ │   │
-│  │  │     with gh-aw extensions loaded                 │ │   │
-│  │  │  5. session.prompt() → Pi drives the agent       │ │   │
+│  │  │  1. Reads config.json + prompt.txt (pre-parsed by compiler) │ │   │
+│  │  │  2. Parses steps from step config + prompt text            │ │   │
+│  │  │  3. Builds execution DAG                                   │ │   │
+│  │  │  4. For each step: creates Pi AgentSession                 │ │   │
+│  │  │     with gh-aw extensions loaded                           │ │   │
+│  │  │  5. session.prompt() → Pi drives the agent                 │ │   │
 │  │  │                                                  │ │   │
 │  │  │  ┌──────────────────────────────────────────┐   │ │   │
 │  │  │  │  Pi SDK (createAgentSession)             │   │ │   │
@@ -179,7 +176,6 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 │  │  │  │  gh-aw Pi Extensions (loaded into each   │   │ │   │
 │  │  │  │  AgentSession via ExtensionAPI):          │   │ │   │
 │  │  │  │  ├─ safe-outputs (tools + artifact write) │   │ │   │
-│  │  │  │  ├─ mcp-bridge (gateway tools → Pi tools) │   │ │   │
 │  │  │  │  ├─ cost-tracker (budget gates + events)  │   │ │   │
 │  │  │  │  ├─ steering (time/budget pressure)       │   │ │   │
 │  │  │  │  ├─ repair (broken session recovery)      │   │ │   │
@@ -212,9 +208,9 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 
 3. **api-proxy for model resolution.** Pi's `pi-ai` **MUST** be configured to communicate with the api-proxy as an OpenAI-compatible provider. Model names (aliases or explicit) **MUST** be passed through to the proxy without local resolution.
 
-4. **Optimized for gh-aw container.** The harness **MUST** assume that the firewall, api-proxy, and MCP gateway are already running. It **MUST NOT** perform direct LLM API calls, redundant authentication, or network configuration.
+4. **Optimized for gh-aw container.** The harness **MUST** assume that the firewall, api-proxy, and MCP gateway are already running. It **MUST NOT** perform direct LLM API calls, redundant authentication, or network configuration. MCP tools are available to agent sessions as bash CLI tools via `cli-proxy` — no additional bridging is required.
 
-5. **`gh-proxy` and `cli-proxy` always on.** The Pi SDK does not support MCP natively. GitHub and other MCP server tools are bridged into Pi via the `mcp-bridge` extension. This bridge **REQUIRES** both `gh-proxy` (pre-authenticated `gh` CLI in bash) and `cli-proxy` (MCP servers mounted as CLI tools on `PATH`). A conforming implementation **MUST** enable both `gh-proxy` and `cli-proxy` when `engine: aw` is selected. A conforming implementation **MUST NOT** honor attempts to disable these features for `engine: aw`, regardless of the values specified in the workflow frontmatter (see [Section 6.2](#62-overrides-and-fixed-settings)).
+5. **`gh-proxy` and `cli-proxy` always on.** GitHub and other MCP server tools are available to the agent as CLI commands on `PATH` (via `cli-proxy`) and via the pre-authenticated `gh` binary (via `gh-proxy`). A conforming implementation **MUST** enable both `gh-proxy` and `cli-proxy` when `engine: aw` is selected. A conforming implementation **MUST NOT** honor attempts to disable these features for `engine: aw`, regardless of the values specified in the workflow frontmatter (see [Section 6.2](#62-overrides-and-fixed-settings)).
 
 6. **TypeScript → Node 24.** Source **MUST** be TypeScript, compiled to ES2024, bundled via esbuild to a single `.cjs`. Leverages Node 24 features (native fetch, `structuredClone`, `AbortSignal.any`).
 
@@ -232,13 +228,18 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 
 ### 5.1 Entry Point
 
-The AW Harness **MUST** be invocable as a Node.js CommonJS module from the command line. A conforming invocation has the form:
+The AW Harness **MUST** be invocable as a Node.js CommonJS module from the command line. The gh-aw compiler pre-processes the workflow markdown (parsing frontmatter, extracting the prompt body, resolving imports) and provides the harness with pre-built input files. A conforming invocation has the form:
 
 ```
-node aw_harness.cjs <workflow-path>
+node aw_harness.cjs --config <config-path> --prompt <prompt-path> [--agent-file <agent-path>]
 ```
 
-where `<workflow-path>` is the absolute or relative path to the workflow Markdown file.
+where:
+- `<config-path>` is the path to the compiler-generated `config.json` file containing the parsed harness configuration.
+- `<prompt-path>` is the path to the compiler-generated `prompt.txt` file containing the extracted prompt body.
+- `<agent-path>` is the optional path to a compiled agent file (`.md` with frontmatter stripped), provided when the workflow references an agent via `imports:`.
+
+A conforming implementation **MUST NOT** read or parse workflow Markdown files directly; all configuration and prompt content **MUST** be consumed from the pre-processed input files provided by the compiler.
 
 ### 5.2 Environment Variables
 
@@ -258,7 +259,7 @@ A conforming implementation **SHOULD** read additional standard GitHub Actions e
 |------|---------|
 | `0` | All steps completed successfully |
 | `1` | One or more steps failed (non-recoverable error) |
-| `2` | Invocation error (missing workflow path, invalid frontmatter) |
+| `2` | Invocation error (missing config path, unreadable config file) |
 
 A conforming implementation **MUST** exit with code `0` if and only if all DAG steps complete without error. It **MUST** exit with a non-zero code on any unrecovered failure.
 
@@ -273,7 +274,7 @@ A conforming implementation **MUST** exit with code `0` if and only if all DAG s
 
 ### 6.1 Frontmatter Schema
 
-An `engine: aw` workflow document **MUST** include a YAML frontmatter block conforming to the existing gh-aw frontmatter schema, extended with the optional `harness:` key described below.
+An `engine: aw` workflow document **MUST** include a YAML frontmatter block conforming to the existing gh-aw frontmatter schema, extended with the optional `harness:` key described below. The gh-aw compiler parses this frontmatter at compile time and emits a `config.json` file consumed by the harness at runtime; the harness itself **MUST NOT** re-parse the raw Markdown frontmatter.
 
 > [!NOTE] Non-normative example.
 >
@@ -296,8 +297,8 @@ An `engine: aw` workflow document **MUST** include a YAML frontmatter block conf
 >   pull-requests: read
 >
 > # gh-proxy and cli-proxy are ALWAYS enabled for engine: aw.
-> # Pi SDK does not support MCP natively; the mcp-bridge extension
-> # requires both features to bridge gateway tools into Pi AgentTools.
+> # MCP tools are available as CLI commands on PATH (via cli-proxy) and
+> # via the pre-authenticated `gh` binary (via gh-proxy).
 > cli-proxy: true
 >
 > tools:
@@ -436,20 +437,19 @@ A conforming implementation **MUST** apply the following overrides regardless of
 
 | Setting | Enforced value | Reason |
 |---------|----------------|--------|
-| `cli-proxy` | `true` | Required for MCP bridge functionality |
+| `cli-proxy` | `true` | Required: MCP tools are exposed as CLI tools on `PATH` |
 | `tools.github.mode` | `gh-proxy` | Pi SDK requires `gh-proxy`; `remote` mode is not supported |
 
 A conforming implementation **MUST NOT** honor attempts to disable `cli-proxy` or set `tools.github.mode: remote` when `engine: aw` is active. These settings **MUST** be overridden. A conforming implementation **MUST** emit a warning to stderr when either override is applied, so that workflow authors can diagnose unexpected configuration behaviour.
 
 ### 6.3 Step Extraction Algorithm
 
-A conforming implementation **MUST** extract steps from the workflow document body using the following algorithm:
+A conforming implementation **MUST** extract steps from the compiler-provided inputs as follows:
 
-1. Split the document body on ATX heading boundaries (`##` or `###` level).
-2. For each section, scan for an HTML comment matching `<!-- harness-step: <name> -->`. If found, record `name` as the step annotation.
-3. If a step annotation matches a key in `harness.steps`, apply the corresponding step configuration (agent, parallel, depends).
-4. Steps without a `<!-- harness-step -->` annotation are treated as sequential steps in document order.
-5. If no `harness:` block is present in the frontmatter, the entire document body **MUST** be treated as a single step with no explicit agent or dependency.
+1. Read `harness.steps` from `config.json`. If present, each entry defines a named step with its agent assignment, parallel flag, and dependency list.
+2. Read the prompt body from `prompt.txt`. The compiler splits the body on ATX heading boundaries (`##` or `###` level) and associates each section with a named step via `<!-- harness-step: <name> -->` annotations before writing `prompt.txt`; the harness consumes the resulting sections.
+3. If `harness.steps` is absent or empty, the entire prompt body **MUST** be treated as a single step with no explicit agent or dependency.
+4. Steps without a named entry in `harness.steps` are treated as sequential steps in document order.
 
 ### 6.4 Initial Prompt Context
 
@@ -460,7 +460,7 @@ A conforming implementation **MUST** source every item included in a session's i
 - The step's own Markdown body (extracted per [Section 6.3](#63-step-extraction-algorithm)).
 - Transcripts from upstream steps (passed via the DAG execution model in [Section 7](#7-dag-execution-model)).
 - The agent's `system` prompt as declared under `harness.agents` in the frontmatter.
-- Files or sub-workflows declared via the standard `imports:` frontmatter key.
+- Files or sub-workflows declared via the standard `imports:` frontmatter key and resolved by the compiler into agent files passed at invocation time.
 
 A conforming implementation **MUST NOT** automatically load AGENTS.md files, `.github/agents/` entries, skills directories, or any other ambient repository files unless they are explicitly listed in `imports:`. This behavior is a deliberate divergence from engines such as `engine: copilot` that inject ambient context automatically.
 
@@ -491,11 +491,11 @@ A conforming implementation **MUST** execute the DAG as follows:
 > import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
 >
 > async function main() {
->   const workflow = parseWorkflow(process.argv[2]);
+>   const { configPath, promptPath } = parseArgs(process.argv);
+>   const workflow = loadWorkflow(configPath, promptPath);
 >   const dag = buildDAG(workflow);
 >   const extensions = [
 >     apiProxyProvider,
->     mcpBridgeExtension,
 >     safeOutputsExtension,
 >     costTrackerExtension,
 >     steeringExtension,
@@ -526,8 +526,8 @@ A conforming implementation **MUST** execute the DAG as follows:
 For each execution group in topological order:
 
 1. The implementation **MUST** invoke `createAgentSession()` once per step (or once per agent for steps with `agents: [...]`).
-2. The prompt passed to `session.prompt()` **MUST** be assembled from: (a) the step's Markdown body, (b) transcripts from all upstream steps, and (c) the agent's system prompt, if defined.
-3. The implementation **MUST** load all eight gh-aw Pi extensions (see [Section 8](#8-extensions)) into each session.
+2. The prompt passed to `session.prompt()` **MUST** be assembled from: (a) the step's prompt text (from `prompt.txt`), (b) transcripts from all upstream steps, and (c) the agent's system prompt, if defined.
+3. The implementation **MUST** load all seven gh-aw Pi extensions (see [Section 8](#8-extensions)) into each session.
 4. Steps within the same parallel group **MUST** be executed concurrently using `Promise.all()`.
 5. After each session completes, the implementation **MUST** capture the session transcript for use by downstream steps.
 6. After capturing the transcript, the implementation **MUST** call `session.dispose()`.
@@ -539,12 +539,12 @@ The per-step execution sequence is:
 
 ```
 For each step (respecting DAG order):
-  1. Build prompt = step markdown + upstream transcripts + system prompt
-  2. Create Pi AgentSession with all gh-aw extensions:
+  1. Build prompt = step text (from prompt.txt) + upstream transcripts + system prompt
+  2. Create Pi AgentSession with gh-aw extensions:
      - api-proxy provider registered
-     - MCP bridge tools registered
      - Safe-output tools registered
      - Steering, repair, cost, observability extensions active
+     (MCP tools available as bash CLI commands via cli-proxy — no bridging needed)
   3. session.prompt() → Pi agent loop runs
   4. Extensions handle events (cost tracking, steering, observability)
   5. Capture transcript for downstream steps
@@ -557,7 +557,7 @@ For each step (respecting DAG order):
 
 All gh-aw-specific behavior **MUST** be packaged as Pi extensions. Each extension **MUST** be a standalone TypeScript module that exports a default function with signature `(pi: ExtensionAPI) => void | Promise<void>`.
 
-The following eight extensions **MUST** be loaded into every `AgentSession` created by the harness.
+The following seven extensions **MUST** be loaded into every `AgentSession` created by the harness.
 
 ### 8.1 Extension 1: api-proxy Provider
 
@@ -586,39 +586,7 @@ The following eight extensions **MUST** be loaded into every `AgentSession` crea
 > }
 > ```
 
-### 8.2 Extension 2: MCP Gateway Bridge
-
-**Purpose:** Bridges gh-aw's MCP gateway tools into Pi's tool system as `AgentTool` instances.
-
-**Requirements:**
-
-- The extension **MUST** read the MCP gateway configuration and register each MCP tool as a Pi `AgentTool` via `pi.registerTool()`.
-- Tool names **MUST** be namespaced as `<serverName>_<toolName>` to avoid collisions.
-- The `execute` handler **MUST** delegate to the MCP gateway via the existing gateway IPC mechanism.
-- `gh-proxy` and `cli-proxy` **MUST** be active in the container when this extension executes. The extension **MAY** assert their presence at startup and **MUST** fail with a descriptive error if they are absent.
-
-> [!NOTE] Non-normative example.
->
-> ```typescript
-> export default function(pi: ExtensionAPI) {
->   const gatewayConfig = loadMCPGatewayConfig();
->
->   for (const [serverName, tools] of Object.entries(gatewayConfig)) {
->     for (const tool of tools) {
->       pi.registerTool({
->         name: `${serverName}_${tool.name}`,
->         description: tool.description,
->         parameters: tool.inputSchema,
->         async execute(toolCallId, params, signal) {
->           return await callMCPGateway(serverName, tool.name, params);
->         },
->       });
->     }
->   }
-> }
-> ```
-
-### 8.3 Extension 3: Safe Outputs
+### 8.2 Extension 2: Safe Outputs
 
 **Purpose:** Registers safe-output tools (create-issue, create-pull-request, add-comment, etc.) and writes artifact files in the gh-aw safe-outputs format.
 
@@ -658,7 +626,7 @@ The following eight extensions **MUST** be loaded into every `AgentSession` crea
 > }
 > ```
 
-### 8.4 Extension 4: Cost Tracker
+### 8.3 Extension 3: Cost Tracker
 
 **Purpose:** Monitors token usage and cost via Pi's event stream and enforces budget gates.
 
@@ -697,7 +665,7 @@ The following eight extensions **MUST** be loaded into every `AgentSession` crea
 > }
 > ```
 
-### 8.5 Extension 5: Steering (Resource Pressure)
+### 8.4 Extension 4: Steering (Resource Pressure)
 
 **Purpose:** Monitors time remaining and budget, and injects steering messages via Pi's native `session.steer()`.
 
@@ -740,7 +708,7 @@ The following eight extensions **MUST** be loaded into every `AgentSession` crea
 > }
 > ```
 
-### 8.6 Extension 6: Session Repair
+### 8.5 Extension 5: Session Repair
 
 **Purpose:** Detects broken tool calls and repairs the session via Pi's message history manipulation.
 
@@ -776,7 +744,7 @@ The following eight extensions **MUST** be loaded into every `AgentSession` crea
 > }
 > ```
 
-### 8.7 Extension 7: Observability
+### 8.6 Extension 6: Observability
 
 **Purpose:** Emits JSONL events to stderr and generates OTel spans.
 
@@ -811,7 +779,7 @@ The following eight extensions **MUST** be loaded into every `AgentSession` crea
 > }
 > ```
 
-### 8.8 Extension 8: Checkpoint
+### 8.7 Extension 7: Checkpoint
 
 **Purpose:** Persists run state for long workflows, enabling resume from a prior checkpoint.
 
@@ -968,15 +936,14 @@ aw-harness/
 ├── tsconfig.json                 # target: es2024, module: es2022
 ├── build.ts                      # esbuild → dist/aw_harness.cjs
 ├── src/
-│   ├── index.ts                  # Entry point: parseWorkflow → buildDAG → run sessions
-│   ├── parser.ts                 # Workflow markdown → steps + config
+│   ├── index.ts                  # Entry point: read config.json + prompt.txt → buildDAG → run sessions
+│   ├── parser.ts                 # config.json + prompt.txt → steps + config
 │   ├── planner.ts                # DAG construction, topological sort
 │   ├── dag-runner.ts             # Orchestrate sessions (sequential + parallel)
 │   ├── transcript.ts             # Inter-step data flow (save/load/summarize)
 │   ├── context.ts                # Prompt assembly, compaction
 │   └── extensions/               # gh-aw Pi extensions
 │       ├── api-proxy-provider.ts # Register api-proxy as Pi provider
-│       ├── mcp-bridge.ts         # MCP gateway tools → Pi AgentTool
 │       ├── safe-outputs.ts       # Safe-output tools + artifact writing
 │       ├── cost-tracker.ts       # Budget gates via turn_end events
 │       ├── steering.ts           # Time/budget pressure via session.steer()
@@ -1031,33 +998,31 @@ The following ordered work items describe the implementation sequence:
 
 2. **Implement api-proxy provider extension** — Pi extension that registers the api-proxy as a custom provider via `pi.registerProvider()`. Async factory fetches available models at startup. All model requests route through the proxy.
 
-3. **Implement parser** — Read workflow markdown, extract frontmatter, parse `harness:` block, extract steps from heading boundaries and `<!-- harness-step -->` annotations. Fall back to single-step mode.
+3. **Implement parser** — Read `config.json` (compiler-generated harness config) and `prompt.txt` (compiler-generated prompt body). Parse step sections from prompt text; resolve step configuration from `harness.steps`. Fall back to single-step mode when no step config is present.
 
 4. **Implement DAG planner** — Topological sort, parallel group detection, sequential fallback. Validate no cycles, all agent/step references resolve.
 
-5. **Implement MCP bridge extension** — Pi extension that reads MCP gateway config and registers each MCP tool as a Pi `AgentTool` via `pi.registerTool()`.
-
-6. **Implement safe-outputs extension** — Pi extension that registers safe-output tools (create-issue, create-pull-request, add-comment, etc.). Uses `pi.on("agent_end")` to finalize artifact manifest.
+5. **Implement safe-outputs extension** — Pi extension that registers safe-output tools (create-issue, create-pull-request, add-comment, etc.). Uses `pi.on("agent_end")` to finalize artifact manifest.
 
-7. **Implement DAG runner** — Orchestrates multiple `createAgentSession()` calls. Sequential steps + `Promise.all()` for parallel groups. Passes gh-aw extensions to each session. Manages transcript flow between steps.
+6. **Implement DAG runner** — Orchestrates multiple `createAgentSession()` calls. Sequential steps + `Promise.all()` for parallel groups. Passes gh-aw extensions to each session. Manages transcript flow between steps.
 
-8. **Implement transcript manager** — Save step output to disk. Load for downstream steps. Support `summary` mode (use a Pi session to summarize) and `full` mode.
+7. **Implement transcript manager** — Save step output to disk. Load for downstream steps. Support `summary` mode (use a Pi session to summarize) and `full` mode.
 
-9. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
+8. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
 
-10. **Implement cost tracker extension** — Pi extension that monitors `turn_end` events for token/cost data. Enforces soft (steer warning) and hard (abort) budget gates.
+9. **Implement cost tracker extension** — Pi extension that monitors `turn_end` events for token/cost data. Enforces soft (steer warning) and hard (abort) budget gates.
 
-11. **Implement steering extension** — Pi extension that monitors time/budget and injects user messages via `session.steer()` on `turn_end`.
+10. **Implement steering extension** — Pi extension that monitors time/budget and injects user messages via `session.steer()` on `turn_end`.
 
-12. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
+11. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
 
-13. **Implement checkpoint extension** — Pi extension that persists step completion state on `agent_end`. Resume from checkpoint on `--continue`.
+12. **Implement checkpoint extension** — Pi extension that persists step completion state on `agent_end`. Resume from checkpoint on `--continue`.
 
-14. **Implement observability extension** — Pi extension that emits JSONL to stderr on agent/tool events. Generates OTel spans using `observability.otlp` config.
+13. **Implement observability extension** — Pi extension that emits JSONL to stderr on agent/tool events. Generates OTel spans using `observability.otlp` config.
 
-15. **Write tests** — Unit tests for parser, planner, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
+14. **Write tests** — Unit tests for parser, planner, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
 
-16. **Write example workflows** — Three examples: single-step, multi-step sequential, multi-agent parallel with different models.
+15. **Write example workflows** — Three examples: single-step, multi-step sequential, multi-agent parallel with different models.
 
 17. **Add build to Makefile** — Add `make aw-harness` target that runs esbuild and copies `aw_harness.cjs` to `actions/setup/js/`.
 
@@ -1065,7 +1030,7 @@ The following ordered work items describe the implementation sequence:
 
 ## 11. Security Considerations
 
-**Mandatory proxy features.** The `gh-proxy` and `cli-proxy` features **MUST** always be active for `engine: aw`. Disabling them would leave MCP gateway tools inaccessible to agent sessions, and any attempt by a workflow author to disable them **MUST** be silently overridden (see [Section 6.2](#62-overrides-and-fixed-settings)).
+**Mandatory proxy features.** The `gh-proxy` and `cli-proxy` features **MUST** always be active for `engine: aw`. MCP tools are available to agent sessions as CLI commands via `cli-proxy`; disabling it would make those tools inaccessible. Any attempt by a workflow author to disable either feature **MUST** be silently overridden (see [Section 6.2](#62-overrides-and-fixed-settings)).
 
 **No direct LLM access.** The harness **MUST NOT** make direct calls to LLM provider APIs. All model requests **MUST** pass through the api-proxy, which enforces rate limits, budget caps, and access controls at the container boundary.
 

From 384419808321b62c48dcede76ce45ebd6e0b803b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 04:17:36 +0000
Subject: [PATCH 07/14] =?UTF-8?q?docs:=20remove=20--agent-file=20flag;=20d?=
 =?UTF-8?q?irect=20provider=20env=20vars;=20no=20harness=20key;=20rename?=
 =?UTF-8?q?=20steps=E2=86=92tasks?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/e3669f00-f88f-43f3-b828-20c156761234

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 308 +++++++++++++++++++-------------------------
 1 file changed, 131 insertions(+), 177 deletions(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 22d30d6174a..9001f9943c6 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -2,7 +2,7 @@
 
 ---
 
-**Title:** AW Harness — Multi-Step Agentic Workflow Execution Engine
+**Title:** AW Harness — Multi-Task Agentic Workflow Execution Engine
 
 **Status:** Unofficial Draft
 
@@ -14,7 +14,7 @@
 
 ## Abstract
 
-This document specifies the **AW Harness** (`aw_harness.cjs`), a Node.js execution engine for the `engine: aw` mode of GitHub Agentic Workflows (gh-aw). The harness provides multi-step orchestration, multi-agent coordination, context engineering, cost tracking, and observability, built on top of the Pi agent SDK ecosystem. All gh-aw-specific capabilities are implemented as Pi extensions using Pi's native `ExtensionAPI` extensibility mechanism.
+This document specifies the **AW Harness** (`aw_harness.cjs`), a Node.js execution engine for the `engine: aw` mode of GitHub Agentic Workflows (gh-aw). The harness provides multi-task orchestration, multi-agent coordination, context engineering, cost tracking, and observability, built on top of the Pi agent SDK ecosystem. All gh-aw-specific capabilities are implemented as Pi extensions using Pi's native `ExtensionAPI` extensibility mechanism.
 
 ## Status of This Document
 
@@ -44,11 +44,11 @@ This is an internal design specification for the GitHub gh-aw project. It is not
 
 *(This section is non-normative.)*
 
-The existing gh-aw harnesses (`copilot_harness.cjs`, `claude_harness.cjs`) are thin retry loops around a single CLI invocation. As workflow complexity grows, authors need multi-step orchestration, parallel agent execution, per-step model selection, budget management, and structured observability — none of which the current harnesses provide.
+The existing gh-aw harnesses (`copilot_harness.cjs`, `claude_harness.cjs`) are thin retry loops around a single CLI invocation. As workflow complexity grows, authors need multi-task orchestration, parallel agent execution, per-task model selection, budget management, and structured observability — none of which the current harnesses provide.
 
-The AW Harness introduces `engine: aw` as a new opt-in execution engine. It does not replace existing engines; `engine: copilot`, `engine: claude`, and `engine: codex` continue to operate unchanged via their current harnesses. The AW Harness is a Pi SDK application: it creates one `AgentSession` per workflow step, loads a fixed set of gh-aw Pi extensions into each session, and orchestrates sessions according to a DAG derived from the workflow's `harness:` frontmatter block and Markdown heading structure.
+The AW Harness introduces `engine: aw` as a new opt-in execution engine. It does not replace existing engines; `engine: copilot`, `engine: claude`, and `engine: codex` continue to operate unchanged via their current harnesses. The AW Harness is a Pi SDK application: it creates one `AgentSession` per workflow task, loads a fixed set of gh-aw Pi extensions into each session, and orchestrates sessions according to a DAG derived from the workflow's `harness:` frontmatter block and Markdown heading structure.
 
-The harness is designed exclusively for the gh-aw Actions container environment. It assumes the firewall, api-proxy, and MCP gateway are already running. It performs no direct LLM API calls and requires no additional authentication setup beyond what the container already provides.
+The harness is designed exclusively for the gh-aw Actions container environment. It assumes the firewall and MCP gateway are already running. AWF injects provider credentials into the container environment; the harness reads these credentials and passes them to Pi SDK directly.
 
 ### 1.1 Scope
 
@@ -56,9 +56,9 @@ This specification covers:
 
 - The entry-point invocation contract for `aw_harness.cjs`.
 - The frontmatter schema for `engine: aw` workflows.
-- The step-extraction and DAG-construction algorithms.
-- The normative requirements for each of the eight gh-aw Pi extensions.
-- The model resolution contract via the api-proxy.
+- The task-extraction and DAG-construction algorithms.
+- The normative requirements for each of the seven gh-aw Pi extensions.
+- The model connection contract via provider environment variables.
 - The build and deployment configuration.
 
 This specification does not cover:
@@ -66,7 +66,7 @@ This specification does not cover:
 - The compilation of workflow Markdown to GitHub Actions YAML (handled by `gh-aw` proper).
 - Safe-outputs post-processing and threat detection (handled by post-agent jobs, unchanged).
 - The Pi SDK internals (`pi-agent-core`, `pi-ai`).
-- The api-proxy internals (`model-resolver.js`).
+- LLM provider internals or credential rotation.
 
 ### 1.2 Background and Motivation
 
@@ -97,19 +97,19 @@ A **conforming implementation** is one that satisfies all **MUST** and **MUST NO
 ## 3. Terminology and Definitions
 
 **AW Harness**
-: The execution engine implemented in `aw_harness.cjs`, invoked when a workflow declares `engine: aw`. It is responsible for parsing the workflow, constructing the DAG, and orchestrating one `AgentSession` per step.
+: The execution engine implemented in `aw_harness.cjs`, invoked when a workflow declares `engine: aw`. It is responsible for parsing the workflow, constructing the DAG, and orchestrating one `AgentSession` per task.
 
 **AgentSession**
 : A Pi SDK session object, obtained via `createAgentSession()`, that manages a single agent's message loop, tool calls, and event stream.
 
 **api-proxy**
-: A sidecar process (`gh-aw-firewall/containers/api-proxy/model-resolver.js`) running in the gh-aw container. It accepts OpenAI-compatible API requests, resolves model aliases, and routes calls to the appropriate LLM provider. The harness communicates with it at `http://localhost:8080/v1` by default.
+: A sidecar process in the gh-aw container used by other engines for model routing. The AW Harness does **not** use the api-proxy; it connects to LLM providers directly via environment variables.
 
 **cli-proxy**
 : A feature that mounts MCP servers as CLI tools on `PATH`, making them callable as ordinary shell commands within agent sessions.
 
 **DAG (Directed Acyclic Graph)**
-: The execution graph derived from the workflow's `harness.steps` declarations and implicit document-order dependencies. Nodes are steps; directed edges encode `depends` relationships. Cycles are prohibited.
+: The execution graph derived from the workflow's `harness.tasks` declarations and implicit document-order dependencies. Nodes are tasks; directed edges encode `depends` relationships. Cycles are prohibited.
 
 **ExtensionAPI**
 : The Pi SDK interface (`ExtensionAPI` from `@mariozechner/pi-coding-agent`) that a Pi extension receives as its sole argument. Provides `pi.registerTool()`, `pi.registerProvider()`, and `pi.on()`.
@@ -117,14 +117,14 @@ A **conforming implementation** is one that satisfies all **MUST** and **MUST NO
 **gh-proxy**
 : A feature that provides a pre-authenticated `gh` CLI binary in the agent's bash environment, enabling direct GitHub API access without separate token management.
 
-**harness step**
-: A unit of work within an `engine: aw` workflow. Each step is assigned one `AgentSession`, a prompt derived from the corresponding Markdown section, and optionally a named agent definition.
+**harness task**
+: A unit of work within an `engine: aw` workflow. Each task is assigned one `AgentSession`, a prompt derived from the corresponding Markdown section, and optionally a named agent definition.
 
 **MCP Gateway**
 : The gh-aw MCP gateway process that exposes GitHub tools and custom MCP server tools as CLI commands (via `cli-proxy`) in the agent's bash environment. It runs independently of the harness in the same container.
 
 **model alias**
-: A short name (e.g., `"sonnet"`, `"gpt-5-codex"`) resolved by the api-proxy to a fully-qualified `provider/model` string. The harness passes aliases through without resolution.
+: A short name (e.g., `"sonnet"`, `"gpt-5-codex"`) that Pi SDK resolves to a fully-qualified `provider/model` string using the provider registrations configured by Extension 1.
 
 **Pi extension**
 : A TypeScript module that exports a default function with signature `(pi: ExtensionAPI) => void | Promise<void>`. Loaded into an `AgentSession` to register tools, subscribe to events, or register providers.
@@ -132,11 +132,11 @@ A **conforming implementation** is one that satisfies all **MUST** and **MUST NO
 **safe output**
 : A deferred GitHub action (create issue, create pull request, add comment, etc.) expressed as an artifact file written during agent execution and processed by the post-agent job.
 
-**step annotation**
-: An HTML comment of the form `<!-- harness-step: <name> -->` embedded in a Markdown section to associate that section with a named entry in `harness.steps`.
+**task annotation**
+: An HTML comment of the form `<!-- harness-task: <name> -->` embedded in a Markdown section to associate that section with a named entry in `harness.tasks`.
 
 **transcript**
-: The complete message history of a completed `AgentSession`, optionally summarized, passed as context to downstream steps.
+: The complete message history of a completed `AgentSession`, optionally summarized, passed as context to downstream tasks.
 
 **workflow document**
 : A Markdown file with YAML frontmatter that declares an `engine: aw` workflow. The frontmatter is parsed by the gh-aw compiler at compile time; the harness itself never reads the raw Markdown file. Instead, the compiler provides the harness with pre-processed inputs: `config.json` (harness configuration), `prompt.txt` (extracted prompt body), and any referenced agent files.
@@ -154,22 +154,22 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 │  GitHub Actions Job (compiled from .lock.yml by gh-aw)       │
 │                                                              │
 │  ┌──────────────────────────────────────────────────────┐   │
-│  │  Container (firewall, api-proxy, MCP gateway)         │   │
+│  │  Container (firewall, MCP gateway)                   │   │
 │  │                                                       │   │
 │  │  ┌─────────────────────────────────────────────────┐ │   │
 │  │  │  aw_harness.cjs (entry point)                   │ │   │
 │  │  │                                                  │ │   │
 │  │  │  1. Reads config.json + prompt.txt (pre-parsed by compiler) │ │   │
-│  │  │  2. Parses steps from step config + prompt text            │ │   │
+│  │  │  2. Parses tasks from task config + prompt text            │ │   │
 │  │  │  3. Builds execution DAG                                   │ │   │
-│  │  │  4. For each step: creates Pi AgentSession                 │ │   │
+│  │  │  4. For each task: creates Pi AgentSession                 │ │   │
 │  │  │     with gh-aw extensions loaded                           │ │   │
 │  │  │  5. session.prompt() → Pi drives the agent                 │ │   │
 │  │  │                                                  │ │   │
 │  │  │  ┌──────────────────────────────────────────┐   │ │   │
 │  │  │  │  Pi SDK (createAgentSession)             │   │ │   │
 │  │  │  │  ├─ pi-agent-core (agent loop, events)   │   │ │   │
-│  │  │  │  ├─ pi-ai → api-proxy → LLM providers   │   │ │   │
+│  │  │  │  ├─ pi-ai → provider env vars → LLM providers │   │ │   │
 │  │  │  │  └─ compaction, steering, auto-retry      │   │ │   │
 │  │  │  └──────────────────────────────────────────┘   │ │   │
 │  │  │  ┌──────────────────────────────────────────┐   │ │   │
@@ -183,10 +183,6 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 │  │  │  │  └─ checkpoint (persist/resume state)     │   │ │   │
 │  │  │  └──────────────────────────────────────────┘   │ │   │
 │  │  │  ┌──────────────────────────────────────────┐   │ │   │
-│  │  │  │  api-proxy (model alias resolution)      │   │ │   │
-│  │  │  │  └─ Recursive alias → provider routing    │   │ │   │
-│  │  │  └──────────────────────────────────────────┘   │ │   │
-│  │  │  ┌──────────────────────────────────────────┐   │ │   │
 │  │  │  │  MCP Gateway (gh-aw, already running)    │   │ │   │
 │  │  │  │  └─ GitHub tools, custom MCP servers      │   │ │   │
 │  │  │  └──────────────────────────────────────────┘   │ │   │
@@ -206,9 +202,9 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 
 2. **Extensions-first.** Every gh-aw feature **MUST** be implemented as a proper Pi extension using `ExtensionAPI`. Extensions **MUST** use `pi.registerTool()` for tools, `pi.on()` for events, and `pi.registerProvider()` for model routing, making them reusable outside gh-aw.
 
-3. **api-proxy for model resolution.** Pi's `pi-ai` **MUST** be configured to communicate with the api-proxy as an OpenAI-compatible provider. Model names (aliases or explicit) **MUST** be passed through to the proxy without local resolution.
+3. **Direct provider connections.** Pi SDK **MUST** be configured to use the LLM provider credentials that AWF injects into the container environment. The harness **MUST NOT** interpose an additional proxy layer; provider routing and model selection **MUST** be handled by the Pi SDK and the provider-specific environment variables.
 
-4. **Optimized for gh-aw container.** The harness **MUST** assume that the firewall, api-proxy, and MCP gateway are already running. It **MUST NOT** perform direct LLM API calls, redundant authentication, or network configuration. MCP tools are available to agent sessions as bash CLI tools via `cli-proxy` — no additional bridging is required.
+4. **Optimized for gh-aw container.** The harness **MUST** assume that the firewall and MCP gateway are already running. It **MUST NOT** perform redundant network configuration. MCP tools are available to agent sessions as bash CLI tools via `cli-proxy` — no additional bridging is required.
 
 5. **`gh-proxy` and `cli-proxy` always on.** GitHub and other MCP server tools are available to the agent as CLI commands on `PATH` (via `cli-proxy`) and via the pre-authenticated `gh` binary (via `gh-proxy`). A conforming implementation **MUST** enable both `gh-proxy` and `cli-proxy` when `engine: aw` is selected. A conforming implementation **MUST NOT** honor attempts to disable these features for `engine: aw`, regardless of the values specified in the workflow frontmatter (see [Section 6.2](#62-overrides-and-fixed-settings)).
 
@@ -218,7 +214,7 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 
 8. **New opt-in engine.** `engine: aw` is an independent opt-in. Existing engines **MUST** be untouched.
 
-9. **Markdown-native steps.** `## Heading` or `### Heading` elements **MUST** be recognized as step boundaries. HTML comments carry step metadata.
+9. **Markdown-native tasks.** `## Heading` or `### Heading` elements **MUST** be recognized as task boundaries. HTML comments carry task metadata.
 
 10. **Observable.** All implementations **MUST** emit a JSONL event stream to stderr and **SHOULD** generate OTel spans when an OTLP endpoint is configured.
 
@@ -231,37 +227,30 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 The AW Harness **MUST** be invocable as a Node.js CommonJS module from the command line. The gh-aw compiler pre-processes the workflow markdown (parsing frontmatter, extracting the prompt body, resolving imports) and provides the harness with pre-built input files. A conforming invocation has the form:
 
 ```
-node aw_harness.cjs --config <config-path> --prompt <prompt-path> [--agent-file <agent-path>]
+node aw_harness.cjs --config <config-path> --prompt <prompt-path>
 ```
 
 where:
-- `<config-path>` is the path to the compiler-generated `config.json` file containing the parsed harness configuration.
+- `<config-path>` is the path to the compiler-generated `config.json` file containing the parsed harness configuration (including resolved agent file paths).
 - `<prompt-path>` is the path to the compiler-generated `prompt.txt` file containing the extracted prompt body.
-- `<agent-path>` is the optional path to a compiled agent file (`.md` with frontmatter stripped), provided when the workflow references an agent via `imports:`.
 
 A conforming implementation **MUST NOT** read or parse workflow Markdown files directly; all configuration and prompt content **MUST** be consumed from the pre-processed input files provided by the compiler.
 
 ### 5.2 Environment Variables
 
-A conforming implementation **MUST** read the following environment variables:
-
-| Variable | Description | Default |
-|----------|-------------|---------|
-| `AWF_API_PROXY_URL` | Base URL of the api-proxy OpenAI-compatible endpoint | `http://localhost:8080/v1` |
-| `AWF_API_PROXY_TOKEN` | Bearer token for api-proxy authentication | *(required; no default)* |
-| `AWF_MODEL_ALIASES` | JSON string containing model alias configuration | *(empty; aliases resolved by proxy)* |
+A conforming implementation **MUST** read LLM provider credentials from the container environment. AWF sets up the appropriate provider-specific environment variables (e.g., `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GITHUB_TOKEN`) for whichever providers are enabled in the workflow configuration. The harness **MUST NOT** hard-code any provider URL or token; it **MUST** rely exclusively on the environment injected by AWF.
 
-A conforming implementation **SHOULD** read additional standard GitHub Actions environment variables (`GITHUB_REPOSITORY`, `GITHUB_RUN_ID`, etc.) for use in observability spans and checkpoint keys.
+A conforming implementation **SHOULD** read standard GitHub Actions environment variables (`GITHUB_REPOSITORY`, `GITHUB_RUN_ID`, etc.) for use in observability spans and checkpoint keys.
 
 ### 5.3 Exit Codes
 
 | Code | Meaning |
 |------|---------|
-| `0` | All steps completed successfully |
-| `1` | One or more steps failed (non-recoverable error) |
+| `0` | All tasks completed successfully |
+| `1` | One or more tasks failed (non-recoverable error) |
 | `2` | Invocation error (missing config path, unreadable config file) |
 
-A conforming implementation **MUST** exit with code `0` if and only if all DAG steps complete without error. It **MUST** exit with a non-zero code on any unrecovered failure.
+A conforming implementation **MUST** exit with code `0` if and only if all DAG tasks complete without error. It **MUST** exit with a non-zero code on any unrecovered failure.
 
 ### 5.4 Standard Streams
 
@@ -288,8 +277,7 @@ An `engine: aw` workflow document **MUST** include a YAML frontmatter block conf
 >
 > engine:
 >   id: aw
->   model: sonnet                  # Model alias — api-proxy resolves
->   harness: aw_harness.cjs
+>   model: sonnet                  # Model alias — provider resolves
 >
 > permissions:
 >   contents: read
@@ -349,7 +337,7 @@ An `engine: aw` workflow document **MUST** include a YAML frontmatter block conf
 >         You synthesize multiple review perspectives into a single,
 >         prioritized action list.
 >
->   steps:
+>   tasks:
 >     parallel-review:
 >       agents: [reviewer, scanner]
 >       parallel: true
@@ -371,16 +359,16 @@ An `engine: aw` workflow document **MUST** include a YAML frontmatter block conf
 > Review all changes pushed to the default branch in the last 24 hours.
 >
 > ### Gather Changes
-> <!-- harness-step: gather -->
+> <!-- harness-task: gather -->
 > Use `git log --since="24 hours ago"` and `git diff` to collect all
 > recent changes. Summarize the scope.
 >
 > ### Parallel Review
-> <!-- harness-step: parallel-review -->
+> <!-- harness-task: parallel-review -->
 > Each reviewer examines the changes independently.
 >
 > ### Synthesize
-> <!-- harness-step: synthesize -->
+> <!-- harness-task: synthesize -->
 > Read outputs from both reviewers. Produce a prioritized findings list.
 >
 > ### Report
@@ -400,7 +388,7 @@ The `harness.context` key is **OPTIONAL**. When present, it **MAY** contain:
 
 - `compaction` (string): One of `none`, `sliding-window`, or `summarize`. Default: `none`.
 - `compaction-threshold` (number, 0–1): Context fill fraction at which compaction triggers. Default: `0.75`.
-- `transcript-mode` (string): One of `full` or `summary`. Controls how upstream step transcripts are included in downstream prompts. Default: `full`.
+- `transcript-mode` (string): One of `full` or `summary`. Controls how upstream task transcripts are included in downstream prompts. Default: `full`.
 
 #### 6.1.3 `harness.agents`
 
@@ -409,14 +397,14 @@ The `harness.agents` key is **OPTIONAL**. Each entry defines a named agent with:
 - `model` (string, **REQUIRED**): Model alias or fully-qualified `provider/model` string.
 - `system` (string, **OPTIONAL**): System prompt override for this agent.
 
-#### 6.1.4 `harness.steps`
+#### 6.1.4 `harness.tasks`
 
-The `harness.steps` key is **OPTIONAL**. Each entry defines a named step with:
+The `harness.tasks` key is **OPTIONAL**. Each entry defines a named task with:
 
 - `agent` (string, **OPTIONAL**): Name of an agent defined in `harness.agents`. Mutually exclusive with `agents`.
-- `agents` (array of strings, **OPTIONAL**): Names of agents to run in parallel for this step. Mutually exclusive with `agent`.
+- `agents` (array of strings, **OPTIONAL**): Names of agents to run in parallel for this task. Mutually exclusive with `agent`.
 - `parallel` (boolean, **OPTIONAL**): When `true` and `agents` is provided, all listed agents execute in parallel. Default: `false`.
-- `depends` (array of strings, **OPTIONAL**): Names of steps that **MUST** complete before this step begins.
+- `depends` (array of strings, **OPTIONAL**): Names of tasks that **MUST** complete before this task begins.
 
 #### 6.1.5 `harness.steering`
 
@@ -429,7 +417,7 @@ The `harness.steering` key is **OPTIONAL**. When present, it **MAY** contain:
 
 #### 6.1.6 `harness.checkpoint`
 
-The `harness.checkpoint` key is **OPTIONAL**. When set to `true`, the checkpoint extension **MUST** persist step state on `agent_end`.
+The `harness.checkpoint` key is **OPTIONAL**. When set to `true`, the checkpoint extension **MUST** persist task state on `agent_end`.
 
 ### 6.2 Overrides and Fixed Settings
 
@@ -442,14 +430,14 @@ A conforming implementation **MUST** apply the following overrides regardless of
 
 A conforming implementation **MUST NOT** honor attempts to disable `cli-proxy` or set `tools.github.mode: remote` when `engine: aw` is active. These settings **MUST** be overridden. A conforming implementation **MUST** emit a warning to stderr when either override is applied, so that workflow authors can diagnose unexpected configuration behaviour.
 
-### 6.3 Step Extraction Algorithm
+### 6.3 Task Extraction Algorithm
 
-A conforming implementation **MUST** extract steps from the compiler-provided inputs as follows:
+A conforming implementation **MUST** extract tasks from the compiler-provided inputs as follows:
 
-1. Read `harness.steps` from `config.json`. If present, each entry defines a named step with its agent assignment, parallel flag, and dependency list.
-2. Read the prompt body from `prompt.txt`. The compiler splits the body on ATX heading boundaries (`##` or `###` level) and associates each section with a named step via `<!-- harness-step: <name> -->` annotations before writing `prompt.txt`; the harness consumes the resulting sections.
-3. If `harness.steps` is absent or empty, the entire prompt body **MUST** be treated as a single step with no explicit agent or dependency.
-4. Steps without a named entry in `harness.steps` are treated as sequential steps in document order.
+1. Read `harness.tasks` from `config.json`. If present, each entry defines a named task with its agent assignment, parallel flag, and dependency list.
+2. Read the prompt body from `prompt.txt`. The compiler splits the body on ATX heading boundaries (`##` or `###` level) and associates each section with a named task via `<!-- harness-task: <name> -->` annotations before writing `prompt.txt`; the harness consumes the resulting sections.
+3. If `harness.tasks` is absent or empty, the entire prompt body **MUST** be treated as a single task with no explicit agent or dependency.
+4. Tasks without a named entry in `harness.tasks` are treated as sequential tasks in document order.
 
 ### 6.4 Initial Prompt Context
 
@@ -457,8 +445,8 @@ The AW Harness **MUST NOT** inject any predefined or ambient context into agent
 
 A conforming implementation **MUST** source every item included in a session's initial prompt from one of the following explicitly declared origins:
 
-- The step's own Markdown body (extracted per [Section 6.3](#63-step-extraction-algorithm)).
-- Transcripts from upstream steps (passed via the DAG execution model in [Section 7](#7-dag-execution-model)).
+- The task's own Markdown body (extracted per [Section 6.3](#63-task-extraction-algorithm)).
+- Transcripts from upstream tasks (passed via the DAG execution model in [Section 7](#7-dag-execution-model)).
 - The agent's `system` prompt as declared under `harness.agents` in the frontmatter.
 - Files or sub-workflows declared via the standard `imports:` frontmatter key and resolved by the compiler into agent files passed at invocation time.
 
@@ -473,12 +461,12 @@ A conforming implementation **MUST NOT** automatically load AGENTS.md files, `.g
 
 ### 7.1 DAG Construction
 
-A conforming implementation **MUST** construct a DAG from the extracted steps as follows:
+A conforming implementation **MUST** construct a DAG from the extracted tasks as follows:
 
-1. Create one node per extracted step.
-2. For each step with a `depends` list, add a directed edge from each named dependency to that step.
+1. Create one node per extracted task.
+2. For each task with a `depends` list, add a directed edge from each named dependency to that task.
 3. Perform a cycle check. If a cycle is detected, the implementation **MUST** abort with exit code `2` and emit a diagnostic to stderr identifying the cycle.
-4. Compute a topological order. Steps at the same depth **MAY** be executed in parallel.
+4. Compute a topological order. Tasks at the same depth **MAY** be executed in parallel.
 
 ### 7.2 Execution Algorithm
 
@@ -495,7 +483,7 @@ A conforming implementation **MUST** execute the DAG as follows:
 >   const workflow = loadWorkflow(configPath, promptPath);
 >   const dag = buildDAG(workflow);
 >   const extensions = [
->     apiProxyProvider,
+>     providerSetupExtension,
 >     safeOutputsExtension,
 >     costTrackerExtension,
 >     steeringExtension,
@@ -504,19 +492,19 @@ A conforming implementation **MUST** execute the DAG as follows:
 >     checkpointExtension,
 >   ];
 >
->   for (const stepGroup of dag.executionOrder()) {
->     await Promise.all(stepGroup.map(async (step) => {
+>   for (const taskGroup of dag.executionOrder()) {
+>     await Promise.all(taskGroup.map(async (task) => {
 >       const { session } = await createAgentSession({
 >         sessionManager: SessionManager.inMemory(),
 >         extensions,
->         model: resolveModel(step.agent?.model || workflow.defaultModel),
->         systemPrompt: buildSystemPrompt(step),
+>         model: resolveModel(task.agent?.model || workflow.defaultModel),
+>         systemPrompt: buildSystemPrompt(task),
 >       });
 >
->       const prompt = buildStepPrompt(step, transcripts);
+>       const prompt = buildTaskPrompt(task, transcripts);
 >       await session.prompt(prompt);
 >
->       transcripts[step.name] = captureTranscript(session);
+>       transcripts[task.name] = captureTranscript(session);
 >       session.dispose();
 >     }));
 >   }
@@ -525,29 +513,29 @@ A conforming implementation **MUST** execute the DAG as follows:
 
 For each execution group in topological order:
 
-1. The implementation **MUST** invoke `createAgentSession()` once per step (or once per agent for steps with `agents: [...]`).
-2. The prompt passed to `session.prompt()` **MUST** be assembled from: (a) the step's prompt text (from `prompt.txt`), (b) transcripts from all upstream steps, and (c) the agent's system prompt, if defined.
+1. The implementation **MUST** invoke `createAgentSession()` once per task (or once per agent for tasks with `agents: [...]`).
+2. The prompt passed to `session.prompt()` **MUST** be assembled from: (a) the task's prompt text (from `prompt.txt`), (b) transcripts from all upstream tasks, and (c) the agent's system prompt, if defined.
 3. The implementation **MUST** load all seven gh-aw Pi extensions (see [Section 8](#8-extensions)) into each session.
-4. Steps within the same parallel group **MUST** be executed concurrently using `Promise.all()`.
-5. After each session completes, the implementation **MUST** capture the session transcript for use by downstream steps.
+4. Tasks within the same parallel group **MUST** be executed concurrently using `Promise.all()`.
+5. After each session completes, the implementation **MUST** capture the session transcript for use by downstream tasks.
 6. After capturing the transcript, the implementation **MUST** call `session.dispose()`.
 7. If the budget gate has been triggered (via the cost-tracker extension), the implementation **MUST NOT** launch further sessions and **MUST** exit with code `1`.
 
-### 7.3 Step Execution Summary
+### 7.3 Task Execution Summary
 
-The per-step execution sequence is:
+The per-task execution sequence is:
 
 ```
-For each step (respecting DAG order):
-  1. Build prompt = step text (from prompt.txt) + upstream transcripts + system prompt
+For each task (respecting DAG order):
+  1. Build prompt = task text (from prompt.txt) + upstream transcripts + system prompt
   2. Create Pi AgentSession with gh-aw extensions:
-     - api-proxy provider registered
+     - Provider setup registered
      - Safe-output tools registered
      - Steering, repair, cost, observability extensions active
      (MCP tools available as bash CLI commands via cli-proxy — no bridging needed)
   3. session.prompt() → Pi agent loop runs
   4. Extensions handle events (cost tracking, steering, observability)
-  5. Capture transcript for downstream steps
+  5. Capture transcript for downstream tasks
   6. Budget gate check, checkpoint state
 ```
 
@@ -559,30 +547,33 @@ All gh-aw-specific behavior **MUST** be packaged as Pi extensions. Each extensio
 
 The following seven extensions **MUST** be loaded into every `AgentSession` created by the harness.
 
-### 8.1 Extension 1: api-proxy Provider
+### 8.1 Extension 1: Provider Setup
 
-**Purpose:** Registers the gh-aw api-proxy as a custom Pi provider.
+**Purpose:** Registers LLM providers with Pi SDK using credentials from the container environment.
 
 **Requirements:**
 
-- The extension **MUST** call `pi.registerProvider()` with the api-proxy base URL and the `AWF_API_PROXY_TOKEN` environment variable as the API key reference.
-- The extension **MUST** fetch the available model list from the api-proxy at startup, before any session begins.
-- The extension **MUST** use `"openai-completions"` as the API type, as the api-proxy speaks the OpenAI completions protocol.
-- The `AWF_API_PROXY_URL` environment variable **MUST** be used as the base URL, defaulting to `http://localhost:8080/v1`.
+- The extension **MUST** call `pi.registerProvider()` for each LLM provider whose credentials are present in the environment (e.g., `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GITHUB_TOKEN`).
+- The extension **MUST NOT** hard-code provider URLs or API keys; all credentials **MUST** come from environment variables injected by AWF.
+- The extension **MUST** register at least one provider before any session begins; if no provider credentials are found, the extension **MUST** fail with a descriptive error.
 
 > [!NOTE] Non-normative example.
 >
 > ```typescript
 > export default async function(pi: ExtensionAPI) {
->   const proxyUrl = process.env.AWF_API_PROXY_URL || "http://localhost:8080/v1";
->   const models = await fetchModelsFromProxy(proxyUrl);
->
->   pi.registerProvider("aw-proxy", {
->     baseUrl: proxyUrl,
->     apiKey: "AWF_API_PROXY_TOKEN",
->     api: "openai-completions",
->     models,
->   });
+>   if (process.env.ANTHROPIC_API_KEY) {
+>     pi.registerProvider("anthropic", {
+>       apiKey: process.env.ANTHROPIC_API_KEY,
+>       api: "anthropic",
+>     });
+>   }
+>   if (process.env.OPENAI_API_KEY) {
+>     pi.registerProvider("openai", {
+>       apiKey: process.env.OPENAI_API_KEY,
+>       api: "openai-completions",
+>     });
+>   }
+>   // Additional providers registered as their env vars are present
 > }
 > ```
 
@@ -752,16 +743,16 @@ The following seven extensions **MUST** be loaded into every `AgentSession` crea
 
 - The extension **MUST** subscribe to `agent_start`, `turn_end`, `tool_execution_end`, and `agent_end` events.
 - On each event, the extension **MUST** emit a corresponding JSONL record to stderr.
-- If `observability.otlp.endpoint` is configured in the workflow frontmatter, the extension **MUST** create and close OTel spans for each step.
-- OTel span attributes **MUST** include at minimum: step name, model, token counts, and cost.
+- If `observability.otlp.endpoint` is configured in the workflow frontmatter, the extension **MUST** create and close OTel spans for each task.
+- OTel span attributes **MUST** include at minimum: task name, model, token counts, and cost.
 
 > [!NOTE] Non-normative example.
 >
 > ```typescript
 > export default function(pi: ExtensionAPI) {
 >   pi.on("agent_start", async (event) => {
->     emitJsonl({ event: "step_start", step: currentStep, model: currentModel });
->     startOtelSpan(currentStep);
+>     emitJsonl({ event: "task_start", task: currentTask, model: currentModel });
+>     startOtelSpan(currentTask);
 >   });
 >
 >   pi.on("turn_end", async (event) => {
@@ -773,8 +764,8 @@ The following seven extensions **MUST** be loaded into every `AgentSession` crea
 >   });
 >
 >   pi.on("agent_end", async (event) => {
->     emitJsonl({ event: "step_end", step: currentStep, tokens: event.tokens, cost: event.cost });
->     endOtelSpan(currentStep);
+>     emitJsonl({ event: "task_end", task: currentTask, tokens: event.tokens, cost: event.cost });
+>     endOtelSpan(currentTask);
 >   });
 > }
 > ```
@@ -785,9 +776,9 @@ The following seven extensions **MUST** be loaded into every `AgentSession` crea
 
 **Requirements:**
 
-- When `harness.checkpoint: true` is set, the extension **MUST** subscribe to `agent_end` and persist the step name, completion status, session transcript, and accumulated cost.
+- When `harness.checkpoint: true` is set, the extension **MUST** subscribe to `agent_end` and persist the task name, completion status, session transcript, and accumulated cost.
 - Checkpoint data **MUST** be stored in a location accessible across job retries (e.g., a Actions cache or artifact).
-- An implementation **SHOULD** support a `--continue` invocation flag that resumes from the last successful checkpoint, skipping already-completed steps.
+- An implementation **SHOULD** support a `--continue` invocation flag that resumes from the last successful checkpoint, skipping already-completed tasks.
 
 > [!NOTE] Non-normative example.
 >
@@ -795,7 +786,7 @@ The following seven extensions **MUST** be loaded into every `AgentSession` crea
 > export default function(pi: ExtensionAPI) {
 >   pi.on("agent_end", async (event, ctx) => {
 >     await saveCheckpoint({
->       step: currentStep,
+>       task: currentTask,
 >       status: event.error ? "failed" : "done",
 >       transcript: ctx.agent.state.messages,
 >       cost: totalCost,
@@ -810,63 +801,26 @@ The following seven extensions **MUST** be loaded into every `AgentSession` crea
 
 *(This section is non-normative.)*
 
-The harness does not perform provider inference or model routing. That responsibility belongs to the api-proxy (`gh-aw-firewall/containers/api-proxy/model-resolver.js`), which runs as a sidecar in the gh-aw container.
+The harness does not perform provider inference or model routing directly. Pi SDK resolves model names using the providers registered by Extension 1. AWF injects provider-specific credentials into the container environment; the harness passes them through to Pi SDK, which handles the routing.
 
-### 9.1 Alias Resolution Flow
+### 9.1 Model Selection Flow
 
 ```
-Harness (Pi SDK) → api-proxy → LLM provider
-  model: "sonnet"           resolves to copilot/claude-sonnet-4.6
-  model: "gpt-5-codex"      resolves to copilot/gpt-5.3-codex
-  model: "copilot/gpt-4.1"  passes through as-is
+Harness (Pi SDK) → Registered provider → LLM provider API
+  model: "claude-sonnet-4.6"   → Anthropic (via ANTHROPIC_API_KEY)
+  model: "gpt-5-codex"         → OpenAI (via OPENAI_API_KEY)
+  model: "copilot/gpt-4.1"     → Copilot (via GITHUB_TOKEN)
 ```
 
-### 9.2 Alias Configuration
-
-Model aliases are configured in the api-proxy via the `AWF_MODEL_ALIASES` environment variable.
-
-> [!NOTE] Non-normative example of alias configuration JSON.
->
-> ```json
-> {
->   "models": {
->     "sonnet": ["copilot/*sonnet*", "anthropic/*sonnet*"],
->     "gpt-5-codex": ["copilot/gpt-5*-codex", "openai/gpt-5*-codex"],
->     "": ["sonnet", "gpt-5*-codex"]
->   }
-> }
-> ```
-
-**Key properties of the alias resolver:**
+### 9.2 Per-Task Model Selection
 
-- `providerid/modelid` syntax with `*` wildcards.
-- Recursive alias resolution with loop detection.
-- Case-insensitive matching.
-- Semver sorting (highest version first among candidates).
-- Default policy via `""` key (applied when no model is specified in the workflow).
+Per-task and per-agent model selection is accomplished by passing a different model name string to `createAgentSession()`. The harness reads the model name from `config.json` (compiled from `harness.agents[*].model` or the top-level `engine.model` field in the workflow frontmatter).
 
 ### 9.3 Implications for the Harness
 
-- Pi SDK's `pi-ai` is configured with the api-proxy as an OpenAI-compatible custom provider (via `pi.registerProvider()` in Extension 1).
-- The harness passes model name strings through as-is — aliases such as `"sonnet"` work without any harness-side resolution.
-- No `provider:` field is needed in frontmatter — the proxy handles all routing.
-- Per-step and per-agent model selection is accomplished by passing a different model name string to `createAgentSession()`.
-- The harness inherits the api-proxy's full model catalog without maintaining its own.
-
-> [!NOTE] Non-normative example of provider registration.
->
-> ```typescript
-> export default async function apiProxyProvider(pi: ExtensionAPI) {
->   const proxyUrl = process.env.AWF_API_PROXY_URL || "http://localhost:8080/v1";
->
->   pi.registerProvider("aw-proxy", {
->     baseUrl: proxyUrl,
->     apiKey: "AWF_API_PROXY_TOKEN",
->     api: "openai-completions",
->     models: await fetchAvailableModels(proxyUrl),
->   });
-> }
-> ```
+- The harness passes model name strings through as-is to `createAgentSession()`.
+- No `provider:` field is needed in frontmatter — Pi SDK selects the provider based on the model name and registered providers.
+- The harness inherits the provider catalog determined by the env vars AWF injects.
 
 ---
 
@@ -937,13 +891,13 @@ aw-harness/
 ├── build.ts                      # esbuild → dist/aw_harness.cjs
 ├── src/
 │   ├── index.ts                  # Entry point: read config.json + prompt.txt → buildDAG → run sessions
-│   ├── parser.ts                 # config.json + prompt.txt → steps + config
+│   ├── parser.ts                 # config.json + prompt.txt → tasks + config
 │   ├── planner.ts                # DAG construction, topological sort
 │   ├── dag-runner.ts             # Orchestrate sessions (sequential + parallel)
-│   ├── transcript.ts             # Inter-step data flow (save/load/summarize)
+│   ├── transcript.ts             # Inter-task data flow (save/load/summarize)
 │   ├── context.ts                # Prompt assembly, compaction
 │   └── extensions/               # gh-aw Pi extensions
-│       ├── api-proxy-provider.ts # Register api-proxy as Pi provider
+│       ├── provider-setup.ts     # Register LLM providers from env vars
 │       ├── safe-outputs.ts       # Safe-output tools + artifact writing
 │       ├── cost-tracker.ts       # Budget gates via turn_end events
 │       ├── steering.ts           # Time/budget pressure via session.steer()
@@ -983,10 +937,10 @@ A `make aw-harness` Makefile target **SHOULD** be added that runs esbuild and co
 | `engine: copilot` (existing) | Uses current `copilot_harness.cjs` — unchanged |
 | `engine: claude` (existing) | Uses current Claude Code flow — unchanged |
 | `engine: codex` (existing) | Uses current Codex flow — unchanged |
-| `engine: aw` without `harness:` block | Single-step: entire body = one Pi session prompt |
-| `engine: aw` with `harness:` block | Multi-step orchestration mode |
-| `engine: aw` with `harness.steps` | Explicit DAG (parallel, depends, agent assignment) |
-| `engine: aw` without `harness.agents` | All steps use `engine.model` |
+| `engine: aw` without `harness:` block | Single-task: entire body = one Pi session prompt |
+| `engine: aw` with `harness:` block | Multi-task orchestration mode |
+| `engine: aw` with `harness.tasks` | Explicit DAG (parallel, depends, agent assignment) |
+| `engine: aw` without `harness.agents` | All tasks use `engine.model` |
 | `engine: aw` + `cli-proxy: false` | **Ignored** — `cli-proxy` is always on for `engine: aw` |
 | `engine: aw` + `tools.github.mode: remote` | **Overridden to `gh-proxy`** — Pi SDK requires `gh-proxy`; `remote` mode is not supported |
 
@@ -996,17 +950,17 @@ The following ordered work items describe the implementation sequence:
 
 1. **Scaffold project** — Initialize TypeScript project in `aw-harness/`. Configure package.json with Pi SDK deps (`@mariozechner/pi-coding-agent`, `pi-agent-core`, `pi-ai`). Set up tsconfig for ES2024/Node 24. Configure esbuild bundle → `dist/aw_harness.cjs`.
 
-2. **Implement api-proxy provider extension** — Pi extension that registers the api-proxy as a custom provider via `pi.registerProvider()`. Async factory fetches available models at startup. All model requests route through the proxy.
+2. **Implement provider setup extension** — Pi extension that registers LLM providers via `pi.registerProvider()` using provider credentials injected by AWF into the container environment.
 
-3. **Implement parser** — Read `config.json` (compiler-generated harness config) and `prompt.txt` (compiler-generated prompt body). Parse step sections from prompt text; resolve step configuration from `harness.steps`. Fall back to single-step mode when no step config is present.
+3. **Implement parser** — Read `config.json` (compiler-generated harness config) and `prompt.txt` (compiler-generated prompt body). Parse task sections from prompt text; resolve task configuration from `harness.tasks`. Fall back to single-task mode when no task config is present.
 
-4. **Implement DAG planner** — Topological sort, parallel group detection, sequential fallback. Validate no cycles, all agent/step references resolve.
+4. **Implement DAG planner** — Topological sort, parallel group detection, sequential fallback. Validate no cycles, all agent/task references resolve.
 
 5. **Implement safe-outputs extension** — Pi extension that registers safe-output tools (create-issue, create-pull-request, add-comment, etc.). Uses `pi.on("agent_end")` to finalize artifact manifest.
 
-6. **Implement DAG runner** — Orchestrates multiple `createAgentSession()` calls. Sequential steps + `Promise.all()` for parallel groups. Passes gh-aw extensions to each session. Manages transcript flow between steps.
+6. **Implement DAG runner** — Orchestrates multiple `createAgentSession()` calls. Sequential tasks + `Promise.all()` for parallel groups. Passes gh-aw extensions to each session. Manages transcript flow between tasks.
 
-7. **Implement transcript manager** — Save step output to disk. Load for downstream steps. Support `summary` mode (use a Pi session to summarize) and `full` mode.
+7. **Implement transcript manager** — Save task output to disk. Load for downstream tasks. Support `summary` mode (use a Pi session to summarize) and `full` mode.
 
 8. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
 
@@ -1016,13 +970,13 @@ The following ordered work items describe the implementation sequence:
 
 11. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
 
-12. **Implement checkpoint extension** — Pi extension that persists step completion state on `agent_end`. Resume from checkpoint on `--continue`.
+12. **Implement checkpoint extension** — Pi extension that persists task completion state on `agent_end`. Resume from checkpoint on `--continue`.
 
 13. **Implement observability extension** — Pi extension that emits JSONL to stderr on agent/tool events. Generates OTel spans using `observability.otlp` config.
 
 14. **Write tests** — Unit tests for parser, planner, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
 
-15. **Write example workflows** — Three examples: single-step, multi-step sequential, multi-agent parallel with different models.
+15. **Write example workflows** — Three examples: single-task, multi-task sequential, multi-agent parallel with different models.
 
 17. **Add build to Makefile** — Add `make aw-harness` target that runs esbuild and copies `aw_harness.cjs` to `actions/setup/js/`.
 
@@ -1032,15 +986,15 @@ The following ordered work items describe the implementation sequence:
 
 **Mandatory proxy features.** The `gh-proxy` and `cli-proxy` features **MUST** always be active for `engine: aw`. MCP tools are available to agent sessions as CLI commands via `cli-proxy`; disabling it would make those tools inaccessible. Any attempt by a workflow author to disable either feature **MUST** be silently overridden (see [Section 6.2](#62-overrides-and-fixed-settings)).
 
-**No direct LLM access.** The harness **MUST NOT** make direct calls to LLM provider APIs. All model requests **MUST** pass through the api-proxy, which enforces rate limits, budget caps, and access controls at the container boundary.
+**No direct LLM routing by harness.** The harness delegates all LLM routing to Pi SDK and the provider credentials injected by AWF. It **MUST NOT** perform additional proxy interception or credential manipulation.
 
 **Safe outputs isolation.** The safe-outputs extension **MUST NOT** perform live GitHub API calls during agent execution. All GitHub mutations **MUST** be expressed as artifact files processed by the post-agent job, which applies threat detection and validation before acting.
 
 **Budget enforcement.** The cost-tracker extension provides a hard budget gate. A conforming implementation **MUST** abort the session if the cost exceeds the configured maximum, preventing runaway spending from misbehaving agents.
 
-**Transcript confidentiality.** Transcripts captured for inter-step context **SHOULD** be stored only in memory or in ephemeral container storage. Implementations **SHOULD NOT** persist transcripts to external storage unless checkpointing is explicitly enabled.
+**Transcript confidentiality.** Transcripts captured for inter-task context **SHOULD** be stored only in memory or in ephemeral container storage. Implementations **SHOULD NOT** persist transcripts to external storage unless checkpointing is explicitly enabled.
 
-**Token and secret handling.** The `AWF_API_PROXY_TOKEN` value **MUST NOT** be logged to stderr or embedded in JSONL events. Implementations **MUST** treat it as an opaque secret.
+**Token and secret handling.** Provider credentials (e.g., `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GITHUB_TOKEN`) **MUST NOT** be logged to stderr or embedded in JSONL events. Implementations **MUST** treat all credential env vars as opaque secrets.
 
 ---
 
@@ -1048,13 +1002,13 @@ The following ordered work items describe the implementation sequence:
 
 *(This section is non-normative.)*
 
-**Data residency.** All agent execution occurs within the gh-aw Actions container. No workflow content, prompts, or transcripts leave the container except via the api-proxy to the configured LLM provider endpoint, or via OTLP to the configured telemetry endpoint.
+**Data residency.** All agent execution occurs within the gh-aw Actions container. No workflow content, prompts, or transcripts leave the container except via the Pi SDK to the configured LLM provider endpoint, or via OTLP to the configured telemetry endpoint.
 
-**Transcript retention.** Step transcripts held in memory for inter-step context are discarded when the harness process exits. If checkpointing is enabled, transcript data may be persisted to GitHub Actions artifacts; workflow authors **SHOULD** evaluate the sensitivity of transcript content before enabling checkpointing.
+**Transcript retention.** Task transcripts held in memory for inter-task context are discarded when the harness process exits. If checkpointing is enabled, transcript data may be persisted to GitHub Actions artifacts; workflow authors **SHOULD** evaluate the sensitivity of transcript content before enabling checkpointing.
 
-**Telemetry scope.** When `observability.otlp` is configured, OTel spans contain step names, model names, token counts, and cost data. They **SHOULD NOT** contain raw prompt or response text. Implementations **SHOULD** redact sensitive content from span attributes.
+**Telemetry scope.** When `observability.otlp` is configured, OTel spans contain task names, model names, token counts, and cost data. They **SHOULD NOT** contain raw prompt or response text. Implementations **SHOULD** redact sensitive content from span attributes.
 
-**Model provider data handling.** Prompt content is transmitted to the LLM provider as configured in the api-proxy. Workflow authors are responsible for ensuring that content transmitted to LLM providers complies with applicable data handling policies.
+**Model provider data handling.** Prompt content is transmitted to the LLM provider using the credentials AWF injects into the container. Workflow authors are responsible for ensuring that content transmitted to LLM providers complies with applicable data handling policies.
 
 ---
 

From 58d000c43979e3fff64b81585aa294fb10610caa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 10:24:58 +0000
Subject: [PATCH 08/14] docs: skills must be declaratively imported via
 imports: key

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/c76a9b71-ee57-42f3-8586-62026963362c

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 44 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 42 insertions(+), 2 deletions(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 9001f9943c6..ae5f00fb18e 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -284,6 +284,13 @@ An `engine: aw` workflow document **MUST** include a YAML frontmatter block conf
 >   issues: read
 >   pull-requests: read
 >
+> # All files and skills the agent may reference MUST be declared here.
+> # The compiler resolves each path and passes the contents to the harness.
+> # Skills are files under skills/ and must be listed explicitly.
+> imports:
+>   - skills/reporting/SKILL.md    # Skill: formatting guidelines for reports
+>   - shared/review-criteria.md    # Shared context: review checklist
+>
 > # gh-proxy and cli-proxy are ALWAYS enabled for engine: aw.
 > # MCP tools are available as CLI commands on PATH (via cli-proxy) and
 > # via the pre-authenticated `gh` binary (via gh-proxy).
@@ -419,6 +426,27 @@ The `harness.steering` key is **OPTIONAL**. When present, it **MAY** contain:
 
 The `harness.checkpoint` key is **OPTIONAL**. When set to `true`, the checkpoint extension **MUST** persist task state on `agent_end`.
 
+#### 6.1.7 `imports:`
+
+The `imports:` key is **OPTIONAL**. It is a standard gh-aw frontmatter key that lists the paths of files whose contents **MUST** be resolved by the compiler and made available to the harness as part of the compiled inputs.
+
+Each entry is a repository-relative path (string). Entries **MAY** point to:
+
+- **Skill files** — files under `skills/` (e.g., `skills/reporting/SKILL.md`).
+- **Shared context files** — markdown or text files shared across workflows (e.g., `shared/review-criteria.md`).
+- **Agent files** — custom agent `.yml` files (resolved and embedded by the compiler).
+
+A conforming implementation **MUST NOT** treat any skill, shared file, or agent file as implicitly available unless it appears in `imports:`. Skills directories are **NOT** auto-discovered or auto-loaded.
+
+> [!NOTE] Non-normative example.
+>
+> ```yaml
+> imports:
+>   - skills/reporting/SKILL.md        # Skill: formatting guidelines
+>   - skills/github-issue-query/SKILL.md  # Skill: querying GitHub issues
+>   - shared/review-criteria.md        # Shared review checklist
+> ```
+
 ### 6.2 Overrides and Fixed Settings
 
 A conforming implementation **MUST** apply the following overrides regardless of values specified in the workflow frontmatter:
@@ -448,12 +476,24 @@ A conforming implementation **MUST** source every item included in a session's i
 - The task's own Markdown body (extracted per [Section 6.3](#63-task-extraction-algorithm)).
 - Transcripts from upstream tasks (passed via the DAG execution model in [Section 7](#7-dag-execution-model)).
 - The agent's `system` prompt as declared under `harness.agents` in the frontmatter.
-- Files or sub-workflows declared via the standard `imports:` frontmatter key and resolved by the compiler into agent files passed at invocation time.
+- Files, skills, and sub-workflows declared via the `imports:` frontmatter key (see [Section 6.1.7](#617-imports)) and resolved by the compiler into inputs passed at invocation time.
 
 A conforming implementation **MUST NOT** automatically load AGENTS.md files, `.github/agents/` entries, skills directories, or any other ambient repository files unless they are explicitly listed in `imports:`. This behavior is a deliberate divergence from engines such as `engine: copilot` that inject ambient context automatically.
 
+Skills **MUST** be treated as ordinary imported files: they carry no special runtime status and **MUST** be listed individually under `imports:` just like any other resource. There is no automatic discovery of skills based on directory presence or workflow content.
+
 > [!IMPORTANT]
-> Workflow authors **MUST** explicitly declare every file or skill they wish the agent to reference using the `imports:` frontmatter key. Relying on ambient context that is auto-injected by other engines will produce a missing-context failure when running with `engine: aw`.
+> Workflow authors **MUST** explicitly declare every file and skill they wish the agent to reference using the `imports:` frontmatter key. Relying on ambient context that is auto-injected by other engines will produce a missing-context failure when running with `engine: aw`.
+
+> [!NOTE] Non-normative example.
+>
+> ```yaml
+> # All skills and files must be declared explicitly.
+> imports:
+>   - skills/reporting/SKILL.md          # Skill: formatting guidelines
+>   - skills/github-issue-query/SKILL.md # Skill: querying issues
+>   - shared/pr-review-criteria.md       # Shared context: review checklist
+> ```
 
 ---
 

From 355543b9057b12efc21eb0ac59ab1b0e60fee519 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 10:37:00 +0000
Subject: [PATCH 09/14] docs: remove subagents - simplify aw-harness to
 single-session execution

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/e542825e-8ae2-4bb9-9736-f9ae2df187a5

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 338 +++++++++++++-------------------------------
 1 file changed, 96 insertions(+), 242 deletions(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index ae5f00fb18e..7e5f3a8b199 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -2,7 +2,7 @@
 
 ---
 
-**Title:** AW Harness — Multi-Task Agentic Workflow Execution Engine
+**Title:** AW Harness — Single-Session Agentic Workflow Execution Engine
 
 **Status:** Unofficial Draft
 
@@ -14,7 +14,7 @@
 
 ## Abstract
 
-This document specifies the **AW Harness** (`aw_harness.cjs`), a Node.js execution engine for the `engine: aw` mode of GitHub Agentic Workflows (gh-aw). The harness provides multi-task orchestration, multi-agent coordination, context engineering, cost tracking, and observability, built on top of the Pi agent SDK ecosystem. All gh-aw-specific capabilities are implemented as Pi extensions using Pi's native `ExtensionAPI` extensibility mechanism.
+This document specifies the **AW Harness** (`aw_harness.cjs`), a Node.js execution engine for the `engine: aw` mode of GitHub Agentic Workflows (gh-aw). The harness runs a single Pi `AgentSession` with a compiled prompt, budget management, steering, and observability. All gh-aw-specific capabilities are implemented as Pi extensions using Pi's native `ExtensionAPI` extensibility mechanism.
 
 ## Status of This Document
 
@@ -30,7 +30,7 @@ This is an internal design specification for the GitHub gh-aw project. It is not
 4. [Architecture](#4-architecture)
 5. [Harness Invocation Contract](#5-harness-invocation-contract)
 6. [Workflow Definition](#6-workflow-definition)
-7. [DAG Execution Model](#7-dag-execution-model)
+7. [Single-Session Execution Model](#7-single-session-execution-model)
 8. [Extensions](#8-extensions)
 9. [Model Resolution](#9-model-resolution)
 10. [Build and Deployment](#10-build-and-deployment)
@@ -44,9 +44,9 @@ This is an internal design specification for the GitHub gh-aw project. It is not
 
 *(This section is non-normative.)*
 
-The existing gh-aw harnesses (`copilot_harness.cjs`, `claude_harness.cjs`) are thin retry loops around a single CLI invocation. As workflow complexity grows, authors need multi-task orchestration, parallel agent execution, per-task model selection, budget management, and structured observability — none of which the current harnesses provide.
+The existing gh-aw harnesses (`copilot_harness.cjs`, `claude_harness.cjs`) are thin retry loops around a single CLI invocation. As workflow complexity grows, authors need structured budget management, cost tracking, time steering, and structured observability — none of which the current harnesses provide.
 
-The AW Harness introduces `engine: aw` as a new opt-in execution engine. It does not replace existing engines; `engine: copilot`, `engine: claude`, and `engine: codex` continue to operate unchanged via their current harnesses. The AW Harness is a Pi SDK application: it creates one `AgentSession` per workflow task, loads a fixed set of gh-aw Pi extensions into each session, and orchestrates sessions according to a DAG derived from the workflow's `harness:` frontmatter block and Markdown heading structure.
+The AW Harness introduces `engine: aw` as a new opt-in execution engine. It does not replace existing engines; `engine: copilot`, `engine: claude`, and `engine: codex` continue to operate unchanged via their current harnesses. The AW Harness is a Pi SDK application: it creates a single `AgentSession` and runs the compiled prompt through it, with gh-aw Pi extensions providing budget gating, steering, safe outputs, and observability.
 
 The harness is designed exclusively for the gh-aw Actions container environment. It assumes the firewall and MCP gateway are already running. AWF injects provider credentials into the container environment; the harness reads these credentials and passes them to Pi SDK directly.
 
@@ -56,8 +56,8 @@ This specification covers:
 
 - The entry-point invocation contract for `aw_harness.cjs`.
 - The frontmatter schema for `engine: aw` workflows.
-- The task-extraction and DAG-construction algorithms.
-- The normative requirements for each of the seven gh-aw Pi extensions.
+- The prompt loading and session execution algorithm.
+- The normative requirements for each of the six gh-aw Pi extensions.
 - The model connection contract via provider environment variables.
 - The build and deployment configuration.
 
@@ -97,7 +97,7 @@ A **conforming implementation** is one that satisfies all **MUST** and **MUST NO
 ## 3. Terminology and Definitions
 
 **AW Harness**
-: The execution engine implemented in `aw_harness.cjs`, invoked when a workflow declares `engine: aw`. It is responsible for parsing the workflow, constructing the DAG, and orchestrating one `AgentSession` per task.
+: The execution engine implemented in `aw_harness.cjs`, invoked when a workflow declares `engine: aw`. It is responsible for loading the compiled prompt, creating a single Pi `AgentSession`, and running that session to completion.
 
 **AgentSession**
 : A Pi SDK session object, obtained via `createAgentSession()`, that manages a single agent's message loop, tool calls, and event stream.
@@ -106,10 +106,7 @@ A **conforming implementation** is one that satisfies all **MUST** and **MUST NO
 : A sidecar process in the gh-aw container used by other engines for model routing. The AW Harness does **not** use the api-proxy; it connects to LLM providers directly via environment variables.
 
 **cli-proxy**
-: A feature that mounts MCP servers as CLI tools on `PATH`, making them callable as ordinary shell commands within agent sessions.
-
-**DAG (Directed Acyclic Graph)**
-: The execution graph derived from the workflow's `harness.tasks` declarations and implicit document-order dependencies. Nodes are tasks; directed edges encode `depends` relationships. Cycles are prohibited.
+: A feature that mounts MCP servers as CLI tools on `PATH`, making them callable as ordinary shell commands within the agent session.
 
 **ExtensionAPI**
 : The Pi SDK interface (`ExtensionAPI` from `@mariozechner/pi-coding-agent`) that a Pi extension receives as its sole argument. Provides `pi.registerTool()`, `pi.registerProvider()`, and `pi.on()`.
@@ -117,9 +114,6 @@ A **conforming implementation** is one that satisfies all **MUST** and **MUST NO
 **gh-proxy**
 : A feature that provides a pre-authenticated `gh` CLI binary in the agent's bash environment, enabling direct GitHub API access without separate token management.
 
-**harness task**
-: A unit of work within an `engine: aw` workflow. Each task is assigned one `AgentSession`, a prompt derived from the corresponding Markdown section, and optionally a named agent definition.
-
 **MCP Gateway**
 : The gh-aw MCP gateway process that exposes GitHub tools and custom MCP server tools as CLI commands (via `cli-proxy`) in the agent's bash environment. It runs independently of the harness in the same container.
 
@@ -132,14 +126,8 @@ A **conforming implementation** is one that satisfies all **MUST** and **MUST NO
 **safe output**
 : A deferred GitHub action (create issue, create pull request, add comment, etc.) expressed as an artifact file written during agent execution and processed by the post-agent job.
 
-**task annotation**
-: An HTML comment of the form `<!-- harness-task: <name> -->` embedded in a Markdown section to associate that section with a named entry in `harness.tasks`.
-
-**transcript**
-: The complete message history of a completed `AgentSession`, optionally summarized, passed as context to downstream tasks.
-
 **workflow document**
-: A Markdown file with YAML frontmatter that declares an `engine: aw` workflow. The frontmatter is parsed by the gh-aw compiler at compile time; the harness itself never reads the raw Markdown file. Instead, the compiler provides the harness with pre-processed inputs: `config.json` (harness configuration), `prompt.txt` (extracted prompt body), and any referenced agent files.
+: A Markdown file with YAML frontmatter that declares an `engine: aw` workflow. The frontmatter is parsed by the gh-aw compiler at compile time; the harness itself never reads the raw Markdown file. Instead, the compiler provides the harness with pre-processed inputs: `config.json` (harness configuration) and `prompt.txt` (extracted prompt body).
 
 ---
 
@@ -160,11 +148,9 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 │  │  │  aw_harness.cjs (entry point)                   │ │   │
 │  │  │                                                  │ │   │
 │  │  │  1. Reads config.json + prompt.txt (pre-parsed by compiler) │ │   │
-│  │  │  2. Parses tasks from task config + prompt text            │ │   │
-│  │  │  3. Builds execution DAG                                   │ │   │
-│  │  │  4. For each task: creates Pi AgentSession                 │ │   │
+│  │  │  2. Creates a single Pi AgentSession                       │ │   │
 │  │  │     with gh-aw extensions loaded                           │ │   │
-│  │  │  5. session.prompt() → Pi drives the agent                 │ │   │
+│  │  │  3. session.prompt() → Pi drives the agent                 │ │   │
 │  │  │                                                  │ │   │
 │  │  │  ┌──────────────────────────────────────────┐   │ │   │
 │  │  │  │  Pi SDK (createAgentSession)             │   │ │   │
@@ -173,14 +159,13 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 │  │  │  │  └─ compaction, steering, auto-retry      │   │ │   │
 │  │  │  └──────────────────────────────────────────┘   │ │   │
 │  │  │  ┌──────────────────────────────────────────┐   │ │   │
-│  │  │  │  gh-aw Pi Extensions (loaded into each   │   │ │   │
+│  │  │  │  gh-aw Pi Extensions (loaded into the    │   │ │   │
 │  │  │  │  AgentSession via ExtensionAPI):          │   │ │   │
 │  │  │  │  ├─ safe-outputs (tools + artifact write) │   │ │   │
 │  │  │  │  ├─ cost-tracker (budget gates + events)  │   │ │   │
 │  │  │  │  ├─ steering (time/budget pressure)       │   │ │   │
 │  │  │  │  ├─ repair (broken session recovery)      │   │ │   │
-│  │  │  │  ├─ observability (JSONL + OTel)          │   │ │   │
-│  │  │  │  └─ checkpoint (persist/resume state)     │   │ │   │
+│  │  │  │  └─ observability (JSONL + OTel)          │   │ │   │
 │  │  │  └──────────────────────────────────────────┘   │ │   │
 │  │  │  ┌──────────────────────────────────────────┐   │ │   │
 │  │  │  │  MCP Gateway (gh-aw, already running)    │   │ │   │
@@ -214,9 +199,7 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 
 8. **New opt-in engine.** `engine: aw` is an independent opt-in. Existing engines **MUST** be untouched.
 
-9. **Markdown-native tasks.** `## Heading` or `### Heading` elements **MUST** be recognized as task boundaries. HTML comments carry task metadata.
-
-10. **Observable.** All implementations **MUST** emit a JSONL event stream to stderr and **SHOULD** generate OTel spans when an OTLP endpoint is configured.
+9. **Observable.** All implementations **MUST** emit a JSONL event stream to stderr and **SHOULD** generate OTel spans when an OTLP endpoint is configured.
 
 ---
 
@@ -240,17 +223,17 @@ A conforming implementation **MUST NOT** read or parse workflow Markdown files d
 
 A conforming implementation **MUST** read LLM provider credentials from the container environment. AWF sets up the appropriate provider-specific environment variables (e.g., `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GITHUB_TOKEN`) for whichever providers are enabled in the workflow configuration. The harness **MUST NOT** hard-code any provider URL or token; it **MUST** rely exclusively on the environment injected by AWF.
 
-A conforming implementation **SHOULD** read standard GitHub Actions environment variables (`GITHUB_REPOSITORY`, `GITHUB_RUN_ID`, etc.) for use in observability spans and checkpoint keys.
+A conforming implementation **SHOULD** read standard GitHub Actions environment variables (`GITHUB_REPOSITORY`, `GITHUB_RUN_ID`, etc.) for use in observability spans.
 
 ### 5.3 Exit Codes
 
 | Code | Meaning |
 |------|---------|
-| `0` | All tasks completed successfully |
-| `1` | One or more tasks failed (non-recoverable error) |
+| `0` | Prompt completed successfully |
+| `1` | Session failed (non-recoverable error) |
 | `2` | Invocation error (missing config path, unreadable config file) |
 
-A conforming implementation **MUST** exit with code `0` if and only if all DAG tasks complete without error. It **MUST** exit with a non-zero code on any unrecovered failure.
+A conforming implementation **MUST** exit with code `0` if and only if the agent session completes without error. It **MUST** exit with a non-zero code on any unrecovered failure.
 
 ### 5.4 Standard Streams
 
@@ -325,61 +308,18 @@ An `engine: aw` workflow document **MUST** include a YAML frontmatter block conf
 >   context:
 >     compaction: summarize
 >     compaction-threshold: 0.75
->     transcript-mode: summary
->
->   agents:
->     reviewer:
->       model: sonnet
->       system: |
->         You are a senior code reviewer. Focus on correctness, security,
->         and maintainability.
->     scanner:
->       model: gpt-5-codex
->       system: |
->         You are a security specialist. Focus on vulnerabilities,
->         injection risks, and credential exposure.
->     synthesizer:
->       model: copilot/gpt-4.1
->       system: |
->         You synthesize multiple review perspectives into a single,
->         prioritized action list.
->
->   tasks:
->     parallel-review:
->       agents: [reviewer, scanner]
->       parallel: true
->     synthesize:
->       agent: synthesizer
->       depends: [parallel-review]
 >
 >   steering:
 >     time-warning-minutes: 5
 >     time-critical-minutes: 2
 >     budget-warn-percent: 75
 >     budget-critical-percent: 90
->
->   checkpoint: true
 > ---
 >
-> ## Daily Code Review
->
 > Review all changes pushed to the default branch in the last 24 hours.
->
-> ### Gather Changes
-> <!-- harness-task: gather -->
-> Use `git log --since="24 hours ago"` and `git diff` to collect all
-> recent changes. Summarize the scope.
->
-> ### Parallel Review
-> <!-- harness-task: parallel-review -->
-> Each reviewer examines the changes independently.
->
-> ### Synthesize
-> <!-- harness-task: synthesize -->
-> Read outputs from both reviewers. Produce a prioritized findings list.
->
-> ### Report
-> Create a GitHub issue with the synthesized review.
+> Use `git log --since="24 hours ago"` and `git diff` to collect changes,
+> review them for correctness, security, and maintainability, then create
+> a GitHub issue with a prioritized findings list.
 > ```
 
 #### 6.1.1 `harness.budget`
@@ -395,25 +335,8 @@ The `harness.context` key is **OPTIONAL**. When present, it **MAY** contain:
 
 - `compaction` (string): One of `none`, `sliding-window`, or `summarize`. Default: `none`.
 - `compaction-threshold` (number, 0–1): Context fill fraction at which compaction triggers. Default: `0.75`.
-- `transcript-mode` (string): One of `full` or `summary`. Controls how upstream task transcripts are included in downstream prompts. Default: `full`.
 
-#### 6.1.3 `harness.agents`
-
-The `harness.agents` key is **OPTIONAL**. Each entry defines a named agent with:
-
-- `model` (string, **REQUIRED**): Model alias or fully-qualified `provider/model` string.
-- `system` (string, **OPTIONAL**): System prompt override for this agent.
-
-#### 6.1.4 `harness.tasks`
-
-The `harness.tasks` key is **OPTIONAL**. Each entry defines a named task with:
-
-- `agent` (string, **OPTIONAL**): Name of an agent defined in `harness.agents`. Mutually exclusive with `agents`.
-- `agents` (array of strings, **OPTIONAL**): Names of agents to run in parallel for this task. Mutually exclusive with `agent`.
-- `parallel` (boolean, **OPTIONAL**): When `true` and `agents` is provided, all listed agents execute in parallel. Default: `false`.
-- `depends` (array of strings, **OPTIONAL**): Names of tasks that **MUST** complete before this task begins.
-
-#### 6.1.5 `harness.steering`
+#### 6.1.3 `harness.steering`
 
 The `harness.steering` key is **OPTIONAL**. When present, it **MAY** contain:
 
@@ -422,11 +345,7 @@ The `harness.steering` key is **OPTIONAL**. When present, it **MAY** contain:
 - `budget-warn-percent` (number): Budget percentage at which a warning **SHOULD** be injected. Default: `75`.
 - `budget-critical-percent` (number): Budget percentage at which the session **MUST** be aborted. Default: `90`.
 
-#### 6.1.6 `harness.checkpoint`
-
-The `harness.checkpoint` key is **OPTIONAL**. When set to `true`, the checkpoint extension **MUST** persist task state on `agent_end`.
-
-#### 6.1.7 `imports:`
+#### 6.1.4 `imports:`
 
 The `imports:` key is **OPTIONAL**. It is a standard gh-aw frontmatter key that lists the paths of files whose contents **MUST** be resolved by the compiler and made available to the harness as part of the compiled inputs.
 
@@ -458,25 +377,25 @@ A conforming implementation **MUST** apply the following overrides regardless of
 
 A conforming implementation **MUST NOT** honor attempts to disable `cli-proxy` or set `tools.github.mode: remote` when `engine: aw` is active. These settings **MUST** be overridden. A conforming implementation **MUST** emit a warning to stderr when either override is applied, so that workflow authors can diagnose unexpected configuration behaviour.
 
-### 6.3 Task Extraction Algorithm
+### 6.3 Prompt Loading
+
+A conforming implementation **MUST** load the prompt from the compiler-provided inputs as follows:
 
-A conforming implementation **MUST** extract tasks from the compiler-provided inputs as follows:
+1. Read `config.json` to obtain the harness configuration (budget, context, steering settings).
+2. Read the prompt body from `prompt.txt`. The entire contents of `prompt.txt` constitute the initial user message passed to the single `AgentSession`.
+3. Prepend the contents of any files resolved from `imports:` (as declared in the workflow frontmatter and compiled into the inputs) to the prompt body or supply them as system context, in the order declared.
 
-1. Read `harness.tasks` from `config.json`. If present, each entry defines a named task with its agent assignment, parallel flag, and dependency list.
-2. Read the prompt body from `prompt.txt`. The compiler splits the body on ATX heading boundaries (`##` or `###` level) and associates each section with a named task via `<!-- harness-task: <name> -->` annotations before writing `prompt.txt`; the harness consumes the resulting sections.
-3. If `harness.tasks` is absent or empty, the entire prompt body **MUST** be treated as a single task with no explicit agent or dependency.
-4. Tasks without a named entry in `harness.tasks` are treated as sequential tasks in document order.
+A conforming implementation **MUST NOT** split or subdivide `prompt.txt` into sub-tasks. The prompt is treated as a single, atomic instruction to the agent.
 
 ### 6.4 Initial Prompt Context
 
-The AW Harness **MUST NOT** inject any predefined or ambient context into agent sessions. There are no implicit files, skills, or instruction documents automatically added to a session's initial prompt.
+The AW Harness **MUST NOT** inject any predefined or ambient context into the agent session. There are no implicit files, skills, or instruction documents automatically added to the session's initial prompt.
 
-A conforming implementation **MUST** source every item included in a session's initial prompt from one of the following explicitly declared origins:
+A conforming implementation **MUST** source every item included in the session's initial prompt from one of the following explicitly declared origins:
 
-- The task's own Markdown body (extracted per [Section 6.3](#63-task-extraction-algorithm)).
-- Transcripts from upstream tasks (passed via the DAG execution model in [Section 7](#7-dag-execution-model)).
-- The agent's `system` prompt as declared under `harness.agents` in the frontmatter.
-- Files, skills, and sub-workflows declared via the `imports:` frontmatter key (see [Section 6.1.7](#617-imports)) and resolved by the compiler into inputs passed at invocation time.
+- The Markdown body from `prompt.txt` (loaded per [Section 6.3](#63-prompt-loading)).
+- The `harness.system` prompt if declared in the `harness:` frontmatter block.
+- Files, skills, and sub-workflows declared via the `imports:` frontmatter key (see [Section 6.1.4](#614-imports)) and resolved by the compiler into inputs passed at invocation time.
 
 A conforming implementation **MUST NOT** automatically load AGENTS.md files, `.github/agents/` entries, skills directories, or any other ambient repository files unless they are explicitly listed in `imports:`. This behavior is a deliberate divergence from engines such as `engine: copilot` that inject ambient context automatically.
 
@@ -497,22 +416,13 @@ Skills **MUST** be treated as ordinary imported files: they carry no special run
 
 ---
 
-## 7. DAG Execution Model
-
-### 7.1 DAG Construction
+## 7. Single-Session Execution Model
 
-A conforming implementation **MUST** construct a DAG from the extracted tasks as follows:
+### 7.1 Execution Algorithm
 
-1. Create one node per extracted task.
-2. For each task with a `depends` list, add a directed edge from each named dependency to that task.
-3. Perform a cycle check. If a cycle is detected, the implementation **MUST** abort with exit code `2` and emit a diagnostic to stderr identifying the cycle.
-4. Compute a topological order. Tasks at the same depth **MAY** be executed in parallel.
+A conforming implementation **MUST** execute the workflow as follows:
 
-### 7.2 Execution Algorithm
-
-A conforming implementation **MUST** execute the DAG as follows:
-
-> [!NOTE] Non-normative example illustrating the orchestration entry point.
+> [!NOTE] Non-normative example illustrating the execution entry point.
 >
 > ```typescript
 > // index.ts — entry point
@@ -520,8 +430,7 @@ A conforming implementation **MUST** execute the DAG as follows:
 >
 > async function main() {
 >   const { configPath, promptPath } = parseArgs(process.argv);
->   const workflow = loadWorkflow(configPath, promptPath);
->   const dag = buildDAG(workflow);
+>   const { config, prompt } = loadInputs(configPath, promptPath);
 >   const extensions = [
 >     providerSetupExtension,
 >     safeOutputsExtension,
@@ -529,54 +438,38 @@ A conforming implementation **MUST** execute the DAG as follows:
 >     steeringExtension,
 >     repairExtension,
 >     observabilityExtension,
->     checkpointExtension,
 >   ];
 >
->   for (const taskGroup of dag.executionOrder()) {
->     await Promise.all(taskGroup.map(async (task) => {
->       const { session } = await createAgentSession({
->         sessionManager: SessionManager.inMemory(),
->         extensions,
->         model: resolveModel(task.agent?.model || workflow.defaultModel),
->         systemPrompt: buildSystemPrompt(task),
->       });
->
->       const prompt = buildTaskPrompt(task, transcripts);
->       await session.prompt(prompt);
+>   const { session } = await createAgentSession({
+>     sessionManager: SessionManager.inMemory(),
+>     extensions,
+>     model: config.model,
+>     systemPrompt: config.harness?.system,
+>   });
 >
->       transcripts[task.name] = captureTranscript(session);
->       session.dispose();
->     }));
->   }
+>   await session.prompt(prompt);
+>   session.dispose();
 > }
 > ```
 
-For each execution group in topological order:
-
-1. The implementation **MUST** invoke `createAgentSession()` once per task (or once per agent for tasks with `agents: [...]`).
-2. The prompt passed to `session.prompt()` **MUST** be assembled from: (a) the task's prompt text (from `prompt.txt`), (b) transcripts from all upstream tasks, and (c) the agent's system prompt, if defined.
-3. The implementation **MUST** load all seven gh-aw Pi extensions (see [Section 8](#8-extensions)) into each session.
-4. Tasks within the same parallel group **MUST** be executed concurrently using `Promise.all()`.
-5. After each session completes, the implementation **MUST** capture the session transcript for use by downstream tasks.
-6. After capturing the transcript, the implementation **MUST** call `session.dispose()`.
-7. If the budget gate has been triggered (via the cost-tracker extension), the implementation **MUST NOT** launch further sessions and **MUST** exit with code `1`.
+1. The implementation **MUST** invoke `createAgentSession()` exactly once per harness invocation.
+2. The prompt passed to `session.prompt()` **MUST** be the full contents of `prompt.txt` as loaded per [Section 6.3](#63-prompt-loading).
+3. The implementation **MUST** load all six gh-aw Pi extensions (see [Section 8](#8-extensions)) into the session.
+4. After the session completes (success or failure), the implementation **MUST** call `session.dispose()`.
+5. If the budget gate has been triggered (via the cost-tracker extension), the implementation **MUST** exit with code `1`.
 
-### 7.3 Task Execution Summary
-
-The per-task execution sequence is:
+### 7.2 Execution Summary
 
 ```
-For each task (respecting DAG order):
-  1. Build prompt = task text (from prompt.txt) + upstream transcripts + system prompt
-  2. Create Pi AgentSession with gh-aw extensions:
-     - Provider setup registered
-     - Safe-output tools registered
-     - Steering, repair, cost, observability extensions active
-     (MCP tools available as bash CLI commands via cli-proxy — no bridging needed)
-  3. session.prompt() → Pi agent loop runs
-  4. Extensions handle events (cost tracking, steering, observability)
-  5. Capture transcript for downstream tasks
-  6. Budget gate check, checkpoint state
+1. Load config.json + prompt.txt
+2. Create single Pi AgentSession with gh-aw extensions:
+   - Provider setup registered
+   - Safe-output tools registered
+   - Steering, repair, cost, observability extensions active
+   (MCP tools available as bash CLI commands via cli-proxy — no bridging needed)
+3. session.prompt(promptText) → Pi agent loop runs
+4. Extensions handle events (cost tracking, steering, observability)
+5. session.dispose()
 ```
 
 ---
@@ -585,7 +478,7 @@ For each task (respecting DAG order):
 
 All gh-aw-specific behavior **MUST** be packaged as Pi extensions. Each extension **MUST** be a standalone TypeScript module that exports a default function with signature `(pi: ExtensionAPI) => void | Promise<void>`.
 
-The following seven extensions **MUST** be loaded into every `AgentSession` created by the harness.
+The following six extensions **MUST** be loaded into the `AgentSession` created by the harness.
 
 ### 8.1 Extension 1: Provider Setup
 
@@ -784,15 +677,15 @@ The following seven extensions **MUST** be loaded into every `AgentSession` crea
 - The extension **MUST** subscribe to `agent_start`, `turn_end`, `tool_execution_end`, and `agent_end` events.
 - On each event, the extension **MUST** emit a corresponding JSONL record to stderr.
 - If `observability.otlp.endpoint` is configured in the workflow frontmatter, the extension **MUST** create and close OTel spans for each task.
-- OTel span attributes **MUST** include at minimum: task name, model, token counts, and cost.
+- OTel span attributes **MUST** include at minimum: model, token counts, and cost.
 
 > [!NOTE] Non-normative example.
 >
 > ```typescript
 > export default function(pi: ExtensionAPI) {
 >   pi.on("agent_start", async (event) => {
->     emitJsonl({ event: "task_start", task: currentTask, model: currentModel });
->     startOtelSpan(currentTask);
+>     emitJsonl({ event: "session_start", model: currentModel });
+>     startOtelSpan("aw_session");
 >   });
 >
 >   pi.on("turn_end", async (event) => {
@@ -804,33 +697,8 @@ The following seven extensions **MUST** be loaded into every `AgentSession` crea
 >   });
 >
 >   pi.on("agent_end", async (event) => {
->     emitJsonl({ event: "task_end", task: currentTask, tokens: event.tokens, cost: event.cost });
->     endOtelSpan(currentTask);
->   });
-> }
-> ```
-
-### 8.7 Extension 7: Checkpoint
-
-**Purpose:** Persists run state for long workflows, enabling resume from a prior checkpoint.
-
-**Requirements:**
-
-- When `harness.checkpoint: true` is set, the extension **MUST** subscribe to `agent_end` and persist the task name, completion status, session transcript, and accumulated cost.
-- Checkpoint data **MUST** be stored in a location accessible across job retries (e.g., a Actions cache or artifact).
-- An implementation **SHOULD** support a `--continue` invocation flag that resumes from the last successful checkpoint, skipping already-completed tasks.
-
-> [!NOTE] Non-normative example.
->
-> ```typescript
-> export default function(pi: ExtensionAPI) {
->   pi.on("agent_end", async (event, ctx) => {
->     await saveCheckpoint({
->       task: currentTask,
->       status: event.error ? "failed" : "done",
->       transcript: ctx.agent.state.messages,
->       cost: totalCost,
->     });
+>     emitJsonl({ event: "session_end", tokens: event.tokens, cost: event.cost });
+>     endOtelSpan("aw_session");
 >   });
 > }
 > ```
@@ -852,9 +720,9 @@ Harness (Pi SDK) → Registered provider → LLM provider API
   model: "copilot/gpt-4.1"     → Copilot (via GITHUB_TOKEN)
 ```
 
-### 9.2 Per-Task Model Selection
+### 9.2 Model Selection
 
-Per-task and per-agent model selection is accomplished by passing a different model name string to `createAgentSession()`. The harness reads the model name from `config.json` (compiled from `harness.agents[*].model` or the top-level `engine.model` field in the workflow frontmatter).
+The model name is read from `config.json` (compiled from the top-level `engine.model` field in the workflow frontmatter) and passed as-is to `createAgentSession()`.
 
 ### 9.3 Implications for the Harness
 
@@ -930,11 +798,8 @@ aw-harness/
 ├── tsconfig.json                 # target: es2024, module: es2022
 ├── build.ts                      # esbuild → dist/aw_harness.cjs
 ├── src/
-│   ├── index.ts                  # Entry point: read config.json + prompt.txt → buildDAG → run sessions
-│   ├── parser.ts                 # config.json + prompt.txt → tasks + config
-│   ├── planner.ts                # DAG construction, topological sort
-│   ├── dag-runner.ts             # Orchestrate sessions (sequential + parallel)
-│   ├── transcript.ts             # Inter-task data flow (save/load/summarize)
+│   ├── index.ts                  # Entry point: read config.json + prompt.txt → create session → run
+│   ├── loader.ts                 # config.json + prompt.txt → config + prompt string
 │   ├── context.ts                # Prompt assembly, compaction
 │   └── extensions/               # gh-aw Pi extensions
 │       ├── provider-setup.ts     # Register LLM providers from env vars
@@ -942,18 +807,16 @@ aw-harness/
 │       ├── cost-tracker.ts       # Budget gates via turn_end events
 │       ├── steering.ts           # Time/budget pressure via session.steer()
 │       ├── repair.ts             # Broken session recovery
-│       ├── observability.ts      # JSONL events + OTel spans
-│       └── checkpoint.ts         # Persist/resume run state
+│       └── observability.ts      # JSONL events + OTel spans
 ├── test/
-│   ├── parser.test.ts
-│   ├── planner.test.ts
+│   ├── loader.test.ts
 │   ├── extensions/
 │   │   ├── cost-tracker.test.ts
 │   │   ├── steering.test.ts
 │   │   ├── repair.test.ts
 │   │   └── ...
 │   └── integration/
-│       └── dag-runner.test.ts
+│       └── harness.test.ts
 └── dist/
     └── aw_harness.cjs            # → copied to actions/setup/js/
 ```
@@ -962,7 +825,7 @@ aw-harness/
 
 Tests use the same Vitest setup as the existing `actions/setup/js/` scripts:
 
-- Unit tests for parser, planner, and each extension.
+- Unit tests for loader and each extension.
 - Integration tests with mock Pi sessions (`SessionManager.inMemory()`).
 - Tests co-located: `aw_harness.test.cjs` or in a `test/` subdirectory.
 
@@ -977,10 +840,11 @@ A `make aw-harness` Makefile target **SHOULD** be added that runs esbuild and co
 | `engine: copilot` (existing) | Uses current `copilot_harness.cjs` — unchanged |
 | `engine: claude` (existing) | Uses current Claude Code flow — unchanged |
 | `engine: codex` (existing) | Uses current Codex flow — unchanged |
-| `engine: aw` without `harness:` block | Single-task: entire body = one Pi session prompt |
-| `engine: aw` with `harness:` block | Multi-task orchestration mode |
-| `engine: aw` with `harness.tasks` | Explicit DAG (parallel, depends, agent assignment) |
-| `engine: aw` without `harness.agents` | All tasks use `engine.model` |
+| `engine: gemini` (existing) | Uses current Gemini flow — unchanged |
+| `engine: opencode` (existing) | Uses current OpenCode flow — unchanged |
+| `engine: crush` (existing) | Uses current Crush flow — unchanged |
+| `engine: aw` | Single-session: entire `prompt.txt` = one Pi session prompt |
+| `engine: aw` without `harness:` block | Uses defaults for budget/steering/compaction |
 | `engine: aw` + `cli-proxy: false` | **Ignored** — `cli-proxy` is always on for `engine: aw` |
 | `engine: aw` + `tools.github.mode: remote` | **Overridden to `gh-proxy`** — Pi SDK requires `gh-proxy`; `remote` mode is not supported |
 
@@ -992,33 +856,27 @@ The following ordered work items describe the implementation sequence:
 
 2. **Implement provider setup extension** — Pi extension that registers LLM providers via `pi.registerProvider()` using provider credentials injected by AWF into the container environment.
 
-3. **Implement parser** — Read `config.json` (compiler-generated harness config) and `prompt.txt` (compiler-generated prompt body). Parse task sections from prompt text; resolve task configuration from `harness.tasks`. Fall back to single-task mode when no task config is present.
-
-4. **Implement DAG planner** — Topological sort, parallel group detection, sequential fallback. Validate no cycles, all agent/task references resolve.
-
-5. **Implement safe-outputs extension** — Pi extension that registers safe-output tools (create-issue, create-pull-request, add-comment, etc.). Uses `pi.on("agent_end")` to finalize artifact manifest.
+3. **Implement loader** — Read `config.json` (compiler-generated harness config) and `prompt.txt` (compiler-generated prompt body). Return config and prompt string to the entry point.
 
-6. **Implement DAG runner** — Orchestrates multiple `createAgentSession()` calls. Sequential tasks + `Promise.all()` for parallel groups. Passes gh-aw extensions to each session. Manages transcript flow between tasks.
+4. **Implement safe-outputs extension** — Pi extension that registers safe-output tools (create-issue, create-pull-request, add-comment, etc.). Uses `pi.on("agent_end")` to finalize artifact manifest.
 
-7. **Implement transcript manager** — Save task output to disk. Load for downstream tasks. Support `summary` mode (use a Pi session to summarize) and `full` mode.
+5. **Implement entry point** — Create a single `createAgentSession()` with gh-aw extensions loaded. Pass `prompt.txt` contents as the prompt. Dispose session on completion.
 
-8. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
+6. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
 
-9. **Implement cost tracker extension** — Pi extension that monitors `turn_end` events for token/cost data. Enforces soft (steer warning) and hard (abort) budget gates.
+7. **Implement cost tracker extension** — Pi extension that monitors `turn_end` events for token/cost data. Enforces soft (steer warning) and hard (abort) budget gates.
 
-10. **Implement steering extension** — Pi extension that monitors time/budget and injects user messages via `session.steer()` on `turn_end`.
+8. **Implement steering extension** — Pi extension that monitors time/budget and injects user messages via `session.steer()` on `turn_end`.
 
-11. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
+9. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
 
-12. **Implement checkpoint extension** — Pi extension that persists task completion state on `agent_end`. Resume from checkpoint on `--continue`.
+10. **Implement observability extension** — Pi extension that emits JSONL to stderr on agent/tool events. Generates OTel spans using `observability.otlp` config.
 
-13. **Implement observability extension** — Pi extension that emits JSONL to stderr on agent/tool events. Generates OTel spans using `observability.otlp` config.
+11. **Write tests** — Unit tests for loader, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
 
-14. **Write tests** — Unit tests for parser, planner, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
+12. **Write example workflows** — Single-task examples demonstrating `engine: aw` with various tools and safe outputs.
 
-15. **Write example workflows** — Three examples: single-task, multi-task sequential, multi-agent parallel with different models.
-
-17. **Add build to Makefile** — Add `make aw-harness` target that runs esbuild and copies `aw_harness.cjs` to `actions/setup/js/`.
+13. **Add build to Makefile** — Add `make aw-harness` target that runs esbuild and copies `aw_harness.cjs` to `actions/setup/js/`.
 
 ---
 
@@ -1032,8 +890,6 @@ The following ordered work items describe the implementation sequence:
 
 **Budget enforcement.** The cost-tracker extension provides a hard budget gate. A conforming implementation **MUST** abort the session if the cost exceeds the configured maximum, preventing runaway spending from misbehaving agents.
 
-**Transcript confidentiality.** Transcripts captured for inter-task context **SHOULD** be stored only in memory or in ephemeral container storage. Implementations **SHOULD NOT** persist transcripts to external storage unless checkpointing is explicitly enabled.
-
 **Token and secret handling.** Provider credentials (e.g., `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GITHUB_TOKEN`) **MUST NOT** be logged to stderr or embedded in JSONL events. Implementations **MUST** treat all credential env vars as opaque secrets.
 
 ---
@@ -1042,11 +898,9 @@ The following ordered work items describe the implementation sequence:
 
 *(This section is non-normative.)*
 
-**Data residency.** All agent execution occurs within the gh-aw Actions container. No workflow content, prompts, or transcripts leave the container except via the Pi SDK to the configured LLM provider endpoint, or via OTLP to the configured telemetry endpoint.
-
-**Transcript retention.** Task transcripts held in memory for inter-task context are discarded when the harness process exits. If checkpointing is enabled, transcript data may be persisted to GitHub Actions artifacts; workflow authors **SHOULD** evaluate the sensitivity of transcript content before enabling checkpointing.
+**Data residency.** All agent execution occurs within the gh-aw Actions container. No workflow content, prompts, or session data leave the container except via the Pi SDK to the configured LLM provider endpoint, or via OTLP to the configured telemetry endpoint.
 
-**Telemetry scope.** When `observability.otlp` is configured, OTel spans contain task names, model names, token counts, and cost data. They **SHOULD NOT** contain raw prompt or response text. Implementations **SHOULD** redact sensitive content from span attributes.
+**Telemetry scope.** When `observability.otlp` is configured, OTel spans contain model names, token counts, and cost data. They **SHOULD NOT** contain raw prompt or response text. Implementations **SHOULD** redact sensitive content from span attributes.
 
 **Model provider data handling.** Prompt content is transmitted to the LLM provider using the credentials AWF injects into the container. Workflow authors are responsible for ensuring that content transmitted to LLM providers complies with applicable data handling policies.
 

From 9be73be7d0a23c030cb2f8217c4c8f4dacbe485d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 10:47:50 +0000
Subject: [PATCH 10/14] docs: add context observability section to aw-harness
 spec

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/44933735-2676-4d52-a7c8-b2d7b7628f90

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 106 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 100 insertions(+), 6 deletions(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 7e5f3a8b199..5d95a5f1766 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -239,6 +239,7 @@ A conforming implementation **MUST** exit with code `0` if and only if the agent
 
 - **stdout**: Reserved for structured output (e.g., JSON summaries). A conforming implementation **SHOULD NOT** write diagnostic messages to stdout.
 - **stderr**: All diagnostic messages, JSONL event stream, and debug output **MUST** be written to stderr.
+- **GitHub Actions step summary** (`$GITHUB_STEP_SUMMARY`): The harness **MUST** write a Markdown-formatted execution summary to the file path indicated by the `GITHUB_STEP_SUMMARY` environment variable when that variable is set. The summary **MUST** be valid GitHub-flavored Markdown so that it renders correctly in the GitHub Actions step summary UI.
 
 ---
 
@@ -670,25 +671,109 @@ The following six extensions **MUST** be loaded into the `AgentSession` created
 
 ### 8.6 Extension 6: Observability
 
-**Purpose:** Emits JSONL events to stderr and generates OTel spans.
+**Purpose:** Emits structured event streams to stderr, writes a context provenance file for downstream analysis, renders a Markdown step summary, and reports per-turn token consumption.
 
 **Requirements:**
 
+#### 8.6.1 JSONL Event Stream
+
 - The extension **MUST** subscribe to `agent_start`, `turn_end`, `tool_execution_end`, and `agent_end` events.
 - On each event, the extension **MUST** emit a corresponding JSONL record to stderr.
-- If `observability.otlp.endpoint` is configured in the workflow frontmatter, the extension **MUST** create and close OTel spans for each task.
+- If `observability.otlp.endpoint` is configured in the workflow frontmatter, the extension **MUST** create and close OTel spans for the session.
 - OTel span attributes **MUST** include at minimum: model, token counts, and cost.
 
-> [!NOTE] Non-normative example.
+#### 8.6.2 Context Provenance File
+
+- The extension **MUST** produce a context provenance file at a well-known path (e.g., `/tmp/gh-aw/context-provenance.jsonl`) when the session completes.
+- The file **MUST** contain one JSON record per context entry added to the session, in chronological order. Each record **MUST** include:
+  - `timestamp` (ISO 8601 string): When the entry was added.
+  - `source` (string): The declared origin of the text — one of `"prompt"` (from `prompt.txt`), `"import"` (from an `imports:` file, with `path` sub-field), or `"system"` (from `harness.system`).
+  - `path` (string, **OPTIONAL**): Repository-relative path for `"import"` entries.
+  - `tokens` (number): Estimated token count for this entry at the time it was added.
+  - `cumulative_tokens` (number): Running total of tokens in the context window at the time of this entry.
+  - `role` (string): The message role — `"user"`, `"assistant"`, or `"system"`.
+- The purpose of this file is to allow downstream tools (e.g., `gh aw audit`) to perform deep analysis of context growth, identify which imports consumed the most token budget, and diagnose context-window pressure.
+
+#### 8.6.3 GitHub Actions Step Summary
+
+- When the `GITHUB_STEP_SUMMARY` environment variable is set, the extension **MUST** write a Markdown-formatted execution summary to the file at that path.
+- The summary **MUST** be valid GitHub-flavored Markdown so that it renders correctly in the GitHub Actions step summary UI.
+- The summary **MUST** include at minimum:
+  - A header identifying the workflow and model used.
+  - A table showing per-turn token consumption (input tokens, output tokens, cumulative total, and estimated cost).
+  - A final row with session totals (total tokens, total cost, elapsed time).
+  - A context provenance section listing each `imports:` file with its token contribution.
+
+#### 8.6.4 Per-Turn Token Consumption Output
+
+- The extension **MUST** subscribe to `turn_end` events and emit a human-readable token consumption line to stderr after each turn.
+- The line **MUST** report: turn number, input tokens, output tokens, cumulative total tokens, and estimated cumulative cost.
+- The line **MUST** be formatted as valid GitHub-flavored Markdown (e.g., using a `>` blockquote prefix) so that it renders correctly when appended to the step summary.
+
+> [!NOTE] Non-normative examples.
+>
+> **JSONL event (turn_end):**
+> ```json
+> {"event":"turn_end","turn":3,"input_tokens":4200,"output_tokens":850,"cumulative_tokens":15320,"cumulative_cost_usd":0.0412,"model":"claude-sonnet-4.6","ts":"2026-05-02T10:30:00.000Z"}
+> ```
+>
+> **Context provenance record:**
+> ```json
+> {"timestamp":"2026-05-02T10:29:00.000Z","source":"import","path":"skills/reporting/SKILL.md","tokens":1240,"cumulative_tokens":1240,"role":"user"}
+> {"timestamp":"2026-05-02T10:29:00.001Z","source":"prompt","tokens":520,"cumulative_tokens":1760,"role":"user"}
+> ```
+>
+> **Step summary (excerpt):**
+> ```markdown
+> ## AW Harness Run — `claude-sonnet-4.6`
+>
+> | Turn | Input Tokens | Output Tokens | Cumulative | Est. Cost |
+> |------|-------------|---------------|------------|-----------|
+> | 1    | 1,760       | 420           | 2,180      | $0.0058   |
+> | 2    | 2,180       | 640           | 2,820      | $0.0076   |
+> | **Total** | | | **2,820** | **$0.0076** |
+>
+> ### Context Provenance
+> | Source | Path | Tokens |
+> |--------|------|--------|
+> | import | skills/reporting/SKILL.md | 1,240 |
+> | prompt | _(prompt.txt)_ | 520 |
+> ```
+>
+> **Implementation sketch:**
 >
 > ```typescript
 > export default function(pi: ExtensionAPI) {
+>   let turnCount = 0;
+>   let cumulativeTokens = 0;
+>   let cumulativeCost = 0;
+>   const provenanceLog: ProvenanceEntry[] = [];
+>
 >   pi.on("agent_start", async (event) => {
 >     emitJsonl({ event: "session_start", model: currentModel });
 >     startOtelSpan("aw_session");
+>     recordContextProvenance(provenanceLog); // records imports + prompt entries
 >   });
 >
->   pi.on("turn_end", async (event) => {
+>   pi.on("turn_end", async (event, ctx) => {
+>     turnCount++;
+>     cumulativeTokens += event.inputTokens + event.outputTokens;
+>     cumulativeCost += event.costUsd ?? 0;
+>     emitJsonl({
+>       event: "turn_end",
+>       turn: turnCount,
+>       input_tokens: event.inputTokens,
+>       output_tokens: event.outputTokens,
+>       cumulative_tokens: cumulativeTokens,
+>       cumulative_cost_usd: cumulativeCost,
+>       model: currentModel,
+>       ts: new Date().toISOString(),
+>     });
+>     // Human-readable per-turn line to stderr (markdown blockquote)
+>     process.stderr.write(
+>       `> **Turn ${turnCount}**: ${event.inputTokens} in / ${event.outputTokens} out ` +
+>       `| cumulative ${cumulativeTokens.toLocaleString()} tokens ($${cumulativeCost.toFixed(4)})\n`
+>     );
 >     recordOtelAttributes(event);
 >   });
 >
@@ -697,8 +782,10 @@ The following six extensions **MUST** be loaded into the `AgentSession` created
 >   });
 >
 >   pi.on("agent_end", async (event) => {
->     emitJsonl({ event: "session_end", tokens: event.tokens, cost: event.cost });
+>     emitJsonl({ event: "session_end", tokens: cumulativeTokens, cost: cumulativeCost });
 >     endOtelSpan("aw_session");
+>     await writeContextProvenanceFile(provenanceLog);
+>     await writeStepSummary({ turnCount, cumulativeTokens, cumulativeCost, provenanceLog });
 >   });
 > }
 > ```
@@ -870,7 +957,12 @@ The following ordered work items describe the implementation sequence:
 
 9. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
 
-10. **Implement observability extension** — Pi extension that emits JSONL to stderr on agent/tool events. Generates OTel spans using `observability.otlp` config.
+10. **Implement observability extension** — Pi extension that:
+    - Emits JSONL to stderr on agent/tool events (§8.6.1).
+    - Writes a context provenance file (`/tmp/gh-aw/context-provenance.jsonl`) on `agent_end` recording the source and token cost of every context entry (§8.6.2).
+    - Appends a Markdown execution summary table (per-turn tokens + context provenance) to `$GITHUB_STEP_SUMMARY` when that env var is set (§8.6.3).
+    - Emits a human-readable per-turn token consumption line to stderr after each `turn_end` (§8.6.4).
+    - Generates OTel spans using `observability.otlp` config.
 
 11. **Write tests** — Unit tests for loader, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
 
@@ -902,6 +994,8 @@ The following ordered work items describe the implementation sequence:
 
 **Telemetry scope.** When `observability.otlp` is configured, OTel spans contain model names, token counts, and cost data. They **SHOULD NOT** contain raw prompt or response text. Implementations **SHOULD** redact sensitive content from span attributes.
 
+**Context provenance file.** The context provenance file (`/tmp/gh-aw/context-provenance.jsonl`) records the source path and token count of every context entry added to the session. It **MUST NOT** include raw prompt or response text; only metadata (source type, path, token counts) is recorded. Workflow authors **SHOULD** evaluate the sensitivity of file paths before enabling downstream analysis tools that read this file.
+
 **Model provider data handling.** Prompt content is transmitted to the LLM provider using the credentials AWF injects into the container. Workflow authors are responsible for ensuring that content transmitted to LLM providers complies with applicable data handling policies.
 
 ---

From cbc260c81d1c7164a6d34974194fcb1dd4a01249 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 10:57:10 +0000
Subject: [PATCH 11/14] =?UTF-8?q?docs:=20add=20=C2=A74.3=20Pi=20SDK=20and?=
 =?UTF-8?q?=20OpenClaw=20extension=20format=20compatibility?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d0716110-540a-4ed3-b2a2-39304ecdfa79

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 43 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 42 insertions(+), 1 deletion(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 5d95a5f1766..dc4f35e29e1 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -28,6 +28,7 @@ This is an internal design specification for the GitHub gh-aw project. It is not
 2. [Conformance](#2-conformance)
 3. [Terminology and Definitions](#3-terminology-and-definitions)
 4. [Architecture](#4-architecture)
+   - [4.3 Pi SDK and OpenClaw Extension Format Compatibility](#43-pi-sdk-and-openclaw-extension-format-compatibility)
 5. [Harness Invocation Contract](#5-harness-invocation-contract)
 6. [Workflow Definition](#6-workflow-definition)
 7. [Single-Session Execution Model](#7-single-session-execution-model)
@@ -201,6 +202,43 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 
 9. **Observable.** All implementations **MUST** emit a JSONL event stream to stderr and **SHOULD** generate OTel spans when an OTLP endpoint is configured.
 
+### 4.3 Pi SDK and OpenClaw Extension Format Compatibility
+
+*(This section is non-normative.)*
+
+The AW Harness uses Pi SDK's `ExtensionAPI` as its native extension mechanism. OpenClaw (a separate open-source agent runtime at `openclaw/openclaw`) uses a structurally similar but distinct plugin API (`OpenClawPluginApi`). This section documents the relationship between the two APIs and the compatibility requirements that apply.
+
+#### 4.3.1 API Surface Comparison
+
+| Pi SDK (`ExtensionAPI`) | OpenClaw (`OpenClawPluginApi`) | Notes |
+|-------------------------|-------------------------------|-------|
+| `pi.registerTool(tool)` | `api.registerTool(tool, opts?)` | Semantically equivalent; parameter schema shapes may differ (Pi uses Pi-native schema; OpenClaw uses TypeBox `Type.Object(...)`) |
+| `pi.on(event, handler)` | `api.on(hookName, handler, opts?)` | Event names differ between the two runtimes (e.g., Pi `"turn_end"` vs. OpenClaw `"after_agent_turn"`) |
+| `pi.registerProvider(id, config)` | `api.registerProvider(...)` | Config shape differs; OpenClaw providers declared in `openclaw.plugin.json` manifest |
+| `(no direct equivalent)` | `api.registerAgentHarness(...)` | OpenClaw's experimental low-level agent executor seam; Pi SDK exposes the session directly instead |
+| `(lifecycle via pi.on)` | `api.registerHook(events, handler)` | OpenClaw has a separate lifecycle hook registry distinct from its agent event hooks |
+| `(no equivalent)` | `api.registerSessionExtension(...)` | OpenClaw-specific: plugin-owned session state projected through Gateway sessions |
+
+#### 4.3.2 Manifest Requirements (OpenClaw Only)
+
+OpenClaw plugins **MUST** ship an `openclaw.plugin.json` manifest in the plugin root. Pi SDK does not use a manifest file; extensions are registered programmatically. The two formats are **not interchangeable**:
+
+- A Pi extension is a TypeScript function `(pi: ExtensionAPI) => void`.
+- An OpenClaw plugin is a package exporting `definePluginEntry({ id, name, description, register(api) { ... } })` with an accompanying `openclaw.plugin.json`.
+
+#### 4.3.3 Compatibility Requirements
+
+The gh-aw extensions in this specification target Pi SDK only. However, to facilitate future portability to OpenClaw:
+
+- Extensions **SHOULD** keep Pi SDK dependencies isolated to the entry function boundary rather than scattered across extension modules.
+- Tool definitions (name, description, parameter schema, and `execute` function) **SHOULD** be expressed as plain objects so they can be adapted to either `pi.registerTool()` or `api.registerTool()` without rewriting tool logic.
+- Event handler functions **SHOULD** treat the event source (Pi `"turn_end"` vs. OpenClaw `"after_agent_turn"`) as a configuration layer, not embedded constants, to ease future porting.
+- Extensions **MUST NOT** use Pi-internal APIs that have no OpenClaw equivalent (e.g., direct `AgentSession` handle manipulation outside the `ExtensionAPI` surface).
+
+#### 4.3.4 `registerAgentHarness` Consideration
+
+OpenClaw exposes an experimental `api.registerAgentHarness(...)` method that provides a low-level agent executor seam. This seam could in principle host the AW Harness loop within an OpenClaw gateway. The gh-aw aw harness **does not** require or target this seam; it runs standalone via Pi SDK. If a future integration with OpenClaw's gateway is desired, `registerAgentHarness` is the appropriate OpenClaw extension point to evaluate at that time.
+
 ---
 
 ## 5. Harness Invocation Contract
@@ -477,7 +515,7 @@ A conforming implementation **MUST** execute the workflow as follows:
 
 ## 8. Extensions
 
-All gh-aw-specific behavior **MUST** be packaged as Pi extensions. Each extension **MUST** be a standalone TypeScript module that exports a default function with signature `(pi: ExtensionAPI) => void | Promise<void>`.
+All gh-aw-specific behavior **MUST** be packaged as Pi extensions. Each extension **MUST** be a standalone TypeScript module that exports a default function with signature `(pi: ExtensionAPI) => void | Promise<void>`. For compatibility guidance between Pi's `ExtensionAPI` and OpenClaw's `OpenClawPluginApi`, see [§4.3](#43-pi-sdk-and-openclaw-extension-format-compatibility).
 
 The following six extensions **MUST** be loaded into the `AgentSession` created by the harness.
 
@@ -1026,3 +1064,6 @@ OpenTelemetry specification for distributed tracing. <https://opentelemetry.io/d
 
 **[gh-aw]**
 GitHub Agentic Workflows — the gh-aw CLI extension that compiles Markdown workflow files to GitHub Actions YAML. <https://github.com/github/gh-aw>
+
+**[OpenClaw]**
+OpenClaw — open-source agent runtime with a plugin SDK (`OpenClawPluginApi`) that is structurally related to but distinct from Pi SDK's `ExtensionAPI`. See §4.3 for API compatibility notes. <https://github.com/openclaw/openclaw>

From 4abef9bb7543e7f84a3caff14944d0cac00e089a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 11:19:56 +0000
Subject: [PATCH 12/14] docs: remove OpenClaw section; add harness.extensions
 for user Pi extensions

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/fee78d07-2ea8-4402-8baf-7257b063450c

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 112 ++++++++++++++++++++++++++------------------
 1 file changed, 67 insertions(+), 45 deletions(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index dc4f35e29e1..55d9367f8ab 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -28,7 +28,6 @@ This is an internal design specification for the GitHub gh-aw project. It is not
 2. [Conformance](#2-conformance)
 3. [Terminology and Definitions](#3-terminology-and-definitions)
 4. [Architecture](#4-architecture)
-   - [4.3 Pi SDK and OpenClaw Extension Format Compatibility](#43-pi-sdk-and-openclaw-extension-format-compatibility)
 5. [Harness Invocation Contract](#5-harness-invocation-contract)
 6. [Workflow Definition](#6-workflow-definition)
 7. [Single-Session Execution Model](#7-single-session-execution-model)
@@ -202,43 +201,6 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 
 9. **Observable.** All implementations **MUST** emit a JSONL event stream to stderr and **SHOULD** generate OTel spans when an OTLP endpoint is configured.
 
-### 4.3 Pi SDK and OpenClaw Extension Format Compatibility
-
-*(This section is non-normative.)*
-
-The AW Harness uses Pi SDK's `ExtensionAPI` as its native extension mechanism. OpenClaw (a separate open-source agent runtime at `openclaw/openclaw`) uses a structurally similar but distinct plugin API (`OpenClawPluginApi`). This section documents the relationship between the two APIs and the compatibility requirements that apply.
-
-#### 4.3.1 API Surface Comparison
-
-| Pi SDK (`ExtensionAPI`) | OpenClaw (`OpenClawPluginApi`) | Notes |
-|-------------------------|-------------------------------|-------|
-| `pi.registerTool(tool)` | `api.registerTool(tool, opts?)` | Semantically equivalent; parameter schema shapes may differ (Pi uses Pi-native schema; OpenClaw uses TypeBox `Type.Object(...)`) |
-| `pi.on(event, handler)` | `api.on(hookName, handler, opts?)` | Event names differ between the two runtimes (e.g., Pi `"turn_end"` vs. OpenClaw `"after_agent_turn"`) |
-| `pi.registerProvider(id, config)` | `api.registerProvider(...)` | Config shape differs; OpenClaw providers declared in `openclaw.plugin.json` manifest |
-| `(no direct equivalent)` | `api.registerAgentHarness(...)` | OpenClaw's experimental low-level agent executor seam; Pi SDK exposes the session directly instead |
-| `(lifecycle via pi.on)` | `api.registerHook(events, handler)` | OpenClaw has a separate lifecycle hook registry distinct from its agent event hooks |
-| `(no equivalent)` | `api.registerSessionExtension(...)` | OpenClaw-specific: plugin-owned session state projected through Gateway sessions |
-
-#### 4.3.2 Manifest Requirements (OpenClaw Only)
-
-OpenClaw plugins **MUST** ship an `openclaw.plugin.json` manifest in the plugin root. Pi SDK does not use a manifest file; extensions are registered programmatically. The two formats are **not interchangeable**:
-
-- A Pi extension is a TypeScript function `(pi: ExtensionAPI) => void`.
-- An OpenClaw plugin is a package exporting `definePluginEntry({ id, name, description, register(api) { ... } })` with an accompanying `openclaw.plugin.json`.
-
-#### 4.3.3 Compatibility Requirements
-
-The gh-aw extensions in this specification target Pi SDK only. However, to facilitate future portability to OpenClaw:
-
-- Extensions **SHOULD** keep Pi SDK dependencies isolated to the entry function boundary rather than scattered across extension modules.
-- Tool definitions (name, description, parameter schema, and `execute` function) **SHOULD** be expressed as plain objects so they can be adapted to either `pi.registerTool()` or `api.registerTool()` without rewriting tool logic.
-- Event handler functions **SHOULD** treat the event source (Pi `"turn_end"` vs. OpenClaw `"after_agent_turn"`) as a configuration layer, not embedded constants, to ease future porting.
-- Extensions **MUST NOT** use Pi-internal APIs that have no OpenClaw equivalent (e.g., direct `AgentSession` handle manipulation outside the `ExtensionAPI` surface).
-
-#### 4.3.4 `registerAgentHarness` Consideration
-
-OpenClaw exposes an experimental `api.registerAgentHarness(...)` method that provides a low-level agent executor seam. This seam could in principle host the AW Harness loop within an OpenClaw gateway. The gh-aw aw harness **does not** require or target this seam; it runs standalone via Pi SDK. If a future integration with OpenClaw's gateway is desired, `registerAgentHarness` is the appropriate OpenClaw extension point to evaluate at that time.
-
 ---
 
 ## 5. Harness Invocation Contract
@@ -353,6 +315,11 @@ An `engine: aw` workflow document **MUST** include a YAML frontmatter block conf
 >     time-critical-minutes: 2
 >     budget-warn-percent: 75
 >     budget-critical-percent: 90
+>
+>   # Optional: Pi SDK-compatible extensions to load alongside built-in gh-aw extensions.
+>   # Each entry is a repo-relative path to a compiled .cjs file or an npm package name.
+>   extensions:
+>     - ./extensions/custom-tool.cjs
 > ---
 >
 > Review all changes pushed to the default branch in the last 24 hours.
@@ -384,7 +351,55 @@ The `harness.steering` key is **OPTIONAL**. When present, it **MAY** contain:
 - `budget-warn-percent` (number): Budget percentage at which a warning **SHOULD** be injected. Default: `75`.
 - `budget-critical-percent` (number): Budget percentage at which the session **MUST** be aborted. Default: `90`.
 
-#### 6.1.4 `imports:`
+#### 6.1.4 `harness.extensions`
+
+The `harness.extensions` key is **OPTIONAL**. When present, it **MUST** be a list of Pi SDK-compatible extension references that the harness loads and registers into the `AgentSession` at runtime, in addition to the built-in gh-aw extensions.
+
+Each entry is a string in one of the following forms:
+
+- **Repository-relative path** — a path starting with `./` or `../` pointing to a compiled CommonJS file (`.cjs`) co-located with the workflow (e.g., `./extensions/my-tool.cjs`).
+- **npm package name** — a bare or scoped npm package name (e.g., `@my-org/my-pi-extension`) that is available in the container `node_modules` at runtime.
+
+Each referenced module **MUST** export a default function with the Pi SDK `ExtensionAPI` signature:
+
+```typescript
+export default function(pi: ExtensionAPI): void | Promise<void>;
+```
+
+A conforming implementation **MUST**:
+
+- Dynamically load each extension using `require()` (for local paths) or `require()` / `import()` (for npm packages).
+- Register each user extension into the `AgentSession` alongside the built-in gh-aw extensions, after all built-in extensions have been registered.
+- Emit a warning to stderr if an extension fails to load, and **MUST NOT** abort the session for a failed user extension unless `harness.extensions-required: true` is set.
+
+A conforming implementation **MUST NOT** allow user extensions to override or replace the built-in gh-aw extensions. User extensions run after all built-in extensions and **MUST NOT** be able to unregister or intercept built-in extension behavior.
+
+> [!NOTE] Non-normative example.
+>
+> ```yaml
+> harness:
+>   budget:
+>     max-cost-usd: 5.00
+>   extensions:
+>     - ./extensions/custom-tool.cjs       # Local compiled extension
+>     - @my-org/pi-extension-rate-limiter  # npm package extension
+> ```
+>
+> Each extension module exports a Pi SDK-compatible function:
+>
+> ```typescript
+> // extensions/custom-tool.cjs (compiled from TypeScript)
+> module.exports = function(pi) {
+>   pi.registerTool({
+>     name: "my_custom_tool",
+>     description: "Does something custom",
+>     parameters: { type: "object", properties: { input: { type: "string" } } },
+>     execute: async (_id, params) => ({ content: [{ type: "text", text: params.input }] }),
+>   });
+> };
+> ```
+
+#### 6.1.5 `imports:`
 
 The `imports:` key is **OPTIONAL**. It is a standard gh-aw frontmatter key that lists the paths of files whose contents **MUST** be resolved by the compiler and made available to the harness as part of the compiled inputs.
 
@@ -434,7 +449,7 @@ A conforming implementation **MUST** source every item included in the session's
 
 - The Markdown body from `prompt.txt` (loaded per [Section 6.3](#63-prompt-loading)).
 - The `harness.system` prompt if declared in the `harness:` frontmatter block.
-- Files, skills, and sub-workflows declared via the `imports:` frontmatter key (see [Section 6.1.4](#614-imports)) and resolved by the compiler into inputs passed at invocation time.
+- Files, skills, and sub-workflows declared via the `imports:` frontmatter key (see [Section 6.1.5](#615-imports)) and resolved by the compiler into inputs passed at invocation time.
 
 A conforming implementation **MUST NOT** automatically load AGENTS.md files, `.github/agents/` entries, skills directories, or any other ambient repository files unless they are explicitly listed in `imports:`. This behavior is a deliberate divergence from engines such as `engine: copilot` that inject ambient context automatically.
 
@@ -470,6 +485,10 @@ A conforming implementation **MUST** execute the workflow as follows:
 > async function main() {
 >   const { configPath, promptPath } = parseArgs(process.argv);
 >   const { config, prompt } = loadInputs(configPath, promptPath);
+>
+>   // Load user-declared extensions from harness.extensions
+>   const userExtensions = await loadUserExtensions(config.harness?.extensions ?? []);
+>
 >   const extensions = [
 >     providerSetupExtension,
 >     safeOutputsExtension,
@@ -477,6 +496,7 @@ A conforming implementation **MUST** execute the workflow as follows:
 >     steeringExtension,
 >     repairExtension,
 >     observabilityExtension,
+>     ...userExtensions,  // User extensions run after built-in extensions
 >   ];
 >
 >   const { session } = await createAgentSession({
@@ -493,7 +513,7 @@ A conforming implementation **MUST** execute the workflow as follows:
 
 1. The implementation **MUST** invoke `createAgentSession()` exactly once per harness invocation.
 2. The prompt passed to `session.prompt()` **MUST** be the full contents of `prompt.txt` as loaded per [Section 6.3](#63-prompt-loading).
-3. The implementation **MUST** load all six gh-aw Pi extensions (see [Section 8](#8-extensions)) into the session.
+3. The implementation **MUST** load all six gh-aw Pi extensions (see [Section 8](#8-extensions)) into the session. If `harness.extensions` is declared in the configuration, the implementation **MUST** also load each user-declared extension after the built-in extensions (see [Section 6.1.4](#614-harness-extensions)).
 4. After the session completes (success or failure), the implementation **MUST** call `session.dispose()`.
 5. If the budget gate has been triggered (via the cost-tracker extension), the implementation **MUST** exit with code `1`.
 
@@ -506,6 +526,7 @@ A conforming implementation **MUST** execute the workflow as follows:
    - Safe-output tools registered
    - Steering, repair, cost, observability extensions active
    (MCP tools available as bash CLI commands via cli-proxy — no bridging needed)
+   - User extensions (from harness.extensions) loaded and registered after built-ins
 3. session.prompt(promptText) → Pi agent loop runs
 4. Extensions handle events (cost tracking, steering, observability)
 5. session.dispose()
@@ -515,7 +536,7 @@ A conforming implementation **MUST** execute the workflow as follows:
 
 ## 8. Extensions
 
-All gh-aw-specific behavior **MUST** be packaged as Pi extensions. Each extension **MUST** be a standalone TypeScript module that exports a default function with signature `(pi: ExtensionAPI) => void | Promise<void>`. For compatibility guidance between Pi's `ExtensionAPI` and OpenClaw's `OpenClawPluginApi`, see [§4.3](#43-pi-sdk-and-openclaw-extension-format-compatibility).
+All gh-aw-specific behavior **MUST** be packaged as Pi extensions. Each extension **MUST** be a standalone TypeScript module that exports a default function with signature `(pi: ExtensionAPI) => void | Promise<void>`.
 
 The following six extensions **MUST** be loaded into the `AgentSession` created by the harness.
 
@@ -987,6 +1008,8 @@ The following ordered work items describe the implementation sequence:
 
 5. **Implement entry point** — Create a single `createAgentSession()` with gh-aw extensions loaded. Pass `prompt.txt` contents as the prompt. Dispose session on completion.
 
+5a. **Implement user extension loader** — Read `harness.extensions` from `config.json`. For each entry: resolve repository-relative paths from the harness working directory; load npm package names via `require()`. Verify each loaded module exports a default function of type `(pi: ExtensionAPI) => void`. Emit a stderr warning for each failed load; abort only if `harness.extensions-required: true` is set. Append loaded extensions to the session extension list after built-ins.
+
 6. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
 
 7. **Implement cost tracker extension** — Pi extension that monitors `turn_end` events for token/cost data. Enforces soft (steer warning) and hard (abort) budget gates.
@@ -1018,6 +1041,8 @@ The following ordered work items describe the implementation sequence:
 
 **Safe outputs isolation.** The safe-outputs extension **MUST NOT** perform live GitHub API calls during agent execution. All GitHub mutations **MUST** be expressed as artifact files processed by the post-agent job, which applies threat detection and validation before acting.
 
+**User extension isolation.** Extensions declared via `harness.extensions` run inside the same Node.js process as the built-in extensions. A conforming implementation **MUST NOT** execute user extensions with elevated privileges. Extension authors are responsible for ensuring that their extensions do not exfiltrate credentials or subvert built-in budget or safe-outputs behavior. Workflow authors are responsible for auditing third-party npm extension packages before referencing them in `harness.extensions`.
+
 **Budget enforcement.** The cost-tracker extension provides a hard budget gate. A conforming implementation **MUST** abort the session if the cost exceeds the configured maximum, preventing runaway spending from misbehaving agents.
 
 **Token and secret handling.** Provider credentials (e.g., `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GITHUB_TOKEN`) **MUST NOT** be logged to stderr or embedded in JSONL events. Implementations **MUST** treat all credential env vars as opaque secrets.
@@ -1064,6 +1089,3 @@ OpenTelemetry specification for distributed tracing. <https://opentelemetry.io/d
 
 **[gh-aw]**
 GitHub Agentic Workflows — the gh-aw CLI extension that compiles Markdown workflow files to GitHub Actions YAML. <https://github.com/github/gh-aw>
-
-**[OpenClaw]**
-OpenClaw — open-source agent runtime with a plugin SDK (`OpenClawPluginApi`) that is structurally related to but distinct from Pi SDK's `ExtensionAPI`. See §4.3 for API compatibility notes. <https://github.com/openclaw/openclaw>

From e8988b75857df859a7988f5db8cc4e64f961c399 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 11:42:55 +0000
Subject: [PATCH 13/14] docs: remove openclaw section, add piagent extensions
 support, fix budget/safe-outputs design

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d1715722-ea93-4860-873f-802b6fab33c5

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 146 ++++++++++++++------------------------------
 1 file changed, 46 insertions(+), 100 deletions(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 55d9367f8ab..5852f706a1e 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -46,7 +46,7 @@ This is an internal design specification for the GitHub gh-aw project. It is not
 
 The existing gh-aw harnesses (`copilot_harness.cjs`, `claude_harness.cjs`) are thin retry loops around a single CLI invocation. As workflow complexity grows, authors need structured budget management, cost tracking, time steering, and structured observability — none of which the current harnesses provide.
 
-The AW Harness introduces `engine: aw` as a new opt-in execution engine. It does not replace existing engines; `engine: copilot`, `engine: claude`, and `engine: codex` continue to operate unchanged via their current harnesses. The AW Harness is a Pi SDK application: it creates a single `AgentSession` and runs the compiled prompt through it, with gh-aw Pi extensions providing budget gating, steering, safe outputs, and observability.
+The AW Harness introduces `engine: aw` as a new opt-in execution engine. It does not replace existing engines; `engine: copilot`, `engine: claude`, and `engine: codex` continue to operate unchanged via their current harnesses. The AW Harness is a Pi SDK application: it creates a single `AgentSession` and runs the compiled prompt through it, with gh-aw Pi extensions providing budget gating, steering, and observability. Safe-output tools are CLI commands provided by `cli-proxy` and require no special Pi-level extension.
 
 The harness is designed exclusively for the gh-aw Actions container environment. It assumes the firewall and MCP gateway are already running. AWF injects provider credentials into the container environment; the harness reads these credentials and passes them to Pi SDK directly.
 
@@ -161,7 +161,7 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 │  │  │  ┌──────────────────────────────────────────┐   │ │   │
 │  │  │  │  gh-aw Pi Extensions (loaded into the    │   │ │   │
 │  │  │  │  AgentSession via ExtensionAPI):          │   │ │   │
-│  │  │  │  ├─ safe-outputs (tools + artifact write) │   │ │   │
+│  │  │  │  ├─ cost-tracker (budget gates)              │   │ │   │
 │  │  │  │  ├─ cost-tracker (budget gates + events)  │   │ │   │
 │  │  │  │  ├─ steering (time/budget pressure)       │   │ │   │
 │  │  │  │  ├─ repair (broken session recovery)      │   │ │   │
@@ -303,8 +303,7 @@ An `engine: aw` workflow document **MUST** include a YAML frontmatter block conf
 > # ── Harness config (optional) ───────────────────────────────
 > harness:
 >   budget:
->     max-cost-usd: 5.00
->     warn-at-percent: 80
+>     max-effective-tokens: 100000
 >
 >   context:
 >     compaction: summarize
@@ -332,8 +331,7 @@ An `engine: aw` workflow document **MUST** include a YAML frontmatter block conf
 
 The `harness.budget` key is **OPTIONAL**. When present, it **MUST** contain:
 
-- `max-cost-usd` (number): Maximum total cost in USD for the run. The cost-tracker extension **MUST** abort the current session if this limit is exceeded.
-- `warn-at-percent` (number, 0–100): Percentage of `max-cost-usd` at which a steering warning **MUST** be injected.
+- `max-effective-tokens` (number): Maximum effective token count for the run. The cost-tracker extension **MUST** abort the current session if this limit is exceeded. Using token count rather than cost makes this budget reliable across providers where pricing is unknown.
 
 #### 6.1.2 `harness.context`
 
@@ -379,7 +377,7 @@ A conforming implementation **MUST NOT** allow user extensions to override or re
 > ```yaml
 > harness:
 >   budget:
->     max-cost-usd: 5.00
+>     max-effective-tokens: 100000
 >   extensions:
 >     - ./extensions/custom-tool.cjs       # Local compiled extension
 >     - @my-org/pi-extension-rate-limiter  # npm package extension
@@ -448,7 +446,6 @@ The AW Harness **MUST NOT** inject any predefined or ambient context into the ag
 A conforming implementation **MUST** source every item included in the session's initial prompt from one of the following explicitly declared origins:
 
 - The Markdown body from `prompt.txt` (loaded per [Section 6.3](#63-prompt-loading)).
-- The `harness.system` prompt if declared in the `harness:` frontmatter block.
 - Files, skills, and sub-workflows declared via the `imports:` frontmatter key (see [Section 6.1.5](#615-imports)) and resolved by the compiler into inputs passed at invocation time.
 
 A conforming implementation **MUST NOT** automatically load AGENTS.md files, `.github/agents/` entries, skills directories, or any other ambient repository files unless they are explicitly listed in `imports:`. This behavior is a deliberate divergence from engines such as `engine: copilot` that inject ambient context automatically.
@@ -491,7 +488,6 @@ A conforming implementation **MUST** execute the workflow as follows:
 >
 >   const extensions = [
 >     providerSetupExtension,
->     safeOutputsExtension,
 >     costTrackerExtension,
 >     steeringExtension,
 >     repairExtension,
@@ -503,7 +499,6 @@ A conforming implementation **MUST** execute the workflow as follows:
 >     sessionManager: SessionManager.inMemory(),
 >     extensions,
 >     model: config.model,
->     systemPrompt: config.harness?.system,
 >   });
 >
 >   await session.prompt(prompt);
@@ -513,7 +508,7 @@ A conforming implementation **MUST** execute the workflow as follows:
 
 1. The implementation **MUST** invoke `createAgentSession()` exactly once per harness invocation.
 2. The prompt passed to `session.prompt()` **MUST** be the full contents of `prompt.txt` as loaded per [Section 6.3](#63-prompt-loading).
-3. The implementation **MUST** load all six gh-aw Pi extensions (see [Section 8](#8-extensions)) into the session. If `harness.extensions` is declared in the configuration, the implementation **MUST** also load each user-declared extension after the built-in extensions (see [Section 6.1.4](#614-harness-extensions)).
+3. The implementation **MUST** load all five gh-aw Pi extensions (see [Section 8](#8-extensions)) into the session. If `harness.extensions` is declared in the configuration, the implementation **MUST** also load each user-declared extension after the built-in extensions (see [Section 6.1.4](#614-harness-extensions)).
 4. After the session completes (success or failure), the implementation **MUST** call `session.dispose()`.
 5. If the budget gate has been triggered (via the cost-tracker extension), the implementation **MUST** exit with code `1`.
 
@@ -523,9 +518,8 @@ A conforming implementation **MUST** execute the workflow as follows:
 1. Load config.json + prompt.txt
 2. Create single Pi AgentSession with gh-aw extensions:
    - Provider setup registered
-   - Safe-output tools registered
    - Steering, repair, cost, observability extensions active
-   (MCP tools available as bash CLI commands via cli-proxy — no bridging needed)
+   (MCP tools and safe-output tools available as bash CLI commands via cli-proxy — no bridging needed)
    - User extensions (from harness.extensions) loaded and registered after built-ins
 3. session.prompt(promptText) → Pi agent loop runs
 4. Extensions handle events (cost tracking, steering, observability)
@@ -538,7 +532,7 @@ A conforming implementation **MUST** execute the workflow as follows:
 
 All gh-aw-specific behavior **MUST** be packaged as Pi extensions. Each extension **MUST** be a standalone TypeScript module that exports a default function with signature `(pi: ExtensionAPI) => void | Promise<void>`.
 
-The following six extensions **MUST** be loaded into the `AgentSession` created by the harness.
+The following five extensions **MUST** be loaded into the `AgentSession` created by the harness.
 
 ### 8.1 Extension 1: Provider Setup
 
@@ -547,6 +541,7 @@ The following six extensions **MUST** be loaded into the `AgentSession` created
 **Requirements:**
 
 - The extension **MUST** call `pi.registerProvider()` for each LLM provider whose credentials are present in the environment (e.g., `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GITHUB_TOKEN`).
+- The extension **MUST** also check for provider-specific base URL environment variables (e.g., `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`) and, when present, use them as the endpoint for the corresponding provider.
 - The extension **MUST NOT** hard-code provider URLs or API keys; all credentials **MUST** come from environment variables injected by AWF.
 - The extension **MUST** register at least one provider before any session begins; if no provider credentials are found, the extension **MUST** fail with a descriptive error.
 
@@ -558,98 +553,55 @@ The following six extensions **MUST** be loaded into the `AgentSession` created
 >     pi.registerProvider("anthropic", {
 >       apiKey: process.env.ANTHROPIC_API_KEY,
 >       api: "anthropic",
+>       ...(process.env.ANTHROPIC_BASE_URL ? { baseUrl: process.env.ANTHROPIC_BASE_URL } : {}),
 >     });
 >   }
 >   if (process.env.OPENAI_API_KEY) {
 >     pi.registerProvider("openai", {
 >       apiKey: process.env.OPENAI_API_KEY,
 >       api: "openai-completions",
+>       ...(process.env.OPENAI_BASE_URL ? { baseUrl: process.env.OPENAI_BASE_URL } : {}),
 >     });
 >   }
 >   // Additional providers registered as their env vars are present
 > }
 > ```
 
-### 8.2 Extension 2: Safe Outputs
+### 8.2 Extension 2: Cost Tracker
 
-**Purpose:** Registers safe-output tools (create-issue, create-pull-request, add-comment, etc.) and writes artifact files in the gh-aw safe-outputs format.
+**Purpose:** Monitors token usage via Pi's event stream and enforces budget gates.
 
 **Requirements:**
 
-- The extension **MUST** register at minimum the `create_issue`, `create_pull_request`, and `add_comment` tools via `pi.registerTool()`.
-- Each `execute` handler **MUST** write a safe-output artifact file in the format expected by the post-agent job, without performing any live GitHub API calls.
-- The extension **MUST** subscribe to the `agent_end` Pi event to finalize the safe-output manifest.
-- The extension **MUST** enforce any `max-count` limit declared in the workflow's `safe-outputs` frontmatter.
-
-> [!NOTE] Non-normative example.
->
-> ```typescript
-> export default function(pi: ExtensionAPI) {
->   const config = loadSafeOutputsConfig();
->
->   pi.registerTool({
->     name: "create_issue",
->     description: "Create a GitHub issue (safe output)",
->     parameters: Type.Object({
->       title: Type.String(),
->       body: Type.String(),
->       labels: Type.Optional(Type.Array(Type.String())),
->     }),
->     async execute(toolCallId, params, signal) {
->       const artifact = buildSafeOutputArtifact("create-issue", params, config);
->       await writeSafeOutputArtifact(artifact);
->       return { content: [{ type: "text", text: `Issue queued: ${params.title}` }] };
->     },
->   });
->
->   // ... register other safe-output tools
->
->   pi.on("agent_end", async (event, ctx) => {
->     await finalizeSafeOutputManifest();
->   });
-> }
-> ```
-
-### 8.3 Extension 3: Cost Tracker
-
-**Purpose:** Monitors token usage and cost via Pi's event stream and enforces budget gates.
-
-**Requirements:**
-
-- The extension **MUST** subscribe to `turn_end` events and accumulate total cost from each turn.
-- When accumulated cost reaches or exceeds `harness.budget.warn-at-percent`, the extension **MUST** inject a steering message via `ctx.agent.steer()` warning the agent to be concise.
-- When accumulated cost reaches or exceeds the value corresponding to `harness.steering.budget-critical-percent`, the extension **MUST** call `ctx.agent.abort()`.
-- The extension **MUST** subscribe to `agent_end` and emit a cost summary to the JSONL event stream.
+- The extension **MUST** subscribe to `turn_end` events and accumulate total token usage from each turn.
+- When accumulated tokens reach or exceed `harness.steering.budget-warn-percent` of `harness.budget.max-effective-tokens`, the extension **MUST** inject a steering message via `ctx.agent.steer()` warning the agent to be concise.
+- When accumulated tokens reach or exceed `harness.steering.budget-critical-percent` of `harness.budget.max-effective-tokens`, the extension **MUST** call `ctx.agent.abort()`.
 
 > [!NOTE] Non-normative example.
 >
 > ```typescript
 > export default function(pi: ExtensionAPI) {
 >   const budget = loadBudgetConfig();
->   let totalCost = 0;
+>   let totalTokens = 0;
 >
 >   pi.on("turn_end", async (event, ctx) => {
->     totalCost += extractCostFromTurn(event);
+>     totalTokens += extractTokensFromTurn(event);
 >
->     const percent = (totalCost / budget.maxCostUsd) * 100;
+>     const percent = (totalTokens / budget.maxEffectiveTokens) * 100;
 >     if (percent >= budget.budgetCriticalPercent) {
 >       ctx.agent.abort();
 >     } else if (percent >= budget.budgetWarnPercent) {
 >       ctx.agent.steer({
 >         role: "user",
->         content: `⚠️ Budget: ${percent.toFixed(0)}% used. Be concise.`,
+>         content: `⚠️ Token budget: ${percent.toFixed(0)}% used (${totalTokens}/${budget.maxEffectiveTokens}). Be concise.`,
 >         timestamp: Date.now(),
 >       });
 >     }
 >   });
->
->   pi.on("agent_end", async () => {
->     emitCostSummary(totalCost);
->   });
 > }
 > ```
 
-### 8.4 Extension 4: Steering (Resource Pressure)
+### 8.3 Extension 3: Steering (Resource Pressure)
 
 **Purpose:** Monitors time remaining and budget, and injects steering messages via Pi's native `session.steer()`.
 
@@ -692,7 +644,7 @@ The following six extensions **MUST** be loaded into the `AgentSession` created
 > }
 > ```
 
-### 8.5 Extension 5: Session Repair
+### 8.4 Extension 4: Session Repair
 
 **Purpose:** Detects broken tool calls and repairs the session via Pi's message history manipulation.
 
@@ -728,32 +680,31 @@ The following six extensions **MUST** be loaded into the `AgentSession` created
 > }
 > ```
 
-### 8.6 Extension 6: Observability
+### 8.5 Extension 5: Observability
 
 **Purpose:** Emits structured event streams to stderr, writes a context provenance file for downstream analysis, renders a Markdown step summary, and reports per-turn token consumption.
 
 **Requirements:**
 
-#### 8.6.1 JSONL Event Stream
+#### 8.5.1 JSONL Event Stream
 
-- The extension **MUST** subscribe to `agent_start`, `turn_end`, `tool_execution_end`, and `agent_end` events.
-- On each event, the extension **MUST** emit a corresponding JSONL record to stderr.
+- The extension **MUST** subscribe to all Pi SDK agent events and, on each event, emit a corresponding JSONL record to stderr.
 - If `observability.otlp.endpoint` is configured in the workflow frontmatter, the extension **MUST** create and close OTel spans for the session.
 - OTel span attributes **MUST** include at minimum: model, token counts, and cost.
 
-#### 8.6.2 Context Provenance File
+#### 8.5.2 Context Provenance File
 
 - The extension **MUST** produce a context provenance file at a well-known path (e.g., `/tmp/gh-aw/context-provenance.jsonl`) when the session completes.
 - The file **MUST** contain one JSON record per context entry added to the session, in chronological order. Each record **MUST** include:
   - `timestamp` (ISO 8601 string): When the entry was added.
-  - `source` (string): The declared origin of the text — one of `"prompt"` (from `prompt.txt`), `"import"` (from an `imports:` file, with `path` sub-field), or `"system"` (from `harness.system`).
+  - `source` (string): The declared origin of the text — one of `"prompt"` (from `prompt.txt`) or `"import"` (from an `imports:` file, with `path` sub-field).
   - `path` (string, **OPTIONAL**): Repository-relative path for `"import"` entries.
   - `tokens` (number): Estimated token count for this entry at the time it was added.
   - `cumulative_tokens` (number): Running total of tokens in the context window at the time of this entry.
   - `role` (string): The message role — `"user"`, `"assistant"`, or `"system"`.
 - The purpose of this file is to allow downstream tools (e.g., `gh aw audit`) to perform deep analysis of context growth, identify which imports consumed the most token budget, and diagnose context-window pressure.
 
-#### 8.6.3 GitHub Actions Step Summary
+#### 8.5.3 GitHub Actions Step Summary
 
 - When the `GITHUB_STEP_SUMMARY` environment variable is set, the extension **MUST** write a Markdown-formatted execution summary to the file at that path.
 - The summary **MUST** be valid GitHub-flavored Markdown so that it renders correctly in the GitHub Actions step summary UI.
@@ -763,7 +714,7 @@ The following six extensions **MUST** be loaded into the `AgentSession` created
   - A final row with session totals (total tokens, total cost, elapsed time).
   - A context provenance section listing each `imports:` file with its token contribution.
 
-#### 8.6.4 Per-Turn Token Consumption Output
+#### 8.5.4 Per-Turn Token Consumption Output
 
 - The extension **MUST** subscribe to `turn_end` events and emit a human-readable token consumption line to stderr after each turn.
 - The line **MUST** report: turn number, input tokens, output tokens, cumulative total tokens, and estimated cumulative cost.
@@ -949,7 +900,6 @@ aw-harness/
 │   ├── context.ts                # Prompt assembly, compaction
 │   └── extensions/               # gh-aw Pi extensions
 │       ├── provider-setup.ts     # Register LLM providers from env vars
-│       ├── safe-outputs.ts       # Safe-output tools + artifact writing
 │       ├── cost-tracker.ts       # Budget gates via turn_end events
 │       ├── steering.ts           # Time/budget pressure via session.steer()
 │       ├── repair.ts             # Broken session recovery
@@ -1000,36 +950,34 @@ The following ordered work items describe the implementation sequence:
 
 1. **Scaffold project** — Initialize TypeScript project in `aw-harness/`. Configure package.json with Pi SDK deps (`@mariozechner/pi-coding-agent`, `pi-agent-core`, `pi-ai`). Set up tsconfig for ES2024/Node 24. Configure esbuild bundle → `dist/aw_harness.cjs`.
 
-2. **Implement provider setup extension** — Pi extension that registers LLM providers via `pi.registerProvider()` using provider credentials injected by AWF into the container environment.
+2. **Implement provider setup extension** — Pi extension that registers LLM providers via `pi.registerProvider()` using provider credentials injected by AWF into the container environment. Also detects provider-specific base URL env vars (e.g., `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`) and uses them as the provider endpoint when present.
 
 3. **Implement loader** — Read `config.json` (compiler-generated harness config) and `prompt.txt` (compiler-generated prompt body). Return config and prompt string to the entry point.
 
-4. **Implement safe-outputs extension** — Pi extension that registers safe-output tools (create-issue, create-pull-request, add-comment, etc.). Uses `pi.on("agent_end")` to finalize artifact manifest.
+4. **Implement entry point** — Create a single `createAgentSession()` with gh-aw extensions loaded. Pass `prompt.txt` contents as the prompt. Dispose session on completion.
 
-5. **Implement entry point** — Create a single `createAgentSession()` with gh-aw extensions loaded. Pass `prompt.txt` contents as the prompt. Dispose session on completion.
+4a. **Implement user extension loader** — Read `harness.extensions` from `config.json`. For each entry: resolve repository-relative paths from the harness working directory; load npm package names via `require()`. Verify each loaded module exports a default function of type `(pi: ExtensionAPI) => void`. Emit a stderr warning for each failed load; abort only if `harness.extensions-required: true` is set. Append loaded extensions to the session extension list after built-ins.
 
-5a. **Implement user extension loader** — Read `harness.extensions` from `config.json`. For each entry: resolve repository-relative paths from the harness working directory; load npm package names via `require()`. Verify each loaded module exports a default function of type `(pi: ExtensionAPI) => void`. Emit a stderr warning for each failed load; abort only if `harness.extensions-required: true` is set. Append loaded extensions to the session extension list after built-ins.
+5. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
 
-6. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
+6. **Implement cost tracker extension** — Pi extension that monitors `turn_end` events for effective token usage. Enforces soft (steer warning) and hard (abort) budget gates against `harness.budget.max-effective-tokens`.
 
-7. **Implement cost tracker extension** — Pi extension that monitors `turn_end` events for token/cost data. Enforces soft (steer warning) and hard (abort) budget gates.
+7. **Implement steering extension** — Pi extension that monitors time/budget and injects user messages via `session.steer()` on `turn_end`.
 
-8. **Implement steering extension** — Pi extension that monitors time/budget and injects user messages via `session.steer()` on `turn_end`.
+8. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
 
-9. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
-
-10. **Implement observability extension** — Pi extension that:
-    - Emits JSONL to stderr on agent/tool events (§8.6.1).
-    - Writes a context provenance file (`/tmp/gh-aw/context-provenance.jsonl`) on `agent_end` recording the source and token cost of every context entry (§8.6.2).
-    - Appends a Markdown execution summary table (per-turn tokens + context provenance) to `$GITHUB_STEP_SUMMARY` when that env var is set (§8.6.3).
-    - Emits a human-readable per-turn token consumption line to stderr after each `turn_end` (§8.6.4).
+9. **Implement observability extension** — Pi extension that:
+    - Emits JSONL to stderr on all agent events (§8.5.1).
+    - Writes a context provenance file (`/tmp/gh-aw/context-provenance.jsonl`) on `agent_end` recording the source and token cost of every context entry (§8.5.2).
+    - Appends a Markdown execution summary table (per-turn tokens + context provenance) to `$GITHUB_STEP_SUMMARY` when that env var is set (§8.5.3).
+    - Emits a human-readable per-turn token consumption line to stderr after each `turn_end` (§8.5.4).
     - Generates OTel spans using `observability.otlp` config.
 
-11. **Write tests** — Unit tests for loader, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
+10. **Write tests** — Unit tests for loader, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
 
-12. **Write example workflows** — Single-task examples demonstrating `engine: aw` with various tools and safe outputs.
+11. **Write example workflows** — Single-task examples demonstrating `engine: aw` with various tools.
 
-13. **Add build to Makefile** — Add `make aw-harness` target that runs esbuild and copies `aw_harness.cjs` to `actions/setup/js/`.
+12. **Add build to Makefile** — Add `make aw-harness` target that runs esbuild and copies `aw_harness.cjs` to `actions/setup/js/`.
 
 ---
 
@@ -1039,11 +987,9 @@ The following ordered work items describe the implementation sequence:
 
 **No direct LLM routing by harness.** The harness delegates all LLM routing to Pi SDK and the provider credentials injected by AWF. It **MUST NOT** perform additional proxy interception or credential manipulation.
 
-**Safe outputs isolation.** The safe-outputs extension **MUST NOT** perform live GitHub API calls during agent execution. All GitHub mutations **MUST** be expressed as artifact files processed by the post-agent job, which applies threat detection and validation before acting.
-
-**User extension isolation.** Extensions declared via `harness.extensions` run inside the same Node.js process as the built-in extensions. A conforming implementation **MUST NOT** execute user extensions with elevated privileges. Extension authors are responsible for ensuring that their extensions do not exfiltrate credentials or subvert built-in budget or safe-outputs behavior. Workflow authors are responsible for auditing third-party npm extension packages before referencing them in `harness.extensions`.
+**User extension isolation.** Extensions declared via `harness.extensions` run inside the same Node.js process as the built-in extensions. A conforming implementation **MUST NOT** execute user extensions with elevated privileges. Extension authors are responsible for ensuring that their extensions do not exfiltrate credentials or subvert built-in budget behavior. Workflow authors are responsible for auditing third-party npm extension packages before referencing them in `harness.extensions`.
 
-**Budget enforcement.** The cost-tracker extension provides a hard budget gate. A conforming implementation **MUST** abort the session if the cost exceeds the configured maximum, preventing runaway spending from misbehaving agents.
+**Budget enforcement.** The cost-tracker extension provides a hard budget gate. A conforming implementation **MUST** abort the session if the effective token count exceeds `harness.budget.max-effective-tokens`, preventing runaway usage from misbehaving agents.
 
 **Token and secret handling.** Provider credentials (e.g., `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GITHUB_TOKEN`) **MUST NOT** be logged to stderr or embedded in JSONL events. Implementations **MUST** treat all credential env vars as opaque secrets.
 

From 1af3ca4ed878e2742594a9afcec14455088e87c7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 2 May 2026 11:44:02 +0000
Subject: [PATCH 14/14] docs: fix code review issues - duplicate diagram entry,
 sequential numbering

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d1715722-ea93-4860-873f-802b6fab33c5

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 specs/aw-harness.md | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/specs/aw-harness.md b/specs/aw-harness.md
index 5852f706a1e..9fd48c991f0 100644
--- a/specs/aw-harness.md
+++ b/specs/aw-harness.md
@@ -161,7 +161,6 @@ The AW Harness is the topmost layer within the gh-aw container. The following AS
 │  │  │  ┌──────────────────────────────────────────┐   │ │   │
 │  │  │  │  gh-aw Pi Extensions (loaded into the    │   │ │   │
 │  │  │  │  AgentSession via ExtensionAPI):          │   │ │   │
-│  │  │  │  ├─ cost-tracker (budget gates)              │   │ │   │
 │  │  │  │  ├─ cost-tracker (budget gates + events)  │   │ │   │
 │  │  │  │  ├─ steering (time/budget pressure)       │   │ │   │
 │  │  │  │  ├─ repair (broken session recovery)      │   │ │   │
@@ -956,28 +955,28 @@ The following ordered work items describe the implementation sequence:
 
 4. **Implement entry point** — Create a single `createAgentSession()` with gh-aw extensions loaded. Pass `prompt.txt` contents as the prompt. Dispose session on completion.
 
-4a. **Implement user extension loader** — Read `harness.extensions` from `config.json`. For each entry: resolve repository-relative paths from the harness working directory; load npm package names via `require()`. Verify each loaded module exports a default function of type `(pi: ExtensionAPI) => void`. Emit a stderr warning for each failed load; abort only if `harness.extensions-required: true` is set. Append loaded extensions to the session extension list after built-ins.
+5. **Implement user extension loader** — Read `harness.extensions` from `config.json`. For each entry: resolve repository-relative paths from the harness working directory; load npm package names via `require()`. Verify each loaded module exports a default function of type `(pi: ExtensionAPI) => void`. Emit a stderr warning for each failed load; abort only if `harness.extensions-required: true` is set. Append loaded extensions to the session extension list after built-ins.
 
-5. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
+6. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
 
-6. **Implement cost tracker extension** — Pi extension that monitors `turn_end` events for effective token usage. Enforces soft (steer warning) and hard (abort) budget gates against `harness.budget.max-effective-tokens`.
+7. **Implement cost tracker extension** — Pi extension that monitors `turn_end` events for effective token usage. Enforces soft (steer warning) and hard (abort) budget gates against `harness.budget.max-effective-tokens`.
 
-7. **Implement steering extension** — Pi extension that monitors time/budget and injects user messages via `session.steer()` on `turn_end`.
+8. **Implement steering extension** — Pi extension that monitors time/budget and injects user messages via `session.steer()` on `turn_end`.
 
-8. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
+9. **Implement repair extension** — Pi extension that detects broken tool calls via `tool_result` events. Repairs via message truncation or summarize-and-restart.
 
-9. **Implement observability extension** — Pi extension that:
+10. **Implement observability extension** — Pi extension that:
     - Emits JSONL to stderr on all agent events (§8.5.1).
     - Writes a context provenance file (`/tmp/gh-aw/context-provenance.jsonl`) on `agent_end` recording the source and token cost of every context entry (§8.5.2).
     - Appends a Markdown execution summary table (per-turn tokens + context provenance) to `$GITHUB_STEP_SUMMARY` when that env var is set (§8.5.3).
     - Emits a human-readable per-turn token consumption line to stderr after each `turn_end` (§8.5.4).
     - Generates OTel spans using `observability.otlp` config.
 
-10. **Write tests** — Unit tests for loader, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
+11. **Write tests** — Unit tests for loader, each extension (mock `ExtensionAPI`). Integration tests with `createAgentSession()` + `SessionManager.inMemory()`.
 
-11. **Write example workflows** — Single-task examples demonstrating `engine: aw` with various tools.
+12. **Write example workflows** — Single-task examples demonstrating `engine: aw` with various tools.
 
-12. **Add build to Makefile** — Add `make aw-harness` target that runs esbuild and copies `aw_harness.cjs` to `actions/setup/js/`.
+13. **Add build to Makefile** — Add `make aw-harness` target that runs esbuild and copies `aw_harness.cjs` to `actions/setup/js/`.
 
 ---
 
