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
mainat commit86d896e1cd, 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.
-
Ask. The agent asked the two questions above. The run starts with the research and research:ask skills loaded.

-
Scope. The brief was written, with research questions, scope boundaries, an evidence policy and decision axes.
-
Plan. The question was split into four areas, one per gather sub-agent: worktree services, supervisor lifecycle, tool layer and git docs.
-
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.

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

-
Filter. The raw evidence was deduplicated into one table of 20 sources.
-
Synthesize. The report was written, with its findings, recommendations and a citation table of 20 sources.
-
Decide. The decision record was written. Its main recommendation is ADOPT, and the other items are marked DEFER or REJECT.

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

| Stage | Score | What it produced |
|---|---|---|
| ASK | not scored | intent.md: the verbatim question and the two answers |
| SCOPE | 0.75 | brief.md: research questions, scope boundaries, evidence policy, decision axes |
| PLAN | not scored | manifest.yaml: four gather sub-agents and their subtopics |
| GATHER | 0.87 | Four evidence files, one per subtopic |
| VERIFY | 1.00 | verified-evidence.md: citation audit; 2 claims corrected, 3 conflict families surfaced |
| FILTER | 1.00 | filtered-evidence.md: deduplicated table of 20 sources |
| SYNTHESIZE | 1.00 | report.md: findings, recommendations, 20-source citation table |
| DECIDE | 1.00 | decision.md: ADOPT, DEFER and REJECT states |
| EVAL | 1.00 | eval.score and aggregate.json |
| Aggregate | average 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 --forceplusgit 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:
| ID | Subject | State | Note |
|---|---|---|---|
| D1 | Keep merge parent-owned (orchestrator runs git), not service-auto-merge | ADOPT | Matches orchestrator.txt:92-95 and the harness integrate precedent |
| D2 | Tell isolate children to commit; return branch in session create output | ADOPT | Smallest change with the largest loss reduction |
| D3 | Add fork-owned inspect() preflight (rev-list, merge-base --is-ancestor, status, merge-tree --write-tree) | ADOPT | Pure module in FORK_ONLY packages/worktree; no new service |
| D4 | Non-destructive Worktree.remove: -d plus ancestor check; -D only when forced | ADOPT | Turns silent loss into a refusal |
| D5 | session cancel reports branch/ahead/dirty and refuses unmerged work without --force | ADOPT | Fixes the constant false-success string |
| D6 | Require explicit merge target; refuse when the parent is a detached HEAD | ADOPT | “Parent branch” is otherwise undefined |
| D7 | Await worktree population before spawning an isolate child | ADOPT | Removes the empty-tree/clobber race |
| D8 | Fix orchestrator.txt to stop hard-coding hordecode/* | ADOPT | FORK_ONLY file; prompt is wrong for the isolate path |
| D9 | Auto-merge on child completion | DEFER | Needs review/concurrency semantics first; rejected for now in deliberation |
| D10 | Control-plane detached worktree lifecycle | DEFER | Out of the isolate path; no branch to merge, preserve-on-dirty needed |
| D11 | Rename the service ref prefix to hordecode/ | REJECT | Upstream identifier in a FORK_DELTA file; FORK-RULES section 3 forbids rebranding; tests assert opencode/ |
| D12 | Squash, patch, or bundle as the default transport | REJECT | Loses ancestry or adds no fidelity for linked worktrees; squash would defeat any later “is it merged?” check |
| D13 | Prompt-only fix (documentation change without code guards) | REJECT | The 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:
- The isolate service creates
opencode/<name>(packages/opencode/src/worktree/index.ts:183), while the prompt tells the parent to look forhordecode/*(orchestrator.txt:92-95). A parent following the prompt cannot find the branch, so “integration” can silently do nothing. - 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 byworktree remove --force(worktree/index.ts:419). - Cleanup is unconditional and silent:
session cancelis the only supervisor child cleanup path, runsworktree remove --forcethenbranch -D, swallows failures withEffect.exit, and prints a constant success string (tool/session.ts:792-824, output at:821). - 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
| Finding | Source | Confidence | UNCORROBORATED |
|---|---|---|---|
Isolate uses packages/opencode/src/worktree (opencode/ prefix) | tool/session.ts:26; worktree/index.ts:183 | high | [ ] |
Prompt documents hordecode/* and a manual merge | orchestrator.txt:92-95 | high | [ ] |
Removal = worktree remove --force + branch -D, skipped when detached | worktree/index.ts:409-446; adapters/worktree.ts:35 | high | [ ] |
session cancel is the only supervisor-child cleanup and reports a constant string | tool/session.ts:792-824,821 | high | [ ] |
| No prompt tells an isolate child to commit; model prompts forbid it | trinity.txt:78; deepseek.txt:108; session.txt | high | [ ] |
| Harness precedent: commit, agent merge, then worktree remove | harness.js:425,431-439 | high | [ ] |
Isolate create forks boot (reset --hard) without awaiting | worktree/index.ts:281-293,237 | high | [x] (ordering inference) |
| Supervisor has no branch/worktree state to drive cleanup | packages/supervisor/src/schema.ts:22-50; package.json:22-34 | high | [ ] |
git branch -d refuses unmerged; -D forces and deletes reflog | https://git-scm.com/docs/git-branch | high | [ ] |
worktree remove refuses dirty/untracked without --force | https://git-scm.com/docs/git-worktree | high | [ ] |
merge-tree --write-tree preflights conflicts (>= 2.38) | https://git-scm.com/docs/git-merge-tree; local git 2.53.0 | high | [ ] |
| Fork ownership: worktree copy is FORK_DELTA; prompt/tool/workflow are FORK_ONLY | docs/upstream-map-files.tsv:1356,2897,2952,7076-7078 | high | [ ] |
Target
- Milestone: next fork delta cycle after the current
dev/0.1.4baseline (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.mdexists 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.mdsections F1-F9 and R1-R8, plus the file:line sources in the Evidence table
ADR
- ADR ID: new ADR needed (no
ADR-LOG.mdexists in this repository) - Consequences:
session cancelon an isolate child with unmerged work stops deleting; it returns the branch and the merge command, and requires--forceto discard. Behavioural change for any automation that relied on cancel always cleaning up.Worktree.removegains a target-aware ancestor check and aforcepath; reset/HTTP callers that intend reclamation must passforce.session create --isolateoutput 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.dashboardfinally displaysahead, 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 theopencode/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)
| # | Source | Type | Date | Verified |
|---|---|---|---|---|
| 1 | packages/opencode/src/worktree/index.ts | official | 2026-10-30 | [x] |
| 2 | packages/worktree/src/index.ts | official | 2026-10-30 | [x] |
| 3 | packages/opencode/src/tool/session.ts | official | 2026-10-30 | [x] |
| 4 | packages/core/src/session/prompt/orchestrator.txt | official | 2026-10-30 | [x] |
| 5 | packages/core/src/session/prompt/trinity.txt, deepseek.txt | official | 2026-10-30 | [x] |
| 6 | packages/supervisor/src/{spawn,schema,group}.ts, package.json | official | 2026-10-30 | [x] |
| 7 | packages/workflow/src/runtime.ts | official | 2026-10-30 | [x] |
| 8 | packages/workflow/src/builtin/harness.js | official | 2026-10-30 | [x] |
| 9 | packages/opencode/src/tool/shell.ts | official | 2026-10-30 | [x] |
| 10 | packages/opencode/src/tool/fleet.ts, session.shell.txt, session.txt | official | 2026-10-30 | [x] |
| 11 | packages/opencode/src/control-plane/adapters/worktree.ts | official | 2026-10-30 | [x] |
| 12 | packages/opencode/test/tool/*worktree*, test/project/worktree-remove.test.ts | official | 2026-10-30 | [x] |
| 13 | docs/upstream-map-files.tsv, docs/FORK-RULES.md | official | 2026-10-30 | [x] |
| 14 | https://git-scm.com/docs/git-worktree | official | 2026-10-30 | [x] |
| 15 | https://git-scm.com/docs/git-branch | official | 2026-10-30 | [x] |
| 16 | https://git-scm.com/docs/git-merge | official | 2026-10-30 | [x] |
| 17 | https://git-scm.com/docs/git-cherry-pick | official | 2026-10-30 | [x] |
| 18 | https://git-scm.com/docs/git-format-patch, git-am | official | 2026-10-30 | [x] |
| 19 | https://git-scm.com/docs/git-bundle | official | 2026-10-30 | [x] |
| 20 | https://git-scm.com/docs/git-merge-tree, git-gc | official | 2026-10-30 | [x] |
Time and cost
| Measure | Value | Source |
|---|---|---|
| Wall time (main session) | 19m 40s | TUI sidebar, Activity > Uptime |
| Turns (main session) | 89 | TUI sidebar |
| Tool calls (main session) | 110, of which 1 error | TUI sidebar |
| Tokens (main session) | 122.3K in, 45.1K out, 15.1K reasoning | TUI sidebar |
| Cache hit (main session) | 98.8% (10.3M of 10.4M) | TUI sidebar |
| Cost (main session) | $0.09 | TUI sidebar |
| Sessions (main + sub-agents) | 10 | horde stats |
| Messages (all sessions) | 366 | horde stats |
| Tokens (all sessions) | 793.4K input, 138.7K output, 31.8M cache read | horde stats |
| Cost (all sessions) | $0.34 | horde 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.