Skip to content

feat(kernelforge): offer a configured knowledge base to every forge role and record why each step was taken - #2034

Closed
zili-amd wants to merge 27 commits into
mainfrom
feat/kernelforge-knowledge-tools
Closed

zili-amd wants to merge 27 commits into
mainfrom
feat/kernelforge-knowledge-tools

Conversation

@zili-amd

@zili-amd zili-amd commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator
  • Description: what and why

    The forge planning roles (dispatch, specialists, synthesis, plan critic, supervisor) have no shell, so a kernel knowledge base reachable only through Bash never reaches the agents that choose the optimization direction, and a run leaves no record of why each step was taken. This PR adds three foundations, each its own commit:

    1. Knowledge base as read-only tools for every forge role. FORGE_KNOWLEDGE_WIKI_ROOT names a KernelWiki-layout checkout. Every session that has tools then gets the same stdio MCP server (kernelforge.knowledge.wiki_tool.mcp_server, on the shared tools.mcp_stdio transport) with search, page, grep and guide, which run the checkout's own scripts. A page path that is absolute or resolves outside the checkout is refused before the script runs. Each call takes a required one-line reason and is appended, with the calling role, the reason and only the arguments the tool declares, to FORGE_KNOWLEDGE_WIKI_LOG (knowledge_wiki_calls.jsonl). Unset, no session changes. Set, a root that is not an absolute path to such a checkout, or a log path that is not absolute, is refused when the campaign starts, and so is a backend without MCP support, which would silently drop the tools: when each role is built at startup, the supervisor, whose backend is built on its first pass, by what its provider and its fallback provider declare. The built-in claude and codex backends support MCP; deepseek does not, so it is refused while a knowledge base is set. Both variables are documented in docs/reference/environment-variables.md; FORGE_KNOWLEDGE_WIKI_ROLE is set by forge for each session's server and is internal.
    2. Decision rationale on every iteration. Both Implementer prompts (with and without the in-session gate) ask for one RATIONALE: line just before the PLAN: line: the direction and why it should beat best, or why a direction is exhausted. agent_fn records that line, parsed as PLAN: is (the last such line, at most 500 characters), and no rationale provided when there is none; before, it returned the gate tag and the first 200 characters of the session text, which on Claude is every text block of the session, so the opening remarks were recorded instead of the reasoning. The rationale goes on the iteration_result event, and each reader keeps its own cap: 200 characters on the console line, 80 in the history line, 72 in a KEEP's commit subject. The gate's figures stay where they already were, in the session sink and on the [session-end] line. A KEEP is committed under one line with no trailing space, which is the subject a resume compares with the one git recorded. A fan-out lane keeps its own rationale through the lane queue (a queue written before this restores an empty one), so the iteration that measures it records lane N of a fan-out round: <rationale> instead of the bare lane number. An iteration whose planning failed records planning failed; no Implementer session: <error>.
    3. --target-mean-case-speedup X. Ends the campaign before the next round once the incumbent's mean per-case speedup over the baseline (the aggregate a KEEP is scored by) reaches X, with termination reason speedup_target_met; a resume whose recorded best already meets it ends without another round. Zero (default) disables it; any other value at or below 1.0 (a negative one included), or one that is not finite, is refused; and it never enters the KEEP decision.

    Why one PR: part 1 is what the wiki-guided planning mode (feat(kernelforge): spend the first round on a knowledge base batch with --wiki-guided #2036, a draft stacked on this one) builds on in code. Parts 2 and 3 are not referenced by feat(kernelforge): spend the first round on a knowledge base batch with --wiki-guided #2036's diff; they are what its evaluation runs used, to compare trajectories by why each step was taken and to stop at a set speedup. Each is a separate commit and can be reviewed on its own. An earlier revision also let a declared target named like a test escape the test-name protection; that is dropped from this PR, because the tests in such a file would leave the tamper snapshot, and will come separately with symbol-scoped protection.

  • Linked issue(s): none

  • Tests: added/updated? commands run?

    39 new tests. Through the real call paths: test_knowledge_wiki_tool.py (the MCP server over stdio, the reason requirement and log record, a page path outside the checkout refused before the script runs, a log line that records only the declared arguments, which roles get the server through OrchestrationAgent._run_spec, SpecialistAgent, PlanCriticAgent.review, AnalysisAgentService.ensure_bundle, make_agent_fn and make_supervisor_fn, that a tool-less formatting turn gets none, unusable settings refused before any session starts, and a supervisor refused when it is built if its provider or its fallback provider declares no MCP); test_claude_resume.py (a real ClaudeBackend over a stream that opens with other text records the declared RATIONALE: line, and its prompt asks for one); test_codex_backend.py (with the gate on, the declared line comes back whole, longer than the old cut and without the gate tag, and a session that declares none records the placeholder); test_campaign_cross_process.py (a KEEP committed before the state checkpoint is recovered on resume when its rationale has a newline in the first 72 characters or a cut that ends on a space); test_loop_runner.py (rationale on iteration_result, the lane rationale through a fan-out round, the rationale of an iteration whose planning failed, a resumed best at, short of and without the speedup target); test_forge_loop_resume.py (the speedup option reaches the loop config, is off by default, and refuses a value the baseline meets, a negative one included, and one that can never be reached); test_lane_plan_persistence.py (lane rationale persisted and restored across processes, the old queue format). test_specialist_probe.py and test_forge_orchestration.py now build SpecialistAgent(tools=SpecialistTools(probe=...)); their assertions are unchanged.

    Run locally: pytest src/kernelforge -n 16 (6101 passed, 19 skipped, on main at d0d690b), ruff check and ruff format --check, lint-imports, python scripts/code_metrics.py --base-ref origin/main, python scripts/pr_hygiene.py size, python scripts/check_cli_references.py.

  • Size/complexity triggers crossed: split, or why the shape is right:

    745 production lines (size/XL, over the 400-line review trigger, under the 1000-line limit); 464 of them are the new knowledge/wiki_tool/ package (the MCP server alone is 314). Review commit by commit.

    Requesting baseline-raise. The new logic lives in a new package under every limit (kernelforge/knowledge/wiki_tool/: the session side, the MCP server and the contract between them); what remains is a few lines of wiring in units and modules that were already over their limits at the base:

    Unit Base Now
    forge_loop (function lines) 421 423
    make_agent_fn (function lines) 263 266
    SpecialistAgent.run (function lines) 95 98
    AnalysisAgentService._run_analysis_session (function lines) 81 83
    Config.from_env (function lines) 67 69
    PlanCriticAgent.review (function lines) 66 68
    make_orchestration_service (function lines) 63 66
    loop/runner.py (module lines) 6510 6554
    cli/forge_loop_command.py (module lines, over the limit at the base but not baselined) 2003 2026
    orchestrator/orchestration.py (module lines) 1831 1843
    orchestrator/analysis.py (module lines) 1952 1957

    The lane-rationale text, the planning-failure result and the knowledge-base banner were moved into helpers, so _source_attempt is shorter than at the base (142 lines, from 148) and its and forge_loop's cognitive complexity stay at their base values. The knowledge base path checks and the speedup target's stop are units of their own (Config._normalize_knowledge_wiki_paths, _stop_at_speedup_target), so Config.__post_init__ and _stop_condition_met stay under the 50-line limit. make_agent_fn's cognitive complexity drops from 72 to 70 and agent_fn from 142 to 140 lines, and their baseline entries are lowered to match. SpecialistAgent takes the probe and the knowledge base as one SpecialistTools value to stay at five parameters. Splitting these 60- to 6000-line units is outside a feature PR. Every other check (comments, broad catches, English only, repeated literals, A1 and A8 to A12, duplication, dead code) reports nothing.

  • If this simplifies or refactors: which mechanism was removed, which contract was preserved, and the tests that show it:

    Not a refactor. One internal signature changed: SpecialistAgent(probe=...) is now SpecialistAgent(tools=SpecialistTools(probe=..., knowledge_wiki=...)); its one production caller, the orchestration factory, is updated. The four variables a python -m kernelforge... MCP server needs to start are now one PYTHON_LAUNCH_ENV_VARS beside StdioMcpServer, which the probe server and the knowledge base server share; the probe child's environment is unchanged.

  • Observable effect: what an operator will now see, and what the old behaviour cost them:

    With FORGE_KNOWLEDGE_WIKI_ROOT set, the run banner prints Knowledge base: <root> (calls logged to <log>), and every role's knowledge-base call appears in the log with its role and reason; before, the planning roles could not read the knowledge base at all. A root or log path that is not absolute, a root that is not a knowledge base checkout, or a backend without MCP support is refused, as item 1 describes, instead of running with the tools silently missing. Every iteration_result event carries rationale, the line the Implementer declared, and a lane's iteration names its own reason; before, a run could not be compared by why each step was taken, the multi-lane iterations, often the ones that move the score most, logged no reason, and on Claude the recorded text was the session's opening remarks. A KEEP's commit subject (iter-N: ...) and the Implementer's history line now carry that rationale instead of the gate tag [gate edits=... pass=... end=...; the gate's edit count, pass and end reason remain on the [session-end] line, and the history line still shows the speedup of each measured iteration. The subject is one line with no trailing space, so a campaign interrupted after a KEEP commit still resumes; a subject that git folded or trimmed would have been refused with pending KEEP commit message mismatch. --target-mean-case-speedup ends a run with speedup_target_met; before, the only target was the roofline one, which needs an estimated ceiling and refuses to rule while a cache-resident case beats it.

  • Breaking changes: no (no CLI, configuration or event field removed; the SpecialistAgent constructor change is internal; a resume compares a pending KEEP's commit subject with the one stored when it was built, so the new wording is stored and compared together, and a pending KEEP written before this keeps the subject it stored)

  • PR addresses single concern: no. It bundles the three foundations above: the knowledge base tools the wiki-guided planning PR builds on, and the rationale and speedup target its evaluation runs used; each is a separate commit.

  • Root cause is upstream (Magpie/TraceLens/GEAK/IntelliKit/AgentKernelArena), ticket filed: no

@zili-amd zili-amd added run-forge-e2e Run the single-GPU Forge e2e smoke on this PR (again on every new commit while set) baseline-raise Waives new, worse and baseline-growth code-metrics findings; reason required in the PR description labels Oct 19, 2026
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Code metrics gate: PASSED with waived findings (baseline-raise)

Judged: the 31 files this change touches. Files it does not touch never fail it; anything found there is listed under Outside this change for information.

Dimension Limit New Worse Baseline out of date Baseline entries vs base
Cyclomatic complexity fails > 20 0 0 0 6 +0
Cognitive complexity fails > 30 0 0 0 54 +0
Function length (lines) fails > 50 0 7 0 544 +0
Nested block depth fails > 4 0 0 0 44 +0
Parameter count fails > 5 0 0 0 80 +0
Module length (lines) warns > 800, fails > 1200 1 3 0 44 +0
Duplicated lines per file fails > 0 0 0 0 95 +0
Dead code (vulture) fails > 0 0 0 0 2 +0
Directory size (entries) fails > 50 0 0 0 2 +0
Check Findings Effect
Comments 0
Broad catch without a reason 0
English only 0
Production code imports test code 0
Repeated literals 0
A1 Private name of another package 0
A8 Flag argument 0
A9 Tuple return 0
A10 Raw vocabulary comparison 0
A11 Hidden global write 0
A12 Pure forwarder 0
A12 Single-implementation abstraction 0

New violations (not in the baseline) (waived by the baseline-raise label): 1

Unit Dimension Value Limit
src/kernelforge/cli/forge_loop_command.py:1 Module length (lines) 2026 fails > 1200

Worse than the baseline (waived by the baseline-raise label): 10

Unit Dimension Baseline Now
src/kernelforge/cli/forge_loop_command.py:1512 forge_loop Function length (lines) 421 423
src/kernelforge/config.py:300 Config.from_env Function length (lines) 67 69
src/kernelforge/orchestrator/agent.py:397 make_agent_fn Function length (lines) 263 266
src/kernelforge/orchestrator/analysis.py:1311 AnalysisAgentService._run_analysis_session Function length (lines) 81 83
src/kernelforge/orchestrator/orchestration.py:1769 make_orchestration_service Function length (lines) 63 66
src/kernelforge/orchestrator/plan_critic.py:408 PlanCriticAgent.review Function length (lines) 66 68
src/kernelforge/orchestrator/specialists.py:475 SpecialistAgent.run Function length (lines) 95 98
src/kernelforge/loop/runner.py:1 Module length (lines) 6051 6554
src/kernelforge/orchestrator/analysis.py:1 Module length (lines) 1952 1957
src/kernelforge/orchestrator/orchestration.py:1 Module length (lines) 1831 1843

How to fix

  • New or worse: split the unit (extract a function, a class, a module) until it is back under the limit, or no worse than its recorded value. The baseline cannot absorb a new violation: CI refuses any baseline entry added or raised relative to the base branch.
  • Baseline out of date: something improved. Run python scripts/code_metrics.py --update-baseline and commit scripts/code_metrics_baseline.txt; the command only lowers or removes entries.
  • Override: the baseline-raise PR label waives new, worse and baseline-growth findings and the failing design checks (they stay listed here). Use it only with the reason in the PR description; it waives nothing else.
  • Tools for a local run: pip install ruff==0.16.2 complexipy==8.0.1 vulture==2.16 and npm install -g jscpd@5.4.1 (or point CODE_METRICS_JSCPD at another jscpd command, such as npx --yes jscpd@<version>).

Module length warning: over 800 lines, within the 1200-line failure limit (not failing): 1

Module Lines Limit
src/kernelforge/orchestrator/agent.py 964 warns > 800

Already over at the base (backlog, not this change's): 3

Unit Dimension Base Now
src/kernelforge/cli/forge_loop_command.py:1512 forge_loop Cognitive complexity 31 31
src/kernelforge/loop/runner.py:5832 IterationLoop._collect_session_candidate Cognitive complexity 44 44
src/kernelforge/loop/runner.py:5441 IterationLoop._rule_on_attempt Cognitive complexity 38 38
24 findings in files this change does not touch

Outside this change (informational): 24

Unit Dimension Baseline Now
src/hyperloom/orchestrator/actions/executors/_integrate_apply.py ApplyStageMixin._stage_apply Cognitive complexity absent 32
src/hyperloom_kb/collect/mapping.py _experience Cognitive complexity absent 31
src/hyperloom/orchestrator/loop/_keep_patch_column.py AgentKeepStagingMixin._stage_agent_keep Parameter count absent 6
src/hyperloom/orchestrator/phases/kernel.py KernelPhase._record_geak_delegation_timeline Parameter count absent 7
src/hyperloom/orchestrator/loop/_baseline_failures.py BaselineFailureMixin.handle_unpromotable_result Function length (lines) absent 55
src/hyperloom/orchestrator/loop/_resume_recovery.py ResumeRecoveryMixin._resume_recover_pending_warm_replay Function length (lines) absent 75
src/hyperloom_kb/http_service.py Module length (lines) absent 1525
src/hyperloom/orchestrator/specialists/dispatch.py SpecialistDispatchCollaborator.warm_specialist_params Cognitive complexity 93 94
src/hyperloom/inference_optimizer/cli/preflight.py _probe_rocm_build Function length (lines) 54 57
src/hyperloom/inference_optimizer/breakdown/schema.py Module length (lines) 2681 2686
src/hyperloom/inference_optimizer/cli/__init__.py Module length (lines) 2569 2739
src/hyperloom/orchestrator/actions/executors/_kv_metrics.py Module length (lines) 1578 1579
src/hyperloom/orchestrator/actions/executors/roofline.py Module length (lines) 1303 1305
src/hyperloom/orchestrator/kernel/conc_sweep.py Module length (lines) 1429 1449
src/hyperloom/orchestrator/phases/kernel.py Module length (lines) 4102 4199
src/hyperloom/orchestrator/phases/prelude.py Module length (lines) 2504 2506
src/hyperloom/orchestrator/prompts/prompt_builder.py Module length (lines) 1205 1212
src/hyperloom/orchestrator/prompts/specialist_prompt_builder.py Module length (lines) 2556 2567
src/kernelforge/fusion/command.py Module length (lines) 1584 1708
src/kernelforge/fusion/validate.py Module length (lines) 1513 1514
src/kernelforge/gemm_tune/tuners/_aiter_dense_common.py Module length (lines) 1399 1463
src/kernelforge/loop/insession_gate.py Module length (lines) 1218 1260
src/kernelforge/loop/knowledge_integration.py Module length (lines) 1457 1459
src/kernelforge/loop/task_preparer.py Module length (lines) 1299 1557

Verdict computed by the gate scripts in /home/runner/work/_temp/code-metrics-gate, not by this tree's copy.

Thresholds and sources: [tool.hyperloom.code_metrics] in pyproject.toml. Tools: complexipy 8.0.1, jscpd 5.4.1, ruff 0.16.2, vulture 2.16.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Hyperloom Forge E2E — ✅ Succeeded

item value
result ✅ Succeeded
example triton-softmax-forge-loop (max_hours=1.0, max_iters=100)
resources 1× GPU
PR branch feat/kernelforge-knowledge-tools
commit 5e83f0d925028e88380cd5b56e676074b23b01a9
session_id kernelforge:oneoff-05e4d3cbce594b19
queue → dispatch 1s
run time 42m 0s
total 42m 2s

Actions run

@zili-amd

Copy link
Copy Markdown
Collaborator Author

The two Forge E2E checks (ci-e2e/kernelforge, e2e) failed three times on this PR, each time at the platform's first wait step with session "claw-...-sandbox-..." not found (may have expired or been deleted), before the workload ran. feat/kernelforge-model-relay failed the same way in the same window, so this is the E2E platform rather than this change. I will rerun them once the platform recovers. Every other check passes at 727921f, including the required code-metrics and pr-hygiene.

@zili-amd

Copy link
Copy Markdown
Collaborator Author

/review

@github-actions

Copy link
Copy Markdown

Automated review started for head 5821319. Progress: https://github.com/AMD-AGI/Hyperloom/actions/runs/39652856224

@zili-amd
zili-amd requested review from chaojhou and xiaofei-zheng and removed request for chaojhou and xiaofei-zheng October 21, 2026 18:34

@github-actions github-actions 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.

PR #2034 -- feat(kernelforge): offer a configured knowledge base to every forge role and record why each step was taken

What it does: Gives every tool-bearing forge role (Implementer, Analysis, dispatch/synthesis planners, specialists, plan critic, supervisor) read-only access to a configured KernelWiki knowledge base as four stdio MCP tools -- off unless FORGE_KNOWLEDGE_WIKI_ROOT is set, refused on unusable settings or a backend without MCP, every call logged with its role and a mandatory one-line reason -- and records each iteration's decision rationale on the iteration_result event, with a fan-out lane keeping its own rationale through the queue; a declared target may now escape the test-name protection patterns, and --target-mean-case-speedup stops a campaign once the incumbent's mean per-case speedup over the baseline reaches the operator's target (termination reason speedup_target_met).

Blocking issues: 1

  1. [X3] The documented refusal band for --target-mean-case-speedup is not enforced below zero [verified]
    Problem: Four places this PR adds state "a value at or below 1.0 is refused" -- the cli.md row (docs/kernelforge/reference/cli.md:118), the option's --help text (src/kernelforge/cli/forge_loop_command.py:1369), the validator's own docstring (src/kernelforge/cli/options.py:124-126) and PR body item 4 -- but _validate_speedup_target returns 0.0 without raising for every value <= 0 (src/kernelforge/cli/options.py:129-130), silently disabling the gate.
    Impact: An operator passing a negative value (e.g. --target-mean-case-speedup -3.15, one keystroke away from 3.15) gets a campaign with no speedup stop and no refusal message -- exactly the "campaign quietly ignoring the limit its operator thought they had set" failure that the roofline sibling's refusal exists to prevent (options.py:100-104).
    Action: Raise for values below zero (keeping 0.0 as the documented off default), or narrow the four doc claims to the band the code enforces.

SKIPPED: V4 -- "# PARTIAL: #1276 changes 1311 files, only its first 100 were compared": overlap with #1276's remaining files unchecked.
SKIPPED: V4 -- "# PARTIAL: #1032 changes 1075 files, only its first 100 were compared": overlap with #1032's remaining files unchecked.
SKIPPED: V4 -- "# PARTIAL: #1869 changes 540 files, only its first 100 were compared": overlap with #1869's remaining files unchecked.
SKIPPED: V4 -- "# PARTIAL: #1154 changes 1232 files, only its first 100 were compared": overlap with #1154's remaining files unchecked.

Checked: knowledge/wiki_tool/{init,contract,mcp_server,tool}.py against tools/mcp_stdio.py (serve, dispatch_envelope, InvalidParamsError) and agent_backends/base.py (StdioMcpServer fields, AgentCapabilities.mcp; claude.py:379, codex.py:331); the six role attach sites (agent.py:540, analysis.py:576, orchestration.py:1162/1794, plan_critic.py:423, specialists.py:510, supervisor.py:254) and supervisor.py's except path; runner.py _source_attempt branches, _stop_condition_met, _iteration_result_event, _persist/_restore_lane_queue, and the agent_rationale threading (pre-existing at base runner.py:694/5937/6453); workspace_policy.py PROTECTED_GLOBS vs TARGET_RECLAIMABLE_GLOBS and the pre-existing workspace_guard._target_exempt (base:125); iteration_result readers (run_state.py:705/727, prompt_view.py:135, reporting.py:537) and termination_reason readers tree-wide; .env.template scope; docs rows against contract.py SCRIPTS; the body's 41-new-tests and 452-package-lines claims against counted diff lines and the unit table against wc -l; tree-wide greps for every new identifier's writers and readers | Ran: _validate_speedup_target(-2.0/0.0/0.5) and _validate_roofline_target(-2.0) at head; pytest at head: test_workspace_policy.py (7), test_forge_loop_resume.py -k speedup (3), test_loop_runner.py -k "rationale or speedup_target or declared_target_named" (6), test_knowledge_wiki_tool.py (46, the stdio-session test run separately with an absolute PYTHONPATH for the child env), test_fanout.py -k rationale (1), test_lane_plan_persistence.py -k rationale (2), test_insession_gate_protection.py -k colocated (1), tests/loop/test_knowledge_integration_helpers.py -k named_like (1) -- all passed | Base: bb2da63 | Head: 5821319

@zili-amd

Copy link
Copy Markdown
Collaborator Author

/review

@github-actions

Copy link
Copy Markdown

Automated review started for head 7e2fdcd. Progress: https://github.com/AMD-AGI/Hyperloom/actions/runs/39660361084

@github-actions github-actions 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.

PR #2034 -- feat(kernelforge): offer a configured knowledge base to every forge role and record why each step was taken

What it does: The forge planning roles run without a shell, so a kernel knowledge base mounted as a checkout never reached the agents that choose the optimization direction, and a run's trajectory artifacts recorded no reason for any step. This PR serves that checkout to every tool-bearing forge role (Implementer, Analysis, dispatch/synthesis planners, specialists, plan critic, supervisor) as four read-only stdio MCP tools (mcp__knowledge_wiki__{search,page,grep,guide}), each call logged with its role and a mandatory one-line reason; it is off unless FORGE_KNOWLEDGE_WIKI_ROOT is set, and unusable settings and backends without MCP support are refused rather than run with the tools silently missing. It also records each iteration's decision rationale on the iteration_result event (a fan-out lane keeps its own rationale through the lane queue), lets a declared target escape the test-name protection patterns so a ROCmBench kernel colocated with its tests is editable again, and adds --target-mean-case-speedup, which ends a campaign once the incumbent's mean per-case speedup over the baseline reaches the operator's target (termination reason speedup_target_met).

Blocking issues: 1

  1. [X3] The comment on the new iteration_result.rationale field states absence conditions no production path produces [verified]
    Problem: The added comment says the field is "absent when no agent ran (baseline) or it returned nothing" (src/kernelforge/loop/runner.py:3834), but every producer of result.agent_rationale yields a non-empty string: the no-agent branch assigns rationale = "no-agent (baseline measurement)" (src/kernelforge/loop/runner.py:5801), which reaches result.agent_rationale = rationale (src/kernelforge/loop/runner.py:6487) and the event emitted at src/kernelforge/loop/runner.py:3769, and the implementer channel returns (gate tag + (full or "no rationale provided"))[:200] (src/kernelforge/orchestrator/agent.py:903-923), so an agent whose final message was empty also emits a present field carrying that placeholder; the device-held, queued-lane and staged-merge branches likewise assign their own non-empty rationale. The only path that leaves the field absent is a pending keep persisted before the field existed, restored as str(pending.get("rationale") or "") (src/kernelforge/loop/runner.py:1793).
    Impact: The comment is the only statement of the new persisted field's presence semantics, and it is inverted on both of its clauses -- a reader of the events JSONL (the trajectory comparison this PR exists to enable, and the #2036 wiki-guided planning mode stacked on it) expects a missing rationale key on baseline and empty-message rows and finds placeholder strings instead, so any consumer branching on absence misreads those rows.
    Action: Correct the comment to the real condition (present, with a placeholder, on every production path; absent only for a pre-rationale pending keep or an injected agent_fn returning nothing), or make the no-agent and empty-final-message paths leave agent_rationale unset if absence is the intended contract.

SKIPPED: V4 -- "# PARTIAL: #1276 changes 1311 files, only its first 100 were compared": overlap with #1276's remaining files unchecked.
SKIPPED: V4 -- "# PARTIAL: #1032 changes 1075 files, only its first 100 were compared": overlap with #1032's remaining files unchecked.
SKIPPED: V4 -- "# PARTIAL: #1869 changes 540 files, only its first 100 were compared": overlap with #1869's remaining files unchecked.
SKIPPED: V4 -- "# PARTIAL: #1154 changes 1232 files, only its first 100 were compared": overlap with #1154's remaining files unchecked.

Checked: knowledge/wiki_tool/{init,contract,mcp_server,tool}.py (handle_tool_call/_run/_log/script_arguments, entry argv/env, tool names) against tools/mcp_stdio.py (serve, dispatch_envelope, InvalidParamsError) and agent_backends/base.py (StdioMcpServer fields, AgentCapabilities.mcp), claude.py:_apply_mcp_servers and codex.py:_mcp_server_overrides; the six role attach sites and require_mcp_backend refusals (agent.py:540, analysis.py:576, orchestration.py:1162/1794, plan_critic.py:387, specialists.py:461, supervisor.py:254 and its except path); cli/options.py:_validate_speedup_target beside _validate_roofline_target; runner.py IterationConfig, _stop_condition_met, _is_speedup_target_met, _iteration_result_event and the agent_rationale producers (the _source_attempt branch chain 5675-5801, result assignment sites 5971/6487, pending-keep restore 1793), _persist/_restore_lane_queue, _lane_rejection, _target_source_files, and the base's agent_rationale threading (base runner.py:694/5937/6453/6524) and placeholder return (base agent.py:895-915); the gate tag at agent.py:315-324; fanout.py:_one's error branch; llm/workspace_policy.py TARGET_RECLAIMABLE_GLOBS vs PROTECTED_GLOBS/PROTECTED_DIRS and every editable_paths consumer, beside the pre-existing workspace_guard._target_exempt (base:125) and runner.py:1266 commit gate; iteration_result readers (run_state.py:705/727, prompt_view.py:135, reporting.py:537); .env.template scope; docs rows against contract.py SCRIPTS; the body's size table against numstat and the code-metrics report at head; tree-wide greps for every new identifier's writers and readers | Ran: at head 7e2fdcd -- pytest test_forge_loop_resume.py -k speedup (7 passed), test_knowledge_wiki_tool.py (46 passed, including a real stdio session), test_loop_runner.py -k "rationale or speedup_target or declared_target" (6 passed), test_fanout.py + test_lane_plan_persistence.py -k rationale (3 passed), test_workspace_policy.py + test_insession_gate_protection.py -k "declared_target or colocated or escapes or inventory" (6 passed), a six-module selection -k named_like (2 passed, both this PR's new tests); ruff --select C901 (max-complexity 20) and --select PLR0913 (max-args 5) over the changed non-test files -- both pass | Base: bb2da63 | Head: 7e2fdcd

zili-amd and others added 16 commits October 23, 2026 11:09
--target-mean-case-speedup returned 0.0, the off value, for every
target at or below zero, so a mistyped -3.15 ran the campaign with no
speedup stop and no message, although the help, the reference and the
validator all said a value at or below 1.0 is refused. Zero alone now
turns the target off; any other value at or below 1.0, and one that is
not finite and so can never be reached, is refused.

Co-authored-by: Cursor <cursoragent@cursor.com>
The comment beside the new field said it was absent when no agent ran
or the agent returned nothing, but both paths record a placeholder
("no-agent (baseline measurement)", "no rationale provided"), as do the
held-device, queued-lane and merge paths. The key is omitted only for a
pending KEEP recorded before the field existed; the comment now says so.

Co-authored-by: Cursor <cursoragent@cursor.com>
The escape released the test-name patterns for the whole declared file,
and every caller decides on the whole file. In a ROCmBench layout that
file also holds the correctness tests, so an Implementer could rewrite
the in-file reference and tolerance together with the kernel: the
in-session gate allowed it, the file left the tamper snapshot, the lane
check passed it, and the canonical suite ran the tampered tests, so a
wrong kernel could be kept and reported as a speedup.

Protecting only what lies outside the declared kernel symbols is its own
change; until then a test-named file stays protected, as at the base.

This reverts commit 7e9005c.

Co-authored-by: Cursor <cursoragent@cursor.com>
The prompt asks the Implementer for a rationale specific enough to
compare runs by, but agent_fn prefixed the in-session gate tag (about 94
characters) and returned the first 200, so about 106 characters of the
agent's reasoning reached iteration_result and the lane queue. agent_fn
now returns the agent's own words in full; every reader already caps what
it records (the event at 500 characters, the history line at 80, the
commit subject at 72). The gate's figures stay where they already were:
the session sink and the session-end log line. A KEEP's commit subject
now starts with the rationale rather than the gate tag.

Co-authored-by: Cursor <cursoragent@cursor.com>
The orchestration-failure branch built its IterationResult without one,
so the iteration_result of an ORCHESTRATION_ERROR iteration carried no
rationale key, against the field's stated contract. It now records
"planning failed; no Implementer session: <error>", as the held-device,
merge and no-agent paths record theirs.

Co-authored-by: Cursor <cursoragent@cursor.com>
page relied on the checkout's script for containment, and KernelWiki's
get_page.py reads a lookup containing a slash as a path under its root
with no check, so page_id="/etc/hostname" or "../../../etc/hostname"
returned the file to a planning role that has no shell. The server now
refuses an absolute page path or one that resolves outside the checkout
before the script runs. The call log records only the arguments each tool
declares, so an extra key cannot bloat a line and the reason is logged
once. mcp_stdio's docstring names the third server it carries.

Co-authored-by: Cursor <cursoragent@cursor.com>
agent_fn returned run_result.text as the rationale. Codex reports its
last message there, but Claude reports every text block of the session
joined by newlines, so on Claude, the default backend, every reader's
leading cut recorded the session's opening remarks ("I'll start by
reading the kernel...") and none of the final reasoning; the gate prompt,
which forge-loop always uses, never asked for a rationale at all.

Both Implementer prompts now ask for one `RATIONALE:` line just before
the `PLAN:` line, and the gate's resume feedback asks for both again.
agent_fn parses it as PLAN is parsed (the last such line, one line,
capped at the 500 characters the iteration_result event records) and
records "no rationale provided" when there is none.

Co-authored-by: Cursor <cursoragent@cursor.com>
A resume compares the subject git recorded for a pending KEEP's commit
with the first line of the message it built. git folds a multi-line first
paragraph into one subject and trims trailing whitespace, so a rationale
with a newline in its first 72 characters, or one whose cut ended on a
space, made the resume refuse the campaign with "pending KEEP commit
message mismatch". The gate tag hid this while it led every rationale.
The message is now built as one line with no trailing space, and that
line is the expected subject.

Co-authored-by: Cursor <cursoragent@cursor.com>
main added the DeepSeek Harness backend (#2063), which declares no MCP
support, so a campaign that sets FORGE_KNOWLEDGE_WIKI_ROOT refuses it as
it refuses any backend that would drop the tools. The variable's entry
named only the backends that do support MCP.

Co-authored-by: Cursor <cursoragent@cursor.com>
KnowledgeWikiTool.from_config and require_mcp_backend read typed Config
fields and AgentBackend attributes through getattr defaults, which only
test doubles without those fields needed: with plain attributes, 18
Analysis and orchestration-factory tests failed on a SimpleNamespace
config. Both now read the attributes, and those doubles carry the two
fields, as main already did for the probe fields. require_mcp_backend
also takes a registered AgentProvider, which declares the same name and
capabilities.

The four variables a `python -m kernelforge...` MCP server needs to start
were listed twice, once for the probe server and once for the knowledge
base server; they are now one PYTHON_LAUNCH_ENV_VARS beside
StdioMcpServer, and each server adds only what its own child needs.

Co-authored-by: Cursor <cursoragent@cursor.com>
The supervisor builds its backend on its first pass, so the MCP check ran
there, inside the pass's best-effort handler: with a knowledge base set
and a provider without MCP, every pass was skipped with a log line and
the campaign ran on without a supervisor. The provider registry declares
each provider's capabilities, so make_supervisor_fn now refuses such a
provider when it is built, as every other role is refused at startup.

Co-authored-by: Cursor <cursoragent@cursor.com>
The server re-derived whether its root was a knowledge base checkout and
answered every call with an error when it was not, but only the session
side launches it, always with the root, log and role set, and Config
refuses a root without the scripts before any session starts. The
server now reads the three variables directly, and a missing script
fails the call through the script's own exit status. It also builds a
call's script arguments once instead of validating and then rebuilding
them.

Co-authored-by: Cursor <cursoragent@cursor.com>
agent_fn capped the declared rationale at 500 characters and the
iteration_result event cut it at a separate literal 500, held together
by a comment. The event now uses agent.RATIONALE_MAX_CHARS. The speedup
target check also stops re-coercing a float field the CLI has already
validated, and _lane_rationale names the LanePlan it reads.

Co-authored-by: Cursor <cursoragent@cursor.com>
The supervisor builds its backend on its first pass, and
create_registered_backend returns the fallback provider's backend when
the primary is unavailable. The startup check read only the primary
provider's declared capabilities, so with a knowledge base set, codex as
the primary and deepseek as the fallback, a host without codex ran every
supervisor pass on deepseek with the knowledge base tools silently
dropped. require_mcp_runtime now refuses a runtime when its provider or
its fallback declares no MCP, and make_supervisor_fn uses it, so the
campaign is refused at startup instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
… own

main now fails a function over 50 lines, and this change had grown
Config.__post_init__ and IterationLoop._stop_condition_met to 55 each.
The knowledge base path checks move to Config._normalize_knowledge_wiki_paths,
and the speedup target's stop to _stop_at_speedup_target, which sets the
termination reason itself and replaces the _is_speedup_target_met query.
Behaviour is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
main's function-length count records IterationLoop._source_attempt at
148 and make_agent_fn.agent_fn at 142; this change shortens them to 142
and 140, so their entries are lowered to match.

Co-authored-by: Cursor <cursoragent@cursor.com>
@zili-amd
zili-amd force-pushed the feat/kernelforge-knowledge-tools branch from d183e78 to 6a154d0 Compare October 23, 2026 18:21
@zili-amd

Copy link
Copy Markdown
Collaborator Author

The review on 523cda3 is addressed at head 6a154d0, rebased onto main d0d690b.

Blocking: the supervisor fallback (fixed in c1cd81d). I took a variant of the second option.

  • The supervisor still builds its backend on its first pass. But make_supervisor_fn now refuses the runtime at startup when either its provider or its fallback provider declares no MCP. The check is require_mcp_runtime in knowledge/wiki_tool/tool.py.
  • create_registered_backend only ever returns the primary's backend or the single fallback's, so those two cover every backend a pass can run on.
  • Why not the other two options:
    • building the backend eagerly would change when an unavailable supervisor provider stops the campaign;
    • clearing fallback_provider while a knowledge base is set would change fallback behaviour through a setting that has nothing to do with fallback.
  • test_a_supervisor_that_could_run_without_mcp_is_refused_when_it_is_built covers two cases: deepseek as the primary, and the case from the review, codex as the primary with deepseek as the fallback. The second case fails when only the primary is checked.
  • With the default fallback (claude), nothing changes.

Also in this push, after the rebase onto main's stricter function-length rule (#2127):

  • This change had grown Config.__post_init__ and _stop_condition_met to 55 lines. The knowledge base path checks and the speedup-target stop are now units of their own (89d60de).
  • Two baseline entries for functions this change shortens are lowered (6a154d0).
  • The description's table now uses the new count.

At 6a154d0, these all pass:

  • the full kernelforge suite (6101 passed, 19 skipped);
  • ruff check and ruff format --check;
  • lint-imports;
  • the code-metrics gate (passes with baseline-raise);
  • pr_hygiene template;
  • check_cli_references.

CI and e2e will run on this push.

@zili-amd

Copy link
Copy Markdown
Collaborator Author

/review

@github-actions

Copy link
Copy Markdown

Automated review started for head 6a154d0. Progress: https://github.com/AMD-AGI/Hyperloom/actions/runs/40002707111

KnowledgeWikiServer read the three launch variables from os.environ
unless handed a dict, and only the tests ever handed one. It now takes
root, log_path and role, and _serve, the module's entry point, reads
them from the environment the session side launches it with.

Co-authored-by: Cursor <cursoragent@cursor.com>
@zili-amd

Copy link
Copy Markdown
Collaborator Author

On the heads-up of 10-23 12:04, every point holds at head 5e83f0d. The branch sits on main d0d690b and is 0 commits behind.

  • Supervisor fallback. Fixed in c1cd81d. require_mcp_runtime refuses the supervisor at startup when either its provider or its fallback provider declares no MCP.

  • Baseline. I took main's scripts/code_metrics_baseline.txt and regenerated it with --update-baseline on the rebased tree. This PR now only lowers three entries: make_agent_fn cognitive 72 → 70, _source_attempt 148 → 142, and agent_fn 142 → 140.

  • The two new 50-line violations. Both are split out, in 89d60de: Config._normalize_knowledge_wiki_paths and _stop_at_speedup_target. The script no longer reports either function.

  • Functions already over the limit. The size table lists every function the script reports as worse than its baseline:

    • forge_loop;
    • make_agent_fn;
    • SpecialistAgent.run;
    • _run_analysis_session;
    • Config.from_env;
    • PlanCriticAgent.review;
    • make_orchestration_service.

    Under the script's count, supervisor_fn, make_supervisor_fn and run_lanes are not over the limit and have no baseline entry, so they have no row.

  • CI. The test shards and coverage fail at this head on a collection error in test_multi_node_kept_patch_restore.py. Main d0d690b fails the same way, and fix(tests): import IntegrateRecoveryIncomplete from its new module #2149 fixes it. This PR does not touch src/hyperloom. Once fix(tests): import IntegrateRecoveryIncomplete from its new module #2149 is in, I will rebase onto it.

@github-actions

Copy link
Copy Markdown

Automated review: no verdict applied -- the PR head moved from 6a154d0 to 5e83f0d during the review. Comment /review to retry. Run: https://github.com/AMD-AGI/Hyperloom/actions/runs/40002707111

PR #2034 -- feat(kernelforge): offer a configured knowledge base to every forge role and record why each step was taken

What it does: The forge planning roles run their sessions without a shell, so a kernel knowledge base laid out as a checkout never reached the agents that choose the optimization direction, and a run left no record of why any step was taken. This PR serves that checkout to every tool-bearing forge role (Implementer, Analysis, the dispatch and synthesis planners, the specialists, the plan critic, the supervisor) as four read-only stdio MCP tools (mcp__knowledge_wiki__{search,page,grep,guide}) — off unless FORGE_KNOWLEDGE_WIKI_ROOT is set, refused on a relative root or log path, a root missing the checkout's scripts, or a backend without MCP support, every call appended to a JSONL log naming the calling role and a mandatory one-line reason, a page path outside the checkout refused before the script runs. It records each iteration's decision rationale on the iteration_result event — the RATIONALE: line the Implementer declares (not the session text, no gate tag, no 200-character cut), a fan-out lane keeping its own through the lane queue, a planning failure recording why no Implementer ran — and adds --target-mean-case-speedup, which ends the campaign once the incumbent's mean per-case speedup over the baseline (the aggregate a KEEP is scored by) reaches the operator's target, with termination reason speedup_target_met, refusing any non-zero value at or below 1.0 or one that is not finite, and never entering the KEEP decision.

Blocking issues: none

SKIPPED: V4 -- "# PARTIAL: #1869 changes 695 files, only its first 100 were compared": overlap with #1869's remaining files unchecked.

Checked: knowledge/wiki_tool/{init,contract,mcp_server,tool}.py (handle_tool_call/_run/_log/script_arguments/_require_inside_root/_ARGUMENT_KEYS, TOOL_DEFINITIONS, serve entry) against tools/mcp_stdio.py (serve, dispatch_envelope, InvalidParamsError -> -32602) and agent_backends/base.py (PYTHON_LAUNCH_ENV_VARS, StdioMcpServer, AgentCapabilities.mcp; claude.py _apply_mcp_servers:558, codex.py _mcp_server_overrides:506, registry.py _prepare_backend setattr:303 and create_registered_backend primary-or-single-fallback:239-289); the six role attach sites and their refusals (agent.py:540-541/827, analysis.py:587/1371-1374, orchestration.py:314/1166-1187/1797-1815, plan_critic.py:396/436-439, specialists.py:467/510-517, supervisor.py:235-237/290-293); cli/options.py:_validate_speedup_target (122-149) beside _validate_roofline_target; forge_loop_command.py (the option, target_mean_case_speedup into IterationConfig:1740, _announce_knowledge_base:152/1945); config.py (_normalize_knowledge_wiki_paths:239-255, from_env:374-375); runner.py (IterationConfig:544, _stop_at_speedup_target:3398-3410 inside _stop_condition_met:5272 at the top of _run_locked:6422 after best is hydrated at 3536; _iteration_result_event:3859 and every emitter traced -- _settle_locked_attempt's result.agent_rationale=rationale at 6394 covering the hazard/merge/no-agent branches, _collect_session_candidate's at 5987, _planning_failure:744, _pending_keep_result:1810; the caps: event RATIONALE_MAX_CHARS, console 5790, history 6533, subject 1061; _inspect_pending_keep's subject comparison:1163; _persist/_restore_lane_queue:4568/4593); fanout.py (LaneResult.rationale:343, session type:276, _one's error path); the agent_fn return's every caller (runner.py:5788/1979, cli/lanes.py:181, port_loop.py:222); iteration_result and termination_reason readers tree-wide (run_state.py:229/365/369 make_event drops None, prompt_view.py:135, reporting.py:519, langfuse_emitter.py:266, forge_fusion.py:202 — no closed-set reader); base-tree comparisons for every pre-existing shape (pending-keep keys, agent_fn's gate-tag return at base agent.py:895-915, the lane-queue and signature-introspection excepts); docs rows against the executed band; tree-wide occurrence counts for all 23 new identifiers; all six prior blocking findings re-verified fixed from the code at this head | Ran: at head 6a154d0 -- pytest test_knowledge_wiki_tool.py (49 passed, real stdio MCP session included), test_forge_loop_resume.py -k speedup (7 passed), test_loop_runner.py -k "rationale or speedup_target or lane" (21 passed), test_claude_resume.py rationale test + test_codex_backend.py -k rationale (3 passed), test_fanout.py + test_lane_plan_persistence.py -k rationale (3 passed), test_campaign_cross_process.py resume KEEP variants (3 passed), test_specialist_probe.py + test_forge_orchestration.py + test_analysis_agent.py (182 passed); ruff --select C901 (max-complexity 20) and --select PLR0913 (max-args 5) over the changed non-test files -- both pass | Base: d0d690b | Head: 6a154d0

@zili-amd

Copy link
Copy Markdown
Collaborator Author

/review

@github-actions

Copy link
Copy Markdown

Automated review started for head 5e83f0d. Progress: https://github.com/AMD-AGI/Hyperloom/actions/runs/40010067668

@github-actions

Copy link
Copy Markdown

Automated review: no verdict applied -- a clean card cannot defer a surviving finding. Comment /review to retry. Run: https://github.com/AMD-AGI/Hyperloom/actions/runs/40010067668

PR #2034 -- feat(kernelforge): offer a configured knowledge base to every forge role and record why each step was taken

What it does: The forge planning roles run their sessions without a shell, so a kernel knowledge base laid out as a checkout never reached the agents that choose the optimization direction, and a run left no record of why any step was taken -- agent_fn returned the in-session gate tag plus the first 200 characters of the whole session text, which on Claude is every text block of the session, opening remarks first. This PR serves the checkout to every tool-bearing role (Implementer, Analysis, the dispatch and synthesis planners, the specialists, the plan critic, the supervisor) as four read-only stdio MCP tools (mcp__knowledge_wiki__{search,page,grep,guide}) from a new kernelforge/knowledge/wiki_tool/ package that runs the checkout's own scripts: off unless FORGE_KNOWLEDGE_WIKI_ROOT is set, refused at startup on a relative root or log path, a root missing scripts/{query,get_page,grep_wiki}.py, or a backend or provider without MCP (deepseek refused while it is set, the supervisor's provider and fallback provider checked because its backend is built on its first pass), every call appended to a JSONL log naming the role and a mandatory one-line reason, and a page path that is absolute or resolves outside the checkout refused before the script runs. It records each iteration's decision rationale on the iteration_result event -- the RATIONALE: line the Implementer declares, parsed as PLAN: is, with a placeholder when none is declared, a fan-out lane keeping its own rationale through the lane queue so the iteration that measures it names it, and a planning failure recording planning failed; no Implementer session: <error> -- with a KEEP's commit subject built from one line with no trailing space so a resume still matches the subject git recorded, and the gate's figures staying in the session sink and on the [session-end] line. It also adds --target-mean-case-speedup X, which ends the campaign with termination reason speedup_target_met once the incumbent's mean per-case speedup over the baseline (the aggregate a KEEP is scored by) reaches the operator's target, however much budget remains -- a --resume whose recorded best already meets it ends without another round -- refusing any non-zero value at or below 1.0 (a negative one included) or one that is not finite, and never entering the KEEP decision.

Blocking issues: none

deferred: V4 -- advisory by rules.md's severity contract: #2037 (Experience KB, another author) rewrites the same units from the same base values (make_agent_fn.agent_fn 142->132 there vs 142->140 here; _source_attempt 148->146 vs 148->142; both reshape the run_lanes session callable), so the second to land rebases on a moved floor; not a blocking finding, so no numbered entry

SKIPPED: V4 -- "# PARTIAL: #1869 changes 695 files, only its first 100 were compared": overlap with #1869's remaining files unchecked.

Checked: knowledge/wiki_tool/{init,contract,mcp_server,tool}.py (handle_tool_call/_run/_log/script_arguments/_require_inside_root/_ARGUMENT_KEYS, TOOL_DEFINITIONS, serve entry; AST-verified that the session side never imports the python -m module) against tools/mcp_stdio.py (serve's sequential per-request loop, dispatch_envelope, InvalidParamsError) and agent_backends (base.py PYTHON_LAUNCH_ENV_VARS/StdioMcpServer/AgentCapabilities.mcp; claude.py _apply_mcp_servers, codex.py _mcp_server_overrides, registry.py create_registered_backend primary-or-single-fallback, deepseek/claude/codex capability declarations); the six role attach sites and their refusals (agent.py:540/898, analysis.py:584/1353, orchestration.py:311/1166/1797-1815, plan_critic.py:387/423 and the tool-less repair at 523, specialists.py:461/510, supervisor.py:235/276); cli/options.py:_validate_speedup_target beside _validate_roofline_target (the band executed over 0.0, -0.0, 0.5, 1.0, 3.15, -3.15, inf, nan: only 0 off, 3.15 accepted, the rest refused with the documented messages); forge_loop_command.py (the option, IterationConfig wiring, _announce_knowledge_base); config.py (_normalize_knowledge_wiki_paths, from_env); runner.py (IterationConfig, _stop_at_speedup_target inside _stop_condition_met before _price_round and after best hydration, _iteration_result_event and every IterationResult producer traced -- hazard/queued-lane/merge/agent/planning-failure/no-agent/crash/recovered-pending paths, the caps, _lane_rationale, _persist/_restore_lane_queue, _build_pending_keep's one-line subject, _inspect_pending_keep's git %s comparison); fanout.py (_one's rationale and error paths); agent.py (both prompts' _RATIONALE_REQUEST, the gate feedback, _parse_output_tag, gate_outcome_tag deleted with zero tree-wide survivors); iteration_result and termination_reason readers tree-wide (no closed-set reader); occurrence counts for all 25 new identifiers tree-wide (no writer without a reader, no removed caller with a surviving helper); the docs rows (docs/kernelforge/reference/cli.md:118, docs/reference/environment-variables.md:316-317) against contract.py SCRIPTS, the executed validator band and the backend declarations; .env.template (no FORGE* scope to keep); the body's 39-new-tests, 745/464/314-line and size-table claims counted against diff.txt, numstat and wc -l (runner.py 6510 -> 6554); ruff C901 max-complexity 20 and PLR0913 max-args 5 run over the 17 changed non-test src files (both pass); the six prior review rounds' blocking findings re-verified fixed at this head from the code | Ran: at head 5e83f0d925028e88380cd5b56e67607423b01a9 -- pytest test_knowledge_wiki_tool.py (49 passed, real stdio MCP session included), test_forge_loop_resume.py -k speedup (7 passed), test_loop_runner.py -k "rationale or speedup_target" (5 passed), test_claude_resume.py -k rationale (1 passed), test_codex_backend.py -k rationale (2 passed), test_fanout.py + test_lane_plan_persistence.py -k rationale (3 passed), test_specialist_probe.py + test_forge_orchestration.py + test_analysis_agent.py (182 passed); against the base install -- the claude rationale test fails with the session's opening text, the two hard resume KEEP variants fail with pending KEEP commit message mismatch, the codex gate-on cases fail with the gate tag and the 200-char cut, the speedup cases fail with No such option '--target-mean-case-speedup', and the CI collection error in test_multi_node_kept_patch_restore.py reproduces identically on the base tree (src/hyperloom byte-identical between base and head) | Base: d0d690b | Head: 5e83f0d

@zili-amd

Copy link
Copy Markdown
Collaborator Author

/review

@github-actions

Copy link
Copy Markdown

Automated review started for head 5e83f0d. Progress: https://github.com/AMD-AGI/Hyperloom/actions/runs/40020248470

@github-actions

Copy link
Copy Markdown

PR #2034 -- feat(kernelforge): offer a configured knowledge base to every forge role and record why each step was taken

What it does: The forge planning roles run their sessions without a shell, so a kernel knowledge base laid out as a checkout never reached the roles that choose the optimization direction, and a run left no record of why each step was taken -- agent_fn recorded the in-session gate tag plus the first 200 characters of the whole session text, which on Claude is every text block of the session, opening remarks first. This PR serves that checkout to every tool-bearing role (Implementer, Analysis, the dispatch and synthesis planners, the specialists, the plan critic, the supervisor) as four read-only stdio MCP tools (mcp__knowledge_wiki__{search,page,grep,guide}) from a new kernelforge/knowledge/wiki_tool/ package that runs the checkout's own scripts: off unless FORGE_KNOWLEDGE_WIKI_ROOT is set, refused at startup on a relative root or log path, a root missing scripts/{query,get_page,grep_wiki}.py, or a backend or provider without MCP support (deepseek refused while it is set; the supervisor's provider and fallback provider checked because its backend is built on its first pass), every call appended to a JSONL log naming the role and a mandatory one-line reason, and a page path that is absolute or resolves outside the checkout refused before the script runs. It records each iteration's decision rationale on the iteration_result event -- the RATIONALE: line the Implementer declares, parsed as PLAN: is, with a placeholder when none is declared, a fan-out lane keeping its own rationale through the lane queue so the iteration that measures it names it, and a planning failure recording planning failed; no Implementer session: <error> -- with a KEEP's commit subject built from one whitespace-collapsed line with no trailing space so a resume still matches the subject git recorded, and the gate's figures staying in the session sink and on the [session-end] line. It also adds --target-mean-case-speedup X, which ends the campaign with termination reason speedup_target_met once the incumbent's mean per-case speedup over the baseline (the aggregate a KEEP is scored by) reaches the operator's target, however much budget remains -- a --resume whose recorded best already meets it ends without another round -- refusing any non-zero value at or below 1.0 (a negative one included) or one that is not finite, and never entering the KEEP decision.

Blocking issues: none

Checked: knowledge/wiki_tool/{init,contract,mcp_server,tool}.py (handle_tool_call/_run/_log/script_arguments/_require_inside_root/_ARGUMENT_KEYS, TOOL_DEFINITIONS, the serve entry) against tools/mcp_stdio.py (serve, dispatch_envelope, InvalidParamsError) and agent_backends/base.py (PYTHON_LAUNCH_ENV_VARS, StdioMcpServer, AgentCapabilities.mcp), claude.py:374-383/codex.py:327-334/deepseek.py:162-171 capability declarations, registry.py create_registered_backend's primary-or-single-fallback; the six role attach sites and their refusals (agent.py:540/827/898, analysis.py:584/1353-1374, orchestration.py:311/1166-1187/1797-1838, plan_critic.py:387/423-439 and the tool-less repair at :523, specialists.py:461/510-517, supervisor.py:235-237/290-293); cli/options.py:_validate_speedup_target beside _validate_roofline_target; forge_loop_command.py (the option, IterationConfig wiring at :1740, _announce_knowledge_base at :152/1945, Config.from_env at :1657); config.py (_normalize_knowledge_wiki_paths, from_env); runner.py (IterationConfig:544, _stop_at_speedup_target:3398 inside _stop_condition_met:5272 at the top of _run_locked:6422 after best hydration at :3536, _iteration_result_event:3859 and every IterationResult producer traced -- hazard/queued-lane/merge/agent/planning-failure/no-agent paths and the crash path, the caps: event RATIONALE_MAX_CHARS at agent.py:57, console [:200] at runner.py:5793, history [:80] at :6533, subject [:72] at :1061, _lane_rationale:240, _planning_failure:733, _persist/_restore_lane_queue:4568/4593, _build_pending_keep's one-line subject and _inspect_pending_keep's git %s comparison at :1061/:1103/:1162-1164); fanout.py (LaneResult.rationale, the widened session Callable, _one's rationale and error paths); the agent_fn return's every caller tree-wide (runner.py:5788, runner.py:1979, cli/lanes.py:190, port_loop.py:222 which discards it); iteration_result and termination_reason readers tree-wide (make_event drops None at run_state.py:377, no closed-set reader; fusion/command.py:512 checks a failure subset speedup_target_met is not in); base-tree comparisons for every pre-existing shape (the gate-tag return at base agent.py:895-915, agent_rationale threading at base runner.py:696/667/6489, PENDING_KEEP_FIELDS pre-including rationale and commit_subject, the probe= parameter and its one production caller); docs rows against contract.py SCRIPTS and the executed validator band; tree-wide occurrence counts for every new identifier (no writer without a reader, no removed caller with a surviving helper, _gate_outcome_tag zero survivors); per-file comment/code ratios measured from diff.txt; ruff C901 (max-complexity 20) and PLR0913 (max-args 5) over the 17 changed non-test src files -- both pass | Ran: at head 5e83f0d -- pytest src/kernelforge/tests/test_knowledge_wiki_tool.py (49 passed, real stdio MCP session included), test_forge_loop_resume.py -k speedup (7 passed), test_loop_runner.py -k "rationale or speedup_target" (5 passed), test_claude_resume.py (36 passed) and test_codex_backend.py -k rationale (2 passed), test_fanout.py (33 passed) and test_lane_plan_persistence.py (36 passed), test_campaign_cross_process.py (13 passed), test_specialist_probe.py + test_forge_orchestration.py + test_analysis_agent.py (182 passed); _validate_speedup_target executed directly over 0.0/-0.0/0.5/1.0/3.15/-3.15/inf/-inf/nan (only 0 off, 3.15 accepted, the rest refused with the documented messages); the real KnowledgeWikiServer probed over a fake checkout (a page inside the root served, a symlink pointing out refused, /etc/hostname, ../../../etc/hostname and scripts/../../x refused before the script runs, a leading-dash query staying a query word after --, a 100KB undeclared key kept out of the log); against the base tree d0d690b -- the claude rationale test reproduced failing with the session's opening text, the two hard resume-KEEP variants reproduced failing with pending KEEP commit message mismatch, src/hyperloom byte-identical between base and head so the CI shard failures on test_multi_node_kept_patch_restore.py are the base's own (author's note: #2149 fixes it) | Base: d0d690b | Head: 5e83f0d

LGTM

@zili-amd zili-amd closed this Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review baseline-raise Waives new, worse and baseline-growth code-metrics findings; reason required in the PR description run-forge-e2e Run the single-GPU Forge e2e smoke on this PR (again on every new commit while set)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants