On this page:
2.1 Parentheses, prefix, and lambdas
2.2 Keyword arguments
2.3 Hashes
2.4 Lists and quoting
2.5 Parameters and dynamic extent
2.6 define-values for multiple returns
2.7 Modules and requires
2.8 Threads and channels
2.9 Tips for reading nested parentheses
2.10 Where to go for more
9.1

2 Reading Racket🔗ℹ

This guide covers just enough Racket to read and write Stone pipelines. It isn’t a Racket tutorial — if you want to learn the language proper, start with Quick: An Introduction to Racket with Pictures or The Racket Guide. Everything here is the subset Stone uses pervasively, nothing more.

2.1 Parentheses, prefix, and lambdas🔗ℹ

A Racket expression is a parenthesized list. The first element is the operator; the rest are its arguments.

(+ 1 2)                  ; => 3
(list 'a 'b 'c)          ; => '(a b c)
(define x 42)            ; binds x to 42

A function is a lambda:

(lambda (dag) (typed-node dag 'hello (hasheq)))

You’ll see this shape constantly — Stone’s ashlar bodies are (lambda (dag) ...). The parameter list is in parens, the body is the rest.

define can name a function directly:

(define (my-ashlar dag)
  (typed-node dag 'hello (hasheq)))

equivalent to

(define my-ashlar
  (lambda (dag)
    (typed-node dag 'hello (hasheq))))

2.2 Keyword arguments🔗ℹ

Stone uses keyword arguments heavily. A keyword is an identifier prefixed with #::

(make-ashlar produce-requirement
  #:produces 'requirement
  #:name 'seed)

#:produces and #:name are keyword arguments. They can appear in any order and are optional when the function supplies defaults. When you’re reading a constructor call, the positional argument comes first, and the keyword arguments follow in any order.

2.3 Hashes🔗ℹ

A hasheq is the most common shape for node content — a hash table that compares keys with eq? (which is what symbols want):

(hasheq 'name "Alice"
        'age 30
        'roles '(admin user))

You read a field with hash-ref:

(hash-ref some-hash 'name)          ; => "Alice"
(hash-ref some-hash 'missing #f)    ; => #f (default)

Stone’s node-get is a shape-safe version of the same pattern that tolerates missing nodes and non-hash content:

(node-get (dag-nearest-ancestor dag 'requirement) 'text "")

Prefer node-get in ashlar bodies.

2.4 Lists and quoting🔗ℹ

A quoted list is data, not a function call:

'(a b c)                 ; => (a b c) — a list of symbols
(list 'a 'b 'c)          ; => (a b c) — equivalent

The quote prevents Racket from evaluating the list as a function call. You’ll see quoted symbols constantly — every node type in Stone is a symbol: 'requirement, 'summary, 'project-config.

2.5 Parameters and dynamic extent🔗ℹ

A parameter is a piece of state scoped to a dynamic extent. make-parameter creates one, parameterize rebinds it for the body of an expression:

(define current-user (make-parameter #f))
 
(parameterize ([current-user "alice"])
  (some-function))     ; inside, (current-user) returns "alice"
(current-user)         ; back outside, it's #f again

Stone uses parameters for current-trace-id, current-span-id, and default-model. The key property is that the rebinding applies to every function call below the parameterize, no matter how deep, without any argument threading.

2.6 define-values for multiple returns🔗ℹ

A function can return more than one value; define-values binds the whole tuple:

(define-values (result final-dag)
  (run-pipeline pipeline (make-dag)))

run-pipeline returns two values — the final node and the updated DAG. define-values names them both.

2.7 Modules and requires🔗ℹ

A Racket source file starts with #lang racket to declare the language, then require forms to pull in modules:

(require stone/edge
         stone/dag
         stone/llm-ashlar
         stone/llm-client)

stone/edge makes make-ashlar, ~>, ashlar-loop and friends available. Each Stone module maps to a source file in the package; the reference documents them module by module.

2.8 Threads and channels🔗ℹ

The last piece you need for make-ask-human frontends. thread starts a background thread; channels rendezvous between threads.

(define ch (make-channel))
 
(thread (lambda ()
          (define msg (channel-get ch))
          (displayln msg)))
 
(channel-put ch "hello")

channel-put blocks until a reader is ready; channel-get blocks until a writer is ready. An ask-human frontend runs in a thread that channel-gets questions from the ashlar and channel-puts answers back.

sync and handle-evt let a single thread wait on multiple channels at once — the usual shape for a frontend serving several ask-human ashlars.

2.9 Tips for reading nested parentheses🔗ℹ

Stone pipelines nest several layers deep. Two reading habits help:

  • Indentation is load-bearing. Racket code is formatted so that logically grouped arguments align. If you’re lost, look at the column a form starts in — its closing paren is at the same column or on a line that lines up under it.

  • Read outside in. The outermost form tells you what kind of thing you’re looking at. Start there, then drill into the arguments in order. A (ashlar-loop body #:until pred #:max 5) is always a loop; the details inside are just the body and the parameters.

A Racket-aware editor (DrRacket, Emacs racket-mode, VS Code’s Magic Racket) matches parens and highlights structure. Use one.

2.10 Where to go for more🔗ℹ

If you can read a function definition, apply it to arguments, destructure a hash, and understand that parameterize changes a value for a scope, you can read any Stone pipeline in this manual.