Agent Skills: Postal System

Complete messaging system — letters, texts, goals, rewards, attachments

UncategorizedID: simhacker/moollm/postal

Install this agent skill to your local

pnpm dlx add-skill https://github.com/SimHacker/moollm/tree/HEAD/skills/postal

Skill Files

Browse the full folder contents for postal.

Download Skill

Loading file tree…

skills/postal/SKILL.md

Skill Metadata

Name
postal
Description
Complete messaging system — letters, texts, goals, rewards, attachments

Postal System

"Any address can send and receive. Goals come from anyone, not just Mom." — The Gezelligheid Grotto Design Principles


What Is It?

The Postal System is the complete messaging infrastructure:

| Feature | Description | |---------|-------------| | Messages | Letters between any endpoints | | Texts | SMS-style instant messages | | Attachments | Items, gold, images, buffs, room access | | Goals | Any character can assign/modify/complete goals | | Phone | Always-available mobile access (it's 2026!) | | Routing | Deterministic delivery without LLM |


Core Concepts

Endpoints

Anything addressable can send/receive:

to: "player"                       # Reserved keyword
to: "characters/family/mom/"       # Character path
to: "characters/npcs/bartender/"   # Any NPC
to: "pub/"                         # Room
to: "storage/vault/"               # Directory
to: "start/mailbox.yml"            # Object

Deterministic Addressing

CRITICAL: Addresses are paths, not names. The simulator routes without LLM.

# BAD: Symbolic (requires LLM)
from: mom
to: the bartender

# GOOD: Deterministic paths
from: "characters/family/mom/"
to: "characters/npcs/bartender/"

Reserved Keywords

| Keyword | Resolves To | |---------|-------------| | player | Current player character | | party | All party members | | narrator | System/narrative voice |


It's 2026 — You Have a Phone!

You carry a phone at all times. Mail and texts are always available.

player:
  devices:
    phone:
      always_carried: true
      capabilities: [mail, text, notifications, camera, maps]

Notification Types

| Type | Example | |------|---------| | 📬 Mail | "New letter from Mom!" | | 💬 Text | "Don: Meet me at the pub" | | 🎯 Quest | "Quest updated: Find the Key" | | ⚠️ Alert | "WARNING: Grue nearby!" | | 🎁 Reward | "You received: Gold Coins (50)" |


Letters (Full Messages)

Structure

letter:
  id: "letter-001"
  from: "characters/family/mom/"
  to: "player"
  subject: "Your First Quest"
  
  body: |
    Dearest child,
    
    I need you to find something for me.
    There's a brass key somewhere in the maze.
    
    With love,
    Mom
    
  attachments: []
  
  # Goal integration (optional — ANY character can do this!)
  creates_goal:
    id: find-key
    name: "Find the Key"
    complete_when: "player has brass-key"
    reward:
      letter: mom-reward-001
      item: family-locket
      
  # Metadata
  sent: null
  delivered: null
  read: false
  status: draft  # draft | outbox | sent | delivered | read

Goal Integration

Any character can create/modify/complete goals — not just Mom:

# Bartender assigns a quest
letter:
  from: "characters/npcs/bartender/"
  to: "player"
  subject: "A Favor to Ask"
  body: "Could you deliver this package to the mayor?"
  
  creates_goal:
    id: deliver-package
    name: "Deliver the Package"
    complete_when: "player has talked to mayor with package"
    reward:
      gold: 50
      
# Don Hopkins updates your quest
letter:
  from: "characters/real-people/don-hopkins/"
  to: "player"
  subject: "Found something!"
  
  modifies_goal:
    id: find-key
    extend_with: "also check the old chest"

Texts (Instant Messages)

Short, instant messages. It's 2026!

text:
  id: "text-001"
  from: "characters/npcs/bartender/"
  to: "player"
  body: "Hey, you left your hat here!"
  
  quick_replies:
    - "On my way!"
    - "Thanks, be there soon"
    - "Can you hold it?"
    
  delivery: instant  # Not queued like letters
  timestamp: null
  read: false

Texts vs Letters

| Feature | Text | Letter | |---------|------|--------| | Length | Short (< 160) | Long | | Attachments | Photos only | Anything | | Delivery | Instant | Next tick | | Creates goals | Rarely | Yes | | Formality | Casual | Can be formal |


Attachments

Messages can carry anything:

attachments:
  # Objects
  - type: object
    ref: "brass-key"
    action: send       # Remove from sender, give to recipient
    
  - type: object
    ref: "map"
    action: reference  # Just mention, don't transfer
    
  # Currency
  - type: gold
    quantity: 100
    action: send
    
  # Images
  - type: image
    ref: "images/treasure-map.png"
    action: copy
    
  - type: image
    action: generate
    prompt: "A hand-drawn map to the treasure..."
    
  # Buffs
  - type: buff
    ref:
      name: "Blessed"
      effect: "+1 luck"
      duration: 10
    action: apply
    
  # Room access
  - type: room
    ref: "maze/secret-chamber/"
    action: unlock

Attachment Actions

| Action | Effect | |--------|--------| | send | Remove from sender, give to recipient | | give | Give to recipient (sender keeps if applicable) | | copy | Duplicate for recipient | | reference | Mention without transfer | | unlock | Grant access (for rooms) | | apply | Apply effect (for buffs) | | generate | Create on delivery (for images) |


Inbox & Outbox

Inbox

# player/INBOX.yml
inbox:
  owner: "player"
  
  messages:
    - ref: "messages/mom-quest.yml"
      received: "2026-01-10T10:00:00Z"
      read: false
      priority: high
      from: "characters/family/mom/"
      subject: "Your First Quest"
      preview: "Dearest child, I need you to..."
      
  unread_count: 1
  
  settings:
    max_messages: 100
    forward_to: null

Outbox (Optional)

Stage messages before sending:

# player/OUTBOX.yml
outbox:
  owner: "player"
  
  drafts:
    - ref: "messages/draft-001.yml"
      started: "2026-01-10T09:00:00Z"
      
  pending:
    - ref: "messages/ready-001.yml"
      to: "characters/family/mom/"

Delivery Simulation

The postal system simulates realistic delivery:

Delivery Time

Messages don't arrive instantly (unless texting!):

letter:
  from: "characters/family/mom/"
  to: "player"
  
  # Delivery simulation
  delivery:
    method: letter           # letter, express, text
    
    # Time calculation
    base_time: 3             # Base turns
    distance_factor: 1       # Turns per "hop" of distance
    total_time: 5            # Calculated: base + (distance * factor)
    
    # Timestamps
    sent: "2026-01-10T10:00:00Z"
    estimated_arrival: "2026-01-10T10:05:00Z"  # 5 turns later
    delivered: null          # Set when actually delivered
    
    # Status
    status: in_transit       # draft | outbox | in_transit | delivered | read
    turns_remaining: 5

Delivery Methods

| Method | Base Time | Cost | Features | |--------|-----------|------|----------| | text | 0 (instant) | Free | Photos only, casual | | letter | 3 turns | 1 gold | Full attachments, goals | | express | 1 turn | 5 gold | Priority delivery | | freight | 10 turns | 0.5 gold/kg | Heavy items, bulk | | courier | 2 turns | 10 gold | Guaranteed, tracked |

Distance Calculation

Distance is measured in "hops" through the room graph:

# Distance examples:
# Same room: 0 hops
# Adjacent room: 1 hop
# Through maze: 5+ hops
# Different "region": 10+ hops

delivery:
  from_location: "pub/"
  to_location: "characters/family/mom/"  # Mom's home
  hops: 4
  time_per_hop: 1
  total_delivery_time: 7  # base 3 + (4 * 1)

Postage Costs

Sending mail costs resources:

letter:
  from: "player"
  to: "characters/family/mom/"
  
  postage:
    method: letter
    base_cost: 1             # Gold
    weight_cost: 0           # Per kg for heavy items
    distance_cost: 0.5       # Per hop
    total_cost: 3            # Calculated
    
    # Payment
    paid: false
    paid_from: "player"      # Who pays
    
    # Insufficient funds?
    requires_payment: true
    can_send: true           # false if can't afford

Free Messaging

Some messages are free:

# Texts are free
text:
  postage:
    method: text
    total_cost: 0
    
# System messages are free
letter:
  from: "narrator"
  postage:
    total_cost: 0
    reason: "system message"
    
# Within same location is free
letter:
  from: "pub/bartender.yml"
  to: "pub/patron.yml"
  postage:
    total_cost: 0
    reason: "same location"

In-Transit Tracking

Track messages in transit:

# world.skills.postal state
postal:
  in_transit:
    - id: letter-001
      from: "player"
      to: "characters/family/mom/"
      turns_remaining: 3
      
    - id: letter-002
      from: "characters/npcs/bartender/"
      to: "player"
      turns_remaining: 1
      
  # Each tick, turns_remaining decrements
  # When 0, message is delivered

Delivery Simulation in Tick

# MAIL phase of simulation
def deliver_mail(world):
    postal = world.skills.postal
    
    # Decrement turns for in-transit messages
    still_in_transit = []
    for message in postal.get('in_transit', []):
        message['turns_remaining'] -= 1
        
        if message['turns_remaining'] <= 0:
            # Deliver now!
            actually_deliver(world, message)
            world.trigger_event('MAIL_ARRIVED', {
                'from': message['from'],
                'to': message['to']
            })
        else:
            still_in_transit.append(message)
    
    postal['in_transit'] = still_in_transit

Express & Priority

Pay more for faster delivery:

letter:
  delivery:
    method: express
    
  postage:
    base_cost: 5             # Express premium
    guaranteed_time: 1       # Arrives next turn
    tracking: true           # Can check status
    insurance: true          # Compensated if lost

Lost Mail (Optional Mechanic)

For added realism/drama:

postal:
  settings:
    loss_chance: 0.01        # 1% chance of loss
    loss_recoverable: true   # Can be found later
    
    # Lost mail goes to:
    lost_mail_location: "maze/lost-and-found/"

Deterministic Routing

The simulator delivers mail without LLM. See ROUTING.md.

Routing Instructions

letter:
  from: "characters/family/mom/"
  to: "player"
  
  # Generated routing (deterministic)
  routing:
    destination_type: player
    destination_path: player
    delivery_point: inbox
    inbox_path: player/INBOX.yml
    
    attachments_transfer:
      - type: object
        ref: "brass-key"
        action: send
        from_inventory: "characters/family/mom/"
        to_inventory: "player"
        
    triggers:
      - event: MESSAGE_RECEIVED
        data: { from: "characters/family/mom/" }
      - event: GOAL_CREATED
        data: { goal_id: find-key }

Simulation Phase

Mail delivery is a phase in the simulation tick:

1. RESET     — foo_effective = foo
2. BUFFS     — Apply modifiers
3. SIMULATE  — Objects run
4. MAIL      — Deliver queued messages (deterministic!)
5. EVENTS    — Process queue
6. NAVIGATE  — Move player
7. DISPLAY   — Update UI

Storage Integration

Mail items to your vault:

letter:
  from: "player"
  to: "storage/vault/"
  subject: "Depositing treasure"
  
  attachments:
    - type: gold
      quantity: 500
      action: send

Logistics Integration — Postal IS the Transport Layer!

KEY INSIGHT: You don't need physical bots to move items between logistic containers. The postal system IS the transport layer — instantaneous, free, efficient!

The Unification

| Factorio | MOOLLM | |----------|--------| | Logistics bots | Postal system (free, instant) | | Flying between chests | Email/text delivery | | Request list | Triggers automated mail | | Active provider pushing | Auto-send attachments | | Circuit signals | Text notifications |

How It Works

When a logistics requester needs items, instead of dispatching a bot:

# Automatic postal delivery for logistics
logistics_delivery:
  trigger: "requester.request_unfulfilled"
  
  # Find a provider with matching items
  provider: "nw/iron-ore/"
  requester: "forge/"
  item: "iron-ore"
  count: 20
  
  # Generate a postal transfer (instant, free!)
  postal_transfer:
    type: internal-logistics    # Special type: no cost, instant
    from: "nw/iron-ore/"
    to: "forge/"
    attachments:
      - type: object
        ref: "iron-ore"
        quantity: 20
        action: send
        
    # No delivery time for internal logistics!
    delivery:
      method: logistics         # New method: instant, free
      total_time: 0
      cost: 0

Logistics Delivery Methods

| Method | Time | Cost | Use Case | |--------|------|------|----------| | logistics | 0 | Free | Internal network transfers | | text | 0 | Free | Signals, notifications | | letter | 3+ turns | 1+ gold | Player-to-player with drama | | freight | 10+ turns | Per weight | Physical bulk transport |

Camera Phone = Image Generation!

Your phone's camera can generate images via prompts:

text:
  from: "player"
  to: "narrator"
  body: "Take a photo of this treasure map"
  
  attachments:
    - type: image
      action: generate
      prompt: "A weathered treasure map showing the maze layout..."
      save_to: "player/photos/treasure-map.png"

Text Messages = Circuit Signals!

Texts can carry logistics signals:

# Room emits signal when low on fuel
room:
  signals:
    enabled: true
    emit:
      - signal: "low-fuel"
        when: "stacks.coal < 5"
        action:
          text:
            to: "player"
            body: "⚠️ Forge is running low on coal!"

No Bots Needed (But You Can Have Them!)

Default: Logistics uses postal (instant, free) Optional: Physical bots for gameplay/drama

logistic-container:
  transport:
    method: postal           # Default: instant via mail
    # OR
    method: bot              # Physical transport (slower, visible)
    bot_path: "characters/courier-kitten/"

When using bots:

  • Player sees the kitten carrying items
  • Can intercept, pet, or redirect
  • Adds gameplay and drama
  • But slower than postal!

The Complete Flow

1. REQUESTER: "I need 20 iron ore"
   │
   ▼
2. LOGISTICS ENGINE finds provider with iron ore
   │
   ▼
3. Generate POSTAL TRANSFER (internal, instant, free)
   │
   ├─── method: postal ────→ Instant delivery
   │                         No physical movement
   │                         Items "teleport"
   │
   └─── method: bot ───────→ Dispatch courier kitten
                             Physical movement
                             Player can see/interact
   │
   ▼
4. REQUESTER receives items
   │
   ▼
5. on_request_fulfilled fires

Why This Is Better

| Physical Bots | Postal Transport | |---------------|------------------| | Slow (travel time) | Instant | | Visible (might break immersion) | Invisible (just works) | | Can get stuck | Never fails | | Limited cargo | Unlimited | | Fun to watch | Efficient |

Use bots for drama. Use postal for efficiency.


Commands

| Command | Effect | |---------|--------| | CHECK PHONE | See notifications and quick access | | READ MAIL | Open inbox | | READ [letter] | Display letter | | COMPOSE | Start writing | | REPLY | Reply to current | | ATTACH [item] | Add attachment | | SEND | Send current message | | TEXT [character] | Quick text |


Mom as a Character

Mom isn't special infrastructure — she's just a character:

# characters/family/mom/CHARACTER.yml
character:
  id: mom
  name: "Mom"
  type: correspondent
  
  personality:
    warmth: 10
    worry: 7
    pride: 9
    
  voice:
    patterns:
      - "Dearest child"
      - "I'm so proud"
      - "Be careful out there"
      
  triggers:
    on_goal_complete: "Send congratulations letter"
    on_danger: "Send worried letter"
    on_long_silence: "Send 'are you okay?' letter"

Any character can have similar triggers. The bartender can send quests. Don Hopkins can send updates. Goals come from anyone!


Browser UI

See BROWSER-UI.md for the delightful mail interface.


Protocol Symbol

POSTAL-SYSTEM — Complete messaging with deterministic routing