Skip to content

Engine-agnostic agent loop on lean and lean-chat; loop fixes and tests; README and license - #1

Merged
ilnmtlbnm merged 19 commits into
mainfrom
publish-review
Oct 11, 2026
Merged

ilnmtlbnm merged 19 commits into
mainfrom
publish-review

Conversation

@ilnmtlbnm

Copy link
Copy Markdown
Member
  • The agent loop is engine-agnostic: it talks to a small generator trait (prompt text in, streamed text out, abort, a typed context-overflow error). LeanGenerator implements it on lean.
  • Prompts are rendered by lean-chat with the model's chat template and tools, byte-exact with transformers' apply_chat_template. The agent's own template code and tokenizer use are removed.
  • The loop is async, so the same loop runs natively and in the browser.
  • Loop fixes: oversize tool results go back to the model as a tool error, a context overflow mid-turn is recoverable, tool calls without arguments parse, retry notes stay out of the history, ids in provide_tool_results are checked, and a forced final answer can no longer return tool-call markup.
  • Unit tests on a scripted generator for the loop and the eval scoring, and a check of the 46 eval cases.
  • README with model downloads, build, test and run commands; eval data log with the true case counts (46 cases, 33 scored, 13-tool subset); MIT license. Pages deploy is off until there is a demo page.

…nfig

RUSTFLAGS replaces the rustflags of .cargo/config.toml instead of adding
to them. SIMD128 now lives in the config file, so CI's plain wasm-pack
build gets it too, and build.sh passes the path remap with --config,
which cargo merges.
…chat

The loop now reaches the model only through a small Generator trait:
prompt text in, reply text streamed out, an abort flag, and a typed
context-overflow error. Agent steps are async so the same loop runs on
WebGPU in the browser. Prompts are rendered by lean_chat::render with
the tools, from the model's chat template; tool-call parsing and tool
results stay here.

LeanGenerator implements the trait on lean::Lean, which picks its
backend by capability and reuses the KV cache across steps. The agent's
own template rendering and the direct forward-pass calls (including the
buffer-pool reset) are removed. agent-wasm exposes the same loop on
lean's ladder instead of the CPU engine only, with streaming, an abort
handle and contextOverflow/aborted outcomes.

lean and lean-chat are pinned to llm-web 1fce23d.
…n) and count its tools instead of hardcoding 12

tools-12.json has held 13 tools (play_artist included) since the eval
set was written, and cases that need play_artist are scored, so the
report's hardcoded 12 was wrong. The report now counts the tools it
was given.
…calls with no arguments; a null error field is not an error

MCP makes description and inputSchema optional. start_from_mcp and
the CLI silently dropped such tools while the eval rejected them; all
three now read tools through Tool::try_from_mcp. A tool call with no
arguments key (common for no-parameter tools) parses as {} instead of
costing retries, and a double-encoded arguments string is decoded.
- Agent<G> has no tool-caller type: the step-wise API never used it.
  run() takes the caller as an argument, uses the agent's max_steps
  instead of overwriting it, and feeds a caller error to the model as a
  tool error (call_or_error), as the CLI and the eval now do too.
- FixtureCaller moves to the native-only eval module; the scripted test
  generator is test-only. Neither is in the wasm build any more.
- Retry notes are part of that step's prompts only, never of the
  history, so the next step's prompt extends the previous one and an
  engine can reuse its KV cache. Each note says what was wrong.
- Spent retries end in an error that carries the last reason (parse
  error, unknown tool) instead of a fixed message.
- A call whose result was an error may be repeated without tripping the
  repeat guard.
- A forced final answer that still calls a tool is an error; its markup
  is no longer returned as the answer text.
- provide_tool_results checks that the results match the pending calls
  one to one (an unknown, duplicate or missing id is an error that
  leaves the agent unchanged) and feeds them back in call order.
- A context overflow leaves the agent as it was before the call, so the
  caller can retry with smaller results, fewer tools or fewer new
  tokens. A tool result over max_result_chars is fed back as a "too
  large" tool error instead of a printed warning.
- Step.tool_errors counts the error results fed into that step, not the
  running consecutive-error counter.
…instead of wasm-bindgen's aliasing panic

The agent lives in a cell it leaves for the duration of a call, so a
second start, provideToolResults, setOnText or reset (a double click,
or an on_text callback that calls back) gets a JS error and the
instance stays usable. backend and abortHandle never need the agent.
…and the 46-case load check; a subset case with no expected call fails instead of panicking
@ilnmtlbnm
ilnmtlbnm merged commit 8bb63f7 into main Oct 11, 2026
3 checks passed
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