Skip to content

Commit 79c43c7

Browse files
claudeZhiXiao-Lin
authored andcommitted
feat(python-sdk): expose subagent task query API
Mirrors the new Session APIs onto the Python SDK so callers can introspect delegated subagent tasks symmetrically with the Node SDK. - `Session.subagent_task(task_id)` → dict or None - `Session.subagent_tasks()` → list of dicts (this session) - `Session.pending_subagent_tasks()` → only `running` entries Each method follows the existing run-query pattern: drop the GIL via `py.allow_threads`, block on the tokio runtime, then serialize the result via serde_json so Python sees plain dict / list / None. Smoke test under `tests/test_subagent_query_api.py` confirms the three methods exist and return the expected empty-state shapes for a fresh session, without needing real LLM credentials.
1 parent 50185f9 commit 79c43c7

2 files changed

Lines changed: 93 additions & 0 deletions

File tree

sdk/python/src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,40 @@ impl PySession {
15351535
json_string_to_py(py, &json)
15361536
}
15371537

1538+
/// Look up a delegated subagent task by id. Returns None when no such
1539+
/// task has been observed in this session.
1540+
fn subagent_task(&self, py: Python<'_>, task_id: String) -> PyResult<PyObject> {
1541+
let session = self.inner.clone();
1542+
let snapshot =
1543+
py.allow_threads(move || get_runtime().block_on(session.subagent_task(&task_id)));
1544+
let json = serde_json::to_string(&snapshot).map_err(|e| {
1545+
PyRuntimeError::new_err(format!("Failed to serialize subagent task: {e}"))
1546+
})?;
1547+
json_string_to_py(py, &json)
1548+
}
1549+
1550+
/// Return snapshots of every delegated subagent task observed in this
1551+
/// session (including completed and failed ones), oldest first.
1552+
fn subagent_tasks(&self, py: Python<'_>) -> PyResult<PyObject> {
1553+
let session = self.inner.clone();
1554+
let tasks = py.allow_threads(move || get_runtime().block_on(session.subagent_tasks()));
1555+
let json = serde_json::to_string(&tasks).map_err(|e| {
1556+
PyRuntimeError::new_err(format!("Failed to serialize subagent tasks: {e}"))
1557+
})?;
1558+
json_string_to_py(py, &json)
1559+
}
1560+
1561+
/// Return snapshots of subagent tasks still in `running` state.
1562+
fn pending_subagent_tasks(&self, py: Python<'_>) -> PyResult<PyObject> {
1563+
let session = self.inner.clone();
1564+
let tasks =
1565+
py.allow_threads(move || get_runtime().block_on(session.pending_subagent_tasks()));
1566+
let json = serde_json::to_string(&tasks).map_err(|e| {
1567+
PyRuntimeError::new_err(format!("Failed to serialize pending subagent tasks: {e}"))
1568+
})?;
1569+
json_string_to_py(py, &json)
1570+
}
1571+
15381572
/// Cancel a specific run only if it is still the active run.
15391573
fn cancel_run(&self, py: Python<'_>, run_id: String) -> bool {
15401574
let session = self.inner.clone();
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Smoke test for the subagent task query API exposed in PR #4.
2+
3+
Verifies the three new Session methods are reachable from Python and
4+
return the expected empty-state shapes for a fresh session. Mirrors the
5+
Node SDK smoke test in sdk/node/test.mjs.
6+
7+
Run with: A3S_CONFIG_FILE not needed — uses inline ACL.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import tempfile
13+
14+
from a3s_code import Agent, LocalWorkspaceBackend, PermissionPolicy, SessionOptions
15+
16+
17+
INLINE_CONFIG = """
18+
default_model = "anthropic/claude-sonnet-4-20250514"
19+
20+
providers "anthropic" {
21+
api_key = "test-key"
22+
models "claude-sonnet-4-20250514" {
23+
name = "Claude Sonnet 4"
24+
}
25+
}
26+
""".strip()
27+
28+
29+
def main() -> None:
30+
workspace = tempfile.mkdtemp(prefix="a3s-code-python-subagent-")
31+
agent = Agent.create(INLINE_CONFIG)
32+
33+
opts = SessionOptions()
34+
opts.permission_policy = PermissionPolicy(default_decision="allow")
35+
opts.workspace_backend = LocalWorkspaceBackend(workspace)
36+
37+
session = agent.session(workspace, opts)
38+
39+
tasks = session.subagent_tasks()
40+
assert isinstance(tasks, list), f"subagent_tasks() should return list, got {type(tasks)!r}"
41+
assert tasks == [], f"fresh session should have no subagent tasks, got {tasks!r}"
42+
43+
pending = session.pending_subagent_tasks()
44+
assert isinstance(
45+
pending, list
46+
), f"pending_subagent_tasks() should return list, got {type(pending)!r}"
47+
assert (
48+
pending == []
49+
), f"fresh session should have no pending subagent tasks, got {pending!r}"
50+
51+
missing = session.subagent_task("task-does-not-exist")
52+
assert missing is None, f"unknown subagent task id should return None, got {missing!r}"
53+
54+
session.close()
55+
print("python sdk subagent query api ok")
56+
57+
58+
if __name__ == "__main__":
59+
main()

0 commit comments

Comments
 (0)