:: In active development. Codebase tracks the dev branch. Explore at your own risk. Official release coming soon.
Skip to content

Kernel

After reading: you'll have a Hoon cheatsheet at hand and know which subpage to open when you're writing a domain cause, a peek path, or a replacement verification gate.

Most of your kernel is composed for you by nockup graft: commitment, state, and behavior primitives ship as graft libraries that drop into any nockapp. The behavior unique to your app's domain is the Hoon you write between the markers — the cause that says "register an artifact" or "issue a badge," the state field that tracks it, the ?- arm that wires the primitives together. Generic graft libraries are built for reuse across many apps; the domain-specific layer stays in your kernel so each app can shape it independently.

Typically the amount is small: one or two custom causes, the ?- arms that handle them, optional peek paths, and (sometimes) a replacement verification gate. The subpages cover those three patterns.

Heads up

This is a basic Hoon-and-kernel guide: enough syntax and patterns to write custom domain logic in a vesl kernel. For a deeper Hoon education, work through Hoon School. The Hoon Cheatsheet below is a quick reference for the runes, auras, and stdlib gates used in the worked examples.

Hoon Cheatsheet

A skim-able reference for the syntax used throughout these pages. For a fuller introduction, see Hoon School.

Syntax and Semantics

TermMeaning
NounHoon's universal value type. Either an atom or a cell.
AtomA non-negative integer. The primitive scalar.
CellAn ordered pair of nouns, written [a b]. Nests right: [1 2 3] parses as [1 [2 3]].
AuraA tag on an atom that says how to read it. Bits stay the same; the aura is metadata for the printer and parser.
Tagged unionA type with multiple variants, each marked by a head tag. Hoon's $%(...) constructor builds one; ?- is the exhaustive switch over the tag. The same concept as Rust's enum.
LoobeanHoon's boolean type, written ?. Two values: %.y (true) and %.n (false).
CoreA piece of code (the battery) plus its closed-over data (the payload). Hoon's closest thing to a class.
ArmA function on a core. Invoked via ~(arm core arg) or dotted-axis (arm.core).
GateA function. A specialized core with one arm ($) and a sample slot.
SampleA gate's argument slot. `
Wet-gateA polymorphic gate, declared with `
DoorA core used as a module-style API; arms accessed via ~(arm door arg). Examples: by (map ops), in (set ops).
SubjectThe lexical scope at a point in source: every name visible there.
MoteAn error class — a short @tas printed in stderr traces (e.g. mote=Exit, mote=Fail). Soft-cast (;;) and assertion (?>) failures raise Exit; mule catches them as %.n.
++pokeThe kernel's top-level write entrypoint. Takes (cause, state), returns [effects new-state].
++peekThe kernel's top-level read entrypoint. Takes a path, returns (unit (unit *)).

Auras

The aura is a tag on an atom describing how to read its integer value. Bits stay the same; the aura changes how it prints and parses.

AuraReadingExample
@bare atom (no read-as)5, 123
@udunsigned decimal5, 100
@uxunsigned hex0x10, 0xdead
@tUTF-8 cord'hello'
@taslowercase-and-dashes symbol%settle, %mint-graft, %my-action
@daabsolute date~2026.5.10

Syntactic Marks

The single-character and two-character marks Hoon uses for literals, punctuation, and access:

MarkMeaning
::Line comment. Everything after the two colons to end-of-line is ignored.
%nameSymbol literal — an @tas atom. The % is the syntactic mark; the name is what follows.
'text'Cord literal — UTF-8 text packed into a @t atom.
0xNNHex literal — @ux atom.
~Null. Used as the empty unit, the end-of-list marker, and the "no result" return.
`Unit-wrap shorthand. `value is [~ value]. Used to return (unit *) succinctly.
``Twice-unit-wrap. ``value is [~ ~ value]. Peek arms return (unit (unit *)).
~YYYY.MM.DDAbsolute date literal — @da atom.
[a b ...]Cell literal. Right-associative: [1 2 3] parses as [1 [2 3]].
name.subjectDotted-axis access. Reach a named field inside subject.
name=typeField or binding declaration. Same shape in records, gate arguments, and =/ let-bindings.
==Block terminator. Closes $%, ?-, and $: blocks.
--Core terminator. Closes `

Runes — The 12 vesl Hoon Uses

The runes a domain author writing 5–10 lines of Hoon per cause actually reaches for. Drawn from the rune-frequency profile across shipped graft Hoon, narrowed to what you write inside a ?- arm or a +$ declaration.

RunePurposeExample (from settle / kv graft)
?-Exhaustive switch on a tagged union. The shape of every cause-arm dispatch. Closes with ==.?- -.cause %kv-set set-arm %kv-delete del-arm ==
?:If-then-else.?: (gte ~(wyt by store) store-cap) reject proceed
?.If-not (inverse condition).?. =(root expected-root) reject accept
?>Assertion. Deterministic exit (Exit mote) if the test fails — the clean-deny shape for arm preconditions.?> (verify-leaf root note)
^-Type cast (downcast). The canonical empty-effect-list cast.^- [(list kv-effect) kv-state]
:_Cell constructor, tail-first. :_ X Y is [Y X] — the canonical [(list effect) new-state] return shape.:_ state(store new-store) ~[[%kv-stored key.cause]]
=/Typed let-binding: =/ name=type expr.=/ new-store (~(put by store.state) key.cause value.cause)
+$Type declaration. State, effects, and cause unions are declared this way.+$ kv-state $:(store=(map @t @))
|=Gate (function) declaration: |= sample body. The Hoon equivalent of a function literal.|= [state=kv-state cause=kv-cause]
++Arm declaration in a core. Kernel ++poke and ++peek are arms.++ kv-poke |= [state=kv-state cause=kv-cause]
/+Import a Hoon library into scope. Graft imports land here at the nockup:imports marker./+ *kv-graft
;;Soft-cast / mold-check. ;;(type expr) re-checks expr against type and exits on a shape mismatch. The standard guard for *-typed inputs.;;((map @t @) raw-store)

Deeper Hoon Rune Reference

The other runes you'll encounter in shipped graft Hoon and the wider language. Less load-bearing for domain code, but useful when you read graft bodies or peek-chain plumbing.

RunePurposeExample (from settle / kv graft)
=>Compose: evaluate the second expression with the first in scope.=> zeke (tip5 leaves)
?~Branch on null vs non-null (unit).?~ found [~ ~] [~ ~ u.found]
?=Type-pattern match.?= [%kv-set *] cause
^*Default value of a type.^* kv-state
~(arm core arg)Method-call shape: invoke arm on core with sample arg.(~(put by store) key value)
|.Trap — a zero-argument core. |. expr is a thunk evaluated by its $ arm. mule wraps a trap to catch crashes.(mule |.((kv-poke state cause)))
%-Function call (slam). (gate arg) is the irregular form.(kv-poke kv.state cause)
%=Record update by name. a(b c) is the irregular form.state(store new-store)
+(x)Increment.+(note-counter.state)
~[a b c]List literal.~[[%kv-stored key.cause]]
$%Tagged-union type constructor. Closes with ==.$% [%kv-set key=@t value=@] [%kv-delete key=@t] ==
$:Record-type constructor. Closes with ==.$: store=(map @t @) ==
$-Gate-type constructor. $-(sample return) is the type of a gate from sample to return. verify-gate is $-([note-id=@ data=* expected-root=@] ?).$-([@t @] ?)

Stdlib Gates

Hoon stdlib gates the worked examples and reference pages use. The by door operates on maps; in operates on sets.

GateCallBehavior
wyt~(wyt by m) / ~(wyt in s)Cardinality — count of entries in a map or members in a set.
gut(~(gut by m) key fallback)Get-or-default. Returns m[key] if present, else fallback.
has(~(has by m) key) / (~(has in s) elem)Membership test. Returns ?.
put(~(put by m) key value) / (~(put in s) elem)Insert (or overwrite).
scag(scag n list)List take — first n elements.
slag(slag n list)List drop — elements after position n.
turn(turn list f)List map — apply f to each element.
lent(lent list)List length.
welp(welp a b)List concatenate (type-tolerant).
weld(weld a b)List concatenate (stricter — same element type).
shax(shax atom)SHA-256 hash → atom.
mule`(mule. expr)`

Writing Hoon for vesl

Three places where you'll write Hoon, in order of how often you'll touch them.

A New Domain Cause

The kernel handles [%my-action ...] by adding a state field, a cause variant, and a ?- arm. Most apps live here. Walked on Adding a Domain Cause.

A Custom Peek Path

The kernel answers [%my-query ...] by walking your state and returning a (unit (unit *)). Walked on Adding a Domain Peek.

A Replacement Verification Gate

Swap the default hash-comparison gate when you need Merkle manifests, signatures, or STARK proofs. Walked on Replacing a Verification Gate.

Each ?- arm has the same shape:

domain arm in the ?- switch
├── cause-tag         %issue-badge              matched by ?-
├── reads             subject.u.act             dotted-axis off u.act
├── reads/writes      state(badges <new>)       record update by name
├── (opt) gate-call   (verify-gate arg)         swappable verify-gate (commitment causes)
├── emits             ~[[%badge-issued n]]      a list of effect variants
└── returns           :_  state(...)  ~[...]    cell, tail-first: [(list effect) new-state]

For domain arms that thread state through more than one graft at once, Coordinating Multiple Grafts in One Arm walks the apply-<graft> wet-gate pattern from domain-patterns.

How a Poke Updates State

A nockapp kernel handles each poke as a pure transformation: (cause, state) in, [effects new-state] out. The whole versioned-state value is the kernel's memory. Pokes receive it by value, compute the next version, and return it as the tail-half of an [effects new-state] cell. The runtime takes that returned state and uses it for the next poke; subsequent pokes see the post-update value because the runtime holds the "current" pointer for you.

versioned-state is the type your kernel declares at the nockup:state marker — a tagged record listing every field the kernel knows about. Each graft adds the fields it owns there; your domain appends its own at the bottom.

In practice, "modifying" state means constructing a copy with the fields you want different. Hoon's state(field new-value) record-update syntax is the shorthand: a copy of state with one field replaced. It plays the role of State { field: new-value, ..state } in Rust. The modifications go at the start of the expression and the base goes inside the parens.

Settle-Graft

settle-graft is the lowest-priority graft in the commitment family, so its arms top the ?- (exhaustive switch) block in dispatch order.

GraftPriority
settle10
mint20
guard30
forge40

Of the four it owns the fullest lifecycle: register a hull-id to a root (%settle-register), verify each payload through a swappable gate (%settle-verify), and record settled notes for replay protection (%settle-note). The other three are smaller-scope tiers (mint-graft registers a root with no verification, guard-graft adds a hash-leaf check, forge-graft generates a STARK proof over the hashing). settle is also the only graft whose poke body declares the gate splice point that lets a manifest swap the default verification gate via [graft.gates]; Replacing a Verification Gate walks the swap.

Each arm has its own success effect:

  • %settle-register emits [%settle-registered hull root]. One-shot bind of a hull-id to a Merkle root. No gate is run. The registered=(map @ @) field is mutated; re-registering the same hull is rejected.
  • %settle-note emits [%settle-noted note=[id hull root [%settled ~]]]. Verifies the payload through the active gate, then records the note-id in the settled set for replay protection. State mutates (settled, settle-count). When settle-count hits the per-epoch cap, the arm also emits [%settle-epoch-rotated old-epoch new-epoch] and rotates the active epoch before recording the note.
  • %settle-verify emits [%settle-verified ok=?]. Soft preflight; leaves state unchanged. Runs the same verification path as %settle-note and reports whether a %settle-note with the same payload would succeed.

All three convert failure into [%settle-error msg=@t] rather than crashing.

What's Out of Scope

Hoon as a general-purpose language is documented at docs.urbit.org/hoon. The patterns covered here and on the subpages handle almost everything a typical vesl nockapp writes; for deeper Hoon — runes, generics, mark types, the Hoon compiler's type system — go upstream.

Stuck?

Something broken? The breakage is probably already in Common Pitfalls.

See Also