Pinet Core
The runtime under the adapters: how the broker owns the mesh, how messages become durable inbox entries, and how the system repairs itself when an agent goes quiet.
Runtime modes
Every pi session that loads the bridge runs in one of four modes. Pinet stays off unless you set runtimeMode, autoConnect, or autoFollow. Nothing connects to your workspace by accident.
| Mode | Role | Use when |
|---|---|---|
single | One instance handles everything: ingress, work, replies. | You run one agent and want Slack access without a mesh. |
broker | Coordinates: watches Slack, assigns work, tracks ownership, syncs state. | You run several agents and want one control plane. |
follower | Connects to the broker, receives assigned work, stays in sync. | Worker sessions joining an existing broker. |
off | Extension loaded, nothing running. | Default. |
Inside pi, /pinet start makes the current session the broker,/pinet follow joins it, and /pinet status tells you which of these is true right now. Only one session holds the Socket Mode connection: Slack allows a limited number per app, and a single WebSocket owner avoids duplicate delivery.
The message primitive
message.send is the one primitive. A Slack reply, an agent-to-agent note, and a broker nudge are the same operation with different addresses; adapters translate at the edges. The broker itself needs no adapter: it is a pi session, and you can type to it in its own terminal. This is what keeps new transports cheap: Discord, Linear, or GitHub would plug into the same flow rather than growing their own routing.
inbox semantics
Each agent has a durable inbox. Messages are:
- routed by thread ownership (an owned thread keeps flowing to its owner)
- queued while the agent is mid-turn, drained on the next turn
- marked read once processed
- preserved across restarts
Thread ownership is the continuity mechanism. Once an agent claims a Slack thread, replies in that thread route to it until the claim is released, by the agent finishing or by RALPH noticing the owner is gone.
Agent identity
Agents carry a logical identity that survives reconnects. Heartbeats and leases handle liveness: a worker that stops responding loses its lease, and its claims become assignable again. Dead agents get reaped, not ignored.
Identities are named. When "Rocket Dolphin" or "Silent Crocodile" posts in a thread, you know which agent holds which task. With dozens of tasks moving at once, names are what make the mesh legible to the humans steering it. Rename a session any time with/pinet rename.
The RALPH loop
RALPH is the broker's autonomous maintenance cycle. On every pass (default: five minutes) it:
- checks worker presence,
- releases stale thread claims held by unavailable workers,
- observes pending backlog while broker maintenance handles assignment, and
- triggers scheduled wake-ups.
The loop is tunable: faster cycles for quicker recovery, snoozing after empty cycles for quiet nights.
{
"slack-bridge": {
"ralphLoopIntervalMs": 120000,
"ralphSnoozeAfterEmptyCycles": 3,
"ralphSnoozeDurationMs": 1800000
}
}/pinet status for current state,/pinet logs for repeated failures, then let RALPH run; most stalls clear on its next pass without intervention.The dispatcher
Agents drive coordination through a compact pinet dispatcher rather than a sprawl of tools: one entry point, discoverable actions, a small schema per action.
{
"action": "send",
"args": {
"to": "@worker",
"message": "Please review PR #123"
}
}- send
- Message an agent or a broker-only channel.
- read
- Read this agent's inbox.
- schedule
- Schedule a future wake-up.
- free
- Mark this agent idle and available for work.
- help
- Discover actions and their schemas; nothing bloats the hot path.
Hot-path Slack replies go through a dedicated slack_send tool; uploads, canvases, pins, bookmarks, and other cold Slack actions sit behind a matchingslack dispatcher. The split is deliberate: frequent operations get compact dedicated tools, everything else is discovered on demand.
@pinet/pinet-core
The pinet-core package holds the runtime helpers that work independently of Slack: the pieces the bridge uses internally, extracted behind package exports so future adapters can move one boundary at a time.
- output option normalisation (defaults to
cli; acceptsjsonorfull), - durable read-result formatting,
- scheduled wake-up time parsing, and
- thread ID helpers.
No Slack types, no adapter imports. If you are building a different transport for Pinet, this is the layer you build against.