phalanx-cli 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/PKG-INFO +1 -1
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/README.md +41 -53
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/__init__.py +1 -1
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/cli.py +3 -1
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/team/create.py +38 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx_cli.egg-info/PKG-INFO +1 -1
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/pyproject.toml +1 -1
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/__main__.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/artifacts/__init__.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/artifacts/reader.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/artifacts/schema.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/artifacts/writer.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/backends/__init__.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/backends/base.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/backends/claude.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/backends/codex.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/backends/cursor.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/backends/gemini.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/backends/model_router.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/backends/registry.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/comms/__init__.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/comms/file_lock.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/comms/messaging.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/config.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/db.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/defaults/config.toml +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/init_cmd.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/monitor/__init__.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/monitor/gc.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/monitor/heartbeat.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/monitor/lifecycle.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/monitor/stall.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/process/__init__.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/process/manager.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/process/pool.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/process/worktree.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/soul/__init__.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/soul/loader.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/soul/skill_body.md +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/soul/team_lead.md +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/soul/worker.md +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/team/__init__.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/team/orchestrator.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx/team/spawn.py +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx_cli.egg-info/SOURCES.txt +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx_cli.egg-info/dependency_links.txt +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx_cli.egg-info/entry_points.txt +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx_cli.egg-info/requires.txt +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/phalanx_cli.egg-info/top_level.txt +0 -0
- {phalanx_cli-0.2.0 → phalanx_cli-0.2.2}/setup.cfg +0 -0
|
@@ -16,12 +16,16 @@ Requires: Python 3.11+, tmux.
|
|
|
16
16
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
|
+
Initialize Phalanx in your workspace:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
phalanx init
|
|
23
|
+
```
|
|
24
|
+
|
|
19
25
|
Start an agent session and talk to it:
|
|
20
26
|
|
|
21
27
|
```bash
|
|
22
|
-
phalanx
|
|
23
|
-
phalanx -b gemini # use a specific backend
|
|
24
|
-
phalanx run "fix the failing tests" # with an initial prompt
|
|
28
|
+
phalanx run-agent "fix the failing tests"
|
|
25
29
|
```
|
|
26
30
|
|
|
27
31
|
Then just ask your agent to create teams:
|
|
@@ -79,12 +83,12 @@ phalanx models reset # restore defaults
|
|
|
79
83
|
|
|
80
84
|
## Architecture
|
|
81
85
|
|
|
82
|
-
- **State**: SQLite (WAL mode) at
|
|
83
|
-
- **Process
|
|
84
|
-
- **
|
|
85
|
-
- **
|
|
86
|
-
- **Stall
|
|
87
|
-
- **GC**: Opportunistic,
|
|
86
|
+
- **State**: SQLite (WAL mode) at `.phalanx/state.db`
|
|
87
|
+
- **TUI Process Isolation**: Agents run interactively inside background `tmux` sessions. Output is captured via `pipe-pane`, meaning Phalanx can screen-scrape and deterministically handle CLI prompts (like workspace trust or tool approval) without relying on fragile prompt engineering.
|
|
88
|
+
- **Real-Time Communication**: Because agents are running live in `tmux`, Phalanx can send them keystrokes or interrupt them (`Ctrl+C`) to deliver messages instantly, instead of relying on slow, costly session restarts via `--resume`.
|
|
89
|
+
- **Artifacts**: Ephemeral JSON storing structured outputs per agent.
|
|
90
|
+
- **Stall Detection**: `stream.log` (from `tmux`) is monitored via a Heartbeat system. If an agent hangs or crashes, Phalanx detects the lack of output, interrupts, and prompts the agent to continue or fail gracefully.
|
|
91
|
+
- **GC**: Opportunistic cleanup of dead teams, running on standard CLI commands.
|
|
88
92
|
|
|
89
93
|
## CLI Reference
|
|
90
94
|
|
|
@@ -92,72 +96,56 @@ phalanx models reset # restore defaults
|
|
|
92
96
|
|
|
93
97
|
| Command | Description |
|
|
94
98
|
|---------|-------------|
|
|
95
|
-
| `phalanx` |
|
|
96
|
-
| `phalanx run "
|
|
97
|
-
| `phalanx
|
|
98
|
-
| `phalanx
|
|
99
|
-
| `phalanx team-
|
|
100
|
-
| `phalanx
|
|
101
|
-
| `phalanx
|
|
102
|
-
| `phalanx
|
|
103
|
-
| `phalanx
|
|
104
|
-
| `phalanx
|
|
105
|
-
| `phalanx
|
|
99
|
+
| `phalanx init` | Initialize `.phalanx` in workspace, generate skill files |
|
|
100
|
+
| `phalanx run-agent "task"` | Spawn a single interactive agent (no team lead) |
|
|
101
|
+
| `phalanx create-team "task"` | Create a team and start its team lead |
|
|
102
|
+
| `phalanx monitor <id>` | Attach a blocking DEM-style monitoring loop |
|
|
103
|
+
| `phalanx team-status [id]` | View team status summary |
|
|
104
|
+
| `phalanx agent-status [id]` | View specific agent status |
|
|
105
|
+
| `phalanx team-result <id>` | Read team lead's final artifact |
|
|
106
|
+
| `phalanx message <id> "msg"` | Message a team lead |
|
|
107
|
+
| `phalanx message-agent <id> "msg"` | Message a specific agent |
|
|
108
|
+
| `phalanx send-keys <id> <keys>` | Send raw keystrokes to an agent's `tmux` pane |
|
|
109
|
+
| `phalanx stop <id>` | Stop a team (kills processes) |
|
|
110
|
+
| `phalanx status` | List all running agents across all teams |
|
|
111
|
+
| `phalanx gc` | Clean up old data and dead teams |
|
|
106
112
|
|
|
107
113
|
### Agent Tools (used by spawned agents)
|
|
108
114
|
|
|
109
115
|
| Command | Description |
|
|
110
116
|
|---------|-------------|
|
|
111
|
-
| `phalanx
|
|
112
|
-
| `phalanx
|
|
117
|
+
| `phalanx spawn-agent` | Spawn a sub-agent (used by team lead) |
|
|
118
|
+
| `phalanx write-artifact` | Write structured result (success/failure/escalation) |
|
|
113
119
|
| `phalanx agent-result <id>` | Read peer artifact |
|
|
114
|
-
| `phalanx
|
|
115
|
-
| `phalanx message-agent <id> "msg"` | Message a specific worker |
|
|
116
|
-
| `phalanx lock/unlock <path>` | File locking |
|
|
120
|
+
| `phalanx lock/unlock <path>` | Advisory file locking to prevent collisions |
|
|
117
121
|
|
|
118
122
|
## Develop Locally
|
|
119
123
|
|
|
120
124
|
```bash
|
|
121
125
|
git clone https://github.com/creynir/phalanx.git
|
|
122
126
|
cd phalanx
|
|
123
|
-
|
|
127
|
+
uv sync
|
|
128
|
+
uv pip install -e ".[dev]"
|
|
124
129
|
```
|
|
125
130
|
|
|
126
131
|
Run tests:
|
|
127
132
|
|
|
128
133
|
```bash
|
|
129
|
-
pytest tests/
|
|
130
|
-
pytest tests/integration/ # integration tests (requires tmux)
|
|
131
|
-
pytest tests/e2e/ # end-to-end tests
|
|
132
|
-
pytest tests/ # all 211 tests
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Pre-commit hooks (ruff lint + format) are configured — install with:
|
|
136
|
-
|
|
137
|
-
```bash
|
|
138
|
-
pip install pre-commit
|
|
139
|
-
pre-commit install
|
|
134
|
+
uv run pytest tests/
|
|
140
135
|
```
|
|
141
136
|
|
|
142
137
|
## Configuration
|
|
143
138
|
|
|
144
|
-
|
|
145
|
-
Workspace override: `.phalanx/config.toml`
|
|
146
|
-
|
|
147
|
-
```toml
|
|
148
|
-
[defaults]
|
|
149
|
-
backend = "cursor"
|
|
150
|
-
|
|
151
|
-
[timeouts]
|
|
152
|
-
agent_inactivity_minutes = 30
|
|
153
|
-
team_gc_hours = 24
|
|
154
|
-
stall_seconds = 180
|
|
139
|
+
Workspace overrides can be edited at `.phalanx/config.json`:
|
|
155
140
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"default_backend": "cursor",
|
|
144
|
+
"idle_timeout_seconds": 1800,
|
|
145
|
+
"max_runtime_seconds": 1800,
|
|
146
|
+
"stall_check_interval": 20,
|
|
147
|
+
"auto_approve": true
|
|
148
|
+
}
|
|
161
149
|
```
|
|
162
150
|
|
|
163
151
|
## License
|
|
@@ -181,11 +181,12 @@ def run_agent(ctx, task, backend, model, auto_approve):
|
|
|
181
181
|
|
|
182
182
|
@cli.command("create-team")
|
|
183
183
|
@click.argument("task")
|
|
184
|
+
@click.option("--agents", "-a", default="coder", help="Agent spec: role[:count],...")
|
|
184
185
|
@click.option("--backend", "-b", default=None, help="Backend")
|
|
185
186
|
@click.option("--model", "-m", default=None, help="Model")
|
|
186
187
|
@click.option("--auto-approve/--no-auto-approve", default=True)
|
|
187
188
|
@click.pass_context
|
|
188
|
-
def create_team_cmd(ctx, task, backend, model, auto_approve):
|
|
189
|
+
def create_team_cmd(ctx, task, agents, backend, model, auto_approve):
|
|
189
190
|
"""Create a team and start its team lead."""
|
|
190
191
|
from phalanx.monitor.heartbeat import HeartbeatMonitor
|
|
191
192
|
from phalanx.process.manager import ProcessManager
|
|
@@ -204,6 +205,7 @@ def create_team_cmd(ctx, task, backend, model, auto_approve):
|
|
|
204
205
|
process_manager=pm,
|
|
205
206
|
heartbeat_monitor=hb,
|
|
206
207
|
task=task,
|
|
208
|
+
agents_spec=agents,
|
|
207
209
|
backend_name=backend or config.default_backend,
|
|
208
210
|
model=model or config.default_model,
|
|
209
211
|
auto_approve=auto_approve,
|
|
@@ -15,12 +15,28 @@ from phalanx.team.spawn import spawn_agent
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def parse_agents_spec(spec: str) -> list[tuple[str, int]]:
|
|
19
|
+
"""Parse agent spec like 'researcher,coder:2,reviewer' into [(role, count)]."""
|
|
20
|
+
agents = []
|
|
21
|
+
if not spec:
|
|
22
|
+
return agents
|
|
23
|
+
for part in spec.split(","):
|
|
24
|
+
part = part.strip()
|
|
25
|
+
if ":" in part:
|
|
26
|
+
role, count_str = part.split(":", 1)
|
|
27
|
+
agents.append((role.strip(), int(count_str)))
|
|
28
|
+
else:
|
|
29
|
+
agents.append((part, 1))
|
|
30
|
+
return agents
|
|
31
|
+
|
|
32
|
+
|
|
18
33
|
def create_team(
|
|
19
34
|
phalanx_root: Path,
|
|
20
35
|
db: StateDB,
|
|
21
36
|
process_manager: ProcessManager,
|
|
22
37
|
heartbeat_monitor: HeartbeatMonitor,
|
|
23
38
|
task: str,
|
|
39
|
+
agents_spec: str = "coder",
|
|
24
40
|
backend_name: str = "cursor",
|
|
25
41
|
model: str | None = None,
|
|
26
42
|
auto_approve: bool = True,
|
|
@@ -43,6 +59,28 @@ def create_team(
|
|
|
43
59
|
team_dir = phalanx_root / "teams" / team_id
|
|
44
60
|
team_dir.mkdir(parents=True, exist_ok=True)
|
|
45
61
|
|
|
62
|
+
# Spawn workers first
|
|
63
|
+
worker_specs = parse_agents_spec(agents_spec)
|
|
64
|
+
worker_index = 0
|
|
65
|
+
for role, count in worker_specs:
|
|
66
|
+
for _ in range(count):
|
|
67
|
+
worker_id = f"w{worker_index}-{role}"
|
|
68
|
+
spawn_agent(
|
|
69
|
+
phalanx_root=phalanx_root,
|
|
70
|
+
db=db,
|
|
71
|
+
process_manager=process_manager,
|
|
72
|
+
heartbeat_monitor=heartbeat_monitor,
|
|
73
|
+
team_id=team_id,
|
|
74
|
+
task=task,
|
|
75
|
+
role=role,
|
|
76
|
+
agent_id=worker_id,
|
|
77
|
+
backend_name=backend_name,
|
|
78
|
+
model=None, # let router decide based on role
|
|
79
|
+
auto_approve=auto_approve,
|
|
80
|
+
config=config,
|
|
81
|
+
)
|
|
82
|
+
worker_index += 1
|
|
83
|
+
|
|
46
84
|
# Spawn team lead
|
|
47
85
|
spawn_agent(
|
|
48
86
|
phalanx_root=phalanx_root,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|