Skip to content

Research example

One real research run on a hordecode design question, with its stages, decision record and cost.

This page shows one real run of the research agent on a design question from hordecode’s own codebase. It is run 2026-10-30-worktree-merge-back-01, a research-only run that changed no code. The outputs below are shown as the run wrote them.


The question

The question, verbatim from the run:

Research question for this repository: when a supervisor child session runs with isolate (its own git worktree), how should its work merge back into the parent branch? Today worktree removal runs git branch -D and unmerged commits are lost. Use the code in this repo (packages/worktree, packages/supervisor, packages/opencode/src/tool) as the primary evidence and upstream git documentation as secondary. Run the full research pipeline, write everything in English, and end with a decision record (ADOPT/DEFER/REJECT).

The agent asked two questions before it started, and the operator answered both:

  • Scope: “Research only; all worktree producers” (the agent’s recommended option).
  • Depth: “Standard depth: repo code plus upstream git documentation, no survey of other products. Still run the gather stage with parallel sub-agents, one per area.”

No other operator input was given during the run.


Setup

  • Product: horde 0.1.4, the installed release binary.
  • Agent: research, the primary agent, with the research skill bundle.
  • Model: deepseek/deepseek-flash, shown in the TUI as “DeepSeek V4.1 Flash 1017”.
  • Workspace: a detached git worktree of hordecode main at commit 86d896e1cd, with an isolated data home that held no earlier sessions.

Stages

The run went through nine stages in this order: ask, scope, plan, gather, verify, filter, synthesize, decide and eval. Nothing failed and nothing was retried. Each screenshot sits beside the stage it shows.

  1. Ask. The agent asked the two questions above. The run starts with the research and research:ask skills loaded.

    Terminal at the start of the research run: shell output lists the repository packages and the git worktrees, and the sidebar shows token and cost telemetry.

  2. Scope. The brief was written, with research questions, scope boundaries, an evidence policy and decision axes.

  3. Plan. The question was split into four areas, one per gather sub-agent: worktree services, supervisor lifecycle, tool layer and git docs.

  4. Gather. The four sub-agents wrote their evidence in parallel. The sidebar lists two explore sub-agents active at once, one for worktree services and one for supervisor lifecycle.

    Gather stage: the sidebar lists two explore sub-agents active at the same time, one for the worktree services and one for the supervisor lifecycle, while the main session writes its evidence file.

  5. Verify. An independent audit sub-agent re-read the citations and checked the claims.

    Verify stage: the claims table, where two claims are marked corrected, above the phase gate checklist with every item checked.

  6. Filter. The raw evidence was deduplicated into one table of 20 sources.

  7. Synthesize. The report was written, with its findings, recommendations and a citation table of 20 sources.

  8. Decide. The decision record was written. Its main recommendation is ADOPT, and the other items are marked DEFER or REJECT.

    Decide stage: the decision record's ADR checklist with its gates checked, followed by the consequences list.

  9. Eval. Each stage was scored and the run aggregate was written. The final summary appears in the TUI.

    Final summary in the TUI: the pipeline, the ADOPT decision with its DEFER and REJECT items, the four-part defect chain and the recommended fixes.

StageScoreWhat it produced
ASKnot scoredintent.md: the verbatim question and the two answers
SCOPE0.75brief.md: research questions, scope boundaries, evidence policy, decision axes
PLANnot scoredmanifest.yaml: four gather sub-agents and their subtopics
GATHER0.87Four evidence files, one per subtopic
VERIFY1.00verified-evidence.md: citation audit; 2 claims corrected, 3 conflict families surfaced
FILTER1.00filtered-evidence.md: deduplicated table of 20 sources
SYNTHESIZE1.00report.md: findings, recommendations, 20-source citation table
DECIDE1.00decision.md: ADOPT, DEFER and REJECT states
EVAL1.00eval.score and aggregate.json
Aggregateaverage 0.94, minimum 0.75 (scope)Gate PASS

Notes on the scores:

  • Scope (0.75) lost points on the bounded-vocabulary check.
  • Gather (0.87) lost points on source-domain diversity, and 1 of 199 claim rows has no source. The TUI captures show at most two gather sub-agents running at the same time.
  • Verify (1.00) corrected 2 claims and surfaced 3 conflict families.

Run-level metrics: 20 sources, 22 of 22 report claims sourced, an UNCORROBORATED rate of 0.106 (19 of 179 raw gather claims, all inferences), 3 conflicts and 0 stale sources.


Decision record

The run’s decision.md, copied as written. Its headings sit one level lower here.

Date: 2026-10-30 Run ID: 2026-10-30-worktree-merge-back-01 Finding: An isolate child’s work is destroyed by an unguarded git worktree remove --force plus git branch -D, while the documented merge-back is prompt-only, targets the wrong branch prefix, and assumes commits that no prompt asks the child to make.


Decision

ADOPT — parent-owned git merge into an explicitly named target branch, with three mechanical guards: (1) isolated children are told to commit and the branch is returned to the parent, (2) git-aware preflight (ahead/dirty/conflict) is exposed through session status and dashboard, and (3) worktree removal preserves unmerged branches and refuses to destroy work unless explicitly forced.

Sub-decisions:

IDSubjectStateNote
D1Keep merge parent-owned (orchestrator runs git), not service-auto-mergeADOPTMatches orchestrator.txt:92-95 and the harness integrate precedent
D2Tell isolate children to commit; return branch in session create outputADOPTSmallest change with the largest loss reduction
D3Add fork-owned inspect() preflight (rev-list, merge-base --is-ancestor, status, merge-tree --write-tree)ADOPTPure module in FORK_ONLY packages/worktree; no new service
D4Non-destructive Worktree.remove: -d plus ancestor check; -D only when forcedADOPTTurns silent loss into a refusal
D5session cancel reports branch/ahead/dirty and refuses unmerged work without --forceADOPTFixes the constant false-success string
D6Require explicit merge target; refuse when the parent is a detached HEADADOPT“Parent branch” is otherwise undefined
D7Await worktree population before spawning an isolate childADOPTRemoves the empty-tree/clobber race
D8Fix orchestrator.txt to stop hard-coding hordecode/*ADOPTFORK_ONLY file; prompt is wrong for the isolate path
D9Auto-merge on child completionDEFERNeeds review/concurrency semantics first; rejected for now in deliberation
D10Control-plane detached worktree lifecycleDEFEROut of the isolate path; no branch to merge, preserve-on-dirty needed
D11Rename the service ref prefix to hordecode/REJECTUpstream identifier in a FORK_DELTA file; FORK-RULES section 3 forbids rebranding; tests assert opencode/
D12Squash, patch, or bundle as the default transportREJECTLoses ancestry or adds no fidelity for linked worktrees; squash would defeat any later “is it merged?” check
D13Prompt-only fix (documentation change without code guards)REJECTThe current prompt already warns about the exact failure and is still violated by the code path

Reasoning

The evidence supports the direction the repository already intends, not a new subsystem. orchestrator.txt:92-95 tells the parent to merge an isolated child’s branch with git, and the harness workflow implements exactly that pattern (commit in the worktree, an agent merges, then git worktree remove: harness.js:425,431-439). What is missing is enforcement and correctness, not architecture.

Four facts force code changes rather than another prompt paragraph:

  1. The isolate service creates opencode/<name> (packages/opencode/src/worktree/index.ts:183), while the prompt tells the parent to look for hordecode/* (orchestrator.txt:92-95). A parent following the prompt cannot find the branch, so “integration” can silently do nothing.
  2. No isolate path asks the child to commit, and the model prompts forbid unsolicited commits (trinity.txt:78, deepseek.txt:108). Even a correct merge command often has no commits to merge; the work sits as dirty files and is destroyed by worktree remove --force (worktree/index.ts:419).
  3. Cleanup is unconditional and silent: session cancel is the only supervisor child cleanup path, runs worktree remove --force then branch -D, swallows failures with Effect.exit, and prints a constant success string (tool/session.ts:792-824, output at :821).
  4. The target is implicit. A parent workspace can be a detached HEAD (this research workspace is one), in which case “merge into the parent branch” has no referent and a merge would create unreferenced commits.

git merge is the right mechanism because linked worktrees share refs and objects: merge preserves ancestry, conflicts leave MERGE_HEAD with a documented --abort, and git merge-tree --write-tree can preflight conflicts without touching the parent’s worktree (git >= 2.38; local git is 2.53.0). Cherry-pick loses ancestry, patch/am loses merge topology and binary fidelity, and bundle adds transport that linked worktrees do not need.

Automatic merging on completion is deferred rather than adopted because it is irreversible, it would interleave concurrent children into the user’s workspace without review, and it contradicts the prompt’s own rule to ask before irreversible actions (orchestrator.txt:143,148).

Evidence

FindingSourceConfidenceUNCORROBORATED
Isolate uses packages/opencode/src/worktree (opencode/ prefix)tool/session.ts:26; worktree/index.ts:183high[ ]
Prompt documents hordecode/* and a manual mergeorchestrator.txt:92-95high[ ]
Removal = worktree remove --force + branch -D, skipped when detachedworktree/index.ts:409-446; adapters/worktree.ts:35high[ ]
session cancel is the only supervisor-child cleanup and reports a constant stringtool/session.ts:792-824,821high[ ]
No prompt tells an isolate child to commit; model prompts forbid ittrinity.txt:78; deepseek.txt:108; session.txthigh[ ]
Harness precedent: commit, agent merge, then worktree removeharness.js:425,431-439high[ ]
Isolate create forks boot (reset --hard) without awaitingworktree/index.ts:281-293,237high[x] (ordering inference)
Supervisor has no branch/worktree state to drive cleanuppackages/supervisor/src/schema.ts:22-50; package.json:22-34high[ ]
git branch -d refuses unmerged; -D forces and deletes refloghttps://git-scm.com/docs/git-branchhigh[ ]
worktree remove refuses dirty/untracked without --forcehttps://git-scm.com/docs/git-worktreehigh[ ]
merge-tree --write-tree preflights conflicts (>= 2.38)https://git-scm.com/docs/git-merge-tree; local git 2.53.0high[ ]
Fork ownership: worktree copy is FORK_DELTA; prompt/tool/workflow are FORK_ONLYdocs/upstream-map-files.tsv:1356,2897,2952,7076-7078high[ ]

Target

  • Milestone: next fork delta cycle after the current dev/0.1.4 baseline (no milestone ledger exists in this tree)
  • Task: new task needed — “isolate merge-back guards” (D1-D8 as one unit; D2/D5/D8 first because they stop silent loss with the smallest surface)
  • Queue: no QUEUE.md exists in this repository; nothing to update

Context Delta (S3, merge — CONTEXT.md linkage)

The repository keeps a domain glossary at CONTEXT.md (225 lines) but it is about the session runtime, not worktree integration. Proposed addition when D1-D8 are implemented: a Worktree Integration term (“the explicit, parent-owned act of merging a child worktree’s branch into a named target, preceded by conflict preflight and followed by a guarded removal”), plus Preserved Worktree (“a worktree whose branch is not reachable from the removal target and therefore must survive cleanup”). No CONTEXT.md change is made by this research run.

ADR 3-Gate Checklist (S3, conditional on ADR linkage)

  • Gate 1 — decision state ADOPT/DEFER/REJECT recorded (D1-D13)
  • Gate 2 — consequences enumerated (below)
  • Gate 3 — linkage exists: findings/2026-10-30-worktree-merge-back-01/report.md sections F1-F9 and R1-R8, plus the file:line sources in the Evidence table

ADR

  • ADR ID: new ADR needed (no ADR-LOG.md exists in this repository)
  • Consequences:
    • session cancel on an isolate child with unmerged work stops deleting; it returns the branch and the merge command, and requires --force to discard. Behavioural change for any automation that relied on cancel always cleaning up.
    • Worktree.remove gains a target-aware ancestor check and a force path; reset/HTTP callers that intend reclamation must pass force.
    • session create --isolate output and metadata gain the branch name; the child prompt gains a commit directive, which changes the child’s instructions (and therefore its commit behaviour) for isolated sessions only.
    • dashboard finally displays ahead, so the field stops being dead data.
    • New fork-owned module packages/worktree/src/integrate.ts; one small fork hunk in the FORK_DELTA worktree copy to call it. No identifier rebranding and no change to the opencode/ ref prefix.
    • Auto-merge on completion and control-plane detached lifecycle remain open (D9, D10) and must be decided before treating cleanup as fully safe.


Report

The report from the synthesize stage traces the failure to four defects that stack. Uncommitted edits are deleted by worktree remove --force before the branch is touched, so the loss is conditional rather than a single git branch -D step. The isolated child is never asked to commit, and the orchestrator prompt names a hordecode/ branch prefix while the isolate service creates opencode/ branches. session cancel removes the worktree without checks and prints a fixed success message, and the isolate create path does not wait for its own population step. The recommendation is a parent-owned git merge into an explicitly named branch, preceded by git merge-tree preflight checks, with a removal path that keeps unmerged branches unless forced. The report does not recommend automatic merging when a child completes, because that step cannot be undone and would run without review. When the parent workspace is a detached HEAD, the merge target is undefined, and the report says so. Open items include the git version floor for merge-tree --write-tree (git 2.38) and the detached worktrees created by the control-plane adapter, which the run did not trace end to end.

Citation table (20 sources)
#SourceTypeDateVerified
1packages/opencode/src/worktree/index.tsofficial2026-10-30[x]
2packages/worktree/src/index.tsofficial2026-10-30[x]
3packages/opencode/src/tool/session.tsofficial2026-10-30[x]
4packages/core/src/session/prompt/orchestrator.txtofficial2026-10-30[x]
5packages/core/src/session/prompt/trinity.txt, deepseek.txtofficial2026-10-30[x]
6packages/supervisor/src/{spawn,schema,group}.ts, package.jsonofficial2026-10-30[x]
7packages/workflow/src/runtime.tsofficial2026-10-30[x]
8packages/workflow/src/builtin/harness.jsofficial2026-10-30[x]
9packages/opencode/src/tool/shell.tsofficial2026-10-30[x]
10packages/opencode/src/tool/fleet.ts, session.shell.txt, session.txtofficial2026-10-30[x]
11packages/opencode/src/control-plane/adapters/worktree.tsofficial2026-10-30[x]
12packages/opencode/test/tool/*worktree*, test/project/worktree-remove.test.tsofficial2026-10-30[x]
13docs/upstream-map-files.tsv, docs/FORK-RULES.mdofficial2026-10-30[x]
14https://git-scm.com/docs/git-worktreeofficial2026-10-30[x]
15https://git-scm.com/docs/git-branchofficial2026-10-30[x]
16https://git-scm.com/docs/git-mergeofficial2026-10-30[x]
17https://git-scm.com/docs/git-cherry-pickofficial2026-10-30[x]
18https://git-scm.com/docs/git-format-patch, git-amofficial2026-10-30[x]
19https://git-scm.com/docs/git-bundleofficial2026-10-30[x]
20https://git-scm.com/docs/git-merge-tree, git-gcofficial2026-10-30[x]

Time and cost

MeasureValueSource
Wall time (main session)19m 40sTUI sidebar, Activity > Uptime
Turns (main session)89TUI sidebar
Tool calls (main session)110, of which 1 errorTUI sidebar
Tokens (main session)122.3K in, 45.1K out, 15.1K reasoningTUI sidebar
Cache hit (main session)98.8% (10.3M of 10.4M)TUI sidebar
Cost (main session)$0.09TUI sidebar
Sessions (main + sub-agents)10horde stats
Messages (all sessions)366horde stats
Tokens (all sessions)793.4K input, 138.7K output, 31.8M cache readhorde stats
Cost (all sessions)$0.34horde stats

The sidebar shows the main session only. horde stats covers the main session plus every sub-agent session, 10 sessions in all, and the full run cost $0.34.


For how the research agent runs these stages, see Agents.