Skip to content

Use published Pi version and clean up the PiHarness options - #2423

Merged
aron-cf merged 5 commits into
mainfrom
pi-durable-driver
Oct 1, 2026
Merged

aron-cf merged 5 commits into
mainfrom
pi-durable-driver

Conversation

@aron-cf

@aron-cf aron-cf commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

This PR now pins the @earendil-works/pi-durable package rather than vendoring it and cleans up the PiHarness options.

import { createRegistry, Harness } from "@earendil-works/pi-durable";

export class PiAgent extends DurableObject<Env> {
  readonly registry = (() => {
    const registry = createRegistry();
    registry.systemPrompt.section("preamble", () => "Be concise.", { tag: false });
    for (const tool of createTools()) registry.tools.add(tool);
    return registry;
  })();

  readonly harness = new PiHarness({
    harness: ({ storage, context }) =>
      Harness.open(
        storage,
        {
          models: createModels({ providers: [workersAI(this.env.AI)] }),
          registry: this.registry
        },
        context
      ),
    defaults: {
      model: { provider: "cloudflare-workers-ai", modelId: MODEL_ID }
    }
  });

  readonly lifecycle = Lifecycle.install(this).use(this.harness);
}

Only harness is required:

  • defaults sets the model, thinking level and retry policy for new sessions. If no model is set, a session's prompts end unanswered with reason no_model until you call setModel.
  • store changes the table prefix, which defaults to pi_.
  • timing changes how long the wake job waits.

Devin Review

The vendor existed because npm 0.99.1 predated pi's events API. 0.99.2
ships it -- watchEvents, AgentEvent, AgentEventStream, SnapshotEvent,
MessageChange -- so the reason is gone.

Checked before switching: the exported surface of the vendored build is a
strict subset of published 0.99.2. Nothing is in the vendor that is not on
npm, and 0.99.2 adds the ten compaction types. pi-ai's surface is
identical. Every pi symbol src/harness imports compiles against 0.99.2
under --strict. Two of the four features the vendor was justified by --
the inbox and ownership -- were already in published 0.99.1, and subagents
are absent from the vendored build too.

Deletes 1.5 MB of tarballs, pack.mjs and SHA256SUMS.

Two consequences worth knowing:

- The repo sets minimumReleaseAge: 1440, so a pi release under 24h old
  will not install. 0.99.2 needed @earendil-works/* added to
  minimumReleaseAgeExclude. That list is a supply-chain control, so the
  entry is deliberate rather than incidental -- the alternative is waiting
  for each release to age out.
- Published chord depends on esbuild (~12 MB), which the vendored archive
  stripped. It is only used by chord's Node bundler and never reached from
  a Worker; worth an upstream ask to make it an optional peer.
PiHarnessOptions becomes a discriminated union. The declarative form is
unchanged in spirit -- give models and the harness opens pi itself -- and
a harness factory form lets a caller own the Harness.open call.

  type PiHarnessOptions = PiHarnessCommon & (
    | { models: Models; env?; onReport?; harness?: never }
    | { harness: PiHarnessFactory; models?: never; env?: never; onReport?: never }
  );

The factory receives the already-open store, the registry the harness
built from tools/systemPrompt/skills/configure, and a context, so it only
decides how pi is opened. Use it for a HarnessOptions field the
declarative form does not forward, to wrap the Harness, or for a custom
registry.

A union rather than replacing the declarative fields, because models, env
and onReport are Harness.open arguments: if a factory owns the open they
must move with it, or two places could set them and a precedence rule is
needed. The never branches make that a type error instead. The
registry-building fields are not Harness.open arguments -- they run before
it -- so they stay shared and a factory caller still gets
tools/systemPrompt/skills.

Two groupings come with it. defaults{model,thinkingLevel,retry} replaces
three top-level fields whose 'new sessions only' semantics were invisible
at the call site. timing{sleepThresholdMs,waitBudgetMs,heartbeatMs}
replaces module constants a test could not shorten; the fixture now sets
heartbeatMs 1s so the suite does not sit on the real 30s heartbeat. The
constructor rejects a non-positive timing.

Tests: adds options.test.ts and a PiFactoryTestObject so both
construction paths are covered at runtime, not just in the types -- a
prompt answered through a caller-supplied Harness, a tool round proving
the registry handed to the factory is the one in force, and the wake
still parking with no alarm. Mutation-checked rather than trusted green:
forcing #doOpen down the declarative branch fails two of the three. The
third passes either way by design and says so.

Typecheck clean, 41/41 tests pass.
PiHarnessOptions drops the declarative/factory union. The harness factory
is the one required option and owns Harness.open, so models, env and
onReport live there:

  type PiHarnessOptions = {
    harness: PiHarnessFactory; // ({ storage, context }) => Harness
    defaults?: PiSessionDefaults;
    store?: PiSessionStoreOptions;
    timing?: PiWakeTiming;
  };

The registry moves to the caller too. systemPrompt, tools, skills and
configure only wrote into a registry the harness never read, so the host
now composes pi's own Registry and passes it to Harness.open. Skills
become an addSkills(registry, sources) helper the factory awaits.
PiSessionSockets takes the registry as a RegistryReader for its tool
list, since pi's Harness does not expose the one it was opened with.

defaults, and defaults.model, are optional. A session without a model
ends its prompts unanswered with pi's no_model reason until setModel.
The factory test fixture, now identical to the main one, becomes a
no-defaults fixture covering that.

Local types that pi already publishes are replaced with pi's: PiJson
with JsonValue, PiMessageInput and PiImage with UserInput, PiToolContent
and the text/image message parts with TextContent | ImageContent. The
asJson cast, userInput() and toolContent() conversions go with them.
@changeset-bot

changeset-bot Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 048df07

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

This PR includes changesets to release 1 package
Name Type
@cloudflare/worker-bundler 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

devin-ai-integration[bot]

This comment was marked as resolved.

@agent-think

agent-think Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

🟡 agents import sizes: 2 entry points grew

Entry point Exports Largest gzip change Size now
🟡 agents/browser/tanstack-ai 1 resized +1.2 KiB (+0.71%) 164.3 KiB
🟡 agents/browser/ai 3 resized, 1 new up to +1.1 KiB (+0.75%) 148.5 KiB
🆕 agents/browser 3 new — 40.5 KiB
🟢 agents/vite 1 resized -121.3 KiB (-28.96%) 297.6 KiB
Changed exports (9)
Import Gzip change Size now
🟡 agents/browser/tanstack-ai#createBrowserTools +1.2 KiB (+0.71%) 164.3 KiB
🟡 agents/browser/ai#createBrowserRuntime +1.1 KiB (+0.75%) 148.5 KiB
🟡 agents/browser/ai#createBrowserTools +1.1 KiB (+0.75%) 148.5 KiB
🟡 agents/browser/ai#createQuickActionTools +1 KiB (+0.82%) 123.5 KiB
🆕 agents/browser/ai#browserTool — 141 KiB
🆕 agents/browser#Browser — 40.5 KiB
🆕 agents/browser#CdpConnection — 37.4 KiB
🆕 agents/browser#browserRun — 36 KiB
🟢 agents/vite#default -121.3 KiB (-28.96%) 297.6 KiB
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 3c6a1d08 → 048df074 · workflow run · reported by agent-think[bot]

@aron-cf

aron-cf commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

@agent-think can you bump the pi-recovery dependency to the same pi version as used by this branch to avoid the serif issue.

sherif rejects the workspace for declaring @earendil-works/pi-ai at two
ranges: ^0.99.2 in the pi harness example and ^0.80.2 in
experimental/pi-recovery. Move pi-recovery to ^0.99.2.

pi-recovery also uses @earendil-works/pi-agent-core, whose last release
is 0.84.1 and which depends on its own pi-ai. With two pi-ai copies its
AgentMessage and pi-ai's Message no longer type-check against each other,
so a scoped pnpm override points pi-agent-core at the workspace's pi-ai.
pi-recovery type-checks, its codec tests pass, and its SIGKILL recovery
e2e passes against the single 0.99.2 copy.

The pi harness README introduction still described an unreleased pinned
pi build; it now points at the published npm packages.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Devin Review

Comment thread pnpm-workspace.yaml
- "@typescript/*"
- "@earendil-works/*"
overrides:
"@earendil-works/pi-agent-core>@earendil-works/pi-ai": "^0.99.2"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Validate mixed Pi package versions

The override pairs pi-agent-core@0.80.2 with pi-ai@0.99.2 in the recovery fixture. Its streaming recovery path needs runtime validation against this pairing.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

The Python installer dropped every .dist-info/ file from installed
wheels, so importlib.metadata.version() found no distribution for any
package installed from PyPI.

FastAPI 0.142 imports OpenTelemetry at startup, and opentelemetry.context
calls uuid4() at import time, outside a request. The Workers Python SDK
permits that entropy call for known packages after checking the package
version with importlib.metadata, which raised PackageNotFoundError for
fastapi and stopped the Worker from starting. The e2e tests that import
fastapi failed on every attempt.

Keep each distribution's METADATA file and continue to skip the rest of
.dist-info/ and .data/.
@pkg-pr-new

pkg-pr-new Bot commented Oct 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

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

@cloudflare/ai-chat

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

@cloudflare/codemode

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

hono-agents

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

@cloudflare/shell

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

@cloudflare/think

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

@cloudflare/voice

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

@cloudflare/worker-bundler

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

commit: 048df07

@aron-cf
aron-cf merged commit 0c29717 into main Oct 1, 2026
6 checks passed
@aron-cf
aron-cf deleted the pi-durable-driver branch October 1, 2026 11:52
@github-actions github-actions Bot mentioned this pull request Oct 2, 2026
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