npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@synadia-ai/nats-pi-channel

v0.5.6

Published

Synadia Agent Protocol for NATS channel for PI Agent. Makes every PI session a discoverable, spec-compliant agent on NATS.

Readme

@synadia-ai/nats-pi-channel

NATS channel extension for PI Agent. Every running PI session becomes discoverable, addressable, and streamable over NATS — anyone with a Synadia Agent Protocol for NATS client (e.g. @synadia-ai/agents or synadia-ai-agents) can find your session, prompt it, and stream the reply back.

Install

# From npm
pi install npm:@synadia-ai/nats-pi-channel

# From a local clone (development)
pi install /absolute/path/to/nats-pi-channel

When iterating on the SDKs locally, both @synadia-ai/agents and @synadia-ai/agent-service need a current dist/ for PI's loader to resolve the file: links — see README-DEV.md at the repo root for the build sequence.

Then start PI normally:

pi

You should see Connected to NATS (<server>) as agents.prompt.pi.<you>.<session> and a footer status line NATS: agents.prompt.pi.<you>.<session>.

Configure

Out of the box, no configuration is needed: PI connects to demo.nats.io and uses your $USER + the basename of the working directory as the subject tokens. Your session is reachable at:

agents.prompt.pi.<owner>.<session>

For real deployments, point PI at your own NATS via a context file. Two common setups:

Production with a NATS CLI context (already configured via nats context add):

// ~/.pi/agent/nats-channel.json
{
  "context": "prod"
}

Pin a stable session name (so callers can address the same logical session even if you cd around):

{
  "context": "prod",
  "sessionName": "my-session"
}

Restart PI to pick up changes — or use the in-PI commands below.

Configuration reference

Config file lives at ~/.pi/agent/nats-channel.json:

| Field | Required | Default | Description | |-------|----------|---------|-------------| | context | no | — | Name of a NATS CLI context (file under ~/.config/nats/context/<name>.json). When unset, falls back to $NATS_URL or, if that's also unset, the built-in demo.nats.io. | | sessionName | no | sanitized basename of CWD | The 5th subject token. Override to give your session a stable, addressable name. |

The owner token (4th) is always derived from $USER — there's no override for it. For multi-tenant isolation, see Multi-tenancy below.

Environment variables

Env vars override the config file:

| Variable | Sets | Notes | |----------|------|-------| | NATS_CONTEXT | context | Highest precedence — see below. | | NATS_URL | raw URL (no auth context) | Used only when NATS_CONTEXT and config.context are both unset. | | NATS_SESSION_NAME | sessionName | |

Resolution order

  1. Built-in default — demo.nats.io, no auth
  2. config.context — wizard-set / hand-edited NATS CLI context
  3. $NATS_URL — raw URL fallback (only consulted when no context is set)
  4. $NATS_CONTEXT — wins over everything

For sessionName: $NATS_SESSION_NAME overrides config.sessionName, which overrides the CWD-basename default.

In-PI commands

Available inside a running PI session:

| Command | What it does | |---------|--------------| | /nats-status | Show current subject, service, instance id, protocol version, pending/queued counts | | /nats-configure | Print current config | | /nats-configure <context> | Switch NATS context | | /nats-configure session <name> | Override session name | | /nats-configure session clear | Revert to CWD basename |

/nats-configure writes the config file; restart PI to apply. (Live reconnect on context switch is a deferral — see Limitations.)

Verify

# Find your session (and any other agents on the same NATS)
nats req '$SRV.INFO.agents' '' --replies=0 --timeout=2s

# Watch heartbeats — your session beats every ~5 s
nats sub 'agents.hb.*.*.*'

A successful $SRV.INFO.agents response for a PI session looks like:

{
  "type": "io.nats.micro.v1.info_response",
  "name": "agents",
  "id": "JC8O0IGAWI5APOHLAOA96N",
  "version": "0.4.0",
  "description": "PI agent (my-session) in /home/me",
  "metadata": {
    "agent": "pi",
    "owner": "me",
    "session": "my-session",
    "protocol_version": "0.3",
    "cwd": "/home/me"
  },
  "endpoints": [
    {
      "name": "prompt",
      "subject": "agents.prompt.pi.me.my-session",
      "queue_group": "agents",
      "metadata": { "max_payload": "8MB", "attachments_ok": "true" }
    },
    {
      "name": "status",
      "subject": "agents.status.pi.me.my-session",
      "queue_group": "agents"
    }
  ]
}

If you see your agents.prompt.pi.<owner>.<session> subject in the response, you're discoverable. Multiple PI sessions show up as multiple responses to the same query — the cwd metadata field tells you which working directory each one was started from, useful when you've got several PI windows open and need to pick the right session to prompt.

Talk to your session

From the CLI:

# Plain text prompt
nats req agents.prompt.pi.<owner>.<session> "What files are here?" \
  --wait-for-empty --reply-timeout 30s --timeout 120s

# JSON envelope (caller SDKs use this form)
nats req agents.prompt.pi.<owner>.<session> '{"prompt":"What files are here?"}' \
  --wait-for-empty --reply-timeout 30s --timeout 120s

--wait-for-empty is required: replies stream as multiple chunks and end with an empty terminator message.

--reply-timeout matters too. Its default is 300 ms — the maximum gap allowed between consecutive replies. The agent publishes an immediate {type:"status",data:"ack"} chunk on request receipt, but the LLM's first response chunk typically lands 1–2 s later, so the default fires before the first response and the CLI exits after just the ack. Setting --reply-timeout 30s gives the LLM enough warm-up time. SDK callers (requestMany with strategy:"sentinel") don't hit this — they wait the full maxWait regardless of inter-arrival gaps.

From TypeScript using @synadia-ai/agents:

import { connect } from "@nats-io/transport-node";
import { Agents } from "@synadia-ai/agents";

const nc = await connect({ servers: "nats://localhost:4222" });
const agents = new Agents({ nc });

const [agent] = await agents.discover({ filter: { agent: "pi" } });

for await (const msg of await agent!.prompt("What files are here?")) {
  if (msg.type === "response") process.stdout.write(msg.text);
}

await agents.close();
await nc.close();

Attachments

When a request envelope carries attachments, each file is decoded and staged at:

~/.pi/agent/attachments/<session>/<uuid>/<filename>

The absolute paths are prepended to the prompt text so PI's model can open them with its file tools. Files staged earlier in a session stay on disk so follow-up turns can reference them; the whole <session>/ directory is removed on session shutdown.

Encode files with base64 -w0 <file> (Linux/macOS) or Buffer.from(bytes).toString("base64") in Node before embedding in the JSON envelope. Caller SDKs do this for you.

Caller-side limits (rejected with 400 if violated):

  • content must be standard-alphabet padded base64 — no URL-safe variant, no whitespace.
  • filename must be a plain basename. Path separators, .., absolute paths, and NUL bytes are rejected, not silently flattened.
  • The fully-encoded request must fit within the server-negotiated max_payload (1 MB on a default nats-server, more if the operator raised --max_payload).

Concurrency

Each PI session processes one NATS request at a time. Additional requests queue until the session is idle. The local TUI input and inbound NATS prompts share the same agent — typing locally during a NATS-driven turn means that local output flows to the NATS reply alongside the remote prompt's response.

Multiple PI sessions on the same host register as distinct service instances; nats micro info agents aggregates across all of them. If two sessions try to register on the same owner + session, the later one auto-suffixes -2, -3, … — pick a stable name with /nats-configure session <name> if you want addressability.

Multi-tenancy

The agent subject layout has no per-tenant slot. For real isolation between tenants or environments, use NATS accounts and subject permissions — that's a server-side configuration, not an extension one. Within a single account, sessions with distinct owner values (i.e. different $USERs) coexist cleanly.

Limitations

Deliberate deferrals:

  • No mid-stream queries. PI doesn't initiate permission prompts or clarifications over this channel; the protocol's query chunk type is supported by callers but never emitted by the PI side.
  • No live reconfigure. /nats-configure writes the config file; PI must be restarted for the new context or session name to apply.
  • TUI bleed. Local typing during a NATS-driven turn flows to the NATS reply subject as part of the response.

Troubleshooting

  • NATS: reconnecting… — the connection dropped; the channel keeps retrying indefinitely (maxReconnectAttempts: -1 from the SDK's withAgentReconnectDefaults), so just leave it — it will recover when the server is reachable again, including after a host sleep / network blip.
  • NATS: disconnected in footer — terminal. The client gave up reconnecting; the typical cause is repeated identical auth errors (the one path nats.js does not retry through, regardless of our defaults). Run /nats-status, then check the context file at ~/.config/nats/context/<context>.json and that the NATS server is reachable. Restart PI after fixing.
  • My session got a -2 suffix — another PI session was already registered on the same owner + session. Use /nats-configure session <name> to pick a different one.
  • nats req returns only the initial ack and exits — pass --reply-timeout 30s (default is 300 ms, shorter than the gap between the ack chunk and the LLM's first response). See the "Talk to your session" section above for the full command. --wait-for-empty alone isn't enough.
  • nats req hangs or returns nothing — pass --wait-for-empty. The protocol ends streams with an empty-body message, not a single response.
  • 400 attachment[N] has invalid base64 content — the caller emitted URL-safe base64 or unpadded output. Buffer.from(bytes).toString("base64") (Node) produces the right form.
  • 400 attachment[N] has unsafe filename — send the basename only ("report.pdf"), not a path ("./reports/report.pdf").
  • Stale attachments piling up under ~/.pi/agent/attachments/ — clean session shutdown removes the whole <session>/ tree, but a force-quit or crash leaves the per-request UUID directories on disk. Safe to rm -rf ~/.pi/agent/attachments/<session>/ between runs if you don't need to re-reference earlier attachments.

See also

License

Apache-2.0