mrkeyoor.com_
Mon 26 Oct 22:45 UTC
npmAI / MLupdated 26 Oct 2026

@earendil-works/pi-agent-core review

Your model can request a tool call, but your application still needs to run it and feed the result back. @earendil-works/pi-agent-core 1.4.0 provides a stateful agent loop with tool execution and event streaming, built on pi-ai. The Agent class keeps a transcript in memory, executes supplied tools, and queues steering or follow-up messages. Your application supplies the model connection, storage, and authorization rules. Version 1.0.0 removed sessions, session storage, and the durable runtime from this package.

Verdict

Choose @earendil-works/pi-agent-core when you want an embeddable loop and can own the tools, permissions, and storage around it. Start with a read-only tool. Before connecting a tool that writes real data, define how your application records its result and handles an interrupted run.

Answers from our run

@earendil-works/pi-agent-core or ai: which should you use?

ai: Consider Vercel's AI SDK for a shared interface across model providers. Its supplied npm description offers a single interface through the Vercel AI Gateway or direct provider connections. This supports a scope comparison, not a performance or feature-parity judgment. Choose @earendil-works/pi-agent-core when you want an embeddable loop and can own the tools, permissions, and storage around it.

When should you not use @earendil-works/pi-agent-core?

You need this package to provide durable sessions. Version 1.0.0 removed sessions, session storage, and the durable runtime.

API stability3/5Under the stated documentation rubric, 3 credits a usable current interface with explicit migration costs. The supplied Agent source exposes prompting, queues, cancellation, and subscriptions, while the changelog records the 1.0.0 removal of durable/session APIs and the 0.87.0 stopping-hook replacement. This is not a measured compatibility score.
Docs4/5Under the stated rubric, 4 credits specified behavior that can be checked against supplied evidence. The tagged README explains event order, queue behavior, tool hooks, and subscriber settlement, and the Agent source supports the state and lifecycle details. The illustrations remain unexecuted; this rating does not validate provider configuration or application policies.
Maintenance4/5This 4 credits documented maintenance work, not operational reliability. The tagged changelog records request identity controls in 1.4.0 on October 21, 2026, tool-duration reporting in 1.1.0, and the package-scope change in 1.0.0. Those entries provide concrete evidence of recent changes, but do not establish support response times or future release commitments.
Ecosystem3/5This 3 credits a documented integration path with explicit limits. Registry metadata pins pi-ai and typebox as runtime dependencies, and the tagged README connects model registration and tool schemas to Agent. The supplied evidence does not measure adoption or establish compatibility with other libraries, so neither contributes to the score.

Use it if

  • You're embedding an agent in a TypeScript application and want to supply its tools and model connection.
  • You need conversation state, streaming events, and tool-result feedback in one loop.
  • You can enforce authorization in your application services and use beforeToolCall for an additional execution check.
  • Your team can own transcript storage and recovery after an interrupted operation.
Skip it if

Setup reality

Install explicit dependencies for these imports: npm install --save-exact @earendil-works/pi-agent-core@1.4.0 @earendil-works/pi-ai@1.4.0 typebox@1.3.27. Core 1.4.0 is an ESM package with TypeScript declarations. Its registry metadata declares Node >=22.19.0 and pins pi-ai 1.4.0 and typebox 1.3.27 as runtime dependencies. Use an ESM entry point and retain the lockfile. Provider configuration, credentials, and account access remain prerequisites for a model request.

The snippets are unexecuted TypeScript illustrations based on the tagged 1.4.0 README and supplied Agent implementation. The supplied evidence contains no installation results, model-call results, benchmarks, security audit, or comparison of published tarball files against the tagged files. Later snippets reuse the agent from the first example. Try each separately while the agent is idle unless the example starts a run; configuration changes remain in effect until changed again.

Agent keeps its transcript in memory. Restoring messages is possible, but a saved transcript does not resolve an interrupted external write. Before connecting a tool that changes data, define what happens if the operation completes and the process dies before your application records the result. Enforce authorization in the service that performs the operation, even when beforeToolCall also checks it. Blocking a call supplies feedback to the model; it does not necessarily end the run.

Tool execution defaults to parallel. Set toolExecution to sequential when calls depend on order. A tool with executionMode: 'sequential' forces the entire batch to execute sequentially. This controls scheduling; your application still needs transactions when writes must succeed together. Overlapping prompt() calls on one Agent are rejected. Use steer() or followUp() to queue additional input, or wait for completion. Queuing input does not abort the active operation.

Subscribers are awaited in registration order. Agent.subscribe() listeners are awaited in registration order, and prompt() and waitForIdle() settle only after awaited agent_end listeners finish. Keep subscriber work bounded. Awaiting a new prompt or waitForIdle() inside a subscriber on the same active Agent can prevent settlement. After a prompt settles, inspect state.errorMessage and the final assistant message rather than treating promise resolution as proof of success.

The tagged changelog dates 1.4.0 to October 21, 2026 and records request identity controls. Older integrations also need to account for the removal of durable/session APIs in 1.0.0 and the replacement of shouldStopAfterTurn with finishTurn in 0.87.0.

Ratings use a documentation-based rubric from 1 to 5: 1 means insufficient support for this integration task; 2 means a narrow or poorly explained surface; 3 means usable with explicit caveats; 4 means clearly specified behavior supported by supplied documentation or source; 5 requires operational evidence beyond this record. The category rationales explain what each score credits. These are editorial judgments, not measurements of reliability, adoption, compatibility, or support response time.

Patterns

Connect a model explicitly create-agent

import { Agent } from '@earendil-works/pi-agent-core';
import { createModels } from '@earendil-works/pi-ai';
import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic';

const models = createModels();
models.setProvider(anthropicProvider());
const model = models.getModel('anthropic', 'claude-sonnet-4-6');
if (!model) throw new Error('Model not found');

const agent = new Agent({
  initialState: { model, systemPrompt: 'Use only the tools provided.' },
  streamFn: models.streamSimple.bind(models),
});
await agent.prompt('Explain what tools you have.');

Unexecuted illustration based on the tagged 1.4.0 quick start. Registration does not establish provider authorization, account access, or availability of the named model to your application.

Read text deltas stream-text

const unsubscribe = agent.subscribe((event) => {
  if (event.type === 'message_update' &&
      event.assistantMessageEvent.type === 'text_delta') {
    process.stdout.write(event.assistantMessageEvent.delta);
  }
});
try {
  await agent.prompt('Describe the next step.');
} finally {
  unsubscribe();
}

A message_update event concerns an assistant message, but its nested event is not always a text delta. Subscribers run in registration order and are awaited, so slow handling delays the loop and final settlement.

Expose a small lookup define-read-tool

import { Type } from 'typebox';
import type { AgentTool } from '@earendil-works/pi-agent-core';

const entries = new Map([['public', 'Published help text']]);
const lookup: AgentTool = {
  name: 'lookup_entry',
  label: 'Look up entry',
  description: 'Read a named entry from the example catalog',
  parameters: Type.Object({ key: Type.String() }),
  execute: async (_callId, { key }) => {
    const text = entries.get(key);
    if (text === undefined) throw new Error('Unknown entry');
    return { content: [{ type: 'text', text }], details: { key } };
  },
};
agent.state.tools = [lookup];

This unexecuted example uses local fixture data, not a database integration. The README directs tools to throw on failure; the agent reports thrown errors to the model as tool errors. Assigning state.tools replaces the executable loadout and copies the top-level array.

Select sequential tool batches choose-tool-order

agent.toolExecution = 'sequential';
await agent.prompt('Use the catalog to answer my question.');

The default is parallel. A tool's executionMode: 'sequential' also forces the entire batch into sequential execution. Serial scheduling does not supply application transactions.

Deny a tool before execution block-tool-call

agent.beforeToolCall = async ({ toolCall }) => {
  if (toolCall.name === 'lookup_entry') {
    return { block: true, reason: 'Catalog access is disabled.' };
  }
  return undefined;
};

This deliberately disables the lookup tool and leaves the hook installed. The hook runs after validated argument parsing. Blocking this call does not guarantee that the run ends; the model can receive the blocked result and continue.

Queue another request during a run queue-follow-up

const pending = agent.prompt('Explain the available catalog.');
agent.followUp({
  role: 'user',
  content: 'Then give one example question.',
  timestamp: Date.now(),
});
await pending;

Overlapping prompt() calls on one Agent are rejected. followUp() queues input for when the loop would otherwise stop and does not cancel the current response. A finishTurn decision of action: 'end' can leave queued input for a later run.

Signal cancellation and await settlement abort-active-run

const pending = agent.prompt('Explain the catalog entries.');
agent.abort();
await pending;
await agent.waitForIdle();
console.log(agent.state.errorMessage);

abort() signals the active run. It does not undo completed external operations. waitForIdle() includes awaited agent_end listeners; awaiting it inside a listener on that same run can prevent settlement. An undefined errorMessage alone does not establish that an intended operation succeeded.

Keep an in-memory transcript checkpoint retain-transcript

await agent.waitForIdle();
const checkpoint = agent.state.messages.slice();
// Later, while idle, restore the top-level message array.
agent.state.messages = checkpoint;

The setter copies the array; the message objects remain shared. This illustrates an in-memory checkpoint, not durable storage or recovery of external side effects. Preserve system messages because they carry the replayed prompt.

Stop after a completed normal turn end-after-turn

agent.finishTurn = async ({ message }) => {
  if (message.stopReason === 'error' ||
      message.stopReason === 'aborted') return;
  return { action: 'end' };
};

This ends after a normal assistant turn and its finalized tool results, before another response to those results. The hook remains installed. finishTurn replaces shouldStopAfterTurn; returning action: 'continue' unconditionally would keep requesting responses.

Clear the conversation after settlement reset-when-idle

await agent.waitForIdle();
agent.reset();

reset() rejects during an active run. It clears conversation state and queues while retaining the replayed prompt/tool baseline. It does not erase every instruction or undo external side effects.

Alternatives

PackageRegistryPick it when
ainpmConsider Vercel's AI SDK for a shared interface across model providers. Its supplied npm description offers a single interface through the Vercel AI Gateway or direct provider connections. This supports a scope comparison, not a performance or feature-parity judgment.
openainpmThe openai package is the official TypeScript library for the OpenAI API. Consider it when OpenAI API access is the requirement and you intend to implement the surrounding application behavior yourself. The supplied metadata does not establish feature parity with Pi's agent loop.

More ai / ml guides

openai · mcp · huggingface-hub · tiktoken · scikit-learn · tokenizers · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.