refactor(runtime): #3710 god-crate split — 5 standalone crates + oauth/wasm collapse#5053
Merged
Conversation
…system) Move tool_runner.rs into tool_runner/mod.rs as the first step of #3710's god-crate split. Carve out three pure-leaf helper modules: - `taint`: outbound TaintSink checks (shell_exec, net_fetch, outbound_text, outbound_header) plus their secret-key/secret-header denylists. - `shell_safety`: RO-workspace classifier for shell_exec (quote-aware tokenizer, RoSafety enum, classify_shell_exec_ro_safety + segment). - `system`: location_get and system_time leaf tools. Behavior-preserving: only `pub(super)` visibility was added so the parent mod.rs can keep its existing call sites unchanged via `use self::*::*`. No call path, no public surface, and no test was touched. mod.rs shrinks from 14,337 LOC to 13,451 LOC (-886). Verification: cargo check -p librefang-runtime and cargo clippy -p librefang-runtime --lib -- -D warnings both clean. Pre-existing failures in tests/mcp_oauth_integration.rs and tests/tool_runner_forwarding.rs (missing mcp_oauth module and OAuthTokens type) reproduce on origin/main HEAD and are out of scope for this refactor. Refs #3710
Pull out the no-helper, single-dispatch tools into their own files: - `notify` — notify_owner (opaque operator ack) - `memory` — memory_store / recall / list - `wiki` — wiki_get / search / write - `agent` — agent_find (agent_send/spawn/list/kill stay in mod.rs for now) - `task` — task_post / claim / complete / list / status - `event` — event_publish - `goal` — goal_update require_kernel is promoted to pub(super) so the new modules can share the same Option<&Arc<dyn KernelHandle>> -> &Arc<dyn KernelHandle> unwrapping behavior the original inline functions had. Behavior-preserving: each tool is a verbatim function move with only pub(super) visibility added; the parent dispatch sites stay unchanged via use self::<module>::<fn> re-imports. mod.rs: 13,451 -> 13,123 LOC (-328). cargo check + cargo clippy --lib both clean. Refs #3710
…l modules Carve out four more leaf domains from tool_runner/mod.rs: - `workflow` — run / list / status / start / cancel - `knowledge` — add_entity / add_relation / query, plus the parse_entity_type / parse_relation_type helpers they share. - `schedule` — natural-language schedule wrappers create / list / delete, with the parse_schedule_to_cron and parse_time_to_hour helpers kept module-private (they exist only to serve the schedule_* surface). - `cron` — low-level cron_create / list / cancel that go straight to KernelHandle::cron_*. Includes the ownership check that keeps cron_cancel from deleting another agent's jobs. Each tool keeps its original signature and body verbatim; only the `fn` -> `pub(super) fn` visibility tweak was needed so the parent dispatch sites can keep their existing call shape. mod.rs: 13,123 -> 12,510 LOC (-613). cargo check + cargo clippy --lib both clean. Refs #3710
- `hand` — list / activate / status / deactivate. Each verbatim move from the original tool_runner.rs body; only visibility upgraded to pub(super). - `a2a` — discover and send for cross-instance agent communication. Pulls in the existing taint sinks (check_taint_outbound_text, check_taint_net_fetch) via super:: so the SECURITY (#3786) trust gate and exfil-data check keep their original ordering — taint rejection still precedes the trusted-agent allowlist check. Behavior-preserving. The stale "// Location tool" section header left behind in an earlier batch is intentionally not touched here; it'll go when the fs/media helpers (resolve_file_path_ext et al.) move out and the surrounding section comments are rebuilt. mod.rs: 12,510 -> 12,360 LOC (-150). cargo check + cargo clippy --lib both clean. Refs #3710
…odules Four more leaf modules pulled out of tool_runner/mod.rs: - `process` — start / poll / write / kill / list, each a thin wrapper over crate::process_manager::ProcessManager. - `canvas` — sanitize_canvas_html + tool_canvas_present. The sanitizer keeps its `pub fn` visibility and is re-exported from tool_runner via `pub use self::canvas::sanitize_canvas_html`, so any `librefang_runtime::tool_runner::sanitize_canvas_html` consumer continues to resolve. canvas_present pulls CANVAS_MAX_BYTES from the parent module's task-local declaration. - `skill` — the eight skill-evolution tools (create / update / patch / delete / rollback / write_file / remove_file) plus tool_skill_read_file, with the agent_author_tag and ensure_not_frozen helpers kept module-private since they are only consumed by this module. - `artifact` — tool_read_artifact (#3347). Verbatim function moves; only visibility upgraded to `pub(super)` (or `pub` where the symbol was already crate-public). No behavior change. mod.rs: 12,360 -> 11,744 LOC (-616). cargo check + cargo clippy --lib both clean. The orphan `// ---` separator that the previous Docker / process boundary left behind has also been removed. Refs #3710
Three more leaf domains out of tool_runner/mod.rs: - `web_legacy` — tool_web_fetch_legacy + tool_web_search_legacy, the WebToolsContext-less fallbacks. parse_ddg_results is consumed here now, so the corresponding import on mod.rs goes away. - `meta` — tool_load + tool_search + the meta_lookup_pool helper. Pulls super::builtin_tool_definitions() so an empty granted slice still reports "not found" and only a None slice falls back to the builtin catalog (#3044). ToolExecutionStatus moves with it. - `sandbox` — tool_docker_exec. The Docker module itself stays in crate::docker_sandbox; this only carves out the dispatch-side wrapper. Verbatim function moves; visibility upgraded to pub(super). No behavior change. mod.rs: 11,744 -> 11,449 LOC (-295). cargo check + cargo clippy --lib both clean. Refs #3710
Largest single-tool extraction so far: tool_channel_send plus its two private helpers parse_poll_options and mirror_channel_send_to_session (issue #4824 mirror-back-to-session contract) all move into tool_runner/channel.rs. The channel tool handles four delivery modes — text, image_url / file_url URL forwarding, local-file file_path attachment, and Telegram-style polls — each branch verbatim. Taint checks (check_taint_outbound_text) and the mirror-back call follow the original ordering. parse_poll_options keeps fail-fast behavior on non-string entries; mirror_channel_send_to_session keeps the JSON envelope shape required for the inbound-routing session context. To unblock the local-file branch, resolve_file_path_ext is promoted to pub(super); the same change unblocks the upcoming image / media extraction batch, which also reads files from the agent's workspace sandbox. mod.rs: 11,449 -> 11,058 LOC (-391). cargo check + cargo clippy --lib both clean. Refs #3710
tool_image_analyze + its three format/dimension helpers (detect_image_format, extract_image_dimensions, extract_jpeg_dimensions) and format_file_size move into tool_runner/image.rs. detect_image_format is promoted to pub(super) because the test block at the bottom of tool_runner/mod.rs already exercises it directly; the others stay module-private since they have no out-of-module caller. The stale "// Location tool" section header that the system extraction left behind in mod.rs is removed in the same hunk — both Location tools (location_get and system_time) already live in tool_runner/system, so the divider was meaningless. mod.rs: 11,058 -> 10,889 LOC (-169). cargo check + cargo clippy --lib both clean. Refs #3710
tool_shell_exec moves into tool_runner/shell.rs verbatim. All security gating (taint sinks via tool_runner::taint, RO-workspace verb classification via tool_runner::shell_safety) stays upstream in the dispatcher — by the time we reach this function the command has already been cleared, so the new module pulls in no super::* helper beyond the standard `crate::*` references it already had. Both execution paths (Allowlist direct-exec, Full sh -c) keep their original byte-for-byte logic, including the pipe-draining wait_with_output() + 100ms interrupt-tick loop that #4759 introduced to fix the >8KB-output deadlock and preserve cancel-cascade behavior. mod.rs: 10,889 -> 10,699 LOC (-190). cargo check + cargo clippy --lib both clean. Refs #3710
Move agent_send, agent_spawn, agent_list, agent_kill plus their two private helpers (build_agent_manifest_toml, tools_to_parent_capabilities) into tool_runner/agent.rs, joining the agent_find tool that was extracted earlier. The module docstring is updated to reflect that the whole inter-agent surface now lives there. AGENT_CALL_DEPTH (task-local) is promoted from `static` to `pub(super) static` so tool_agent_send can keep using its scoped recursion guard from outside mod.rs. The companion `pub fn current_agent_depth()` accessor in mod.rs is untouched, so any external caller continues to resolve `tool_runner::current_agent_depth`. Taint check on outgoing messages and self-send guard preserved in order. The stale doc-comment for notify_owner that was orphaned in mod.rs after the notify extraction is removed in the same hunk. mod.rs: 10,699 -> 10,461 LOC (-238). cargo check + cargo clippy --lib both clean. Refs #3710
…odule The four user-facing fs tools (file_read, file_write, file_list, apply_patch) move into tool_runner/fs.rs together with every helper they share: - resolve_file_path_ext: the workspace-sandbox path resolver. - named_ws_prefixes / _writable / _readonly: per-agent named-workspace allowlist accessors used by both the dispatcher and apply_patch's #3662 defense-in-depth check. - check_absolute_path_inside_workspace: the ACP-path-jail guard plus its full `path_check_tests` regression block (#3313, dot-dot-traversal fix included). - maybe_snapshot: the spawn_blocking-wrapped checkpoint snapshot helper invoked by file_write and apply_patch before they mutate the workspace. - maybe_dedup_file_read: the #4971 dedup shim that gates file_read on `[context_engine] deduplicate_file_reads`. All helpers are promoted to `pub(super)` so the dispatcher at the top of mod.rs continues to call them under their original names; no call site changed. The now-unused `PathBuf` import on mod.rs goes away in the same hunk (mod.rs still pulls in `Path` for the ToolExecContext fields). mod.rs: 10,461 -> 10,043 LOC (-418). cargo check + cargo clippy --lib both clean. Refs #3710
The largest single-batch extraction yet — 12 functions and their two constants/helpers move into tool_runner/media.rs: - tool_media_describe / tool_media_transcribe (vision + audio understanding via media_understanding::MediaEngine) - tool_image_generate (+ save_media_images_to_workspace, save_media_images_to_uploads helpers) - tool_video_generate / tool_video_status (async video pipelines) - tool_music_generate - tool_text_to_speech (+ convert_to_ogg_opus, finish_tts_result helpers) - tool_speech_to_text SUPPORTED_AUDIO_EXTS_DOC is promoted to pub(super) because builtin_tool_definitions in mod.rs reads it for the media_transcribe and speech_to_text schema descriptions (so the agent-facing format list stays in lockstep with audio_mime_from_ext). audio_mime_from_ext itself is also pub(super) so the inline tests still reach it via `use self::media::audio_mime_from_ext` from the tests module. The internal helpers (save_media_images_*, convert_to_ogg_opus, finish_tts_result) stay module-private because they have no out-of- module caller. mod.rs: 10,043 -> 9,219 LOC (-824). cargo check + cargo clippy --lib both clean. Refs #3710
resolve_spill_config + spill_or_passthrough move into tool_runner/spill.rs. These are the shared overflow helpers that web_fetch and web_search use to spill oversize tool results into the artifact store (#3347 5/N) instead of burning context. Both are verbatim moves with visibility upgraded to pub(super); their behavior on the `0` -> compiled-default fallback in resolve_spill_config is unchanged. The stale `// Web tools` section header in mod.rs (which used to sit above these helpers but no longer aligned with anything after the upstream web tools moved out earlier) is removed in the same hunk. mod.rs: 9,219 -> 9,172 LOC (-47). cargo check + cargo clippy --lib both clean. Refs #3710
…module The ~1.1k-line builtin_tool_definitions() function — every native tool's JSON schema — moves into tool_runner/definitions.rs along with its ALWAYS_NATIVE_TOOLS constant (#3044) and select_native_tools helper. mod.rs re-exports all three with `pub use` so external consumers (librefang_kernel::kernel::mod, the test suite, the auto_dream module) keep resolving the same paths (`librefang_runtime::tool_runner::builtin_tool_definitions`, etc.). definitions.rs reaches SUPPORTED_AUDIO_EXTS_DOC via `super::media` — sibling access is fine because the const is pub(super) inside tool_runner and definitions.rs is also a child of tool_runner. While here, replace the stale `// Inter-agent tools` section header above require_kernel with a one-line doc comment, since the inter-agent tools themselves no longer live next to it. mod.rs: 9,171 -> 7,976 LOC (-1195). cargo check + cargo clippy --lib both clean. The single largest extraction in this issue so far. Refs #3710
execute_tool_raw, execute_tool, ToolExecContext, and current_agent_depth
all move into tool_runner/dispatch.rs. mod.rs re-exports them via
`pub use self::dispatch::{...}` so every external consumer
(librefang_kernel::kernel::mod, the test suite, auto_dream) keeps
resolving the same paths
(`librefang_runtime::tool_runner::execute_tool_raw`, etc.).
dispatch.rs uses `use super::*;` to pull every sibling-module tool
name (tool_a2a_send, tool_file_read, …) into scope without re-listing
each one — the parent module already aggregates them. Everything
that used to be at mod.rs top (`mcp`, `WebToolsContext`, `SkillRegistry`,
`TaintSink`, `ToolDefinition`/`ToolResult`, `normalize_tool_name`,
`Path`, `debug`/`warn`) follows dispatch.rs because those types are
only referenced by the dispatch path; mod.rs is left with just
`Arc` + `KernelHandle` prelude for `require_kernel`.
mod.rs: 7,976 -> 6,489 LOC (-1487). cargo check + cargo clippy --lib
both clean. The 1.5k-line dispatch table now has its own roof; only
the inline test suite (~6k LOC) remains in mod.rs and is the next
candidate for redistribution.
Refs #3710
The 6.3k-LOC `mod tests { ... }` block at the bottom of mod.rs moves
into tool_runner/tests.rs. mod.rs declares it with `#[cfg(test)] mod tests;`
so the suite is path-equivalent (`tool_runner::tests::*` runs the same
tests).
The dedent + de-nesting pulls the previously-inside-mod-tests `#[test]`
fns up one level so test names stay flat and the `super::*` import
resolves to tool_runner (not tool_runner::tests::tests). The 8 outer-
scope `#[tokio::test]` fns that already lived at file root keep their
existing layout.
Six previously-private helpers needed pub(super) so the moved tests
can still call them directly:
- channel::parse_poll_options
- agent::build_agent_manifest_toml
- agent::tools_to_parent_capabilities
- schedule::parse_schedule_to_cron / parse_time_to_hour
- image::{extract_image_dimensions, extract_jpeg_dimensions,
format_file_size}
(detect_image_format was already pub(super).)
tests.rs imports the types it needs (TaintSink, SkillRegistry,
ToolDefinition, ToolResult, Path, AtomicUsize, Arc) directly rather
than relying on mod.rs holding test-only re-exports; mod.rs's top
import block stays focused on its lib-runtime essentials
(KernelHandle prelude + Arc for require_kernel).
mod.rs: 6,489 -> 122 LOC (-6,367). cargo check + cargo check --tests
both clean. The 2 pre-existing "can't find crate librefang_runtime"
failures in tests/tool_runner_*_test.rs reproduce on origin/main and
are out of scope.
Refs #3710
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…act types module First step of agent_loop split (#3710 phase 2). Move the 13,853-LOC `agent_loop.rs` to `agent_loop/mod.rs` and carve out the public-facing type cluster — `LoopPhase`, `PhaseCallback`, `LoopOptions`, `AgentLoopResult`, `ExperimentContext` (+ its `Default` impl) — into `agent_loop/types.rs`. mod.rs re-exports the five symbols via `pub use self::types::{...}` so every external consumer keeps resolving the same paths (`librefang_runtime::agent_loop::AgentLoopResult`, `librefang_kernel::agent_loop::AgentLoopResult`, etc.). types.rs imports only what it actually needs (`TokenUsage`, `DecisionTrace`, `Arc`); fields that reference `crate::interrupt::*`, `crate::aux_client::*`, or `librefang_types::config::*` keep their full-path form so the new file pulls in no extra `use`s that would collide with mod.rs's import block on a future merge. The extraction is purely visibility-preserving: every type stays `pub`, no internal call site changes. mod.rs: 13,853 -> 13,623 LOC (-230). cargo check + cargo clippy --lib both clean. Refs #3710
`LAZY_TOOLS_THRESHOLD`, `resolve_request_tools`, and the
`ResolvedToolsCache` struct (with its `new` / `get` methods) move into
`agent_loop/tool_resolution.rs`. All three become `pub(super)` so the
main loop in `mod.rs` can keep its existing call shape
(`ResolvedToolsCache::new(...)` at the per-turn init site,
`tools_cache.get(...)` per iteration).
`resolve_request_tools` and `LAZY_TOOLS_THRESHOLD` are only consumed
from the in-file `#[cfg(test)] mod tests` block, so they are imported
into the test module directly (`use super::tool_resolution::{...};`)
rather than at file scope. This keeps `unused_imports` quiet on the
release lane — the lint can't see through `use super::*;` to a child
test module — without introducing a `#[allow]`.
Verbatim function bodies; only visibility changes. The lazy-tool
gating contract from #3044 (require `tool_load` in the granted set
before stripping non-native tools) and the per-iteration `Arc` cache
from #3586 are preserved exactly. mod.rs: 13,623 -> 13,531 LOC (-92).
cargo check + cargo clippy --lib both clean.
Refs #3710
The web-search augmentation cluster — `should_augment_web_search`, `SEARCH_QUERY_GEN_PROMPT`, `generate_search_queries`, and `web_search_augment` — moves into `agent_loop/web_augment.rs`. Only `web_search_augment` is consumed from the main loop (called once each at the head of `run_agent_loop` and `run_agent_loop_streaming` before the first LLM call), so it is the only `use self::web_augment::*` re-import at file scope. `should_augment_web_search` and `SEARCH_QUERY_GEN_PROMPT` are referenced only from the `#[cfg(test)] mod tests` block, so they are pulled in alongside the existing `tool_resolution` test-only imports — same pattern as the prior commit, no `#[allow(unused_imports)]` needed. `generate_search_queries` stays module-private — it has no caller outside this cluster. The new module is named `web_augment` (not `web_search`) to avoid name-collision confusion with the sibling `crate::web_search` module that holds the `WebToolsContext` it consumes. Behavior-preserving: verbatim function bodies, only visibility was adjusted (`pub(super)` on the 3 cluster-public items, private on `generate_search_queries`). The Auto-mode fallback (assume tools supported when `model_supports_tools` metadata is absent) and the JSON-extraction sentinel-vs-error contract on `generate_search_queries` are unchanged. mod.rs: 13,531 -> 13,349 LOC (-182). cargo check + cargo clippy --lib both clean. Refs #3710
The model-identifier helper cluster moves into `agent_loop/model.rs`: - `strip_provider_prefix` (pub) — strips `provider/` or `provider:` prefix from the catalog model id, then for providers that demand `org/model` form (OpenRouter, Together, Fireworks, Replicate, HuggingFace) backfills via `normalize_bare_model_id`. - `apply_session_model_override_to_manifest` (pub) — per-session `model_override` apply path from #4898; called by `kernel::agent_execution::execute_llm_agent`. - `needs_qualified_model_id` (pub(super)) — provider allowlist for the qualified-id rule above. - `normalize_bare_model_id` (private) — bare-name → `org/model` lookup table. - `DEFAULT_CONTEXT_WINDOW` (pub(super), `#[allow(dead_code)]`) — kept as the documented authoritative value referenced by `docs/architecture/message-history-trimming.md`. - `UNKNOWN_MODEL_CONTEXT_WINDOW` (pub(super)) — 8K fallback used by the main loop's context-window resolution path (#3349). - `stable_prefix_mode_enabled` (pub(super)) — manifest metadata check for the stable-prefix experiment flag. The in-file `mod session_model_override_tests` (7 tests) travels with `apply_session_model_override_to_manifest` so the test/function pair stays co-located. mod.rs re-exports the two `pub` items via `pub use self::model::{...}` so kernel and channel callers keep resolving the same `librefang_runtime::agent_loop::strip_provider_prefix` path. `STABLE_PREFIX_MODE_METADATA_KEY` is removed from mod.rs's import block (consumed only by `stable_prefix_mode_enabled`, which is now in model.rs). The big `mod tests` block in mod.rs picks up `needs_qualified_model_id` via the existing test-only `use super::model::*` pattern. Behavior-preserving. mod.rs: 13,349 -> 13,096 LOC (-253). cargo check + cargo clippy --lib both clean. Refs #3710
The message-history trim config — `DEFAULT_MAX_HISTORY_MESSAGES` (pub, 60), `MIN_HISTORY_MESSAGES` (pub(super), 4), `resolve_max_history` (pub(super)) and `clamp_max_history` (private) — moves into `agent_loop/history.rs`. `resolve_max_history` is used by the main `run_agent_loop` and `run_agent_loop_streaming` entry points to derive the per-turn trim cap; `DEFAULT_MAX_HISTORY_MESSAGES` is re-exported via `pub use self::history::DEFAULT_MAX_HISTORY_MESSAGES` so docs that quote `librefang_runtime::agent_loop::DEFAULT_MAX_HISTORY_MESSAGES` (message-history-trimming architecture page, configuration core page) keep resolving. `MIN_HISTORY_MESSAGES` is referenced from the in-file `mod tests` clamp-tests; pulled in alongside the existing `super::model::*` / `super::web_augment::*` test-only imports. `clamp_max_history` stays private — `resolve_max_history` is its only caller. The 4-tier resolution order (`manifest > opts > DEFAULT > clamped-to-floor`) is preserved exactly. Behavior-preserving. mod.rs: 13,096 -> 13,027 LOC (-69). cargo check + cargo clippy --lib + cargo check --tests all clean. Refs #3710
The message-shape helper cluster moves into `agent_loop/message.rs`: - `ACCUMULATED_TEXT_MAX_BYTES` + `push_accumulated_text` — the bounded 64-KiB fallback buffer the empty-response guard reads when no text arrives alongside `tool_use` blocks; sealed-on-overflow with ASCII padding so subsequent appends short-circuit. - `is_no_reply`, `is_progress_text_leak`, `is_cascade_leak` — model- output classifiers (delegates to `silent_response` for two of them). - `is_soft_error_content`, `is_parameter_error_content` — tool-error content classifiers used by the consecutive-failure abort threshold. - `sanitize_for_memory` (+ the inline `use` of `ENVELOPE_LINE_PREFIXES` / `ENVELOPE_STANDALONE_MARKERS` that fed it) — channel-envelope stripper for the proactive-memory persist path; preserves inline bracketed content, returns `None` when nothing meaningful survives. - `safe_trim_messages` — the trim-on-turn-boundary helper for working history, with the rescued-pinned-message reinsertion and the post-trim re-validate / ensure-starts-with-user safety net. - `strip_processed_image_data`, `strip_prior_image_data` — image-data trimmers for already-processed turns. - `accumulate_token_usage` — per-turn `TokenUsage` rollup. - `sanitize_tool_result_content` — strip injection markers + dynamic truncate, delegating to the `ContextEngine` when present. - `sanitize_sender_label` — bounded `[name]:` prefix sanitizer for group-chat senders. mod.rs drops the now-unused `truncate_tool_result_dynamic`, `ERR_PATH_TRAVERSAL`, `ERR_SANDBOX_ESCAPE` top-level imports — all three were only consumed by the helpers being extracted. The big `mod tests` block picks up `ACCUMULATED_TEXT_MAX_BYTES`, `ENVELOPE_LINE_PREFIXES`, and `ENVELOPE_STANDALONE_MARKERS` via the same test-only `use super::message::*` / `use crate::silent_response::*` pattern the prior commits established. The single stranded `/// Check if a response is a silent-reply sentinel.` doc-line that used to sit above the moved `is_no_reply` (and which the deletion range missed by one line) is removed in this commit too, fixing the resulting `clippy::empty-line-after-doc-comments` lint on the `tool_use_blocks_from_calls` boundary. Behavior-preserving. mod.rs: 13,027 -> 12,641 LOC (-386). cargo check + cargo clippy --lib + cargo check --tests all clean. Refs #3710
The text-based tool-call recovery cluster moves into
`agent_loop/text_recovery.rs`. Three `pub(super)` entry points:
- `looks_like_hallucinated_action` — flags model output that claims a
completed action (English present-perfect, Italian `ho` + past
participle, impersonal `è stato/stata`, `successfully X`,
`messaggio inviato`, …) without an actual tool call. Used by the
end-turn classifier in `mod.rs` to decide whether to nudge the
model to retry with a real tool invocation.
- `user_message_has_action_intent` — checks the user's request for
exact-word action verbs (`send`, `execute`, `create`, `deploy`, …)
so the hallucination nudge only fires when the user actually asked
for an action.
- `recover_text_tool_calls` — extracts synthetic `ToolCall` entries
from free-text LLM output across nine on-the-wire formats:
`<function=NAME>{…}</function>` (Pattern 1), `<function>NAME{…}</function>`
(P2), `<tool>NAME{…}</tool>` (P3 — Qwen / DeepSeek), markdown code
blocks (P4), backtick-wrapped (P5), `[TOOL_CALL]…[/TOOL_CALL]` with
JSON or arrow syntax (P6 — issue #354), `<tool_call>JSON</tool_call>`
(P7 — Qwen3, issue #332), `<function name="…" parameters="…" />`
(P9 — Groq/Llama XML attribute, cached regex per #3491), and the
bare-JSON last-resort fallback (P8).
Internal helpers (`parse_json_tool_call_object`, `parse_arrow_syntax_tool_call`,
`parse_dash_dash_args`, `try_parse_bare_json_tool_call`) stay private to
the module except for the two that the `mod tests` block exercises
directly (`parse_json_tool_call_object`, `parse_dash_dash_args`) — those
are `pub(super)` and pulled in via the existing test-only import block.
Behavior-preserving: every pattern's matching logic, dedupe key
(`name + input`), recovered-id format (`recovered_<uuid>`), and
warn/info log site are byte-for-byte the same as before the move. The
ordering of pattern-matching (1 → 9 → 8) is unchanged, including the
P8 bare-JSON guard that only runs when `calls.is_empty()`.
mod.rs: 12,641 -> 11,812 LOC (-829). cargo check + cargo clippy --lib
+ cargo check --tests all clean.
Refs #3710
The LLM-call retry path moves into `agent_loop/retry.rs`. Two
`pub(super)` entry points consumed by the main loop bodies:
- `call_with_retry` — non-streaming wrapper. Honors the
`ProviderCooldown` circuit-breaker (Allow / AllowProbe / Reject),
acquires `LLM_CONCURRENCY` per attempt so backoff sleeps don't pin
a slot, classifies errors via `llm_errors` for sanitized
user-facing messages, and appends the
`librefang_channels::message_journal::RATE_LIMIT_DEFER_MARKER` to
exhausted-retry errors so the channel bridge can route them to
`JournalStatus::Deferred` instead of `Failed`.
- `stream_with_retry` — streaming variant returning
`StreamWithRetryResult { response, cascade_leak_aborted }`. Same
retry policy as the non-streaming path, plus the incremental
cascade-leak guard that stops forwarding `TextDelta` events
mid-stream when the in-memory accumulator trips
`is_cascade_leak`.
Cluster-private helpers stay private to the module:
`check_retry_cooldown`, `record_retry_success`, `record_retry_failure`,
`handle_retryable_llm_error`, `build_user_facing_llm_error`.
Constants travel with their consumers: `MAX_RETRIES` (pub(super)),
`BASE_RETRY_DELAY_MS` (pub(super)), `MAX_CONCURRENT_LLM_CALLS`
(private), `DEFAULT_DEFER_MS` (private), and the `LLM_CONCURRENCY`
static. The two `pub(super)` constants are referenced from the
`mod tests` block and imported there via the existing
test-only `use super::retry::{...}` pattern.
`StreamWithRetryResult.response` and
`StreamWithRetryResult.cascade_leak_aborted` are `pub(super)` because
the streaming main loop in mod.rs destructures them after the call.
mod.rs sheds the now-orphan top-level imports that only the retry
path needed: `CooldownVerdict`, `ProviderCooldown`, `LlmError`, and
the entire `crate::llm_errors` use. retry.rs picks them up locally
along with `StopReason`, `TokenUsage`, and `super::TIMEOUT_PARTIAL_OUTPUT_MARKER`.
Behavior-preserving. mod.rs: 11,812 -> 11,384 LOC (-428).
cargo check + cargo clippy --lib + cargo check --tests all clean.
Refs #3710
The end-of-turn cluster moves into `agent_loop/end_turn.rs`:
- Three context structs — `FinalizeEndTurnContext`,
`FinalizeEndTurnResultData`, `EndTurnRetryContext` — promoted to
`pub(super)` along with every field. The main loop in mod.rs
literally constructs these (both `run_agent_loop` and
`run_agent_loop_streaming` build them at four sites each), so
literal-construction requires all fields to be visible at the
call site.
- `EndTurnRetry` enum (`EmptyResponse { is_silent_failure }`,
`HallucinatedAction`, `ActionIntent`) — `pub(super)`. Pattern-
matched at two main-loop sites.
- `classify_end_turn_retry` — decides whether the just-finished
turn should retry. Three rules: empty response (iter 0 or
zero-token "silent failure"), hallucinated action claim with no
actual tool call, and action-intent user message ignored by a
text-only reply. The hallucination + action-intent branches
delegate to `text_recovery`.
- `finalize_end_turn_text` — empty-response fallback, with the
accumulated_text rescue for agents that emitted text alongside
earlier `tool_use` blocks.
- `finalize_successful_end_turn` — the big success path: push
assistant message, prune heartbeat turns, save_session_async
(skipped on fork/incognito), proactive-memory write through
`sanitize_for_memory`, and assemble the `AgentLoopResult`.
- `maybe_fold_stale_tool_results` — periodic history-fold to keep
the context window viable.
- `gated_proactive_memory_for_retrieve` / `_for_memorize` —
per-agent gates on the optional `ProactiveMemoryStore`. Both
called from prompt setup (retrieve) and end-turn (memorize) on
both the streaming and non-streaming paths.
- `serialize_session_messages` — stays module-private (only
`finalize_successful_end_turn` consumes it).
- `build_silent_agent_loop_result` — bare success result for the
silent-reply path (NO_REPLY / `[[silent]]` directives).
end_turn.rs uses `use super::*;` to inherit the parent module's
re-exports (LoopOptions, AgentLoopResult, ExperimentContext, etc.)
plus explicit `super::message::sanitize_for_memory` and
`super::text_recovery::{looks_like_hallucinated_action,
user_message_has_action_intent}` for the names not re-exported
through the wildcard.
The `mod tests` block picks up `sanitize_for_memory`,
`looks_like_hallucinated_action`, and `user_message_has_action_intent`
via the existing test-only `use super::*` pattern.
Behavior-preserving. mod.rs: 11,384 -> 10,893 LOC (-491).
cargo check + cargo clippy --lib + cargo check --tests all clean.
Refs #3710
This was referenced May 16, 2026
houko
added a commit
that referenced
this pull request
May 16, 2026
After PR #5053 split plugin_manager.rs into a plugin_manager/ directory, the todo-to-issue workflow's IGNORE rule still pointed at the old file path. When #5053 merged, the workflow scanned the new plugin_manager/scaffold.rs (which contains TODO-marker placeholders inside raw string literals — those are starter-code templates emitted into user-generated plugins, not project TODOs) and opened 21 false-positive issues (#5079-#5099, all subsequently closed as not-planned). Widen IGNORE to the directory so the scaffolder's string-literal templates are skipped, and any future siblings under plugin_manager/ are covered without another rebase.
houko
added a commit
that referenced
this pull request
May 16, 2026
This was referenced May 17, 2026
This was referenced May 20, 2026
f-liva
pushed a commit
to f-liva/librefang
that referenced
this pull request
May 21, 2026
…put (drift-pin) `is_cascade_leak` (librefang#4907) hard-codes string-literal markers in `THEMATIC_HEADERS`, `SCAFFOLD_ONLY_HEADERS`, `STRUCTURAL_TURN_FRAMES`, `ENVELOPE_LINE_PREFIXES`, and `ENVELOPE_STANDALONE_MARKERS`. A cosmetic prompt-builder change (e.g. `## Sender` -> `## From`) silently invalidates the detector — the leak it was tuned to catch (librefang#5141) stops firing, and CI stays green because no existing test runs `build_system_prompt` and greps its output for the marker strings. Add two pinned tests in `silent_response::tests`: - `structural_and_envelope_markers_absent_from_prompt_builder` — negative pin. `STRUCTURAL_TURN_FRAMES` / `ENVELOPE_LINE_PREFIXES` / `ENVELOPE_STANDALONE_MARKERS` must NOT appear in a fully-populated `build_system_prompt` output. Otherwise the cascade-leak guard false-positives on its own scaffolding the moment the model paraphrases the prompt back. - `thematic_and_scaffold_headers_match_prompt_builder_output` — positive pin via a snapshot of (marker, currently-emitted) tuples. Today the five `THEMATIC_HEADERS` entries are all snapshotted as `absent` — a pre-existing divergence the closed-PR-librefang#4760 reviewer asked the re-port to surface. Renaming or restoring a section in `prompt_builder.rs` that touches one of these names now fails the snapshot in lock-step. This is the narrower re-port of `keywords_match_real_prompt_headers` from closed PR librefang#4760 (closing comment recommended carrying it forward), retargeted for the post-librefang#5053 prompt-builder layout and the post-librefang#5073 `granted_tool_hints` `PromptContext` shape. Items 1-5 in the librefang#4760 close comment ("genuinely new shapes") are out of scope — this PR is just the test. Verification: - cargo check -p librefang-runtime --lib OK - cargo clippy -p librefang-runtime --lib -- -D warnings OK - cargo fmt -p librefang-runtime --check OK - cargo test -p librefang-runtime --lib silent_response:: 31 passed - cargo test -p librefang-runtime --lib prompt_builder:: 81 passed
f-liva
pushed a commit
to f-liva/librefang
that referenced
this pull request
May 21, 2026
…fang#5053 prompt builder The drift-pin test added by PR librefang#5344 (`silent_response::tests::thematic_and_scaffold_headers_match_prompt_builder_output`) surfaced that `THEMATIC_HEADERS` and `SCAFFOLD_ONLY_HEADERS` in `crates/librefang-runtime/src/silent_response.rs` were calibrated against five legacy strings — `## Sender`, `## Today`, `## Calendar`, `## Tasks`, `## Response Style` — none of which the post-librefang#5053 prompt builder emits. Consequence: the `1 structural + 1 thematic` branch of `is_cascade_leak` was effectively dead, and the detector silently degraded to a `2+ structural` rule. The canonical chat-template regurgitation shape from today's builder — `## Persona\n…\n## Memory` — had zero thematic hits and no longer tripped on its own, exactly the class of cross-chat leak the detector exists to catch. This reconciles the two lists with the actual headers enumerated by `prompt_builder.rs`: - 21 current-builder strings move into `SCAFFOLD_ONLY_HEADERS` (pure prompt-frame; no genuine reply emits a `## Persona\n` line-leader). - `## Current Date` is the lone ambiguous-subset entry, kept in `THEMATIC_HEADERS` but intentionally NOT in `SCAFFOLD_ONLY_HEADERS` — a legitimate "what's today?" reply may produce it. - The legacy five stay in both lists as a defensive belt-and-suspenders layer for any deployment still on the pre-librefang#5053 builder. Cheap to leave; same false-positive risk as before. The drift-pin snapshot is flipped from `(marker, false)` for the legacy five to `(marker, true)` for the 21 current strings; the legacy five remain `false`. The two cross-check loops the snapshot test ships ensure no list can be edited without updating the snapshot in lock-step and vice versa — drift like this can't recur silently. Regression tests added in `silent_response::tests`: - `cascade_leak_current_builder_scaffold_only_pair_trips` — `## Persona\n…\n## Memory` and two other current-builder pairs trip (2 structural-equivalent hits). - `cascade_leak_current_builder_scaffold_plus_envelope_trips` — `[Group message from …]\n## Persona\n…` trips (1 envelope-structural + 1 scaffold-structural-equivalent). - `cascade_leak_current_builder_ambiguous_alone_legitimate` — `## Current Date\n…\nyour day looks clear` is NOT a leak (only ambiguous header, no structural). - `cascade_leak_current_builder_ambiguous_plus_structural_trips` — `[Past exchange]\n…\n## Current Date\n…` trips (the 1+1 branch that was previously dead).
houko
added a commit
that referenced
this pull request
May 22, 2026
…put (drift-pin) (#5344) `is_cascade_leak` (#4907) hard-codes string-literal markers in `THEMATIC_HEADERS`, `SCAFFOLD_ONLY_HEADERS`, `STRUCTURAL_TURN_FRAMES`, `ENVELOPE_LINE_PREFIXES`, and `ENVELOPE_STANDALONE_MARKERS`. A cosmetic prompt-builder change (e.g. `## Sender` -> `## From`) silently invalidates the detector — the leak it was tuned to catch (#5141) stops firing, and CI stays green because no existing test runs `build_system_prompt` and greps its output for the marker strings. Add two pinned tests in `silent_response::tests`: - `structural_and_envelope_markers_absent_from_prompt_builder` — negative pin. `STRUCTURAL_TURN_FRAMES` / `ENVELOPE_LINE_PREFIXES` / `ENVELOPE_STANDALONE_MARKERS` must NOT appear in a fully-populated `build_system_prompt` output. Otherwise the cascade-leak guard false-positives on its own scaffolding the moment the model paraphrases the prompt back. - `thematic_and_scaffold_headers_match_prompt_builder_output` — positive pin via a snapshot of (marker, currently-emitted) tuples. Today the five `THEMATIC_HEADERS` entries are all snapshotted as `absent` — a pre-existing divergence the closed-PR-#4760 reviewer asked the re-port to surface. Renaming or restoring a section in `prompt_builder.rs` that touches one of these names now fails the snapshot in lock-step. This is the narrower re-port of `keywords_match_real_prompt_headers` from closed PR #4760 (closing comment recommended carrying it forward), retargeted for the post-#5053 prompt-builder layout and the post-#5073 `granted_tool_hints` `PromptContext` shape. Items 1-5 in the #4760 close comment ("genuinely new shapes") are out of scope — this PR is just the test. Verification: - cargo check -p librefang-runtime --lib OK - cargo clippy -p librefang-runtime --lib -- -D warnings OK - cargo fmt -p librefang-runtime --check OK - cargo test -p librefang-runtime --lib silent_response:: 31 passed - cargo test -p librefang-runtime --lib prompt_builder:: 81 passed Co-authored-by: Federico Liva <[email protected]> Co-authored-by: Evan <[email protected]>
f-liva
pushed a commit
to f-liva/librefang
that referenced
this pull request
May 22, 2026
…fang#5053 prompt builder The drift-pin test added by PR librefang#5344 (`silent_response::tests::thematic_and_scaffold_headers_match_prompt_builder_output`) surfaced that `THEMATIC_HEADERS` and `SCAFFOLD_ONLY_HEADERS` in `crates/librefang-runtime/src/silent_response.rs` were calibrated against five legacy strings — `## Sender`, `## Today`, `## Calendar`, `## Tasks`, `## Response Style` — none of which the post-librefang#5053 prompt builder emits. Consequence: the `1 structural + 1 thematic` branch of `is_cascade_leak` was effectively dead, and the detector silently degraded to a `2+ structural` rule. The canonical chat-template regurgitation shape from today's builder — `## Persona\n…\n## Memory` — had zero thematic hits and no longer tripped on its own, exactly the class of cross-chat leak the detector exists to catch. This reconciles the two lists with the actual headers enumerated by `prompt_builder.rs`: - 21 current-builder strings move into `SCAFFOLD_ONLY_HEADERS` (pure prompt-frame; no genuine reply emits a `## Persona\n` line-leader). - `## Current Date` is the lone ambiguous-subset entry, kept in `THEMATIC_HEADERS` but intentionally NOT in `SCAFFOLD_ONLY_HEADERS` — a legitimate "what's today?" reply may produce it. - The legacy five stay in both lists as a defensive belt-and-suspenders layer for any deployment still on the pre-librefang#5053 builder. Cheap to leave; same false-positive risk as before. The drift-pin snapshot is flipped from `(marker, false)` for the legacy five to `(marker, true)` for the 21 current strings; the legacy five remain `false`. The two cross-check loops the snapshot test ships ensure no list can be edited without updating the snapshot in lock-step and vice versa — drift like this can't recur silently. Regression tests added in `silent_response::tests`: - `cascade_leak_current_builder_scaffold_only_pair_trips` — `## Persona\n…\n## Memory` and two other current-builder pairs trip (2 structural-equivalent hits). - `cascade_leak_current_builder_scaffold_plus_envelope_trips` — `[Group message from …]\n## Persona\n…` trips (1 envelope-structural + 1 scaffold-structural-equivalent). - `cascade_leak_current_builder_ambiguous_alone_legitimate` — `## Current Date\n…\nyour day looks clear` is NOT a leak (only ambiguous header, no structural). - `cascade_leak_current_builder_ambiguous_plus_structural_trips` — `[Past exchange]\n…\n## Current Date\n…` trips (the 1+1 branch that was previously dead).
f-liva
pushed a commit
to f-liva/librefang
that referenced
this pull request
May 25, 2026
…fang#5053 prompt builder The drift-pin test added by PR librefang#5344 (`silent_response::tests::thematic_and_scaffold_headers_match_prompt_builder_output`) surfaced that `THEMATIC_HEADERS` and `SCAFFOLD_ONLY_HEADERS` in `crates/librefang-runtime/src/silent_response.rs` were calibrated against five legacy strings — `## Sender`, `## Today`, `## Calendar`, `## Tasks`, `## Response Style` — none of which the post-librefang#5053 prompt builder emits. Consequence: the `1 structural + 1 thematic` branch of `is_cascade_leak` was effectively dead, and the detector silently degraded to a `2+ structural` rule. The canonical chat-template regurgitation shape from today's builder — `## Persona\n…\n## Memory` — had zero thematic hits and no longer tripped on its own, exactly the class of cross-chat leak the detector exists to catch. This reconciles the two lists with the actual headers enumerated by `prompt_builder.rs`: - 21 current-builder strings move into `SCAFFOLD_ONLY_HEADERS` (pure prompt-frame; no genuine reply emits a `## Persona\n` line-leader). - `## Current Date` is the lone ambiguous-subset entry, kept in `THEMATIC_HEADERS` but intentionally NOT in `SCAFFOLD_ONLY_HEADERS` — a legitimate "what's today?" reply may produce it. - The legacy five stay in both lists as a defensive belt-and-suspenders layer for any deployment still on the pre-librefang#5053 builder. Cheap to leave; same false-positive risk as before. The drift-pin snapshot is flipped from `(marker, false)` for the legacy five to `(marker, true)` for the 21 current strings; the legacy five remain `false`. The two cross-check loops the snapshot test ships ensure no list can be edited without updating the snapshot in lock-step and vice versa — drift like this can't recur silently. Regression tests added in `silent_response::tests`: - `cascade_leak_current_builder_scaffold_only_pair_trips` — `## Persona\n…\n## Memory` and two other current-builder pairs trip (2 structural-equivalent hits). - `cascade_leak_current_builder_scaffold_plus_envelope_trips` — `[Group message from …]\n## Persona\n…` trips (1 envelope-structural + 1 scaffold-structural-equivalent). - `cascade_leak_current_builder_ambiguous_alone_legitimate` — `## Current Date\n…\nyour day looks clear` is NOT a leak (only ambiguous header, no structural). - `cascade_leak_current_builder_ambiguous_plus_structural_trips` — `[Past exchange]\n…\n## Current Date\n…` trips (the 1+1 branch that was previously dead).
houko
added a commit
that referenced
this pull request
May 30, 2026
…prompt builder (#5351) The drift-pin test added by PR #5344 (`silent_response::tests::thematic_and_scaffold_headers_match_prompt_builder_output`) surfaced that `THEMATIC_HEADERS` and `SCAFFOLD_ONLY_HEADERS` in `crates/librefang-runtime/src/silent_response.rs` were calibrated against five legacy strings — `## Sender`, `## Today`, `## Calendar`, `## Tasks`, `## Response Style` — none of which the post-#5053 prompt builder emits. Consequence: the `1 structural + 1 thematic` branch of `is_cascade_leak` was effectively dead, and the detector silently degraded to a `2+ structural` rule. The canonical chat-template regurgitation shape from today's builder — `## Persona\n…\n## Memory` — had zero thematic hits and no longer tripped on its own, exactly the class of cross-chat leak the detector exists to catch. This reconciles the two lists with the actual headers enumerated by `prompt_builder.rs`: - 21 current-builder strings move into `SCAFFOLD_ONLY_HEADERS` (pure prompt-frame; no genuine reply emits a `## Persona\n` line-leader). - `## Current Date` is the lone ambiguous-subset entry, kept in `THEMATIC_HEADERS` but intentionally NOT in `SCAFFOLD_ONLY_HEADERS` — a legitimate "what's today?" reply may produce it. - The legacy five stay in both lists as a defensive belt-and-suspenders layer for any deployment still on the pre-#5053 builder. Cheap to leave; same false-positive risk as before. The drift-pin snapshot is flipped from `(marker, false)` for the legacy five to `(marker, true)` for the 21 current strings; the legacy five remain `false`. The two cross-check loops the snapshot test ships ensure no list can be edited without updating the snapshot in lock-step and vice versa — drift like this can't recur silently. Regression tests added in `silent_response::tests`: - `cascade_leak_current_builder_scaffold_only_pair_trips` — `## Persona\n…\n## Memory` and two other current-builder pairs trip (2 structural-equivalent hits). - `cascade_leak_current_builder_scaffold_plus_envelope_trips` — `[Group message from …]\n## Persona\n…` trips (1 envelope-structural + 1 scaffold-structural-equivalent). - `cascade_leak_current_builder_ambiguous_alone_legitimate` — `## Current Date\n…\nyour day looks clear` is NOT a leak (only ambiguous header, no structural). - `cascade_leak_current_builder_ambiguous_plus_structural_trips` — `[Past exchange]\n…\n## Current Date\n…` trips (the 1+1 branch that was previously dead). Co-authored-by: Federico Liva <[email protected]> Co-authored-by: Evan <[email protected]>
houko
added a commit
that referenced
this pull request
May 30, 2026
…log gen (#5924) extract_pr_numbers grepped every #N on a oneline subject, so in-title cross-references (issue refs like 'fixes #5740', prior-PR refs like 'post-#5053', and 'part N of M' markers like '(#2)') were treated as their own PRs and fed to 'gh pr view', pulling unrelated ancient or unmerged PRs into the generated release notes. A squash merge always appends the PR reference as the trailing (#N) of the subject, so parse only the last match per line. Split the parsing into a pure parse_pr_numbers helper with unit coverage. Co-authored-by: Evan <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements all four acceptance criteria of #3710. Three new standalone crates carved out
of the runtime god-crate, two pre-existing shell crates collapsed, every file in
librefang-runtime/src/is now under 2,000 LOC, and each tool dispatcher has its own fileunder
tool_runner/.Acceptance criteria
librefang-runtime/src/exceeds 2,000 LOCcontext_engine/scriptable/mod.rs).cargo build -p librefang-runtime --no-default-featuresexcludes browser/docker/media without compile errorscargo check -p librefang-runtime --lib --no-default-features.librefang-runtime-oauth,-wasm,-browser,-apply-patch), +3 added (-audit,-sandbox-docker,-media).tool_runner/Crates removed
Two re-export shell crates (~3 LOC each, no encapsulation):
librefang-runtime-oauth→ inlined back as runtime moduleslibrefang-runtime-wasm→ inlined back as runtime modulesTwo of the Phase 1 standalone crates were collapsed back into runtime as feature-gated
modules to satisfy AC #3 literally (the issue's "Suggested split" table proposed them as
crates, but AC #3 overrides when they conflict):
librefang-runtime-browser(~1 kLOC) →crates/librefang-runtime/src/browser.rsbehindfeature = "browser", withbrowser_stub.rsfallbacklibrefang-runtime-apply-patch(~1 kLOC) →crates/librefang-runtime/src/apply_patch.rs(small enough that a standalone crate adds no real encapsulation value)Crates added
Three subsystems large enough that standalone-crate encapsulation pays off:
librefang-runtime-auditlibrefang-typesdep.librefang-runtime-sandbox-dockersubprocess_sandbox/str_utilshelpers (duplicate-by-design) to avoid a circular dep on runtime.librefang-runtime-mediaMediaDrivertrait / cache, andmedia_understanding.rs.librefang-runtimere-exports each new crate under its existing module path(
pub use librefang_runtime_audit as audit;etc.), so every existing call site keeps workingunchanged.
What was NOT changed
librefang-runtime-mcpstays as-is (already a clean ~3.5 kLOC standalone crate; not a shell).agent_loopandtool_runnerstay insidelibrefang-runtime. Their top-level files wereshrunk via the smaller-extractions in this branch (
run_streaming.rs,dispatch.rs,definitions.rs,spill.rs,media.rs, …) so every file now fits under 2 kLOC, butpulling the whole modules out would be a separate, deeper refactor — consistent with the
project's CLAUDE.md guidance ("No new
agent_loop.rsortool_runner.rsfile additions;both files are slated to shrink, not grow").
Verification
Workspace-wide
cargo testis intentionally not run locally per repo policy; CI's splitunit-fast + integration shards cover it.
Commit map
3ec5efa5— collapselibrefang-runtime-oauthandlibrefang-runtime-wasm(Phase 2 shells)76432ded— extractlibrefang-runtime-auditeeb9d6c6— extractlibrefang-runtime-sandbox-dockera1b049db— extractlibrefang-runtime-browser(later collapsed back; see below)2307fb82— extractlibrefang-runtime-media1637e95a— extractlibrefang-runtime-apply-patch(later collapsed back; see below)35740a1a— fix grep-guard allow-list for the post-splitagent_loop/run_streaming.rs67b31c8c— collapse-browser+-apply-patchback into runtime as feature-gated modules (AC Bump docker/login-action from 3 to 4 #3)Other commits between these are the AC #1 / AC #4 work (splitting
agent_loopandtool_runnerinternals so every file fits under 2 kLOC and every tool dispatcher has itsown file).
Closes #3710.