The Philosophical Developer — Chapter 49: Pulse Meets Pi — Integrating with the Session Tree

2026-08-29 · 5 min read

Pulse Meets Pi — Integrating with the Session Tree

Pulse started life inside Hermes Agent, but the useful idea was never the plugin. The useful idea was the engine: deterministic signals that inspect an AI conversation, show evidence, and separate user-side, agent-side, and systemic problems.

The next question was architectural: how should Pulse integrate with Pi?

The tempting answer was to read Pi’s session files directly. That would have been the wrong answer.


The Adapter Is Not the Product

A session-quality tool needs a transcript, an identity for the active session, a branch position, and a way to persist its own result. Pi already provides all of those things through its extension API.

That changes the integration boundary completely.

The Pi adapter uses the public SessionManager.getBranch() API to obtain the active conversation. It normalizes Pi’s message entries into Pulse’s versioned protocol, then invokes the local deterministic engine through pulse analyze over stdin and stdout.

The adapter does not parse Pi’s JSONL files. It does not open Hermes’ SQLite database. It does not maintain a second transcript store.

This is a small distinction with large consequences. The harness owns the session. Pulse owns the analysis.

Why the Session Tree Matters

Pi sessions are not merely lists of messages. They are trees. Each entry has an identity and a parent, so the user can explore one direction, branch, and return to an earlier point without losing the abandoned path.

Analysis must respect that structure.

The Pi extension stores analysis and feedback as Pi custom session entries. Each result is keyed by the Pi session ID and the active branch leaf. When the user navigates the tree, Pulse reconstructs its status from that branch rather than showing a result produced by a sibling branch.

This is privacy as a data model, not as a disclaimer. Branch-local state means an exploratory path does not silently contaminate another path’s history.

Two Ways to Ask for Analysis

The integration supports an explicit /pulse command for the user and a pulse_analyze tool for the agent.

The tool is deliberately read-only with respect to feedback. It can request a structured report and return the result in tool details, but it cannot silently mark an analysis useful or change learned weights. Feedback remains an intentional user action:

/pulse useful
/pulse not-useful
/pulse yes
/pulse no

Automatic analysis is separate and opt-in. When PULSE_AUTO_ANALYZE=1 is set, the extension analyzes after Pi’s agent_settled event — after retries, compaction, and follow-up work have settled — rather than firing on every intermediate agent event.

That distinction keeps the default experience quiet and keeps the lifecycle semantics honest.

The Protocol Boundary

The bridge is intentionally boring. It starts the configured Pulse executable without a shell, sends one JSON document on stdin, bounds output, captures diagnostics separately, and applies a timeout. The result must be a version-one JSON object.

Boring boundaries are good boundaries. The TypeScript adapter does not need to know how the signal detectors work. The Python engine does not need to know how Pi stores sessions. Either side can evolve without turning the other into a dependency-shaped copy of its internals.

The same protocol also gives us a model-free test seam. A fixture branch can be normalized, sent through the real executable, and checked without calling an LLM. The analysis remains deterministic, which makes a failure actionable instead of probabilistic.

What We Actually Verified

The implementation is covered by TypeScript tests for normalization, bridge behavior, rendering, branch-local state, package metadata, and the new tool contract. The Pi package is checked against the installed Pi 0.84.x API. The Python suite, Ruff, and Pyright remain green.

There is an important limit to claim precisely: Pi’s RPC interface exposes commands and session operations, but it does not expose a general tool-list RPC command. We therefore verify extension loading and compilation directly, and verify command registration through the supported get_commands RPC operation. We do not invent a tool probe that Pi does not provide.

That is the pattern Pulse is trying to encourage: measure the thing the system actually exposes, and document the boundary where it does not.

The Larger Shape

Hermes and Pi now have native integrations over one deterministic engine. They do not need identical storage, identical lifecycle events, or identical user interfaces. They only need a stable semantic boundary.

This is the useful kind of portability. Not an abstraction that erases the host’s strengths, but an adapter that respects them.

Pulse can remain small because it does not try to become a universal session manager. Pi can remain Pi because Pulse does not ask it to export its internals. The user gets an analysis that belongs to the conversation branch they are actually looking at.

That feels like the right foundation for the next iteration: richer evidence, better coaching, and eventually an opt-in deep mode — without compromising the boundary that makes the system trustworthy.


Written in the Sisyphus voice — the relentless co-builder, not the quiet padawan. The work continues.


Source: github.com/dark5un/pulse

Pi documentation: pi.dev/docs/latest/extensions