Skip to content

feat(agents): add "agents/harnesses/pi" - #2424

Merged
aron-cf merged 1 commit into
models-ai-sdkfrom
pi-entrypoint
Oct 2, 2026
Merged

aron-cf merged 1 commit into
models-ai-sdkfrom
pi-entrypoint

Conversation

@aron-cf

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

Copy link
Copy Markdown
Contributor

This adds a new "agents/harnesses/pi" entrypoint that provides a PiHarness that uses the "agents/models/pi-ai" model provider.

import { DurableObject } from "cloudflare:workers";
import { Type } from "@earendil-works/pi-ai";
import { createRegistry, Harness } from "@earendil-works/pi-durable";
import { PiHarness } from "agents/harnesses/pi";
import { Lifecycle } from "agents/lifecycle";
import { createModels, workersAI, WORKERS_AI_PROVIDER } from "agents/providers/pi-ai";

const MODEL_ID = "@cf/moonshotai/kimi-k2.7-code";

export class Assistant extends DurableObject<Env> {
  // pi's own registry: system prompt and tools.
  readonly registry = (() => {
    const registry = createRegistry();
    registry.systemPrompt.section("preamble", () => "You are concise.", {
      tag: false
    });
    registry.tools.add({
      name: "current_time",
      description: "The current UTC time.",
      parameters: Type.Object({}),
      replay: "safe",
      execute: async () => ({
        content: [{ type: "text", text: new Date().toISOString() }]
      })
    });
    return registry;
  })();

  // The harness opens pi over this object's SQLite and wakes it after eviction.
  readonly harness = new PiHarness({
    harness: ({ storage, context }) =>
      Harness.open(
        storage,
        {
          models: createModels({ providers: [workersAI(this.env.AI)] }),
          registry: this.registry
        },
        context
      ),
    defaults: { model: { provider: WORKERS_AI_PROVIDER, modelId: MODEL_ID } }
  });

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

  /** Ask in one session; `session` defaults to the root, "1". */
  async ask(text: string, session?: string): Promise<string> {
    const result = await this.harness.prompt(text, session ? { session } : {});
    return result.status === "done"
      ? (result.text ?? "")
      : `No answer: ${result.reason ?? "unknown"}`;
  }
}

Devin Review

@changeset-bot

changeset-bot Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2ddae8c

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

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: 1 entry point changed, no growth

Entry point Exports Largest gzip change Size now
🆕 agents/harnesses/pi 6 new — 21.4 KiB
Changed exports (6)
Import Gzip change Size now
🆕 agents/harnesses/pi#addSkills — 21.4 KiB
🆕 agents/harnesses/pi#PiHarness — 13.4 KiB
🆕 agents/harnesses/pi#openPiSessionStore — 7.2 KiB
🆕 agents/harnesses/pi#PiSession — 4.8 KiB
🆕 agents/harnesses/pi#PiSessions — 1.1 KiB
🆕 agents/harnesses/pi#ROOT_SESSION — 400 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 a5cbddaf → 2ddae8c9 · workflow run · reported by agent-think[bot]

Base automatically changed from pi-durable-driver to main October 1, 2026 11:52

@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.

Note

Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.

Devin Review found 2 new potential issues.

Devin Review

Comment on lines +19 to +22
export {
openPiSessionStore,
type PiSessionStoreOptions
} from "./session-store";

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.

🔍 Upstream SQL changes need compatibility coverage

The published store derives its identifier rewrite from pi migrations. Its conformance tests cover the current release, but the peer range allows newer pi releases with different SQL shapes. Consider testing compatible releases or constraining the supported range.

Devin Review


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

Comment on lines +9 to +14
export {
WORKERS_AI_PROVIDER,
workersAI,
type WorkersAIModelOptions,
type WorkersAIOptions
} from "./workers-ai";

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.

🔍 Binding transport has no direct test

The harness tests use a faux provider, leaving the new Workers AI binding transport unexercised. A binding-backed test can cover request shaping and streamed responses.

Devin Review


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

@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 3 new potential issues.

Devin Review

Comment on lines +405 to +412
if (!sawFinishReason) {
if (sawDone || this.blocks.length > 0) {
this.output.stopReason = hasToolCalls ? "toolUse" : "stop";
} else {
this.output.stopReason = "error";
this.output.errorMessage =
"The stream ended before the model produced any output.";
}

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.

🔴 Truncated streams become completed answers

When an SSE connection drops after text, finish marks the incomplete answer as stopped without receiving a finish reason or [DONE]. Callers receive truncated text as a successful assistant turn.

Learn more

The chat-completions wire reads SSE chunks and passes sawFinishReason and sawDone into CompletionAssembler.finish. A connection that ends after emitting content but before a terminal chunk has both flags false. The current condition treats the existing content as proof of completion, sets stopReason to stop, and publishes a done event. A dropped connection therefore turns an incomplete response into a successful one.

Example: A model emits data: {"choices":[{"delta":{"content":"The answer is"}}]} and the socket closes without another event. The result contains The answer is and reports stop rather than an error.

Recommended fix: For the SSE path, distinguish a clean terminal marker or explicit finish reason from a mere partial block. Set an error stop reason when neither is observed, while retaining handling for providers that legitimately omit [DONE] but send a finish reason.

Devin Review


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

Comment on lines +101 to +103
committed = true;
if (pendingStart !== undefined) outer.push(pendingStart);
pendingStart = undefined;

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.

🟡 Empty stream starts block model fallback

When a leg emits text_start before failing, streamWithFallback commits to it despite receiving no content. The fallback models never run, leaving the caller with an error instead of a possible answer.

Learn more

The fallback dispatcher holds each leg's initial start event until it can commit to that leg. CompletionAssembler.pushText emits text_start before text_delta, and tool and reasoning blocks likewise begin before their content. streamWithFallback commits on the first event other than start, so a failure after an empty block start is treated as a committed failure. That bypasses later models even though no answer content was delivered.

Example: A leg emits start, text_start, then error when its upstream stream breaks. The second leg is not tried, although the first emitted no text delta.

Recommended fix: Buffer block-start events along with start and commit only on a content-bearing delta, completed tool call, or successful terminal event. Discard buffered events when an uncommitted leg fails, and preserve their order when committing.

Devin Review


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

Comment on lines +12 to 15
import { createModels } from "@earendil-works/pi-ai/models";
import { routeAgentRequest } from "agents";
import { PiHarness } from "agents/harnesses/pi";
import { Lifecycle } from "agents/lifecycle";

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.

🔍 Provider example and PR description diverge

The example uses agents/models/pi-ai, while the PR description demonstrates the removed agents/providers/pi-ai entry point. Align the published usage example with the current API.

Devin Review


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

@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@2424

@cloudflare/ai-chat

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

@cloudflare/codemode

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

hono-agents

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

@cloudflare/shell

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

@cloudflare/think

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

@cloudflare/voice

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

@cloudflare/worker-bundler

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

commit: 2ddae8c

@aron-cf
aron-cf changed the base branch from main to models-ai-sdk October 2, 2026 08:56
@aron-cf
aron-cf added this pull request to stack #2447 October 2, 2026 08:56
@aron-cf aron-cf changed the title feat(agents): add "agents/harnesses/pi" and "agents/providers/pi-ai" feat(agents): add "agents/harnesses/pi" Oct 2, 2026
Move the pi harness example's core into the agents package as an
experimental entry point. agents/harnesses/pi exports PiHarness,
PiSession, PiSessions, the Durable Object session store, addSkills, and
the harness's own types. @earendil-works/pi-durable becomes an optional
peer dependency, so the main agents entry does not load pi.

The harness does not depend on @earendil-works/chord. pi-durable takes a
chord Context on every call without re-exporting it, and the harness
needs only a background context and withAbortSignal, so context.ts
inlines both against chord's structural Context interface. chord's own
derived contexts read the abort signal through a private key, so a chord
withContextValue around one of these would not see its signal; pi 0.99.2
never wraps a caller's context that way.

Transcripts are pi's own entries: messages() and prompt()'s messages
return EntryRecord[]. The display projection (PiMessage, PiMessagePart)
moves to the example as src/transcript.ts, next to the view that uses it.

The harness, options and session store tests move with the code into
their own harnesses-pi vitest project, with pi-ai's faux provider on a
plain Models registry. The example keeps its socket tests, and the test
that folds a run's events into the same view a late joiner gets moves to
its view.test.ts. Models come from agents/models/pi-ai.
@aron-cf
aron-cf merged commit 1e54cf1 into main Oct 2, 2026
15 of 30 checks passed
@aron-cf
aron-cf deleted the pi-entrypoint branch October 2, 2026 10:17
@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