Skip to content

feat(pi): @cotal-ai/pi native-embed coding-agent mesh peer#126

Open
Lanzelot1 wants to merge 3 commits into
mainfrom
feat/land-connector-pi
Open

feat(pi): @cotal-ai/pi native-embed coding-agent mesh peer#126
Lanzelot1 wants to merge 3 commits into
mainfrom
feat/land-connector-pi

Conversation

@Lanzelot1

@Lanzelot1 Lanzelot1 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Adds @cotal-ai/pi: the Cotal mesh as a pi extension — a thin client into the user's own pi installation. No pi runtime is bundled; the user's pi version, settings, auth, and other extensions all apply.

One extension file covers three surfaces:

  • Interactivepi --extension <file> (plus COTAL_* env): the TUI session a human is sitting in becomes a mesh peer.
  • Spawned workers — the Connector (agent type pi) launches the operator's installed pi binary with the extension loaded, like the Claude Code connector spawns claude. Under the manager's pty runtime the worker is the real pi TUI — cotal attach shows it.
  • Agents built on pi's SDK — pi's default resource loader discovers ~/.pi/agent/extensions/, so default SDK embedders join with no per-app work. (Forks and custom-loader apps, e.g. openclaw, need their own thin plugin — a follow-up.)

Without a mesh identity in the env the extension stays inert, so an installed copy never affects normal pi sessions.

Inbound DMs, role anycasts, and @mentions land as user turns in the live session (steered mid-turn while streaming), with ack-on-surface via the package's InboxTurn — a crash redelivers, nothing is lost. (InboxTurn lives in this package as its sole consumer; it gets promoted to connector-core if/when another native-embed adapter needs it.) The model gets the full shared cotal_* toolset (rendered from connector-core's cotalToolSpecs, the same source the Claude Code and OpenCode adapters render) and owns its replies: it answers by calling cotal_dm / cotal_send / cotal_anycast, can stay silent when no reply is warranted, and nothing leaves the peer that it didn't deliberately send. cotal_inbox is read-only (peek) since the loop drives delivery and acking. Persona body and model: from the agent file ride pi's own CLI flags (--append-system-prompt with a frontmatter-stripped file, --model) — contributed by @TheReaperJay, whose commit is included with authorship.

Contents

  • extensions/pi/* — the extension + tool renderer + connector, plus InboxTurn (the ack-on-surface turn helper) and its smoke:inbox
  • extensions/connector-core — additive only: MeshAgent.ackInbox (ack-by-id) and small shared-surface fixes (sender framing, own-echo wake exclusion, #chan normalization, provider keys)
  • bin/cotal.ts — one import line: the binary registers the pi connector like every sibling, so cotal spawn --agent pi works out of the box (no dedicated example needed — none of the other connectors has one)
  • docs (agent-frameworks.md, READMEs)

Scope

pi only. packages/core untouched — no new message kinds, subjects, or endpoint methods. buildLaunch follows the sibling launchEnv/aclEnv contract. One accepted residual, documented in code: pi's extension API can't drain the steering queue at turn end, so a message steered after the loop's final poll can carry into the next turn; SDK embedders needing it fully closed can wire session.clearQueue() + InboxTurn.commitExcept directly (documented pattern).

Vetting

  • pnpm typecheck, pnpm build, pnpm smoke:ci green; smoke:inbox covers the InboxTurn invariants.
  • Live e2e against stock pi with the extension loaded (real model turns): peer joins; a DM is answered via a cotal_dm tool call and a channel @mention via cotal_send back on that channel; ambient chatter is ack-dropped in silence; cotal_roster / read-only cotal_inbox work from a prompt; killing the pi process mid-turn and restarting redelivers the un-acked message and it gets answered — no message loss; without COTAL_* env a normal pi session is completely unaffected.

@Lanzelot1
Lanzelot1 force-pushed the feat/land-connector-pi branch from 85b3f02 to 6d39239 Compare June 26, 2026 20:06
@Lanzelot1

Copy link
Copy Markdown
Collaborator Author

Rebased onto current main + locally vetted ✅

Rebased feat/land-connector-pi onto main (84a560e) to clear the conflict — the only conflict was pnpm-lock.yaml, regenerated via pnpm install. Scope unchanged: 40 files, +2736/−14. Now MERGEABLE / CLEAN.

Vetting (local, since CI's functional gate doesn't fully cover the embed loop):

  • pnpm typecheck + pnpm build — green across all 21 packages (incl. extensions/{pi,openai-agents,vercel-ai} + the three examples/03-*).
  • connector-core smokes — green: inbox (InboxTurn ack-on-surface), orientation, attention, channel-attention, cross-path-dedup, feedback, spawn-args.

Live pi e2e (local NATS mesh, real model turn via pi's own stored auth):

  • pi peer joins, presence flips idle → working → idle, and a DM gets a real reply routed on the correct delivery mode (DM → DM the sender back).
  • Ack-on-surface verified end-to-end: DM'd a long-running prompt, SIGKILL'd the peer mid-turn (no reply emitted), restarted it with the same id → the un-acked DM redelivered from the durable inbox and was answered. No message loss.

CI now green: check + CodeQL (js-ts, python).

@Lanzelot1
Lanzelot1 force-pushed the feat/land-connector-pi branch from 6d39239 to 79c9c43 Compare June 26, 2026 20:55
@Lanzelot1 Lanzelot1 changed the title feat(connectors): native-embed agent-framework peers (pi, openai-agents, vercel-ai) feat(pi): @cotal-ai/pi — native-embed coding-agent mesh peer Jun 26, 2026
@Lanzelot1
Lanzelot1 force-pushed the feat/land-connector-pi branch from 79c9c43 to 89a57ed Compare June 26, 2026 20:58
Lanzelot1 added a commit that referenced this pull request Jun 26, 2026
Sibling native-embed adapters to @cotal-ai/pi, reusing connector-core's
InboxTurn: @cotal-ai/openai-agents (OpenAI Agents SDK) and @cotal-ai/vercel-ai
(Vercel AI SDK). Each embeds MeshAgent + the framework session and answers
DMs/anycasts/@-mentions off the inbox. Adds examples/03-{openai-agents,vercel-ai}.

Stacked on feat/land-connector-pi (pi PR #126) for the shared InboxTurn; becomes
a standalone PR once pi lands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Lanzelot1

Copy link
Copy Markdown
Collaborator Author

Scope / containment

This change is contained in the integrations tier — the protocol core is not touched:

  • packages/core (the wire standard): 0 files. No new message kinds, subjects, or endpoint methods.
  • All code lives in extensions/: the new extensions/pi adapter, plus an additive-only change to the shared extensions/connector-core runtime — a new InboxTurn class, a new MeshAgent.ackInbox() method, and one export. Nothing existing is modified or removed.
  • InboxTurn lives in connector-core (not pi) on purpose: it's the shared embed-loop primitive the sibling adapters (openai-agents, vercel-ai on feat/connector-openai-vercel) reuse. connector-core imports no adapter (correct dependency direction), and the existing connectors (Claude Code / Codex / OpenCode / Hermes) don't reference the new API, so there's no blast radius.

@Lanzelot1 Lanzelot1 changed the title feat(pi): @cotal-ai/pi — native-embed coding-agent mesh peer feat(pi): @cotal-ai/pi native-embed coding-agent mesh peer Jun 26, 2026
@deshbirc

Copy link
Copy Markdown

I've been building a Pi integration from a different angle and wanted to share two things that might be complementary rather than overlapping.

1. Interactive-session vs. headless peer.
This PR embeds Pi's SDK via createAgentSession() as a background peer, great or spawning unattended worker bots. I took the other path: pi --extension, which loads the connector into the real interactive pi TUI, so the session that joins the mesh is the same one a human can drop into, watch, and type into (works as a cmux pane today). The two patterns serve different needs:

  • headless swarm workers vs.
  • watchable/joinable sessions

2. A waiting presence state for the permission gap.
The PR notes spawned peers "run unattended" since Pi has no permission gate. I hit the same wall and solved it without owning a gate: an observer that taps Pi's in-process event bus for a gate's begin/resolve signals and reflects a waiting presence state — distinct from working/idle. Presence stays a pure reflection of the bus (never inferred), and gating/security stays separate from coordination, joined by one published signal contract (agent:attention:needed / agent:attention:resolved).

@TheReaperJay

Copy link
Copy Markdown
Contributor

@Lanzelot1 I've got my PR stacked on top at #182 - very clean; thanks very much for the Pi foundation!

@deshbirc I had the same thought and put together this PR for exactly the same reason. I want to be able to manually watch/interact with agent terminals so that I can babysit edit permissions and code since "just throw it at the wall and see what sticks" vibing never produces anything near what proper engineering does. I've managed to add a ctx.hasUI friendly flag that should keep all of this intact and allow BOTH headless and TUI based agents. Feel free to hop on that one and let me know if it solves your original concerns or if you had a different shape. I don't think I looked to in-depth into the "working/idle" approach like you have so that would be cool to hear about.

@Lanzelot1
Lanzelot1 force-pushed the feat/land-connector-pi branch 12 times, most recently from dd59322 to 30b8774 Compare July 8, 2026 07:06
Lanzelot1 and others added 3 commits July 8, 2026 00:29
MeshAgent.ackInbox acks specific buffered messages by id, wherever they sit —
immune to the MAX_INBOX front-eviction that shifts positions out from under
drainInbox — so a consumer can ack exactly the messages a turn surfaced.
Plus shared-layer fixes that apply to every connector: senders rendered as
"name" (role) so models stop replying to the literal name/role text, own
channel echoes excluded from the wake policy, display-form "#chan" channel
args normalized fail-loud instead of silently token-rewritten, historical
items flagged as catch-up context in injections, and the provider-key
allow-list extended (Gemini/Groq/xAI/DeepSeek/Mistral).
The mesh as a pi extension, plugged into the user's OWN pi — no bundled
runtime. One file covers three surfaces: `pi --extension` puts the
interactive TUI session on the mesh; the Connector spawns the operator's
installed `pi` with the extension loaded (the worker IS the real TUI,
watchable via cotal attach); and pi's default resource loader discovers
~/.pi/agent/extensions, so agents built on pi's SDK join with no per-app
work. Without a COTAL_* identity in the env the extension stays inert.

Inbound DMs/anycasts/@mentions land as user turns in the live session
(steered mid-turn while streaming); the loop owns reply routing on the
correct delivery scope, with ack-on-surface via InboxTurn — a crash
redelivers, nothing is lost. The model gets cotal_roster / cotal_status;
sending stays in the loop.
Spawned peers ignored the agent file's persona body and pinned model —
the pi connector was the lone holdout among the harness connectors.
buildLaunch parses the file at launch (a malformed persona fails loud at
cotal spawn), passes the frontmatter-stripped persona body to pi via
--append-system-prompt with a file path (no ARG_MAX ceiling), and
forwards the resolved model via --model, with the spawn flag taking
precedence over the file's model: pin.
@Lanzelot1
Lanzelot1 force-pushed the feat/land-connector-pi branch from 30b8774 to f5699de Compare July 8, 2026 07:32
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.

3 participants