Liking cljdoc? Tell your friends :D

toolnexus.agents.compaction

Context compaction (SPEC.md §7F) — keep a long-lived agent under budget.

A long-lived agent grows its transcript until it overflows the model's window. compactor returns a §8 :before-llm hook that summarizes the older transcript and keeps a recent tail. It rides the seam that already exists: the loop applies a :before-llm message rewrite by REPLACING the working transcript, and that vector flows on into the run result and the conversation store. So compaction is a pure messages -> messages helper and adds no loop behavior — it is the canonical use of the :before-llm hook.

Three invariants (SPEC §7F):

  1. At or below :max-tokens the hook is a NO-OP — it returns nil and the run is byte-identical to a run with no compactor.
  2. Tool-pair safety — the retained tail always begins at a user turn, so no tool message is ever orphaned from the assistant carrying its tool_call_id.
  3. The leading system prompt (identity / soul / skills) is preserved verbatim; only the body between it and the tail is summarized.

Messages are the same maps toolnexus.client builds: :role, :content, and where present :tool_calls / :tool_call_id.

(client/create {:hooks {:before-llm (compaction/compactor
                                      {:max-tokens 120000
                                       :summarize  my-summarizer})}})
Context compaction (SPEC.md §7F) — keep a long-lived agent under budget.

A long-lived agent grows its transcript until it overflows the model's window.
`compactor` returns a §8 `:before-llm` hook that summarizes the older transcript
and keeps a recent tail. It rides the seam that already exists: the loop applies
a `:before-llm` message rewrite by REPLACING the working transcript, and that
vector flows on into the run result and the conversation store. So compaction is
a pure `messages -> messages` helper and adds no loop behavior — it is the
canonical use of the `:before-llm` hook.

Three invariants (SPEC §7F):

  1. At or below `:max-tokens` the hook is a NO-OP — it returns nil and the run is
     byte-identical to a run with no compactor.
  2. Tool-pair safety — the retained tail always begins at a `user` turn, so no
     `tool` message is ever orphaned from the `assistant` carrying its
     `tool_call_id`.
  3. The leading system prompt (identity / soul / skills) is preserved verbatim;
     only the body between it and the tail is summarized.

Messages are the same maps `toolnexus.client` builds: `:role`, `:content`, and
where present `:tool_calls` / `:tool_call_id`.

    (client/create {:hooks {:before-llm (compaction/compactor
                                          {:max-tokens 120000
                                           :summarize  my-summarizer})}})
raw docstring

compactorclj/s

(compactor {:keys [max-tokens summarize keep-tail count-tokens
                   flush-to-memory]})

Build a §8 :before-llm hook that compacts the transcript once it exceeds :max-tokens.

Below budget it returns nil (a byte-identical no-op); above it, it replaces the transcript with

[leading system prompt, summary system message, (flush reminder?), ...tail]

splitting at a clean user boundary so tool groups are never broken.

:max-tokens compact only when the estimate exceeds this; at/below => no-op (REQUIRED) :summarize (fn [older] "summary") — produces the summary. MAY call an LLM; the library makes no model call on the host's behalf (REQUIRED) :keep-tail keep at least this many tokens of the most recent tail (default: half of :max-tokens) :count-tokens (fn [messages] n) — token estimate (default estimate-tokens) :flush-to-memory when true, inject a pre-compact system reminder telling the model to persist durable facts through the §7E memory tool before the head is summarized (off by default)

Build a §8 `:before-llm` hook that compacts the transcript once it exceeds
`:max-tokens`.

Below budget it returns nil (a byte-identical no-op); above it, it replaces the
transcript with

    [leading system prompt, summary system message, (flush reminder?), ...tail]

splitting at a clean `user` boundary so tool groups are never broken.

  :max-tokens       compact only when the estimate exceeds this; at/below => no-op
                    (REQUIRED)
  :summarize        (fn [older] "summary") — produces the summary. MAY call an
                    LLM; the library makes no model call on the host's behalf
                    (REQUIRED)
  :keep-tail        keep at least this many tokens of the most recent tail
                    (default: half of :max-tokens)
  :count-tokens     (fn [messages] n) — token estimate (default `estimate-tokens`)
  :flush-to-memory  when true, inject a pre-compact system reminder telling the
                    model to persist durable facts through the §7E `memory` tool
                    before the head is summarized (off by default)
sourceraw docstring

estimate-tokensclj/s

(estimate-tokens messages)

Cheap, deterministic token estimate: ceil(chars/4) over each message's JSON serialization, summed.

An ESTIMATOR, not a tokenizer — exactness is the host's call, which is why :count-tokens exists. The counts are not promised to be equal across ports: each port serializes with its own JSON writer, and SPEC §7F pins the formula, not the byte count.

Cheap, deterministic token estimate: `ceil(chars/4)` over each message's JSON
serialization, summed.

An ESTIMATOR, not a tokenizer — exactness is the host's call, which is why
`:count-tokens` exists. The counts are not promised to be equal across ports:
each port serializes with its own JSON writer, and SPEC §7F pins the formula,
not the byte count.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close