diff --git a/CHANGELOG.md b/CHANGELOG.md index ff48c0c..ef7d486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,81 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.2.0] - 2026-05-24 + +### Added + +- Added a queryable subagent task tracker so callers can observe delegated + child runs by `task_id` instead of scanning `run_events()`. The tracker is + a materialized view over the existing `SubagentStart` / `SubagentProgress` + / `SubagentEnd` event stream — the stream remains the authoritative record. +- Added three new APIs on `AgentSession` (and mirrored bindings on the Node + and Python SDKs): + - `subagent_task(task_id)` — look up a task snapshot by id. + - `subagent_tasks()` — list every delegated subagent task observed in this + session, oldest first. + - `pending_subagent_tasks()` — list only tasks still in `running` state. +- Added emission of `SubagentProgress` events from the child loop forwarder. + Two milestones are surfaced today: `status = "tool_completed"` after each + child tool ends (metadata: tool, exit_code, output_bytes, optional + error_kind) and `status = "turn_completed"` after each child LLM turn + (metadata: turn, prompt/completion/total tokens). Noisy events (TextDelta, + ToolStart, ToolOutputDelta, nested subagent events) are intentionally not + translated; consumers needing token-level streaming should subscribe to the + raw event stream directly. +- Added `SubagentStatus::Cancelled` and `AgentSession::cancel_subagent_task(id)` + for interrupting in-flight delegated child runs without cancelling the parent + run. Bindings on both SDKs (`session.cancelSubagentTask(taskId)` / + `session.cancel_subagent_task(task_id)`). A late `SubagentEnd` from a + cancelled child does not downgrade the terminal status — it stays + `Cancelled`. +- Added `SubagentTaskSnapshot` carrying `task_id`, `parent_session_id`, + `child_session_id`, `agent`, `description`, `status`, `started_ms`, + `updated_ms`, optional `finished_ms` / `output` / `success`, and a + `progress` log. The Cancellation path also propagates a real cancellation + token into the child loop via `AgentLoop::execute_with_session`, so the + signal honors existing LLM-streaming yield points. +- Added `InMemorySubagentTaskTracker` and `SubagentProgressEntry` to the + public crate-root re-exports of `a3s-code-core` alongside the existing + `SubagentStatus` / `SubagentTaskSnapshot` types. + +### Changed + +- Marked `SubagentStatus` `#[non_exhaustive]` so future variants can be added + without a major version bump. +- Reshaped the Node SDK type layout to survive `napi-rs` regeneration. The + build now writes generated declarations to `generated.d.ts`; hand-authored + types that mirror JSON wire shapes (`ToolErrorKind`, `VerificationStatus`, + `VerificationCheck`, `VerificationReport`, `ToolArtifact`) now live in + `extra-types.d.ts`; the published `index.d.ts` is a small hand-authored + aggregator that re-exports both. The `types` field in `package.json` still + points at `index.d.ts`, so consumer imports are unchanged. A new + `npm run test:types` script type-checks the aggregator to guard against + future regressions. + +### Fixed + +- Fixed `TaskExecutor::execute` and `execute_background` so the emitted + `SubagentStart` carries the real parent session id (previously + `String::new()`), and `execute_background` returns the same `task_id` + that appears in lifecycle events (previously a throwaway id). The + background path also pre-emits `SubagentStart` synchronously so callers + that query the tracker immediately after scheduling do not race the + spawned task. + +### Breaking + +- `TaskExecutor::execute`, `execute_parallel`, and `execute_background` now + take an additional `parent_session_id: Option<&str>` (or + `Option` for the background variant) so the emitted lifecycle + events can be correctly associated with the parent session. Direct + callers of `TaskExecutor` need to pass `None` (or the parent session id) + to keep current behavior. +- `register_task_with_mcp` gained a trailing + `subagent_tracker: Option>` parameter so + the session bootstrap path can share a single tracker Arc with the + executor and the live `AgentSession`. Pass `None` to opt out. + ## [3.1.0] - 2026-05-23 ### Added diff --git a/Cargo.lock b/Cargo.lock index 35f2735..d937ac5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ dependencies = [ [[package]] name = "a3s-code-core" -version = "3.1.0" +version = "3.2.0" dependencies = [ "a3s-acl 0.2.0", "a3s-ahp", diff --git a/README.md b/README.md index 00fb714..b946e6e 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,29 @@ Intent -> Context -> Action -> Observation -> Verification -> Compaction Everything else is an extension of that loop. +### What's new in 3.2 + +- **Subagent task tracker** — every delegated child run is now observable + through a queryable view fed by the existing `subagent_start` / + `subagent_progress` / `subagent_end` event stream. The new + `AgentSession::subagent_task(id)`, `subagent_tasks()`, and + `pending_subagent_tasks()` APIs (mirrored on Node and Python) let + dashboards introspect child runs without scanning `run_events()`. +- **Mid-task progress milestones** — the child loop forwarder now + synthesizes `SubagentProgress` events for `tool_completed` and + `turn_completed`, so callers see intermediate state instead of just + Start → End. +- **Cancel by task id** — `AgentSession::cancel_subagent_task(id)` + (and `session.cancelSubagentTask` / `session.cancel_subagent_task` + on the SDKs) interrupts an in-flight delegated run without + cancelling the parent. A late `SubagentEnd` from a cancelled child + does not downgrade the terminal status — it stays `Cancelled`. + +Full migration notes are in [CHANGELOG.md](./CHANGELOG.md). The +`TaskExecutor` signature additions and the `SubagentStatus` variant +addition are the only breaking changes; `SubagentStatus` is now +`#[non_exhaustive]` so future variants are non-breaking. + ### What's new in 3.0 - **Cloud-native workspace** — `S3WorkspaceBackend` with ETag @@ -1115,6 +1138,20 @@ Core delegation primitives: - `task` — run one focused delegated child run - `parallel_task` — run independent delegated child runs concurrently +Once a child run is in flight, the parent session can observe and steer +it through the subagent task tracker: + +| Operation | Rust | Node | Python | +|---|---|---|---| +| Look up a task by id | `session.subagent_task(id)` | `session.subagentTask(id)` | `session.subagent_task(id)` | +| List subagent tasks (this session) | `session.subagent_tasks()` | `session.subagentTasks()` | `session.subagent_tasks()` | +| List only in-flight subagent tasks | `session.pending_subagent_tasks()` | `session.pendingSubagentTasks()` | `session.pending_subagent_tasks()` | +| Observe mid-task milestones | `subagent_progress` in `run_events()` | same | same | +| Cancel an in-flight task | `session.cancel_subagent_task(id)` | `session.cancelSubagentTask(id)` | `session.cancel_subagent_task(id)` | + +The tracker is a materialized view over the existing event stream; the +stream remains the authoritative record. + Built-in subagents are available through these primitives and through automatic delegation: diff --git a/core/Cargo.toml b/core/Cargo.toml index 1b6c2fa..0466e9d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "a3s-code-core" -version = "3.1.0" +version = "3.2.0" edition = "2021" authors = ["A3S Lab Team"] license = "MIT" diff --git a/core/src/subagent_task_tracker.rs b/core/src/subagent_task_tracker.rs index 5226916..55ff29d 100644 --- a/core/src/subagent_task_tracker.rs +++ b/core/src/subagent_task_tracker.rs @@ -13,6 +13,7 @@ use tokio_util::sync::CancellationToken; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] +#[non_exhaustive] pub enum SubagentStatus { Running, Completed, diff --git a/sdk/node/Cargo.toml b/sdk/node/Cargo.toml index c810d2f..64f21f2 100644 --- a/sdk/node/Cargo.toml +++ b/sdk/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "a3s-code-node" -version = "3.1.0" +version = "3.2.0" edition = "2021" authors = ["A3S Lab Team"] license = "MIT" @@ -11,7 +11,7 @@ description = "A3S Code Node.js bindings - Native addon via napi-rs" crate-type = ["cdylib"] [dependencies] -a3s-code-core = { version = "3.1.0", path = "../../core", features = ["ahp", "s3"] } +a3s-code-core = { version = "3.2.0", path = "../../core", features = ["ahp", "s3"] } napi = { version = "2", features = ["async", "napi6", "serde-json"] } napi-derive = "2" tokio = { version = "1.35", features = ["full"] } diff --git a/sdk/node/examples/package-lock.json b/sdk/node/examples/package-lock.json index fe2da73..faeba6f 100644 --- a/sdk/node/examples/package-lock.json +++ b/sdk/node/examples/package-lock.json @@ -18,7 +18,7 @@ }, "..": { "name": "@a3s-lab/code", - "version": "3.1.0", + "version": "3.2.0", "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2", @@ -27,12 +27,12 @@ "typescript": "^5.9.3" }, "optionalDependencies": { - "@a3s-lab/code-darwin-arm64": "3.1.0", - "@a3s-lab/code-linux-arm64-gnu": "3.1.0", - "@a3s-lab/code-linux-arm64-musl": "3.1.0", - "@a3s-lab/code-linux-x64-gnu": "3.1.0", - "@a3s-lab/code-linux-x64-musl": "3.1.0", - "@a3s-lab/code-win32-x64-msvc": "3.1.0" + "@a3s-lab/code-darwin-arm64": "3.2.0", + "@a3s-lab/code-linux-arm64-gnu": "3.2.0", + "@a3s-lab/code-linux-arm64-musl": "3.2.0", + "@a3s-lab/code-linux-x64-gnu": "3.2.0", + "@a3s-lab/code-linux-x64-musl": "3.2.0", + "@a3s-lab/code-win32-x64-msvc": "3.2.0" } }, "node_modules/@a3s-lab/code": { diff --git a/sdk/node/package-lock.json b/sdk/node/package-lock.json index 02f126a..a91baa8 100644 --- a/sdk/node/package-lock.json +++ b/sdk/node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@a3s-lab/code", - "version": "3.1.0", + "version": "3.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@a3s-lab/code", - "version": "3.1.0", + "version": "3.2.0", "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2", @@ -15,12 +15,12 @@ "typescript": "^5.9.3" }, "optionalDependencies": { - "@a3s-lab/code-darwin-arm64": "3.1.0", - "@a3s-lab/code-linux-arm64-gnu": "3.1.0", - "@a3s-lab/code-linux-arm64-musl": "3.1.0", - "@a3s-lab/code-linux-x64-gnu": "3.1.0", - "@a3s-lab/code-linux-x64-musl": "3.1.0", - "@a3s-lab/code-win32-x64-msvc": "3.1.0" + "@a3s-lab/code-darwin-arm64": "3.2.0", + "@a3s-lab/code-linux-arm64-gnu": "3.2.0", + "@a3s-lab/code-linux-arm64-musl": "3.2.0", + "@a3s-lab/code-linux-x64-gnu": "3.2.0", + "@a3s-lab/code-linux-x64-musl": "3.2.0", + "@a3s-lab/code-win32-x64-msvc": "3.2.0" } }, "node_modules/@a3s-lab/code-darwin-arm64": { diff --git a/sdk/node/package.json b/sdk/node/package.json index 14c229c..60916db 100644 --- a/sdk/node/package.json +++ b/sdk/node/package.json @@ -1,6 +1,6 @@ { "name": "@a3s-lab/code", - "version": "3.1.0", + "version": "3.2.0", "description": "A3S Code - Native Node.js bindings for the coding-agent runtime", "main": "index.js", "types": "index.d.ts", @@ -43,11 +43,11 @@ "test:helpers": "node test-helpers.mjs" }, "optionalDependencies": { - "@a3s-lab/code-darwin-arm64": "3.1.0", - "@a3s-lab/code-linux-x64-gnu": "3.1.0", - "@a3s-lab/code-linux-x64-musl": "3.1.0", - "@a3s-lab/code-linux-arm64-gnu": "3.1.0", - "@a3s-lab/code-linux-arm64-musl": "3.1.0", - "@a3s-lab/code-win32-x64-msvc": "3.1.0" + "@a3s-lab/code-darwin-arm64": "3.2.0", + "@a3s-lab/code-linux-x64-gnu": "3.2.0", + "@a3s-lab/code-linux-x64-musl": "3.2.0", + "@a3s-lab/code-linux-arm64-gnu": "3.2.0", + "@a3s-lab/code-linux-arm64-musl": "3.2.0", + "@a3s-lab/code-win32-x64-msvc": "3.2.0" } } diff --git a/sdk/python/Cargo.toml b/sdk/python/Cargo.toml index 029b4bb..e86ea1d 100644 --- a/sdk/python/Cargo.toml +++ b/sdk/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "a3s-code-py" -version = "3.1.0" +version = "3.2.0" edition = "2021" authors = ["A3S Lab Team"] license = "MIT" @@ -12,7 +12,7 @@ name = "a3s_code" crate-type = ["cdylib"] [dependencies] -a3s-code-core = { version = "3.1.0", path = "../../core", features = ["ahp", "s3"] } +a3s-code-core = { version = "3.2.0", path = "../../core", features = ["ahp", "s3"] } pyo3 = "0.23" tokio = { version = "1.35", features = ["full"] } serde_json = "1.0" diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index b61fd1d..6bb06b8 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "a3s-code" -version = "3.1.0" +version = "3.2.0" description = "A3S Code - Native Python bindings for the coding-agent runtime" readme = "README.md" license = {text = "MIT"}