Skip to content

A mind's learned tool head is sent when her lane runs the engine that takes tail_tools (engine pin 1c6681593) - #5491

Merged
joelteply merged 4 commits into
canaryfrom
fable/engine-tail-tools
Oct 26, 2026
Merged

joelteply merged 4 commits into
canaryfrom
fable/engine-tail-tools

Conversation

@joelteply

@joelteply joelteply commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

A resident's learned tool head (goal 10) now reaches the wire on a lane that can call a verb outside her cached head, so loading a long-tail tool stops re-prefilling her prompt (goal 11; order of work line 2, prefix reuse). Until now it ran only in shadow (delib.tool_head.epoch sent=false).

What changes

  • Engine pin core/vendor/llama.cpp moves from e7e1077b3 to 1c6681593, and LLAMA_CPP_PIN is updated to match. git log e7e1077b3..1c6681593 is one commit: server: tail_tools are callable without entering the rendered head (restacked on the engine pin) llama.cpp#75 (tail_tools, +208/−3 in 4 files).

  • The capability comes from the running engine, not from a constant. The fork parses tail_tools but names it in no /props key and no --help flag. So the evidence is the build the running lane reports (/props build_info), matched against the compiled-in pin by the one SHA rule (deploy_provenance::sha_matches):

    • New llama_server/engine_features.rs is the one owner of the decision.
    • The gateway reconcile records the lane's build from the same /props read that already converges the engine. A probe error keeps the last reading, so her tool surface (and with it her prefix) never flips for one tick.
    • engine_install::build_commit is now the one build_info parse, shared with the stale-engine check.
  • OpenAICompatibleAdapter::accepts_tail_tools requires all of the following:

    • the provider declares the fork-gateway family (the llama-server catalog row, now tail_tools: true; ProviderCapabilities doc updated);
    • the adapter is not a dedicated lane;
    • the adapter targets the local serving lane;
    • that lane reports the pinned build.

    Cloud, airc-remote, stock, operator, stale or unreadable builds answer false.

  • Guard on the pin: a source-hygiene vendor_pin test asserts the pinned fork source parses tail_tools. Moving the pin to an engine without it fails CI.

  • Follow-up, card ae4137f1 (owner Fable): the fork reports tail_tools in /props, the core reads that report, and the revision tie is deleted.

Lines: +110/−10 across 8 Rust files (git diff --shortstat origin/canary -- '*.rs'), plus the pin and the submodule. No file over 1,000 lines; no new unwrap, env var or constant true.

Tested (local, M5, serving down, build window, 2026-10-26, head 9c10113)

  • cargo check --tests -p continuum-engine --features metal -p source-hygiene: clean, with no warnings in the touched files. The Metal check also compiles the bumped submodule through the llama crate.
  • cargo test -p continuum-engine --features metal --lib: 1347 passed, 0 failed, 5 ignored. That includes the new only_a_lane_reporting_the_pinned_build_takes_tail_tools (pinned short and full commit give true; e7e1077b3, unknown, an empty commit and an empty string give false).
  • cargo test -p source-hygiene: 67 passed, including the_pinned_engine_source_parses_tail_tools and the_vendored_llama_cpp_pointer_equals_the_recorded_pin.
  • ratchets origin/canary (base 6dd7f8c): every ratchet is flat, exit 0.
  • Engine (Metal): I did not rebuild it here. 1c6681593 is the squash of ca53e26b3, and the two trees are identical (git rev-parse 1c6681593^{tree} ca53e26b3^{tree} gives 7e44b5fc both times). The complete Metal gate ran on that head on this M5 earlier today: ctest -L main 56/56 and -L python 1/1, with fatal warnings on and 0 warnings. Receipt: server: tail_tools are callable without entering the rendered head (restacked on the engine pin) llama.cpp#75 (comment)

Measured (after deploy)

Owner: Fable. Deadline: the first deploy window after merge, followed by a 15-minute live read. The card stays open until these receipts are posted.

  1. Installed and running engine revision: .llama-server.stamp reads 1c6681593…, and the lane's /props build_info ends in 1c6681593. engine_stale is clear.
  2. delib.tool_head.epoch sent=true for every resident on the lane.
  3. One resident's successful real call to a tail verb: an ai.request.tool_surface record with tail_tools_n > 0, followed by that verb's executed tool result, which is a real call and not text.
  4. Before and after over 15 minutes at the same residents: prefix reuse, act quality (tool calls per hour, failed or repeated acts), and timing (median seconds per act, prefill tokens per act). Worse than canary means the node goes back to canary at once.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo

… takes tail_tools (engine pin 1c6681593, fork #75)

Bumps core/vendor/llama.cpp e7e1077b3 -> 1c6681593 (one commit: tail_tools, the squash of
ca53e26b3) and records it in LLAMA_CPP_PIN.

The fork names tail_tools in no /props key and no --help flag, so the capability is read from
the build the RUNNING lane reports (/props build_info), matched against the compiled-in pin:
- llama_server/engine_features.rs: the one owner; the gateway reconcile records the lane's
  build from the same /props read that converges the engine (a probe error keeps the last
  reading, so her tool surface never flips for a tick).
- engine_install::build_commit: the one build_info parse, shared with the stale-engine check.
- OpenAI adapter accepts_tail_tools: the provider's family flag (llama-server row, now true)
  AND not a dedicated lane AND the local serving lane AND its build is the pin. Cloud, remote,
  stock, operator, stale or unreadable builds answer false.
- source-hygiene vendor_pin: the pinned source must parse tail_tools, so moving the pin to an
  engine without it fails CI.

Follow-up (card ae4137f1, Fable): the fork reports tail_tools in /props and the revision tie
is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
@joelteply

Copy link
Copy Markdown
Contributor Author

Source review at 9c10113: changes requested before enabling live tail tools.

[P1] Bind cached build evidence to the actual lane generation. SERVED_ENGINE_BUILD stores only a process-global build string; the sole writer is the successful props read in the already-serving reconciliation branch. Nothing clears or rebinds it when serve_if_current replaces the child. After a pinned lane has set true, a replacement/rollback at the same URL can inherit true until its next successful props probe; errors explicitly preserve the old reading. targets_local_serving_lane checks URL equality, which cannot distinguish successive processes at that URL. That can send tail-only callable verbs to an unsupported replacement engine. Preserve evidence through transient probe failure only for the same verified process/generation; invalidate on replacement and require evidence from the new lane. Add a lifecycle regression: supported A, replace with unverified/old B at same URL, props error => false; unchanged A plus transient error may remain true.

Also verify the per-model vision route: endpoints_for_model can redirect an adapter request to vision_base_url, while accepts_tail_tools tests the main base and main lane evidence. A separate sidecar must not inherit the main lane's capability without its own verified build or an explicit restriction preventing tail tools on that path.

The pure SHA tests and owner-reported 1347 engine tests do not cover these route/lifecycle transitions. No local build or deployment performed during this review.

…he request reaches (Codex on #5491)

P1, lane generation: the recorded build is keyed to the serving lane's EngineIncarnation
(pid + OS start time + port), read from the live pidfile and its registry record
(lane_registry::live_incarnation, two file reads, no process probe). An answered /props
replaces the evidence; a probe error keeps it only while the same known incarnation serves;
every live launch (launch_prepared, the one live spawn) forgets it, so a replacement engine
answers false until its own /props is read. LlamaServerControl::served_incarnation defaults
to None, so fakes and remote controls never carry a reading across an error.

P1, vision route: accepts_tail_tools(model) and finish_body read one decision,
takes_tail_tools_for(model), which resolves the request's real route through
endpoints_for_model_in (the same function the request uses). Only the main serving base,
whose build the reconcile verified, answers true; the vision sidecar, a dedicated lane,
another base or a provider outside the fork family answer false.

Tests: evidence_survives_a_props_error_only_on_the_same_incarnation (A supported; A + error
keeps; replaced by B + error false; unknown identity false; old build false; after a launch
false) and tail_tools_are_decided_for_the_engine_the_request_reaches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
@joelteply

Copy link
Copy Markdown
Contributor Author

Both findings are addressed at 5076b5c.

[P1] Cached build evidence is bound to the lane's generation

  • Identity: I reused the existing EngineIncarnation (pid + OS start time + port), the identity adopted retirement already relies on. I did not add a new one.
    • lane_registry::live_incarnation() reads it from the live-lane pidfile plus its registry record: two file reads, with no process-table probe on the tick.
    • It requires a nonzero started_s.
    • LlamaServerControl::served_incarnation() defaults to None, so fakes and remote controls never keep a reading across an error. control_impl implements it, filtered to its own port.
  • State: engine_features::ServedEngine { incarnation, build }, updated by observe(now, props):
    • an answered /props replaces the evidence;
    • a probe error keeps it only while the same known incarnation serves;
    • a different or unknown incarnation clears it.
  • Recording: the gateway reconcile records through observe_served_engine, from the same read that converges the engine.
  • Invalidation: launch_prepared calls forget_served_engine() for the live lane. It is the only live spawn site: owned launch, restore, convergence and relaunch all pass through it. A replacement engine therefore answers false until its own /props is read.
  • Regression test: engine_features::tests::evidence_survives_a_props_error_only_on_the_same_incarnation:
    • A reports the pinned build: true;
    • A plus a props error: still true;
    • B replaces A at the same port, then a props error: false;
    • unknown identity plus an error: false;
    • B answers with the old build: false;
    • after a launch, the new engine gets a props error: false.

[P1] Vision-sidecar route: the decision is made for the actual destination

  • endpoints_for_model now delegates to a pure endpoints_for_model_in(model, base, snapshot).
  • One decision, takes_tail_tools_for(model), feeds both the learned head and the wire:
    • AIProviderAdapter::accepts_tail_tools(model) now takes the model; cognition passes binding.requested_model().
    • request_body::finish_body takes the same answer as a bool instead of cfg.tail_tools.
  • tail_tools_reach(model, snap, engine_takes) resolves the request's route with endpoints_for_model_in. It answers true only when that route is the main serving base and the lane's build is verified.
  • Test: openai_adapter::tests::tail_tools_reach_the_verified_lane_only:
    • main lane, verified: true;
    • the sidecar's model, routed to vision_base_url: false;
    • main lane with the build unverified: false;
    • dedicated lane: false;
    • another base: false;
    • provider outside the fork family: false.

Verified locally (M5, serving down, build window, 2026-10-26, head 5076b5c):

  • cargo check --tests -p continuum-engine --features metal -p continuum-cognition -p source-hygiene: clean, with no warnings in the touched files.
  • cargo test -p continuum-engine --features metal --lib: 1349 passed, 0 failed, 5 ignored. That includes both new tests and the pinned-build test.
  • cargo test -p continuum-cognition --features continuum-engine/metal --lib: 1180 passed, 0 failed, 2 ignored.
  • cargo test -p source-hygiene: 67 passed.
  • ratchets origin/canary: every ratchet is flat, exit 0.

All touched files stay under 1,000 lines. The live receipts in the PR body are unchanged, owner Fable, after deploy.

@github-actions github-actions Bot added size: L and removed size: M labels Oct 26, 2026
…nation cannot take is refused and rebuilt, never dropped (Codex on #5491)

Route: OpenAICompatibleAdapter::route_now(model, base) loads the serving snapshot and the
engine evidence once and returns RequestRoute { base, tail_tools } (pure core: route_for).
The send takes the wire URL and the tail gate from that one pair; accepts_tail_tools reads
the same function. takes_tail_tools_for/tail_tools_reach are gone.

Refusal: finish_body returns Result; a request that carries a tail the body cannot send is
InferenceError::TailToolsUnreachable (not retryable unchanged), never a silent drop. The
deliberation faculty handles it by rebuilding ONCE on the activity's own surface
(select_activity_surface, split out of select_tool_surface) with no tail and resending; a
second refusal after the rebuild is a named fault.

Transition tests: a_route_that_moves_between_selection_and_send_refuses_the_tail_never_drops_it
(main lane at selection, sidecar at send: consistent pair, refusal) and
a_refused_tail_rebuilds_her_request_without_it_once (one refusal: rebuilt send answers;
two: named fault, exactly two requests); request_body test now asserts the refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
@joelteply

Copy link
Copy Markdown
Contributor Author

Both remaining items are addressed at 93101d2.

(1) One snapshot gives both the route and the tail gate

  • OpenAICompatibleAdapter::route_now(model, base) loads the serving snapshot and the engine evidence once, and returns RequestRoute { base, tail_tools }. Its pure core is route_for(model, base, snap, engine_takes), which resolves the route with endpoints_for_model_in.
  • stream_checked takes the wire URL (route.base.chat_completions()) and the gate (route.tail_tools) from that one pair, so they cannot come from different snapshots. accepts_tail_tools(model) reads the same function.
  • takes_tail_tools_for and tail_tools_reach are removed, and the send no longer calls endpoints_for_model separately.

(2) A tail the destination cannot take is refused and rebuilt, never dropped

  • request_body::finish_body now returns Result. It refuses any request that carries a non-empty tail which the body ended up not sending, whatever the reason: the gate is false, the protocol is not native, or the model has no ToolUse capability. The error is the new InferenceError::TailToolsUnreachable { model }, with is_retryable_unchanged() == false, and a probe records it (ai.request.tail_tools_unreachable).
  • The deliberation faculty (verdict/contribute.rs) handles it by rebuilding the request once on the activity's own surface, with no tail, and resending. select_activity_surface is split out of select_tool_surface for this. The probe is delib.tool_head.tail_refused.
  • A second refusal after the rebuild is a named fault, never a loop.

Transition tests

  • openai_adapter::tests::tail_tools_reach_the_verified_lane_only::a_route_that_moves_between_selection_and_send_refuses_the_tail_never_drops_it:
    • at selection the main lane serves the model, so the tail is accepted;
    • at send, a new snapshot routes it to the sidecar, giving a consistent pair (sidecar URL, tail_tools=false);
    • finish_body then returns TailToolsUnreachable, which is not retryable unchanged.
  • llm_deliberation_faculty::tests::verdicts::capacity_and_wire::a_refused_tail_rebuilds_her_request_without_it_once:
    • one refusal: exactly two requests; the rebuilt one has no tail and offers exactly her activity surface, and the turn answers;
    • two refusals: exactly two requests and a named fault.
    • The scripted adapter injects the typed refusal, because a fixture cannot easily hold a learned head with a loaded tail: that needs measured prefill and decode rates, a vocabulary and a held head. So this test covers the faculty's handling of the refusal. The engine test above covers the refusal being raised from a real tail.
  • request_body test: a cloud destination given a tail now asserts Err(TailToolsUnreachable) instead of a silent omission, and a request with no tail is still Ok.

Verified locally (M5, build window, serving down, 2026-10-26, head 93101d2):

  • cargo check --tests -p continuum-engine --features metal -p continuum-cognition -p source-hygiene: clean.
  • cargo test -p continuum-engine --features metal --lib: 1350 passed, 0 failed, 5 ignored.
  • cargo test -p continuum-cognition --features continuum-engine/metal --lib: 1181 passed, 0 failed, 2 ignored.
  • cargo test -p source-hygiene: 67 passed.
  • ratchets origin/canary: every ratchet is flat, exit 0.

No crate outside continuum-engine matches InferenceError variants, so the new variant breaks no exhaustive match elsewhere. All touched files stay under 1,000 lines; the largest is contribute.rs at 848. The live receipts in the PR body are unchanged, owner Fable, after deploy.

@github-actions github-actions Bot added size: XL and removed size: L labels Oct 26, 2026
@joelteply

Copy link
Copy Markdown
Contributor Author

Scoped source PASS at 93101d2 for my requested lifecycle and routing corrections. Launch invalidation and incarnation-aware probe handling address the stale-cache case; stream_checked now uses one RequestRoute for both URL and tail gate. A non-empty tail that cannot reach the wire produces a typed refusal, and the faculty rebuilds once then faults on a second refusal. Reviewed transition tests cover changed sidecar route and bounded rebuild. Owner reports engine1350/cognition1181/hygiene67 on serving-down M5; I did not independently execute those suites. Exact-head CI and the planned installed/running/consumer-specific 15-minute live read still govern integration and rollout. No live speed or learning claim.

…r_model wrapper had no caller left (CI warning ratchet 14 vs 13)

route_now loads the serving snapshot once; the pure route function takes the name
endpoints_for_model(model, base, snap). No allow: the dead wrapper is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
@joelteply

Copy link
Copy Markdown
Contributor Author

CI at 93101d2: the only real failure was continuum-core's warning ratchet, 14 against a baseline of 13. The new warning was dead_code at openai_adapter/runtime.rs:157: the snapshot-loading endpoints_for_model wrapper had no caller left once the send moved to route_now. The other two failures (cargo test -p continuum-core --lib and ts-rs binding drift detector) are aggregate mirrors of that job. There is no real binding drift: neither InferenceError nor RequestRoute derives TS.

Fixed at 304d00f. I deleted the wrapper rather than allowing the warning. The pure route function takes over the name endpoints_for_model(model, base, snap), so there is one routing function and route_now is its only snapshot loader.

Verified locally (M5, build window, 2026-10-26):

  • cargo check --tests with --message-format=json on the engine (Metal), cognition and source-hygiene: 0 lint warnings in any file this PR touches. The counter does see the existing baseline warnings elsewhere, e.g. llamacpp_scheduler.rs:154.
  • engine lib: 1350 passed, 0 failed, 5 ignored.
  • cognition lib: 1181 passed, 0 failed, 2 ignored.
  • source-hygiene: 67 passed.
  • ratchets origin/canary: every ratchet flat, exit 0.

CI's exact 13-warning count runs on ubuntu with default features; this CI run is the confirmation.

@joelteply
joelteply merged commit b2deeee into canary Oct 26, 2026
34 checks passed
@joelteply
joelteply deleted the fable/engine-tail-tools branch October 26, 2026 22:08
joelteply added a commit that referenced this pull request Oct 27, 2026
… learned tool head (#5502)

Every resident is bound through PlacementSwitch, which forwards each capability to
current() but not accepts_tail_tools, so it answered the trait default (false) and
no resident's learned tool head was ever sent, even on the engine that takes
tail_tools (M5 2026-10-27: delib.tool_head.epoch sent=false, Aiko head 5,047 tokens
of which 4,514 never called, engine 1c6681593 serving). #5491's tests checked the
engine adapter directly, never through the switch.


Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant