Skip to main content

Identity

FieldValue
Workflow IDMasflGBKdowUZwpJ
Webhook path/webhook/atomiser
n8n instancehttps://hab.so1.io
Local JSON/TOMMY.json (untracked)
Node count34
PurposeDecompose a repo into tasksets, atomise into task-lets, execute each via Claude Code CLI over SSH

Five Phases

PhasePurposeToken cost
P0: INITMerge webhook input with defaults0
P1: INSPECTFetch repo tree, pipeline.yaml, README from GitHub API0
P2: IDENTIFYSingle Anthropic API call to identify tasksets (~1100 tokens). Skipped when forge_tasks provided or pipeline.yaml exists.0-1100
P3: ATOMISEConvert tasksets into individual task-let prompts0
P4: EXECUTESplitInBatches → SSH → claude --print --output-format json --max-turns 3 --dangerously-skip-permissions -p '<prompt>'Variable
P5: CONFIRMAggregate results, return ALL_PASS or HAS_FAILURES0

Webhook Input Schema

{
  "repo": "owner/repo",
  "branch": "main",
  "taskset_filter": null,
  "project_path": "/path/to/local/clone",
  "ssh_host": "user@host",
  "dry_run": false,
  "max_concurrency": 1,
  "delay_between_ms": 5000,
  "forge_tasks": null,
  "forge_context": null
}
  • forge_tasks + forge_context: When provided, TOMMY skips Phases 1-2 and jumps straight to Atomise (FORGE shortcut).
  • dry_run: true: Logs task-let prompts without executing.
  • taskset_filter: Array of taskset numbers to execute. Null = all.

Response Schema

Success (ALL_PASS):
{
  "status": "SUCCESS",
  "message": "Block complete. N/N task-lets executed successfully.",
  "is_dry_run": false,
  "run_id": "run_1711234567890_abc123",
  "tasklets": [{ "id": "TS-1", "title": "...", "status": "completed" }]
}
Failure (HAS_FAILURES):
{
  "status": "PARTIAL_FAILURE",
  "message": "Block incomplete. 2/3 passed, 1 failed.",
  "failures": [{ "id": "TS-2", "error": "...", "exit_code": 1 }],
  "retry_command": "curl -X POST .../webhook/atomiser -d '{\"taskset_filter\": [2]}'"
}

SSH Execution Command

cd <project_path> && claude --print --output-format json --max-turns 3 \
  --dangerously-skip-permissions -p '<escaped_prompt>'

Credentials

NameIDType
GitHub (Dev4rno)tSg1ejqnd5r3clEggithubApi
google_ai_keyPdEKB19B1R0PQ4thhttpHeaderAuth
SSH(n8n internal)ssh
Critical: After PUT update, re-bind all 3 credentials in the n8n UI.

Architecture Changes (2026-03-28)

Phase 1 fetch nodes were rewired from parallel fan-out to sequential chain to eliminate duplication:
Before: Phase 1 → Tree    ─┐
         Phase 1 → Pipeline ─┼→ Assemble (ran 3x!)
         Phase 1 → README   ─┘

After:  Phase 1 → Tree → Pipeline → README → Assemble (runs 1x)
Pipeline and README URL expressions changed from {{ $json.repo }} to {{ $('📋 Merge & Set Defaults').first().json.repo }} since they no longer receive the config directly. Assemble Inspection Context code uses $() node references instead of $input.all():
const config = $('📋 Merge & Set Defaults').first().json;
const treeResponse = $('🌳 Fetch Repo Tree').first().json;
const pipelineResponse = $('📄 Fetch pipeline.yaml').first().json;
const readmeResponse = $('📖 Fetch README').first().json;

Credentials

NameIDType
GitHub (Dev4rno)tSg1ejqnd5r3clEggithubApi
Anthropic API KeyPdEKB19B1R0PQ4thhttpHeaderAuth (header: x-api-key)
SSHQF2ld9Q3UGLT5yUKssh
Critical: After PUT update, re-bind all 3 credentials in the n8n UI. The google_ai_key credential was reconfigured to use header name x-api-key for the Anthropic API.

Known Issues

  • No pipeline.yaml files exist in any repo — TOMMY always takes LLM-identify (Path B) or FORGE shortcut (Path C). Path A is dormant.
  • Timeout: FORGE triggers TOMMY with 600s timeout. Rover UI also uses 600s for tommy-mode.
  • 📄 Fetch pipeline.yaml must have On Error: Continue (Using Regular Output) — a 404 is expected when pipeline.yaml doesn’t exist.
  • 🚀 FORGE Shortcut? If node: condition uses {{ $json.forge_tasks.length }} > 0 (number comparison), NOT array “is not empty” — avoids strict type validation crash when forge_tasks is [].
  • 📋 Merge & Set Defaults: forge_tasks defaults to [] (empty array), not null — prevents type coercion errors downstream.
  • 🔍 Filter: Skip Completed: Code returns {json: ...} (bare object), not [{json: ...}] — required for runOnceForEachItem mode.
  • ⚛️ Atomise to Task-lets: Uses $('📊 Parse Tasksets').first()?.json || $('📄 Parse Existing Pipeline').first()?.json to bypass the empty Merge node output.
  • 🔄 Task-let Execution Loop: SplitInBatches v3 — batch size must be explicitly set to 1 in Options. Default empty options skip the loop body.
  • ❓ Pipeline YAML Exists?: has Execute Once enabled to prevent downstream duplication from Phase 1 multi-trigger.