Repository navigation
Releases: paveler/theloop
Releases · paveler/theloop
Release list
v1.3.0
Added
- Kimi provider (
llm.provider: "kimi", envKIMI_API_KEY, SPEC §9.1). Moonshot AI's
Anthropic-compatible API, K3 line (kimi-k3.1-pro,-highspeed,kimi-k3, 1M window).
params.thinkingmaps tooutput_config.effort(low/high/max); without it the
depth islow, since K3 always thinks and the provider defaultmaxis slow. Sampling is
not sent (fixed on K3). Kimi thinking blocks are unsigned; the engine stores them with an
empty signature so they go back in the history of a tool loop.
v1.2.1
Fixed
- An empty answer no longer ends a run silently (SPEC §10.1 rule 8). The v1.2.0 fallback
covered only themax_steps/max_tool_callsfinal step: a model that went text-only
after §6.7 disabled tools (a repeat, or two empty results) and then replied with nothing
ended the runcompleted / end_turnwith an empty answer. Now an empty step (no text, no
tool calls) is dropped and re-asked once without tools, with an ephemeral engine note;
if that is empty too, the run endscompletedwith a localized fallback and
answer_source: "fallback". New journal eventempty_answer, newstep_startfield
empty_answer_reask.limits.final_answer: falsekeeps the old behavior.
v1.2.0
RFC 0002 and
RFC 0003. Additive to contract "1", with one
default behavior change (max_steps / max_tool_calls, opt-out below); everything from
RFC 0003 is opt-in.
Changed
- A run never ends on
max_steps/max_tool_callswithout an answer (SPEC §10.1).
The last allowed step goes withtool_choice: noneand an ephemeral engine note (answer
with what you have); a refusedmax_tool_callsbatch, or a budget used up exactly, makes
the next step text-only. The model answers →completed / end_turnwith the new
limit_reached. No answer (empty, a tool call anyway, a provider failure, a budget) →
stopped_limitwith a fixed localized fallback as the last assistant message and
answer_source: "fallback".limits.final_answer: falserestores the old behavior
exactly. Harnesses that showedstopped_limitas an error should render its answer
(new obligation H22). - A model that calls a tool despite
tool_choice: noneis refused by the AI SDK; on the
final step that attempt is no longer retried.
Added
- Journal on disk (SPEC §12.4, §4.9).
LOG_DIRturns on daily JSONL files that survive
restarts;LOG_RETENTION_DAYS(7),LOG_DIR_MAX_BYTES(2 GB) and
LOG_DISK_CONTENT_MAX_CHARS(200 000) bound them.GET /logsreads memory first and
falls back to disk; the response gainssource: memory | disk | mixed, the query gains
daysandinline_prompts. Entries on disk carryboot_id. - Prompts by hash.
run_startstoressystem_sha256; the text is kept once in memory
and once per day file (prompt_blob), andGET /logsinlines it back by default. - One presentation per run (SPEC §6.9).
tools[].presents,output_checks[].presents
andlimits.max_presentations: presenting calls are admitted in order up to the budget;
once it is spent, presenting tools are hidden and refused (tool_calls[].status: presentation_limit) and presenting fences are cut (output_checks[].status: suppressed).result.presentationslists what reached the user. - Blocks belong to the final answer (SPEC §5.4).
output_checks[].interim: "drop"
removes blocks of the fence from the text of tool steps, from the stored message and the
next prompt; SSEoutput_checkdropped. - Configurable repeat tolerance (SPEC §6.7).
limits.max_repeated_calls(default 1,
ceiling 3);0disables tools on the first repeat. - Grounded values (SPEC §5.4).
output_checks[].grounded: [{ path, tools }]requires
values at a JSON Pointer (with*) to come fromokresults of the listed tools; an
invented value goes through the normal repair (outcome: ungroundedin the journal). - Journal events
presentation,presentation_limit,interim_block_dropped. - Journal events
final_answer,journal_retention,journal_pruned,
journal_disk_ephemeral;step_start.final_answer_stepwithcontent.engine_note;
run_done.limit_reached/answer_source.
v1.1.2
No breaking contract changes. SPEC §6.5 now spells out the cancelled deferred call, and
Appendix A gains H21.
Fixed
- Cancel with a deferred call in the batch (SPEC §6.5). When a write tool had already
answereddeferredand a parallel read was still running, cancel left the deferred
tool_callwithout a tool message, and the next run with that history was refused with
unresolved_tool_call. Every terminal status other thanawaiting_toolnow closes such
a call withcancelled; itstool_calls[]entry keeps theoperation_id, so the
harness can cancel the operation (new obligation H21). - Cancel during an image download (SPEC §6.5). The aborted download surfaced as
image_fetch_failedand the run endedfailed; an aborted run now ends by its abort
reason (cancelled, orfailed/run_timeout). - Repeated calls and
max_tool_calls(SPEC §6.7). The limit was checked before repeats
were detected, so a repeat could end the runstopped_limit. Repeats are not executed
and no longer count; the model getsrepeated_calland goes on. - Output estimate without provider usage (SPEC §8.2). Generated tool calls (name and
arguments) are now counted, not only text and reasoning. - Prompt estimate and date lines (SPEC §5.1, §9.4). The per-message date lines from
created_atwere added to the prompt after the estimate, so trimming and
max_total_tokensran on a smaller number than the prompt actually sent.
Example harness
- A new amount updates the pending operation (SPEC §6.4). "Refund 60 instead of 120"
created a second operation and left the card for 120 confirmable. The pending comparison
now matches by the subject (the order); a changed amount updates the operation and bumps
its version. - A callback before the
POST /runsresponse is recorded and applied once the run row
exists; it used to be lost until recovery. - An operation that is already closed (rejected at registration, executed before a
replay) continues the conversation at once instead of leaving the session in
awaiting_tool. - H21: a deferred call closed by
cancelcancels its operation.
Security
- Callback diagnostics. A receiver that echoed the request in its error body could put
theCALLBACK_SECRETvalue, acallback.headersvalue or a callback query value into
last_error, the event journal and stdout. These values are now redacted wherever they
occur, raw, JSON-escaped or URL-encoded.
v1.1.1
No contract changes.
Fixed
- Signed Claude thinking after the engine's own edits (SPEC §9.3). Claude Fable 5.1,
Opus 5.5, Sonnet 5.5 and Haiku 5.5 bind each thinking block to the exact prefix before it;
on accounts created on or after 2026-08-31 a replayed block after a change is a400.
Trimming (§5.1) and a user message withoutcreated_atthat lost its current-time line
(§9.4) are such changes. On those steps the engine now sends
block_binding: { prefix_mismatch_behavior: "drop_block" }: the provider drops the stale
blocks and the run goes on. Drops are journaled asthinking_blocks_dropped. - Claude context window: 1M. The built-in profile assumed 200k for every
claude-*, so
the engine trimmed Claude histories five times too early. Now 1M, 200k only for Claude 3,
Opus / Sonnet 4.0, Opus 4.1 and the 4.5 models.
Security
- Dependencies with known advisories updated:
hono4.13,@hono/node-server1.19.17,
undici7.30.1,fast-uri3.1.9 (transitive).npm auditreports none. - Zod 4. Zod 3 accumulates an issue for every failing array element without a cap
(GHSA-mrhx-c7xc-m3w5), so a large invalid body could exhaust memory before the request
is refused. Error codes and paths in400 bad_requestare unchanged; the human-readable
part of the message now comes from Zod 4 (Invalid input: expected string, received numberinstead ofExpected string, received number).
v1.1.0 — skills, deferred tools, search_tools
Contract changes are additive: a request that does not use the new fields behaves exactly as
before.
Added
- Skills (RFC 0001, SPEC §6.8).
skills[]in
the request, in theSKILL.mdshape:{ name, description, body, tools }. The engine appends
an index (name and description, sorted by name) tosystemand offers a built-in tool
load_skill. It returns the skill's instructions and makes the skill's tools available from
the next step. - Deferred tools.
tools[].defer_loading: truekeeps a tool out of the model's context
until a skill or a search activates it. A call to a tool that is not loaded yet gets
tool_not_loaded: it is not executed, does not count towardmax_tool_callsand is not
treated as a repeat. search_tools. A built-in tool offered when a deferred tool belongs to no skill. It runs
BM25 over tool names, descriptions and argument names, works the same on every provider and
is reproducible from the request. Matches activate from the next step; matches that belong to
a skill point the model toload_skill.- The prompt cache survives activation. On Anthropic models with tool search, activated
tools arrive astool_referenceblocks inside the conversation, so the cached prefix never
changes. On other providers the next step's tools array grows: one prefix cache miss per
activation. - Still stateless. What is loaded is derived from the history on every run, from
tool
messages the engine marksbuiltin: true(harness obligation H20). Trimming never drops a
turn that carries an activation in effect. - Built-in calls appear in
tool_calls[]withbuiltin: true. New journal events:
skill_loaded,skill_unknown,skills_restored,tool_search,
builtin_history_invalid. examples/sqlite-harness: a complete harness on SQLite with no
dependencies, end to end, with a chat in the terminal (npm run example:sqlite).
Changed
- Vercel AI SDK 6 (
ai6.0,@ai-sdk/anthropic3.0,@ai-sdk/openai-compatible2.0).
Usage is read from the SDK's unified split of uncached, cache read and cache write tokens;
billing still records the uncached input (SPEC §8.1). Ontool_choice: nonesteps Anthropic
now keeps the tools array and receivestool_choice: { type: "none" }. - README: a banner, badges, a comparison with neighbouring tools, and a quickstart with the
published image.
Docker: ghcr.io/paveler/theloop:1.1.0 (also 1.1, 1, latest).
v1.0.1 — quickstart fix, trust model, images
- Fix: the quickstart now works.
npm run devandnpm startread.env(--env-file-if-exists). Before,cp .env.example .env+npm run devfailed withENGINE_TOKEN is required. Node 22.9+ is required. - Docs: trust model. README states what "a prompt-injected model can ask, not act" depends on — write endpoints that only register intents,
readtools without side effects, approval in the harness, data scoped bymeta,ENGINE_TOKENonly on trusted servers — and what the engine guarantees itself. - Docs: AGENTS.md — one instruction file for any coding agent;
CLAUDE.mdimports it. - Images — every
vX.Y.Ztag publishesghcr.io/paveler/theloop:<version>(and<major>.<minor>,<major>,latest) after typecheck and tests.
No contract changes.
v1.0.0 — the v1 contract, implemented
First public release of theloop: an agent loop as a stateless HTTP service.
- Contract — SPEC.md,
contract_version: "1": onePOST /runs= one run, everything in the request; SSE with replay; synchronous cancel; at-least-once callback withGET /runs/{id}recovery and anack-held session lock. - Loop — step by step; tools only after
finish-step; limits at the step boundary; repeated-call and empty-result detectors; a format gate for fenced blocks with repair steps. - Write intents —
effect: writemust answerdeferred; approvals live in the harness. - Usage — one record per provider attempt (
ok | failed | unknown), cache tokens, optional cost, estimated budgets. - Providers — Anthropic, OpenAI, DeepSeek, GLM, GigaChat, MiniMax, any OpenAI-compatible endpoint; per-key queue with AIMD.
- Operations — controlled egress, provider proxies, graceful shutdown, error webhook, in-memory event journal; engine text in
en/ru.
377 tests, no network and no keys. "1.0" is a promise about the contract — no breaking changes within 1.x — not a claim of operational maturity.