Skip to content

Commit 775b4ab

Browse files
committed
feat: add prompt slots examples for Rust/Python/Node.js, fix SDK session() prompt slots wiring, bump to v0.9.0
1 parent f62fcd8 commit 775b4ab

10 files changed

Lines changed: 348 additions & 9 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-code-cli"
3-
version = "0.8.0"
3+
version = "0.9.0"
44
edition = "2021"
55
authors = ["A3S Lab Team"]
66
license = "MIT"
@@ -12,7 +12,7 @@ name = "a3s-code"
1212
path = "src/main.rs"
1313

1414
[dependencies]
15-
a3s-code-core = { version = "0.8", path = "../core" }
15+
a3s-code-core = { version = "0.9", path = "../core" }
1616
tokio = { version = "1.35", features = ["rt-multi-thread", "macros", "signal"] }
1717
clap = { version = "4.4", features = ["derive"] }
1818
serde_json = "1.0"

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.8.0"
3+
version = "0.9.0"
44
edition = "2021"
55
authors = ["A3S Lab Team"]
66
license = "MIT"

core/examples/test_prompt_slots.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! # System Prompt Slots — Customizing agent personality without overriding core behavior
2+
//!
3+
//! Demonstrates the slot-based system prompt customization API:
4+
//! 1. Create a session with custom role, guidelines, and response style
5+
//! 2. Send a prompt and verify the agent responds according to the custom persona
6+
//! 3. Show that core agentic capabilities (tool use, etc.) are preserved
7+
//!
8+
//! ```bash
9+
//! cd crates/code
10+
//! cargo run --example test_prompt_slots
11+
//! ```
12+
13+
use a3s_code_core::{Agent, SessionOptions, SystemPromptSlots};
14+
use std::path::PathBuf;
15+
16+
fn find_config() -> PathBuf {
17+
if let Ok(p) = std::env::var("A3S_CONFIG") {
18+
return PathBuf::from(p);
19+
}
20+
let home = dirs::home_dir().expect("no home dir");
21+
let home_cfg = home.join(".a3s/config.hcl");
22+
if home_cfg.exists() {
23+
return home_cfg;
24+
}
25+
let project_cfg = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../.a3s/config.hcl");
26+
if project_cfg.exists() {
27+
return project_cfg;
28+
}
29+
panic!("Config not found. Create ~/.a3s/config.hcl or set A3S_CONFIG");
30+
}
31+
32+
#[tokio::main]
33+
async fn main() -> anyhow::Result<()> {
34+
tracing_subscriber::fmt()
35+
.with_env_filter("a3s_code_core=info")
36+
.init();
37+
38+
let config = find_config();
39+
println!("Config: {}", config.display());
40+
41+
let agent = Agent::new(config.to_str().unwrap()).await?;
42+
println!("Agent created ✓\n");
43+
44+
let workspace = tempfile::tempdir()?;
45+
46+
// --- Test 1: Custom role only ---
47+
println!("═══ Test 1: Custom role ═══");
48+
let opts = SessionOptions::new().with_prompt_slots(SystemPromptSlots {
49+
role: Some("You are a senior Rust developer who specializes in async programming.".into()),
50+
..Default::default()
51+
});
52+
let session = agent.session(workspace.path().to_str().unwrap(), Some(opts))?;
53+
let result = session
54+
.send("What is your area of expertise? Reply in one sentence.", None)
55+
.await?;
56+
println!("Response: {}\n", result.text.trim());
57+
assert!(!result.text.is_empty(), "should get a response");
58+
59+
// --- Test 2: Role + guidelines + response style ---
60+
println!("═══ Test 2: Role + guidelines + response style ═══");
61+
let opts = SessionOptions::new().with_prompt_slots(SystemPromptSlots {
62+
role: Some("You are a Python code reviewer.".into()),
63+
guidelines: Some("Always check for type hints. Flag any use of `eval()`.".into()),
64+
response_style: Some("Reply in bullet points. Be concise.".into()),
65+
..Default::default()
66+
});
67+
let session = agent.session(workspace.path().to_str().unwrap(), Some(opts))?;
68+
69+
// Write a Python file for the agent to review
70+
std::fs::write(
71+
workspace.path().join("app.py"),
72+
r#"
73+
def add(a, b):
74+
return eval(f"{a} + {b}")
75+
76+
def greet(name):
77+
return "Hello " + name
78+
"#,
79+
)?;
80+
81+
let result = session
82+
.send(
83+
"Review the file app.py and list any issues you find.",
84+
None,
85+
)
86+
.await?;
87+
println!("Response:\n{}\n", result.text.trim());
88+
assert!(result.tool_calls_count > 0, "should have used read_file tool");
89+
90+
// --- Test 3: Extra instructions only (backward compat style) ---
91+
println!("═══ Test 3: Extra instructions ═══");
92+
let opts = SessionOptions::new().with_prompt_slots(SystemPromptSlots {
93+
extra: Some("Always end your response with '— A3S'".into()),
94+
..Default::default()
95+
});
96+
let session = agent.session(workspace.path().to_str().unwrap(), Some(opts))?;
97+
let result = session
98+
.send("Say hello.", None)
99+
.await?;
100+
println!("Response: {}\n", result.text.trim());
101+
102+
// --- Test 4: Verify tools still work (core behavior preserved) ---
103+
println!("═══ Test 4: Core tool behavior preserved ═══");
104+
let opts = SessionOptions::new().with_prompt_slots(SystemPromptSlots {
105+
role: Some("You are a minimalist file manager.".into()),
106+
guidelines: Some("Only create files when explicitly asked.".into()),
107+
..Default::default()
108+
});
109+
let session = agent.session(workspace.path().to_str().unwrap(), Some(opts))?;
110+
let result = session
111+
.send(
112+
"Create a file called test.txt with the content 'prompt slots work'. Then read it back.",
113+
None,
114+
)
115+
.await?;
116+
println!("Response: {}\n", result.text.trim());
117+
assert!(result.tool_calls_count > 0, "should have used tools");
118+
let test_file = workspace.path().join("test.txt");
119+
assert!(test_file.exists(), "test.txt should exist");
120+
121+
println!("═══ All prompt slots tests passed ✓ ═══");
122+
Ok(())
123+
}

sdk/node/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-code-node"
3-
version = "0.8.0"
3+
version = "0.9.0"
44
edition = "2021"
55
authors = ["A3S Lab Team"]
66
license = "MIT"
@@ -11,7 +11,7 @@ description = "A3S Code Node.js bindings - Native addon via napi-rs"
1111
crate-type = ["cdylib"]
1212

1313
[dependencies]
14-
a3s-code-core = { version = "0.8", path = "../../core" }
14+
a3s-code-core = { version = "0.9", path = "../../core" }
1515
napi = { version = "2", features = ["async", "napi6", "serde-json"] }
1616
napi-derive = "2"
1717
tokio = { version = "1.35", features = ["full"] }
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* System Prompt Slots — Customizing agent personality without overriding core behavior
3+
*
4+
* Demonstrates the slot-based system prompt customization API:
5+
* 1. Custom role (persona)
6+
* 2. Custom guidelines (coding standards)
7+
* 3. Custom response style
8+
* 4. Extra freeform instructions
9+
* 5. Verify core tool behavior is preserved
10+
*
11+
* Run with: node test_prompt_slots.js
12+
*/
13+
14+
const { Agent } = require("a3s-code");
15+
const path = require("path");
16+
const os = require("os");
17+
const fs = require("fs");
18+
19+
function findConfig() {
20+
if (process.env.A3S_CONFIG) return process.env.A3S_CONFIG;
21+
const homeConfig = path.join(os.homedir(), ".a3s", "config.hcl");
22+
if (fs.existsSync(homeConfig)) return homeConfig;
23+
throw new Error("Config not found. Create ~/.a3s/config.hcl or set A3S_CONFIG");
24+
}
25+
26+
function assert(condition, message) {
27+
if (!condition) throw new Error(`Assertion failed: ${message}`);
28+
}
29+
30+
async function main() {
31+
const config = findConfig();
32+
console.log(`Config: ${config}`);
33+
34+
const agent = await Agent.create(config);
35+
console.log("Agent created ✓\n");
36+
37+
const workspace = fs.mkdtempSync(path.join(os.tmpdir(), "a3s-slots-"));
38+
try {
39+
// --- Test 1: Custom role only ---
40+
console.log("═══ Test 1: Custom role ═══");
41+
let session = agent.session(workspace, {
42+
role: "You are a senior Rust developer who specializes in async programming.",
43+
});
44+
let result = await session.send("What is your area of expertise? Reply in one sentence.");
45+
console.log(`Response: ${result.text.trim()}\n`);
46+
assert(result.text, "should get a response");
47+
48+
// --- Test 2: Role + guidelines + response style ---
49+
console.log("═══ Test 2: Role + guidelines + response style ═══");
50+
session = agent.session(workspace, {
51+
role: "You are a Python code reviewer.",
52+
guidelines: "Always check for type hints. Flag any use of `eval()`.",
53+
responseStyle: "Reply in bullet points. Be concise.",
54+
});
55+
56+
// Write a Python file for the agent to review
57+
fs.writeFileSync(
58+
path.join(workspace, "app.py"),
59+
'def add(a, b):\n' +
60+
' return eval(f"{a} + {b}")\n' +
61+
'\n' +
62+
'def greet(name):\n' +
63+
' return "Hello " + name\n'
64+
);
65+
66+
result = await session.send("Review the file app.py and list any issues you find.");
67+
console.log(`Response:\n${result.text.trim()}\n`);
68+
assert(result.toolCallsCount > 0, "should have used read_file tool");
69+
70+
// --- Test 3: Extra instructions only ---
71+
console.log("═══ Test 3: Extra instructions ═══");
72+
session = agent.session(workspace, {
73+
extra: "Always end your response with '— A3S'",
74+
});
75+
result = await session.send("Say hello.");
76+
console.log(`Response: ${result.text.trim()}\n`);
77+
78+
// --- Test 4: Verify tools still work (core behavior preserved) ---
79+
console.log("═══ Test 4: Core tool behavior preserved ═══");
80+
session = agent.session(workspace, {
81+
role: "You are a minimalist file manager.",
82+
guidelines: "Only create files when explicitly asked.",
83+
});
84+
result = await session.send(
85+
"Create a file called test.txt with the content 'prompt slots work'. " +
86+
"Then read it back."
87+
);
88+
console.log(`Response: ${result.text.trim()}\n`);
89+
assert(result.toolCallsCount > 0, "should have used tools");
90+
assert(fs.existsSync(path.join(workspace, "test.txt")), "test.txt should exist");
91+
92+
console.log("═══ All prompt slots tests passed ✓ ═══");
93+
} finally {
94+
fs.rmSync(workspace, { recursive: true, force: true });
95+
}
96+
}
97+
98+
main().catch((err) => {
99+
console.error("Test failed:", err.message);
100+
process.exit(1);
101+
});

sdk/node/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,16 @@ impl Agent {
612612
if o.default_security.unwrap_or(false) {
613613
opts = opts.with_default_security();
614614
}
615+
// Build prompt slots if any slot is set
616+
if o.role.is_some() || o.guidelines.is_some() || o.response_style.is_some() || o.extra.is_some() {
617+
let slots = a3s_code_core::SystemPromptSlots {
618+
role: o.role,
619+
guidelines: o.guidelines,
620+
response_style: o.response_style,
621+
extra: o.extra,
622+
};
623+
opts = opts.with_prompt_slots(slots);
624+
}
615625
opts
616626
});
617627

sdk/python/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-code-py"
3-
version = "0.8.0"
3+
version = "0.9.0"
44
edition = "2021"
55
authors = ["A3S Lab Team"]
66
license = "MIT"
@@ -12,7 +12,7 @@ name = "a3s_code"
1212
crate-type = ["cdylib"]
1313

1414
[dependencies]
15-
a3s-code-core = { version = "0.8", path = "../../core" }
15+
a3s-code-core = { version = "0.9", path = "../../core" }
1616
pyo3 = { version = "0.23", features = ["extension-module"] }
1717
tokio = { version = "1.35", features = ["full"] }
1818
serde_json = "1.0"

0 commit comments

Comments
 (0)