Autonomous session orchestrator for Claude Code or OpenAI Codex. Python spawns the configured provider CLI in a loop; the child agent reads session state, executes phase-specific work, signals next step.
main.py # Orchestrator entry point - main loop
workflow.md # Master prompt template for provider iterations
worker/ # Core package (~1,600 lines)
config.py # Configuration from .samocode + .env
phases.py # Phase enum, config registry, transition validation
runner.py # Provider CLI execution with retry
signal_history.py # Signal history tracking for debugging
signals.py # Signal file I/O (continue/done/blocked/waiting)
timestamps.py # Centralized timestamp formatting
logging.py # Rotating file + console logging
notifications.py # Telegram notifications
agents/ # Phase-specific agent instructions (md files)
skills/ # Claude/Codex skills
commands/ # Standalone Claude slash commands
tests/ # pytest suite - one file per worker module
- Python 3.10+ (uses
|union syntax) - Dependencies: python-dotenv, requests (for Telegram)
- Testing: pytest
- Linting: ruff, pyright
pytest tests/ # Run all tests
pytest tests/test_runner.py # Run specific test file
ruff check . # Lint
ruff format . # Format
pyright # Type check
python main.py --help # Run orchestrator- Strict typing - no
anytypes, use|for unions - Main functions at top, utilities below
- Frozen dataclasses for config/data structures
- Enums for status values (ExecutionStatus, SignalStatus, Phase)
- Global imports at file top, no dynamic imports
- Section comments (
# ===) for large module organization - Short, context-independent comments
Three layers: Parent CLI -> Worker (Python) -> Child provider instances
Phase flow: init -> investigation -> requirements -> planning -> implementation -> testing -> quality -> done
Signal protocol - child provider writes _signal.json to control flow:
continue- Next iterationdone- Workflow completeblocked- Needs human interventionwaiting- Paused for human input (Q&A or plan approval)
Stateless iterations - Each provider invocation reads _overview.md fresh, executes one action, signals, exits.
- One test file per worker module (test_config.py, test_runner.py, etc.)
- Fixtures in conftest.py
- Test real functionality, not mocks
- All tests must pass
CLI arguments:
python main.py --config ~/project/.samocode --session my-task--config(required) - Full path to.samocodefile--session(required) - Session name (not path)
.samocode file (per-project, all required):
MAIN_REPO=~/project
WORKTREES=~/project/worktrees/
SESSIONS=~/project/_sessions/
Environment variables (in .env) - runtime settings only:
SAMOCODE_PROVIDER-claudeorcodex(default: claude)CLAUDE_PATH,CLAUDE_MODEL- Claude CLI settingsCODEX_PATH,CODEX_MODEL- Codex CLI settingsTELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_ID- Optional notifications
worker/phases.py- Phase enum, PhaseConfig registry, transition/signal validation (source of truth)worker/runner.py- Core execution logic, provider CLI invocationworker/config.py- ProjectConfig, RuntimeConfig, SamocodeConfig dataclassesworker/signals.py- Signal dataclass, JSON parsingworker/signal_history.py- Records signals to_signal_history.jsonlfor debuggingworkflow.md- Master prompt injected into each provider runTECH_DEBT.md- Known architectural issues
- When rewriting git history with
filter-repo(orfilter-branch), stash or commit uncommitted working-tree changes first — the rewrite ends withgit reset --hardfollowed bygit gc, which destroys uncommitted work irrecoverably from git - When working-tree edits made by Claude Code are lost, scan
~/.claude/projects/**/*.jsonlforEdit/Write/Readtool calls on the affected paths and replay chronologically from the latest Read snapshot — Claude session logs are a non-git backup of recent file states - After
filter-repofinishes it removes theoriginremote by design; re-add it, thengit fetch originto rebuild remote-tracking refs before any--force-with-leasepush - Pin repo-local git identity (
git config --local user.email …) when the repo's intended author differs from your global config — worktrees inherit local config automatically - Never accept secrets pasted into a chat as a working approach — Claude transcripts persist; treat any pasted token as compromised and rotate it immediately, then guide the user to env vars or a credentials file for future runs
- When a session uses a worktree, start the orchestrator from the worktree path (or pass the worktree as working dir) — otherwise commits land on the main-repo branch instead of the session branch and the PR ends up split between two locations