From 34b6bbb96a8964bd8e36a3d7750d212ef73e83a9 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Mon, 10 Aug 2026 01:19:25 -0700 Subject: [PATCH] feat(skills): add bundled merge-reconciler skill for neutral multi-agent conflict resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds skills/autonomous-ai-agents/merge-reconciler — a bundled skill teaching a neutral third-party agent to resolve git merge conflicts between two agents' branches: gather both diffs + intents, classify each hunk (disjoint-intent / same-question-different-answer / superseded), resolve under an impartiality contract, verify, and hand back a per-hunk summary. Procedure was live-tested end-to-end against a real conflict fixture. Includes contract tests (tests/skills/test_merge_reconciler_skill.py) and a kanban docs cross-reference (en + zh-Hans): assign a third neutral profile a reconciliation card with both conflicted cards as parents. --- .../merge-reconciler/SKILL.md | 154 ++++++++++++++++++ tests/skills/test_merge_reconciler_skill.py | 102 ++++++++++++ website/docs/user-guide/features/kanban.md | 13 ++ .../current/user-guide/features/kanban.md | 4 + 4 files changed, 273 insertions(+) create mode 100644 skills/autonomous-ai-agents/merge-reconciler/SKILL.md create mode 100644 tests/skills/test_merge_reconciler_skill.py diff --git a/skills/autonomous-ai-agents/merge-reconciler/SKILL.md b/skills/autonomous-ai-agents/merge-reconciler/SKILL.md new file mode 100644 index 0000000000..f81cb4c168 --- /dev/null +++ b/skills/autonomous-ai-agents/merge-reconciler/SKILL.md @@ -0,0 +1,154 @@ +--- +name: merge-reconciler +description: "Neutral third-party resolution of agent merge conflicts." +version: 1.0.0 +author: Hermes Agent +license: MIT +platforms: [linux, macos, windows] +metadata: + hermes: + tags: [Multi-Agent, Git, Merge-Conflict, Kanban, Arbitration] + related_skills: [hermes-agent] +--- + +# Merge Reconciler + +Resolve a git merge conflict between two AGENTS' branches as an impartial third +party. Agents resolving conflicts against a peer's work reliably either +overwrite the peer or abandon their own change — they lack the peer's context +and are biased toward their own side. This skill is the fix: a neutral +reconciler that receives both diffs plus both sides' stated intents and +produces a merged result, like a merge-queue arbiter. + +## When to Use + +- Two agent branches/worktrees collide during a parallel campaign (kanban + engineering pipeline, parallel-PR wave, multi-worktree refactor). +- `git merge` or `git rebase` halts on conflicts between two agents' work and + neither original agent should self-adjudicate. +- Do NOT use for conflicts within a single agent's own work, or for trivial + lockfile/generated-file conflicts (regenerate those instead). + +## Prerequisites + +- A repo checkout containing the halted merge, or the two branch names plus + permission to run the merge yourself. +- Both sides' intent sources: kanban completion summaries (`terminal` running + `hermes kanban show `), PR bodies, or at minimum each branch's + commit messages. +- The project's build/test command, if one exists. + +## How to Run + +**Standalone** — a human (or agent) invokes this skill inside the conflicted +repo: load the skill, then follow the Procedure top to bottom. + +**Spawned neutral agent** — the preferred shape in multi-agent campaigns: + +- `delegate_task`: spawn a subagent whose task message contains the repo path, + both branch names, and both sides' intent summaries verbatim, plus an + instruction to follow this skill. +- Kanban-native: create a reconciliation card assigned to a **third profile** + (not either worker's profile) with BOTH conflicted cards linked as parents — + `kanban_create(title="reconcile branch-a x branch-b", assignee="reconciler", + parents=["t_a", "t_b"])`. The parent links carry both sides' completion + summaries into the reconciler's context automatically; the card body should + name the repo path and the two branches. + +## Quick Reference + +| Hunk class | Definition | Resolution | +|---|---|---| +| disjoint-intent | The two changes serve different goals and can coexist | Combine both | +| same-question-different-answer | Both sides answered one design question differently | Pick ONE per stated intents; surface the decision | +| superseded | One side's premise no longer holds after the other's change | Keep the surviving side; note why | + +Impartiality contract: never favor the side that spawned you; touch ONLY +conflicted regions (no drive-by edits); every design-question pick must appear +explicitly in the hand-back summary. + +## Procedure + +### 1. Gather both sides + +- Run via `terminal`: `git status` (confirm the conflicted state and list + conflicted files), `git merge-base `, then for each side + `git log --oneline ..` and `git diff .. -- ` + for every conflicted file. In a halted merge, `HEAD` is one side and + `MERGE_HEAD` is the other. +- Collect each side's intent: `hermes kanban show ` for completion + summaries/metadata, or the PR body, or the commit messages from the log + above. Write down one sentence of intent per side before touching any file. +- Done when: you can state both intents in your own words and have both diffs + for every conflicted file. + +### 2. Classify every conflicted hunk + +- Open each conflicted file with `read_file` and locate each + `<<<<<<<`/`=======`/`>>>>>>>` block. +- Assign each hunk exactly one class from the Quick Reference table, judging + by the stated intents — not by which change looks nicer. +- If a single hunk contains multiple independent decisions (e.g., new logic + that combines cleanly PLUS a styling/rounding choice both sides answered + differently), decompose it into sub-decisions and classify each one. +- A single file often mixes classes: one hunk may be a design collision while + a neighboring hunk is disjoint. Classify per hunk, not per file. +- Done when: every hunk has a written class and a one-line rationale. + +### 3. Resolve under the impartiality contract + +- Edit each hunk with `patch` (or `write_file` for whole-file rewrites): + - disjoint-intent → merge both changes so each intent is fully served. + - same-question-different-answer → pick the answer that best serves the + STATED intents (e.g., an intent of "strict validation" beats "quick + default" if the task required correctness). Never split the difference + into a hybrid neither side asked for. + - superseded → keep the surviving side; delete the dead premise. +- Never favor the side that spawned you. If intents genuinely tie, escalate + (block the kanban card / report back) rather than guess. +- Change nothing outside conflict markers — no formatting, renames, or + opportunistic fixes. +- `git add` each resolved file via `terminal`. +- Done when: `search_files` finds no `<<<<<<<` markers in the repo and every + resolved file is staged. + +### 4. Verify + +- Run the project's build/tests via `terminal`; at minimum import/execute the + touched modules. Both intents must be observable in the merged behavior + (e.g., side A's new semantics AND side B's disjoint addition both present). +- Complete the merge: `git commit` (the default merge message plus a body + listing hunk decisions is fine). +- Done when: verification passes and the merge commit exists. + +### 5. Hand back + +- Produce a completion summary naming EVERY hunk decision: + `file:lines — class — which side(s) kept — rationale`. For every + same-question-different-answer hunk, state the design question and the + answer you picked so a human can veto it — never bury a design call. +- Kanban: `kanban_complete(summary=...)`. Standalone: print the summary. +- Done when: the summary is delivered and lists all hunks. + +## Pitfalls + +- **Self-favoring**: if you were spawned by one of the conflicting agents, + you are structurally biased — state this and weigh the other side's intent + deliberately. Prefer the third-profile shape so this never arises. +- **Splitting the difference** on a design collision produces a hybrid nobody + designed; pick one answer and surface it. +- **Per-file classification**: files usually mix hunk classes; classifying a + whole file as one class silently drops a disjoint change. +- **Drive-by edits** make the merge unreviewable and steal decisions from the + original agents. +- **Missing intents**: commit messages alone can be thin; prefer kanban + completion summaries or PR bodies. If neither side's intent is recoverable, + escalate instead of guessing. + +## Verification + +- `git status` shows a clean tree on the target branch with a merge commit. +- No conflict markers remain (`search_files` pattern `<<<<<<<`). +- Build/tests pass; both sides' intents are demonstrably present or the + dropped one is explicitly named in the summary. +- The hand-back summary enumerates every hunk with class and rationale. diff --git a/tests/skills/test_merge_reconciler_skill.py b/tests/skills/test_merge_reconciler_skill.py new file mode 100644 index 0000000000..adb6db068b --- /dev/null +++ b/tests/skills/test_merge_reconciler_skill.py @@ -0,0 +1,102 @@ +"""Contract checks for the bundled merge-reconciler skill asset. + +Reads only the SKILL.md markdown asset — no .py source reads, no network. +""" +import re +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +SKILL_PATH = ( + REPO_ROOT + / "skills" + / "autonomous-ai-agents" + / "merge-reconciler" + / "SKILL.md" +) + +REQUIRED_SECTIONS = [ + "## When to Use", + "## Prerequisites", + "## How to Run", + "## Quick Reference", + "## Procedure", + "## Pitfalls", + "## Verification", +] + + +def _frontmatter_and_body(): + content = SKILL_PATH.read_text(encoding="utf-8") + assert content.startswith("---"), "SKILL.md must open with frontmatter" + m = re.search(r"\n---\s*\n", content[3:]) + assert m, "frontmatter must close with ---" + fm_text = content[3 : m.start() + 3] + body = content[m.end() + 3 :] + fm = {} + for line in fm_text.splitlines(): + km = re.match(r"^(\w[\w-]*):\s*(.*)$", line) + if km: + fm[km.group(1)] = km.group(2).strip().strip('"') + return fm, body + + +def test_skill_file_exists(): + assert SKILL_PATH.is_file() + + +def test_frontmatter_required_fields(): + fm, _ = _frontmatter_and_body() + for field in ("name", "description", "version", "author", "license", "platforms"): + assert field in fm, f"missing frontmatter field: {field}" + assert fm["name"] == "merge-reconciler" + + +def test_description_hardline(): + fm, _ = _frontmatter_and_body() + desc = fm["description"] + assert len(desc) <= 60, f"description is {len(desc)} chars; hardline is 60" + assert desc.endswith("."), "description must end with a period" + assert desc.count(".") == 1, "description must be one sentence" + + +def test_required_sections_present_in_order(): + _, body = _frontmatter_and_body() + positions = [] + for section in REQUIRED_SECTIONS: + idx = body.find(section) + assert idx != -1, f"missing section: {section}" + positions.append(idx) + assert positions == sorted(positions), "sections out of required order" + + +def test_body_size_within_bundled_norms(): + content = SKILL_PATH.read_text(encoding="utf-8") + lines = content.count("\n") + 1 + assert 80 <= lines <= 260, f"SKILL.md is {lines} lines; expected ~100-200" + + +def test_references_native_hermes_tools(): + _, body = _frontmatter_and_body() + for tool in ("`terminal`", "`read_file`", "`patch`", "`delegate_task`"): + assert tool in body, f"body must reference native tool {tool}" + + +def test_classification_taxonomy_present(): + _, body = _frontmatter_and_body() + for cls in ( + "disjoint-intent", + "same-question-different-answer", + "superseded", + ): + assert cls in body, f"missing hunk class: {cls}" + + +def test_impartiality_contract_stated(): + _, body = _frontmatter_and_body() + assert "never favor" in body.lower() + assert "drive-by" in body.lower() + + +def test_no_machine_local_paths(): + content = SKILL_PATH.read_text(encoding="utf-8") + assert "/home/" not in content diff --git a/website/docs/user-guide/features/kanban.md b/website/docs/user-guide/features/kanban.md index 8206002ad0..9f05c44894 100644 --- a/website/docs/user-guide/features/kanban.md +++ b/website/docs/user-guide/features/kanban.md @@ -933,6 +933,19 @@ EOF The remediation worker spawns with the original card's summary and metadata (changed files, decisions) already in context, plus the fresh evidence you put in the body. +### Reconciling colliding worker branches + +In engineering pipelines (P1/P2 with worktrees), two workers' branches can +conflict when merged. Don't let either worker self-adjudicate — the colliding +agent lacks its peer's context and reliably overwrites the other side or +abandons its own. Instead, create a reconciliation card assigned to a **third, +neutral profile** with **both** conflicted cards linked as parents: the parent +links carry both sides' completion summaries into the reconciler's context, so +it receives both diffs *and* both intents. The bundled +[`merge-reconciler` skill](https://github.com/NousResearch/hermes-agent/blob/main/skills/autonomous-ai-agents/merge-reconciler/SKILL.md) +gives that worker the full procedure: classify each conflicted hunk, resolve +impartially, verify, and hand back a summary naming every decision. + ## Multi-tenant usage When one specialist fleet serves multiple businesses, tag each task with a tenant: diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/kanban.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/kanban.md index 7e031d142e..763f60ac42 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/kanban.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/kanban.md @@ -689,6 +689,10 @@ EOF 修复 worker 启动时,原卡片的 summary 和 metadata(改动的文件、决策)已在其上下文中,再加上你写入正文的新证据。 +### 调解冲突的 worker 分支 + +在工程流水线(使用 worktree 的 P1/P2)中,两个 worker 的分支合并时可能发生冲突。不要让任一 worker 自行裁决 —— 发生冲突的 agent 缺乏对方的上下文,往往会覆盖对方的改动或放弃自己的改动。正确做法是:创建一张调解卡片,指派给**第三个中立配置文件**,并把**两张**冲突卡片都链接为其父任务:父任务链接会把双方的完成摘要带入调解者的上下文,使其同时获得双方的 diff *和*双方的意图。内置的 [`merge-reconciler` 技能](https://github.com/NousResearch/hermes-agent/blob/main/skills/autonomous-ai-agents/merge-reconciler/SKILL.md) 为该 worker 提供完整流程:对每个冲突块分类、公正地解决、验证,并在交回摘要中说明每一项决定。 + ## 多租户使用 当一个专家团队为多个业务提供服务时,为每个任务添加租户标签: