Skip to content

feat(agents): portable harness extensions (agents/harness/extensions) - #2511

Draft
mattzcarey wants to merge 2 commits into
mainfrom
feat/portable-harness-extensions
Draft

mattzcarey wants to merge 2 commits into
mainfrom
feat/portable-harness-extensions

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Adds agents/harness/extensions (experimental): one extension format for PiHarness, OpenCodeHarness, ThinkHarness and the container harness. An extension is a function of its context, and a harness takes an array of them. The names follow OpenCode 2's plugins (OpenCode Reloaded). PiHarness runs them today.

RFC with the per-harness mapping, what can't be ported, and known limits: design/rfc-portable-harness-extensions.md.

function guard(ctx: ExtensionContext) {
  ctx.tool.add({
    id: "add",
    description: "Add two numbers.",
    input: z.object({ a: z.number(), b: z.number() }), // Standard Schema + JSON Schema
    execute: ({ a, b }) => ({ content: String(a + b), metadata: { a, b } })
  });
  ctx.instructions.set("guard", "No destructive commands.");
  ctx.tool.hook("execute.before", (event) => {
    if (/rm -rf/.test(String(event.input.command))) event.block = "destructive";
    if (/deploy/.test(String(event.input.command))) event.ask = "Deploy?"; // durable approval
  });
  ctx.command.add({ name: "review", description: "", run: (args) => ({ prompt: `Review ${args}` }) });
  ctx.event.on("turn.end", (e) => ctx.storage("guard").put("last", e.text));
}

new PiHarness({
  extensions: [guard, webAccess({ search }), mcp],
  harness: ({ storage, context, registry }) => {
    registry.install(nativePiExtension); // native tools are in the tool draft too
    return Harness.open(storage, { models, registry }, context);
  }
});
flowchart LR
  ext["extensions: (ctx) => …"] -->|transform| domains[("tool · instructions · skill · command<br/>rebuilt from base, every transform once")]
  ext -->|hook / event.on| hooks["execute.before · execute.after · events"]
  natives["native pi extensions"] -->|base of the tool draft| domains
  domains -->|snapshot| view["registry view: one 'agents.extensions'<br/>+ one pi extension per deferred tool"]
  view --> pi["pi-durable"]
  pi -->|"ToolTask / GenerationTask hooks"| hooks
  hooks -->|"ask"| req[("requests() / reply()")]
Loading

What an extension can do:

ctx.
tool.add, tool.transform catalog, starting from native tools; deferred tools are offered once a session activates them (activate on a result, or session(id).tools.activate)
tool.hook execute.before (edit, block, ask) and execute.after (replace result), on native tools too
instructions.set, skill.add, command.add prompt sections, agents/skills sources, slash commands
event.on session.created, message.end, tool.end, turn.end
storage(ns) synchronous durable KV, .session(id) scoped
session(id) submit, note, tools.activate / deactivate / offered
in a tool: call.ask, call.update durable question for the user; live metadata
  • Transforms rebuild from the base every time, so a refresh can't undo a policy, an edit can't apply twice, and a dropped source's tools disappear. host.test.ts reproduces each of the post's bugs.
  • call.ask keys its request on a memo of the pi tool task. After a crash, pi reruns the safe tool and ask returns the stored answer. A tool that asks must be replay: "safe".
  • Commands run before anything is stored and are recorded by operation id. A { prompt } result becomes an ordinary durable submission.
  • A harness declares its features. Using a missing one fails that extension loudly, unless it checks ctx.supports().

The five most downloaded pi packages, ported in ported-extensions.ts and run on PiHarness in extensions.test.ts:

Package Weekly Port
pi-mcp-adapter 534k deferred server tools behind mcp_enable, one transform over the catalog (no unregisterTool), /mcp
billion-context 388k not portable (fetch-patching proxy, provider + compaction hooks)
pi-web-access 230k web_enable activates deferred search/fetch tools per session; pages in session storage
pi-subagents 190k subagent runs a child session; skills; prompt template as a command
rpiv-ask-user-question 96k call.ask({ kind: "select" }), answered with reply(), across a crash

Known limits (in the RFC): a parked ask keeps the object awake through the wake heartbeat. activate is read from the tool's own result, not from after-hooks. session.created only covers sessions created through the harness API.

@changeset-bot

changeset-bot Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9c69f79

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agents Patch
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@agent-think

agent-think Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

🔴 agents import sizes: 1 entry point over threshold

Entry point Exports Largest gzip change Size now
🔴 agents/harness/pi 6 resized, 2 new up to +66.1 KiB (+16926.25%) 66.5 KiB
🆕 agents/harness/extensions 11 new — 3.6 KiB
Changed exports (19)
Import Gzip change Size now
🔴 agents/harness/pi#ROOT_SESSION +66.1 KiB (+16926.25%) 66.5 KiB
🔴 agents/harness/pi#PiHarness +65.9 KiB (+483.03%) 79.6 KiB
🔴 agents/harness/pi#PiSessions +65.7 KiB (+5904.92%) 66.8 KiB
🔴 agents/harness/pi#openPiSessionStore +65.6 KiB (+854.81%) 73.3 KiB
🔴 agents/harness/pi#PiSession +65.1 KiB (+1362.66%) 69.9 KiB
🔴 agents/harness/pi#skills +45.1 KiB (+210.59%) 66.6 KiB
🆕 agents/harness/pi#NOTE_ENTRY — 66.5 KiB
🆕 agents/harness/pi#PORTABLE_EXTENSION — 66.5 KiB
🆕 agents/harness/extensions#ExtensionHost — 3.6 KiB
🆕 agents/harness/extensions#RequestStore — 1003 B
🆕 agents/harness/extensions#extensionStorage — 302 B
🆕 agents/harness/extensions#jsonSchema — 228 B
🆕 agents/harness/extensions#memoryKeyValueStore — 191 B
🆕 agents/harness/extensions#ExtensionSetupFailed — 175 B
🆕 agents/harness/extensions#ExtensionFeatureUnsupported — 166 B
🆕 agents/harness/extensions#deleteSessionStorage — 156 B
🆕 agents/harness/extensions#ExtensionAlreadyInstalled — 143 B
🆕 agents/harness/extensions#isNativeTool — 82 B
🆕 agents/harness/extensions#tool — 66 B
How this works

Each runtime export is bundled on its own, minified, and gzipped. Changes smaller than 100 B, or smaller than 1% and 1 KiB, are ignored. Growth over 10% or 5 KiB is marked 🔴. This report is informational and does not fail CI. The workflow artifact contains every measurement.

Compared 92d1770d → 9c69f790 · workflow run · reported by agent-think[bot]

@pkg-pr-new

pkg-pr-new Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2511

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2511

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2511

hono-agents

npm i https://pkg.pr.new/hono-agents@2511

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2511

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2511

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2511

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2511

commit: 9c69f79

…mmands, events, storage, native tools and metadata
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant