Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 50 additions & 10 deletions adapters/vllm_omni/rebase/tool_schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
},
{
"name": "git_show_test_baseline",
"description": "Show the origin/main version of a test file. Use this FIRST when a test fails compare origin/main's test with omni's test before changing any product code.",
"description": "Show the origin/main version of a test file. Use this FIRST when a test fails \u2014 compare origin/main's test with omni's test before changing any product code.",
"input_schema": {
"type": "object",
"properties": {
Expand All @@ -184,7 +184,7 @@
},
{
"name": "reproduce",
"description": "Hermetically reproduce a failing test on a CLEAN GPU. This is the ONLY sanctioned way to run a GPU test it kills leaked stage/API processes, clears dead device locks, asserts free VRAM, and runs with CUDA_LAUNCH_BLOCKING=1 so a device-side assert points at the true kernel. Prefer this over raw run_shell pytest, which corrupts shared GPU state and produces phantom failures. Returns a structured outcome including whether the failure looks environmental (env_dirty) vs a real code bug.",
"description": "Hermetically reproduce a failing test on a CLEAN GPU. This is the ONLY sanctioned way to run a GPU test \u2014 it kills leaked stage/API processes, clears dead device locks, asserts free VRAM, and runs with CUDA_LAUNCH_BLOCKING=1 so a device-side assert points at the true kernel. Prefer this over raw run_shell pytest, which corrupts shared GPU state and produces phantom failures. Returns a structured outcome including whether the failure looks environmental (env_dirty) vs a real code bug.",
"input_schema": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -309,7 +309,7 @@
},
{
"name": "request_plan_review",
"description": "Submit your plan for L4 review BEFORE making any code edits. You MUST call this after writing plan JSON + MD files and before editing any code. Pass the exact paths you just wrote to the reviewer only reads those files, so there is no ambiguity. The L4 reviewer is a fresh, independent session it has no access to your conversation history. It will critique your plan and return a verdict (lgtm/revise/block). The review is advisory you retain final authority but MUST write a decision file per critique before editing code.",
"description": "Submit your plan for L4 review BEFORE making any code edits. You MUST call this after writing plan JSON + MD files and before editing any code. Pass the exact paths you just wrote to \u2014 the reviewer only reads those files, so there is no ambiguity. The L4 reviewer is a fresh, independent session \u2014 it has no access to your conversation history. It will critique your plan and return a verdict (lgtm/revise/block). The review is advisory \u2014 you retain final authority but MUST write a decision file per critique before editing code.",
"input_schema": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -346,7 +346,7 @@
},
{
"name": "search_debug_memory",
"description": "Search past debug memory for similar failures and their proven fixes. Use this FIRST when you encounter an error someone may have already fixed it in a previous run. Returns matching entries with symptom, root cause, and the exact fix applied.",
"description": "Search past debug memory for similar failures and their proven fixes. Use this FIRST when you encounter an error \u2014 someone may have already fixed it in a previous run. Returns matching entries with symptom, root cause, and the exact fix applied.",
"input_schema": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -386,15 +386,15 @@
},
"symptom": {
"type": "string",
"description": "What went wrong error message, traceback summary"
"description": "What went wrong \u2014 error message, traceback summary"
},
"root_cause": {
"type": "string",
"description": "Why it went wrong upstream change, missing API, etc."
"description": "Why it went wrong \u2014 upstream change, missing API, etc."
},
"fix": {
"type": "string",
"description": "How you fixed it what code was changed and why"
"description": "How you fixed it \u2014 what code was changed and why"
},
"tags": {
"type": "string",
Expand All @@ -406,7 +406,7 @@
},
"watch_outs": {
"type": "string",
"description": "Things to watch for similar issues in other places"
"description": "Things to watch for \u2014 similar issues in other places"
}
},
"required": [
Expand All @@ -419,7 +419,7 @@
},
{
"name": "skill_manage",
"description": "Create or update a reusable SKILL a distilled runbook for a recurring rebase/debug pattern. Use this AFTER you confirm a fix that is likely to recur (e.g. an upstream API drift you had to port). Skills are injected into future runs' prompts for the matching module. Prefer editing an existing skill (action=update) over creating near-duplicates.",
"description": "Create or update a reusable SKILL \u2014 a distilled runbook for a recurring rebase/debug pattern. Use this AFTER you confirm a fix that is likely to recur (e.g. an upstream API drift you had to port). Skills are injected into future runs' prompts for the matching module. Prefer editing an existing skill (action=update) over creating near-duplicates.",
"input_schema": {
"type": "object",
"properties": {
Expand All @@ -441,7 +441,7 @@
},
"trigger": {
"type": "string",
"description": "When this skill applies the symptom/condition to match."
"description": "When this skill applies \u2014 the symptom/condition to match."
},
"modules": {
"type": "string",
Expand Down Expand Up @@ -487,5 +487,45 @@
},
"required": []
}
},
{
"name": "doc_search",
"description": "Search the repo's CURATED KNOWLEDGE BASE \u2014 human-written pages on components, models, CI, git workflow and debugging lore. Use this BEFORE exploring with shell: a page often states the design intent or the owning component outright, which grep cannot tell you. Complements search_debug_memory (past failures and their fixes) \u2014 this is documented design, not incident history. Returns matching pages with paths to open via doc_read.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Free text: component, model, symbol, subsystem or concept."
},
"limit": {
"type": "integer",
"description": "Max matches (default 20, max 40)."
}
},
"required": [
"query"
]
}
},
{
"name": "doc_read",
"description": "Read one knowledge page returned by doc_search. Pass the path exactly as doc_search reported it. Use offset to page through a long document.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Document path from a doc_search match."
},
"offset": {
"type": "integer",
"description": "Character offset for long pages."
}
},
"required": [
"path"
]
}
}
]
4 changes: 2 additions & 2 deletions doc/architecture/SPEC/engine/steps/rebase_v3.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# engine/steps/rebase_v3.py —— 规范

<!-- verified-against: 2026-09-06 -->
<!-- verified-against: 2026-09-19 -->

`LOC ~2204 · step 库(v3 rebase 装配层) · refactor-status: oversized`

Expand All @@ -26,7 +26,7 @@ step —— 薄的受治理 wrapper,substate-first、类型化失败、发布

## 公开契约(注册的 step 之外)
`_adapter_manifest/_substate/_task_params`(被 `rebase_knowledge` import)、
`manifest_job_to_test_job`(golden 测试)、`_build_backends`(read-compat
`manifest_job_to_test_job`(golden 测试)、`_build_backends`(含 `doc_search`/`doc_read`:复用 review 流程同一套 `KnowledgeDocs` 视图,按 adapter manifest 的 `knowledge.repo_subdir` 限定仓库)(read-compat
测试)、`_make_ci_client`(模块级工厂,测试注入 fake 客户端)。

## 不变量
Expand Down
4 changes: 2 additions & 2 deletions doc/architecture/SPEC/rebase_engine.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# rebase_engine/ —— 规范

<!-- verified-against: 2026-09-06 -->
<!-- verified-against: 2026-09-19 -->

`LOC ~7500(26 个模块) · repo-rebase-v3 的原生 rebase 引擎 · refactor-status: ok`

Expand Down Expand Up @@ -35,7 +35,7 @@
| `push_gate.py` | 推送闸裁决:结构性 vs 断言失败的确定性分类(Rev 8 §2.3) |
| `push_to_ci.py` | commit+push-to-CI 编排:preflight、WAL 卫生、C4 双闸、单一传输 |
| `push_wal.py` | 推送 WAL:先落盘的 intent、精确 OID 三分对账、回滚数据 |
| `rebase_tools.py` | 父级 20 工具作为 `ToolDef`;未接线后端**可见地**失败 |
| `rebase_tools.py` | 父级 20 工具 + `doc_search`/`doc_read`(追加在父级顺序之后)作为 `ToolDef`;未接线后端**可见地**失败 |
| `runctx.py` | `RebaseRuntime` + `CheckoutLock`(flock+卫生盾)+ 按事件循环的注册表 |
| `substate.py` | 可持久、单写者、merge-not-overwrite 的 `state.json`(run_id 戳) |
| `test_loop.py` | 本地测试环:逐测试恢复、baseline 复跑分流回归、类型化 skip |
Expand Down
42 changes: 42 additions & 0 deletions src/infermatrix_copilot/engine/steps/rebase_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,49 @@ def skill_manage(**kw) -> dict:
except Exception as exc: # noqa: BLE001
return {"error": f"skill proposal failed: {exc}"}

def doc_search(**kw) -> dict:
"""Curated knowledge base, repo-scoped like the review flows.

The rebase agents previously saw only debug_memory (past FIXES) and
skills; the human-written pages under the adapter's knowledge slice
— component maps, model notes, CI and git lore — were unreachable,
so a module agent rediscovered by shell what a page already stated.
"""
try:
from ...knowledge_docs import KnowledgeDocs
kn = manifest.get("knowledge") or {}
docs = KnowledgeDocs(ctx.settings.knowledge_dir,
kn.get("repo_subdir"))
query = str(kw.get("query") or kw.get("keyword") or "").strip()
if not query:
return {"error": "doc_search requires a non-empty query"}
limit = max(1, min(int(kw.get("limit") or 20), 40))
hits = docs.search(query, limit=limit)
return {"query": query, "repo": repo, "matches": hits,
"truncated": len(hits) >= limit}
except Exception as exc: # noqa: BLE001
return {"error": f"doc_search failed: {exc}"}

def doc_read(**kw) -> dict:
"""Read one knowledge page by the path doc_search returned."""
try:
from ...knowledge_docs import KnowledgeDocs
kn = manifest.get("knowledge") or {}
docs = KnowledgeDocs(ctx.settings.knowledge_dir,
kn.get("repo_subdir"))
path = str(kw.get("path") or "").strip()
if not path:
return {"error": "doc_read requires a path from doc_search"}
page = docs.read(path, offset=int(kw.get("offset") or 0))
return {"repo": repo, **page}
except FileNotFoundError:
return {"error": f"no such document: {kw.get('path')!r} "
"(use a path returned by doc_search)"}
except Exception as exc: # noqa: BLE001
return {"error": f"doc_read failed: {exc}"}

return RebaseBackends(
doc_search=doc_search, doc_read=doc_read,
search_debug_memory=search_debug_memory,
record_debug_memory=record_debug_memory,
skill_manage=skill_manage, search_skills=search_skills,
Expand Down
26 changes: 25 additions & 1 deletion src/infermatrix_copilot/rebase_engine/prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,30 @@ def _format_module_test_plan(plan: dict,
return "\n".join(lines) + "\n"


_KNOWLEDGE_GUIDANCE = (
"**Knowledge base.** Before exploring an unfamiliar component, model or CI\n"
"behaviour with shell, run `doc_search` with the component/model/symbol name\n"
"and `doc_read` the best match by its reported path. A curated page often\n"
"states design intent or the owning component outright, which grep cannot\n"
"tell you. This is documented design; `search_debug_memory` is incident\n"
"history — consult both. Cite the page path in your plan when it informed a\n"
"decision. If a page and the code disagree, the CODE is authoritative for\n"
"this rebase: say so in your decision file rather than editing to match."
)


def _with_knowledge_guidance(adaptive_guidance: str) -> str:
"""Render the ADAPTIVE_GUIDANCE slot with the knowledge-base instruction.

Carried here rather than in the templates on purpose: the shipped templates
are DATA held byte-identical to the parent agent's copies (enforced by
test_adapter_knowledge.test_templates_are_parent_verbatim), so behaviour
changes belong in builder-supplied tokens.
"""
rest = (adaptive_guidance or "").strip()
return f"{_KNOWLEDGE_GUIDANCE}\n\n{rest}" if rest else _KNOWLEDGE_GUIDANCE


def build_module_prompt(
module: str,
data: ModulePromptData,
Expand Down Expand Up @@ -249,7 +273,7 @@ def build_module_prompt(
"SIGNAL_DIR": signal_dir,
"MAX_DEBUG_RETRIES": str(max_debug_retries),
"PROMPT_SOURCE": "",
"ADAPTIVE_GUIDANCE": adaptive_guidance.strip() or "(No adaptive rules yet.)",
"ADAPTIVE_GUIDANCE": _with_knowledge_guidance(adaptive_guidance),
"KILL_TEST_SCRIPT": f"{script_dir}/lib/kill_test_tree.sh",
"REMOTE_CONTEXT": "### Execution mode: LOCAL",
"DEBUG_MEMORY": "Use the `search_debug_memory` tool to query past fixes. Do NOT read the debug_memory.md file directly.",
Expand Down
4 changes: 4 additions & 0 deletions src/infermatrix_copilot/rebase_engine/rebase_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class RebaseBackends:
"""Injected implementations for the knowledge-plane + plan-review tools.
Each takes the tool's kwargs and returns the parent-shaped dict."""

doc_search: Handler = field(default_factory=lambda: _unwired("doc_search"))
doc_read: Handler = field(default_factory=lambda: _unwired("doc_read"))
search_debug_memory: Handler = field(
default_factory=lambda: _unwired("search_debug_memory"))
record_debug_memory: Handler = field(
Expand Down Expand Up @@ -293,6 +295,8 @@ def _audit_ok(result: str) -> bool:
"git_diff": (handle_git_diff, None),
"git_diff_tests_upstream": (handle_git_diff_tests_upstream, None),
"request_plan_review": (backends.request_plan_review, None),
"doc_search": (backends.doc_search, None),
"doc_read": (backends.doc_read, None),
"search_debug_memory": (backends.search_debug_memory, None),
"record_debug_memory": (backends.record_debug_memory, None),
"skill_manage": (backends.skill_manage, None),
Expand Down
9 changes: 8 additions & 1 deletion test/goldens/module_prompt_model_config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,14 @@ Typical fixes vs `/nonexistent/vllm-checkout`: `StageEngineCoreClient.shutdown`


### Adaptive runbook
(No adaptive rules yet.)
**Knowledge base.** Before exploring an unfamiliar component, model or CI
behaviour with shell, run `doc_search` with the component/model/symbol name
and `doc_read` the best match by its reported path. A curated page often
states design intent or the owning component outright, which grep cannot
tell you. This is documented design; `search_debug_memory` is incident
history — consult both. Cite the page path in your plan when it informed a
decision. If a page and the code disagree, the CODE is authoritative for
this rebase: say so in your decision file rather than editing to match.

Use the `search_debug_memory` tool to query past fixes. Do NOT read the debug_memory.md file directly.

Expand Down
9 changes: 8 additions & 1 deletion test/goldens/module_prompt_worker_runner.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,14 @@ Typical fixes vs `/nonexistent/vllm-checkout`: `StageEngineCoreClient.shutdown`


### Adaptive runbook
(No adaptive rules yet.)
**Knowledge base.** Before exploring an unfamiliar component, model or CI
behaviour with shell, run `doc_search` with the component/model/symbol name
and `doc_read` the best match by its reported path. A curated page often
states design intent or the owning component outright, which grep cannot
tell you. This is documented design; `search_debug_memory` is incident
history — consult both. Cite the page path in your plan when it informed a
decision. If a page and the code disagree, the CODE is authoritative for
this rebase: say so in your decision file rather than editing to match.

Use the `search_debug_memory` tool to query past fixes. Do NOT read the debug_memory.md file directly.

Expand Down
42 changes: 41 additions & 1 deletion test/goldens/request_shape_turn1.json

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions test/test_adapter_knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,18 @@ def prompt_data() -> ModulePromptData:

@pytest.mark.parametrize("module", ["model_config", "worker_runner"])
def test_module_prompt_matches_parent_golden(prompt_data, module):
"""Byte-identical render vs the PARENT builder's captured output (the
golden was generated by running agent/prompts/builder.py itself with the
same fixed inputs). Prompt bytes are prompt-cache load-bearing."""
"""Byte-identical render vs the stored golden. Prompt bytes are
prompt-cache load-bearing.

NOTE (2026-09-18): these goldens no longer record the PARENT builder's
output. The parent agent had no access to the curated knowledge base, and
the owner asked for the rebase pipeline to consult it, so the render now
carries knowledge-base guidance in the ADAPTIVE_GUIDANCE slot and the tool
surface gained doc_search/doc_read. The goldens were deliberately
regenerated from THIS builder at that change. The shipped templates remain
byte-identical to the parent's — see test_templates_are_parent_verbatim,
which is why the guidance rides a builder-supplied token, not a template
edit."""
ours = build_module_prompt(module, prompt_data, **GOLDEN_KWARGS)
golden = (GOLDENS / f"module_prompt_{module}.txt").read_text()
assert ours == golden
Expand Down
4 changes: 4 additions & 0 deletions test/test_engine_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ def test_tool_schemas_load_in_parent_dispatcher_order():
"git_diff", "git_diff_tests_upstream",
"request_plan_review", "search_debug_memory", "record_debug_memory",
"skill_manage", "search_skills",
# appended AFTER the parent's 20 (2026-09-18): the knowledge-base
# tools the parent dispatcher never had. Parent order above is the
# intact prefix, so this still pins it.
"doc_search", "doc_read",
]
with pytest.raises(ValueError, match="has no handler"):
build_rebase_tools([{"name": "mystery", "description": "?",
Expand Down
Loading