mail-async

$npx mdskill add Tibsfox/gsd-skill-creator/mail-async

Provides durable async messaging between agents via filesystem mailboxes.

  • Solves inter-agent communication that survives crashes and restarts.
  • Depends on atomic file writes and directory-based mailboxes on disk.
  • Decides message delivery by writing JSON files atomically to recipient mailboxes.
  • Delivers results as persisted JSON messages readable by any agent.

SKILL.md

.github/skills/mail-asyncView on GitHub ↗
---
name: mail-async
description: Durable asynchronous messaging channel for inter-agent communication. Implements write-once read-many filesystem mail using atomic writes and directory-based mailboxes.
type: skill
category: state
status: stable
origin: tibsfox
modified: false
first_seen: 2026-03-06
first_path: .claude/skills/mail-async/SKILL.md
superseded_by: null
---
# Mail Async

Filesystem-backed durable messaging for multi-agent orchestration. Every message is a JSON file written atomically into a per-agent mailbox directory. Messages are never lost -- the write-then-rename pattern guarantees that a reader sees either a complete message or no message, never a partial write.

## Purpose

Mail is the high-bandwidth, durable communication channel in the Gastown chipset -- the PCIe equivalent. It carries work assignments from the mayor, completion reports from polecats, merge notifications from the refinery, and coordination messages between any pair of agents. Because messages persist on disk, they survive process crashes, agent restarts, and network disconnections.

## Filesystem Contract

```
.chipset/state/mail/{agent-id}/{timestamp}-{from-agent}.json
```

Each agent has a dedicated mailbox directory. Incoming messages are named with the creation timestamp and sender ID, guaranteeing unique filenames and natural chronological ordering when listing the directory.

**Example paths:**

```
.chipset/state/mail/polecat-alpha/2026-03-05T10-30-00Z-mayor-a1b2c.json
.chipset/state/mail/polecat-alpha/2026-03-05T10-31-00Z-witness-d3e4f.json
.chipset/state/mail/mayor-a1b2c/2026-03-05T10-32-00Z-polecat-alpha.json
```

## Message Format

```json
{
  "from": "mayor-a1b2c",
  "to": "polecat-alpha",
  "type": "work_assignment",
  "subject": "Bead gt-abc12 assigned to your rig",
  "body": "Implement the auth middleware as specified in the convoy plan. Priority P1.",
  "timestamp": "2026-03-05T10:30:00Z",
  "read": false,
  "priority": "normal"
}
```

### Field Reference

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `from` | string | yes | Sender agent ID |
| `to` | string | yes | Recipient agent ID |
| `type` | string | yes | Message type (see Message Types below) |
| `subject` | string | yes | Short summary for logs and listings |
| `body` | string | yes | Full message content |
| `timestamp` | string | yes | ISO 8601 creation timestamp |
| `read` | boolean | yes | Whether recipient has processed this message |
| `priority` | string | yes | `urgent`, `normal`, or `low` |

### Message Types

| Type | Sender | Recipient | Purpose |
|------|--------|-----------|---------|
| `work_assignment` | mayor | polecat | New bead assigned via hook |
| `completion_report` | polecat | mayor | Bead work completed |
| `merge_notification` | refinery | mayor | Merge result (success or conflict) |
| `health_escalation` | witness | mayor | Agent stall detected |
| `coordination` | any | any | General-purpose coordination |

## Sending a Message

The send protocol uses atomic writes to guarantee durability.

### Protocol

1. **Construct** the message JSON with all required fields
2. **Generate** the filename: `{ISO-timestamp}-{from-agent}.json` (replace colons with dashes in timestamp for filesystem compatibility)
3. **Serialize** with sorted keys for git-friendly output
4. **Write** to a temporary file in the recipient's mailbox: `.chipset/state/mail/{to}/.msg.tmp`
5. **Fsync** the temporary file to ensure data reaches disk
6. **Rename** the temporary file to the final path (atomic on POSIX)

### Pseudocode

```typescript
async function sendMail(message: MailMessage): Promise<void> {
  const mailDir = join(stateDir, 'mail', message.to);
  await mkdir(mailDir, { recursive: true });

  const safestamp = message.timestamp.replace(/:/g, '-');
  const filename = `${safestamp}-${message.from}.json`;
  const filePath = join(mailDir, filename);

  const content = serializeSorted(message);
  const tmpPath = join(mailDir, '.msg.tmp');

  const fd = await open(tmpPath, 'w');
  try {
    await fd.writeFile(content, 'utf-8');
    await fd.sync();
  } finally {
    await fd.close();
  }
  await rename(tmpPath, filePath);
}
```

## Receiving Messages

### Polling

Agents poll their mailbox directory on each state cycle. Polling returns all JSON files sorted by filename (which is sorted by timestamp due to the naming convention).

```typescript
async function checkMail(agentId: string): Promise<MailMessage[]> {
  const mailDir = join(stateDir, 'mail', agentId);
  let files: string[];
  try {
    files = (await readdir(mailDir)).filter(f => f.endsWith('.json')).sort();
  } catch {
    return []; // No mailbox yet
  }

  const messages: MailMessage[] = [];
  for (const file of files) {
    const msg = await readJson<MailMessage>(join(mailDir, file));
    if (msg) messages.push(msg);
  }
  return messages;
}
```

### Reading Unread Messages

Filter by `read === false` to get only unprocessed messages:

```typescript
const unread = (await checkMail(agentId)).filter(m => !m.read);
```

### Marking as Read

Update the `read` field atomically by rewriting the message file:

```typescript
async function markRead(agentId: string, filename: string): Promise<void> {
  const filePath = join(stateDir, 'mail', agentId, filename);
  const msg = await readJson<MailMessage>(filePath);
  if (!msg || msg.read) return;

  msg.read = true;
  await atomicWrite(filePath, serializeSorted(msg));
}
```

## Archival

Messages older than 24 hours are moved to an archive subdirectory to keep the active mailbox small and fast to scan.

```
.chipset/state/mail/{agent-id}/archive/{filename}
```

### Archival Protocol

1. List all files in the agent's mailbox
2. For each file, parse the timestamp from the filename
3. If the timestamp is more than 24 hours old, move the file to `archive/`
4. Archive moves use rename (atomic, no data loss)

```typescript
async function archiveOldMail(agentId: string): Promise<number> {
  const mailDir = join(stateDir, 'mail', agentId);
  const archiveDir = join(mailDir, 'archive');
  const cutoff = Date.now() - 24 * 60 * 60 * 1000;
  let archived = 0;

  const files = (await readdir(mailDir)).filter(f => f.endsWith('.json'));
  for (const file of files) {
    const tsStr = file.split('-').slice(0, 3).join('-').replace(/T/, 'T');
    const fileTime = new Date(tsStr).getTime();
    if (fileTime < cutoff) {
      await mkdir(archiveDir, { recursive: true });
      await rename(join(mailDir, file), join(archiveDir, file));
      archived++;
    }
  }
  return archived;
}
```

## Cross-Channel Integration

Mail integrates with other communication channels in a defined flow:

1. **Hook set** (hook-persistence): Mayor assigns work via hook
2. **Mail sent** (mail-async): Mayor sends `work_assignment` message with details
3. **GUPP activation**: Agent picks up hook, reads mail for context
4. **Work execution**: Agent processes the bead
5. **Mail sent** (mail-async): Agent sends `completion_report` to mayor
6. **Hook cleared** (hook-persistence): Work item retired

If an agent stalls during step 4, the witness detects via nudge (nudge-sync) and escalates to the mayor via `health_escalation` mail.

## Error Handling

| Condition | Behavior |
|-----------|----------|
| Recipient mailbox doesn't exist | Created automatically on first send |
| Corrupt JSON in mailbox | Logged as warning, skipped during polling |
| Disk full during write | OS error propagated, temp file left for cleanup |
| Concurrent writes to same mailbox | Safe -- each message has a unique filename |
| Agent terminated with unread mail | Mail persists; available if agent restarts |

## Constraints

- **Filesystem only:** No sockets, no tmux, no network. All communication through JSON files
- **Write-once:** Message content is immutable after creation. Only the `read` field changes
- **No guaranteed delivery order:** Recipients poll; order depends on filesystem listing
- **No acknowledgment protocol:** Sender does not get confirmation of receipt. Use mail replies for coordination
- **24-hour retention:** Active mailbox keeps messages for 24 hours before archival

More from Tibsfox/gsd-skill-creator

SkillDescription
3d-interaction-designDesign heuristics for three-dimensional interaction — input modalities (6DoF controllers, gaze, gesture, voice), selection and manipulation techniques (ray-cast, virtual hand, go-go, world-in-miniature), navigation (teleport, continuous, redirected walking), and feedback loops (haptic, audio, visual). Covers Fitts' law in 3D, discoverability vs affordance, safe-zone design, and comfort/simulator sickness mitigation. Use when designing VR/AR interactions, immersive walkthroughs, or any input system where the user's body is the controller.
accessibility-patternsProvides web accessibility best practices for semantic HTML, ARIA, keyboard navigation, color contrast, and screen reader patterns. Use when building UI components, reviewing accessibility, or when user mentions 'a11y', 'accessibility', 'ARIA', 'screen reader', 'keyboard navigation', 'WCAG'.
active-listeningActive listening techniques for effective communication. Covers attending behaviors, paraphrasing, reflective listening, clarifying questions, empathic response, barriers to listening, listening in conflict, and cross-cultural listening. Use when building listening skills, improving understanding in conversation, mediating disputes, or analyzing communication breakdowns.
adversarial-pr-reviewAdversarial spec-compliance PR review — cross-references diffs against approved specs, verifies runtime claims against source, detects competing PRs, audits scope/convention compliance. Use before merging.
agile-methodsAgile and Lean software development methodologies for iterative, adaptive project execution. Covers Scrum (roles, ceremonies, artifacts), Kanban (WIP limits, flow metrics), XP (TDD, pair programming, CI), SAFe overview, Lean Software Development (Poppendieck's 7 principles), user stories (INVEST criteria), estimation via story points and velocity, and the Agile Manifesto's values and principles. Includes Cynefin framework for methodology selection and GSD's relationship to Agile as a structured Lean variant.
algebraic-reasoningSymbolic manipulation, equation solving, and algebraic structures for mathematical reasoning. Covers distributive law, factoring, completing the square, linear through polynomial equation solving, systems of equations (substitution, elimination, Gaussian elimination, matrix methods), algebraic structures (groups, rings, fields), modular arithmetic, polynomial theory, and inequalities. Use when solving equations, simplifying expressions, working with algebraic structures, or performing symbolic manipulation.
algorithmic-awarenessUnderstanding how algorithmic systems shape what users see, know, and do -- from recommendation feeds to search ranking to credit scoring to hiring software. Covers the mechanics of recommendation systems, algorithmic bias and its sources, personalization's effects on information diets, opacity and accountability, AI limitations (hallucination, confident wrongness), and the human-in-the-loop question. Use when a learner needs to think critically about why particular content reached them.
algorithms-data-structuresAlgorithms and data structures from first principles through advanced analysis. Covers sorting (bubble, insertion, selection, merge, quick, heap, radix), searching (linear, binary, BFS, DFS, Dijkstra, A*), fundamental data structures (arrays, linked lists, stacks, queues, hash tables, trees, heaps, graphs, tries), complexity analysis (Big-O, Big-Omega, Big-Theta, amortized), recurrence relations, and algorithm design paradigms (divide-and-conquer, greedy, dynamic programming, backtracking). Use when analyzing, selecting, implementing, or comparing algorithms and data structures.
aminet-browserAminet search and browse: full-text search, category tree navigation, architecture filtering, package detail, and curated collections. Use when searching, browsing, or managing package collections.
aminet-emulatorFS-UAE emulator configuration and launch: hardware profiles, ROM management, WHDLoad integration, config generation, and state snapshots. Use when configuring emulation, managing ROMs, or launching Amiga software.