Skip to content

Commit 78610b5

Browse files
ZhiXiao-Linclaude
andcommitted
docs: add parallel plan execution examples and wave scheduler documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent dc63846 commit 78610b5

1 file changed

Lines changed: 144 additions & 5 deletions

File tree

README.md

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
- **8 Lifecycle Hooks** — Pre/post events for tool calls, sessions, messages, and errors
3838
- **Security** — 5 layers: sanitizer, taint tracking, interceptor, injection detection, audit logging
3939
- **Memory** — 4 types: episodic, semantic, procedural, working memory
40-
- **JSON-Structured Planning** — Execution plans and goal tracking via LlmPlanner
41-
- **Parallel Plan Execution** — Independent plan steps execute concurrently via wave-based dependency graph scheduling
40+
- **JSON-Structured Planning** — Execution plans with dependency graphs and goal tracking via LlmPlanner
41+
- **Parallel Plan Execution** — Independent plan steps execute concurrently via wave-based dependency graph scheduling (`tokio::JoinSet`)
4242
- **Lane Queue** — Priority-based tool routing with parallel reads (Query lane) and external task offloading
4343
- **Context Compaction** — Auto-summarize long conversations (80% threshold)
4444
- **Context Store** — Persistent context storage (feature-gated: `context-store`)
@@ -276,6 +276,145 @@ providers {
276276

277277
> **Note:** `skill_dirs` and `agent_dirs` can be set in both `CodeConfig` (agent-level defaults) and `SessionOptions` (per-session overrides, merged with agent-level). `queue_config` is session-level only.
278278
279+
## Parallel Plan Execution
280+
281+
When planning is enabled, A3S Code decomposes complex tasks into steps with a dependency graph and executes independent steps **in parallel**. No configuration beyond `planning_enabled = true` is needed — the scheduler automatically groups independent steps into waves and spawns them concurrently via `tokio::JoinSet`.
282+
283+
### How It Works
284+
285+
```
286+
ExecutionPlan:
287+
step 1: Analyze auth module (no deps)
288+
step 2: Analyze database schema (no deps)
289+
step 3: Implement JWT integration (depends on 1, 2)
290+
step 4: Write tests (depends on 3)
291+
292+
Execution:
293+
Wave 1: [step 1, step 2] ← parallel (independent)
294+
Wave 2: [step 3] ← waits for wave 1
295+
Wave 3: [step 4] ← waits for wave 2
296+
```
297+
298+
Each wave:
299+
1. `get_ready_steps()` finds steps whose dependencies are all `Completed`
300+
2. **Single step** → executes sequentially, preserving the full history chain
301+
3. **Multiple steps** → spawns all into a `JoinSet`, each with a clone of the base history
302+
4. After the wave completes, results are merged into shared history for subsequent steps
303+
5. Failed steps are detected — dependent steps become unreachable (deadlock detection breaks the loop)
304+
305+
### Rust
306+
307+
```rust
308+
use a3s_code_core::{Agent, AgentConfig, AgentEvent, SessionOptions};
309+
310+
let agent = Agent::new("agent.hcl").await?;
311+
let session = agent.session("/my-project", Some(
312+
SessionOptions::new()
313+
.with_planning(true) // enable plan decomposition
314+
.with_goal_tracking(true) // track progress against success criteria
315+
))?;
316+
317+
// Stream with parallel step execution events
318+
let (mut rx, _handle) = session.stream("Refactor auth to use JWT and update all tests").await?;
319+
while let Some(event) = rx.recv().await {
320+
match event {
321+
AgentEvent::StepStart { step_id, description, step_number, total_steps } => {
322+
println!("[{step_number}/{total_steps}] Starting: {description}");
323+
}
324+
AgentEvent::StepEnd { step_id, status, step_number, total_steps } => {
325+
println!("[{step_number}/{total_steps}] {status}");
326+
}
327+
AgentEvent::GoalProgress { goal, progress, completed_steps, total_steps } => {
328+
println!("Progress: {:.0}% ({completed_steps}/{total_steps})", progress * 100.0);
329+
}
330+
AgentEvent::TextDelta { text } => print!("{text}"),
331+
AgentEvent::End { .. } => break,
332+
_ => {}
333+
}
334+
}
335+
```
336+
337+
### TypeScript
338+
339+
```typescript
340+
const { Agent } = require('@a3s-lab/code');
341+
342+
const agent = await Agent.create('agent.hcl');
343+
const session = agent.session('/my-project', {
344+
planning: true,
345+
goalTracking: true,
346+
});
347+
348+
const events = await session.stream('Refactor auth to use JWT and update all tests');
349+
for (const event of events) {
350+
switch (event.type) {
351+
case 'step_start':
352+
console.log(`[${event.stepNumber}/${event.totalSteps}] Starting: ${event.description}`);
353+
break;
354+
case 'step_end':
355+
console.log(`[${event.stepNumber}/${event.totalSteps}] ${event.status}`);
356+
break;
357+
case 'goal_progress':
358+
console.log(`Progress: ${(event.progress * 100).toFixed(0)}%`);
359+
break;
360+
case 'text_delta':
361+
process.stdout.write(event.text);
362+
break;
363+
}
364+
}
365+
```
366+
367+
### Python
368+
369+
```python
370+
from a3s_code import Agent
371+
372+
agent = Agent.create("agent.hcl")
373+
session = agent.session("/my-project", planning=True, goal_tracking=True)
374+
375+
for event in session.stream("Refactor auth to use JWT and update all tests"):
376+
if event.event_type == "step_start":
377+
print(f"[{event.step_number}/{event.total_steps}] Starting: {event.description}")
378+
elif event.event_type == "step_end":
379+
print(f"[{event.step_number}/{event.total_steps}] {event.status}")
380+
elif event.event_type == "goal_progress":
381+
print(f"Progress: {event.progress:.0%} ({event.completed_steps}/{event.total_steps})")
382+
elif event.event_type == "text_delta":
383+
print(event.text, end="", flush=True)
384+
```
385+
386+
### Dependency Graph API
387+
388+
```rust
389+
use a3s_code_core::planning::{ExecutionPlan, Task, TaskStatus, Complexity};
390+
391+
let mut plan = ExecutionPlan::new("Refactor auth", Complexity::Complex);
392+
393+
// Independent steps — will run in parallel (Wave 1)
394+
plan.add_step(Task::new("s1", "Analyze auth module"));
395+
plan.add_step(Task::new("s2", "Analyze database schema"));
396+
397+
// Dependent step — waits for Wave 1 (Wave 2)
398+
plan.add_step(
399+
Task::new("s3", "Implement JWT")
400+
.with_dependencies(vec!["s1".to_string(), "s2".to_string()])
401+
);
402+
403+
// Wave 1: s1, s2 are ready
404+
assert_eq!(plan.get_ready_steps().len(), 2);
405+
406+
// After completing wave 1
407+
plan.mark_status("s1", TaskStatus::Completed);
408+
plan.mark_status("s2", TaskStatus::Completed);
409+
410+
// Wave 2: s3 is now ready
411+
assert_eq!(plan.get_ready_steps().len(), 1);
412+
assert_eq!(plan.get_ready_steps()[0].id, "s3");
413+
414+
// Deadlock detection
415+
assert!(!plan.has_deadlock());
416+
```
417+
279418
## Architecture
280419

281420
```
@@ -300,8 +439,8 @@ providers {
300439
│ │ │ Llm │ Security │ Memory │ File │ │ │
301440
│ │ │ Planner │ │ │ History │ │ │
302441
│ │ ├─────────┼──────────┼──────────┼─────────┤ │ │
303-
│ │ │ Context │ Cost CronSession │ │ │
304-
│ │ │Compactor│ Tracking │Scheduler │ Store │ │ │
442+
│ │ │ Wave │ ContextCostCron │ │ │
443+
│ │ │Scheduler│Compactor │ Tracking │Scheduler│ │ │
305444
│ │ └─────────┴──────────┴──────────┴─────────┘ │ │
306445
│ └──────────────────────────────────────────────┘ │
307446
└──────────────────────────────────────────────────────┘
@@ -377,7 +516,7 @@ code/
377516
│ ├── hooks/ # HookEngine (8 lifecycle events)
378517
│ ├── security/ # Sanitizer, taint tracking, injection detection, audit
379518
│ ├── memory.rs # Episodic, semantic, procedural, working memory
380-
│ ├── planning/ # LlmPlanner, execution plans, goal tracking
519+
│ ├── planning/ # LlmPlanner, execution plans, wave scheduler, goal tracking
381520
│ ├── context.rs # Context compaction
382521
│ ├── context_store/ # Persistent context store (feature-gated)
383522
│ ├── mcp/ # Model Context Protocol integration

0 commit comments

Comments
 (0)