UPDATE THIS FILE when making architectural changes, adding patterns, or changing conventions.
Message-passing primitives for multi-agent workflows. Install: npm i -g @the-open-engine/zeroshot or npm link (dev).
| Rule | Why | Forbidden | Required |
|---|---|---|---|
| GENERAL PURPOSE ONLY | Zeroshot runs on ANY repo | Hardcoded paths, scripts, languages, domains | Discover from target repo's CLAUDE.md/README |
| Never spawn without permission | Consumes API credits | "I'll run zeroshot on 123" | User says "run zeroshot" |
| Never use git in validators | Git state unreliable | git diff, git status in prompts |
Validate files directly |
| Never ask questions | Agents run non-interactively | AskUserQuestion, waiting for confirmation |
Make autonomous decisions |
| Never edit CLAUDE.md | Context file for Claude Code | Editing this file | Read-only unless explicitly asked to update docs |
Zeroshot is a GENERAL-PURPOSE multi-agent orchestrator. It MUST work on ANY repository, ANY programming language, ANY domain.
FORBIDDEN in templates/prompts:
- Hardcoded script names (
check-all.sh,validate.sh) - Hardcoded test commands (
npm test,pytest,cargo test) - Hardcoded file paths (
server/,src/,tests/) - Hardcoded context file names (
CLAUDE.md- other providers use different files) - Language-specific assumptions (TypeScript, Python, Rust)
- Domain-specific assumptions (web, CLI, mobile)
- Provider-specific assumptions (Claude, Codex, Gemini)
- Covibes-specific patterns
REQUIRED:
- Discover validation commands from target repo's context files (README, Makefile, package.json, pyproject.toml, Cargo.toml, etc.)
- Discover test runners from target repo's build system
- Use generic examples in prompts (e.g., "the repo's validation script" NOT "./scripts/check-all.sh")
- Use generic terms for context files ("repo context files" NOT "CLAUDE.md")
- Work correctly on: Python projects, Rust crates, Go modules, Ruby gems, Java/Kotlin, C/C++, etc.
- Work correctly with: Claude, Codex, Gemini, OpenAI, and any future providers
Worker git operations: Allowed with isolation (--worktree, --docker, --pr, --ship). Forbidden without isolation (auto-injected restriction).
Read-only safe: zeroshot list, zeroshot status, zeroshot logs
Destructive (needs permission): zeroshot kill, zeroshot clear, zeroshot purge
Detached runs: Always forward zeroshot run options via ZEROSHOT_RUN_OPTIONS (see buildDaemonEnv + buildStartOptions) so PR/worktree config survives daemon mode.
WHEN USER POSTS LOGS → THERE IS A BUG. INVESTIGATE.
WHEN TESTS FAIL → Test is source of truth unless PROVEN otherwise.
TEST BEHAVIOR, NOT IMPLEMENTATION. TESTS FIND BUGS, NOT PASS.
READ THE STACK TRACE. FIX ROOT CAUSE, NOT SYMPTOM.
FAIL FAST. Silent failures are worst. Errors > Warnings.
VERIFY ASSUMPTIONS. Don't assume - check.
BUILD WHAT WAS ASKED. Not what you think should be built.
DON'T OVERENGINEER. No abstractions before they're needed.
DON'T REINVENT. Read existing code before writing new.
DON'T SWALLOW ERRORS. Try/catch that ignores = hidden bugs.
IS THIS HOW A SENIOR STAFF ARCHITECT WOULD DO IT? ACT LIKE ONE.
| Concept | File |
|---|---|
| Conductor classification | src/conductor-bootstrap.js |
| Base templates | cluster-templates/base-templates/ |
| Message bus | src/message-bus.js |
| Ledger (SQLite) | src/ledger.js |
| Trigger evaluation | src/logic-engine.js |
| Agent wrapper | src/agent-wrapper.js |
| Docker mounts/env | lib/docker-config.js |
| Container lifecycle | src/isolation-manager.js |
| Issue providers | src/issue-providers/ |
| Git remote detection | lib/git-remote-utils.js |
| Input helpers | src/input-helpers.js |
| Settings | lib/settings.js |
# Flag cascade: --ship → --pr → --worktree
zeroshot run 123 # Local, no isolation
zeroshot run 123 --worktree # Git worktree isolation
zeroshot run 123 --pr # Worktree + create PR
zeroshot run 123 --pr --pr-base dev # PR base: dev, worktree base: origin/dev (incl. -d)
zeroshot run 123 --ship # Worktree + PR + auto-merge
zeroshot run 123 --docker # Docker container isolation
zeroshot run 123 -d # Background (daemon) mode
# Management
zeroshot list # All clusters (--json)
zeroshot status <id> # Cluster details
zeroshot logs <id> [-f] # Stream logs
zeroshot resume <id> [prompt] # Resume failed cluster
zeroshot stop <id> # Graceful stop
zeroshot kill <id> # Force kill
# Utilities
zeroshot export <id> # Export conversation
zeroshot agents list # Available agents
zeroshot settings # View/modify settingsThe TUI is not included in this release. Use zeroshot list, zeroshot status <id>,
and zeroshot logs <id> -f or zeroshot logs <id> -w for monitoring.
UX modes:
- Foreground (
zeroshot run): Streams logs, Ctrl+C stops cluster - Daemon (
-d): Background, Ctrl+C detaches - Attach (
zeroshot attach): Connect to daemon, Ctrl+C detaches only
Settings: maxModel (opus/sonnet/haiku cost ceiling), defaultConfig, logLevel
Git Auto-Detection: Bare numbers (e.g., 123) automatically detect provider from git remote URL. No configuration needed when working in a git repository.
Priority order for bare numbers:
- Force flags (
--github,--gitlab,--devops) - Explicit CLI override - Git remote detection - Automatic from
git remote get-url origin - Settings (
defaultIssueSource) - Global user preference - Legacy fallback - GitHub (only when no git context and no settings)
Pub/sub message bus + SQLite ledger. Agents subscribe to topics, execute on trigger match, publish results.
Agent A → publish() → SQLite Ledger → LogicEngine → trigger match → Agent B executes
| Primitive | Purpose |
|---|---|
| Topic | Named message channel (ISSUE_OPENED, VALIDATION_RESULT) |
| Trigger | Condition to wake agent ({ topic, action, logic }) |
| Logic Script | JS predicate for complex conditions |
| Hook | Post-task action (publish message, execute command) |
{
"id": "worker",
"role": "implementation",
"model": "sonnet",
"triggers": [{ "topic": "ISSUE_OPENED", "action": "execute_task" }],
"prompt": "Implement the requested feature...",
"hooks": {
"onComplete": {
"action": "publish_message",
"config": { "topic": "IMPLEMENTATION_READY" }
}
}
}// Ledger (auto-scoped to cluster)
ledger.query({ topic, sender, since, limit });
ledger.findLast({ topic });
ledger.count({ topic });
// Cluster
cluster.getAgents();
cluster.getAgentsByRole('validator');
// Helpers
helpers.allResponded(agents, topic, since);
helpers.hasConsensus(topic, since);Classifies tasks on Complexity × TaskType, routes to parameterized templates.
| Complexity | Description | Validators |
|---|---|---|
| TRIVIAL | 1 file, mechanical | 0 |
| SIMPLE | 1 concern | 1 |
| STANDARD | Multi-file | 3 |
| CRITICAL | Auth/payments/security | 5 |
| TaskType | Action |
|---|---|
| INQUIRY | Read-only exploration |
| TASK | Implement new feature |
| DEBUG | Fix broken code |
Base templates: single-worker, worker-validator, debug-workflow, full-workflow
| Mode | Flag | Use When |
|---|---|---|
| Worktree | --worktree |
Quick isolated work, PR workflows |
| Docker | --docker |
Full isolation, risky experiments, parallel agents |
Worktree: Lightweight git branch isolation (<1s setup).
Docker: Fresh git clone in container, credentials mounted, auto-cleanup.
Configurable credential mounts for --docker mode. See lib/docker-config.js.
| Setting | Type | Default | Description |
|---|---|---|---|
dockerMounts |
Array<string|object> |
['gh','git','ssh'] |
Presets or {host, container, readonly} |
dockerEnvPassthrough |
string[] |
[] |
Extra env vars (supports VAR, VAR_*, VAR=value) |
dockerContainerHome |
string |
/root |
Container home for $HOME expansion |
Mount presets: gh, git, ssh, aws, azure, kube, terraform, gcloud
Env var syntax:
VAR→ pass if set in host envVAR_*→ pass all matching (e.g.,TF_VAR_*)VAR=value→ always set to valueVAR=→ always set to empty string
Config priority: CLI flags > ZEROSHOT_DOCKER_MOUNTS env > settings > defaults
# Persistent config
zeroshot settings set dockerMounts '["gh","git","ssh","aws"]'
# Per-run override
zeroshot run 123 --docker --mount ~/.custom:/root/.custom:ro
# Disable all mounts
zeroshot run 123 --docker --no-mountsCore principle: Tests passing ≠ implementation works. The ONLY verification is: USE IT YOURSELF.
- Read issue → understand requirements
- Look at code → figure out how to invoke
- Run it → did it work?
- Try to break it → edge cases
- Verify each requirement → evidence (command + output)
| File | Content |
|---|---|
~/.zeroshot/clusters.json |
Cluster metadata |
~/.zeroshot/<id>.db |
SQLite message ledger |
Clusters survive crashes. Resume: zeroshot resume <id>
Bash subprocess output not streamed: Claude CLI returns tool_result after subprocess completes. Long scripts show no output until done.
Bug: --ship mode created worktree but template agents (planning, implementation, validator) ran in main directory instead, polluting it with uncommitted changes.
Root cause: _opAddAgents() didn't inject cluster's worktree cwd into dynamically spawned template agents. Initial agents got cwd via startCluster(), but template agents loaded later via conductor classification missed it.
Fix: Added cwd injection to _opAddAgents() and resume path in orchestrator.js. Test: tests/worktree-cwd-injection.test.js
ENFORCE > DOCUMENT. If enforceable, don't document.
| Preference | Method |
|---|---|
| Best | Type system (compile-time) |
| Good | ESLint rule (build-time) |
| Okay | Pre-commit hook, runtime guard |
| Worst | Documentation (hopes someone reads) |
The error message IS the documentation. Write error messages with what + fix:
FORBIDDEN: Direct spawn without isolation - use --worktree or --docker flag
Document ONLY when: Cannot be enforced (architecture decisions, design patterns).
When Claude discovers an enforceable pattern: ASK before adding rule.
# ❌ FORBIDDEN - Consumes API credits without user consent
agent: "I'll run zeroshot on issue #123"
zeroshot run 123
# ✅ CORRECT - Wait for explicit permission
agent: "Would you like me to run zeroshot on issue #123?"
# Wait for user to say "yes" or "run zeroshot"WHY THIS MATTERS: Multi-agent runs can consume significant API credits. User must explicitly consent.
# ❌ FORBIDDEN - Git state unreliable, changes during validation
validator_prompt: "Run git diff to verify changes..."
validator_prompt: "Check git status to ensure clean state..."
# ✅ CORRECT - Validate files directly
validator_prompt: "Read src/index.js and verify function exists..."
validator_prompt: "Run the CLI command and verify output matches spec..."WHY THIS FAILS: Multiple agents modify git state concurrently. Validator reads stale state.
// ❌ FORBIDDEN - Agents run non-interactively
await AskUserQuestion('Should I use approach A or B?');
// Agent waits forever, cluster stuck
// ✅ CORRECT - Make autonomous decision with reasoning
// Decision: Using approach A because requirement specifies XWHY THIS FAILS: Zeroshot agents don't have interactive input. Make decisions autonomously.
# ❌ FORBIDDEN - Pollutes main working directory
zeroshot run 123 # Worker commits directly to main branch
# ✅ CORRECT - Use isolation flags
zeroshot run 123 --worktree # Isolated git worktree
zeroshot run 123 --pr # Worktree + create PR
zeroshot run 123 --ship # Worktree + PR + auto-merge
zeroshot run 123 --docker # Full container isolationWHY THIS MATTERS: Prevents contamination of main working directory, enables parallel work.
# ❌ FORBIDDEN - Stashed work invisible to other agents
git stash
git stash save "WIP changes"
git stash pop
# ✅ CORRECT - WIP commits (visible, recoverable)
git add -A && git commit -m "WIP: feature implementation"
git switch other-branch
# Later: git reset --soft HEAD~1 # Unstage if neededWHY WIP COMMITS BETTER: Visible to other agents, never lost, can be amended, squashable before merge.
// ❌ FORBIDDEN - Hardcoded values in cluster templates
const maxValidators = 3; // What if task needs 5?
// ✅ CORRECT - Parameterized from conductor classification
const maxValidators = cluster.config.complexity === 'CRITICAL' ? 5 : 3;WHY THIS MATTERS: Conductor dynamically adjusts based on task complexity.
CI blocks PRs to main from any branch except dev. See .github/workflows/ci.yml → enforce-main-pr-source job.
Development branch ownership:
devis the integration branch for normal development work.- Feature branches merge into
dev, notmain. mainis release-only. Promotedevtomainonly when you intentionally want a release.- If you are "just shipping a fix" during development, ship it to
dev.
# ❌ CI WILL BLOCK - PRs to main from feature branches
gh pr create --base main --head fix/my-feature # FAILS in CI
# ✅ CORRECT - Always go through dev first
gh pr create --base dev --head fix/my-feature # PR to dev
# After merge to dev:
gh pr create --base main --head dev --title "Release" # dev → main (allowed)POSTMORTEM (2026-01-16): Agent found merge conflicts between dev and main. Instead of resolving conflicts properly (merge main into dev), created a feature branch directly from main and merged fixes to main. This bypassed dev, created divergence, and left dev without the fixes.
FIX: Added CI enforcement (enforce-main-pr-source job). Now mechanically impossible to merge non-dev branches to main.
POSTMORTEM RULE: Treat main as the release branch, not the development branch. If you are choosing a destination branch for active work, the answer is dev.
Merge queue enforces CI on rebased code before merge.
feature-branch (local)
↓
pre-push hook → lint + typecheck (~5s)
↓
push to origin/feature-branch
↓
gh pr create --base dev
↓
CI runs tests on PR branch
↓
gh pr merge --auto --squash → enters merge queue
↓
Queue rebases PR on latest dev + runs CI again
↓
Merge to dev (only if CI passes on rebased code)
Pre-push hook blocks: Direct pushes to main or dev. Must use PR workflow.
Branch intent:
dev: active development / integrationmain: release only- Feature branch → PR to
dev dev→ PR tomainonly for release promotion
Commands:
# Feature work
git switch -c feat/my-feature
# ... make changes ...
git push -u origin feat/my-feature
gh pr create --base dev
gh pr merge --auto --squash
# Release (dev → main)
gh pr create --base main --head dev --title "Release"
# → CI passes → merge → semantic-release publishesSetup merge queue (admin): ./scripts/setup-merge-queue.sh
CRITICAL: Use WIP commits instead of stashing:
git add -A && git commit -m "WIP: save work" # Instead of git stash
git switch <branch> # Instead of git checkout <branch>
git restore <file> # Instead of git checkout -- <file>
git restore --staged <file> # Unstage without discardingRationale: Stashing hides work from other agents. WIP commits are visible, traceable, and never lost.
ALWAYS write tests BEFORE or WITH code changes:
# 1. Create feature file
touch src/new-feature.js
# 2. Create test file FIRST
touch tests/new-feature.test.js
# 3. Write failing tests (TDD)
# 4. Implement feature until tests pass
# 5. Commit both togetherPre-commit hook validates test exists → Commit allowed only if test file present.
When to run manual validation:
| Scenario | Run Validation? | Why |
|---|---|---|
| Trivial changes (<50 lines) | ❌ NO | Pre-commit hook catches issues |
| Reading/exploring code | ❌ NO | No code changes |
| Documentation changes | ❌ NO | No runtime errors possible |
| Significant changes (>50 lines) | ✅ YES | Fast feedback before commit |
| Refactoring across files | ✅ YES | Catch breaking changes early |
| User explicitly requests | ✅ YES | "run tests", "check lint" |
Trust pre-commit hooks for quick checks. Run full suite for major changes.
npm run lint # ESLint
npm run test # Jest tests
npm run typecheck # TypeScript (if applicable)When multiple CI jobs fail, DO NOT assume single root cause.
WRONG: Pick one job → assume it fixes all → Rerun → Still fails RIGHT: Diagnose each job independently → Fix one → Rerun → Repeat
Procedure:
-
Get exact status:
gh api repos/the-open-engine/zeroshot/actions/runs/{RUN_ID}/jobs \ --jq '.jobs[] | "\(.name): \(.status) (\(.conclusion // "pending"))"' -
For EACH failing job, read ACTUAL error:
# ✅ CORRECT - Uses API, works for completed jobs gh api repos/the-open-engine/zeroshot/actions/jobs/{JOB_ID}/logs 2>&1 | grep -E "FAIL|Error" # ❌ WRONG - Waits for ENTIRE run to complete gh run view {RUN_ID} --log
-
Fix ONE error → Commit → Push → Rerun → Repeat
Common multi-failure scenarios:
| Failing | Likely Causes |
|---|---|
| lint + test | Lint error may block tests (different root causes) |
| test-unit + test-integration | Independent issues, fix separately |
| build + test | Build issue OR test setup, diagnose both |
Scope: Narrowest possible. Module-specific → nested CLAUDE.md. Cross-cutting → root.
Content Priority:
- 🔴 CRITICAL gotchas (project-specific, non-obvious, caused real bugs)
- "Where to Look" routing tables
- Anti-patterns with WHY (learned from real failures)
- Commands/env vars/troubleshooting tables
DELETE:
- Tutorial content (LLMs know JavaScript/Node.js/CLI patterns)
- Directory trees (use ls/find)
- Interface definitions (read actual code)
- Anything duplicated from parent CLAUDE.md
Format:
- Tables over prose
ALWAYS/NEVER/CRITICALfor rules (caps + context)- Code examples: ❌ wrong + ✅ correct + WHY
| Antipattern | Enforcement |
|---|---|
| Dangerous fallbacks | ESLint ERROR |
| Manual git tags | Pre-push hook |
| Direct push to main/dev | Pre-push hook (blocks with instructions) |
| Git in validator prompts | Config validator |
| Multiple impl files (-v2) | Pre-commit hook |
| Spawn without permission | Runtime check (CLI) |
| Git stash usage | Pre-commit hook (planned) |
| Merge without CI rebase | GitHub merge queue |
| Pattern | Why |
|---|---|
| ALWAYS await async functions | Missing await = silent failure, unhandled Promise |
| NEVER swallow Promise rejections | Unhandled rejection = process crash in Node 15+ |
| Handle Promise.all failures | One rejection = entire Promise.all rejects |
// ❌ WRONG - Missing await
async function process() {
doAsyncThing(); // Returns immediately, error lost
}
// ✅ CORRECT
async function process() {
await doAsyncThing();
}
// ❌ WRONG - Swallowed rejection
try {
await riskyOperation();
} catch (e) {
// Silent - bug hidden
}
// ✅ CORRECT
try {
await riskyOperation();
} catch (e) {
logger.error('Operation failed', { error: e });
throw e; // Re-throw or handle explicitly
}| Pattern | Why |
|---|---|
| Clean up child processes | Orphaned processes = resource leaks, port conflicts |
| Handle SIGTERM/SIGINT | Users will Ctrl+C. Handle gracefully. |
| Exit codes matter | 0 = success, non-zero = failure. Scripts depend on this. |
// ✅ CORRECT - Signal handling
process.on('SIGTERM', async () => {
await cleanup();
process.exit(0);
});
process.on('SIGINT', async () => {
await cleanup();
process.exit(0);
});
// ✅ CORRECT - Child process cleanup
const child = spawn('command');
process.on('exit', () => child.kill());| Pattern | Why |
|---|---|
| No global mutable state | Agents run in parallel. Globals = race conditions. |
| Never block on user input | Agents are non-interactive. Blocking = stuck forever. |
| Mistake | Why It's Wrong |
|---|---|
| Overengineering | No abstraction layers before they're needed |
| Copy-paste coding | If duplicating, you should be abstracting |
| Gold plating | No features nobody asked for |
| Premature optimization | Measure first, optimize second |
| Reinventing | Read existing code before writing new |
| Leaving edge cases | Incomplete solutions are not solutions |
| Assuming it works | Test it. Verify it. Prove it. |
| Catch-and-ignore | Try/catch that swallows = hidden bugs |