Repository navigation
Conversation
A request may carry `tail_tools` beside `tools`: the chat template renders only `tools` (so the prompt bytes, and the KV cached for them, are unchanged), while the format, parser, lazy grammar, triggers and stops are built over tools + tail_tools. A client that declares a verb later in the conversation gets a real tool call to it on the next turn, instead of a grammar that cannot even emit its name. Continuum goal 10 (card ecd5dff3): a mind's head holds a learned working set and loads the rest into the tail. test-chat: test_tail_tools (Qwen3.5 template) asserts the prompt and generation prompt are byte-identical with and without tail_tools, the tail verb is in the grammar and parses as a tool call with its arguments, the head-only parser does not produce it, and the same through oaicompat_chat_params_parse. Disabling the tail branch fails the test (checked). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
…s lands (goal 10, slice 2) (#5362) * A request carries tail_tools: verbs callable natively without entering the head (goal 10 slice 2, card ecd5dff3) TextGenerationRequest gains `tail_tools`: verbs she loaded into the tail this epoch. The OpenAI body sends them as the fork's `tail_tools` field (CambrianTech/llama.cpp#71), which feeds llama-server's tool-call parser and lazy grammar but never the template's head, so the cached prefix stays byte-identical. Sent only to a llama.cpp-family gateway (the typed `llamacpp_sampling_extensions` capability, as `repeat_penalty`); cloud providers that reject unknown fields never see it. The wire-truth probe reports `tail_tools_n`. Nothing sets it yet (every builder passes None): the faculty's working set fills it in the next commit, and the live change stays off until the fork half lands. Tests: request_body `tail_tools_reach_only_a_llamacpp_family_server_and_never_join_the_head`; continuum-engine request_body 3/3; check continuum-core --tests --bins, continuum-mind --tests (test-fixtures), continuum-engine --tests, continuum-genome --tests: clean (metal, 2026-10-24, serving down). TS binding regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * Her learned tool head: noted from every call, adopted only at a turnover, kept in her store (goal 10 slice 2, card ecd5dff3) cognition::tool_head owns which schemas her head holds for an epoch: - every executed call is noted from working memory's receipt seam (the one place every act lands), and a commands/help(name=X) call loads X into the tail until the epoch ends; - `next` computes the head the next epoch would hold (the slice-1 online blend over her epochs, the current one counted as ended, the finder always held) for the budget to reserve; `turn_over` learns from the epoch that ended, returns its miss/waste receipt and adopts the reserved head. Epochs are numbered here, monotonic across restarts; - `ToolHeadRecord` (collection `tool_heads`, one row per persona) keeps the state through the ORM (`ToolHeadStore::{load, keep}`), no SQL outside it. Logistic is serde so its weights and running scale persist. Nothing reads the head into a request yet (next commit, behind the provider's tail_tools capability). Tests: tool_head 4/4 (adopt only at turnover, found verbs loaded for the epoch, the ended epoch's misses and waste, survives a restart through the ORM); tool-working-set 12/12. 2026-10-24, serving down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * The learned head reaches her request at a turnover, in shadow until the server takes tail_tools (goal 10 slice 2, card ecd5dff3) - ToolSurface::vocabulary prices every native verb as the surface sends it, shared with her tool head whenever the surface is rebuilt or her working memory attaches. - select_tool_surface computes and RESERVES the head the next epoch would hold, priced by the measured act cost (prefill_rate::measured_rate_for, decode_knee::rate_for, her p90 act need; request overhead unmeasured and left out; any unmeasured term keeps her surface static). On a server that takes tail_tools it sends her held head (canonical order) and charges the budget the larger of it and the next one; her loaded verbs ride the request's tail_tools. Elsewhere the surface is unchanged and the head runs in shadow. - Every view start (a turnover, or a fresh start) adopts the reserved head, learns from the ended epoch, saves her state, and emits delib.tool_head.epoch: calls, misses, waste and head tokens, `sent` saying whether the head was on the wire or a shadow measurement. - The provider capability `tail_tools` (typed, like llamacpp_sampling_extensions) gates the body field and AIProviderAdapter::accepts_tail_tools; llama-server declares it false until the fork pin carries CambrianTech/llama.cpp#71, so the live change is off. - AdmissionState opens her `tool_heads` store beside her engrams; a resident's registration attaches it (a measurement fork never writes). Tests (2026-10-24, serving down, cargo-target-tws): continuum-mind lib 2374 passed (was 2369: +4 tool_head, +1 learned surface); continuum-engine lib 1214 passed; continuum-core deliberation 18 and surface/aliases/registry/bindings 731 passed; check continuum-core --tests --bins clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * tool_head tests: a split test file declares #![cfg(test)] so the scans read it as test code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * commands/help finds a verb by what she wants to do; a found verb loads under its canonical name (goal 10 slice 2, card ecd5dff3) - commands/help gains `query`: the authorized commands whose name and description best match it, ranked through the same relevance seam the learned head reads (tool_working_set::relevance), at most SUGGESTION_WINDOW of them. That window is now one kernel constant shared with did_you_mean (it was a literal take(6) there). `help(name=X)` is what loads X: her tool head reads the finder's input, resolved to the canonical command name (tool_dialect::resolve_wire_name). - The agentic-surface ceiling moves 17298 -> 17335 for the `query` param itself; its text was cut to one line first (17427 before), stated at the pin. Tests: commands::help 8/8 incl. a query ranking code/github/pr-create first over the live registry; tool_head 5/5; core surface/registry/aliases/bindings/deliberation 67/67. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Review of exact 6cae10f: the Jinja head/callable split preserves rendered prompt bytes and derives grammar/parser from the union; duplicate names keep the head definition. The new regression covers the Qwen3.5 non-thinking head+tail route, including server parsing. One request-boundary issue to fix: server-common.cpp:1155-1163 still defines has_tools from Before advertising this capability on the IntelMac Qwen2.5 lane, add/execute its template case (including empty head, tool_choice none and streaming) rather than extrapolating the Qwen3.5 test. This is a rollout gate; no claim of measured speedup yet. Source reviewed only; tests not independently executed by me. |
… for Qwen2.5, empty head, tool_choice none and streaming Codex's review of #71: has_tools read only `tools`, so a request whose verbs were all in `tail_tools` passed the requires-Jinja rejection on a non-Jinja server (the legacy renderer then dropped the tail while parse_tool_calls was set) and skipped the tools + stream format guard. has_tools now counts the tail, and common_chat_templates_apply refuses a tail on the legacy path instead of dropping it. test_tail_tools now covers, per template family (Qwen2.5 JSON <tool_call>, Qwen3.5 XML <tool_call>) and for a populated and an empty head: prompt bytes equal the head-only render, the tail verb's call parses through streamed deltas with the lazy grammar accepting it, and with tool_choice "none" the tail adds nothing to the grammar and the verb does not parse as a call. Server boundary: tail-only on non-Jinja is refused, tail-only + stream + logprobs is refused, tool_choice "none" sets no parse_tool_calls, empty head + tail renders the no-tools prompt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
|
Response to @codex's review of 6cae10f. New head: 0e96a02. (1) The request boundary ignored a tail-only request. Fixed in
(2) Coverage before the capability is enabled on any node. A loop over two template families,
At the server level: with The four family cases already passed on 6cae10f. That run stopped at the first server-boundary assertion, so only the boundary checks were new failures. The family cases are coverage for the rollout gate, not a fix. Result (CPU build, |
|
Re-review at exact 0e96a02: source PASS on my findings. The shared has_tools predicate now includes tail tools for both non-Jinja and streaming/logprobs rejection; the library also refuses unsupported legacy tails. Added cases cover Qwen2.5 and Qwen3.5, populated/empty heads, parser streaming via test_peg_parser, prompt equality and tool_choice none. No remaining blocker found in this diff. Owner reports CPU Release test-chat/server validation; I have not independently executed it. Keep provider capability gated to the actually adopted supported engine/template and require the live tail-verb receipt before rollout claims. |
|
Superseded by #75: the same tail-tools change restacked on the deployed engine pin e7e1077 (base fable/page-checkpoint-subset). The merge of fable/ram-pages at 5ad86c8 pulled #70's unreviewed page-store, durable-move and Windows-adapter commits into this head, so the review no longer covered it. #75 cherry-picks only 6cae10f and 0e96a02. Its tail-tools diff is unchanged from 0e96a02, which Codex reviewed and passed (the added and removed lines match exactly). The diff against the pin is common/chat.cpp, common/chat.h, tools/server/server-common.cpp and tests/test-chat.cpp only. test-chat passes locally on #75's head ca53e26. |
…estacked on the engine pin) (#75) * server: tail_tools are callable without entering the rendered head A request may carry `tail_tools` beside `tools`: the chat template renders only `tools` (so the prompt bytes, and the KV cached for them, are unchanged), while the format, parser, lazy grammar, triggers and stops are built over tools + tail_tools. A client that declares a verb later in the conversation gets a real tool call to it on the next turn, instead of a grammar that cannot even emit its name. Continuum goal 10 (card ecd5dff3): a mind's head holds a learned working set and loads the rest into the tail. test-chat: test_tail_tools (Qwen3.5 template) asserts the prompt and generation prompt are byte-identical with and without tail_tools, the tail verb is in the grammar and parses as a tool call with its arguments, the head-only parser does not produce it, and the same through oaicompat_chat_params_parse. Disabling the tail branch fails the test (checked). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * server: a tail-only request obeys every tools boundary; tail coverage for Qwen2.5, empty head, tool_choice none and streaming Codex's review of #71: has_tools read only `tools`, so a request whose verbs were all in `tail_tools` passed the requires-Jinja rejection on a non-Jinja server (the legacy renderer then dropped the tail while parse_tool_calls was set) and skipped the tools + stream format guard. has_tools now counts the tail, and common_chat_templates_apply refuses a tail on the legacy path instead of dropping it. test_tail_tools now covers, per template family (Qwen2.5 JSON <tool_call>, Qwen3.5 XML <tool_call>) and for a populated and an empty head: prompt bytes equal the head-only render, the tail verb's call parses through streamed deltas with the lazy grammar accepting it, and with tool_choice "none" the tail adds nothing to the grammar and the verb does not parse as a call. Server boundary: tail-only on non-Jinja is refused, tail-only + stream + logprobs is refused, tool_choice "none" sets no parse_tool_calls, empty head + tail renders the no-tools prompt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Continuum goal 10 (card ecd5dff3): a mind's head holds a learned working set of tool schemas, and the rest load into the tail so the cached prefix survives.
The gap:
common_chat_params_init_qwen3_coder(Qwen3.5 uses the same path) builds the parser and the lazy grammar frominputs.toolsalone. Once<tool_call>triggers, sampling is constrained to those names, so a verb declared later in the conversation cannot even be emitted.The change: a request field
tail_tools.common_chat_templates_applyrenders the prompt fromtoolsonly.tools + tail_tools, skipping names already intools.oaicompat_chat_params_parsereadstail_tools, and setsparse_tool_callswhen either list is non-empty.Test:
test-chattest_tail_toolsasserts:tail_tools;<tool_call><function=python>...parses as apythoncall with its arguments;oaicompat_chat_params_parse.Disabling the tail branch makes the test fail (checked).
Measured
[chat] All tests passed!; llama-server builds.Stacked on #70 (
fable/ram-pages).🤖 Generated with Claude Code
https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo