Urbit Development Reference
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).
| Arm | Category | Returns | Purpose |
|---|---|---|---|
+on-init | Lifecycle | (quip card _this) | First-time agent initialization |
+on-save | Lifecycle | vase | Export state (for upgrades, suspend, debug) |
+on-load | Lifecycle | (quip card _this) | Import previously exported state |
+on-poke | Request | (quip card _this) | Handle one-off actions/commands |
+on-watch | Request | (quip card _this) | Handle subscription requests |
+on-leave | Request | (quip card _this) | Handle unsubscribe notifications |
+on-agent | Response | (quip card _this) | Handle acks and subscription updates from other agents |
+on-arvo | Response | (quip card _this) | Handle responses from kernel vanes |
+on-peek | Scry | (unit (unit cage)) | Handle local read-only requests |
+on-fail | Failure | (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 identitynow.bowl– current time for timestamps, timerseny.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:
- Subscribe to the result path first
- Poke
%spiderwith%spider-startmark
:: 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-donemark with result vase (success)%thread-failmark 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:
- Channel system – JSON API over SSE for interacting with Gall agents
- 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):
- Send Eyre a
%connecttask in+on-initor+on-poke - Receive
%boundgift in+on-arvoconfirming the binding - Handle incoming requests as pokes with
%handle-http-requestmark - Respond with
%http-response-headerand%http-response-datafacts
The /lib/server.hoon library reduces boilerplate for this pattern.
Serving Frontends
Two approaches:
- Glob: Static files (HTML/CSS/JS) bundled and served by the
%docketagent. Standard for most Urbit apps. - 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" },
});
Recommended Frontend Architecture
Based on patterns from official Urbit apps:
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.
Use
useEffectto 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]);
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
- Call
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
@dalevel (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
errorfield in%wakeis 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
--loomflag - The loom is both persistent state AND working memory for computation
bail: memeerror = 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-pokeblocks 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
- Shard by region: Different ships host different world regions. Players subscribe to their current region’s host.
- Separate concerns: Use multiple agents – one for world state, one for combat, one for chat, one for inventory.
- Bounded state: Implement log rotation, event archiving, old session cleanup.
- Efficient data structures: Use maps (
(map key value)) and sets ((set item)) rather than lists for lookups. - 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
%palsto 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
Versioned state: All serious apps use tagged union state types (
state-0,state-1, etc.) with migration logic in+on-load.Structure files: Types defined in
/sur/app-name.hoon, marks in/mar/app-name/, agent in/app/app-name.hoon.Subscription-based updates: Clients subscribe and receive deltas, not full state. Initial state is sent as a fact in
+on-watchwith empty path list~(targets only the new subscriber).Crash-based validation: Invalid pokes or unauthorized subscriptions are rejected by simply crashing in the handler. Gall converts the crash into a nack.
CQRS-like separation: Pokes modify state; scries and subscriptions read state. Subscriptions shouldn’t cause state changes.
Glob-based frontends: React apps bundled as globs, served by
%docket, communicating via@urbit/http-apichannels.
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
- App School: The Agent Core
- App School: Imports and Aliases
- App School: Subscriptions
- App School: Vanes
- App School Full-Stack: Eyre
- App School Full-Stack: React App Logic
- Gall Data Types
- Eyre Guide
- Engine Pattern
- Core Academy: Behn
- Core Academy: The Loom
- Runtime Reference (loom flags)