`%gall`: Interfacing with a Client

Overview

Teaching: 60 min
Exercises: 30 min
Questions
  • How can I build a %gall app which operates on data?

Objectives
  • Produce an intermediate %gall app.

  • Understand how %gall interfaces with an external client.

Communications

https://urbit.org/docs/userspace/graph-store/sample-application-overview

We need to examine all of the ways a %gall app can communicate with the outside world. Recall that an agent has ten arms:

|_  =bowl:gall
++  on-init
++  on-save
++  on-load
++  on-arvo
++  on-peek
++  on-poke
++  on-watch
++  on-leave
++  on-agent
++  on-fail
--

Arvo alone interacts with several of these:

++  on-init
++  on-save
++  on-load
++  on-arvo

The ++on-agent and ++on-fail arms are called in certain circumstances (i.e. as an update to a subscription to another agent or cleanup after a %poke crash). We can leave them as boilerplate for now.

If you are exposing information, you can do so via a peek (++on-peek), a response to a poke (++on-poke), or a subscription (++on-watch). (++on-leave handles cleanup after a terminated subscription.) These are the main ways that the Urbit API protocol (formerly Airlock) interacts with an agent on a ship. We’ll focus on these.

++on-peek Scry

A scry represents a direct look into the agent state using the Nock .^ dotket operator.

Only local scries are permitted.

++on-poke Request

A poke initiates some kind of well-defined action by an agent. Typically this either triggers an event (such as charlie and bravo’s modification of hexes) or requests a data return of some kind.

Remote pokes are allowed (and common for single-instance requests).

++on-watch Subscription

A subscription is a data-reactive standing request for changes. For instance, one can watch a database agent for any changes to the database. Whenever a change occurs, the agent notifies all subscribers, who then act as they should in the event of a message being received (e.g. from a particular ship).

Remote subscriptions are in common use.

The agent

%charlie is yet another upgrade of %bravo which allows remote ships to poke each other peer-to-peer and push hex values to or pop hex values from each others’ hexes:

/app/charlie.hoon:

/-  charlie
/+  default-agent, dbug
|%
+$  versioned-state
  $%  state-0
  ==
::
+$  state-0
  $:  [%0 hexes=(list @ux)]
  ==
::
+$  card  card:agent:gall
::
--
%-  agent:dbug
=|  state-0
=*  state  -
^-  agent:gall
=<
|_  =bowl:gall
+*  this      .
    default   ~(. (default-agent this %|) bowl)
    main      ~(. +> bowl)
::
++  on-init
  ^-  (quip card _this)
  ~&  >  '%charlie initialized successfully'
  =.  state  [%0 *(list @ux)]
  `this
++  on-save   on-save:default
++  on-load   on-load:default
++  on-poke
  |=  [=mark =vase]
  ^-  (quip card _this)
  ?+    mark  (on-poke:default mark vase)
      %noun
    ?+    q.vase  (on-poke:default mark vase)
        %print-state
      ~&  >>  state
      ~&  >>>  bowl
      `this
      ::
        [%push-local @ux]
      ~&  >  "got poked from {<src.bowl>} with val: {<+.q.vase>}"
      =^  cards  state
      (handle-action:main ;;(action:charlie q.vase))
      [cards this]
      ::
        [%pop-local ~]
      ~&  >  "got poked from {<src.bowl>} with val: {<+.q.vase>}"
      =^  cards  state
      (handle-action:main ;;(action:charlie q.vase))
      [cards this]
    ==
    ::
      %charlie-action
    ~&  >  %charlie-action
    =^  cards  state
    (handle-action:main !<(action:charlie vase))
    [cards this]
  ==
++  on-arvo   on-arvo:default
++  on-watch  on-watch:default
++  on-leave  on-leave:default
++  on-peek
  |=  =path
  ^-  (unit (unit cage))
  ?+    path  (on-peek:default path)
      [%x %hexes ~]
    ``noun+!>(hexes)
  ==
++  on-agent  on-agent:default
++  on-fail   on-fail:default
--
|_  =bowl:gall
++  handle-action
  |=  =action:charlie
  ^-  (quip card _state)
  ?-    -.action
    ::
      %push-remote
    :_  state
    ~[[%pass /poke-wire %agent [target.action %charlie] %poke %noun !>([%push-local value.action])]]
    ::
      %push-local
    =.  hexes.state  (weld hexes.state ~[value.action])
    ~&  >>  hexes.state
    :_  state
    ~[[%give %fact ~[/hexes] [%atom !>(hexes.state)]]]
    ::
      %pop-remote
    :_  state
    ~[[%pass /poke-wire %agent [target.action %charlie] %poke %noun !>(~[%pop-local])]]
    ::
      %pop-local
    =.  hexes.state  (snip hexes.state)
    ~&  >>  hexes.state
    :_  state
    ~[[%give %fact ~[/hexes] [%atom !>(hexes.state)]]]
  ==
--

At this point, if you are running a fakezod then the fakezods must be able to see each other over the local network. Typically this means running two different fakezods on the same host machine. Alternatively, you can spin up a comet or moon and do this with your teammates. We have no filtering for agent permissions here. This will be critical for real-world deployments.

A %charlie agent needs to know how to do two things: receive a push (with data) and receive a pop (here, functionally, a delete rather than a return).

/sur/charlie.hoon

|%
+$  action
  $%  [%push-remote target=@p value=@ux]
      [%push-local value=@ux]
      [%pop-remote target=@p]
      [%pop-local ~]
  ==
--

/mar/charlie/action.hoon

/-  charlie
|_  =action:charlie
++  grab
  |%
  ++  noun  action:charlie
  --
++  grow
  |%
  ++  noun  action
  --
++  grad  %noun
--

The actions:

:charlie &charlie-action [%push-remote ~sampel-palnet 0xbeef]
:charlie &charlie-action [%push-local 0xbeef]
:charlie &charlie-action [%pop-remote ~sampel-palnet]
:charlie &charlie-action pop-local+~

Graph Store and Permissions on Mars (Optional)

Many Gall apps use Graph Store, a backend data storage format and database that both provides internally consistent data and external API communications endpoints.

To understand Graph Store, think in terms of the Urbit data permissions model:

  • A store is a local database.
  • A hook is a permissions broker for the database. They request and return information after negotiating access with remote agents.
  • A view is a data aggregator which parses JSON objects for external clients such as Landscape.

Graph Store handles data access perms at the hook level, not at the store level.

References

Key Points

  • A %gall app can talk to a user interface client.