Skip to content

refactor(kernel): split kernel/mod.rs into per-cluster files (#3744 phases 1-3)#4713

Merged
houko merged 13 commits into
mainfrom
refactor/kernel-mod-split
May 7, 2026
Merged

refactor(kernel): split kernel/mod.rs into per-cluster files (#3744 phases 1-3)#4713
houko merged 13 commits into
mainfrom
refactor/kernel-mod-split

Conversation

@houko

@houko houko commented May 6, 2026

Copy link
Copy Markdown
Contributor

Full split of the kernel/mod.rs god-file into per-cluster sibling
modules under crates/librefang-kernel/src/kernel/. Originally scoped
to phase 1 (trait impls) only; phases 2 and 3 were folded in as the
mechanical move turned out to be tractable in one PR.

Result

  • crates/librefang-kernel/src/kernel/mod.rs: 21,414 → 2,071 lines
    (-19,343, ~90% reduction).
  • ~28 new sibling files cover trait impls, accessors, lifecycle, cron,
    agent execution / state / runtime, MCP, provider probe, session ops,
    hands, prompt context, messaging, etc. See the kernel/ directory
    on the PR head for the full layout.
  • All new files are sibling submodules of kernel, so they retain
    access to LibreFangKernel's private fields and inherent methods
    without any visibility surgery (Rust scopes private items to the
    declaring module and its descendants).

Phases (in order)

  1. Phase 1 (59870124) — extract 16 kernel_handle::* for LibreFangKernel
    trait impls into kernel/handles/.
  2. Phase 2 (f9cc6a5b) — extract free-fn / cron-bridge / probe
    helpers into thematic siblings.
  3. Phase 3a-3e (e2027923a1a905c2) — extract the giant
    impl LibreFangKernel body cluster by cluster: accessors,
    cron_tick, then 5 + 5 + 7 thematic clusters covering everything
    from agent_runtime to config_reload_ops.

Behavioural regressions found and repaired during the split

The mechanical move turned out to silently drop several behavioural
hooks that cargo check / clippy could not see — all of them fail
open / fail-permissive, so CI green didn't catch them initially.
Repairs landed as separate commits inside this PR:

What was NOT touched

  • No method bodies were edited beyond what the regression repairs
    above explicitly call out.
  • No public API surface change. kernel_handle traits, route
    handlers, Kernel::new / boot_with_config, channel adapters etc.
    see the same shapes as on main.
  • No CHANGELOG entry — internal refactor.

Test plan

  • cargo check -p librefang-kernel --lib — clean.
  • cargo clippy --workspace --all-targets -- -D warnings — clean.
  • cargo test -p librefang-kernel --lib — all pass.
  • cargo test -p librefang-api --test config_routes_integration — all pass (covers reload-rejection paths post-33e4b7a9).
  • cargo test -p librefang-runtime --lib silent_response_single_source_of_truth — pass post-33e4b7a9.
  • rustfmt --check --edition 2021 on every moved file — clean.
  • Full workspace nextest + llvm-cov coverage via CI.

@github-actions github-actions Bot added area/kernel Core kernel (scheduling, RBAC, workflows) has-conflicts PR has merge conflicts that need resolution size/XL 1000+ lines changed labels May 6, 2026
houko added 5 commits May 7, 2026 09:36
…andles

Phase 1 of the kernel/mod.rs split. Move every `impl kernel_handle::* for
LibreFangKernel` block out of the 20K-line god-file into per-trait files
under `kernel/handles/`. The submodules are descendants of `kernel`, so
they retain access to LibreFangKernel's private fields and inherent
methods without any visibility surgery.

Mechanical, behavior-preserving move — no method bodies were touched.
mod.rs drops from ~20.6k to ~18.8k lines (-2073). Three imports that
became unused after the move were removed (kernel_handle self-alias,
ToolApprovalSubmission, std::str::FromStr) and re-imported locally in
the consuming handle files.

Traits moved:
  AgentControl, MemoryAccess, TaskQueue, EventBus, KnowledgeGraph,
  CronControl, HandsControl, ApprovalGate, A2ARegistry, ChannelSender,
  PromptStore, WorkflowRunner, GoalControl, ToolPolicy, ApiAuth,
  SessionWriter

Verified: cargo check -p librefang-kernel --lib + cargo clippy
-p librefang-kernel --lib -- -D warnings both green.
…pers

Move the cohesive bottom-of-file helpers out of kernel/mod.rs into
thematic sub-modules of `kernel`:

  mcp_summary.rs       – mcp_summary_cache_key, render_mcp_summary
  reviewer_sanitize.rs – sanitize_reviewer_line, sanitize_reviewer_block
  cron_script.rs       – cron_script_wake_gate, atomic_write_toml,
                         parse_wake_gate
  cron_bridge.rs       – `impl CronChannelSender for KernelCronBridge`,
                         CRON_EMPTY_OUTPUT_HEARTBEAT, cron_fan_out_targets,
                         cron_deliver_response
  provider_probe.rs    – probe_and_update_local_provider,
                         probe_all_local_providers_once

Mechanical, behavior-preserving move. mod.rs drops by ~636 lines (now
~18.2k). The struct `KernelCronBridge` itself stays in mod.rs; only its
trait impl moves. Free fns formerly visible only to mod.rs are now
`pub(super)` so the (still-resident) `LibreFangKernel` methods that call
them keep compiling. mod.rs adds a `use cron_bridge::{cron_deliver_response,
cron_fan_out_targets};` re-export so existing call sites in mod.rs and
`kernel::tests` resolve byte-for-byte.

Also restores `kernel_handle::self` alongside the prelude wildcard
import — phase 1 dropped the self alias as "newly unused", missing the
fact that `kernel::tests` uses it via `kernel_handle::ApprovalGate::...`.
Without this, `cargo test -p librefang-kernel --lib --no-run` fails.
…to kernel::accessors

Move the first `impl LibreFangKernel { ... }` block (formerly mod.rs
lines 1106..2598, ~1500 lines, ~85 methods — accessors like `home_dir`,
`config_ref`, `cron`, `pairing_ref`, plus operational helpers like
`spawn_key_validation`, `spawn_approval_sweep_task`, `record`,
`auto_dream_*`, etc.) into `crates/librefang-kernel/src/kernel/accessors.rs`.

Sibling submodule of `kernel::mod`, so retains private-field/method
access into `LibreFangKernel` without any visibility surgery (same
pattern as the Phase 1 trait-impl moves).

Mechanical, behavior-preserving move — method bodies untouched.
mod.rs drops from ~18.2k to ~16.7k lines (-1500). 4 inherent
`impl LibreFangKernel` blocks remain in mod.rs (down from 5);
phase 3b/3c will continue chipping.

Verified: cargo check / clippy -- -D warnings / cargo check --tests
/ rustfmt --check all green.
…::cron_tick

The cron scheduler tick loop was historically the longest closure in
mod.rs (~528 lines, the landing zone for #4683 et al.). Lift the body
out of `spawn_logged("cron_scheduler", async move { … })` into a free
`pub(super) async fn run_cron_scheduler_loop(kernel: Arc<LibreFangKernel>)`
in `crates/librefang-kernel/src/kernel/cron_tick.rs`.

Behaviour-preserving — body moved byte-for-byte; only the outer wrapper
changed (closure → free fn). Captured state was just `kernel`, which
becomes the single fn parameter; per-tick `cron_sem` and per-job clones
are still constructed inside the loop body, unchanged.

mod.rs drops by ~528 lines (now ~16.2k). Three Phase 2 re-exports
(`cron_deliver_response`, `cron_fan_out_targets`, `cron_script_wake_gate`)
that mod.rs introduced for the inline closure are no longer needed —
the only call sites moved with the loop — so they're gone from mod.rs's
`use` block.

Verified: cargo check / clippy -- -D warnings / cargo check --tests
all green.
… compaction

The Phase 1 mechanical-move subagent worked off a state that pre-dated
four recent main commits, silently reverting their changes during the
trait-impl extraction:

- #4685 (prompt store r2d2 pool): mod.rs called `memory.usage_conn()` and
  passed 1 arg to `PromptStore::new_with_path`. Both APIs had been replaced
  with `memory.pool()` / 2-arg `new_with_path(&db_path, pool_size)`. Restored.
- #3329 (memory_wiki vault): `kernel_handle::WikiAccess` is a `KernelHandle`
  super-trait but the `wiki_vault` field is not yet on `LibreFangKernel` in
  this branch. Replace the broken impl with an empty `impl WikiAccess for
  LibreFangKernel {}` so every method falls through to the trait default
  (`KernelOpError::unavailable("wiki_*")`) until the rebase-on-main work
  pulls in the full vault wiring.
- #4683 (cron summarize-trim compaction): `kernel::tests` references
  `cron_compute_keep_count` / `cron_clamp_keep_recent` /
  `cron_resolve_compaction_mode` / `try_summarize_trim` that were dropped
  from mod.rs. Cherry-pick the four helpers into a new
  `kernel::cron_compaction` sub-module + re-export from mod.rs so tests
  resolve. The matching cron-tick body change still needs to be ported
  into `kernel::cron_tick` — that's a separate item.

Verified: cargo check -p librefang-kernel --lib + cargo clippy
-p librefang-kernel --lib -- -D warnings + cargo check -p librefang-kernel
--lib --tests + rustfmt --check all green.

Note for reviewers: this PR's branch is "behind 4" of origin/main. The
proper fix is a rebase, which will involve genuine merge work in the
giant impl block that the refactor sliced. This commit is the minimum
to keep HEAD compiling against the current workspace deps; it does NOT
restore #4683's runtime changes (the SummarizeTrim path inside the cron
tick body) — only the helpers + their tests.
@houko
houko force-pushed the refactor/kernel-mod-split branch from 9c6458e to 594aa42 Compare May 7, 2026 01:01

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 594aa429e6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +238 to +243
let cfg_snap = kernel_job.config.load();
let max_tokens_raw = cfg_snap.cron_session_max_tokens;
let max_messages_raw = cfg_snap.cron_session_max_messages;
let warn_fraction = cfg_snap.cron_session_warn_fraction;
let warn_fallback = cfg_snap.cron_session_warn_total_tokens;
drop(cfg_snap);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore SummarizeTrim handling in cron compaction

For persistent cron jobs configured with cron_session_compaction_mode = SummarizeTrim, this extracted loop drops the config snapshot before reading either cron_session_compaction_mode or cron_session_compaction_keep_recent, and the only remaining compaction path below is plain front-drain pruning. That regresses the parent implementation's summarize-and-trim branch, so once a cron session exceeds the message/token caps it deletes old context instead of summarizing it, silently disabling the configured feature and losing useful history.

Useful? React with 👍 / 👎.

…n_tick

Phase 3b's `cron_tick.rs` body was the pre-#4683 closure (the version
my Phase 1 subagent saw); the SummarizeTrim runtime path was lost when
the Phase 1 work happened to omit #4683's mod.rs hunk. The earlier
repair commit restored the helper fns into `kernel::cron_compaction`
but left their call site missing.

Replace the body of `run_cron_scheduler_loop` with the post-#4683
version copied from origin/main's mod.rs (the `spawn_logged("cron_scheduler",
async move { … })` block at lines 14386..15064). Now the cron tick
body actually calls:

  - `cron_compute_keep_count` to size the keep window without mutating
  - `cron_resolve_compaction_mode` to fall back from SummarizeTrim →
    Prune when keep_count < 2
  - `cron_clamp_keep_recent` to enforce `[summary] + tail` cap
  - `try_summarize_trim` (aux LLM compaction) on the SummarizeTrim path
  - `apply_cron_prune` for both the configured Prune path and the
    SummarizeTrim → Prune fallback

`apply_cron_prune` was bumped to `pub(super)` so cron_tick.rs (sibling
submodule of cron_compaction) can call it.

Verified: cargo check / clippy -- -D warnings / cargo check --tests /
rustfmt --check all green.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0107d065c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


use super::super::LibreFangKernel;

impl kernel_handle::WikiAccess for LibreFangKernel {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the wiki vault-backed WikiAccess methods

When [memory_wiki] is enabled, this empty impl makes wiki_get, wiki_search, and wiki_write fall through to the trait defaults, so the built-in wiki tools now always report unavailable instead of using the vault. The parent code had a wiki_vault field initialized at boot and concrete methods here, so this extraction silently disables the configured durable wiki feature for any agent/tool call that reaches tool_wiki_*.

Useful? React with 👍 / 👎.

@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels May 7, 2026
Slice the still-resident giant `impl LibreFangKernel { ... }` body into
five cohesive sub-modules under `kernel/`:

  messaging.rs         — 26 send_message* / send_message_streaming_*
                         variants + send_message_full /
                         send_message_full_with_upstream +
                         resolve_agent_home_channel +
                         run_forked_agent_streaming
                         (~2 670 lines)
  assistant_routing.rs — 12 helpers driving the @-mention / specialist
                         routing path: notify_owner_bg,
                         llm_classify_intent, resolve_or_spawn_specialist,
                         send_message_streaming_resolved,
                         resolve_assistant_target,
                         route_assistant_by_metadata, etc.
                         (~460 lines)
  hands_lifecycle.rs   — 14 hand-management methods: activate_hand,
                         deactivate_hand, reload_hands,
                         apply_hand_agent_runtime_override_to_registry,
                         resolve_hand_agent_model_defaults, etc.
                         (~990 lines)
  mcp_setup.rs         — 6 MCP wiring fns: connect_mcp_servers,
                         reload_mcp_servers, retry_mcp_connection,
                         reconnect_mcp_server, run_mcp_health_loop,
                         disconnect_mcp_server (~610 lines)
  prompt_context.rs    — 7 cached-summary builders for prompt
                         assembly: cached_workspace_metadata,
                         cached_skill_metadata, active_goals_for_prompt,
                         build_skill_summary_from_skills,
                         build_mcp_summary, collect_prompt_context, etc.
                         (~340 lines)

Mechanical, behavior-preserving move — method bodies untouched.
mod.rs drops from ~16.2k to ~11.3k lines (-4 950). Combined with
phases 1+2+3a+3b that's a 9 350-line reduction (-45%) on the
original 20 609-line god-file.

Visibility surgery (only what the compiler demanded; private inherent
methods are not visible across sibling submodules without it):

  - messaging::send_message_full / send_message_streaming_with_sender_and_session
    bumped to pub(crate) — called from cron_tick + assistant_routing.
  - assistant_routing::{notify_owner_bg, send_message_streaming_resolved,
    resolve_assistant_target, assistant_route_key,
    should_skip_intent_classification} bumped to pub(crate) — called
    from messaging + tests.
  - mcp_setup::run_mcp_health_loop bumped to pub(crate) — kept-resident
    spawn_logged callsite in mod.rs needs it.
  - prompt_context::{cached_workspace_metadata, cached_skill_metadata,
    active_goals_for_prompt, build_mcp_summary} bumped to pub(crate)
    for cross-module consumers.
  - mod.rs structs CachedWorkspaceMetadata + CachedSkillMetadata
    bumped to pub(crate) struct (private fields kept) so the
    pub(crate) accessor signatures don't trip the private-interfaces
    lint.

Verified across all 5 cluster checkpoints + final pass: cargo check
-p librefang-kernel --lib + clippy -- -D warnings + check --tests +
rustfmt --check all green.
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

crate::config::load_config(Some(&config_path))

P1 Badge Reject malformed config reloads instead of loading defaults

When config.toml exists but has invalid TOML, a broken include, or a deserialization error, load_config logs the problem and returns KernelConfig::default(). In this reload path that default config is then validated and can be stored into the live kernel, so a bad edit from the watcher or POST /api/config/reload can wipe provider keys, channels, model settings, and other hot config instead of leaving the old config intact. Use the strict loader here so reload failures return Err and do not swap state.


let mut substrate = MemorySubstrate::open_with_chunking(

P2 Badge Honor configured SQLite pool size at boot

For deployments that set [memory].pool_size to anything other than the default, this call now ignores the configured value because open_with_chunking always uses DEFAULT_POOL_SIZE. The parent boot path passed config.memory.pool_size to open_with_pool_size, so operators tuning SQLite concurrency or resource usage will silently get the wrong pool size for the kernel's memory substrate.


let warnings = config.validate();

P2 Badge Fail fast on invalid tool-exec backend config

When [tool_exec].kind is ssh or daytona but the matching subtable or required fields are missing, this path now only runs the generic config.validate() warning pass and no longer calls config.tool_exec.validate(). That lets the daemon boot with an unusable tool execution backend, so the operator does not get the intended boot-time failure and only discovers the misconfiguration on the first affected tool call.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Continue slicing the still-resident `impl LibreFangKernel { ... }` body
into thematic sub-modules:

  boot.rs            — session_stream_hub, boot, boot_with_config
                       (~2 005 lines — `boot_with_config` is the
                       single largest method extracted so far)
  spawn.rs           — spawn_agent + 4 spawn_agent_with_*  +
                       spawn_agent_inner + verify_signed_manifest +
                       trusted_manifest_signer_keys (~410 lines)
  agent_execution.rs — execute_wasm_agent, execute_python_agent,
                       should_reuse_cached_route, is_brief_acknowledgement,
                       execute_llm_agent (~1 195 lines)
  session_ops.rs     — inject_message + inject_message_for_session +
                       setup/teardown_injection_channel + resolve_module_path
                       + reset/reboot_session + clear_agent_history +
                       list/create/switch/export/import_session +
                       inject_reset_prompt + evaluate_condition +
                       save_session_summary (~820 lines)
  agent_state.rs     — persist_manifest_to_disk, set_agent_model,
                       reload_agent_from_disk, update_manifest,
                       set_agent_skills, set_agent_mcp_servers,
                       set_agent_tool_filters (~530 lines)

Mechanical move — method bodies untouched. mod.rs drops from
~11.3k to ~6.4k lines (-4 870). Cumulative across phases 1+2+3a+3b+3c+3d
the original 20 609-line god-file is now ~6 385 lines (-69%).

Visibility surgery (only what the compiler demanded for cross-submodule
call sites):

  - spawn::spawn_agent_inner          → pub(crate) (called by hands_lifecycle)
  - agent_execution::execute_wasm_agent / execute_python_agent /
    execute_llm_agent / should_reuse_cached_route → pub(crate)
    (called by messaging + assistant_routing)
  - session_ops::{setup_injection_channel, teardown_injection_channel,
    resolve_module_path, inject_reset_prompt, evaluate_condition}
    → pub(crate) (called by messaging / spawn / tests)

No struct or enum visibility bumps were needed — `boot.rs` builds
the kernel via struct literal because descendant submodules retain
private-field access.

Verified: cargo check / clippy -- -D warnings / cargo check --tests /
rustfmt --check all green at every cluster checkpoint and the final
pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79eb683446

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +136 to +140
// Validate configuration and log warnings
let warnings = config.validate();
for w in &warnings {
warn!("Config: {}", w);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore boot-time tool_exec validation

For deployments that set [tool_exec] kind = "ssh" or "daytona", this boot path now only logs config.validate() warnings and no longer calls config.tool_exec.validate(), so a missing [tool_exec.ssh]/[tool_exec.daytona] subtable no longer aborts startup. The daemon can come up with an unusable active tool backend and fail later on every tool execution instead of surfacing the configuration error at boot.

Useful? React with 👍 / 👎.

Comment on lines +181 to +185
let mut substrate = MemorySubstrate::open_with_chunking(
&db_path,
config.memory.decay_rate as f32,
config.memory.chunking.clone(),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor configured memory pool size

When operators tune [memory] pool_size, the primary MemorySubstrate now ignores it because open_with_chunking uses the library default pool size; the parent path called open_with_pool_size(..., config.memory.pool_size). This makes the runtime use an unexpected SQLite pool size under high or deliberately constrained concurrency, while only the prompt store below still honors the setting.

Useful? React with 👍 / 👎.

Comment on lines +61 to +66
// SECURITY (#3533): reject manifest `module` strings that escape
// the LibreFang home dir before any further work. See
// `validate_manifest_module_path` for the full rationale and the
// sibling enforcement points (boot restore, hot reload,
// update_manifest).
validate_manifest_module_path(&manifest, &name)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject invalid per-agent tool_exec overrides

For agents that set tool_exec_backend = "ssh" or "daytona" while the daemon lacks the matching global subtable, spawn_agent_inner now validates only the module path and then registers the agent. The previous check rejected the spawn before it reached the registry, so this regression leaves an agent running with a backend override that will fail when it tries to execute any tool.

Useful? React with 👍 / 👎.

Move runtime-control methods (cost/stop/list/suspend/resume/compact/
context_report/kill + watchers) from the giant `impl LibreFangKernel`
into `kernel/agent_runtime.rs`. mod.rs drops from 6 385 to 5 825
(-560).

Methods: session_usage_cost, stop_agent_run, stop_session_run,
list_running_sessions, agent_has_active_session, running_session_ids,
suspend_agent, resume_agent, persist_agent_enabled,
compact_agent_session, compact_agent_session_with_id, context_report,
register_agent_watcher, abort_agent_watchers, kill_agent,
kill_agent_with_purge.

Verified: cargo check / clippy -- -D warnings / cargo check --tests
all green.
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

crate::config::load_config(Some(&config_path))

P1 Badge Use the strict loader during hot reload

When config.toml is syntactically invalid or an include chain is broken, load_config is intentionally tolerant and falls back to defaults or a partial root config, so the hot-reload path proceeds to diff/apply those fallback values over the live settings. This reintroduces the #4664 failure documented by reload_config_with_invalid_toml_preserves_live_config: a bad reload can silently replace the operator's active default_model, provider keys, and channel config instead of returning an error and preserving the current config.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@houko

houko commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

Read-only review against the diff at HEAD (8213f05b); did not run CI. The refactor goal (slim kernel/mod.rs) is well-motivated and the bulk of the move looks faithful, but four behavioural regressions slipped through that are not caught by the test plan listed in the PR body (cargo check / clippy / rustfmt only). Three of them are confirmed at HEAD; the fourth was real and you already fixed it in 0107d065 (cron SummarizeTrim).

This is exactly the failure mode of a 19 K-line mechanical move: each missed validation hook compiles and clippies fine because all the types still match — the regression only shows up in behaviour. None of these would be caught without integration tests that exercise boot / spawn / the wiki tools.

Confirmed regressions vs origin/main

[HIGH] boot.rs:181 — primary substrate ignores config.memory.pool_size

let mut substrate = MemorySubstrate::open_with_chunking(
    &db_path,
    config.memory.decay_rate as f32,
    config.memory.chunking.clone(),
)

origin/main:crates/librefang-kernel/src/kernel/mod.rs:2956 calls MemorySubstrate::open_with_pool_size(..., config.memory.pool_size). The 3-arg form falls back to DEFAULT_POOL_SIZE = 8 regardless of operator config. Direct regression of #4685's [memory] pool_size knob; only the prompt store init below this line still honours the setting. Not caught because both signatures compile and a default of 8 is the most common operator value.

Fix: MemorySubstrate::open_with_pool_size(&db_path, config.memory.decay_rate as f32, config.memory.chunking.clone(), config.memory.pool_size).

[HIGH] boot.rs:138config.tool_exec.validate() not called

origin/main:crates/librefang-kernel/src/kernel/mod.rs:2896 runs if let Err(e) = config.tool_exec.validate() { return Err(BootFailed(...)) } after the workspace-wide config.validate() warnings. The PR drops the second call entirely. Deployments with [tool_exec] kind = "ssh" or "daytona" but missing the matching [tool_exec.ssh] / [tool_exec.daytona] subtable will now boot silently and fail-on-first-tool-call instead of aborting at boot with a configuration error.

Fix: re-add the config.tool_exec.validate() call right after the workspace config.validate() block.

[HIGH] spawn.rs:54 — per-agent tool_exec_backend override no longer rejected at spawn

origin/main:crates/librefang-kernel/src/kernel/mod.rs:4860:

if let Some(override_kind) = manifest.tool_exec_backend {
    if let Err(e) = config.tool_exec.validate_override(override_kind) {
        return Err(KernelError::SpawnFailed(format!(
            "agent {name:?} tool_exec_backend = {:?} but {e}", ...
        )));
    }
}

The new spawn.rs keeps validate_manifest_module_path(&manifest, &name) but the entire override-validation block is gone. An agent with tool_exec_backend = "ssh" against a daemon that doesn't have [tool_exec.ssh] configured will spawn into the registry and fail every tool invocation thereafter, instead of being rejected pre-registry like main.

Fix: port the if let Some(override_kind) = manifest.tool_exec_backend { ... } block from the old spawn_agent_inner into the new spawn.rs::spawn_agent_inner, in the same position relative to validate_manifest_module_path.

[HIGH] kernel/handles/wiki_access.rs — empty impl disables wiki on main

//! [...] until the matching `wiki_vault` field lands on `LibreFangKernel`
//! (will arrive with the #3329 main-side merge), this impl block is
//! intentionally empty — every method falls through to the trait default.

impl kernel_handle::WikiAccess for LibreFangKernel {}

The premise here is wrong: wiki_vault and the concrete wiki_get / wiki_search / wiki_write impls are already on origin/main (mod.rs:784 declares the field, lines 18828–18900-ish hold the trait impl). I confirmed via git show origin/main:crates/librefang-kernel/src/kernel/mod.rs | grep -nE 'wiki_vault|wiki_get' — 5 matches, including the field, the boot-time wiki_vault initialiser, and the concrete trait methods.

So this PR is silently disabling the wiki feature for any deployment that has [memory_wiki] enabled — every tool_wiki_get/search/write call now flows to the trait default which returns KernelOpError::unavailable("wiki_*").

Fix: port the actual wiki_get / wiki_search / wiki_write method bodies from main's impl kernel_handle::WikiAccess for LibreFangKernel, and update the file header to remove the "waiting for #3329 merge" framing.

Already fixed

[P1] cron_tick.rs — SummarizeTrim path

Codex flagged this on commit 0107d065's parent. Looking at HEAD (8213f05b), the SummarizeTrim arm is back in place at cron_tick.rs:305-330, including the cron_resolve_compaction_mode re-route and the try_summarize_trim call. Codex's claim that the config snapshot is dropped before cron_session_compaction_mode is read is also no longer true at HEAD — let compaction_mode = cfg_snap.cron_session_compaction_mode; is on line 252, drop(cfg_snap); on line 254. Checked.

Description scope mismatch

The PR body says it is "Phase 1 of the kernel/mod.rs split" and that "mod.rs drops from ~20 600 lines to ~18 800 lines (-2 073)", with Phase 2 / Phase 3 listed as out-of-scope future PRs.

But the diff stat says +19574 / -19177 with mod.rs going +4148 / -19177 — so mod.rs lost ~15 000 lines, not ~2 000, and the new files include boot.rs (+2005), messaging.rs (+2671), cron_tick.rs (+670), accessors.rs (+1531), agent_execution.rs (+1194), session_ops.rs (+822), hands_lifecycle.rs (+987), etc. — clearly Phase 1 + 2 + 3a + 3b + 3c + 3d + 3e/1, all stacked into one PR.

This isn't a blocker on its own, but it makes the PR misleading to drive-by reviewers and turns the body into a load-bearing mismatch with the actual diff. Suggest updating the description to enumerate the actual phases included (one paragraph per phase + the cumulative line-count delta), and renaming the title from "phase 1" to something like "extract handle traits + boot/messaging/cron/accessor clusters".

Process suggestion: a regression-spotter for mechanical moves

For pure mechanical-move refactors, cargo check passing is necessary but not sufficient — every regression I found is type-clean. A 60-second pre-push discipline that would have caught all four:

For each helper / validator that must survive the move, count occurrences in origin/main's mod.rs vs the post-refactor kernel directory. Anything where the after-count is less than the before-count is either intentionally removed (rare for this kind of refactor) or a missed move site. Symbols worth checking on a kernel split: tool_exec.validate, tool_exec_backend, open_with_pool_size, wiki_get / wiki_search / wiki_write, plus any field of LibreFangKernel that previously had a concrete trait impl.

Worth committing as a generic invariant-checking script under scripts/ so the next phase split (phase 4 etc.) doesn't re-discover this class of regression by accident.

Recommendation

Request changes — block on the 4 confirmed regressions. The wiki one is the most user-visible (silent feature disable on [memory_wiki] deployments); the boot pool-size one is a direct regression of just-merged #4685; the spawn and boot tool_exec validations are silent-broken-config regressions.

After the fixes:

  1. Wait for full CI on the merged result.
  2. Update PR description to match actual scope.
  3. Consider squashing the regression-fix commits into the original phase commits before merge so the history doesn't read as "Phase 1 + 5 fixes for things Phase 1 broke".

Refactor itself looks well-structured — handles/ per-trait organisation is the right shape, pub(super) visibility surgery looks consistent, and cargo check cleanness on a 19 K-line move is a real achievement. The remaining work is just fishing out the missed move sites and porting their bodies into the new homes.

Refs Codex review on commits 594aa429 / 0107d065 / 79eb6834. Codex finding #1 (P1 cron SummarizeTrim) addressed by 0107d065; codex findings #2 through #5 (the four P2 items) still open at HEAD.

…ant impl

Continue slicing the still-resident `impl LibreFangKernel { ... }` body
into thematic submodules. After this commit the giant first impl block
is fully drained.

  bindings_and_handle.rs  — set_log_reloader, set_self_handle,
                            kernel_handle, list_bindings, add_binding,
                            remove_binding (~95 lines)
  config_reload_ops.rs    — reload_config + apply_hot_actions_inner
                            (~480 lines)
  triggers_and_workflow.rs — spawn_session_label_generation,
                             one_shot_llm_call, publish_event +
                             publish_event_inner, register_trigger +
                             register_trigger_with_target +
                             remove_trigger / set_trigger_enabled /
                             list_triggers / get_trigger / update_trigger,
                             register_workflow, run_workflow,
                             dry_run_workflow (~640 lines)
  background_lifecycle.rs — start_background_agents, start_ofp_node,
                            self_arc, start_heartbeat_monitor,
                            start_background_for_agent, shutdown
                            (~1 270 lines — the longest single
                            extraction in this PR)
  llm_drivers.rs          — lookup_provider_url, resolve_driver
                            (~250 lines)
  tools_and_skills.rs     — available_tools, reload_skills,
                            try_claim_skill_review_slot,
                            summarize_traces_for_review,
                            background_skill_review,
                            is_transient_review_error,
                            extract_json_from_llm_response,
                            context_engine_for_agent (~1 080 lines)

Mechanical move — method bodies untouched. mod.rs drops from
~5.8k to ~2.0k lines (-3 800). Combined with phases 1+2+3a+3b+3c+3d
the original 20 609-line god-file is now at ~2 000 lines (-90%).

Visibility surgery (only what the compiler demanded):

  - tools_and_skills::{context_engine_for_agent, try_claim_skill_review_slot,
    summarize_traces_for_review, background_skill_review,
    extract_json_from_llm_response, is_transient_review_error,
    MAX_INFLIGHT_SKILL_REVIEWS} → pub(crate) (cross-submodule + tests)
  - mod.rs `enum ReviewError` → pub(crate) enum (private-interfaces lint
    once tools_and_skills::background_skill_review's signature went
    pub(crate))

Plus a cosmetic doc-paragraph rewrap in `llm_drivers.rs` to satisfy
`clippy::doc_lazy_continuation`.

Verified: cargo check / clippy -- -D warnings / cargo check --tests
all green.
@github-actions github-actions Bot added has-conflicts PR has merge conflicts that need resolution and removed ready-for-review PR is ready for maintainer review labels May 7, 2026
…review)

The kernel/mod split landed type-clean but silently dropped four
behavioural hooks that `cargo check` / `clippy` could not see. All
four fail open / fail-permissive (rather than fail-closed), which is
why CI green doesn't catch them — they're only visible by exercising
the boot path or the wiki tools at runtime.

Restored to parity with `origin/main` (pre-split):

- `boot.rs`: re-add `config.tool_exec.validate()` after the
  workspace-wide `config.validate()` warning loop. Without it, a
  deployment with `[tool_exec] kind = "ssh"` or `"daytona"` but
  missing the matching subtable boots silently and fails on first
  tool call instead of aborting at boot.
- `boot.rs`: `MemorySubstrate::open_with_chunking(...)` →
  `open_with_pool_size(..., config.memory.pool_size)`. The 3-arg form
  forces the r2d2 default pool size of 8 regardless of operator
  config, so `[memory] pool_size` (just landed in #4685) was a no-op
  for the primary substrate; only the prompt store init below this
  line still honoured the setting, leaving the two SQLite pools
  inconsistently sized.
- `spawn.rs::spawn_agent_inner`: re-add the
  `manifest.tool_exec_backend` override validation block after
  `validate_manifest_module_path`. Without it, an agent whose
  manifest pins `tool_exec_backend = "ssh"` against a daemon lacking
  `[tool_exec.ssh]` enters the registry and fails every tool
  invocation thereafter, instead of being rejected pre-registry.
- `mod.rs` + `boot.rs` + `handles/wiki_access.rs`: re-add the
  `wiki_vault: Option<Arc<librefang_memory_wiki::WikiVault>>` field on
  `LibreFangKernel`, the boot-time `WikiVault::new` initializer with
  the same fail-open warning behaviour as main, the
  `wiki_vault: wiki_vault.clone()` in the struct construction, and
  the concrete `wiki_get` / `wiki_search` / `wiki_write` method
  bodies (ported verbatim from the pre-split mod.rs). Without these
  three pieces, every `tool_wiki_*` call on a deployment with
  `[memory_wiki] enabled = true` flowed to the trait default and
  returned `KernelOpError::unavailable("wiki_*")`, silently disabling
  the just-landed #3329 feature. Phase 1 had left an empty
  `impl WikiAccess for LibreFangKernel {}` with a header comment
  claiming \"will arrive with the #3329 main-side merge\" — but
  #3329 was already on `origin/main` (#4712) at the time of the
  branch.

Verified locally:
  cargo check  -p librefang-kernel --lib                       OK
  cargo clippy -p librefang-kernel --lib -- -D warnings        0 warnings
  rustfmt      --check --edition 2021 (4 modified files)       clean

Refs Codex review on commits 594aa42 / 0107d06 / 79eb683 of the
parent PR — findings #2-#5 (the four P2 items still open at HEAD)
addressed here. Codex P1 finding #1 (cron SummarizeTrim) was
addressed earlier in 0107d06 and is unaffected by this commit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e14068dcf6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +27 to +28
let mut new_config = if config_path.exists() {
crate::config::load_config(Some(&config_path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve live config when reload parsing fails

When config.toml has invalid TOML, a bad include, or a deserialize error, load_config logs and returns KernelConfig::default() instead of an error; because the default reload mode is Hybrid and defaults validate, this path can build and apply a hot-reload plan against defaults, wiping the live channels/auth/cron/provider settings rather than leaving the old config intact. The previous try_load_config call was the strict path specifically meant to reject these failures before any live swap.

Useful? React with 👍 / 👎.

Comment on lines +147 to +151
if let Err(e) = config.tool_exec.validate() {
return Err(KernelError::BootFailed(format!(
"Invalid [tool_exec] config: {e}"
)));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep warning for ignored tool_exec backends

When [tool_exec] kind = "ssh" or "daytona" has a valid subtable, boot now succeeds silently after validation. I checked the execution path (tool_runner/agent_loop) and it still does not read KernelConfig.tool_exec or call build_backend, so tool calls continue on the legacy local/docker path; the removed warning was the only operator-facing signal that the configured remote backend is not active.

Useful? React with 👍 / 👎.

…llow-list (#4713)

Phase 3e/3 moved Kernel::reload_config into kernel/config_reload_ops.rs
but the body that landed there was the pre-#4664 tolerant version: it
called crate::config::load_config(...) (which silently returns
KernelConfig::default() on parse / migration / deserialize / include
failures) and wrapped validator errors as "Validation failed: ..."
without the "live config unchanged" reload-boundary pledge.

The strict-loader function crate::config::try_load_config and its 7
direct unit tests in config.rs are still present (orphaned), but the
4 api integration tests + 1 kernel integration test that drive
reload_config end-to-end all fail because the strict path was never
re-wired after the move. Restore parity with #4664:

- swap load_config for try_load_config and propagate the Err through
  the same "Config reload failed; live config unchanged: {e}" wrapper
- wrap the validator branch with the same prefix so every
  reload-rejection path satisfies the "live config unchanged"
  invariant the integration helper relies on

Also: phase 3 split Kernel::cron_tick into a sibling kernel/cron_tick.rs
file that carries one inline comment mentioning the NO_REPLY literal.
The silent_response_single_source_of_truth grep-guard in
librefang-runtime allow-lists the literal in selected files; add
cron_tick.rs next to the existing mod.rs allow entry so the guard
stops flagging it.

Verified locally: kernel + api + runtime tests pass; clippy clean.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@houko houko changed the title refactor(kernel): extract 16 kernel_handle trait impls into kernel::handles (#3744 follow-up phase 1) refactor(kernel): split kernel/mod.rs into per-cluster files (#3744 phases 1-3) May 7, 2026
@github-actions github-actions Bot added the area/runtime Agent loop, LLM drivers, WASM sandbox label May 7, 2026
Pull in 7 commits from main since merge-base dbd9042, including the
two Rust changes that landed mid-split:

- #4725 (a6f1f91) — workflow drain on shutdown (#3335). Adds an
  18-line drain block + 1-line trace log between `supervisor.shutdown()`
  and the agent-state-suspend block. Migrated to its post-split home in
  `kernel/background_lifecycle.rs::shutdown` (the original mod.rs site
  was extracted in phase 3a).
- #4726 (13e8bb9) — KernelApi trait + Arc<dyn KernelApi> AppState
  (#3566). Auto-merged cleanly because all touched files outside
  mod.rs (api crate routes / extensions / extractors / kernel_api.rs)
  hit no overlap with the split. The doc comment added on
  `Kernel::run_workflow` referencing `KernelApi::run_workflow_typed`
  was migrated to its post-split home in
  `kernel/triggers_and_workflow.rs`.

The mod.rs conflict was the entire 16,460-line region that the split
moved out into ~28 sibling files. Resolution: keep HEAD (split version,
~2k lines), drop the origin/main reproduction of the pre-split block —
all of that code now lives under sibling submodules with the same
visibility (descendants of `kernel`).

Verified locally:
- `cargo check -p librefang-kernel -p librefang-api --lib` — clean (35s).
- `cargo check --workspace --lib` — clean (2m 10s).
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@houko
houko merged commit 858c79b into main May 7, 2026
18 checks passed
@houko
houko deleted the refactor/kernel-mod-split branch May 7, 2026 03:23
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels May 7, 2026
@houko houko mentioned this pull request May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/kernel Core kernel (scheduling, RBAC, workflows) area/runtime Agent loop, LLM drivers, WASM sandbox ready-for-review PR is ready for maintainer review size/XL 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant