On this page:
5.1 stone/  edge
ashlar-meta
make-ashlar
~>
run-pipeline
ashlar-loop
ashlar-match
on-latest
ashlar-map
ashlar-parallel
ashlar-reduce
make-ask-human
make-scoped-ashlar
ashlar-produces
ashlar-queries
ashlar-produces-all
5.2 stone/  dag
5.2.1 Nodes
node
make-typed-node
typed-node
node-get
node-get*
node-text
make-failure-node
failure-node?
5.2.2 DAGs
dag
make-dag
dag-append
dag-heads
dag-latest-head
dag-nodes
dag-failed?
dag-nearest-ancestor
dag-collect-until
dag-query-all
dag-select
dag-select-window
dag-merge
failure-log-handler
5.3 stone/  messages
message
message-text
5.4 stone/  llm-ashlar
make-agent-ashlar
5.5 stone/  llm-types
5.5.1 Responses
llm-response
tool-call
5.5.2 Exceptions
exn:  fail:  repetition-tripped
exn:  fail:  repetition-exhausted
exn:  fail:  llm-http-error
exn:  fail:  llm-empty-response
5.6 stone/  llm-client
make-openai-caller
make-anthropic-caller
call-llm
call-llm-openai
extract-text
5.6.1 Exceptions raised by the Open  AI caller
5.7 stone/  repetition-watch
5.7.1 Hits
repetition-hit
5.7.2 Karp–Rabin n-gram counter
make-ngram-counter
ngram-counter-add!
ngram-counter-tripped
5.7.3 Compression-ratio detector
make-compression-detector
compression-detector-add!
compression-detector-check
compression-detector-tripped
5.7.4 Combined watcher
make-repetition-watcher
repetition-watcher-add-bytes!
repetition-watcher-tick!
repetition-watcher-tripped
5.8 stone/  tools
5.8.1 Tool middleware
make-tool
has-tool-call-for?
extract-tool-calls
tool-schema
5.8.2 Built-in tool middleware
read-file
write-file
edit-file
delete-file*
list-directory
mkdir*
file-exists*
run-command
start-command
check-command
wait-commands
5.8.3 Ask-human channels
ask-human-channel
make-ask-human-channel
call-with-collected-ask-human-channels
5.9 stone/  decisions
continue-on-tool-use
tool-directed
5.10 stone/  validate
validate-pipeline
validation-result
validation-error
validation-ok?
validation-errors
enumerate-ashlars
enumerate-paths
5.11 stone/  test
5.11.1 Harnesses
with-live-harness
with-mock-harness
ashlar-with-tool-stub
5.11.2 Assertions
tool-calls
tool-calls-by-name
tool-call-count
check-tool-called?
check-tool-not-called?
check-tool-call-count
5.11.3 Stub helpers
stub-answer
stub-fn
5.11.4 Response builders
llm-text
llm-tool-call
llm-multi
5.11.5 Parameters
harness-current-caller
5.12 stone/  logging
stone-logger
log-stone-debug
log-stone-info
log-stone-warning
log-stone-error
log-stone-fatal
stone-event
current-trace-id
current-span-id
current-parent-span-id
generate-id
5.13 stone/  trace
5.13.1 Loading
load-trace
5.13.2 Event accessors
event-data
event-type
event-timestamp
event-ashlar-name
event-turn-number
5.13.3 Aggregators
tally-events
lifecycle-events
find-payloads
5.13.4 Formatting
format-lifecycle-line
5.13.5 CLI entry points
stone-trace-cli
run-tally
run-lifecycle
run-payload
5.14 stone/  lens
lens
lens?
lens-path
5.15 stone
5.15.1 Parameters
default-model
5.15.2 Schema builder
make-json-schema
5.15.3 Middleware onion types
context
middleware
recommendation
make-context
context-set
make-middleware
run-onion
recommend
5.16 Command-line interface
5.16.1 raco stone
5.16.1.1 Configuration
5.16.1.2 Exit codes
5.16.2 raco stone validate
5.16.2.1 Pipeline file requirements
5.16.2.2 Validation categories
5.16.2.3 Output
5.16.2.4 Exit codes
5.16.3 raco stone trace
5.16.3.1 payload options
5.16.3.2 Exit codes
5.16.4 raco stone install-skill
5.16.4.1 Exit codes
9.1

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)
The structure wrapping every ashlar. prop:procedure at position 0 means a ashlar-meta is callable: applying one to a DAG runs fn and returns a DAG.

  • fn — runtime function, (dag? -> (values node? dag?)).

  • 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
Wrap a (dag? -> node?) function as a atomic ashlar. Returning a non-failure node appends it to the DAG; returning a failure node leaves the DAG unchanged. #:name defaults to #:produces, else a fresh ashlar-* id. Emits 'ashlar-start and 'ashlar-end events on each run.

syntax

(~> ashlar ...)

Sequence primitive. Runs each ashlar in order, threading the DAG. produces-all is the union of children’s produces; queries is the subset of child queries not satisfied by earlier siblings. A sequence of zero ashlars returns an 'empty-sequence failure; it stops at the first failing child.

procedure

(run-pipeline ashlar dag)  
node? dag?
  ashlar : ashlar-meta?
  dag : dag?
Top-level entry point. Applies ashlar to dag and returns the last node and the updated DAG.

syntax

(ashlar-loop body #:until predicate #:max max-iterations)

 
predicate = (any-expr)
     
max-iterations = exact-nonnegative-integer?
Bounded repetition. #:until accepts a procedure (dag? -> any/c). Invoked after each body iteration with the loop’s accumulated DAG. The loop terminates when the predicate returns a true value or when #:max iterations have run. A failure from body aborts the loop immediately. Exhausting #:max emits a 'loop-exhausted failure. Iteration N+1 sees every node iteration N produced.

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
Macro; branches collected statically.

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.

See also Edge Primitives for the conceptual model and on-latest for wrapping a node-shaped extractor.

procedure

(on-latest pred)  procedure?

  pred : procedure?
Returns a procedure that, given a DAG, applies pred to (dag-latest-head dag). Use to wrap a node-shaped predicate or extractor as a dag-shaped one when only the latest head matters. When the DAG is empty, the wrapped predicate is called with #f; callers should only use on-latest where a head is guaranteed.

procedure

(ashlar-map extractor body [#:name name])  ashlar-meta?

  extractor : procedure?
  body : ashlar-meta?
  name : (or/c symbol? #f) = #f
Data-dependent fan-out. extractor is (node? -> list?); each lane runs against a snapshot DAG with a synthetic 'map-item node carrying the item. An empty extractor result produces 'map-empty. Must be followed by ashlar-reduce in any position where its last-result would be read.

procedure

(ashlar-parallel [#:name name] lane ...)  ashlar-meta?

  name : (or/c symbol? #f) = #f
  lane : ashlar-meta?
Static fan-out. Each lane runs against the pre-fan-out DAG. With zero lanes, produces 'parallel-empty. Must be followed by ashlar-reduce like ashlar-map.

procedure

(ashlar-reduce ashlar [#:name name])  ashlar-meta?

  ashlar : ashlar-meta?
  name : (or/c symbol? #f) = #f
Marks ashlar as a reducer. Runtime behavior is identical to ashlar; the wrapper exists as a topology marker for the validator and forwards schema and name from its child.

procedure

(make-ask-human channels    
  #:format-fn format-fn    
  #:name name    
  #:produces produces    
  [#:queries queries])  ashlar-meta?
  channels : ask-human-channel?
  format-fn : (dag? . -> . string?)
  name : symbol?
  produces : symbol?
  queries : (listof symbol?) = '()
Builds an ashlar that writes a question to the out channel, waits on in (produces a typed node with content (hasheq 'response answer)) or cancel (produces a node with an empty response). Typically the channel is built with make-ask-human-channel inside call-with-collected-ask-human-channels.

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
The single factory underneath every ashlar in the framework. Takes a (dag? -> dag?) body plus a walk rule and returns a ashlar-meta. Most users never call this directly — reach for it when building a new composition primitive.

procedure

(ashlar-produces s)  (or/c symbol? #f)

  s : any/c
Last symbol in ashlar-meta-produces-all, or #f if s isn’t an ashlar.

procedure

(ashlar-queries s)  (listof symbol?)

  s : any/c
The ashlar’s queries list, or () if s isn’t a ashlar.

procedure

(ashlar-produces-all s)  (listof symbol?)

  s : any/c
The ashlar’s full produces-all list, or () if s isn’t an ashlar.

5.2 stone/dagπŸ”—β„Ή

 (require stone/dag) package: Stone

Content-addressed, append-only typed DAG. See The DAG as Pipeline 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?
Node in the DAG. id is a SHA-1 digest over sorted parents, type, and content. type is the node-type symbol (e.g., 'data, 'failure, or any domain symbol). Prefer make-typed-node or typed-node over the raw constructor.

procedure

(make-typed-node parents type content [meta])  node?

  parents : (listof string?)
  type : symbol?
  content : any/c
  meta : hash? = (hash)
Creates a typed node with the given type. Id is hashed from sorted parents, type, and content.

procedure

(typed-node d type content)  node?

  d : dag?
  type : symbol?
  content : any/c
Convenience wrapper over make-typed-node that defaults parents to (dag-heads d) — the ~99% case. Use this in make-ashlar bodies.

procedure

(node-get n field [default])  any/c

  n : (or/c node? #f)
  field : symbol?
  default : any/c = #f
Pulls field out of n’s content hash. Returns default when n is #f, when content isn’t a hash, or when the key is missing.

procedure

(node-get* n path ...)  any/c

  n : (or/c node? #f)
  path : symbol?
Nested hash walk. Returns #f at any level that isn’t a hash or doesn’t contain the next path segment.

procedure

(node-text n)  string?

  n : (or/c node? #f)
Returns the text representation of n: the raw string content, or the 'text field of a hash content, or (format "~a" content) as a fallback. Returns "" when n is #f.

procedure

(make-failure-node parents kind reason [meta])  node?

  parents : (listof string?)
  kind : symbol?
  reason : string?
  meta : hash? = (hash)
Creates a typed node with type 'failure and content (hasheq 'kind kind 'reason reason). Invokes the failure-log-handler parameter; stone/logging installs a handler that emits an 'error-level event.

procedure

(failure-node? v)  boolean?

  v : any/c
#t iff v is a node? whose node-type is 'failure.

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?
nodes maps ids to nodes. heads is the current head list; new nodes are appended to the end. parent points at the enclosing DAG for composites’ sub-DAGs; label is the scope label (e.g. 'root, 'loop-body).

procedure

(make-dag [#:parent parent #:label label])  dag?

  parent : (or/c dag? #f) = #f
  label : symbol? = 'root
Empty DAG. The framework passes #:parent and #:label when building sub-DAGs; most user code calls (make-dag) for a fresh pipeline root.

procedure

(dag-append d n)  dag?

  d : dag?
  n : node?
Returns a new DAG with n in nodes, n’s parents removed from heads, and n’s id appended to the end of the heads list.

procedure

(dag-heads d)  (listof string?)

  d : dag?
Accessor for the heads list.

procedure

(dag-latest-head d)  (or/c node? #f)

  d : dag?
Returns the most recently appended head node, or #f if the DAG has no nodes. Shorthand for resolving the last id in dag-heads against dag-nodes.

procedure

(dag-nodes d)  (hash/c string? node?)

  d : dag?
Accessor for the node table.

procedure

(dag-failed? d)  boolean?

  d : dag?
#t when the most recent head is a failure node.

procedure

(dag-nearest-ancestor d type)  (or/c node? #f)

  d : dag?
  type : symbol?
Walks first-parent pointers from the DAG’s most recently appended head until it finds a node whose node-type is type. Returns the node, or #f if none is reachable on the first-parent line. When the DAG has no heads, returns #f.

procedure

(dag-collect-until d    
  #:type collect-type    
  #:until sentinel-type)  (listof node?)
  d : dag?
  collect-type : symbol?
  sentinel-type : symbol?
Walks first-parent pointers from the most recently appended head, collecting nodes whose node-type is collect-type, stopping at the first ancestor whose node-type is sentinel-type. The sentinel node itself is the boundary and is excluded from the result.

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
Returns every node of the given type, sorted oldest-first by timestamp. Single in-scope scan over (dag-nodes d). The #:scope argument is retained as a no-op for source compatibility; passing 'conversation yields the same result as omitting it.

procedure

(dag-select d nid)  (listof node?)

  d : dag?
  nid : string?
Walks first-parent pointers from nid back to the root. Returns the nodes oldest-first. Used internally to rebuild a linear conversation from a branching DAG.

procedure

(dag-select-window d nid n)  (listof node?)

  d : dag?
  nid : string?
  n : exact-nonnegative-integer?
Returns the last n nodes of (dag-select d nid). If the linear history is shorter than n, returns the whole history.

procedure

(dag-merge dags)  dag?

  dags : (listof dag?)
Unions the node tables, concatenates and de-duplicates the heads, and uses the root of the first input.

procedure

(failure-log-handler)

  (parameter/c (or/c (symbol? string? hash? . -> . any) #f))
Default #f. When set, make-failure-node calls (handler kind reason meta) before returning the node. stone/logging installs a handler at module load that emits an 'error-level 'stone-event.

5.3 stone/messagesπŸ”—β„Ή

 (require stone/messages) package: Stone

A single turn in an LLM conversation. Self-contained: no link to a pipeline DAG. Used as the element type of the 'conversation list that make-agent-ashlar embeds in its result node.

struct

(struct message (role content tool-calls call-id metadata))

  role : symbol?
  content : (or/c string? hash? (listof hash?))
  tool-calls : list?
  call-id : (or/c string? #f)
  metadata : hash?
A turn in a conversation.

  • 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)
Extract a plain-string view of m’s content, tolerating every shape content can take. When m is #f, returns "". Plays the same role for messages that node-text plays for typed nodes: when new content shapes are added, this helper grows so consumer code does not have to.

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 
  [#:queries queries 
  #:name name 
  #:schema schema 
  #:middleware middleware 
  #:decide decide 
  #:system system-fn 
  #:user user-fn 
  #:max-turns max-turns 
  #:response-format response-format 
  #:model model 
  #:budget budget 
  #:adversary adversary 
  #:heal-with healer 
  #:max-healing max-healing 
  #:outbox outbox 
  #:finalize finalize]) 
  ashlar-meta?
  caller : procedure?
  produces : symbol?
  queries : (listof symbol?) = '()
  name : (or/c symbol? #f) = #f
  schema : (or/c hash? #f) = #f
  middleware : (listof any/c) = '()
  decide : procedure? = continue-on-tool-use
  system-fn : (dag? . -> . string?) = (lambda (dag) "")
  user-fn : (dag? . -> . string?) = (lambda (dag) "")
  max-turns : exact-positive-integer? = 15
  response-format : (or/c hash? #f) = #f
  model : (or/c string? #f) = #f
  budget : exact-nonnegative-integer? = 16384
  adversary : (or/c ashlar-meta? #f) = #f
  healer : (or/c ashlar-meta? #f) = #f
  max-healing : exact-nonnegative-integer? = 3
  outbox : any/c = #f
  finalize : (or/c (any/c . -> . node?) #f) = #f
Runs a multi-turn agent with its own internal loop and conversation list. The agent threads a (listof message) through each turn, preserving full structured content (including tool call blocks) across turns. Produces one typed node at the end; the agent’s conversation is embedded in the result node’s content under the key 'conversation.

  • caller — LLM caller built by make-openai-caller or make-anthropic-caller.

  • #:produces — node type of the final result.

  • #:queries — node types read from the outer DAG.

  • #: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.

  • #:system — builds the system prompt from the DAG.

  • #:user — builds the first user message from the DAG.

  • #: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.

  • #:adversary — quality-gate ashlar. Runs against the pipeline 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.

  • #:outbox — optional outbox for engine→observer events. When set, emits one 'turn-event per LLM turn and forwards per-token 'token-events from the streaming caller. The Anthropic caller accepts but ignores this argument.

  • #: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/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 pipeline code generally does not.

5.5.1 ResponsesπŸ”—β„Ή

struct

(struct llm-response (text tool-calls usage))

  text : string?
  tool-calls : (listof tool-call?)
  usage : hash?
Stone’s internal representation of an LLM response. text is the assistant’s text output; tool-calls is the list of tool invocations requested by the model; usage carries provider-specific token-usage stats and is opaque to Stone.

struct

(struct tool-call (id name input))

  id : string?
  name : string?
  input : hash?
A tool invocation requested by the LLM. id is an opaque identifier that round-trips through the tool result; name selects which tool to invoke; input is the hash of arguments.

5.5.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?
Raised by call-llm-openai when a streaming session’s watcher trips. hit is the repetition-hit from the detector; partial is the channel text streamed before the trip (response-text if the response watcher tripped; thinking-text otherwise). make-openai-caller’s retry layer catches this internally; user code only sees this exception if the retry layer itself is bypassed.

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)
Raised by make-openai-caller when the retry budget is exhausted. attempts is the configured #:max-retries value — the retry budget that was exhausted, not the actual call count. last-hit is the most recent repetition-hit, or #f if none was captured. Caught by the agent layer and converted to a 'failure-typed node with content kind 'output-degenerate.

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?
Raised by the streaming LLM caller when the server returns a non-2xx HTTP response. Without this, an HTTP 400 (e.g. malformed conversation) would come back as an empty SSE stream and the agent loop would silently treat it as a successful empty response, burning turns until #:max-turns exhausts. status is the HTTP status code; body is the response body (typically a JSON error).

struct

(struct exn:fail:llm-empty-response exn:fail ()
    #:extra-constructor-name make-exn:fail:llm-empty-response)
Raised by the agent layer when the model produces no text AND no tool calls. The agent loop has nothing to act on; without this guard, the loop would silently advance through empty turns until #:max-turns exhausts. The agent’s outer with-handlers converts this to an 'agent-empty-response failure node.

5.6 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
Returns a procedure with signature (messages system model max-tokens tools response-format [outbox]) -> hash?. POSTs an OpenAI-compatible chat-completion request and normalizes the response to Anthropic content-block shape. Always streams via SSE. When outbox is provided, additionally emits 'token-events to it.

#: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
Returns a procedure with the same signature as make-openai-caller’s. POSTs an Anthropic messages request with anthropic-version: 2023-06-01. The outbox argument is accepted but ignored — Anthropic streaming isn’t yet implemented. Reserved keys '(model system messages max_tokens tools response_format) raise at call time if overridden through #:extra-body.

#: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)
Low-level POST against an Anthropic-compatible messages endpoint. Returns the parsed JSON response. make-anthropic-caller wraps this.

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    
  #:outbox outbox    
  #: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)
  outbox : any/c = #f
  response-watcher : (or/c repetition-watcher? #f) = #f
  thinking-watcher : (or/c repetition-watcher? #f) = #f
Low-level POST against an OpenAI-compatible chat-completions endpoint. Converts Anthropic tool schemas to OpenAI function schemas, POSTs the request, and normalizes the response to Anthropic content-block shape. Always streams via SSE, parsing <think>...</think> markers into 'thinking token events. make-openai-caller wraps this.

#: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?
Returns the text of the first content block whose type is "text" in a normalized response, or "" if there is none.

5.6.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.7 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.7.1 HitsπŸ”—β„Ή

struct

(struct repetition-hit (kind detail position)
    #:extra-constructor-name make-repetition-hit)
  kind : symbol?
  detail : any/c
  position : exact-nonnegative-integer?
Reports what tripped the watcher. kind is 'ngram (verbatim n-gram repeat) or 'compression (sliding-window deflate ratio dropped below threshold for a streak of ticks). detail is the offending bytes? window for 'ngram hits, or a (listof real?) ratio history (head = newest) for 'compression hits. position is the total bytes seen at trip time.

5.7.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
Constructs a counter with the given window size and trip threshold. Defaults are tuned to ignore structural repetition in enumerated JSON output (e.g. descriptions in a list of behaviors) while still catching verbatim loops; the compression detector covers near-repetition that escapes this stricter bar.

procedure

(ngram-counter-add! c byte)  void?

  c : ngram-counter?
  byte : byte?
Feeds one byte to the counter.

procedure

(ngram-counter-tripped c)  (or/c #f repetition-hit?)

  c : ngram-counter?
Returns the repetition-hit if tripped, otherwise #f.

5.7.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
Constructs a detector with the given window size, ratio threshold, and history length.

procedure

(compression-detector-add! d byte)  void?

  d : compression-detector?
  byte : byte?
Feeds one byte to the detector. Cheap; never runs deflate.

procedure

(compression-detector-check d)  void?

  d : compression-detector?
Runs deflate on the current window, computes the ratio, and updates history. Call this on each streaming-progress tick (every current-streaming-emit-interval ms by default — i.e., once every 5 seconds in production).

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?
Returns the repetition-hit if tripped, otherwise #f.

5.7.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))
Constructs a watcher. Pass #f for either detector to disable that channel. The on-trip callback is invoked at most once, on the first trip from either detector.

procedure

(repetition-watcher-add-bytes! w bs)  void?

  w : repetition-watcher?
  bs : bytes?
Feeds bytes to whichever detectors are non-#f. After feeding, runs the trip check; if newly tripped, fires on-trip.

procedure

(repetition-watcher-tick! w)  void?

  w : repetition-watcher?
Runs compression-detector-check (no-op if compression is disabled). Called by maybe-emit-progress! inside the streaming session at the every-5s tempo.

procedure

(repetition-watcher-tripped w)  (or/c #f repetition-hit?)

  w : repetition-watcher?
Returns the captured repetition-hit if either detector has tripped, otherwise #f.

5.8 stone/toolsπŸ”—β„Ή

 (require stone/tools) package: Stone

Middleware constructors for LLM tool calls plus the ask-human channel primitives. See Agents and Tools and Ask Human for the conceptual models.

5.8.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
Returns a middleware that injects schema into the context’s tools list and dispatches matching tool_use blocks to handler. handler must return (values string? hash?) — the display text shown to the next turn and a structured meta hash. #:allowed-paths rejects calls whose path argument isn’t under one of these prefixes. #:confirm? #t causes the tool to refuse to run and recommend a 'loop with category 'approval-needed.

procedure

(has-tool-call-for? ctx tool-name)  boolean?

  ctx : any/c
  tool-name : symbol?
#t when the last response’s content contains a tool_use block whose name equals (symbol->string tool-name).

procedure

(extract-tool-calls response)  (listof hash?)

  response : hash?
Every content block in response whose type is "tool_use".

procedure

(tool-schema mw)  (or/c hash? #f)

  mw : any/c
Returns the JSON-schema hash that was passed to make-tool when mw was built, or #f if mw was not registered. Backed by a process-wide weak table keyed on middleware identity.

5.8.2 Built-in tool middlewareπŸ”—β„Ή

procedure

(read-file [#:allowed-paths allowed-paths])  any/c

  allowed-paths : (or/c (listof string?) #f) = #f
Creates a read_file tool middleware. Returns file contents; supports optional offset and limit tool arguments for a line-range slice.

procedure

(write-file [#:allowed-paths allowed-paths    
  #:confirm? confirm?])  any/c
  allowed-paths : (or/c (listof string?) #f) = #f
  confirm? : boolean? = #f
Creates a write_file tool middleware. Creates parent directories as needed and overwrites the target file.

procedure

(edit-file [#:allowed-paths allowed-paths    
  #:confirm? confirm?])  any/c
  allowed-paths : (or/c (listof string?) #f) = #f
  confirm? : boolean? = #f
Creates an edit_file tool middleware. Refuses the call if old_string isn’t found or isn’t unique in the target file.

procedure

(delete-file* [#:allowed-paths allowed-paths    
  #:confirm? confirm?])  any/c
  allowed-paths : (or/c (listof string?) #f) = #f
  confirm? : boolean? = #t
Creates a delete_file tool middleware. Note that #:confirm? defaults to #t.

procedure

(list-directory [#:allowed-paths allowed-paths])  any/c

  allowed-paths : (or/c (listof string?) #f) = #f
Creates a list_directory tool middleware.

procedure

(mkdir* [#:allowed-paths allowed-paths])  any/c

  allowed-paths : (or/c (listof string?) #f) = #f
Creates an mkdir tool middleware that creates a directory and any missing parents.

procedure

(file-exists* [#:allowed-paths allowed-paths])  any/c

  allowed-paths : (or/c (listof string?) #f) = #f
Creates a file_exists tool middleware returning "true" or "false".

procedure

(run-command [#:timeout default-timeout    
  #:confirm? confirm?])  any/c
  default-timeout : real? = 60000
  confirm? : boolean? = #f
Creates a run_command tool middleware. Runs a shell command synchronously under /bin/sh -c, waits up to timeout milliseconds, and returns the concatenated exit code, stdout, and stderr. Timed-out commands are killed.

procedure

(start-command [#:timeout default-timeout])  any/c

  default-timeout : real? = 300000
Creates a start_command tool middleware. Spawns the command in the background and returns a handle id.

procedure

(check-command)  any/c

Creates a check_command tool middleware. Given a handle id, returns "running" or the final exit/stdout/stderr.

procedure

(wait-commands)  any/c

Creates a wait_commands tool middleware. Given a list of handle ids, waits (up to each handle’s stored timeout) for them all to finish and returns a combined report.

5.8.3 Ask-human channelsπŸ”—β„Ή

struct

(struct ask-human-channel (name out in cancel)
    #:transparent)
  name : symbol?
  out : any/c
  in : any/c
  cancel : any/c
name is the channel identifier. out is where the ashlar writes questions; in is where the UI writes answers; cancel is the abort signal.

procedure

(make-ask-human-channel name out in cancel)  ask-human-channel?

  name : symbol?
  out : any/c
  in : any/c
  cancel : any/c
Smart constructor. Builds an ask-human-channel and, when called within the dynamic extent of call-with-collected-ask-human-channels, registers it in the currently active collector. Outside any collector, construction is inert. Raises if name collides with another channel already registered in the same collector.

Runs thunk with a collector active and returns both thunk’s value and the list of channels built during its dynamic extent. Only the outermost call creates a fresh collector table; nested calls reuse the outer collector’s table, so any channel built at any depth lands in the outermost list. Nested calls return the delta they added during their own thunk’s extent.

5.9 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)
Default #:decide value on make-agent-ashlar. Halts if any middleware emitted a 'halt recommendation; loops while the recommendation list is non-empty; continues otherwise. Reach for this when the agent’s job is "think, call tools as needed, then reply" and any outstanding tool activity is the signal to keep going.

procedure

(tool-directed ctx recs)  any/c

  ctx : any/c
  recs : (listof any/c)
Stricter variant: loops only when a middleware explicitly recommends 'loop. An empty recommendation list means the agent is done — it’s the tool middlewares’ responsibility to ask for another turn. Use this when you want the tool layer to drive the conversation deliberately, e.g. a workflow where "no tool fired" means "time to finalize the answer."

5.10 stone/validateπŸ”—β„Ή

 (require stone/validate) package: Stone

Static checks over a composed pipeline. See Validation for the conceptual model.

procedure

(validate-pipeline pipeline)  validation-result?

  pipeline : ashlar-meta?
Walks the ashlar tree, dispatching on each ashlar’s validate-walk rule. Collects a 'missing-producer error when an ashlar queries a node type no upstream sibling produces, a 'maybe-unavailable warning when a type is produced by some match branches but not all, an 'invalid-lens error when a ashlar-match has a lens whose path isn’t in the upstream schema, and a 'fanout-not-reduced error when a fan-out isn’t immediately followed by a ashlar-reduce.

struct

(struct validation-result (errors)
    #:transparent)
  errors : (listof validation-error?)
Container for the errors and warnings collected by validate-pipeline. Prefer validation-ok? and validation-errors for inspection.

struct

(struct validation-error (type ashlar-name queried-type message)
    #:transparent)
  type : symbol?
  ashlar-name : (or/c symbol? #f)
  queried-type : symbol?
  message : string?
type is one of 'missing-producer, 'maybe-unavailable, 'invalid-lens, or 'fanout-not-reduced. ashlar-name identifies the offending ashlar. queried-type is the node type or lens path segment at fault. message is a human-readable description.

procedure

(validation-ok? r)  boolean?

  r : validation-result?
#t when r has no hard errors. 'maybe-unavailable warnings don’t make the result not-ok.

Returns the error list inside r.

procedure

(enumerate-ashlars pipeline)  (listof symbol?)

  pipeline : ashlar-meta?
Every named ashlar in the tree, depth-first pre-order. Unnamed ashlars are skipped. See Validate a topology for the orphan-ashlar guard pattern built on this.

procedure

(enumerate-paths pipeline)  (listof (listof symbol?))

  pipeline : ashlar-meta?
Every distinct execution path through the pipeline as a list of ashlar names. A match with n branches contributes n paths; a sequence concatenates its children’s paths; loops and reduce pass through their child’s paths.

5.11 stone/testπŸ”—β„Ή

 (require stone/test) package: Stone

Test harness for Stone pipelines. 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.11.1 HarnessesπŸ”—β„Ή

syntax

(with-live-harness #:caller caller
                   maybe-strict
                   maybe-timeout
                   body ...)
 
maybe-strict = 
  | #:strict-tools? strict?
     
maybe-timeout = 
  | #:timeout seconds
Starts a recorder on stone-logger, parameterizes harness-current-caller, and evaluates body. When #:strict-tools? #t, every recorded tool call must have been installed via ashlar-with-tool-stub; otherwise the harness raises on exit. #:timeout runs body on a background thread; exceeding seconds breaks the thread and raises.

syntax

(with-mock-harness #:caller caller
                   maybe-strict
                   body ...)
(with-mock-harness #:responses responses
                   maybe-strict
                   body ...)
 
maybe-strict = 
  | #:strict-tools? strict?
Same setup as with-live-harness but synchronous with no timeout. Exactly one of #:caller or #:responses must be supplied. #:responses installs an internal caller that pops one response per call from the list; running the queue to empty raises.

procedure

(ashlar-with-tool-stub s    
  tool-name    
  stub-handler)  ashlar-meta?
  s : ashlar-meta?
  tool-name : symbol?
  stub-handler : (hash? . -> . any)
Returns a new ashlar identical to s except the tool middleware named tool-name is replaced by a fresh middleware built with the stored schema and stub-handler. Recurses through composition children. Raises when tool-name isn’t found, when multiple middlewares share the name, or when an ashlar has no rebuilder.

5.11.2 AssertionsπŸ”—β„Ή

procedure

(tool-calls)  (listof hash?)

Every recorded tool-call record in call order. Drains pending log events before reading. Returns '() when called outside a harness.

procedure

(tool-calls-by-name name)  (listof hash?)

  name : symbol?
Filters (tool-calls) down to records whose 'name key is eq? to name.

procedure

(tool-call-count name)  exact-nonnegative-integer?

  name : symbol?

syntax

(check-tool-called? name)

(check-tool-called? name msg)
Rackunit-style assertion. Passes when (tool-call-count name) > 0.

Rackunit-style assertion. Passes when (tool-call-count name) is zero.

syntax

(check-tool-call-count name n)

(check-tool-call-count name n msg)
Rackunit-style assertion. Passes when (tool-call-count name) equals n.

5.11.3 Stub helpersπŸ”—β„Ή

procedure

(stub-answer s)  (hash? . -> . any)

  s : string?
Returns a constant tool handler that ignores its input and returns (values s (hasheq 'stub #t)).

procedure

(stub-fn f)  (hash? . -> . any)

  f : (hash? . -> . any)
Identity passthrough; returns f unchanged. Exists so tests can opt into the explicit (stub-fn ...) form alongside (stub-answer ...) for documentation value.

5.11.4 Response buildersπŸ”—β„Ή

procedure

(llm-text s)  any/c

  s : string?
An llm-response with content s and no tool calls.

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
An llm-response carrying exactly one tool-call block and an empty text body. #:question is sugar: when non-false and #:input is false, builds (hasheq 'question question) as the input.

procedure

(llm-multi text-or-response resp ...)  any/c

  text-or-response : (or/c string? any/c)
  resp : any/c
Variadic combiner. If the first argument is a string, it becomes the text of the new response and tool calls are collected from the remaining arguments. If it’s an llm-response, its text is dropped and its tool calls plus every subsequent response’s tool calls are concatenated.

5.11.5 ParametersπŸ”—β„Ή

parameter

(harness-current-caller)  (or/c procedure? #f)

(harness-current-caller c)  void?
  c : (or/c procedure? #f)
 = #f
Bound by with-live-harness and with-mock-harness to the caller installed for the harness. Read inside body to pass the caller to make-agent-ashlar.

5.12 stone/loggingπŸ”—β„Ή

 (require stone/logging) package: Stone

Structured logging on a dedicated Racket logger. See Observability for the design.

Racket logger named ashlar, created with #:parent #f so events do not propagate to the root logger. Attach a log receiver to this logger to collect events.

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 ...)

Level-specific macros generated by define-logger for stone-logger. See Racket’s define-logger for argument shapes. Prefer stone-event for structured events.

procedure

(stone-event level event data)  void?

  level : (or/c 'debug 'info 'warning 'error 'fatal)
  event : symbol?
  data : hash?
Adds event, trace-id, span-id, parent-span-id, and timestamp to data, then logs via stone-logger only when level is enabled.

parameter

(current-trace-id)  (or/c string? #f)

(current-trace-id id)  void?
  id : (or/c string? #f)
 = #f
Identifies a full pipeline run; set once at the top and inherited by every ashlar below.

parameter

(current-span-id)  (or/c string? #f)

(current-span-id id)  void?
  id : (or/c string? #f)
 = #f
Identifies the currently executing ashlar. make-ashlar’s wrapper parameterizes this to a fresh span-* id for each invocation.

parameter

(current-parent-span-id)  (or/c string? #f)

(current-parent-span-id id)  void?
  id : (or/c string? #f)
 = #f
Holds the enclosing ashlar’s span id. make-ashlar’s wrapper sets this from the caller’s current-span-id before allocating a new span.

procedure

(generate-id [prefix])  string?

  prefix : string? = ""
Returns "<prefix><ms>-<random>" where <ms> is the truncated millisecond clock and <random> is a 5-digit number. Not cryptographically strong; intended for log correlation.

5.13 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.13.1 LoadingπŸ”—β„Ή

procedure

(load-trace path)  (listof hash?)

  path : path-string?
Read a JSONL trace file into a list of event hashes, in file order. Each event is a hash with keys 'level, 'message, 'topic, and 'data; the event-specific payload lives under 'data. Use the accessors below to read fields without digging.

5.13.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?
Return the event-specific payload hash. Most events carry their fields here; the other accessors all read from this.

procedure

(event-type e)  string?

  e : hash?
The event-type string (e.g. "api-call", "tool-dispatch"). Empty string if absent.

procedure

(event-timestamp e)  real?

  e : hash?
Millisecond-epoch timestamp. 0 if absent.

procedure

(event-ashlar-name e)  string?

  e : hash?
Best-effort ashlar/agent/tool name. Falls back through 'ashlar-name, 'agent-ashlar-name, and 'tool-name because different event types use different keys. Empty string if none of those are present.

procedure

(event-turn-number e)  (or/c exact-nonnegative-integer? #f)

  e : hash?
LLM turn number for api-call/api-response events. #f for events that don’t carry one.

5.13.3 AggregatorsπŸ”—β„Ή

procedure

(tally-events events)

  (listof (cons/c string? exact-nonnegative-integer?))
  events : (listof hash?)
Return an alist of (cons event-type count) sorted by count descending. The shape used by raco stone trace tally and useful for a first-pass overview of what happened.

procedure

(lifecycle-events events)  (listof hash?)

  events : (listof hash?)
Filter events down to the ones that tell the story of how the orchestrator progressed: ashlar lifecycle, tool dispatch, api-call/response, streaming progress, and known failure events. Excludes noise like middleware-run. The set is defined inside stone/trace and not configurable; if you need different filtering, use filter with your own predicate.

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
Return "api-call-payload" events in file order, optionally filtered by ashlar name and/or turn number. Useful when you want to inspect the exact messages an ashlar sent to its caller.

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.13.4 FormattingπŸ”—β„Ή

procedure

(format-lifecycle-line e)  string?

  e : hash?
Render one lifecycle event as a single human-readable line with a HH:MM:SS local-time prefix, the event type, the ashlar name, and event-type-specific extras (e.g., turn=5 msgs=19 for "api-call"). Used by raco stone trace lifecycle.

5.13.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

Dispatch the trace subcommand based on current-command-line-arguments. Reads the next argv token as the sub-subcommand name ("tally", "lifecycle", "payload") and forwards remaining args to the matching runner.

procedure

(run-tally path)  any

  path : path-string?
Print the total event count and the type tally to current output. Equivalent to raco stone trace tally <path>.

procedure

(run-lifecycle path)  any

  path : path-string?
Walk lifecycle events in chronological order, one format-lifecycle-line per output line. Equivalent to raco stone trace lifecycle <path>.

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
Print a single "api-call-payload" event: ashlar, turn, model, system prompt (first 400 chars), and one line per message with role + content preview + tool-call count.

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.14 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.

procedure

(lens path ...)  any/c

  path : symbol?
Returns a lens that, applied to a node, walks the node’s content hash through the given path. Used as the extractor in (ashlar-match (lens 'field) ...) to branch on a field value.

procedure

(lens? v)  boolean?

  v : any/c
#t iff v was built by lens.

procedure

(lens-path l)  (listof symbol?)

  l : any/c
Returns the path list stored in l, or #f if l isn’t a lens.

5.15 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.15.1 ParametersπŸ”—β„Ή

parameter

(default-model)  (or/c string? #f)

(default-model m)  void?
  m : (or/c string? #f)
 = #f
Global fallback model identifier, read by make-context when #:model is absent and by make-agent-ashlar when #:model is absent.

5.15.2 Schema builderπŸ”—β„Ή

procedure

(make-json-schema name properties required)  hash?

  name : string?
  properties : hash?
  required : (listof (or/c symbol? string?))
Returns a hash shaped as
(hasheq 'type "json_schema"
        'json_schema
        (hasheq 'name name
                'strict #t
                'schema (hasheq 'type "object"
                                'properties properties
                                'required required
                                'additionalProperties #f)))
suitable for the #:response-format argument of make-agent-ashlar or a caller directly. Symbols in required are coerced to strings.

5.15.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
    history
    cursor
    node-draft)
    #: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?
  history : (or/c any/c #f)
  cursor : (or/c string? #f)
  node-draft : (or/c hash? #f)
Per-turn state threaded through the middleware onion. meta holds 'trace-id, 'span-id, 'agent-name, 'turn-number, 'last-response, 'recommendations.

struct

(struct middleware (name guard handler)
    #:transparent)
  name : symbol?
  guard : (context? . -> . boolean?)
  handler : (context? (context? . -> . context?) . -> . context?)
Middleware record.

struct

(struct recommendation (type source reason)
    #:transparent)
  type : (or/c 'continue 'halt 'loop)
  source : any/c
  reason : any/c
Recommendation emitted by middleware or a decide function.

procedure

(make-context [#:messages messages    
  #:system system    
  #:tools tools    
  #:response-format response-format    
  #:model model    
  #:budget budget    
  #:meta meta    
  #:history history    
  #:cursor cursor    
  #:node-draft node-draft])  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)
  history : any/c = #f
  cursor : (or/c string? #f) = #f
  node-draft : (or/c hash? #f) = #f
Default constructor. #:model is read from default-model at call time when not supplied.

procedure

(context-set ctx    
  [#:messages messages    
  #:system system    
  #:tools tools    
  #:response-format response-format    
  #:model model    
  #:budget budget    
  #:meta meta    
  #:history history    
  #:cursor cursor    
  #:node-draft node-draft])  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
  history : any/c = #f
  cursor : any/c = #f
  node-draft : any/c = (void)
Returns a new context with the specified fields changed. Every field except #:node-draft uses (or new old), so passing #f means "keep," not "clear." #:node-draft uses a private sentinel, so passing #:node-draft #f clears the draft.

procedure

(make-middleware name guard handler)  middleware?

  name : symbol?
  guard : (context? . -> . boolean?)
  handler : (context? (context? . -> . context?) . -> . context?)
Constructor.

procedure

(run-onion middleware-list ctx caller)  context?

  middleware-list : (listof middleware?)
  ctx : context?
  caller : (context? . -> . context?)
Executes the middleware chain around caller. For each middleware whose guard returns #t, the handler is invoked with (ctx inner-call); otherwise the handler is bypassed and the inner call runs directly. Emits 'middleware-run debug events.

procedure

(recommend ctx type source reason)  context?

  ctx : context?
  type : (or/c 'continue 'halt 'loop)
  source : any/c
  reason : any/c
Appends a recommendation to the 'recommendations list under (context-meta ctx).

5.16 Command-line interfaceπŸ”—β„Ή

Stone’s command-line surface: the raco stone TUI launcher and the validate, trace, and install-skill subcommands.

5.16.1 raco stoneπŸ”—β„Ή

Synopsis: raco stone [flags...]

Loads a Stone configuration (if present), merges any command-line flag overrides, and launches the Stone TUI with the resulting agent ashlar and its collected ask-human channels.

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.

Both flags are #:once-each. No positional arguments are accepted.

5.16.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 thunk named build-agent. The loader invokes the thunk inside call-with-collected-ask-human-channels. The thunk’s return value is a ashlar-meta? — the topology to run. Any ask-human channels built during construction flow to the TUI automatically.

#lang racket

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

(provide build-agent)

(define caller (make-openai-caller #:url "http://localhost:8000"))

(define (build-agent)

  (make-agent-ashlar caller

    #:produces 'response

    #:system (lambda (dag) "You are helpful.")

    #:user   (lambda (dag) "")))

5.16.1.2 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.16.2 raco stone validateπŸ”—β„Ή

Synopsis: raco stone validate <pipeline-file>

Loads a Racket file, extracts the binding named pipeline, and runs validate-pipeline on it. Prints errors and warnings; sets exit code from the result.

5.16.2.1 Pipeline 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 pipeline.

  • Bind pipeline to a ashlar-meta? value.

If the file doesn’t export pipeline, the CLI prints Error: <file> does not export pipeline and exits 1.

5.16.2.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.16.2.3 OutputπŸ”—β„Ή

On success:

Pipeline 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.16.2.4 Exit codesπŸ”—β„Ή

Code

  

Meaning

0

  

Pipeline valid, or only maybe-unavailable warnings present.

1

  

Missing pipeline-file argument, file doesn't export pipeline, hard errors reported, or subcommand other than validate supplied.

5.16.3 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.16.3.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.16.3.2 Exit codesπŸ”—β„Ή

Code

  

Meaning

0

  

Subcommand ran successfully.

1

  

Missing path, missing required option value, unknown subcommand, or unrecognized argument.

5.16.4 raco stone install-skillπŸ”—β„Ή

Synopsis: raco stone install-skill [force] [target-dir DIR]

Install the designing-ashlars skill bundle into ~/.claude/skills/ by default. The skill ships with Stone and contains the guided interview for scoping an ashlar plus a scribblings snapshot for the design agent to read.

Flag

  

Description

force

  

Overwrite an existing installation. Without this, the command refuses to clobber an existing skill directory.

target-dir DIR

  

Install under DIR instead of HOME/.claude/skills/. The skill ends up at DIR/designing-ashlars/.

The command is idempotent in spirit: re-running it without force after a successful install exits non-zero with an already installed message rather than silently doing nothing. With force it wipes and re-ships the bundle, picking up any updates to the scribblings snapshot.

5.16.4.1 Exit codesπŸ”—β„Ή

Code

  

Meaning

0

  

Install (or forced re-install) succeeded.

1

  

Skill already installed and @tt{--force} not supplied, or filesystem error during install.