Skip to content

feat(llm-helper): the loop contract - llm.chat serves one turn of the engines' agent loop - #115

Merged
acn-ericlaw merged 1 commit into
mainfrom
feat/llm-chat-loop-contract
Nov 3, 2026
Merged

acn-ericlaw merged 1 commit into
mainfrom
feat/llm-chat-loop-contract

Conversation

@acn-ericlaw

Copy link
Copy Markdown
Contributor

What

  • The twin of mercury-python Bump vite from 6.3.3 to 6.3.4 in /examples #47 (feat(llm-helper): the loop contract - llm.chat serves one turn of the engines' agent loop mercury-python#47): a request that carries tools is one model turn of an agent loop (loopTurn); without tools, llm.chat is unchanged. The request fields (tools, conversation, turn, results, ttl, retention), the reply (status, text/data, calls, refusal, cap, conversation, turn, the four usage fields) and every message match the Python helper word for word; the loop fields are a 400 on llm.stream.
  • The record pins the model at the first turn and keeps each content block as the SDK sends a returned block back (blockOf: the enumerable fields - the SDK's stream keeps a tool call's raw input in a non-enumerable __json_buf), so thinking signatures replay unchanged. The helper builds the one tool_result message in call order. A stale turn or an ended run is a 409, an ended or expired conversation a 404, and each write is a compare-and-set on the turn count.
  • The stores (llm.transcript.store, required for a loop turn; memory in the sample config): MemoryStore, and RedisStore on the redis package 6.3.1 - the Python helper's Lua script and record JSON under transcript:{conversation}, so the two packs can share one Redis. The client connects on the first command (a pending connect is shared and bounded by timeout.ms), each command waits at most timeout.ms, nothing is queued while offline, and an error listener keeps a lost connection from stopping the process. redis is imported on demand: without it a loop turn on the Redis store is a 503 that names the package.
  • createLlmHelper gains the store and newConversationId dependencies beside backend; llm.health info reports transcript_store; the trace annotations add llm_status, llm_turn and the cache tokens; the log line carries status, turn and usage, never content or the conversation reference.
  • redis joins the dev dependencies beside the SDK. The npm package is unchanged: the same 44 files, the same runtime dependencies. The pre-commit guard names its existing package-lock.json exemption (audited 2026-10-13).
  • Tests: the shared test/vectors/llm-loop-vectors.json (66 cases, byte-identical with mercury-python, digest pinned) on the memory store, on a Redis fake that applies the store script's rule, and on a live Redis when LLM_TRANSCRIPT_REDIS_URL names one; plus tests for the stored blocks, logs, traces, store failures (503 by name), a lost compare-and-set (409), health, the connection settings, the missing package, the connect timeout and a closed port. npm test: 410 tests, 346 passed, 64 skipped (the live variants).
  • Verified beyond the suite: on redis-server 6.2.6 (mercury-composable helpers/redis-standalone) the 64 live cases pass and leave no key; the Python store wrote turn 1, this store read it and appended turn 2 under the compare-and-set, and the Python store read turn 2 back, Unicode intact. Six negative controls, each restored, fail exactly as many tests as in the Python twin: the thinking signature stripped 23, fallbacks on a loop turn 60, results out of call order 2, no retention 28, no conversation cache marker 62, no turn check 4.
  • Docs: the README's "The loop contract" and "The transcript store" (ported from the Python twin), the configuration rows, a Redis run, the Bedrock caveat, Tests; the sample configuration's transcript keys, the Python twin's line for line; CHANGELOG Unreleased Added 2 with the upgrade note.

Why

Step 1 of the agent-loop build plan (RFC-0011 in mercury-composable) is the model turn and its transcript store in both language packs, in lockstep. The loop, llm.run, runs in the engines and dispatches each tool call to a route; the model turn stays in the pack, and the transcript with it, so provider blocks never cross an engine codec, the graph's state stays small, the transcript stays append-only as the current models' thinking blocks require, and any replica - of either pack - serves any turn. With this merged, step 1 is complete and step 2 (llm.run in Java) builds on one contract proven on both packs.

Upgrade note: a request without tools behaves as before; tools, conversation, turn, results, ttl and retention are now loop fields, and a loop turn needs llm.transcript.store.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com

🤖 Generated with Claude Code

… engines' agent loop

The twin of mercury-python #47 (RFC-0011 build plan step 1). A request that carries tools
is one model turn of llm.run, the agent loop of the Java and Rust engines: the transcript
stays in the helper's store and the loop holds a reference to it. A later turn sends
conversation, turn and results back; the helper appends the one tool_result message in
call order and the model's turn exactly as the API returned it, thinking blocks included
(blockOf keeps a block's enumerable fields, as the SDK sends one back). The reply carries
status, calls, cap for a cut-off turn, the four usage fields, conversation and turn.

A stale turn count or an ended run is a 409 and each write is a compare-and-set on the
count; an ended or expired conversation is a 404. The model is pinned at the first turn,
no server-side fallback is sent on a loop turn, and the helper places the cache markers.
The store is llm.transcript.store: memory, or redis (the redis package, a dev dependency
like the SDK) with the Python twin's Lua script under transcript:{conversation}, a command
timeout and no offline queue, so a store that is gone answers 503 at once.

The shared tests/vectors/llm-loop-vectors.json (66 cases, byte-identical with mercury-python)
runs on the memory store, a Redis fake and, with LLM_TRANSCRIPT_REDIS_URL, a live Redis.
A request without tools behaves as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
acn-ericlaw added a commit that referenced this pull request Nov 3, 2026
…n of mercury-python #47, opened as #115; a contradiction thread for Eric

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@acn-ericlaw
acn-ericlaw merged commit d6a0bd9 into main Nov 3, 2026
5 checks passed
@acn-ericlaw
acn-ericlaw deleted the feat/llm-chat-loop-contract branch November 3, 2026 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant