The project long known as badlogic/pi-mono now lives at earendil-works/pi. The repository migration is less interesting than the architectural cleanup visible in the current README: Slack and chat automation moved to pi-chat, while the main monorepo retains four core packages.

pi's minimalism is not primarily about a tiny line count. It is about assigning responsibilities to layers that change for different reasons.

Four packages, four contracts

@earendil-works/pi-ai normalizes provider models and streaming. OpenAI, Anthropic, Google, and other backends differ in request schemas, reasoning controls, tool-call deltas, usage accounting, and error events. pi-ai turns those variations into a common model and assistant-message stream.

pi-agent-core owns the smallest stateful loop. Its message boundary is particularly clean:

AgentMessage[]
  -> transformContext()
  -> AgentMessage[]
  -> convertToLlm()
  -> provider Message[]
  -> LLM

transformContext can prune or compact application messages while preserving custom types. convertToLlm is the required final bridge that filters UI-only messages and converts the rest into roles the model understands. The loop therefore does not force an application's entire event history to pretend it is an LLM transcript.

pi-coding-agent adds the product-level state: session trees, compaction, project context, built-in tools, skills, prompt templates, themes, and TypeScript extensions.

pi-tui owns terminal interaction and differential rendering. Provider protocol does not leak into terminal layout, and UI changes do not redefine the agent lifecycle.

Event ordering is part of correctness

The low-level loop emits a structured sequence around agent, turn, message, and tool execution. A tool-using turn looks like:

message_end(assistant with tool calls)
tool_execution_start / update / end
message_start / end(tool results)
turn_end

Tool batches are parallel by default. Preflight is sequential, allowed tools execute concurrently, and completion events are emitted when each tool actually finishes. However, persisted tool-result messages are restored to the assistant's original tool-call order.

That distinction permits concurrency without making the transcript nondeterministic. If any tool declares sequential execution, the entire batch becomes sequential, which is appropriate for side-effectful operations whose order matters.

beforeToolCall runs after argument validation and can block execution. afterToolCall can transform a result or mark it as terminating. A batch stops the automatic follow-up only when every finalized result is terminating, avoiding a race where one early tool prevents other requested results from reaching the model.

These are small semantics with large consequences for replay and audit.

Loop, Agent, and harness are different levels

The raw agentLoop() stream preserves event order but does not await arbitrary consumer work before production continues. The higher-level Agent treats assistant message_end processing as a barrier before tool preflight. A policy listener can therefore update state or prepare an audit before beforeToolCall runs.

The newer harness layer adds session persistence, save-point snapshots, steering and follow-up queues, abort handling, compaction, branch summaries, resource loading, and lifecycle settlement.

This separation distinguishes observation from control. A telemetry consumer should not accidentally become a scheduling barrier. A policy hook that can block a tool must have stronger sequencing guarantees.

Explicit omissions are part of the design

pi does not include a built-in permission system for filesystem, process, network, or credential access. By default, it inherits the authority of the launching process. The project recommends Gondolin, Docker, or OpenShell when a stronger boundary is required.

The coding agent also intentionally leaves MCP, subagents, permission popups, plan mode, todos, and background shell behavior to extensions or external packages rather than treating every popular workflow as core.

This is composable, but it is not “safe by default.” A clearly documented missing boundary is better than a fake one, yet deployers still have to build the boundary.

Personal take

pi will resonate with framework authors, CLI enthusiasts, and advanced developers who want stable events and extension points rather than a prescribed workflow. They are willing to assemble their own agent because the package boundaries make replacement possible.

As model APIs and basic loops become commodities, durable value will accumulate in two markets around kernels like pi: workflow extensions and hardened distributions. The latter will package permissions, containers, enterprise policy, and audit on top of a composable core.

My main takeaway is that “not implemented” can be an architectural virtue when responsibility is explicit. pi refuses to prove completeness by accumulating features. The remaining obligation is an equally explicit external-security contract. Freedom and inherited process permissions are not synonyms.

Sources