Urbit Docs
  • What is Urbit?
  • Get on Urbit
  • Build on Urbit
    • Contents
    • Environment Setup
    • Hoon School
      • 1. Hoon Syntax
      • 2. Azimuth (Urbit ID)
      • 3. Gates (Functions)
      • 4. Molds (Types)
      • 5. Cores
      • 6. Trees and Addressing
      • 7. Libraries
      • 8. Testing Code
      • 9. Text Processing I
      • 10. Cores and Doors
      • 11. Data Structures
      • 12. Type Checking
      • 13. Conditional Logic
      • 14. Subject-Oriented Programming
      • 15. Text Processing II
      • 16. Functional Programming
      • 17. Text Processing III
      • 18. Generic and Variant Cores
      • 19. Mathematics
    • App School I
      • 1. Arvo
      • 2. The Agent Core
      • 3. Imports and Aliases
      • 4. Lifecycle
      • 5. Cards
      • 6. Pokes
      • 7. Structures and Marks
      • 8. Subscriptions
      • 9. Vanes
      • 10. Scries
      • 11. Failure
      • 12. Next Steps
      • Appendix: Types
    • App School II (Full-Stack)
      • 1. Types
      • 2. Agent
      • 3. JSON
      • 4. Marks
      • 5. Eyre
      • 6. React app setup
      • 7. React app logic
      • 8. Desk and glob
      • 9. Summary
    • Core Academy
      • 1. Evaluating Nock
      • 2. Building Hoon
      • 3. The Core Stack
      • 4. Arvo I: The Main Sequence
      • 5. Arvo II: The Boot Sequence
      • 6. Vere I: u3 and the Serf
      • 7. Vere II: The Loom
      • 8. Vanes I: Behn, Dill, Kahn, Lick
      • 9. Vanes II: Ames
      • 10. Vanes III: Eyre, Iris
      • 11. Vanes IV: Clay
      • 12. Vanes V: Gall and Userspace
      • 13. Vanes VI: Khan, Lick
      • 14. Vanes VII: Jael, Azimuth
    • Runtime
      • U3
      • Conn.c Guide
      • How to Write a Jet
      • API Overview by Prefix
      • C in Urbit
      • Cryptography
      • Land of Nouns
    • Tools
      • Useful Links
      • JS Libraries
        • HTTP API
      • Docs App
        • File Format
        • Index File
        • Suggested Structure
    • Userspace
      • Command-Line App Tutorial
      • Remote Scry
      • Unit Tests
      • Software Distribution
        • Software Distribution Guide
        • Docket File
        • Glob
      • Examples
        • Building a CLI App
        • Debugging Wrapper
        • Host a Website
        • Serving a JS Game
        • Ship Monitoring
        • Styled Text
  • Urbit ID
    • What is Urbit ID?
    • Azimuth Data Flow
    • Life and Rift
    • Urbit HD Wallet
    • Advanced Azimuth Tools
    • Custom Roller Tutorial
    • Azimuth.eth Reference
    • Ecliptic.eth Reference
    • Layer 2
      • L2 Actions
      • L2 Rollers
      • L2 Roller HTTP RPC-API
      • L2 Transaction Format
  • Urbit OS
    • What is Urbit OS?
    • Base
      • Hood
      • Threads
        • Basics Tutorial
          • Bind
          • Fundamentals
          • Input
          • Output
          • Summary
        • HTTP API Guide
        • Spider API Reference
        • Strandio Reference
        • Examples
          • Child Thread
          • Fetch JSON
          • Gall
            • Poke Thread
            • Start Thread
            • Stop Thread
            • Take Facts
            • Take Result
          • Main-loop
          • Poke Agent
          • Scry
          • Take Fact
    • Kernel
      • Arvo
        • Cryptography
        • Move Trace
        • Scries
        • Subscriptions
      • Ames
        • Ames API Reference
        • Ames Cryptography
        • Ames Data Types
        • Ames Scry Reference
      • Behn
        • Behn API Reference
        • Behn Examples
        • Behn Scry Reference
      • Clay
        • Clay API Reference
        • Clay Architecture
        • Clay Data Types
        • Clay Examples
        • Clay Scry Reference
        • Filesystem Hierarchy
        • Marks
          • Mark Examples
          • Using Marks
          • Writing Marks
        • Using Clay
      • Dill
        • Dill API Reference
        • Dill Data Types
        • Dill Scry Reference
      • Eyre
        • EAuth
        • Eyre Data Types
        • Eyre External API
        • Eyre Internal API
        • Eyre Scry Reference
        • Low-Level Eyre Guide
        • Noun channels
      • Gall
        • Gall API Reference
        • Gall Data Types
        • Gall Scry Reference
      • Iris
        • Iris API Reference
        • Iris Data Types
        • Iris Example
      • Jael
        • Jael API Reference
        • Jael Data Types
        • Jael Examples
        • Jael Scry Reference
      • Khan
        • Khan API Reference
        • Khan Data Types
        • Khan Example
      • Lick
        • Lick API Reference
        • Lick Guide
        • Lick Examples
        • Lick Scry Reference
  • Hoon
    • Why Hoon?
    • Advanced Types
    • Arvo
    • Auras
    • Basic Types
    • Cheat Sheet
    • Cryptography
    • Examples
      • ABC Blocks
      • Competitive Programming
      • Emirp
      • Gleichniszahlenreihe
      • Islands
      • Luhn Number
      • Minimum Path Sum
      • Phone Letters
      • Restore IP
      • Rhonda Numbers
      • Roman Numerals
      • Solitaire Cipher
      • Water Towers
    • Generators
    • Hoon Errors
    • Hoon Style Guide
    • Implementing an Aura
    • Irregular forms
    • JSON
    • Limbs and wings
      • Limbs
      • Wings
    • Mips (Maps of Maps)
    • Parsing Text
    • Runes
      • | bar · Cores
      • $ buc · Structures
      • % cen · Calls
      • : col · Cells
      • . dot · Nock
      • / fas · Imports
      • ^ ket · Casts
      • + lus · Arms
      • ; mic · Make
      • ~ sig · Hints
      • = tis · Subject
      • ? wut · Conditionals
      • ! zap · Wild
      • Constants (Atoms and Strings)
      • --, == · Terminators
    • Sail (HTML)
    • Serialization
    • Sets
    • Standard Library
      • 1a: Basic Arithmetic
      • 1b: Tree Addressing
      • 1c: Molds and Mold-Builders
      • 2a: Unit Logic
      • 2b: List Logic
      • 2c: Bit Arithmetic
      • 2d: Bit Logic
      • 2e: Insecure Hashing
      • 2f: Noun Ordering
      • 2g: Unsigned Powers
      • 2h: Set Logic
      • 2i: Map Logic
      • 2j: Jar and Jug Logic
      • 2k: Queue Logic
      • 2l: Container from Container
      • 2m: Container from Noun
      • 2n: Functional Hacks
      • 2o: Normalizing Containers
      • 2p: Serialization
      • 2q: Molds and Mold-Builders
      • 3a: Modular and Signed Ints
      • 3b: Floating Point
      • 3c: Urbit Time
      • 3d: SHA Hash Family
      • 3e: AES encryption (Removed)
      • 3f: Scrambling
      • 3g: Molds and Mold-Builders
      • 4a: Exotic Bases
      • 4b: Text Processing
      • 4c: Tank Printer
      • 4d: Parsing (Tracing)
      • 4e: Parsing (Combinators)
      • 4f: Parsing (Rule-Builders)
      • 4g: Parsing (Outside Caller)
      • 4h: Parsing (ASCII Glyphs)
      • 4i: Parsing (Useful Idioms)
      • 4j: Parsing (Bases and Base Digits)
      • 4k: Atom Printing
      • 4l: Atom Parsing
      • 4m: Formatting Functions
      • 4n: Virtualization
      • 4o: Molds
      • 5a: Compiler Utilities
      • 5b: Macro Expansion
      • 5c: Compiler Backend & Prettyprinter
      • 5d: Parser
      • 5e: Molds and mold builders
      • 5f: Profiling support
    • Strings
    • The Engine Pattern
    • Udon (Markdown-esque)
    • Vases
    • Zuse
      • 2d(1-5): To JSON, Wains
      • 2d(6): From JSON
      • 2d(7): From JSON (unit)
      • 2e(2-3): Print & Parse JSON
      • 2m: Ordered Maps
  • Nock
    • What is Nock?
    • Decrement
    • Definition
    • Fast Hints and Jets
    • Implementations
    • Specification
  • User Manual
    • Contents
    • Running Urbit
      • Cloud Hosting
      • Home Servers
      • Runtime Reference
      • Self-hosting S3 Storage with MinIO
    • Urbit ID
      • Bridge Troubleshooting
      • Creating an Invite Pool
      • Get an Urbit ID
      • Guide to Factory Resets
      • HD Wallet (Master Ticket)
      • Layer 2 for planets
      • Layer 2 for stars
      • Proxies
      • Using Bridge
    • Urbit OS
      • Basics
      • Configuring S3 Storage
      • Dojo Tools
      • Filesystem
      • Shell
      • Ship Troubleshooting
      • Star and Galaxy Operations
      • Updates
Powered by GitBook

GitHub

  • Urbit ID
  • Urbit OS
  • Runtime

Resources

  • YouTube
  • Whitepaper
  • Awesome Urbit

Contact

  • X
  • Email
  • Gather
On this page
  • 1. Basic types
  • 2. Actions
  • 3. Updates
  • 4. State
  • Conclusion
  • Resources
Edit on GitHub
  1. Build on Urbit
  2. App School II (Full-Stack)

1. Types

The best place to start when building a new agent is its type definitions in its /sur structure file. The main things to think through are:

  1. What basic types of data does my agent deal with?

  2. What actions/commands does my agent need to handle?

  3. What updates/events will my agent need to send out to subscribers?

  4. What does my agent need to store in its state?

Let's look at each of these questions in turn, and put together our agent's /sur file, which we'll call /sur/journal.hoon.

1. Basic types

Our journal entries will just be plain text, so a simple @t will work fine to store their contents. Entries will be organized by date, so we'll also need to decide a format for that.

One option would be to use an @da, and then use the date functions included in the @urbit/api NPM package on the front-end to convert them to ordinary Javascript Date objects. In this case, to keep it simple, we'll just use the number of milliseconds since the Unix Epoch as an $atom, since it's natively supported by the Javascript Date object.

The structure for a journal entry can therefore be:

+$  id  @
+$  txt  @t
+$  entry  [=id =txt]

2. Actions

Now that we know what a journal entry looks like, we can think about what kind of actions/commands our agent will handle in its +on-poke arm. For our journal app, there are three basic things we might do:

  1. Add a new journal entry.

  2. Edit an existing journal entry.

  3. Delete an existing journal entry.

We can create a tagged union structure for these actions, like so:

+$  action
  $%  [%add =id =txt]
      [%edit =id =txt]
      [%del =id]
  ==

3. Updates

Updates are a little more complicated than our actions. Firstly, our front-end needs to be able to retrieve an initial list of journal entries to display. Once it has that, it also needs to be notified of any changes. For example, if a new entry is added, it needs to know so it can add it to the list it's displaying. If an entry gets deleted, it needs to remove it from the list. Etc.

The simplest approach to the initial entries is just a (list entry). Then, for the subsequent updates, we could send out the $action. Since an $action is a tagged union, it's simpler to have all updates be a tagged union, so when we get to doing mark conversions we can just switch on the head tag. Therefore, we can define an $update structure like so:

+$  update
  $%  action
      [%jrnl list=(list entry)]
  ==

There's one drawback to this structure. Suppose either an agent on a remote ship or an instance of the front-end client is subscribed for updates, and the network connection is disrupted. In the remote ship case, Gall will only allow so many undelivered messages to accumulate in Ames before it automatically kicks the unresponsive subscriber. In the front-end case, the subscription will also be ended if enough unacknowledged messages accumulate, and additionally the client may sometimes need to establish an entirely new connection with the ship, discarding existing subscriptions. When this happens, the remote ship or web client has no way to know how many (if any) updates they've missed.

The only way to resynchronize their state with ours is to discard their existing state, refetch the entire initial state once again, and then resubscribe for updates. This might be fine if the state of our agent is small, but it becomes a problem if it's very large. For example, if our agent holds tens of thousands of chat messages, having to resend them all every time anyone has connectivity issues is quite inefficient.

One solution to this is to keep an "update log". Each update can be tagged with the time it occurred, and stored in our agent's state, separately to the entries. If an agent or web client needs to resynchronize with our agent, it can just request all updates since the last one it received. Our agent is local-only and doesn't have a huge state so it might not be strictly necessary, but we'll use it to demonstrate the approach.

We can define a logged update like so, where the @ is the update timestamp in milliseconds since the Unix Epoch:

+$  logged  (pair @ action)
+$  update
  %+  pair  @
  $%  action
      [%jrnl list=(list entry)]
      [%logs list=(list logged)]
  ==

4. State

We need to store two things in our state: the journal entries and the update log. We could just use a couple of +maps like so:

+$  journal  (map id txt)
+$  log  (map @ action)

Ordinary +maps are fine if we just want to access one value at a time, but we want to be able to:

  1. Retrieve only some of the journal entries at a time, so we can have "lazy loading" in the front-end, loading more entries each time the user scrolls to the bottom of the list.

  2. Retrieve only logged updates newer than a certain time, in the case where the subscription is interrupted due to connectivity issues.

  3. Retrieve journal entries between two dates.

Maps are ordered by the hash of their key, so if we convert them to a list they'll come out in seemingly random order. That means we'd have to convert the map to a list, sort the list, and then iterate over it again to pull out the items we want. We could alternatively store things in a list directly, but retrieving or modifying arbitrary items would be less efficient.

To solve this, rather than using a +map or a +list, we can use an ordered map. The mold builder for an ordered map is a +mop, and it's included in the zuse.hoon utility library rather than the standard library.

A +mop is defined similarly to a +map, but it takes an extra argument in the following manner:

((mop key-mold val-mold) comparator-gate)

The gate is a binary gate which takes two keys and produces a ?. The comparator is used to decide how to order the items in the mop. In our case, we'll create a $journal and $log +mop like so:

+$  journal  ((mop id txt) gth)
+$  log  ((mop @ action) lth)

The entries in $journal are arranged in ascending time order using +gth, so the right-most item is the newest. The $log +mop contains the update log, and is arranged in descending time order, so the right-most item is the oldest.

We'll look at how to use ordered maps later when we get to writing the agent itself.

Conclusion

When we put each of these parts together, we have our complete /sur/journal.hoon file:

/sur/journal.hoon
|%
:: Basic types of the data we're dealing with
::
+$  id  @
+$  txt  @t
+$  entry  [=id =txt]
:: Poke actions
::
+$  action
  $%  [%add =id =txt]
      [%edit =id =txt]
      [%del =id]
  ==
:: Types for updates to subscribers or returned via scries
::
+$  logged  (pair @ action)
+$  update
  %+  pair  @
  $%  action
      [%jrnl list=(list entry)]
      [%logs list=(list logged)]
  ==
:: Types for our agent's state
::
+$  journal  ((mop id txt) gth)
+$  log  ((mop @ action) lth)
--

Resources

  • App School I /sur section - This section of App School covers writing a /sur structure library for an agent.

  • Ordered map functions in /sys/zuse.hoon - This section of zuse.hoon contains all the functions for working with +mops, and is well commented.

PreviousApp School II (Full-Stack)Next2. Agent

Last updated 1 day ago