Skip to content

Commit 314addf

Browse files
ZhiXiao-Linclaude
andcommitted
feat: v0.7.2 — parallel plan execution, lane queue integration, SDK enhancements
- Plan steps now execute in parallel waves based on dependency graph: steps with no unmet dependencies run concurrently via JoinSet, results merge back into shared history for dependent steps - Add lane queue integration: ToolCommand adapter, partition_by_lane() for parallel Query-lane tool execution within a single LLM turn - Expand SDK session options: skill_dirs, agent_dirs, queue_config, external task handling, DLQ access, and queue metrics - Add delegate-task skill and skills example - Remove redundant SDK example files (consolidated into core examples) - Bump all crate versions to 0.7.2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4e25c1c commit 314addf

22 files changed

Lines changed: 2934 additions & 927 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-code-core"
3-
version = "0.7.1"
3+
version = "0.7.2"
44
edition = "2021"
55
authors = ["A3S Lab Team"]
66
license = "MIT"

core/examples/sdk_chat.rs

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
//! - Non-streaming `session.send()` call
77
//! - Streaming `session.stream()` with event handling
88
//! - Model override via `SessionOptions`
9+
//! - Conversation history: auto-accumulated & explicit query
10+
//! - Multi-turn conversation reusing session history
11+
//! - Custom history override via `send(prompt, Some(&history))`
912
//!
1013
//! Requires network access and valid API keys in config.
1114
//!
@@ -22,7 +25,7 @@
2225
//! cd crates/code && cargo run --example sdk_chat -- "Explain Rust's ownership model in 3 sentences"
2326
//! ```
2427
25-
use a3s_code_core::{Agent, AgentEvent, SessionOptions};
28+
use a3s_code_core::{Agent, AgentEvent, ContentBlock, Message, SessionOptions};
2629
use std::path::PathBuf;
2730

2831
fn resolve_config() -> PathBuf {
@@ -62,7 +65,7 @@ async fn main() -> anyhow::Result<()> {
6265
let tmp = tempfile::tempdir()?;
6366
let session = agent.session(tmp.path().display().to_string(), None)?;
6467

65-
let result = session.send(&prompt).await?;
68+
let result = session.send(&prompt, None).await?;
6669
println!(" Response: {}", result.text.trim());
6770
println!(
6871
" Tokens: {} in / {} out",
@@ -74,7 +77,7 @@ async fn main() -> anyhow::Result<()> {
7477
let tmp2 = tempfile::tempdir()?;
7578
let session2 = agent.session(tmp2.path().display().to_string(), None)?;
7679

77-
let (mut rx, handle) = session2.stream(&prompt).await?;
80+
let (mut rx, handle) = session2.stream(&prompt, None).await?;
7881

7982
print!(" Response: ");
8083
let mut collected = String::new();
@@ -97,14 +100,87 @@ async fn main() -> anyhow::Result<()> {
97100
}
98101
handle.abort();
99102

100-
// ── 4. Model override session ────────────────────────────────────────
103+
// ── 4. History API — check auto-accumulated history ──────────────────
104+
println!("\n--- History API (auto-accumulated) ---");
105+
let history = session.history();
106+
println!(" Messages after send(): {}", history.len());
107+
for (i, msg) in history.iter().enumerate() {
108+
let preview = msg
109+
.content
110+
.iter()
111+
.filter_map(|b| match b {
112+
ContentBlock::Text { text } => {
113+
let trimmed = text.trim();
114+
if trimmed.len() > 60 {
115+
Some(format!("{}...", &trimmed[..60]))
116+
} else {
117+
Some(trimmed.to_string())
118+
}
119+
}
120+
ContentBlock::ToolUse { name, .. } => Some(format!("[tool_use: {}]", name)),
121+
ContentBlock::ToolResult { content, .. } => {
122+
Some(format!("[tool_result: {}]", &content[..content.len().min(40)]))
123+
}
124+
})
125+
.collect::<Vec<_>>()
126+
.join(" | ");
127+
println!(" [{}] {}: {}", i, msg.role, preview);
128+
}
129+
130+
// ── 5. Multi-turn conversation (auto-accumulated) ─────────────────────
131+
println!("\n--- Multi-turn conversation ---");
132+
let follow_up = "Now multiply that result by 10. Reply with just the number.";
133+
println!(" Follow-up: {}", follow_up);
134+
let result2 = session.send(follow_up, None).await?;
135+
println!(" Response: {}", result2.text.trim());
136+
println!(
137+
" Tokens: {} in / {} out",
138+
result2.usage.prompt_tokens, result2.usage.completion_tokens
139+
);
140+
141+
let history2 = session.history();
142+
println!(" Messages after 2 turns: {}", history2.len());
143+
144+
// ── 6. Custom history override ────────────────────────────────────────
145+
println!("\n--- Custom history override ---");
146+
let custom_history = vec![
147+
Message {
148+
role: "user".to_string(),
149+
content: vec![ContentBlock::Text {
150+
text: "Remember: the secret number is 42.".to_string(),
151+
}],
152+
reasoning_content: None,
153+
},
154+
Message {
155+
role: "assistant".to_string(),
156+
content: vec![ContentBlock::Text {
157+
text: "Got it, the secret number is 42.".to_string(),
158+
}],
159+
reasoning_content: None,
160+
},
161+
];
162+
let custom_prompt = "What is the secret number? Reply with just the number.";
163+
println!(" Custom history: {} messages injected", custom_history.len());
164+
println!(" Prompt: {}", custom_prompt);
165+
let result3 = session.send(custom_prompt, Some(&custom_history)).await?;
166+
println!(" Response: {}", result3.text.trim());
167+
168+
// Verify internal history was NOT modified by the custom-history call
169+
let history3 = session.history();
170+
println!(
171+
" Internal history unchanged: {} messages (same as before custom call: {})",
172+
history3.len(),
173+
history2.len()
174+
);
175+
176+
// ── 7. Model override session ─────────────────────────────────────────
101177
println!("\n--- Session with model override ---");
102178
let tmp3 = tempfile::tempdir()?;
103179
let opts = SessionOptions::new().with_model("anthropic/claude-sonnet-4-20250514");
104180
match agent.session(tmp3.path().display().to_string(), Some(opts)) {
105181
Ok(override_session) => {
106182
println!("[ok] Session with anthropic/claude-sonnet-4-20250514 created");
107-
match override_session.send(&prompt).await {
183+
match override_session.send(&prompt, None).await {
108184
Ok(result) => {
109185
println!(" Response: {}", result.text.trim());
110186
println!(
@@ -125,6 +201,21 @@ async fn main() -> anyhow::Result<()> {
125201
}
126202
}
127203

204+
// ── 8. Per-session skill_dirs / agent_dirs ────────────────────────────
205+
println!("\n--- Session with per-session skill_dirs ---");
206+
let tmp4 = tempfile::tempdir()?;
207+
let opts2 = SessionOptions::new()
208+
.with_skill_dir("/tmp/my-skills")
209+
.with_agent_dir("/tmp/my-agents");
210+
match agent.session(tmp4.path().display().to_string(), Some(opts2)) {
211+
Ok(_skill_session) => {
212+
println!("[ok] Session created with custom skill_dirs and agent_dirs");
213+
}
214+
Err(e) => {
215+
println!("[skip] Session with custom dirs failed: {}", e);
216+
}
217+
}
218+
128219
println!("\n=== Done ===");
129220
Ok(())
130221
}

core/examples/sdk_skills.rs

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
//! Skills SDK example — built-in skills, catalog injection, and delegate-task.
2+
//!
3+
//! Demonstrates:
4+
//! - Loading built-in skills (`builtin_skills()`)
5+
//! - Verifying delegate-task skill content and structure
6+
//! - Skill catalog injection (`build_skills_injection()`)
7+
//! - TaskTool / ParallelTaskTool descriptions include agent info
8+
//!
9+
//! No LLM calls — runs entirely offline.
10+
//!
11+
//! ## Usage
12+
//!
13+
//! ```bash
14+
//! cd crates/code && cargo run --example sdk_skills
15+
//! ```
16+
17+
use a3s_code_core::tools::{
18+
build_skills_injection, builtin_skills, Skill, SkillKind, DEFAULT_CATALOG_THRESHOLD,
19+
};
20+
21+
fn main() {
22+
println!("=== A3S Code SDK Skills Example ===\n");
23+
24+
// ── 1. Load built-in skills ──────────────────────────────────────────
25+
println!("--- Built-in Skills ---");
26+
let skills = builtin_skills();
27+
println!(" count: {}", skills.len());
28+
assert!(
29+
skills.len() >= 2,
30+
"expected at least 2 built-in skills, got {}",
31+
skills.len()
32+
);
33+
34+
for skill in &skills {
35+
println!(
36+
" - {} (kind={:?}, desc_len={}, content_len={})",
37+
skill.name,
38+
skill.kind,
39+
skill.description.len(),
40+
skill.content.len()
41+
);
42+
}
43+
println!();
44+
45+
// ── 2. Verify find-skills ────────────────────────────────────────────
46+
println!("--- find-skills ---");
47+
let find = skills.iter().find(|s| s.name == "find-skills");
48+
assert!(find.is_some(), "find-skills should be present");
49+
let find = find.unwrap();
50+
assert_eq!(find.kind, SkillKind::Instruction);
51+
assert!(find.content.contains("search_skills"));
52+
assert!(find.content.contains("install_skill"));
53+
println!(" [ok] name={}", find.name);
54+
println!(" [ok] kind=Instruction");
55+
println!(" [ok] content references search_skills, install_skill");
56+
println!();
57+
58+
// ── 3. Verify delegate-task ──────────────────────────────────────────
59+
println!("--- delegate-task ---");
60+
let delegate = skills.iter().find(|s| s.name == "delegate-task");
61+
assert!(delegate.is_some(), "delegate-task should be present");
62+
let delegate = delegate.unwrap();
63+
64+
// Kind
65+
assert_eq!(delegate.kind, SkillKind::Instruction);
66+
println!(" [ok] kind=Instruction");
67+
68+
// Description
69+
assert!(
70+
delegate.description.contains("sub-agent"),
71+
"description should mention sub-agents"
72+
);
73+
println!(
74+
" [ok] description: {}",
75+
&delegate.description[..delegate.description.len().min(80)]
76+
);
77+
78+
// Content: built-in agents
79+
assert!(delegate.content.contains("explore"), "should reference explore");
80+
assert!(delegate.content.contains("general"), "should reference general");
81+
assert!(delegate.content.contains("plan"), "should reference plan");
82+
println!(" [ok] content references agents: explore, general, plan");
83+
84+
// Content: custom agents
85+
assert!(
86+
delegate.content.contains("agent_dirs"),
87+
"should mention agent_dirs"
88+
);
89+
println!(" [ok] content mentions agent_dirs for custom agents");
90+
91+
// Content: parallel_task
92+
assert!(
93+
delegate.content.contains("parallel_task"),
94+
"should reference parallel_task"
95+
);
96+
println!(" [ok] content references parallel_task tool");
97+
98+
// Content: best practices
99+
assert!(
100+
delegate.content.contains("Best Practices"),
101+
"should have best practices section"
102+
);
103+
println!(" [ok] content includes Best Practices section");
104+
println!();
105+
106+
// ── 4. Skill catalog injection (full mode) ───────────────────────────
107+
println!("--- Skill Catalog Injection (full mode) ---");
108+
let full_injection = build_skills_injection(&skills, DEFAULT_CATALOG_THRESHOLD);
109+
assert!(
110+
!full_injection.is_empty(),
111+
"injection should not be empty with {} skills",
112+
skills.len()
113+
);
114+
115+
// 2 skills <= threshold 3 → full mode
116+
assert!(
117+
full_injection.contains("<skills>"),
118+
"should use full mode (<skills> tag)"
119+
);
120+
assert!(
121+
full_injection.contains("delegate-task"),
122+
"injection should include delegate-task"
123+
);
124+
assert!(
125+
full_injection.contains("find-skills"),
126+
"injection should include find-skills"
127+
);
128+
println!(" threshold: {}", DEFAULT_CATALOG_THRESHOLD);
129+
println!(" instruction skills: {}", skills.len());
130+
println!(" [ok] full mode: <skills> tag present");
131+
println!(" [ok] delegate-task included in injection");
132+
println!(" [ok] find-skills included in injection");
133+
println!();
134+
135+
// ── 5. Skill catalog injection (catalog mode) ────────────────────────
136+
println!("--- Skill Catalog Injection (catalog mode, threshold=1) ---");
137+
let catalog_injection = build_skills_injection(&skills, 1);
138+
assert!(
139+
catalog_injection.contains("<skill-catalog>"),
140+
"should use catalog mode with threshold=1"
141+
);
142+
assert!(
143+
catalog_injection.contains("delegate-task"),
144+
"catalog should list delegate-task"
145+
);
146+
assert!(
147+
catalog_injection.contains("load_skill"),
148+
"catalog should reference load_skill"
149+
);
150+
println!(" threshold: 1");
151+
println!(" [ok] catalog mode: <skill-catalog> tag present");
152+
println!(" [ok] delegate-task listed in catalog");
153+
println!(" [ok] catalog references load_skill for on-demand loading");
154+
println!();
155+
156+
// ── 6. Custom skills alongside builtins ──────────────────────────────
157+
println!("--- Custom Skills + Builtins ---");
158+
let mut combined = skills.clone();
159+
combined.push(Skill {
160+
name: "custom-deploy".to_string(),
161+
description: "Deploy to production".to_string(),
162+
allowed_tools: Some("Bash(*)".to_string()),
163+
disable_model_invocation: false,
164+
kind: SkillKind::Instruction,
165+
content: "Custom deployment instructions.".to_string(),
166+
});
167+
combined.push(Skill {
168+
name: "custom-review".to_string(),
169+
description: "Code review assistant".to_string(),
170+
allowed_tools: None,
171+
disable_model_invocation: false,
172+
kind: SkillKind::Instruction,
173+
content: "Custom code review instructions.".to_string(),
174+
});
175+
176+
let combined_injection = build_skills_injection(&combined, DEFAULT_CATALOG_THRESHOLD);
177+
178+
// 4 skills > threshold 3 → catalog mode
179+
assert!(
180+
combined_injection.contains("<skill-catalog>"),
181+
"4 skills should trigger catalog mode"
182+
);
183+
assert!(combined_injection.contains("delegate-task"));
184+
assert!(combined_injection.contains("find-skills"));
185+
assert!(combined_injection.contains("custom-deploy"));
186+
assert!(combined_injection.contains("custom-review"));
187+
println!(" total instruction skills: {}", combined.len());
188+
println!(" [ok] catalog mode triggered (count > threshold)");
189+
println!(" [ok] all 4 skills listed in catalog");
190+
println!();
191+
192+
// ── 7. Tool-kind skills don't affect injection ───────────────────────
193+
println!("--- Tool-kind Skills Excluded ---");
194+
let mut with_tool = skills.clone();
195+
with_tool.push(Skill {
196+
name: "my-tool".to_string(),
197+
description: "A tool skill".to_string(),
198+
allowed_tools: None,
199+
disable_model_invocation: false,
200+
kind: SkillKind::Tool,
201+
content: "Tool content".to_string(),
202+
});
203+
204+
let tool_injection = build_skills_injection(&with_tool, DEFAULT_CATALOG_THRESHOLD);
205+
// 2 instruction + 1 tool → still 2 instruction → full mode
206+
assert!(
207+
tool_injection.contains("<skills>"),
208+
"tool-kind should not count toward threshold"
209+
);
210+
assert!(
211+
!tool_injection.contains("my-tool"),
212+
"tool-kind should not appear in injection"
213+
);
214+
println!(" [ok] Tool-kind skills excluded from injection");
215+
println!(" [ok] Tool-kind does not affect threshold count");
216+
println!();
217+
218+
// ── Done ─────────────────────────────────────────────────────────────
219+
println!("=== All checks passed ===");
220+
}

0 commit comments

Comments
 (0)