Skip to content

Add orchestrator, task-worker and merge-conflict-resolver agents - #100

Open
james-d12 wants to merge 5 commits into
mainfrom
feature/orchestrator-agents
Open

james-d12 wants to merge 5 commits into
mainfrom
feature/orchestrator-agents

Conversation

@james-d12

@james-d12 james-d12 commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds three project-level Claude Code subagent definitions under .claude/agents/ so multi-step work in this repo can be delegated and coordinated:

  • orchestrator (opus, high effort): takes requirements from the user, decomposes them into independent tasks, spawns task-worker subagents on the cheapest model that fits each task, integrates worker branches on a dedicated integration branch, hands conflicts to merge-conflict-resolver, verifies with build/test/format and returns one report. Edit/Write are hook-restricted to its run log in docs/orchestration/, so every repository change goes through a worker. Workers are spawned with worktree isolation so they never collide with each other or with other sessions.
  • task-worker (haiku default, overridable per spawn; the orchestrator picks the tier per task): generic single-task implementer. Reads the brief, stays inside the files it owns, adds tests, runs build/test/format, commits on its own branch and returns a fixed-format report (branch, changes, verification, assumptions, out-of-scope observations). Cannot spawn agents.
  • merge-conflict-resolver (sonnet default; orchestrator uses opus when both sides changed the same logic): identifies the in-progress operation and sides, reads base/ours/theirs and originating commits, resolves to preserve both intents, regenerates rather than hand-merges generated files, verifies, stages or completes the operation, and reports per-file resolutions with confidence and a "needs human review" section. Wholesale ours/theirs, resets, force pushes and aborts are forbidden. Cannot spawn agents.

Orchestration run logs

The orchestrator keeps shared memory across runs in docs/orchestration/:

  • One markdown log per run, named YYYY-MM-DD-HHMM-<slug>.md (UTC start time), with the requirement, a task table (agent / model / outcome), outcome, verification, decisions, open items and "Notes for the next orchestrator". Format is in docs/orchestration/README.md.
  • The orchestrator reads the latest logs before planning, opens its own log with status: in-progress as soon as it has restated the requirement, keeps it current, and closes it on the integration branch so it reaches main with the work.
  • Write/Edit are enabled for the orchestrator, but a PreToolUse hook (.claude/hooks/orchestrator-write-guard.sh) blocks any path other than docs/orchestration/*.md, so code changes still go through task-workers.
  • docs/README.md and CLAUDE.md classify the new folder (append-only, never moved to docs/done/).

No remote writes from workers

task-worker and merge-conflict-resolver get a PreToolUse hook on Bash (.claude/hooks/no-remote-writes.py) that parses each command and rejects anything that writes to the remote: git push in any form (including via -C, timeout, sh -c, xargs, eval), git remote mutations, git config of aliases/remotes/push/credential settings, git send-email/svn, and every gh command except read-only views and gh api GETs. Commit, checkout, switch, fetch, pull, add, status, diff and log remain allowed. Pushing and PRs are the orchestrator's or the user's job. python3 -I .claude/hooks/no-remote-writes.py --self-test runs 41 allow/block cases.

Bash sandbox (strict) and git-level push block

.claude/settings.json turns on the Claude Code Bash sandbox for this repo: no unsandboxed retry, fail if unavailable, network limited to api.anthropic.com, github.com, api.github.com, *.nuget.org; writes allowed to the dotnet/NuGet home dirs; private SSH keys read-denied. On Linux it needs bubblewrap and socat.

Three things had to be worked around to make the .NET toolchain run inside it (details in CLAUDE.md):

  • MSBuild worker nodes create their named-pipe sockets at the hard-coded path /tmp/MSBuild<pid>; the seccomp filter blocks Unix sockets and /tmp is read-only, so solution builds hung for 5 minutes and reported FAILED with 0 errors. Fixed with allowAllUnixSockets + allowWrite: ["/tmp"], with denyRead hiding the D-Bus buses, SSH agent, X11 and Docker/Podman sockets so the usual escape routes stay closed (verified: systemd-run --user, busctl, ssh-add -l all fail inside).
  • SourceLink's LocateRepository fails on the /dev/null mount the sandbox places over .gitmodules; env.EnableSourceControlManagerQueries=false skips it for builds under Claude Code only (CI unaffected).
  • The SSH agent and keyring are unreachable inside the sandbox, so git fetch/pull/push/ls-remote and read-only gh commands run via excludedCommands. Workers still cannot push: their hook blocks the command first.

no-remote-writes.py additionally sets a per-worktree remote.*.pushurl to a dead path (via extensions.worktreeConfig) whenever it runs in a linked worktree, so a push the parser misses fails at the git level. The primary checkout is never touched.

Notes

  • Frontmatter uses only documented subagent fields (model, effort, tools, disallowedTools, color).
  • No engine/code changes; CI should be unaffected.

Test plan

  • Frontmatter of all three files parses as YAML with the expected name/model/tools/disallowedTools/hooks
  • Inside the sandbox (nested claude -p --settings .claude/settings.json sessions): dotnet build, dotnet test (998 tests pass), dotnet format --verify-no-changes, codeguard rules test, git ls-remote, gh pr view all succeed; touch ~/x, direct curl, reading a private key, systemd-run --user, busctl, ssh-add -l all fail
  • Per-worktree pushurl: push from a linked worktree fails, fetch works, primary checkout's push unaffected (scratch repo)
  • no-remote-writes.py --self-test: 41/41 cases pass; stdin protocol checked (git push exit 2, git commit exit 0)
  • Write-guard hook tested by hand: allows docs/orchestration/<file>.md, blocks source paths and nested paths (exit 2)
  • Start a session from a checkout containing this branch and confirm the three agents appear as available agent types

🤖 Generated with Claude Code

james-d12 and others added 5 commits October 14, 2026 18:27
Three project-level Claude Code subagent definitions under .claude/agents/:

- orchestrator: takes requirements, decomposes them into independent
  tasks, spawns task-worker subagents on the cheapest suitable model
  (haiku/sonnet/opus/fable), integrates their branches and hands
  conflicts to merge-conflict-resolver. Edit/Write are disallowed so
  every change goes through a worker.
- task-worker: generic single-task implementer that works in its own
  worktree, verifies with build/test/format, commits on its branch and
  returns a structured report. Cannot spawn agents.
- merge-conflict-resolver: resolves merge/rebase/cherry-pick conflicts
  preserving both sides' intent, verifies, stages or completes the
  operation and reports per-file resolutions. Cannot spawn agents.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The orchestrator overrides the model per task; the cheapest tier is the
right baseline when no override is given.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The orchestrator now keeps shared memory across runs:

- docs/orchestration/README.md defines the run-log format and naming
  (YYYY-MM-DD-HHMM-<slug>.md, UTC start time) with sections for the
  requirement, task table (agent/model/outcome), outcome, verification,
  decisions, open items and notes for the next orchestrator.
- The orchestrator reads the latest logs before planning, opens its own
  log with status: in-progress as soon as it has restated the
  requirement, keeps it current, and closes it on the integration
  branch at the end.
- Write/Edit are now allowed for the orchestrator but a PreToolUse hook
  (.claude/hooks/orchestrator-write-guard.sh) blocks any path other
  than docs/orchestration/*.md, so code changes still go through
  task-workers.
- docs/README.md and CLAUDE.md classify the new folder.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds .claude/hooks/no-remote-writes.py, a PreToolUse hook on Bash wired
into both worker agents. It parses each shell command (segments, global
git options, wrappers like timeout/sh -c/xargs/eval) and exits 2 for:

- git push, send-email, svn, request-pull
- git remote add/set-url/rename/remove and git config of alias.*,
  remote.*, url.*, push.*, credential.*
- any gh command other than read-only pr/issue/run/release/repo/search
  views and gh api without a write method or body

commit, checkout, switch, fetch, pull, add, status, diff, log and
read-only remote inspection remain allowed. `--self-test` runs 41
allow/block cases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
.claude/settings.json turns on the Claude Code Bash sandbox for this
repo: no unsandboxed retry, fail if unavailable, network limited to
api.anthropic.com / github.com / api.github.com / *.nuget.org, writes
allowed to the dotnet/NuGet home dirs, private SSH keys read-denied.

Findings from running build/test/format inside it, all encoded in the
settings file and explained in CLAUDE.md:

- MSBuild worker nodes use named pipes at the hard-coded path
  /tmp/MSBuild<pid>; the seccomp filter blocks Unix sockets and /tmp is
  read-only, so solution builds hung 5 minutes then "failed" with 0
  errors. Fixed with allowAllUnixSockets + allowWrite /tmp, and the
  socket escape routes (D-Bus, SSH agent, X11, Docker/Podman) hidden via
  denyRead. Verified systemd-run/busctl/ssh-add fail in the sandbox.
- SourceLink's LocateRepository fails on the /dev/null mount the sandbox
  puts over .gitmodules; env EnableSourceControlManagerQueries=false
  skips it for builds under Claude Code only.
- SSH agent and keyring are unreachable in the sandbox, so git
  fetch/pull/push/ls-remote and read-only gh commands are
  excludedCommands (workers' hook still blocks pushes).

no-remote-writes.py now also sets a per-worktree remote.*.pushurl to a
dead path (extensions.worktreeConfig) when running in a linked
worktree, so a push the parser misses still fails; the primary checkout
is never touched.

Verified in nested sandboxed sessions: dotnet build, dotnet test (998
tests), dotnet format --verify-no-changes, codeguard rules test, git
ls-remote and gh pr view all succeed; home writes, direct network,
private key reads and D-Bus access all fail.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant