JSPM

@marsulta/mailman

0.1.2
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 3
  • Score
    100M100P100Q80870F
  • License MIT

A structured interagent messaging protocol — send validated, typed packets between roles, tools, and services in multi-agent systems.

Package Exports

  • @marsulta/mailman

Readme

Mailman

A structured interagent messaging protocol for multi-agent systems.

Mailman lets you send typed, validated packets between AI agents, tools, and services — inside a single process or across a multi-agent architecture. It gives every role a clear contract, every message a lifecycle, and every transaction a full trace.

CI npm License: MIT


Why Mailman?

In multi-agent systems, agents need to communicate — but raw function calls have no structure, no validation, and no observability. Mailman gives every message:

  • A type (task.assign, review.request, health.ping, ...)
  • A validated shape — required fields, confidence bounds, known targets
  • A trace — every hop is logged with actor, event, and timestamp
  • A lifecyclecreated → validated → delivered → completed
  • Middleware hooks — tap into the pipeline for logging, auth, or metrics

Install

npm install @marsulta/mailman
# or
pnpm add @marsulta/mailman

Node.js >= 18 required.


Quick Start

import { Runtime, createPacket } from "@marsulta/mailman";

const runtime = new Runtime();
runtime.start();

// 1. Register a role with a handler
runtime.registerRole(
  {
    name: "worker",
    accepts: ["task.assign"],
    description: "Processes tasks",
  },
  async (packet) => {
    const result = String(packet.payload.prompt).toUpperCase();
    return createPacket({
      taskId: packet.taskId,
      parentPacketId: packet.packetId,
      type: "task.result",
      sender: "worker",
      target: packet.sender,
      payload: { result },
      status: "completed",
    });
  }
);

// 2. Send a packet
const reply = await runtime.send(
  createPacket({
    taskId: "task-1",
    type: "task.assign",
    sender: "orchestrator",
    target: "worker",
    payload: { prompt: "Hello, world!" },
  })
);

console.log(reply.payload.result); // "HELLO, WORLD!"

Core Concepts

Packet

The fundamental unit of communication. Every message is a MailmanPacket:

type MailmanPacket = {
  packetId: string;        // auto-generated
  taskId: string;          // links related packets
  parentPacketId?: string; // reply chain

  type: PacketType;        // see below
  sender: string;          // role name
  target: string;          // role name

  payload: Record<string, unknown>;
  status?: PacketStatus;

  // Optional
  intent?: string;
  scope?: { files?: string[]; repoWide?: boolean; artifacts?: string[] };
  constraints?: { allowNewFiles?: boolean; readOnly?: boolean; ... };
  confidence?: number;     // 0–1
  meta?: { timeoutMs?: number; replyRequired?: boolean; tags?: string[] };
  error?: { code: string; message: string };
};

Packet Types

Type Direction Meaning
task.assign → agent Assign a task
task.accept ← agent Agent accepted the task
task.reject ← agent Agent rejected the task
task.result ← agent Task completed, here's the output
review.request → reviewer Please review this result
review.result ← reviewer Review decision
route.request → router Route this packet
route.result ← router Routing decision
error.report ← mailman Something went wrong
health.ping → agent Are you alive?
health.pong ← agent Yes

Roles

A role registers a name, the packet types it accepts, and a handler function:

runtime.registerRole(
  {
    name: "reviewer",
    accepts: ["review.request"],
    description: "Reviews worker output",
    version: "1.0.0",
    health: "healthy",
  },
  async (packet) => { ... }
);

Middleware

Add interceptors that wrap the packet pipeline:

// Logging middleware
runtime.use(async (packet, next) => {
  console.log("→", packet.type, packet.sender, "→", packet.target);
  const reply = await next();
  console.log("←", reply.type, reply.status);
  return reply;
});

// Auth middleware
runtime.use(async (packet, next) => {
  if (!isAuthorized(packet.sender, packet.target)) {
    throw new Error("Unauthorized");
  }
  return next();
});

Middleware runs in insertion order, koa-style.

Client (fluent API)

import { Runtime, createClient } from "@marsulta/mailman";

const client = createClient(new Runtime())
  .start()
  .registerRole({ name: "agent", accepts: ["task.assign"] }, handler);

const reply = await client.send(packet);

Trace

Every packet flowing through the runtime is fully traced:

const trace = runtime.getTrace(packet.packetId);
// [
//   { event: "packet.received", actor: "mailman", timestamp: "..." },
//   { event: "packet.validated", actor: "mailman", timestamp: "..." },
//   { event: "handler.started", actor: "worker", timestamp: "..." },
//   { event: "packet.completed", actor: "mailman", timestamp: "..." },
// ]

API Reference

Runtime

Method Description
start() Start the runtime
stop() Stop the runtime
isRunning() Returns whether the runtime is active
use(fn) Register a middleware function
registerRole(role, handler) Register an agent role
unregisterRole(name) Remove a registered role
listRoles() List all registered roles
send(packet) Send a packet, returns a reply
ping(roleName) Check if a role is alive
getTrace(packetId) Get trace events for a packet
getTaskTrace(taskId) Get all trace events for a task
getStats() Get runtime stats

createPacket(init)

Factory that auto-fills packetId and timestamp. Pass any valid MailmanPacket fields minus those two.

createReply(original, type, sender, payload, overrides?)

Build a reply packet. Automatically:

  • Sets parentPacketId to original.packetId
  • Inherits taskId
  • Flips sender and target

createClient(runtime)

Returns a MailmanClient — a fluent chainable wrapper over Runtime.


CLI

npx mailman start    # Start the runtime
npx mailman status   # Show runtime status
npx mailman roles    # List registered roles
npx mailman inspect <packetId>  # Inspect a packet's trace
npx mailman doctor   # Run health diagnostics

Examples


Roadmap

Mailman v0.x (current)

  • In-process packet routing
  • Typed packet schema + validation
  • Full trace log
  • Middleware pipeline
  • CLI tools
  • Persistent trace (SQLite)
  • Cross-process transport (Unix sockets / HTTP)
  • Retry + dead-letter queue

Postmaster (future)

Postmaster is the "big brother" of Mailman — a standalone daemon that can observe and interpret messages across entire systems, not just agents.

With Postmaster, every subsystem speaks Mailman:

  • 🖨️ Printer spooler issues → error.report packet
  • 🌐 Network/DNS failures → error.report packet
  • 💾 Disk space warnings → error.report packet
  • 🗄️ Database connection drops → error.report packet

Your agents can subscribe to infrastructure signals the same way they subscribe to each other's messages. Postmaster makes the whole stack observable and actionable from a single protocol.


Contributing

See CONTRIBUTING.md.


License

MIT © junkyard22