5 Reference
Complete, neutral description of every module, binding, parameter, and struct exposed by Stone. Organized by module. Every binding’s documentation is co-located with its module, so require-ing a module is the way to discover the vocabulary it adds.
5.1 stone/edge
| (require stone/edge) | package: Stone |
Composition primitives. Every binding in this module produces or consumes a ashlar-meta. See Edge Primitives for the conceptual model.
struct
(struct ashlar-meta ( fn produces-all queries children lens name schema validate-walk middleware rebuilder) #:transparent) fn : procedure? produces-all : (listof symbol?) queries : (listof symbol?) children : (listof ashlar-meta?) lens : (or/c procedure? #f) name : symbol? schema : (or/c hash? #f) validate-walk : (or/c procedure? #f) middleware : list? rebuilder : (or/c procedure? #f)
produces-all — every node type this ashlar or its subtree can produce.
queries — node types read from the DAG that no earlier sibling produces.
children — child ashlars; '() for leaves.
lens — lens attached to the ashlar (set by ashlar-match when its extractor is a lens).
name — name used in logs and validation messages.
schema — JSON schema for structured outputs.
validate-walk — walk rule used by the validator’s driver.
middleware — middleware list when the ashlar is an agent ashlar.
rebuilder — closure that can rebuild this ashlar with substituted children or middleware; used by ashlar-with-tool-stub.
procedure
(make-ashlar fn [ #:produces produces #:queries queries #:name name #:children children #:lens lens #:schema schema]) → ashlar-meta? fn : procedure? produces : (or/c symbol? #f) = #f queries : (listof symbol?) = '() name : (or/c symbol? #f) = #f children : (listof ashlar-meta?) = '() lens : (or/c procedure? #f) = #f schema : (or/c hash? #f) = #f
syntax
(~> ashlar ...)
procedure
(run-ashlar ashlar [ dag #:record record #:loop loop?]) → dag? ashlar : ashlar-meta? dag : (or/c dag? #f) = #f record : (or/c record? #f) = #f loop? : boolean? = #f
Bare (run-ashlar ashlar dag) — runs ashlar once over dag, pure and ephemeral (no journal, no threshold).
Durable (run-ashlar ashlar #:record r) — runs ONE forward pass over the record’s DAG, journaling progress and binding the record’s threshold as (current-threshold). Resumable: replaying over a journal that already holds atomic completions skips the recorded leaves and re-runs from the crash point. dag and #:record are mutually exclusive.
Session loop (run-ashlar ashlar #:record r #:loop #t) — folds the topology over the record unbounded until the terminal head is a session-terminal halt (a 'user-quit or 'input-ended halt node). Requires #:record. This is what run-tui drives on its background thread.
See Run a durable, resumable session for the record / journal / resume model and stone/record for open-record.
syntax
(ashlar-loop body #:until predicate #:max max-iterations)
predicate = (any-expr) max-iterations = exact-nonnegative-integer?
To wrap a node-shaped predicate that only inspects the latest head, see on-latest.
syntax
(ashlar-match extractor maybe-name [val branch] ...)
maybe-name =
| #:name name
extractor is either a lens? or a procedure.
A lens? is applied to the latest head’s content (the value of (node-content (dag-latest-head work-dag)) at the lens’s path).
A procedure is called with the work DAG and must return a value matching one of the val keys.
The branch whose val equals the extractor’s return value runs against the work DAG. If no branch matches, a 'match-failed failure node is appended.
Halt heads. When the latest head is a halt node, ashlar-match bypasses the extractor and routes to the branch keyed by the halt’s halt-node-kind (e.g. 'declined, 'user-quit) — so a single match can catch a cancelled ask-human alongside its normal answer branches. With no matching kind branch, the halt passes through as the terminal head.
See also Edge Primitives for the conceptual model and on-latest for wrapping a node-shaped extractor.
procedure
(on-latest pred) → procedure?
pred : procedure?
procedure
(ashlar-map extractor body [#:name name]) → ashlar-meta?
extractor : procedure? body : ashlar-meta? name : (or/c symbol? #f) = #f
procedure
(ashlar-parallel [#:name name] lane ...) → ashlar-meta?
name : (or/c symbol? #f) = #f lane : ashlar-meta?
procedure
(ashlar-reduce ashlar [#:name name]) → ashlar-meta?
ashlar : ashlar-meta? name : (or/c symbol? #f) = #f
procedure
(make-ask-human #:format-fn format-fn #:name name #:produces produces [ #:queries queries]) → ashlar-meta? format-fn : (dag? . -> . string?) name : symbol? produces : symbol? queries : (listof symbol?) = '()
procedure
(make-scoped-ashlar body #:walk walk [ #:children children #:produces produces #:queries queries #:name name]) → ashlar-meta? body : procedure? walk : procedure? children : (listof ashlar-meta?) = '() produces : (or/c symbol? #f) = #f queries : (listof symbol?) = '() name : (or/c symbol? #f) = #f
procedure
(ashlar-produces s) → (or/c symbol? #f)
s : any/c
procedure
(ashlar-queries s) → (listof symbol?)
s : any/c
procedure
(ashlar-produces-all s) → (listof symbol?)
s : any/c
5.2 stone/dag
| (require stone/dag) | package: Stone |
Content-addressed, append-only typed DAG. See The DAG as Ashlar State for the conceptual model.
5.2.1 Nodes
struct
(struct node (id parents content meta ts type) #:transparent) id : string? parents : (listof string?) content : any/c meta : hash? ts : real? type : symbol?
procedure
(make-typed-node parents type content [meta]) → node?
parents : (listof string?) type : symbol? content : any/c meta : hash? = (hash)
procedure
(typed-node d type content) → node?
d : dag? type : symbol? content : any/c
procedure
(make-failure-node parents kind reason [meta]) → node?
parents : (listof string?) kind : symbol? reason : string? meta : hash? = (hash)
procedure
(failure-node? v) → boolean?
v : any/c
5.2.2 DAGs
struct
(struct dag (nodes heads root parent label) #:transparent) nodes : (hash/c string? node?) heads : (listof string?) root : (or/c string? #f) parent : (or/c dag? #f) label : symbol?
procedure
(dag-append d n) → dag?
d : dag? n : node?
procedure
(dag-latest-head d) → (or/c node? #f)
d : dag?
procedure
(dag-failed? d) → boolean?
d : dag?
procedure
(dag-halted? d) → boolean?
d : dag?
procedure
(dag-terminal? d) → boolean?
d : dag?
procedure
(make-halt-node parents kind reason [meta]) → node?
parents : (listof string?) kind : symbol? reason : string? meta : hash? = (hash)
procedure
(halt-node? n) → boolean?
n : any/c
procedure
(halt-node-kind n) → symbol?
n : node?
procedure
(halt-node-reason n) → string?
n : node?
procedure
(dag-nearest-ancestor d type) → (or/c node? #f)
d : dag? type : symbol?
procedure
(dag-collect-until d #:type collect-type #:until sentinel-type) → (listof node?) d : dag? collect-type : symbol? sentinel-type : symbol?
Returns nodes oldest-first (the one closest to the sentinel comes first). Returns '() when no sentinel-type ancestor is reachable on the first-parent line — there is no enclosing scope, so there is nothing to collect within.
Same first-parent discipline as dag-nearest-ancestor: deterministic under loops and fan-outs, and siblings on other lanes are not visited.
procedure
(dag-query-all d type [#:scope scope]) → (listof node?)
d : dag? type : symbol? scope : (or/c 'conversation #f) = #f
procedure
(dag-select d nid) → (listof node?)
d : dag? nid : string?
procedure
(dag-select-window d nid n) → (listof node?)
d : dag? nid : string? n : exact-nonnegative-integer?
5.3 stone/messages
| (require stone/messages) | package: Stone |
A single turn in an LLM conversation. Self-contained: no link to a ashlar DAG. Used as the element type of the 'conversation list that make-agent-ashlar embeds in its result node.
struct
role : symbol? content : (or/c string? hash? (listof hash?)) tool-calls : list? call-id : (or/c string? #f) metadata : hash?
role — 'system, 'user, 'assistant, or 'tool.
content — free text, a parsed structured-output hash, or a list of multimodal content blocks.
tool-calls — list of tool-call records; assistant role only, '() otherwise.
call-id — tool-result correlation id; tool role only, #f otherwise.
metadata — provider-specific escape hatch. Convention: namespaced symbol keys (e.g. 'anthropic/cache-control, 'openai/logprobs, 'vllm/finish-reason) to prevent collision when multiple providers stash bits there.
procedure
(message-text m) → string?
m : (or/c message? #f)
5.4 stone/llm-ashlar
| (require stone/llm-ashlar) | package: Stone |
Bridge constructor that wraps an LLM caller into a multi-turn agent ashlar. See Agents and Tools for the conceptual model.
procedure
(make-agent-ashlar caller #:produces produces [ #:context ctx #:name name #:schema schema #:middleware middleware #:decide decide #:max-turns max-turns #:response-format response-format #:model model #:budget budget #:context-budget context-budget #:adversary adversary #:heal-with healer #:max-healing max-healing #:finalize finalize]) → ashlar-meta? caller : procedure? produces : symbol? ctx : context? = (context) name : (or/c symbol? #f) = #f schema : (or/c hash? #f) = #f middleware : (listof any/c) = '() decide : procedure? = continue-on-tool-use max-turns : exact-positive-integer? = 15 response-format : (or/c hash? #f) = #f model : (or/c string? #f) = #f budget : exact-nonnegative-integer? = 16384 context-budget : exact-nonnegative-integer? = 400000 adversary : (or/c ashlar-meta? #f) = #f healer : (or/c ashlar-meta? #f) = #f max-healing : exact-nonnegative-integer? = 3 finalize : (or/c (any/c . -> . node?) #f) = #f
caller — LLM caller built by make-openai-caller or make-anthropic-caller.
#:produces — node type of the final result.
#:context — a context data structure (from stone/context-struct) describing how the outgoing messages are projected from the DAG: (context (system ...) (history 'type) (user 'type) #:compaction ashlar). It subsumes the former #:system / #:user / #:queries keywords; the ashlar’s read-set is derived from the context’s lenses, never declared alongside it. Default is an empty context.
#:name — defaults to produces, else a fresh agent-ashlar-* id.
#:schema — JSON schema attached to metadata. Usually auto-populated from #:response-format.
#:middleware — middleware onion wrapping each turn.
#:decide — loop decision function (context? (listof recommendation?) -> recommendation?). Must be a procedure. Default is continue-on-tool-use. For single-turn behavior, pass #:max-turns 1 with empty middleware.
#:max-turns — hard cap on turns inside the agent loop.
#:response-format — when truthy, the final draft must be a hash (parsed from JSON). Failing to parse produces a 'llm-parse-failed failure.
#:model — model identifier; overrides default-model for this ashlar.
#:budget — max response tokens per turn (the response max-tokens handed to the caller).
#:context-budget — a provider-agnostic size hint the projected context is measured against. When the projection overflows and no #:compaction ashlar re-projects it small enough, the agent fails loud (a failure node becomes the head; the caller is never reached). Default is large.
#:adversary — quality-gate ashlar. Runs against the ashlar DAG when decide says 'continue. Failure node = rejection; non-failure = pass.
#:heal-with — healer ashlar. When adversary rejects, healer runs and its output enters the agent’s conversation.
#:max-healing — bounds heal cycles (reject → heal → retry), not adversary invocations. The adversary always votes at least once; the budget gates retries only. #:max-healing 0 is the gate idiom: adversary votes once; pass → done, reject → fail without retry. #:max-healing N allows up to N heal cycles, so the adversary may vote up to N+1 times. On the (+ N 1)th reject, returns a 'healing-exhausted failure node.
The threshold — the single designed opening through which the agent emits observation crossings (turns, and — via the streaming caller — tokens) and poses decisions (tool-call gates) — is not a construction argument. The agent reads (current-threshold) when it emits, a per-run resource bound by the runner (run-tui, or run-ashlar with #:record from the record’s threshold) — symmetric with current-journal. An ephemeral bare run that wants observation does (parameterize ([current-threshold th]) (run-ashlar ...)).
#:finalize — optional hook that receives the parsed final content and returns either a typed node or a failure node. The framework injects 'conversation into typed-node returns; failure-node returns pass through unchanged. Raises if the returned content already contains 'conversation. When absent, the framework builds a typed-node of type produces with the parsed content and the conversation merged in.
5.5 stone/context-struct
| (require stone/context-struct) | package: Stone |
The context data structure an agent ashlar’s #:context is built from (see make-agent-ashlar). A context is plain, inspectable data with two readings of the same value: statically, the validator walks it to derive the ashlar’s read-set (context-read-set); at run time, the agent loop interprets it to project the outgoing messages from the DAG. It subsumes the former #:system / #:user / #:queries keywords.
Naming. stone re-exports a different, unrelated context — the per-turn middleware struct (stone). When you require both, exclude that one: (require (except-in stone context context? on-latest) stone/context-struct).
procedure
(context [ #:compaction compaction] element ...) → context? compaction : (or/c ashlar-meta? #f) = #f element : context-element?
procedure
(system arg) → context-element?
arg : (or/c string? ctx-lens?)
procedure
(user arg) → context-element?
arg : (or/c symbol? ctx-lens?)
procedure
(history arg) → context-element?
arg : (or/c symbol? ctx-lens?)
struct
(struct context-element (role lens) #:transparent) role : (or/c 'system 'user 'history) lens : ctx-lens?
struct
(struct ctx-lens (projector reads literal) #:transparent) projector : procedure? reads : (listof symbol?) literal : any/c
procedure
(context-elements ctx) → (listof context-element?)
ctx : context?
procedure
(context-compaction ctx) → (or/c ashlar-meta? #f)
ctx : context?
procedure
(context-read-set ctx) → (listof symbol?)
ctx : context?
procedure
(compaction-summary-parents dag condensed-turn-ids) → (listof string?) dag : dag? condensed-turn-ids : (listof string?)
5.6 stone/threshold
| (require stone/threshold) | package: Stone |
The threshold is the single designed opening between a running ashlar and everything outside it — one per run. It has two faces: the inside (engine) emits observations and poses decisions; the outside (supervisor) reads the crossing feed and resolves decisions. Every crossing is provenance-tagged by its span, so one threshold serves a whole nested run. See Ask Human for the conceptual model and Run a durable, resumable session for how a run binds one.
procedure
(make-threshold [#:unattended unattended]) → threshold?
unattended : hash? = (hasheq 'gate 'deny 'ask 'cancel)
procedure
(threshold? v) → boolean?
v : any/c
parameter
(current-threshold) → (or/c threshold? #f)
(current-threshold th) → void? th : (or/c threshold? #f)
struct
(struct crossing (span kind payload) #:transparent) span : pair? kind : symbol? payload : any/c
5.6.1 Outside: the supervisor
procedure
(threshold-subscribe th) → evt?
th : threshold?
procedure
(threshold-attach! th) → void?
th : threshold?
procedure
(threshold-detach! th) → void?
th : threshold?
procedure
(threshold-attended? th) → boolean?
th : threshold?
procedure
(threshold-answer! th id text) → void?
th : threshold? id : any/c text : string?
procedure
(threshold-cancel! th id [kind]) → void?
th : threshold? id : any/c kind : symbol? = 'declined
procedure
(threshold-verdict! th id verdict) → void?
th : threshold? id : any/c verdict : (or/c 'allow 'deny)
procedure
(threshold-steer! th text) → void?
th : threshold? text : string?
procedure
(threshold-request-cancel! th) → void?
th : threshold?
5.6.2 Inside: the engine
These are called by the framework, not usually by user code.
procedure
(threshold-ask th question [span]) → (or/c string? cancelled?)
th : threshold? question : string? span : pair? = DECISION-SPAN
procedure
(threshold-gate th tool args call-id [span]) → (or/c 'allow 'deny)
th : threshold? tool : any/c args : any/c call-id : any/c span : pair? = DECISION-SPAN
procedure
(threshold-emit! th c) → void?
th : threshold? c : crossing?
procedure
(threshold-drain! th) → (listof string?)
th : threshold?
struct
(struct run-cancelled () #:transparent)
5.7 stone/record
| (require stone/record) | package: Stone |
A record binds the three things a durable run needs: the DAG it runs against, the append-only journal it writes progress to, and the threshold a supervisor observes and answers through. See Run a durable, resumable session for the workflow.
struct
(struct record (dag journal threshold path) #:transparent) dag : dag? journal : journal? threshold : (or/c threshold? #f) path : (or/c path? string?)
procedure
(open-record path [#:threshold th]) → record?
path : (or/c path? string?) th : (or/c threshold? #f) = #f
Fresh (no file at path) — an empty DAG plus a new journal opened for append.
Resume (the file exists) — the DAG is rebuilt by replaying the journal, and the same file is reopened for append so new facts extend the log.
#:threshold defaults to #f; a driver/TUI passes a real one, and a batch test may pass one to observe or answer decisions. Hand the result to run-ashlar #:record.
5.7.1 stone/journal
| (require stone/journal) | package: Stone |
The record’s on-disk form: an append-only JSONL log of the facts a run produces. The DAG is the single source of truth; the journal is its crash-recovery projection, and replaying it is exactly what a resume does. Most users never touch this module directly — they use open-record and run-ashlar #:record. The facts (node-appended, session-started, ashlar-completed, turn-completed, decision-posed, decision-received, …) are transparent structs so equality — the round-trip contract — is structural.
procedure
(open-journal path) → journal?
path : (or/c path? string?)
procedure
(journal-append! j fact) → void?
j : journal? fact : any/c
procedure
(close-journal j) → void?
j : journal?
parameter
(current-journal) → (or/c journal? #f)
(current-journal j) → void? j : (or/c journal? #f)
5.7.2 stone/tui-main
| (require stone/tui-main) | package: Stone |
procedure
(run-tui ashlar #:record path [ #:threshold th #:initial-state initial-state]) → any ashlar : ashlar-meta? path : (or/c path? string?) th : threshold? = (make-threshold) initial-state : any/c = #f
5.8 stone/llm-types
| (require stone/llm-types) | package: Stone |
Shared response and exception types used by the LLM caller layer. Tool authors and custom callers require this module; user ashlar code generally does not.
5.8.1 Responses
struct
(struct llm-response (text tool-calls usage))
text : string? tool-calls : (listof tool-call?) usage : hash?
5.8.2 Exceptions
struct
(struct exn:fail:repetition-tripped exn:fail (hit partial) #:extra-constructor-name make-exn:fail:repetition-tripped) hit : repetition-hit? partial : string?
struct
(struct exn:fail:repetition-exhausted exn:fail (attempts last-hit) #:extra-constructor-name make-exn:fail:repetition-exhausted) attempts : exact-nonnegative-integer? last-hit : (or/c repetition-hit? #f)
struct
(struct exn:fail:llm-http-error exn:fail (status body) #:extra-constructor-name make-exn:fail:llm-http-error) status : exact-nonnegative-integer? body : string?
struct
(struct exn:fail:llm-empty-response exn:fail () #:extra-constructor-name make-exn:fail:llm-empty-response)
5.9 stone/llm-client
| (require stone/llm-client) | package: Stone |
LLM caller factories compatible with the make-agent-ashlar caller contract. See Provider constraints for per-provider knobs.
procedure
(make-openai-caller #:url url [ #:api-key api-key #:extra-body extra-body #:repetition-watch repetition-watch #:max-retries max-retries]) → procedure? url : string? api-key : string? = "" extra-body : hash? = (hasheq) repetition-watch : (or/c (-> repetition-watcher?) #f) = #f max-retries : exact-nonnegative-integer? = 3
#:url may be the base URL or the full /v1/chat/completions URL; /v1/chat/completions is appended when missing. #:api-key empty means no Authorization header. #:extra-body is closed over at construction time and shallow-merged into every request body; the reserved keys '(model messages max_tokens tools response_format stream) raise at call time if you try to override them.
#:repetition-watch is a zero-arg factory that returns a fresh repetition-watcher; when supplied, every call constructs independent watchers for the response and thinking channels and feeds them mid-stream. If a watcher trips, the SSE stream aborts; the caller appends an assistant turn carrying the partial output and a user turn explaining the model got stuck, then retries up to #:max-retries times. After exhaustion, raises exn:fail:repetition-exhausted. When #f (default), the retry/watch path is bypassed entirely.
procedure
(make-anthropic-caller #:api-key api-key [ #:url url #:extra-body extra-body #:repetition-watch repetition-watch #:max-retries max-retries]) → procedure? api-key : string? url : string? = "https://api.anthropic.com/v1/messages" extra-body : hash? = (hasheq) repetition-watch : (or/c (-> repetition-watcher?) #f) = #f max-retries : exact-nonnegative-integer? = 3
#:repetition-watch and #:max-retries are accepted for API symmetry with make-openai-caller but currently have no effect — they will be honored when Anthropic streaming lands.
procedure
(call-llm #:url url #:model model [ #:system system] #:messages messages [ #:max-tokens max-tokens #:api-key api-key #:tools tools #:response-format response-format #:extra-body extra-body]) → hash? url : string? model : string? system : string? = "" messages : list? max-tokens : exact-nonnegative-integer? = 4096 api-key : string? = "mock-key" tools : list? = '() response-format : (or/c hash? #f) = #f extra-body : hash? = (hasheq)
procedure
(call-llm-openai #:url url #:model model [ #:system system] #:messages messages [ #:max-tokens max-tokens #:api-key api-key #:tools tools #:response-format response-format #:extra-body extra-body #:threshold threshold #:response-watcher response-watcher #:thinking-watcher thinking-watcher]) → hash? url : string? model : string? system : string? = "" messages : list? max-tokens : exact-nonnegative-integer? = 4096 api-key : string? = "" tools : list? = '() response-format : (or/c hash? #f) = #f extra-body : hash? = (hasheq) threshold : (or/c threshold? #f) = #f response-watcher : (or/c repetition-watcher? #f) = #f thinking-watcher : (or/c repetition-watcher? #f) = #f
#:response-watcher / #:thinking-watcher are optional repetition-watchers; if either trips during the SSE stream, the call raises exn:fail:repetition-tripped and closes the response. make-openai-caller’s retry layer constructs and supplies fresh watchers per attempt; supply them here directly only if you’re bypassing that layer.
procedure
(extract-text response) → string?
response : hash?
5.9.1 Exceptions raised by the OpenAI caller
This module raises exn:fail:repetition-tripped and exn:fail:repetition-exhausted from stone/llm-types under the conditions documented at stone/llm-types. The retry layer in make-openai-caller catches exn:fail:repetition-tripped internally; user code only observes it if that retry layer is bypassed.
5.10 stone/repetition-watch
| (require stone/repetition-watch) | package: Stone |
Streaming-time detectors for degenerate LLM output: exact n-gram loops (verbatim repetition) and rambling without progress (near-repetition). Wired into call-llm-openai via the #:response-watcher / #:thinking-watcher keywords; consumed by the caller-level retry loop in make-openai-caller via the #:repetition-watch keyword.
5.10.1 Hits
struct
(struct repetition-hit (kind detail position) #:extra-constructor-name make-repetition-hit) kind : symbol? detail : any/c position : exact-nonnegative-integer?
5.10.2 Karp–Rabin n-gram counter
Detects exact verbatim repetition. Maintains a rolling polynomial hash over the latest n bytes; trips when any window’s hash is observed threshold times (with sample-byte verification to filter hash collisions). Sticky after trip — once tripped, ngram-counter-add! is an O(1) no-op.
procedure
(make-ngram-counter [ #:n n #:threshold threshold]) → ngram-counter? n : exact-positive-integer? = 100 threshold : exact-positive-integer? = 4
procedure
(ngram-counter-add! c byte) → void?
c : ngram-counter? byte : byte?
procedure
(ngram-counter-tripped c) → (or/c #f repetition-hit?)
c : ngram-counter?
5.10.3 Compression-ratio detector
Detects rambling / near-repetition where the model produces grammatical-but-going-nowhere output. Maintains a circular byte buffer of size #:window; per tick, deflates the buffer (via file/gzip) and pushes compressed-size / window-size onto a fixed-length ratio history. Trips when the latest #:history-len ratios are all ≤ #:threshold. Sticky.
procedure
(make-compression-detector [ #:window window #:threshold threshold #:history-len history-len]) → compression-detector? window : exact-positive-integer? = 1024 threshold : real? = 0.18 history-len : exact-positive-integer? = 3
procedure
(compression-detector-add! d byte) → void?
d : compression-detector? byte : byte?
procedure
d : compression-detector?
Calls that complete in less than the emit interval will never engage the compression detector — by design, since short outputs are not a rambling failure mode. The n-gram detector remains active per-byte regardless.
procedure
(compression-detector-tripped d) → (or/c #f repetition-hit?)
d : compression-detector?
5.10.4 Combined watcher
Multiplexes both detectors behind a single API. This is what the streaming session and caller layer plug in.
procedure
(make-repetition-watcher [ #:ngram-counter ngram-counter #:compression compression #:on-trip on-trip]) → repetition-watcher?
ngram-counter : (or/c ngram-counter? #f) = (make-ngram-counter)
compression : (or/c compression-detector? #f) = (make-compression-detector) on-trip : (-> repetition-hit? any/c) = (λ (_) (void))
procedure
(repetition-watcher-add-bytes! w bs) → void?
w : repetition-watcher? bs : bytes?
procedure
w : repetition-watcher?
procedure
(repetition-watcher-tripped w) → (or/c #f repetition-hit?)
w : repetition-watcher?
5.11 stone/tools
| (require stone/tools) | package: Stone |
Middleware constructors for LLM tool calls. See Agents and Tools for the conceptual model. Human interaction is make-ask-human (see stone/edge and Ask Human), which solicits through the run’s threshold rather than through tool middleware.
5.11.1 Tool middleware
procedure
(make-tool name #:schema schema #:handler handler [ #:allowed-paths allowed-paths #:confirm? confirm?]) → any/c name : symbol? schema : hash? handler : (hash? . -> . any) allowed-paths : (or/c (listof string?) #f) = #f confirm? : boolean? = #f
procedure
(has-tool-call-for? ctx tool-name) → boolean?
ctx : any/c tool-name : symbol?
procedure
(extract-tool-calls response) → (listof hash?)
response : hash?
procedure
(tool-schema mw) → (or/c hash? #f)
mw : any/c
5.11.2 Built-in tool middleware
procedure
(write-file [ #:allowed-paths allowed-paths #:confirm? confirm?]) → any/c allowed-paths : (or/c (listof string?) #f) = #f confirm? : boolean? = #f
procedure
(edit-file [ #:allowed-paths allowed-paths #:confirm? confirm?]) → any/c allowed-paths : (or/c (listof string?) #f) = #f confirm? : boolean? = #f
procedure
(delete-file* [ #:allowed-paths allowed-paths #:confirm? confirm?]) → any/c allowed-paths : (or/c (listof string?) #f) = #f confirm? : boolean? = #t
procedure
(list-directory [#:allowed-paths allowed-paths]) → any/c
allowed-paths : (or/c (listof string?) #f) = #f
procedure
(file-exists* [#:allowed-paths allowed-paths]) → any/c
allowed-paths : (or/c (listof string?) #f) = #f
procedure
(run-command [ #:timeout default-timeout #:confirm? confirm?]) → any/c default-timeout : real? = 60000 confirm? : boolean? = #f
procedure
(start-command [#:timeout default-timeout]) → any/c
default-timeout : real? = 300000
procedure
(check-command) → any/c
procedure
(wait-commands) → any/c
5.12 stone/decisions
| (require stone/decisions) | package: Stone |
Ready-made decide functions for the #:decide kwarg of make-agent-ashlar. A decide function has the signature (context? (listof recommendation?) -> recommendation?) and is called after every turn to pick 'continue, 'loop, or 'halt.
procedure
(continue-on-tool-use ctx recs) → any/c
ctx : any/c recs : (listof any/c)
procedure
(tool-directed ctx recs) → any/c
ctx : any/c recs : (listof any/c)
5.13 stone/validate
| (require stone/validate) | package: Stone |
Static checks over a composed ashlar. See Validation for the conceptual model.
procedure
(validate-ashlar ashlar) → validation-result?
ashlar : ashlar-meta?
struct
(struct validation-result (errors) #:transparent) errors : (listof validation-error?)
struct
(struct validation-error (type ashlar-name queried-type message) #:transparent) type : symbol? ashlar-name : (or/c symbol? #f) queried-type : symbol? message : string?
procedure
(validation-ok? r) → boolean?
r : validation-result?
procedure
(validation-errors r) → (listof validation-error?)
r : validation-result?
procedure
(enumerate-ashlars ashlar) → (listof symbol?)
ashlar : ashlar-meta?
procedure
(enumerate-paths ashlar) → (listof (listof symbol?))
ashlar : ashlar-meta?
5.14 stone/test
| (require stone/test) | package: Stone |
Test harness for Stone ashlars. See The test harness for the conceptual model and Test ashlars that use tools for the how-to.
A tool-call record is an immutable hasheq with keys 'name (symbol), 'input (hash), 'result-text (string), and 'result-meta (hash). tool-calls and friends return lists of these records in call order.
5.14.1 Harnesses
syntax
(with-live-harness #:caller caller maybe-strict maybe-timeout body ...)
maybe-strict =
| #:strict-tools? strict? maybe-timeout =
| #:timeout seconds
syntax
(with-mock-harness #:caller caller maybe-strict body ...)
(with-mock-harness #:responses responses maybe-strict body ...)
maybe-strict =
| #:strict-tools? strict?
procedure
(ashlar-with-tool-stub s tool-name stub-handler) → ashlar-meta? s : ashlar-meta? tool-name : symbol? stub-handler : (hash? . -> . any)
5.14.2 Assertions
procedure
(tool-calls) → (listof hash?)
procedure
(tool-calls-by-name name) → (listof hash?)
name : symbol?
procedure
name : symbol?
syntax
(check-tool-called? name)
(check-tool-called? name msg)
syntax
(check-tool-not-called? name)
(check-tool-not-called? name msg)
syntax
(check-tool-call-count name n)
(check-tool-call-count name n msg)
5.14.3 Stub helpers
procedure
(stub-answer s) → (hash? . -> . any)
s : string?
5.14.4 Response builders
procedure
(llm-tool-call tool-name [ #:id id #:question question #:input input]) → any/c tool-name : (or/c symbol? string?) id : string? = (fresh-call-id) question : (or/c string? #f) = #f input : (or/c hash? #f) = #f
5.14.5 Parameters
parameter
(harness-current-caller) → (or/c procedure? #f)
(harness-current-caller c) → void? c : (or/c procedure? #f)
= #f
5.15 stone/logging
| (require stone/logging) | package: Stone |
Structured logging on a dedicated Racket logger. See Observability for the design.
value
syntax
(log-stone-debug arg ...)
syntax
(log-stone-info arg ...)
syntax
(log-stone-warning arg ...)
syntax
(log-stone-error arg ...)
syntax
(log-stone-fatal arg ...)
procedure
(stone-event level event data) → void?
level : (or/c 'debug 'info 'warning 'error 'fatal) event : symbol? data : hash?
parameter
(current-trace-id) → (or/c string? #f)
(current-trace-id id) → void? id : (or/c string? #f)
= #f
parameter
(current-span-id) → (or/c string? #f)
(current-span-id id) → void? id : (or/c string? #f)
= #f
parameter
(current-parent-span-id) → (or/c string? #f)
(current-parent-span-id id) → void? id : (or/c string? #f)
= #f
procedure
(generate-id [prefix]) → string?
prefix : string? = ""
5.16 stone/trace
| (require stone/trace) | package: Stone |
Public API for reading and analyzing Stone’s trace.jsonl files. A trace is produced by attaching a logger to stone-logger (see Trace a run for emission); this module covers the consumption side.
The raco stone trace subcommands (tally, lifecycle, payload) are thin wrappers over the data API below — if you want to build a custom inspection tool, require this module and work with the primitives directly.
5.16.1 Loading
procedure
(load-trace path) → (listof hash?)
path : path-string?
5.16.2 Event accessors
Each accessor takes one event hash (as returned by load-trace) and returns a normalized field. Use these rather than hash-refing directly so callers stay robust against future trace-shape changes.
procedure
(event-data e) → hash?
e : hash?
procedure
(event-type e) → string?
e : hash?
procedure
(event-timestamp e) → real?
e : hash?
procedure
(event-ashlar-name e) → string?
e : hash?
procedure
(event-turn-number e) → (or/c exact-nonnegative-integer? #f)
e : hash?
5.16.3 Aggregators
procedure
(tally-events events)
→ (listof (cons/c string? exact-nonnegative-integer?)) events : (listof hash?)
procedure
(lifecycle-events events) → (listof hash?)
events : (listof hash?)
procedure
(find-payloads events [ #:ashlar ashlar #:turn turn]) → (listof hash?) events : (listof hash?) ashlar : (or/c string? #f) = #f turn : (or/c exact-nonnegative-integer? #f) = #f
Note that "api-call-payload" events are only emitted at the 'debug log level; calls captured at the default 'info level have headers ("api-call") but not full message contents.
5.16.4 Formatting
procedure
(format-lifecycle-line e) → string?
e : hash?
5.16.5 CLI entry points
These are the implementations of the raco stone trace subcommands; they’re provided so other tools (test harnesses, ad-hoc scripts) can invoke them programmatically with the same behavior as the CLI.
procedure
(stone-trace-cli) → any
procedure
path : path-string?
procedure
(run-lifecycle path) → any
path : path-string?
procedure
(run-payload path [ #:ashlar ashlar #:turn turn #:last? last?]) → any path : path-string? ashlar : (or/c string? #f) = #f turn : (or/c exact-nonnegative-integer? #f) = #f last? : boolean? = #f
By default picks the first matching payload; pass #:last? #t for the most recent. Equivalent to raco stone trace payload <path> with –ashlar, –turn, and –last flags.
5.17 stone/lens
| (require stone/lens) | package: Stone |
Lightweight path accessor used by ashlar-match extractors and validator lens checks. Re-exported from stone for convenience.
5.18 stone
| (require stone) | package: Stone |
Umbrella module. Re-exports every public binding from stone/edge, stone/dag, stone/decisions, and stone/lens, plus the runtime types documented here (default-model, make-json-schema, and the middleware / context / recommendation support for custom multi-turn behavior).
For code meant to be compact, (require stone) pulls the core vocabulary. Reach for the per-module requires (stone/llm-ashlar, stone/llm-client, stone/tools, stone/test, stone/validate, stone/logging) for the modules the umbrella doesn’t cover.
5.18.1 Parameters
parameter
(default-model) → (or/c string? #f)
(default-model m) → void? m : (or/c string? #f)
= #f
5.18.2 Schema builder
procedure
(make-json-schema name properties required) → hash?
name : string? properties : hash? required : (listof (or/c symbol? string?))
(hasheq 'type "json_schema" 'json_schema (hasheq 'name name 'strict #t 'schema (hasheq 'type "object" 'properties properties 'required required 'additionalProperties #f)))
5.18.3 Middleware onion types
These are the types make-agent-ashlar uses internally. Most users never construct them directly — reach for them when building a custom decide function or a middleware that needs to inspect context state.
struct
(struct context ( messages system tools response-format model budget meta dag node-draft threshold) #:transparent) messages : (listof hash?) system : string? tools : (listof hash?) response-format : (or/c hash? #f) model : (or/c string? #f) budget : exact-nonnegative-integer? meta : hash? dag : (or/c dag? #f) node-draft : (or/c hash? #f) threshold : (or/c threshold? #f)
struct
(struct middleware (name guard handler) #:transparent) name : symbol? guard : (context? . -> . boolean?) handler : (context? (context? . -> . context?) . -> . context?)
struct
(struct recommendation (type source reason) #:transparent) type : (or/c 'continue 'halt 'loop) source : any/c reason : any/c
procedure
(make-context [ #:messages messages #:system system #:tools tools #:response-format response-format #:model model #:budget budget #:meta meta #:dag dag #:node-draft node-draft #:threshold threshold]) → context? messages : list? = '() system : string? = "" tools : list? = '() response-format : (or/c hash? #f) = #f model : (or/c string? #f) = (default-model) budget : exact-nonnegative-integer? = 4096 meta : hash? = (hash) dag : (or/c dag? #f) = #f node-draft : (or/c hash? #f) = #f threshold : (or/c threshold? #f) = #f
procedure
(context-set ctx [ #:messages messages #:system system #:tools tools #:response-format response-format #:model model #:budget budget #:meta meta #:dag dag #:node-draft node-draft #:threshold threshold]) → context? ctx : context? messages : any/c = #f system : any/c = #f tools : any/c = #f response-format : any/c = #f model : any/c = #f budget : any/c = #f meta : any/c = #f dag : any/c = #f node-draft : any/c = (void) threshold : any/c = (void)
procedure
(make-middleware name guard handler) → middleware?
name : symbol? guard : (context? . -> . boolean?) handler : (context? (context? . -> . context?) . -> . context?)
5.19 Command-line interface
Stone’s command-line surface: the raco stone TUI launcher and the validate, trace, and install-skill subcommands.
5.19.1 raco stone
Synopsis: raco stone [flags...]
Loads a Stone configuration (if present), merges any command-line flag overrides, allocates (or resumes) a session journal, and launches the Stone TUI with the resulting agent ashlar.
Flag |
| Argument |
| Description |
–url |
| url |
| LLM API endpoint URL. Overrides url in config. |
–model |
| model |
| Model identifier. Overrides model in config and sets default-model. |
–continue |
| [id] |
| Resume a recorded session (most-recent when no id). |
–url and –model are #:once-each. No positional arguments are accepted.
5.19.1.1 Configuration
On startup the CLI looks for .stone/settings.rkt (walking up from the current directory to $HOME). If found, it’s loaded with dynamic-require and expected to provide a zero-argument procedure named build-agent. The CLI invokes build-agent with no arguments; its return value is an ashlar-meta? — the topology to run. The CLI builds a single threshold for the run and hands it to run-tui, which binds it per run (as current-threshold) and subscribes to it. The agent reads current-threshold when it emits, so its observation crossings reach the TUI without the config threading a threshold — symmetric with current-journal.
#lang racket |
(require stone/llm-ashlar stone/llm-client stone/context-struct) |
(provide build-agent) |
(define caller (make-openai-caller #:url "http://localhost:8000")) |
(define (build-agent) |
(make-agent-ashlar caller |
#:produces 'response |
#:context (context (system "You are helpful.") |
(history 'response) |
(user 'prompt)))) |
5.19.1.2 Sessions
Each raco stone run is a durable session: its journal lives at .stone/sessions/<id>.jsonl, resolved next to the loaded .stone/settings.rkt (or .stone/sessions/ under the current directory when there is no config). A bare raco stone allocates a fresh session id; raco stone –continue reopens the most-recent session (or the one named by –continue <id>) and resumes it — the journal replays as scrollback and the run picks up where it stopped, as long as the topology is unchanged. List recorded sessions with raco stone sessions (below). See Run a durable, resumable session for the durability model.
5.19.1.3 Exit codes
Code |
| Meaning |
0 |
| Normal TUI exit, or fall-through when no agent is configured. |
The CLI does not call exit itself; exit status is whatever the TUI or the Racket runtime returns on termination.
5.19.2 raco stone sessions
Synopsis: raco stone sessions
Lists the recorded sessions under the current sessions directory (.stone/sessions/), newest activity first, as a fixed-width table with columns ID, STARTED, LAST-ACTIVITY, and LABEL (the first user prompt, truncated). Prints No recorded sessions. when the directory is empty or absent. Pair it with raco stone –continue <id> to resume a specific session.
5.19.3 raco stone validate
Synopsis: raco stone validate <ashlar-file>
Loads a Racket file, extracts the binding named ashlar, and runs validate-ashlar on it. Prints errors and warnings; sets exit code from the result.
5.19.3.1 Ashlar file requirements
The file is loaded via dynamic-require. It must:
Be a valid Racket module that racket can dynamic-require.
provide a binding named exactly ashlar.
Bind ashlar to a ashlar-meta? value.
If the file doesn’t export ashlar, the CLI prints Error: <file> does not export ’ashlar and exits 1.
5.19.3.2 Validation categories
Category |
| Error types |
| Affects exit code |
Hard errors |
| missing-producer, invalid-lens, fanout-not-reduced |
| yes — exit 1 |
Warnings |
| maybe-unavailable |
| no — exit 0 |
5.19.3.3 Output
Ashlar is valid. |
When hard errors are present, a header and one line per error, formatted as [<type>] <ashlar-name>: <message>:
Errors (2): |
[missing-producer] classify: classify queries 'ticket but no upstream Stone produces it |
[invalid-lens] dispatch: lens path '(category) not found in upstream schema properties |
When warnings are present, an analogous Warnings (N): block prints. Both blocks may appear in the same run; the hard-error block (if any) prints first.
5.19.3.4 Exit codes
Code |
| Meaning |
0 |
| Ashlar valid, or only maybe-unavailable warnings present. |
1 |
| Missing ashlar-file argument, file doesn't export ashlar, hard errors reported, or subcommand other than validate supplied. |
5.19.4 raco stone trace
Synopsis: raco stone trace <subcommand> <path> [opts...]
Inspect a trace.jsonl file produced by a Stone run with stone-logger attached. Three subcommands cover the default investigation flow:
Subcommand |
| Description |
tally <path> |
| Print total event count and per-event-type counts. First-pass overview of what happened during the run. |
lifecycle <path> |
| Print the filtered story of the run, one event per line: ashlar start/end, tool dispatch, api-call/response, streaming progress, and failure events. Excludes noise like middleware-run. |
payload <path> [opts] |
| Dump a single api-call-payload event: ashlar, turn, model, system prompt, and one line per message with role + content preview + tool-call count. |
5.19.4.1 payload options
Flag |
| Argument |
| Description |
–ashlar |
| name |
| Filter to payloads from the named ashlar. |
–turn |
| n |
| Filter to the named LLM turn. |
–last |
|
| Pick the most recent matching payload (default is the first). |
"api-call-payload" events are only emitted at the 'debug log level; if payload returns No api-call-payload found, re-run with STONE_LOG_LEVEL=debug in the environment to capture them.
The underlying data API is documented at stone/trace — if you want to script trace analysis beyond what these subcommands provide, require stone/trace and use the primitives directly.
5.19.4.2 Exit codes
Code |
| Meaning |
0 |
| Subcommand ran successfully. |
1 |
| Missing path, missing required option value, unknown subcommand, or unrecognized argument. |
5.19.5 raco stone install-skill
Synopsis: raco stone install-skill [–force] [–target-dir DIR]
Install the bundled Claude Code skills into ~/.claude/skills/ by default. Two skills ship with Stone and work as a pair: scope-ashlar (the guided interview that scopes an ashlar tree) and build-ashlar (which translates a scoped tree into a runnable Racket scaffold). build-ashlar also carries a scribblings snapshot for primitive lookup; scope-ashlar does not (it writes no Racket).
Flag |
| Description |
–force |
| Overwrite existing skill bodies. Without this, an already-installed skill body is left untouched. |
–target-dir DIR |
| Install under DIR instead of HOME/.claude/skills/. The skills end up at DIR/scope-ashlar/ and DIR/build-ashlar/. |
The command is safe to re-run. Without –force, an already-installed skill body is left untouched, and build-ashlar’s scribblings snapshot is refreshed so it tracks your current Stone version — so a bare re-run after upgrading Stone is the normal way to update the docs. With –force it wipes and re-ships the skill bodies as well.
5.19.5.1 Exit codes
Code |
| Meaning |
0 |
| Install, forced re-install, or bare scribblings refresh succeeded. |
1 |
| Unknown flag, bad argument, or filesystem error during install. |