summaryrefslogtreecommitdiff
path: root/docs/core-academy/ca07.md
diff options
context:
space:
mode:
authorpolwex <polwex@sortug.com>2025-10-06 12:31:39 +0700
committerpolwex <polwex@sortug.com>2025-10-06 12:31:39 +0700
commit4a6067863d415e0334b4b61254fab2bd879a6964 (patch)
treecbeadf65ec17ff6bb28be3cb39307ef38a598b4b /docs/core-academy/ca07.md
parentf1de939b8cc592a8d56e62ce36902740a88a7e01 (diff)
brass!!
Diffstat (limited to 'docs/core-academy/ca07.md')
-rw-r--r--docs/core-academy/ca07.md567
1 files changed, 567 insertions, 0 deletions
diff --git a/docs/core-academy/ca07.md b/docs/core-academy/ca07.md
new file mode 100644
index 0000000..0c5644f
--- /dev/null
+++ b/docs/core-academy/ca07.md
@@ -0,0 +1,567 @@
+---
+description: "Core Academy lesson on Arvo vanes covering the Behn timer vane, Dill terminal driver vane, Khan thread runner vane, Lick IPC vane."
+layout:
+ title:
+ visible: true
+ description:
+ visible: false
+ tableOfContents:
+ visible: true
+ outline:
+ visible: true
+ pagination:
+ visible: true
+---
+
+# 8. Vanes I: Behn, Dill, Kahn, Lick
+
+*In this lesson we'll look at Arvo's timer vane, Behn, and Dill, the terminal driver vane. This includes discussion of the Dojo terminal, and the userspace/kernelspace interfaces `%helm` and `%hood`.*
+
+A **vane** is an Arvo kernel module that performs essential system operations. The vanes are:
+
+- Ames, the peer-to-peer networking vane.
+- Behn, the timer vane.
+- Clay, the filesystem, revision-control and build system vane.
+- Dill, the terminal-driver vane.
+- Eyre, the HTTP vane.
+- Gall, the application vane.
+- Iris, the server HTTP vane.
+- Jael, the security vane.
+- Khan, the control vane.
+- Lick, the interprocess communication (IPC) vane.
+
+> As described above, we use Arvo proper to route and control the flow of `move`s. However, Arvo proper is rarely directly responsible for processing the event data that directly causes the desired outcome of a `move`. This event data is contained within a `card`. Instead, Arvo proper passes the `card` off to one of its vanes, which each present an interface to clients for a particular well-defined, stable, and general-purpose piece of functionality.
+
+
+## Vane Interface {#vane-interface}
+
+Arvo is a message dispatcher, which doesn't really know about the vanes except via their existence in a `van=(map term vane)` in Arvo's `$soul`.
+
+Formally, a vane must be a “vane-shaped noun”—an interface presenting the arms:
+
+```hoon
+|%
+:: +call: handle a +task request
+++ call
+:: +load: migrate an old state to a new vane version
+++ load
+:: +scry: view vane state at a particular /path
+++ scry
+:: +stay: extract state before reload
+++ stay
+:: +take: handle $response sign
+++ take
+--
+```
+
+- `+load` and `+stay` are necessary to update the vane.
+- `+call` is used to pass a request in (“advance to target”).
+- `+scry` exposes the read-only scry namespace of the vane.
+- `+take` receives a response from another vane (“retreat along call stack”).
+
+(Now the formerly-elided distinctions between `sign`, `gift`, `task`, and `note` start to matter.)
+
+![](https://media.urbit.org/docs/arvo/cycle.png)
+
+- A `note` is sent by a vane to the Arvo kernel's `+call` arm.
+- Arvo dispatches a `task` to a vane's `+call` arm.
+- The vane performs the work.
+- If a result needs to be passed back, it is emitted as a `gift` along the `duct` back to Arvo's `+take` arm.
+- Arvo dispatches a `sign` to the original caller's `+take` arm.
+
+The actual mechanics of this are that the moves are placed into the appropriate `duct`, which is a `(list wire)`, simply an ordered collection of moves representing the causal history.
+
+`/sys/arvo` tracks what little it knows about vanes at a few points, e.g.:
+
+```hoon
+:: van: vanes while we desire it (in larval stage)
+van=(map term (trap vase))
+::
+++ grow
+ |= way=term
+ ?+ way way
+ %a %ames
+ %b %behn
+ %c %clay
+ %d %dill
+ %e %eyre
+ %g %gall
+ %i %iris
+ %j %jael
+ %k %khan
+ %l %lick
+ ==
+::
++$ vane [=vase =worm]
+```
+
+- `vase` is of course a generic vase, but specifically it expects a noun with the correct `$type`.
+- `worm` is the worm cache as discussed in [*Arvo II: The Boot Sequence*](ca04.md).
+
+Arvo interacts with vanes in vase mode; for instance, a scry takes place via a call to the `+scry` arm via a `+slap` against the `%limb` named `%scry`: `(~(slap wa sac) rig [%limb %scry])`. As usual, working in vase mode permits dynamic updates to the source.
+
+Vanes have as their subject:
+
+- `/sys/hoon` for language definitions.
+- `/sys/arvo` for message dispatch.
+- `/sys/lull` for a shared interface definition.
+- `/sys/zuse` for various stdlib utilities.
+
+In particular, `/sys/lull` acts as a header so that vanes can “see” each other's interface.
+
+### Updates {#updates}
+
+As with other parts of the system, vanes are rebuilt if the inner core on which they rely has been updated or if the vane itself has changed.
+
+An update to a vane is triggered by `+mod:what:pith` in the `+le` event-loop engine. (Recall from `ca05` that `+what` is involved in a system upgrade.) While there are some unfamiliar types here, note particularly the `%=` centis clause building each vane.
+
+```hoon
+++ mod
+ |= [del=news all=?]
+ ^+ ..pith
+ =^ job=oped fat.mod.sol (~(adorn adapt fat.mod.sol) del all)
+ =? lul.mod.sol ?=(^ lul.job)
+ (smit:va "lull" pit /sys/lull/hoon u.lul.job)
+ =? zus.mod.sol ?=(^ zus.job)
+ (smit:va "zuse" lul.mod.sol /sys/zuse/hoon u.zus.job)
+ %- %+ need:wyrd kel.ver.zen
+ :~ lull/;;(@ud q:(slap lul.mod.sol limb/%lull))
+ zuse/;;(@ud q:(slap zus.mod.sol limb/%zuse))
+ ==
+ %= ..pith
+ van.mod
+ %+ roll van.job
+ |= [[nam=term txt=cord] van=_van.mod.sol]
+ ^+ van
+ =/ nex (create:va our zus.mod.sol nam /sys/vane/[nam]/hoon txt)
+ =/ nav (~(get by van) nam)
+ =? nex ?=(^ nav) (update:va vase.u.nav nex)
+ (~(put by van) nam (settle:va nex))
+ ==
+```
+
+The recompilation against `%zuse` takes place in `+adorn:adapt:part`. ([Arvo Pärt](https://www.youtube.com/watch?v=jNxbT0MESTY))
+
+```hoon
+:: kernel modules
+::
+:: %zuse is the subject of the vanes; force all if we have a new %zuse
+::
+=. all |(all ?=(^ zus))
+=| nav=(map term cord)
+=? nav all
+ %- ~(gas by nav)
+ %+ turn
+ ~(tap by dir:(~(dip of fat) /sys/vane))
+ |=([name=@ta _fat] [`@tas`name (sole (need fil))])
+```
+
+
+## Behn {#behn}
+
+Behn is a timer/wake-up call system. Since it's a simple vane, let's approach it obliquely, by looking at a generator that calls it.
+
+- Open `/base/gen/timers/hoon` and examine the code.
+
+```hoon
+.^((list [date=@da =duct]) %bx (en-beam [our %$ [%da now]] /debug/timers))
+```
+
+- [“Behn Overview”](/reference/arvo/dill/behn)
+
+### `/sys/lull` Definition {#syslull-definition}
+
+The interface to Behn is defined in `/sys/lull`:
+
+```hoon
+:: ::::
+:::: ++behn :: (1b) timekeeping
+ :: ::::
+++ behn ^?
+ |%
+ +$ gift :: out result <-$
+ $% [%doze p=(unit @da)] :: next alarm
+ [%wake error=(unit tang)] :: wakeup or failed
+ [%meta p=vase]
+ [%heck syn=sign-arvo] :: response to %huck
+ ==
+ +$ task :: in request ->$
+ $~ [%vega ~] ::
+ $% $>(%born vane-task) :: new unix process
+ [%rest p=@da] :: cancel alarm
+ [%drip p=vase] :: give in next event
+ [%huck syn=sign-arvo] :: give back
+ $>(%trim vane-task) :: trim state
+ $>(%vega vane-task) :: report upgrade
+ [%wait p=@da] :: set alarm
+ [%wake ~] :: timer activate
+ ==
+ -- ::behn
+```
+
+(A `%vega` task informs the vane that the kernel has been upgraded.)
+
+### Structure {#structure}
+
+```hoon
++$ behn-state
+ $: %2
+ timers=(tree [key=@da val=(qeu duct)])
+ unix-duct=duct
+ next-wake=(unit @da)
+ drips=drip-manager
+ ==
+```
+
+- How does Behn think of timers?
+
+`/sys/behn` presents three primary cores:
+
+1. A type definition core.
+2. A helper core.
+3. The primary vane interface.
+
+Behn only has two kinds of moves: `%wait` `task`s and `%wake` `gift`s. How are these processed and what do they result in? (See the `$timer-map` structure too.)
+
+- What is a drip? How is it used?
+
+> Say an app (the Target) is subscribed to updates from Clay (the Client). If Clay `%give`s updates to the app directly and the app crashes, this may cause Clay to crash as well. If instead Clay `%pass`es Behn a `%drip` `task` wrapping the update `gift`, Behn will set a timer for `now` that, when fired, will cause the update `gift` to be given. If it causes a crash then it will have been in response to the `%drip` move, thereby isolating Clay from the crash. Thus `%drip` acts as a sort of buffer against cascading sequences of crashes.
+
+### The Nested Core Pattern {#the-nested-core-pattern}
+
+(~rabsef-bicrym calls this the “`+abet` engine” and while it's not a popular term inside of the core development team, I like the pithiness of it. “Engine” as a term of art is frequently used in the kernel to refer to `+le` two-letter doors so this usage is not completely inconsistent, just more specialized in application.)
+
+The basic concept of the nested core pattern is to have an outer core which builds a list of cards and state changes, then produces the queued changes all at once. (I always think about this as being one of those wind-up cars that you crank and then set down to whir away.)
+
+Behn's nested core pattern is pretty simple: it has an alias to `this`, one `+emit` arm to prepend a move to a list of moves, and an `+abet` arm to yield the `[moves state]`. The `+per-event` core is used to script the neighboring `+scry` and `+call` arms for the vane without leaking state invariants. Behn's instantiation of the `+abet` pattern centralizes the helper outer core as a centralized state machine.
+
+These are some common `+abet` pattern arms. These are not all unique, and many cores will omit all or most of these.
+
+- `+abed`—initialize. Set up the state of the inner core.
+- `+yoke`—initialize. Start from a particular value.
+- `+abet`—finalize. Exit from an inner core to an outer core, taking changes. Commonly, take a modified valued and overwrite it into the final state with a `+put:by`.
+- `+abut`—finalize. Alternative exit from `+abet` for a deletion.
+- `+move`—send a move. Generalization for `+pass`/`+give`.
+- `+pass`—request an action. Prepend a `%pass` move to the current list of moves.
+- `+give`—return a result. Prepend a standard `%give` to the current list of moves.
+- `+emit`—submit a card. Prepend a card to the current list of cards.
+- `+emil`—submit cards. Prepend a list of cards to the current list of cards.
+
+If some state needs to be maintained, this can be built in a door, but Behn's particular example is even more basic. In files with associated doors or with multiple nested core instances, it is common to prepend a two-letter identifier to disambiguate which outer core is being scripted at any given time, such as `+mo-abet` or `+ap-emil`.
+
+- [The Engine Pattern](../../hoon/engine-pattern.md)
+
+### Vere I/O Driver: `vere/io/behn.c` {#vere-io-driver-vereiobehnc}
+
+Arvo acquires its timer updates from Unix via `vere/io/behn.c`. This file presents its primary interface at `u3_behn_io_init()` to initialize a timer. This simply retrieves the current Unix time for a starting point and sets up the interface.
+
+Each communicating vane is linked various I/O drivers in `vere/auto.c`. (These do not correspond one-to-one with vanes.) These are registered into `car_u`, a global `_u3_auto` used for I/O driver invocations and callbacks.
+
+For Behn, the timer is set in `_behn_ef_doze()` using `uv_timer_start()`. The corresponding wakeup timer is emitted in `_behn_time_cb()` when the `libuv` main event loop handler.
+
+Finally, we can examine how the injection comes back into Arvo in `_behn_time_cb()`. An `ovum` is produced by `u3_ovum_init()`, manually injected using `u3_auto_plan()`, and subscribed to with `u3_auto_peer()`. `/sys/behn` then processes this wakeup event as a `%wake` via its `+call` arm.
+
+
+## Dill {#dill}
+
+Dill is Urbit's terminal driver.
+
+```hoon
+|pass [%d %text "hello world"]
+```
+
+Dill as a vane is mostly responsible for actually constructing terminal sessions and coordinating input and output. Thus most of the terminal stack actually lives in userspace (instrumented by Gall) rather than in `/sys/dill`.
+
+What do we mean when we talk about a terminal? Originally, of course, computer were directly programmed by moving wires between vacuum tubes or chips; later, this evolved to the ability to read and output cards. Computer terminals with CRT-based character displays began to be used in the late 1950s and gradually became more common. In fact, the original plasma display screens were used with PLATO in the 1970s–1990s.
+
+![](https://2.bp.blogspot.com/-T0k6GSlqlQE/UIYEXlThxMI/AAAAAAAAABs/Lwq27QO1TTQ/s1600/platoavatar.png)
+
+When we refer to the terminal today, we typically mean a modern [terminal emulator](https://en.wikipedia.org/wiki/Terminal_emulator), which presents a terminal-like text user interface (TUI) for software to treat as if it were an actual character display. Terminal emulators need to track information like dimensions ($x$, $y$), content layout, active sessions or connexions, and cursor position. They provide affordances like a color space, escape codes, layout libraries, and scrollable sessions.
+
+Dill is responsible for interfacing with keystrokes and with the terminal emulator session. Since Urbit can be run in a daemon mode, it's not necessary for Dill to actually have a terminal session for Urbit to run.
+
+Due to terminal emulator limitations, Dill sessions are only properly supported today in the `%webterm` app.
+
+- [“Developer Call: Urbit’s improved Terminal Stack”](https://www.youtube.com/watch?v=E-6E-l1SxFw)
+- [“Dill Overview”](/reference/arvo/dill/dill)
+
+### `/sys/lull` Definition {#syslull-definition}
+
+The `/sys/lull` interface specification for Dill is more complicated than that of Behn. Unlike Behn, a number of supporting types are necessary to produce the basic pair of `gift`/`task` for Dill.
+
+```hoon
+:: ::::
+:::: ++dill :: (1d) console
+ :: ::::
+++ dill ^?
+ |%
+ +$ gift :: out result <-$
+ $% [%blit p=(list blit)] :: terminal output
+ [%logo ~] :: logout
+ [%meld ~] :: unify memory
+ [%pack ~] :: compact memory
+ [%trim p=@ud] :: trim kernel state
+ [%logs =told] :: system output
+ == ::
+ +$ task :: in request ->$
+ $~ [%vega ~] ::
+ $% [%boot lit=? p=*] :: weird %dill boot
+ [%crop p=@ud] :: trim kernel state
+ [%flog p=flog] :: wrapped error
+ [%heft ~] :: memory report
+ $>(%init vane-task) :: after gall ready
+ [%logs p=(unit ~)] :: watch system output
+ [%meld ~] :: unify memory
+ [%pack ~] :: compact memory
+ [%seat =desk] :: install desk
+ [%shot ses=@tas task=session-task] :: task for session
+ $>(%trim vane-task) :: trim state
+ $>(%vega vane-task) :: report upgrade
+ [%verb ~] :: verbose mode
+ [%knob tag=term level=?(%hush %soft %loud)] :: deprecated removeme
+ session-task :: for default session
+ told :: system output
+ == ::
+ :: ::
+ +$ session-task :: session request
+ $% [%belt p=belt] :: terminal input
+ [%blew p=blew] :: terminal config
+ [%flee ~] :: unwatch session
+ [%hail ~] :: terminal refresh
+ [%open p=dude:gall q=(list gill:gall)] :: setup session
+ [%shut ~] :: close session
+ [%view ~] :: watch session blits
+ == ::
+ :: ::
+ +$ told :: system output
+ $% [%crud p=@tas q=tang] :: error
+ [%talk p=(list tank)] :: tanks (in order)
+ [%text p=tape] :: tape
+ == ::
+ ::
+ :::: :: (1d2)
+ ::
+ +$ blew [p=@ud q=@ud] :: columns rows
+ +$ belt :: client input
+ $? bolt :: simple input
+ [%mod mod=?(%ctl %met %hyp) key=bolt] :: w/ modifier
+ [%txt p=(list @c)] :: utf32 text
+ == ::
+ +$ bolt :: simple input
+ $@ @c :: simple keystroke
+ $% [%aro p=?(%d %l %r %u)] :: arrow key
+ [%bac ~] :: true backspace
+ [%del ~] :: true delete
+ [%hit x=@ud y=@ud] :: mouse click
+ [%ret ~] :: return
+ == ::
+ +$ blit :: client output
+ $% [%bel ~] :: make a noise
+ [%clr ~] :: clear the screen
+ [%hop p=$@(@ud [x=@ud y=@ud])] :: set cursor col/pos
+ [%klr p=stub] :: put styled
+ [%mor p=(list blit)] :: multiple blits
+ [%nel ~] :: newline
+ [%put p=(list @c)] :: put text at cursor
+ [%sag p=path q=*] :: save to jamfile
+ [%sav p=path q=@] :: save to file
+ [%url p=@t] :: activate url
+ [%wyp ~] :: wipe cursor line
+ == ::
+ +$ dill-belt :: arvo input
+ $% belt :: client input
+ [%cru p=@tas q=(list tank)] :: errmsg (deprecated)
+ [%hey ~] :: refresh
+ [%rez p=@ud q=@ud] :: resize, cols, rows
+ [%yow p=gill:gall] :: connect to app
+ == ::
+ +$ dill-blit :: arvo output
+ $% blit :: client output
+ [%qit ~] :: close console
+ == ::
+ +$ flog :: sent to %dill
+ $% [%crop p=@ud] :: trim kernel state
+ $>(%crud told) ::
+ [%heft ~] ::
+ [%meld ~] :: unify memory
+ [%pack ~] :: compact memory
+ $>(%text told) ::
+ [%verb ~] :: verbose mode
+ == ::
+ :: ::
+ +$ poke :: dill to userspace
+ $: ses=@tas :: target session
+ dill-belt :: input
+ == ::
+ -- ::dill
+```
+
+The main concepts to keep in mind:
+
+- Dill receives `%belt` `task`s and sends `%blit` `gift`s.
+- `%belt` `task`s result from keystrokes, terminal resizing,
+- `%blit` `gift`s result from output events: putting a character, clearing the screen, placing the cursor.
+
+### Structure {#structure}
+
+Dill's primary state is its `$axle` with a logging level:
+
+```hoon
+|% :: console protocol
++$ axle ::
+ $: %7 ::
+ hey=(unit duct) :: default duct
+ dug=(map @tas axon) :: conversations
+ eye=(jug @tas duct) :: outside observers
+ ear=(set duct) :: syslog listeners
+ lit=? :: boot in lite mode
+ egg=_| :: see +take, removeme
+ == ::
++$ axon :: dill session
+ $: ram=term :: console program
+ tem=(unit (list dill-belt)) :: pending, reverse
+ wid=_80 :: terminal width
+ == ::
++$ log-level ?(%hush %soft %loud) :: none, line, full
+--
+```
+
+As with `/sys/behn`, the primary cores of Dill include:
+
+1. Type definitions (two cores).
+2. Helper core (`+as` per-cause engine).
+3. The primary vane interface.
+
+A good way to familiarize yourself with Dill operations is to follow the full lifecycle of input and output in the next section.
+
+Dill is the first vane in the boot sequence, and is used to boot Jael. (Compare `%aqua`, which does not need to start Dill and can initialize Jael directly.)
+
+### Vere I/O Driver: `vere/io/term.c`, `ptty.c` {#vere-io-driver-vereiotermc-pttyc}
+
+The main entrypoint for the terminal is `u3_term_io_init()`, which simply sets up the interface and callbacks.
+
+As we noted before, each communicating vane is linked various I/O drivers in `vere/auto.c`. Here the global `car_u` has `term.c` connected for invocations and callbacks.
+
+**`vere/vere.h`**:
+
+```c
+// u3_term_start_spinner(): prepare spinner state. RETAIN.
+void u3_term_start_spinner(u3_noun say, c3_o del_o);
+
+// u3_term_stop_spinner(): reset spinner state and restore input line.
+void u3_term_stop_spinner(void);
+
+// u3_term_get_blew(): return window size [columns rows].
+u3_noun u3_term_get_blew(c3_l tid_l);
+
+// u3_term_ef_winc(): window change.
+void u3_term_ef_winc(void);
+
+// u3_term_ef_ctlc(): send ^C.
+void u3_term_ef_ctlc(void);
+
+// u3_term_io_init(): initialize terminal I/O.
+u3_auto* u3_term_io_init(u3_pier* pir_u);
+
+// u3_term_io_hija(): hijack console for cooked print.
+FILE* u3_term_io_hija(void);
+
+// u3_term_it_log(): writes a log message
+void u3_term_io_log(c3_c* line);
+
+// u3_term_io_loja(): release console from cooked print.
+void u3_term_io_loja(int x, FILE* f);
+
+// u3_term_log_init(): initialize terminal for logging
+void u3_term_log_init(void);
+
+// u3_term_log_exit(): clean up terminal.
+void u3_term_log_exit(void);
+
+// u3_ptty_init(): initialize platform-specific tty.
+u3_utty* u3_ptty_init(uv_loop_t* lup_u, const c3_c** err_c);
+```
+
+For instance, a keystroke is processed in the following way:
+
+- `uv_read_start()` is the `libuv` event loop injector.
+- `_term_read_cb()` is the character keystroke callback.
+- `_term_suck()` processes input.
+- `_term_io_suck_char()` decides if it's an `xterm` terminal emulator issue or something for Arvo to know about.
+- `_term_io_spit()` inputs the buffer and belt.
+- `_term_io_belt()` actually sends a value along the belt (as an `ovum`).
+
+Now in Arvo, the keystroke is routed to Dill:
+
+- `+call` takes the `task` from Arvo.
+- `+call:as` receives the input and dispatches on `%belt`.
+- `+send:as` sends the action to the proper session.
+- `+deal:as` signals to pass the keystroke to Gall.
+- `+pass:as` executes the pass to Gall.
+
+Output can happen in three ways:
+
+1. Some output traverses a path from (say) Gall outbound. These are conventionally known as `%slog`s.
+
+ - Somehow a noun is marked for output using a `%slog` hint:
+ - `~&` sigpam does this directly.
+ - `~>` siggar can do this with a `%slog` hint and a priority value.
+ - `+slog` wraps this as a function.
+
+ Back in the runtime:
+
+ - Once we have a `%slog` hint for the runtime, it can be emitted from the Nock processor via Nock Eleven. `noun/nock.c:_n_bint()` dispatches this via `SLOG` and thence `do_slog` in the bytecode processor.
+ - `noun/trace.c:u3t_slog()` prints a value directly through the `u3C.slog_f` print handler, which is `_cw_serf_send_slog()`.
+ - `vere/main.c::_cw_serf_send_slog()` sends the hint output to the serf.
+ - `vere/main.c:_cw_serf_send()` is a plea handler to send pleas to the daemon.
+ - `vere/newt.c:u3_newt_send()` sends a `+jam`med noun of the output (`u3s_jam_xeno()`) as a buffer to a stream. In this case, the value ends up at the `libuv` buffer using `uv_write()`.
+
+ In this case, output results in the fact of the layout of the terminal handler rather than being explicitly known about by Dill. As a consequence, if you are building a TUI application and you don't want to have misaligned output, you need to build it directly using `%blit`s and suppress `%slog`s within your app. (See `tui-toys` for an example in `%snek`.)
+
+2. Other output goes via Dill because the terminal vane explicitly needs to know about its position. This are `%blit`s.
+
+ - A program (like `%snek`) can specify to manually output a `%blit` like `%klr` (styled text) or `%put` (plain text) at the cursor.
+ - [`/lib/etui`](https://github.com/urbit/urbit/blob/wip/tui-toys/pkg/demo/lib/etui.hoon) offers some interesting demonstrations in this vein; see the `+zo` engine.
+ - `vere/io/term.c:_term_io_kick()` applies effects sent to `term.c`, including blits. It was registerd as the effect handler when the I/O drivers were registered.
+ - `vere/io/term.c:_term_ef_blit()` switches on the type of blit (notice it can also track a cursor position).
+ - `vere/io/term.c:_term_it_show_tour()` emits UTF-32 to the cursor location.
+ - Finally, `vere/io/term.c:_term_it_show_line()` prints at the actual cursor position.
+
+3. The runtime terminal manager automatically handles aspects of layout such as maintaining the input line at the bottom of the screen. See `vere/io/term.c:_term_it_restore_line()` for details. (CSI = Control Sequence Introducer sequence) A different terminal handler (like `%webterm`) may handle these decisions differently.
+
+Other output may follow yet a different path; for instance, `u3l_log()` directly prints using `vsnprintf()`to `stderr`. Some output goes via the `libuv` event loop, such as `u3_ptty_init()`..
+
+- Examine `u3_term_log_init()` in `vere/io/term.c`.
+- Examine `_term_it_show_line()` in the same file.
+
+### Dojo, `%hood`, `%helm`, `%drum` {#dojo-hood-helm-drum}
+
+Dojo is Urbit's primary CLI interface, and while it is too complicated to delve deeply into here, the major parts to consider include:
+
+1. Hoon parser. Real-time parsing of input, which evaluates Hoon code for syntactical correctness. (This is the reason that typing at the Dojo prompt is frequently slower than typing at other CLIs.)
+2. Specialized syntax. Besides Hoon input, generators and pokes can be set up and invoked directly from the Dojo prompt. `%say` and `%ask` generators are head-tagged pairs with gates following that return `sole-result`s.
+ 1. `+generator` prefixes cause Dojo to look in `/gen` for a particular generator file.
+ 2. `|generator` is a Hood generator, on which more in a moment.
+ 3. `+desk!generator` invokes a generator on a particular desk.
+ 4. `:agent|generator` takes the output from a generator (at `/gen/agent/generator`) and feeds it as a noun to the agent's `+on-poke` arm.
+ 5. `-thread` invokes a thread, with a similar desk-specific prefix as above.
+3. Hood. Most of the interesting Urbit instrumentation is provided to Dojo by the Hood/Helm agent pipeline.
+ 1. `%hood` is the overarching system app, to which Dojo redirects generator invocations prefixed with `|` bar such as `|pass` and `|install`. (Thus, `|pass` is in fact shorthand for `:hood|pass`.)
+ 2. `%helm` provides the interface for kernel and system functionality, such as `|verb`, `|moon`, etc. Hood calls into Helm.
+ 3. `%drum` manages the active CLI apps (`|dojo/link`, `Ctrl+X`).
+ 4. `%kiln` instruments filesystem operations using Clay.
+
+The overall call web of these is surprisingly tangled; as an example, let's trace `|dojo/link`, which tells Dojo to register a CLI interface to a Gall agent.
+
+- `/gen/hood/link`
+- `/app/hood`
+- `/lib/drum`→`+poke` w/ `%drum-link`
+- `/lib/drum`→`+poke-link`
+- `/lib/drum`→`+se-link`
+- `eel` (what is this?)
+
+
+## Exercises {#exercises}
+
+- Trace the entire lifecycle of `|pass [%d %text "Hello Mars!"]`. Include a function-by-function annotation and commentary.
+- Write a basic vane, `/sys/vane/`. `~&` on receiving a `task`.