MUD on Urbit

Urbit Development Reference

research Doc 14

A practical reference covering Gall agents, Eyre, Behn, frontend integration, inter-agent communication, performance characteristics, and existing app architectures. Written to inform the design and implementation of a MUD on Urbit.


1. Gall Agent Architecture

The 10-Arm Structure

A Gall agent is a door (a core with a sample) with exactly ten arms. The sample is always bowl:gall. Each arm handles a specific category of event. Eight of the ten arms return (quip card _this) – a list of effects (cards) paired with a new version of the agent core (potentially with modified state).

ArmCategoryReturnsPurpose
+on-initLifecycle(quip card _this)First-time agent initialization
+on-saveLifecyclevaseExport state (for upgrades, suspend, debug)
+on-loadLifecycle(quip card _this)Import previously exported state
+on-pokeRequest(quip card _this)Handle one-off actions/commands
+on-watchRequest(quip card _this)Handle subscription requests
+on-leaveRequest(quip card _this)Handle unsubscribe notifications
+on-agentResponse(quip card _this)Handle acks and subscription updates from other agents
+on-arvoResponse(quip card _this)Handle responses from kernel vanes
+on-peekScry(unit (unit cage))Handle local read-only requests
+on-failFailure(quip card _this)Handle crash reports from Gall

State Management

Agent state lives in the core’s payload. State is defined before the door using =+ or in a |% core. The standard pattern uses versioned state types for upgradability:

|%
+$  versioned-state  $%(state-0 state-1)
+$  state-0  [%0 data=@ud]
+$  state-1  [%1 data=@ud name=@t]
+$  card  card:agent:gall
--

Upgrade cycle: When an agent is upgraded, Gall calls +on-save (which packs state into a vase), builds the new agent, then passes the vase to +on-load of the new version. The +on-load arm inspects the state version tag and migrates as needed.

Atomic transactions: Every event is processed atomically. If an arm crashes, the state rolls back to pre-event. Gall virtualizes agents so crashes don’t take down the kernel – they produce appropriate error responses instead.

The Bowl

Gall repopulates the bowl for every event. All arms have access to it.

+$  bowl
  $:  our=ship       :: host ship (@p)
      src=ship       :: ship that caused this event
      dap=term       :: this agent's name
      sap=path       :: provenance
  ::
      wex=boat       :: outgoing subscriptions (map [wire ship term] [acked=? path])
      sup=bitt       :: incoming subscriptions (map duct [ship path])
      sky=(map path fans)  :: remote scry bindings
  ::
      act=@ud        :: total moves processed
      eny=@uvJ       :: 512-bit entropy, fresh per event
      now=@da        :: current timestamp
      byk=beak       :: [ship desk case] -- where agent was loaded from
  ==

Key fields for a MUD:

  • src.bowl – who is poking/subscribing (permission checks)
  • our.bowl – this ship’s identity
  • now.bowl – current time for timestamps, timers
  • eny.bowl – entropy for random number generation (combat rolls, etc.)
  • wex.bowl / sup.bowl – track who’s connected

Cards: Notes and Gifts

A card is (wind note gift) which resolves to either:

  • [%pass path note] – send a message outward (expects responses on the wire/path)
  • [%give gift] – send a response/update to subscribers or ack a request

Note types (outbound):

+$  note
  $%  [%agent [=ship name=term] =task]   :: to another agent
      [%arvo note-arvo]                   :: to a kernel vane
      [%pyre =tang]                       :: crash report
      [%grow =spur =page]                 :: remote scry binding
      [%tomb =case =spur]                 :: remote scry tombstone
      [%cull =case =spur]                 :: remote scry cull
  ==

Task types (agent-to-agent):

+$  task
  $%  [%watch =path]         :: subscribe
      [%watch-as =mark =path] :: subscribe with mark conversion
      [%leave ~]              :: unsubscribe
      [%poke =cage]           :: one-off message
      [%poke-as =mark =cage]  :: poke with mark conversion
  ==

Gift types (outbound responses):

+$  gift
  $%  [%fact paths=(list path) =cage]     :: update to subscribers
      [%kick paths=(list path) ship=(unit ship)]  :: remove subscriber
      [%watch-ack p=(unit tang)]          :: ack/nack subscription
      [%poke-ack p=(unit tang)]           :: ack/nack poke
  ==

Sign types (inbound responses, arrive in +on-agent):

+$  sign
  $%  [%poke-ack p=(unit tang)]    :: poke was accepted (~ = success)
      [%watch-ack p=(unit tang)]   :: subscription accepted/rejected
      [%fact =cage]                :: subscription update
      [%kick ~]                    :: kicked from subscription
  ==

Helper Libraries

default-agent (/lib/default-agent.hoon)

Provides sane defaults for all ten arms. For unused arms, delegate to it instead of writing boilerplate. It either crashes with a message (for arms that shouldn’t be called) or succeeds doing nothing.

+*  this  .
    def   ~(. (default-agent this %.n) bowl)

Then in any arm you don’t need: ++ on-leave on-leave:def

dbug (/lib/dbug.hoon)

Wraps your agent to enable runtime state inspection from the dojo. Transparent to normal operation.

%-  agent:dbug
^-  agent:gall
|_  =bowl:gall
  ...
--

Dojo commands:

  • :your-agent +dbug – print full state
  • :your-agent +dbug %bowl – print the bowl
  • :your-agent +dbug [%state %some-face] – evaluate expression against state
  • :your-agent +dbug [%incoming %ship ~some-ship] – query incoming subs
  • :your-agent +dbug [%outgoing %path /some/path] – query outgoing subs

verb (verbosity)

Gall supports a %spew task to enable verbose logging, and %sift to filter it to specific agents. Currently only one verb flag exists: %odd (prints unusual error cases).

> |pass [%g %spew ~[%odd]]
> |pass [%g %sift ~[%your-agent]]

Standard Agent Boilerplate

/-  sur=your-app                    :: import /sur/your-app.hoon
/+  default-agent, dbug             :: import libraries
|%
+$  versioned-state  $%(state-0)
+$  state-0  [%0 players=(map @p player)]
+$  card  card:agent:gall
--
%-  agent:dbug
=|  state-0                         :: initialize default state
=*  state  -                        :: alias state
^-  agent:gall
|_  =bowl:gall
+*  this  .
    def   ~(. (default-agent this %.n) bowl)
++  on-init
  ^-  (quip card _this)
  `this
++  on-save   !>(state)
++  on-load
  |=  old-state=vase
  ^-  (quip card _this)
  =/  old  !<(versioned-state old-state)
  ?-  -.old
    %0  `this(state old)
  ==
++  on-poke   ...
++  on-watch  ...
++  on-leave  on-leave:def
++  on-peek   ...
++  on-agent  ...
++  on-arvo   ...
++  on-fail   on-fail:def
--

The Engine Pattern (Nested Core / ++abet)

For complex agents, the engine pattern (also called the nested core or ++abet pattern) encapsulates logic into helper cores that accumulate cards and state changes, then finalize with ++abet.

Why: Without it, you end up with deeply nested =^ (tisket) chains for sequencing multiple card emissions. The engine pattern gives you a local mutable context.

Standard arms:

  • ++abet – finalize: return [(flop cards) state]
  • ++abed – initialize: set up the inner core’s sample from outer state
  • ++emit – append a single card: |=(=card cor(cards [card cards]))
  • ++emil – append multiple cards: |=(caz=(list card) cor(cards (welp (flop caz) cards)))

Example from %groups:

|_  [=bowl:gall cards=(list card)]
++  abet  [(flop cards) state]
++  emit  |=(=card cor(cards [card cards]))
++  give  |=(=gift:agent:gall (emit %give gift))
--

Nested engine (e.g., chat within groups):

++  ca-core
  |_  [=flag:c =chat:c gone=_|]
  ++  ca-abet
    %_  cor
      chats
        ?:  gone  (~(del by chats) flag)
        (~(put by chats) flag chat)
    ==
  ++  ca-abed
    |=  f=flag:c
    ca-core(flag f, chat (~(got by chats) f))
  --

Naming convention: Two-letter prefixes matching the core (ca- for chat, di- for DM, etc.).

MUD relevance: This pattern is ideal for room management, combat resolution, NPC behavior, and other subsystems that need to emit multiple cards while transforming state.


2. Inter-Agent Communication

Same-Ship Communication

Agents on the same ship communicate via pokes and subscriptions, exactly the same card format as cross-ship:

:: Poke another local agent
[%pass /wire %agent [our.bowl %other-agent] %poke %mark-name !>(data)]

:: Subscribe to a local agent
[%pass /wire %agent [our.bowl %other-agent] %watch /some/path]

Responses arrive in +on-agent on the specified wire.

Cross-Ship Communication (Ames)

Ames is Urbit’s encrypted peer-to-peer protocol. Cross-ship communication uses identical card syntax – just change the ship:

:: Poke a remote agent
[%pass /wire %agent [~other-ship %agent-name] %poke %mark !>(data)]

:: Subscribe to a remote agent
[%pass /wire %agent [~other-ship %agent-name] %watch /updates]

Guarantees:

  • Messages within a flow are processed in order
  • Messages are delivered exactly once
  • All traffic is encrypted using keys from Jael (PKI vane)
  • Ames handles retransmission automatically

MUD relevance: Players on different ships can interact via cross-ship pokes (commands) and subscriptions (room updates). The host ship runs the authoritative game state; player ships subscribe for updates.

Wire Management

Wires tag outgoing messages so you can route responses in +on-agent:

++  on-agent
  |=  [=wire =sign:agent:gall]
  ^-  (quip card _this)
  ?+  wire  (on-agent:def wire sign)
      [%game-updates ~]
    ?+  -.sign  (on-agent:def wire sign)
        %fact
      :: handle game update
        %kick
      :: resubscribe
    ==
  ==

Best practice: Use descriptive, hierarchical wires. Include identifying info when needed:

  • /room-updates/~ship-name
  • /combat/session-id
  • /timer/heartbeat

Unique wires allow multiple subscriptions to the same agent/path combination.

Threads (Spider) for Async Operations

Threads handle complex sequences of IO that would be awkward in a Gall agent’s synchronous event model. They’re managed by the %spider agent and live in /ted/.

Starting a thread from a Gall agent:

  1. Subscribe to the result path first
  2. Poke %spider with %spider-start mark
:: Subscribe for result
[%pass /thread-result %agent [our.bowl %spider] %watch /thread-result/[tid]]

:: Start the thread
[%pass /start-thread %agent [our.bowl %spider] %poke %spider-start !>(start-args)]

Results arrive as:

  • %thread-done mark with result vase (success)
  • %thread-fail mark with error and traceback (failure)

Strands are the composable building blocks of threads, glued together with the ;< operator for async pipelines.

MUD relevance: Useful for operations like fetching external data, complex multi-step actions (e.g., resolving a trade between ships), or any IO sequence that shouldn’t block the main agent loop.


3. Eyre (HTTP Server Vane)

Overview

Eyre is Urbit’s HTTP server. It provides two main interfaces for external clients:

  1. Channel system – JSON API over SSE for interacting with Gall agents
  2. Scry interface – Read-only GET requests for data retrieval

Plus the ability for agents to handle raw HTTP requests directly.

Authentication

Clients authenticate by POSTing the ship’s web login code:

curl -i localhost:8080/~/login -X POST -d "password=lidlut-tabwed-pillex-ridrup"

Response includes a session cookie:

set-cookie: urbauth-~zod=0v4.ilskp...; Path=/; Max-Age=604800

This cookie must be included in all subsequent requests. Frontends served directly from the ship (via %docket) have authentication handled automatically.

The login code is obtained by running +code in the dojo.

Channel System

The channel system is the primary way web clients interact with Gall agents. It provides a JSON interface mapping to the native poke/subscription system.

Creating a channel: Send an HTTP PUT with a unique channel ID:

curl --header "Content-Type: application/json" \
  --cookie "urbauth-~zod=COOKIE" \
  --request PUT \
  --data '[{"id":1,"action":"poke","ship":"zod","app":"hood","mark":"helm-hi","json":"hello"}]' \
  http://localhost:8080/~/channel/my-channel-1234

Receiving events: Open an SSE stream with GET to the same URL:

curl --cookie "urbauth-~zod=COOKIE" \
  http://localhost:8080/~/channel/my-channel-1234

Key behaviors:

  • A single channel handles multiple concurrent subscriptions to different agents/paths
  • Different agents can be poked through one channel
  • Channels timeout after 12 hours of inactivity
  • Subscriptions become clogged at 50 unacknowledged facts (channel closes)
  • All events must be acknowledged; acking event N implicitly acks all events < N

JSON Action Formats

All actions are sent as a JSON array in the PUT body.

Poke:

{"id": 1, "action": "poke", "ship": "zod", "app": "app-name", "mark": "mark-name", "json": {...}}

Subscribe:

{"id": 2, "action": "subscribe", "ship": "zod", "app": "app-name", "path": "/updates"}

Acknowledge:

{"id": 3, "action": "ack", "event-id": 2}

Unsubscribe:

{"id": 4, "action": "unsubscribe", "subscription": 2}

Delete channel:

{"id": 5, "action": "delete"}

SSE Event Stream Format

Events arrive as standard Server-Sent Events:

id: 0
data: {"ok":"ok","id":1,"response":"poke"}

id: 1
data: {"ok":"ok","id":2,"response":"subscribe"}

id: 2
data: {"json":{"room":"tavern","text":"A dragon appears!"},"id":2,"response":"diff"}

id: 3
data: {"id":2,"response":"quit"}

Response types:

  • "poke" – poke acknowledgment ("ok" or "err")
  • "subscribe" – subscription acknowledgment
  • "diff" – subscription update (a fact)
  • "quit" – subscription closed (kicked)

Scry Endpoint

Read-only data retrieval via GET:

curl -i --cookie "urbauth-~zod=COOKIE" \
  http://localhost:8080/~/scry/agent-name/path.json

URL format: /~/scry/{agent}/{path}.{mark}

The mark must have a +mime arm in its +grow core. Returns HTTP 200 with data or 404 on failure.

Direct HTTP Handling in Agents

Agents can bind URL paths and handle HTTP directly (useful for custom APIs):

  1. Send Eyre a %connect task in +on-init or +on-poke
  2. Receive %bound gift in +on-arvo confirming the binding
  3. Handle incoming requests as pokes with %handle-http-request mark
  4. Respond with %http-response-header and %http-response-data facts

The /lib/server.hoon library reduces boilerplate for this pattern.

Serving Frontends

Two approaches:

  1. Glob: Static files (HTML/CSS/JS) bundled and served by the %docket agent. Standard for most Urbit apps.
  2. Direct: Agent handles HTTP requests and dynamically generates responses.

MUD relevance: The channel system with SSE is perfect for pushing game updates (room descriptions, combat results, chat) to a web client. The 50-fact clog limit means the client must ack regularly – important for real-time games. For a terminal-style MUD client, direct HTTP handling might be simpler.


4. Frontend Integration

@urbit/http-api

The official JS/TS library for communicating with Urbit ships from browser or Node.js. Most methods are async and return Promises.

Installation:

npm install @urbit/http-api

CORS: Ships need CORS approval for external origins:

> +cors-registry
> |cors-approve 'http://localhost:3000'

Frontends served directly from the ship don’t need CORS configuration.

Core Methods

Poke

await window.urbit.poke({
  app: "mud-engine",
  mark: "mud-action",
  json: { move: { direction: "north" } },
  onSuccess: () => console.log("command accepted"),
  onError: (err) => console.error("command rejected", err),
});

Subscribe

window.urbit.subscribe({
  app: "mud-engine",
  path: "/room-updates",
  event: (update) => {
    // handle incoming game events
    renderRoom(update);
  },
  err: () => console.error("subscription rejected"),
  quit: () => {
    // kicked -- resubscribe
    resubscribe();
  },
});

Scry

const roomData = await window.urbit.scry({
  app: "mud-engine",
  path: "/room/tavern",
});

Scries return a Promise. They don’t modify state – pure reads. Only JSON results are supported through the library (direct GET requests to /~/scry/ support other marks).

Thread

await window.urbit.thread({
  inputMark: "mud-thread-input",
  outputMark: "mud-thread-output",
  threadName: "resolve-combat",
  body: { attacker: "~zod", defender: "~bus" },
});

Based on patterns from official Urbit apps:

  1. State updates come from subscriptions, not poke responses. When you poke (e.g., “move north”), don’t update UI on poke success. Wait for the subscription fact that confirms the state change. This ensures consistency across multiple clients.

  2. Use useEffect to process subscription events:

const [subEvent, setSubEvent] = useState(null);

// In init:
urbit.subscribe({ app: "mud-engine", path: "/updates", event: setSubEvent });

// Process events:
useEffect(() => {
  if (!subEvent) return;
  switch (subEvent.type) {
    case "room-update": updateRoom(subEvent.data); break;
    case "combat-event": renderCombat(subEvent.data); break;
    case "message": appendMessage(subEvent.data); break;
  }
}, [subEvent]);
  1. Connection management: The Urbit class auto-retries on disconnect (up to 3 times), then fires onError. Reconnection pattern:

    • Call urbit.reset() to close connections and generate new channel ID
    • Scry for missed updates: /updates/since/[timestamp]
    • Replay missed events
    • Reestablish subscriptions
  2. Initial state via scry, ongoing updates via subscription. On app load, scry for current state, then subscribe for deltas.

WebSocket vs SSE

Eyre uses SSE (Server-Sent Events) for server-to-client push, not WebSocket. The channel system is:

  • Client -> Server: HTTP PUT requests (JSON actions)
  • Server -> Client: SSE stream (EventSource)

This is a half-duplex design. For a MUD, this means:

  • Player commands: PUT requests (pokes)
  • Game output: SSE stream (subscription facts)

SSE is well-supported in browsers via EventSource and is what @urbit/http-api uses internally.


5. Behn (Timer Vane)

Overview

Behn manages timers in a priority queue. Agents request timers; Behn fires them via Unix.

Setting a Timer

From a Gall agent, emit a card with a %wait task:

[%pass /some/wire %arvo %b %wait (add ~m1 now.bowl)]

This sets a timer to fire 1 minute from now. The wire /some/wire is used to route the response.

Receiving Timer Fires

Timer fires arrive in +on-arvo:

++  on-arvo
  |=  [=wire =sign-arvo]
  ^-  (quip card _this)
  ?+  wire  (on-arvo:def wire sign-arvo)
      [%heartbeat ~]
    ?+  +<.sign-arvo  (on-arvo:def wire sign-arvo)
        %wake
      ?~  error.sign-arvo
        :: timer fired successfully -- do work, then re-arm
        =/  cards  (process-tick)
        :_  this
        :_  cards
        [%pass /heartbeat %arvo %b %wait (add ~s5 now.bowl)]
      :: timer error
      `this
    ==
  ==

Cancelling a Timer

Use %rest with the exact same @da that was passed to %wait:

[%pass /some/wire %arvo %b %rest target-time]

You must remember the exact time you set to cancel it.

Recurring Timer Pattern (Re-arm)

Behn has no built-in recurring timer. The standard pattern is re-arm on fire: when a %wake arrives, process the event, then immediately set a new %wait.

:: In on-init or on-poke, start the cycle:
:_  this
:~  [%pass /heartbeat %arvo %b %wait (add ~s5 now.bowl)]
==

:: In on-arvo, when the timer fires:
:: 1. Do periodic work (update NPC positions, regen health, etc.)
:: 2. Re-arm the timer
:_  this
:~  [%pass /heartbeat %arvo %b %wait (add ~s5 now.bowl)]
==

Resolution and Reliability

  • Timer resolution is at the @da level (sub-second precision available)
  • Timers are persistent – they survive ship restarts
  • If a ship is offline when a timer should fire, it fires immediately upon restart
  • The error field in %wake is non-null if something went wrong

MUD Relevance

Behn is essential for:

  • Game tick loop: Regular heartbeat for NPC AI, regen, weather, day/night cycles
  • Combat rounds: Timed combat resolution
  • Cooldown timers: Spell cooldowns, ability timers
  • Session timeouts: Idle player detection
  • Scheduled events: World events, respawn timers

A typical MUD game loop would use a 1-5 second heartbeat timer that processes all periodic game logic on each tick.


6. Performance and Scaling

Memory (The Loom)

  • Default loom size: 2 GB (2^31)
  • Configurable up to 8 GB (2^33) via --loom flag
  • The loom is both persistent state AND working memory for computation
  • bail: meme error = out of memory
# Start with larger loom
urbit --loom 32 your-ship    # 4 GB
urbit --loom 33 your-ship    # 8 GB

Warning: If persistent state grows beyond 2 GB, you can never go back to default loom size without reducing it.

Maintenance tools:

  • |pack – defragment snapshot (fast, mild compaction)
  • |meld – deduplicate snapshot (slower, can reduce size by up to 50%)

Event Processing

  • Every Gall agent event is processed synchronously and atomically
  • Events are single-threaded – one event at a time across the entire ship
  • No parallel execution within a ship
  • Event log grows indefinitely (though snapshots allow trimming)

Implications for a MUD:

  • Keep event handlers fast. A slow +on-poke blocks everything on the ship.
  • Avoid heavy computation in the main event loop. Use threads for complex operations.
  • State size matters – large maps and lists slow down operations due to tree traversal in Hoon’s persistent data structures.

Practical Throughput

  • Urbit improved roughly 10x in recent years for most operations
  • Boot times, local compute, and network latency all improved
  • Jets (C implementations of hot Hoon paths) are critical for performance
  • Deterministic execution means no caching shortcuts – same computation, same cost every time

State Size Guidelines

For a MUD agent:

  • Hundreds of rooms with descriptions: fine (tens of KB)
  • Thousands of items/NPCs: fine with proper data structures
  • Chat/message history: should be bounded or archived
  • Player state for hundreds of concurrent players: feasible
  • Media/binary data: avoid storing in agent state; use Clay or external storage

Scaling Strategies

  1. Shard by region: Different ships host different world regions. Players subscribe to their current region’s host.
  2. Separate concerns: Use multiple agents – one for world state, one for combat, one for chat, one for inventory.
  3. Bounded state: Implement log rotation, event archiving, old session cleanup.
  4. Efficient data structures: Use maps ((map key value)) and sets ((set item)) rather than lists for lookups.
  5. Minimize subscription fan-out: Send targeted updates, not full state dumps.

7. Existing App Architectures

%groups (Tlon)

The flagship Urbit social app. Architecture patterns relevant to a MUD:

Multi-agent design: Groups uses several cooperating agents (groups, chat, diary, heap) rather than one monolithic agent.

Subscription model: Remote ships subscribe using group IDs as paths. The host’s +on-watch checks if the subscriber is whitelisted for the requested group, sends initial state, then continues sending delta updates.

Engine pattern: Groups uses the nested core / ++abet pattern extensively. Chat operations go through ++ca-core with ++ca-abed (initialize for a specific chat) and ++ca-abet (finalize changes back to the chat map). DMs use ++di-core. This keeps agent arms clean.

State management: Versioned state with migration in +on-load. Heavy use of maps keyed by flags (ship + name pairs).

%pals (~paldev)

A social graph / friend list agent. Key patterns:

  • Simple state: a set of ships (friends/contacts)
  • Other apps can subscribe to %pals to discover the user’s social connections
  • Demonstrates the “utility agent” pattern – small, focused agents that other apps compose with
  • MUD relevance: a similar pattern could be used for a “party” or “friends” system

Urbit Chess

A fully decentralized, peer-to-peer chess game. Architecture:

  • Gall agent backend in Hoon with React/TypeScript frontend
  • Cross-ship multiplayer via Urbit’s native networking
  • Game state managed in the agent; moves sent as pokes between ships
  • Frontend built with Webpack
  • Testing uses multiple fake ships that communicate with each other

Patterns relevant to a MUD:

  • Turn-based game state in Gall agent
  • Cross-ship game actions via pokes
  • React frontend communicating via @urbit/http-api
  • Fake ship testing infrastructure for multiplayer scenarios

Ship Monitoring (%ahoy)

A ship monitoring agent that demonstrates Behn timer patterns:

  • Uses recurring Behn timers to periodically check on other ships
  • Demonstrates the re-arm timer pattern in practice
  • Clean wire management for timer responses

Common Patterns Across Apps

  1. Versioned state: All serious apps use tagged union state types (state-0, state-1, etc.) with migration logic in +on-load.

  2. Structure files: Types defined in /sur/app-name.hoon, marks in /mar/app-name/, agent in /app/app-name.hoon.

  3. Subscription-based updates: Clients subscribe and receive deltas, not full state. Initial state is sent as a fact in +on-watch with empty path list ~ (targets only the new subscriber).

  4. Crash-based validation: Invalid pokes or unauthorized subscriptions are rejected by simply crashing in the handler. Gall converts the crash into a nack.

  5. CQRS-like separation: Pokes modify state; scries and subscriptions read state. Subscriptions shouldn’t cause state changes.

  6. Glob-based frontends: React apps bundled as globs, served by %docket, communicating via @urbit/http-api channels.


Quick Reference: Card Cookbook

Common cards you’ll emit from a MUD agent:

:: Poke another agent
[%pass /wire %agent [ship %agent-name] %poke %mark !>(data)]

:: Subscribe to another agent
[%pass /wire %agent [ship %agent-name] %watch /path]

:: Unsubscribe
[%pass /wire %agent [ship %agent-name] %leave ~]

:: Send update to all subscribers on a path
[%give %fact ~[/path] %mark !>(data)]

:: Send update to just the new subscriber (in on-watch only)
[%give %fact ~ %mark !>(data)]

:: Kick a subscriber
[%give %kick ~[/path] (some ~ship)]

:: Kick all subscribers on a path
[%give %kick ~[/path] ~]

:: Set a Behn timer
[%pass /timer-wire %arvo %b %wait (add ~s5 now.bowl)]

:: Cancel a Behn timer
[%pass /timer-wire %arvo %b %rest exact-time]

:: Bind URL path in Eyre
[%pass /eyre-bind %arvo %e %connect [~ /app-path] dap.bowl]

Sources

Official Documentation

Libraries and Packages

Example Apps

Community Resources