Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions .claude/templates/coding_prompt.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
You are continuing work on a long-running autonomous development task.
This is a FRESH context window - you have no memory of previous sessions.

### PARALLEL AGENT MODE

Check if you're running as a parallel agent:

```bash
echo "AGENT_ID: $AGENT_ID"
```

**If AGENT_ID is set (e.g., "agent-1", "agent-2"):**
- You are one of multiple agents working in parallel on this project
- You MUST use `feature_claim_next` instead of `feature_get_next` to avoid conflicts
- Other agents are working in separate git worktrees on the same codebase
- The features database is shared - atomic claiming prevents race conditions

### STEP 1: GET YOUR BEARINGS (MANDATORY)

Start by orienting yourself:
Expand Down Expand Up @@ -96,6 +110,17 @@ Features are **test cases** that drive development. This is test-driven developm

Get the next feature to implement:

**If running in PARALLEL MODE (AGENT_ID is set):**

```
# Atomically claim the next feature (prevents race conditions with other agents)
Use the feature_claim_next tool with agent_id=$AGENT_ID
```
Comment on lines +115 to +118
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language specifier to code block for consistency.

The fenced code block is missing a language identifier, which reduces syntax highlighting and readability.

📝 Proposed fix
-```
+```text
 # Atomically claim the next feature (prevents race conditions with other agents)
 Use the feature_claim_next tool with agent_id=$AGENT_ID

Or use `bash` if these are meant to be shell commands, though "Use the feature_claim_next tool" suggests these are instructions rather than executable commands.

</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.18.1)</summary>

115-115: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

In @.claude/templates/coding_prompt.template.md around lines 115 - 118, The
fenced code block that starts with the line "Atomically claim the next feature
(prevents race conditions with other agents)" lacks a language specifier; update
its opening triple-backtick to include a language (e.g., change totext,
or to ```bash if you intend it as a shell command) so the code block becomes
fenced with a language identifier for proper syntax highlighting and consistency
in the template.


</details>

<!-- fingerprinting:phantom:poseidon:puma -->

<!-- This is an auto-generated comment by CodeRabbit -->


This single call gets AND claims the feature atomically - no need to call `feature_mark_in_progress` separately.

**If running in SINGLE AGENT MODE (AGENT_ID is not set):**

```
# Get the highest-priority pending feature
Use the feature_get_next tool
Expand Down Expand Up @@ -273,7 +298,7 @@ Use the feature_mark_passing tool with feature_id=42

**ONLY MARK A FEATURE AS PASSING AFTER VERIFICATION WITH SCREENSHOTS.**

### STEP 8: COMMIT YOUR PROGRESS
### STEP 8: COMMIT AND MERGE YOUR PROGRESS

Make a descriptive git commit:

Expand All @@ -284,10 +309,41 @@ git commit -m "Implement [feature name] - verified end-to-end
- Added [specific changes]
- Tested with browser automation
- Marked feature #X as passing
- Screenshots in verification/ directory
"
```

**If running in PARALLEL MODE (AGENT_ID is set), merge your changes immediately:**

After each completed feature, integrate your changes so other agents can benefit.

**IMPORTANT:** You're in a git worktree, so you cannot checkout the main branch directly.
Use this worktree-safe approach:

```bash
# 1. Determine the main branch name
MAIN_BRANCH=$(cd "$(git rev-parse --git-common-dir)/.." && git branch --show-current 2>/dev/null || echo "main")

# 2. Pull latest main into your branch (get others' changes first)
git fetch origin $MAIN_BRANCH 2>/dev/null || true
git merge origin/$MAIN_BRANCH --no-edit 2>/dev/null || git merge FETCH_HEAD --no-edit 2>/dev/null || true

# 3. Push your branch to main (worktree-safe merge)
git push . HEAD:$MAIN_BRANCH
```

The `git push . HEAD:main` command merges your current branch into main without needing to checkout main.

**If merge conflicts occur:**
- On step 2 (pulling main): Resolve conflicts, then `git add . && git commit -m "Merge main"`
- On step 3 (pushing to main): This means main has changes not in your branch
- Run step 2 again to get those changes, then retry step 3
- If complex conflicts: Skip merging for now, continue working. Use UI "Merge" button later.

**Why merge after each feature?**
- Other agents immediately get your improvements
- Prevents large conflict pile-ups at the end
- Each agent stays closer to the "true" state of the project

### STEP 9: UPDATE PROGRESS NOTES

Update `claude-progress.txt` with:
Expand Down Expand Up @@ -369,9 +425,13 @@ The feature tools exist to reduce token usage. **DO NOT make exploratory queries
feature_get_stats

# 2. Get the NEXT feature to work on (one feature only)
# - SINGLE AGENT MODE: Use feature_get_next
# - PARALLEL MODE: Use feature_claim_next with agent_id=$AGENT_ID (atomic claim)
feature_get_next
feature_claim_next with agent_id={agent_id} # PARALLEL MODE ONLY

# 3. Mark a feature as in-progress (call immediately after feature_get_next)
# NOTE: Skip this if you used feature_claim_next (it claims automatically)
feature_mark_in_progress with feature_id={id}

# 4. Get up to 3 random passing features for regression testing
Expand All @@ -385,6 +445,9 @@ feature_skip with feature_id={id}

# 7. Clear in-progress status (when abandoning a feature)
feature_clear_in_progress with feature_id={id}

# 8. Release a feature back to the queue (PARALLEL MODE - if you can't complete it)
feature_release with feature_id={id} agent_id={agent_id}
```

### RULES:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Desktop.ini
ui/dist/
ui/.vite/
.vite/
*.tsbuildinfo

# ===================
# Environment files
Expand Down
36 changes: 36 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,42 @@ python autonomous_agent_demo.py --project-dir my-app --yolo

**When to use:** Early prototyping when you want to quickly scaffold features without verification overhead. Switch back to standard mode for production-quality development.

### Parallel Agents Mode

Run multiple agents simultaneously to speed up project completion:

```bash
# CLI: Run 3 agents in parallel
python autonomous_agent_demo.py --project-dir my-app --num-agents 3

# Combine with YOLO mode for fastest iteration
python autonomous_agent_demo.py --project-dir my-app --num-agents 3 --yolo

# API: Start parallel agents via REST
POST /api/projects/{project_name}/parallel-agents/start
Body: { "num_agents": 3, "yolo_mode": false }
```

**How it works:**
- Each agent gets its own **git worktree** for code isolation
- All agents share the same **features.db** for task coordination
- Features are **atomically claimed** to prevent conflicts
- Agent assignment is tracked via `assigned_agent_id` on features

**API Endpoints:**
- `GET /api/projects/{name}/parallel-agents/status` - Get status of all agents
- `POST /api/projects/{name}/parallel-agents/start` - Start N agents
- `POST /api/projects/{name}/parallel-agents/stop` - Stop all agents
- `POST /api/projects/{name}/parallel-agents/merge` - Merge worktree changes
- `POST /api/projects/{name}/parallel-agents/cleanup` - Stop and cleanup

**MCP Tools for Parallel Agents:**
- `feature_claim_next(agent_id)` - Atomically claim next available feature
- `feature_release(feature_id, agent_id)` - Release feature back to queue
- `feature_get_next(agent_id)` - Get next feature (excludes others' assignments)

**When to use:** When you have many independent features and want to parallelize implementation. Best combined with YOLO mode for maximum speed.

### React UI (in ui/ directory)

```bash
Expand Down
25 changes: 17 additions & 8 deletions agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ async def run_autonomous_agent(
model: str,
max_iterations: Optional[int] = None,
yolo_mode: bool = False,
agent_id: Optional[str] = None,
) -> None:
"""
Run the autonomous agent loop.
Expand All @@ -120,20 +121,28 @@ async def run_autonomous_agent(
model: Claude model to use
max_iterations: Maximum number of iterations (None for unlimited)
yolo_mode: If True, skip browser testing and use YOLO prompt
agent_id: Optional agent identifier for parallel execution
"""
prefix = f"[{agent_id}] " if agent_id else ""

print("\n" + "=" * 70)
print(" AUTONOMOUS CODING AGENT DEMO")
if agent_id:
print(f" AUTONOMOUS CODING AGENT - {agent_id}")
else:
print(" AUTONOMOUS CODING AGENT DEMO")
print("=" * 70)
print(f"\nProject directory: {project_dir}")
print(f"Model: {model}")
print(f"\n{prefix}Project directory: {project_dir}")
print(f"{prefix}Model: {model}")
if agent_id:
print(f"{prefix}Agent ID: {agent_id}")
if yolo_mode:
print("Mode: YOLO (testing disabled)")
print(f"{prefix}Mode: YOLO (testing disabled)")
else:
print("Mode: Standard (full testing)")
print(f"{prefix}Mode: Standard (full testing)")
if max_iterations:
print(f"Max iterations: {max_iterations}")
print(f"{prefix}Max iterations: {max_iterations}")
else:
print("Max iterations: Unlimited (will run until completion)")
print(f"{prefix}Max iterations: Unlimited (will run until completion)")
print()

# Create project directory
Expand Down Expand Up @@ -175,7 +184,7 @@ async def run_autonomous_agent(
print_session_header(iteration, is_first_run)

# Create client (fresh context)
client = create_client(project_dir, model, yolo_mode=yolo_mode)
client = create_client(project_dir, model, yolo_mode=yolo_mode, agent_id=agent_id)

# Choose prompt based on session type
# Pass project_dir to enable project-specific prompts
Expand Down
20 changes: 19 additions & 1 deletion api/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Feature(Base):
steps = Column(JSON, nullable=False) # Stored as JSON array
passes = Column(Boolean, default=False, index=True)
in_progress = Column(Boolean, default=False, index=True)
assigned_agent_id = Column(String(50), nullable=True, index=True) # Agent working on this feature

def to_dict(self) -> dict:
"""Convert feature to dictionary for JSON serialization."""
Expand All @@ -41,6 +42,7 @@ def to_dict(self) -> dict:
"steps": self.steps,
"passes": self.passes,
"in_progress": self.in_progress,
"assigned_agent_id": self.assigned_agent_id,
}


Expand Down Expand Up @@ -73,6 +75,21 @@ def _migrate_add_in_progress_column(engine) -> None:
conn.commit()


def _migrate_add_assigned_agent_id_column(engine) -> None:
"""Add assigned_agent_id column to existing databases that don't have it."""
from sqlalchemy import text

with engine.connect() as conn:
# Check if column exists
result = conn.execute(text("PRAGMA table_info(features)"))
columns = [row[1] for row in result.fetchall()]

if "assigned_agent_id" not in columns:
# Add the column (nullable, no default)
conn.execute(text("ALTER TABLE features ADD COLUMN assigned_agent_id VARCHAR(50)"))
conn.commit()


def create_database(project_dir: Path) -> tuple:
"""
Create database and return engine + session maker.
Expand All @@ -87,8 +104,9 @@ def create_database(project_dir: Path) -> tuple:
engine = create_engine(db_url, connect_args={"check_same_thread": False})
Base.metadata.create_all(bind=engine)

# Migrate existing databases to add in_progress column
# Migrate existing databases to add new columns
_migrate_add_in_progress_column(engine)
_migrate_add_assigned_agent_id_column(engine)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
return engine, SessionLocal
Expand Down
110 changes: 95 additions & 15 deletions autonomous_agent_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

# YOLO mode: rapid prototyping without browser testing
python autonomous_agent_demo.py --project-dir my-app --yolo

# Parallel agents: run multiple agents simultaneously
python autonomous_agent_demo.py --project-dir my-app --num-agents 3
"""

import argparse
Expand Down Expand Up @@ -95,6 +98,13 @@ def parse_args() -> argparse.Namespace:
help="Enable YOLO mode: rapid prototyping without browser testing",
)

parser.add_argument(
"--num-agents",
type=int,
default=1,
help="Number of parallel agents to run (default: 1, max: 10)",
)

return parser.parse_args()


Expand Down Expand Up @@ -126,22 +136,92 @@ def main() -> None:
print("Use an absolute path or register the project first.")
return

try:
# Run the agent (MCP server handles feature database)
asyncio.run(
run_autonomous_agent(
project_dir=project_dir,
model=args.model,
max_iterations=args.max_iterations,
yolo_mode=args.yolo,
)
# Check if parallel mode requested
num_agents = min(args.num_agents, 10) # Cap at 10 agents

if num_agents > 1:
# Parallel agent mode
from parallel_agents import ParallelAgentOrchestrator

print(f"\n{'=' * 70}")
print(f" PARALLEL AGENT MODE - {num_agents} AGENTS")
print("=" * 70)
print(f"\nProject directory: {project_dir}")
print(f"Model: {args.model}")
print(f"Number of agents: {num_agents}")
if args.yolo:
print("Mode: YOLO (testing disabled)")
print()

root_dir = Path(__file__).parent
orchestrator = ParallelAgentOrchestrator(
project_dir=project_dir,
root_dir=root_dir,
max_agents=num_agents,
)
except KeyboardInterrupt:
print("\n\nInterrupted by user")
print("To resume, run the same command again")
except Exception as e:
print(f"\nFatal error: {e}")
raise

async def run_parallel():
"""Run multiple agents in parallel and wait for completion."""
try:
results = await orchestrator.start_agents(
num_agents=num_agents,
yolo_mode=args.yolo,
model=args.model,
max_iterations=args.max_iterations,
)
print(f"\nStarted agents: {results}")

# Wait for all agents to complete (or user interrupt)
while True:
health = await orchestrator.healthcheck()
running = sum(1 for v in health.values() if v)
if running == 0:
# Distinguish between completion and crashes
statuses = orchestrator.get_all_statuses()
crashed = [s["agent_id"] for s in statuses if s["status"] == "crashed"]
stopped = [s["agent_id"] for s in statuses if s["status"] == "stopped"]

print("\nAll agents have finished.")
if crashed:
print(f" Crashed: {', '.join(crashed)}")
if stopped:
print(f" Completed: {', '.join(stopped)}")
break
await asyncio.sleep(5)

except KeyboardInterrupt:
print("\n\nInterrupted - stopping all agents...")
await orchestrator.stop_all_agents()
finally:
# Optional: merge worktree changes
print("\nCleaning up worktrees...")
await orchestrator.cleanup()

try:
asyncio.run(run_parallel())
except KeyboardInterrupt:
print("\n\nInterrupted by user")
except Exception as e:
print(f"\nFatal error: {e}")
raise
else:
# Single agent mode (original behavior)
try:
# Run the agent (MCP server handles feature database)
asyncio.run(
run_autonomous_agent(
project_dir=project_dir,
model=args.model,
max_iterations=args.max_iterations,
yolo_mode=args.yolo,
)
)
except KeyboardInterrupt:
print("\n\nInterrupted by user")
print("To resume, run the same command again")
except Exception as e:
print(f"\nFatal error: {e}")
raise


if __name__ == "__main__":
Expand Down
Loading