Skip to content

Context

Auto-generated from source docstrings (mkdocstrings). The complete public contract is agentmaker.__all__ plus each subpackage __all__.

agentmaker.context: context engineering that assembles memory / RAG retrieval candidates into the model window.

memory and RAG are the "suppliers"; this subsystem does "final assembly + quality control": it picks the candidates that most deserve a place in the window, orders them, controls the budget, and compresses when they overflow. It does not re-rank (it trusts upstream retrieval to already be ordered) and only does what assembly alone can do: de-duplicate (MMR), budget per source, structure the layout, and compress as a fallback.

Provides
  • mmr_select: MMR selection, relevant yet mutually distinct (reuses the vectors retrieval brings back).
  • count_tokens: token estimation (mixed Chinese/English).
  • ContextConfig / ContextSource: budget config (with quota validation) and the source interface.
  • CallableSource: adapts any "(query) -> candidates" callable into a ContextSource.
  • ContextBuilder: the main pipeline: Gather -> MMR -> three-region budget (two-round borrowing) -> skeleton assembly; build flattens everything, build_block emits only the dynamic-source block.
  • HistoryCompactor: conversation history compression (cross-session Chat history: LLM summary of old turns + keep the most recent few).
  • reducer: loss-aware trimming of trajectories/history (overflow protection for ReAct / Plan / Reflection trajectories, each preserving its own lifeline).
  • WindowBudget / WindowBudgetConfig: window-wide accounting that allocates the whole window across output reserve / fixed overhead / retrieval block / trajectory in one place.

ContextBuilder

Context assembler: assembles multi-source candidates into the final context text within budget.

__init__(config=None, *, min_chunk_tokens=None, prompts=None, token_counter=count_tokens)

Build a ContextBuilder.

Parameters:

Name Type Description Default
config Optional[ContextConfig]

Budget config; if omitted, the default ContextConfig is used.

None
min_chunk_tokens Optional[int]

The maximum possible tokens of a single candidate's body, used for quota sanity checking; if omitted, config.min_chunk_tokens is used (default 64). If you mainly feed RAG (chunks can reach 512), pass 512 explicitly at construction (or set it in config) for stricter checking.

None

The quota is validated at construction: if a source's quota cannot hold one complete candidate block, it raises immediately (guarding against misconfiguration). Before validating, the body-measure min_chunk_tokens is augmented with the list-prefix overhead (_PREFIX_TOKENS) to convert it to the rendering measure, matching _greedy's _item_tokens at the assembly stage. This eliminates the "validate passes but assembly cannot fit" measure mismatch.

max_tokens may be omitted at construction (require_max_tokens=False): when attached to an Agent, the retrieval-block budget is supplied by the window-wide accounting WindowBudget.rag_budget via build_block(budget=...), max_tokens is not involved and may be left unset; only standalone build() / build_block(budget=None) need it, and it fails loud in those two places if missing (see _max_tokens_or_raise).

build(query, *, sources, system_prompt='', scope=None)

Assemble the final context text (system -> sections -> question), flattened into one string, suitable for single-shot / RAG-style calls.

For multi-turn conversation use build_block instead: once history is flattened into a string, the user/assistant roles are lost.

Parameters:

Name Type Description Default
query str

The current user question.

required
sources List[ContextSource]

The various sources (memory / rag / history / ...).

required
system_prompt str

The system prompt: a fixed reservation, always present, does not compete.

''

Returns:

Name Type Description
str str

The assembled context text (system -> sections -> question).

build_block(query, *, sources, scope=None, budget=None)

Assemble only the dynamic-source (memory / RAG / ...) context block: no system prompt, no [Current question].

For multi-turn conversation scenarios: inject this block as a system message, while the conversation history is passed separately as role-carrying messages. Returns an empty string if there are no candidates.

Parameters:

Name Type Description Default
query str

The current user question (used for retrieval + budget estimation, but not written into the returned block).

required
sources List[ContextSource]

The dynamic sources (memory / rag / ...).

required
budget Optional[int]

An optional "retrieval-block net amount" override (from the Harness's window-wide accounting WindowBudget.rag_budget). If given: used directly as the retrieval-block budget: the window-level accounting has already deducted the output reserve and fixed overhead, so here we only further deduct the structure overhead, not re-reserve for output. If not given (None, standalone builder call): fall back to the builder's own convention max_tokens - output reserve - question - structure overhead.

None

Returns:

Name Type Description
str str

The sections assembled into a context block (empty string if no candidates).

abuild_block(query, *, sources, scope=None, budget=None) async

The async version of build_block (Harness.acontext_block uses it): the budget convention matches build_block, but multiple sources fetch concurrently via _aselect's asyncio.gather afetch (replacing serial fetch); the rest (structure overhead / MMR / allocation / joining) shares the sync implementation.

HistoryCompactor

Conversation history compactor: LLM-summarize old turns + keep the most recent few turns verbatim.

__init__(llm, *, keep_recent=4, trigger_tokens=2000, max_summary_tokens=1000, summary_prompt=None, prompts=None, token_counter=count_tokens)

Parameters:

Name Type Description Default
llm LLMClient

the LLM used for summarization (a cheap model such as deepseek is recommended).

required
keep_recent int

how many recent turns to keep verbatim (uncompressed, ensuring the current topic stays precise and coherent). Default 4, must be >= 1.

4
trigger_tokens int

compress only when the total history token count exceeds this, otherwise return unchanged (not wasting an LLM call). Must be >= 0.

2000
max_summary_tokens int

the hard cap (in tokens) of the recap summary, truncated if exceeded. Default 1000, must be >= 1. This prevents the incrementally merged summary from growing ever larger across hundreds of turns: the LLM's merge output length is uncontrollable, and the cached summary gets fed back as input on the next turn.

1000
summary_prompt Optional[str]

the summary instruction used to compact history; if omitted, the framework default is used (central registry context.summary). Pass your own to switch language or adjust summary style.

None
prompts

optional prompt registry (PromptRegistry, see agentmaker.prompts); if omitted, DEFAULT_PROMPTS is used. summary_prompt is a local shortcut override of it (equivalent to overriding context.summary). The recap prefix is taken from context.summary_prefix.

None

from_config(llm, config, *, summary_prompt=None, prompts=None, token_counter=count_tokens) classmethod

Assemble a HistoryCompactor from an AgentmakerConfig: slice config.compaction (keep_recent / trigger_tokens).

Same mindset as each retrieval class's from_config (set defaults in one place, assemble in one line); the compactor needs the llm, so llm is passed separately.

Parameters:

Name Type Description Default
llm LLMClient

the LLM used for summarization.

required
config

AgentmakerConfig (reads config.compaction).

required
summary_prompt Optional[str]

optional summary-instruction override; if omitted, the framework default is used.

None
prompts

optional prompt registry (PromptRegistry); passed through to the constructor, and if omitted DEFAULT_PROMPTS is used (supports isolated overrides that do not pollute the global).

None

compact(history, *, summarize=None)

When history exceeds trigger_tokens, compress to [recap (system)] + the most recent keep_recent turns; otherwise return unchanged.

Parameters:

Name Type Description Default
history List[Message]

the full conversation history.

required
summarize

optional (text, instruction) -> str summary callback (for an app's custom summary channel). If omitted, the built-in llm is used (standalone, e.g. this file's self-test). The Harness path goes through acompact (whose asummarize is governed via acall_llm). A summary failure should return an empty string, and _assemble then does not compress and keeps the original (no history lost).

None

Returns:

Type Description
List[Message]

List[Message]: the compacted history (fewer turns, fewer tokens).

acompact(history, *, asummarize=None) async

Async version of compact: summarization goes through the asummarize callback (governed via acall_llm if Harness passes it) or the built-in llm.chat (async); splitting / assembly is shared with compact.

CallableSource

Bases: ContextSource

Use a callable as a context source, with signature (query) or (query, scope) -> List[RetrievalResult].

name decides which quota it consumes (corresponding to a key of ContextConfig.source_ratios, e.g. "memory" / "rag").

scope threading (optional): whether / how the run's scope is passed to the callable is decided by pass_scope: - By default (pass_scope=None) it is auto-detected by positional-parameter count: if there are >= 2 positional params (e.g. lambda q, s: ...), scope is passed as the second positional argument; a callable taking only (query) simply ignores scope. - Warning: auto-detection only counts positional params. If your fetch takes scope as keyword-only (e.g. def f(query, *, scope=None), as memory.search / rag.retrieve do), it will NOT be auto-recognized and will not receive the run scope. This is intentional: bind memory.search / rag.retrieve directly and use their own scope. To make such a fetch receive the run scope, pass pass_scope=True (passed by keyword scope=), or rewrite it as lambda q, s: f(q, scope=s) (two positional params).

Example

CallableSource("memory", memory.search) # keyword-only scope, uses its own scope CallableSource("memory", lambda q, s: memory.search(q, scope=Scope(user=s.user))) # positional, by the run's user dimension CallableSource("rag", rag.retrieve, pass_scope=True) # explicitly pass the run scope by keyword to a keyword-only scope CallableSource("rag", lambda q: rag.retrieve(q, top_k=8)) # custom top_k, no scope

__init__(name, fetch, *, pass_scope=None)

Build a CallableSource.

Parameters:

Name Type Description Default
name str

Source name (the quota key).

required
fetch Callable

A callable that fetches candidates by query, with signature (query) or (query, scope) -> List[RetrievalResult].

required
pass_scope Optional[bool]

Whether / how to pass the run's scope to fetch. None (default) = auto-detect by positional-parameter count (>= 2 passes scope as the second positional argument, otherwise not passed); True = force passing by keyword fetch(query, scope=scope) (for when scope is keyword-only and auto-detection cannot recognize it); False = force not passing.

None

fetch(query, scope=None)

Call the underlying callable to fetch candidates (sync path). If the underlying is a coroutine (async fetch), fail loud: use afetch instead.

afetch(query, scope=None) async

Fetch candidates asynchronously: an async fetch is awaited directly, a sync fetch goes through to_thread (each source occupies its own thread when concurrent via gather).

CompactionConfig dataclass

Knobs for cross-session Chat history compression (HistoryCompactor).

Attributes:

Name Type Description
keep_recent int

How many recent items to keep as original text (not compressed, ensuring the current topic stays exact and coherent).

trigger_tokens int

The token count the history must exceed before compression is triggered.

validate()

Range check: both must be >= 1.

ContextConfig dataclass

Context budget config (frozen and immutable; hashable, injectable, derivable via dataclasses.replace).

The budget uses "ratios" rather than absolute numbers: when you switch models (the window grows) you just scale by ratio, which is more stable than absolute numbers.

Attributes:

Name Type Description
max_tokens Optional[int]

Total budget (for the whole context). There is NO hard-coded default: it must be set from the model's real window. Prefer ContextConfig.for_window(llm.context_window), or explicitly pass a value you know to be correct. Leaving it None and building a budget anyway raises in validate(), preventing "silently use some arbitrary default".

output_reserve_ratio float

The ratio reserved for output + the current question (does not compete for candidates).

source_ratios Mapping[str, float]

The quota ratio of each source within the dynamic region (keys are source names, e.g. history/rag/memory/tool); the values are recommended to sum to 1, though other sums are allowed (they allocate the dynamic region by relative proportion). After construction, post_init coerces values to float and deep-freezes them read-only.

mmr_lambda float

MMR's relevance vs diversity trade-off, passed to mmr_select.

dedup_threshold float

MMR near-duplicate threshold (cosine >= it is treated as a duplicate and dropped; > 1 effectively disables dedup), passed to mmr_select.

allow_borrow bool

Whether to enable quota borrowing (a source's unused idle quota is given, in a second round, to sources that still want to place more).

min_chunk_tokens int

The token count of a single body item that a quota must at least be able to hold.

for_window(context_window, *, use_ratio=0.5, fallback_window=None, **kwargs) classmethod

Derive the budget from the model's real context window (max_tokens = window * use_ratio) so the budget tracks the real window instead of being hard-coded.

The window comes from the llm source-of-truth (LLMClient.context_window, i.e. the vendor data verified in the profile). use_ratio defaults to 0.5: the context takes only half the window, leaving ample room for output and safety margin (a larger window need not be fully used; overly long context dilutes the signal and adds cost).

Parameters:

Name Type Description Default
context_window Optional[int]

The model context window in tokens (from LLMClient.context_window).

required
use_ratio float

The ratio the context takes of the window, default 0.5.

0.5
fallback_window Optional[int]

The fallback window used when the window is unknown (local / self-built models, context_window is None); it must be given explicitly (no default, forcing the caller to pin a conservative value), otherwise None raises in validate().

None
**kwargs

Passed through to other ContextConfig fields (source_ratios / mmr_lambda / etc.).

{}
Example

ContextConfig.for_window(LLMClient("deepseek").context_window) # 1M -> max_tokens=500,000 ContextConfig.for_window(None, fallback_window=8000) # local unknown model, explicit fallback

validate(*, min_chunk_tokens=None, require_max_tokens=True)

Sanity-check the config: first validate the value ranges of the numeric parameters, then ensure each source's quota can hold one complete candidate block.

Guards against two kinds of "misconfiguration": (1) a ratio / budget parameter takes an illegal value (negative reserve, out-of-range lambda, all-negative ratios, etc.), which makes the budget compute counter-intuitive results (e.g. a negative reserve inflating the context beyond the window); (2) a quota < a single candidate's size -> even the most relevant item cannot fit (except sources with an explicit ratio=0: treated as intentionally unbudgeted, this check is skipped). (Note: (2) only guards "a single item cannot fit"; "total candidates exceed the budget" is a normal trade-off, handled by the builder keeping the top few by relevance, and is not covered here.)

Parameters:

Name Type Description Default
min_chunk_tokens Optional[int]

The token count of a single item that a quota must at least hold; None (default) uses the field's own value. ContextBuilder passes in the rendering measure of "body + list prefix" to override it, aligning the check with the actual assembly footprint.

None
require_max_tokens bool

Whether to require max_tokens to have a value. True (default, the standalone builder call convention): missing max_tokens raises. False (the Agent-attached convention): the retrieval-block budget is given by the window-wide accounting WindowBudget.rag_budget, max_tokens is not involved, so a missing max_tokens is legal here and the max_tokens-related checks are skipped (including the per-source quota check, which depends on max_tokens). The structural checks (dedup / output_reserve_ratio / mmr_lambda / source_ratios) run under both conventions.

True

ContextSource

Bases: ABC

The unified interface for a context source: a class of supplier (memory / rag / history / ...).

name decides which quota it uses (corresponding to a key of ContextConfig.source_ratios). The system prompt is not a source: it is a fixed reservation, always present, does not compete, and is handled separately by ContextBuilder.build.

fetch(query, scope=None) abstractmethod

Fetch this source's candidates for the query (already carrying score and optional embedding).

scope: the session identifier threaded through the run (optional). Which of its dimensions to use is up to the implementation: e.g. memory uses the user dimension (user-level, cross-conversation), rag might use user/app; agentmaker only threads scope down to here and does not prescribe its semantics.

afetch(query, scope=None) async

The async version of fetch (called by ContextBuilder.abuild_block when fanning out over multiple sources via asyncio.gather).

Defaults to wrapping the sync fetch in to_thread (retrieval involves embedding / DB IO, and each source occupies its own thread concurrently); an async retrieval backend can override this to await natively.

ReducerConfig dataclass

The "how much recent original text to keep" knob for loss-aware trimming of paradigm trajectories (used by Harness.reduce).

The trajectory's token budget is not here: it is given by the window-wide accounting WindowBudget.trajectory_budget (see window_budget.py). This config only controls how many trailing steps/items of each paradigm are kept as original text without summarization.

Attributes:

Name Type Description
agent_keep_recent_steps int

How many trailing atomic units (assistant + its tool results) of the unified-loop Agent tool trajectory to keep as original text.

plan_keep_recent int

How many trailing Plan step results to keep as original text.

validate()

Range check: keep_recent >= 0.

WindowBudget dataclass

The window budget for one run (a value object, not part of config files): allocates the whole window among competing streams.

Attributes:

Name Type Description
window int

The model's real window in tokens.

output_reserve int

The output reserve already computed as min(three) (see WindowBudgetConfig.output_reserve).

system_tokens int

The measured system-prompt tokens (one part of fixed overhead).

tool_tokens int

The measured tool-schema tokens (one part of fixed overhead); they ride in the request's tools= payload, not in messages, so trajectory trimming cannot account for them. They must be subtracted separately, otherwise the unified-loop Agent's trajectory could grow large enough to push the tool schemas out of the window.

rag_ratio float

The retrieval block's fraction of the "allocatable balance" (passed through from config).

fixed property

Total fixed overhead = system prompt + tool schemas (convenience property).

spendable property

The balance actually divisible between retrieval block / trajectory after subtracting output reserve + fixed overhead (system + tool schemas), non-negative.

rag_budget property

Retrieval block cap = allocatable balance * rag_ratio.

trajectory_budget(*, rag_in_scope)

The trimming budget for the paradigm trajectory (the two paradigm classes differ in semantics, hence the branch).

Parameters:

Name Type Description Default
rag_in_scope bool

Whether the data being trimmed already contains the retrieval block. True (unified-loop Agent: the retrieval block sits as a system message in the protected head of state.messages) => gives "window - output reserve - tool schemas": the reducer counts the retrieval block / system into the head it protects, but the tool schemas hang off the tools= payload where the reducer cannot see them, so they are subtracted here separately. False (Plan/Reflection: what is trimmed is step results / draft fragments, with the retrieval block counted separately in the prompt) => gives "balance - retrieval block".

required

Returns:

Name Type Description
int int

The token budget for trajectory trimming.

for_run(*, llm, cfg, system_tokens=0, tool_tokens=0, rag_ratio=None) classmethod

Compute one ledger from this run's real window plus the measured fixed overhead; returns None when the window is unknown (llm.context_window is None).

Parameters:

Name Type Description Default
llm

The LLM client (source of context_window and max_output_tokens).

required
cfg WindowBudgetConfig

The window allocation knobs.

required
system_tokens int

The system-prompt tokens (optional; the unified-loop Agent path does not use it, see trajectory_budget).

0
tool_tokens int

The tool-schema tokens (counts tool usage into fixed overhead).

0
rag_ratio Optional[float]

Overrides cfg.rag_ratio (optional); when no retrieval source is configured, the caller passes 0 so an empty retrieval does not waste trajectory budget (without mutating the frozen cfg).

None

Returns:

Type Description
Optional[WindowBudget]

Optional[WindowBudget]: The ledger; None when the window is unknown (the caller

Optional[WindowBudget]

then falls back to "no cap / no trimming").

WindowBudgetConfig dataclass

Window allocation knobs (single source-of-truth, replacing the scattered ContextConfig.output_reserve_ratio + ReducerConfig.budget_fraction).

Attributes:

Name Type Description
desired_output_tokens int

How many tokens at most you want the model to generate this time (absolute number, the main knob for the output reserve). The default is fine; raise it if you want long answers. It is further clamped by the "model hard cap" and the "window guardrail" (see output_reserve).

max_output_fraction float

Small-window guardrail: the output reserve takes at most this fraction of the window. It only really engages on small windows (e.g. 8K), preventing the output reserve from eating up input space; on large windows it almost never triggers (clamped first by desired / model cap).

rag_ratio float

The fraction of the "allocatable balance" that the retrieval block (memory/RAG) takes; the trajectory gets the rest. Only this one ratio is set, the trajectory no longer lists a second number, structurally ruling out "two ratios summing past the window".

validate()

Range check: output reserve >= 0, and both the guardrail and retrieval block ratio within valid ranges.

output_reserve(*, window, model_max_output)

Output reserve = min(desired, model per-call hard cap, window guardrail), three clamps each guarding against one failure mode.

  • desired_output_tokens: do not reserve too much (the default is already small).
  • model_max_output: do not leave a dead zone on large windows: the model outputs at most this many tokens, so reserving beyond it is pure waste (None means unknown, does not participate in the clamp).
  • window * max_output_fraction: do not eat up input on small windows.

Parameters:

Name Type Description Default
window int

The model's real context window in tokens.

required
model_max_output Optional[int]

The model's max output tokens per call (from LLMClient.max_output_tokens); None means do not clamp on it.

required

Returns:

Name Type Description
int int

The token count reserved for model output this time.

count_tokens(text)

Estimate the token count of text: CJK plus Japanese/Korean characters count as 1 token each, everything else at roughly 4 characters per token.

This is a pre-send context budget estimate and does not feed billing or quota (cost and limits always use the real usage returned by the LLM). It does not tokenize on whitespace, so long whitespace-free runs (base64 / compressed data / very long URLs) are correctly covered by "other characters / 4" rather than being counted as a single token.

Parameters:

Name Type Description Default
text str

The text to measure.

required

Returns:

Name Type Description
int int

The estimated token count.

mmr_select(candidates, *, top_k=None, lambda_=0.7, dedup_threshold=0.95)

Select a subset from candidates by MMR: both relevant and non-redundant, dropping near-duplicates.

Two layers of de-duplication: - dedup_threshold: a candidate whose similarity to any already-selected item is >= this value is treated as a "near-duplicate" and dropped outright (actually reducing count). 0.95 means two items must be nearly byte-for-byte identical to count as duplicates; this is a "near-duplicate test", not a "relevance threshold", and is not sensitive to the exact value. - lambda_: among the non-duplicate candidates, orders by MMR balancing "relevant" against "diverse" (earlier-selected items rank higher).

Parameters:

Name Type Description Default
candidates List[RetrievalResult]

Retrieval candidates (each with its own score and optional embedding; mixing candidates from different sources together enables cross-source de-dup).

required
top_k Optional[int]

Maximum number to select; None = no cap on count (relies only on dedup to remove near-duplicates).

None
lambda_ float

Trade-off between relevance and diversity, 0~1; 1 = purely by relevance.

0.7
dedup_threshold float

Near-duplicate removal threshold (cosine), default 0.95.

0.95

Returns:

Type Description
List[RetrievalResult]

List[RetrievalResult]: The selected subset, in selection order (earlier-selected first).

reduce_agent(messages, *, summarize, budget, keep_recent_steps=3, turn_start=0, counter=count_tokens) async

Trim the unified-loop (Agent) function-call trajectory.

Protected region = all initially assembled messages (messages[:turn_start]: system + compacted conversation history + RAG blocks + current user, delimited by index rather than by "find the first user". The unified loop's trajectory contains conversation history, so the first user is an old history message and delimiting by it would summarize away the current question). The trimmable region is the tool trajectory newly added this turn afterward.

The trimmable region is sliced into atomic units (see _agent_units): the most recent keep_recent_steps units are kept verbatim, and earlier units are summarized into a single system "Attempted Steps Summary". If not over budget, returns unchanged (the same object, so the caller can tell whether trimming happened).

Parameters:

Name Type Description Default
messages List[dict]

the Agent's state.messages.

required
summarize Summarize

async summary callback (see module header).

required
budget int

trajectory token budget (WindowBudget.trajectory_budget, includes the protected region).

required
keep_recent_steps int

how many atomic units to keep at the tail, default 3.

3
turn_start int

this turn's start index (ExecutionState.meta["turn_start"], the count of initially assembled messages).

0

reduce_plan(history, *, summarize, budget, keep_recent=3, counter=count_tokens) async

Trim Plan step results: keep the most recent keep_recent step results verbatim and summarize earlier ones into a single entry (preserving key numbers/conclusions).

Returns unchanged if not over budget. Each history entry looks like "Step N: ...\nResult: ...". See the module header for summarize.

reduce_reflection(entries, *, summarize, budget, counter=count_tokens) async

Trim the Reflection trajectory: keep the latest answer verbatim + summarize past critiques into a de-duplicated list of "suggestions already made", dropping superseded old drafts.

entries is a list of {"kind": "draft"|"critique"|"refine", "text": ...} (dict rather than tuple, so it passes JSON checkpoints); returns a shorter list of the same structure. The paradigm's lifeline is the "latest answer" (which it keeps refining) + the "past critique points" (to avoid re-raising suggestions already made); both are kept and old drafts are dropped. See the module header for summarize.

tokens_of(*texts, counter=count_tokens)

Estimate the total token count of several texts (None treated as empty string); counter is pluggable (defaults to count_tokens).