Skip to content

Commit 4abef9b

Browse files
Copilotpelikhan
andauthored
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>
1 parent cbc260c commit 4abef9b

1 file changed

Lines changed: 67 additions & 45 deletions

File tree

specs/aw-harness.md

Lines changed: 67 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ This is an internal design specification for the GitHub gh-aw project. It is not
2828
2. [Conformance](#2-conformance)
2929
3. [Terminology and Definitions](#3-terminology-and-definitions)
3030
4. [Architecture](#4-architecture)
31-
- [4.3 Pi SDK and OpenClaw Extension Format Compatibility](#43-pi-sdk-and-openclaw-extension-format-compatibility)
3231
5. [Harness Invocation Contract](#5-harness-invocation-contract)
3332
6. [Workflow Definition](#6-workflow-definition)
3433
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
202201

203202
9. **Observable.** All implementations **MUST** emit a JSONL event stream to stderr and **SHOULD** generate OTel spans when an OTLP endpoint is configured.
204203

205-
### 4.3 Pi SDK and OpenClaw Extension Format Compatibility
206-
207-
*(This section is non-normative.)*
208-
209-
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.
210-
211-
#### 4.3.1 API Surface Comparison
212-
213-
| Pi SDK (`ExtensionAPI`) | OpenClaw (`OpenClawPluginApi`) | Notes |
214-
|-------------------------|-------------------------------|-------|
215-
| `pi.registerTool(tool)` | `api.registerTool(tool, opts?)` | Semantically equivalent; parameter schema shapes may differ (Pi uses Pi-native schema; OpenClaw uses TypeBox `Type.Object(...)`) |
216-
| `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"`) |
217-
| `pi.registerProvider(id, config)` | `api.registerProvider(...)` | Config shape differs; OpenClaw providers declared in `openclaw.plugin.json` manifest |
218-
| `(no direct equivalent)` | `api.registerAgentHarness(...)` | OpenClaw's experimental low-level agent executor seam; Pi SDK exposes the session directly instead |
219-
| `(lifecycle via pi.on)` | `api.registerHook(events, handler)` | OpenClaw has a separate lifecycle hook registry distinct from its agent event hooks |
220-
| `(no equivalent)` | `api.registerSessionExtension(...)` | OpenClaw-specific: plugin-owned session state projected through Gateway sessions |
221-
222-
#### 4.3.2 Manifest Requirements (OpenClaw Only)
223-
224-
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**:
225-
226-
- A Pi extension is a TypeScript function `(pi: ExtensionAPI) => void`.
227-
- An OpenClaw plugin is a package exporting `definePluginEntry({ id, name, description, register(api) { ... } })` with an accompanying `openclaw.plugin.json`.
228-
229-
#### 4.3.3 Compatibility Requirements
230-
231-
The gh-aw extensions in this specification target Pi SDK only. However, to facilitate future portability to OpenClaw:
232-
233-
- Extensions **SHOULD** keep Pi SDK dependencies isolated to the entry function boundary rather than scattered across extension modules.
234-
- 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.
235-
- 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.
236-
- Extensions **MUST NOT** use Pi-internal APIs that have no OpenClaw equivalent (e.g., direct `AgentSession` handle manipulation outside the `ExtensionAPI` surface).
237-
238-
#### 4.3.4 `registerAgentHarness` Consideration
239-
240-
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.
241-
242204
---
243205

244206
## 5. Harness Invocation Contract
@@ -353,6 +315,11 @@ An `engine: aw` workflow document **MUST** include a YAML frontmatter block conf
353315
> time-critical-minutes: 2
354316
> budget-warn-percent: 75
355317
> budget-critical-percent: 90
318+
>
319+
> # Optional: Pi SDK-compatible extensions to load alongside built-in gh-aw extensions.
320+
> # Each entry is a repo-relative path to a compiled .cjs file or an npm package name.
321+
> extensions:
322+
> - ./extensions/custom-tool.cjs
356323
> ---
357324
>
358325
> 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:
384351
- `budget-warn-percent` (number): Budget percentage at which a warning **SHOULD** be injected. Default: `75`.
385352
- `budget-critical-percent` (number): Budget percentage at which the session **MUST** be aborted. Default: `90`.
386353
387-
#### 6.1.4 `imports:`
354+
#### 6.1.4 `harness.extensions`
355+
356+
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.
357+
358+
Each entry is a string in one of the following forms:
359+
360+
- **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`).
361+
- **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.
362+
363+
Each referenced module **MUST** export a default function with the Pi SDK `ExtensionAPI` signature:
364+
365+
```typescript
366+
export default function(pi: ExtensionAPI): void | Promise<void>;
367+
```
368+
369+
A conforming implementation **MUST**:
370+
371+
- Dynamically load each extension using `require()` (for local paths) or `require()` / `import()` (for npm packages).
372+
- Register each user extension into the `AgentSession` alongside the built-in gh-aw extensions, after all built-in extensions have been registered.
373+
- 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.
374+
375+
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.
376+
377+
> [!NOTE] Non-normative example.
378+
>
379+
> ```yaml
380+
> harness:
381+
> budget:
382+
> max-cost-usd: 5.00
383+
> extensions:
384+
> - ./extensions/custom-tool.cjs # Local compiled extension
385+
> - @my-org/pi-extension-rate-limiter # npm package extension
386+
> ```
387+
>
388+
> Each extension module exports a Pi SDK-compatible function:
389+
>
390+
> ```typescript
391+
> // extensions/custom-tool.cjs (compiled from TypeScript)
392+
> module.exports = function(pi) {
393+
> pi.registerTool({
394+
> name: "my_custom_tool",
395+
> description: "Does something custom",
396+
> parameters: { type: "object", properties: { input: { type: "string" } } },
397+
> execute: async (_id, params) => ({ content: [{ type: "text", text: params.input }] }),
398+
> });
399+
> };
400+
> ```
401+
402+
#### 6.1.5 `imports:`
388403
389404
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.
390405
@@ -434,7 +449,7 @@ A conforming implementation **MUST** source every item included in the session's
434449
435450
- The Markdown body from `prompt.txt` (loaded per [Section 6.3](#63-prompt-loading)).
436451
- The `harness.system` prompt if declared in the `harness:` frontmatter block.
437-
- 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.
452+
- 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.
438453
439454
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.
440455
@@ -470,13 +485,18 @@ A conforming implementation **MUST** execute the workflow as follows:
470485
> async function main() {
471486
> const { configPath, promptPath } = parseArgs(process.argv);
472487
> const { config, prompt } = loadInputs(configPath, promptPath);
488+
>
489+
> // Load user-declared extensions from harness.extensions
490+
> const userExtensions = await loadUserExtensions(config.harness?.extensions ?? []);
491+
>
473492
> const extensions = [
474493
> providerSetupExtension,
475494
> safeOutputsExtension,
476495
> costTrackerExtension,
477496
> steeringExtension,
478497
> repairExtension,
479498
> observabilityExtension,
499+
> ...userExtensions, // User extensions run after built-in extensions
480500
> ];
481501
>
482502
> const { session } = await createAgentSession({
@@ -493,7 +513,7 @@ A conforming implementation **MUST** execute the workflow as follows:
493513
494514
1. The implementation **MUST** invoke `createAgentSession()` exactly once per harness invocation.
495515
2. The prompt passed to `session.prompt()` **MUST** be the full contents of `prompt.txt` as loaded per [Section 6.3](#63-prompt-loading).
496-
3. The implementation **MUST** load all six gh-aw Pi extensions (see [Section 8](#8-extensions)) into the session.
516+
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)).
497517
4. After the session completes (success or failure), the implementation **MUST** call `session.dispose()`.
498518
5. If the budget gate has been triggered (via the cost-tracker extension), the implementation **MUST** exit with code `1`.
499519
@@ -506,6 +526,7 @@ A conforming implementation **MUST** execute the workflow as follows:
506526
- Safe-output tools registered
507527
- Steering, repair, cost, observability extensions active
508528
(MCP tools available as bash CLI commands via cli-proxy — no bridging needed)
529+
- User extensions (from harness.extensions) loaded and registered after built-ins
509530
3. session.prompt(promptText) → Pi agent loop runs
510531
4. Extensions handle events (cost tracking, steering, observability)
511532
5. session.dispose()
@@ -515,7 +536,7 @@ A conforming implementation **MUST** execute the workflow as follows:
515536

516537
## 8. Extensions
517538

518-
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).
539+
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>`.
519540

520541
The following six extensions **MUST** be loaded into the `AgentSession` created by the harness.
521542

@@ -987,6 +1008,8 @@ The following ordered work items describe the implementation sequence:
9871008

9881009
5. **Implement entry point** — Create a single `createAgentSession()` with gh-aw extensions loaded. Pass `prompt.txt` contents as the prompt. Dispose session on completion.
9891010

1011+
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.
1012+
9901013
6. **Implement context engine** — Prompt assembly with priority ordering. Compaction via `none`, `sliding-window`, or `summarize`.
9911014

9921015
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:
10181041

10191042
**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.
10201043

1044+
**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`.
1045+
10211046
**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.
10221047

10231048
**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
10641089

10651090
**[gh-aw]**
10661091
GitHub Agentic Workflows — the gh-aw CLI extension that compiles Markdown workflow files to GitHub Actions YAML. <https://github.com/github/gh-aw>
1067-
1068-
**[OpenClaw]**
1069-
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>

0 commit comments

Comments
 (0)