Skip to content

Commit 8a9a3c6

Browse files
committed
Merge origin/main into main
2 parents f7cdce2 + 8f99905 commit 8a9a3c6

34 files changed

Lines changed: 2125 additions & 248 deletions

.github/workflows/ci.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,30 @@ jobs:
3737

3838
# - name: Tests
3939
# run: cargo test --workspace --lib
40+
41+
sdk-check:
42+
name: SDK build check
43+
runs-on: ubuntu-latest
44+
steps:
45+
- uses: actions/checkout@v4
46+
47+
- name: Setup workspace context
48+
run: bash .github/setup-workspace.sh
49+
50+
- name: Install Rust
51+
uses: dtolnay/rust-toolchain@stable
52+
53+
- uses: Swatinem/rust-cache@v2
54+
55+
- name: Install protobuf compiler
56+
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
57+
58+
# The Node and Python SDKs are excluded from the core workspace and are
59+
# otherwise only compiled at release time (napi / maturin). Type-check them
60+
# here so a core change that breaks an SDK (e.g. a new struct field that an
61+
# SDK constructs with a literal) fails on the PR instead of the release.
62+
- name: Check Node SDK
63+
run: cargo check --manifest-path sdk/node/Cargo.toml
64+
65+
- name: Check Python SDK
66+
run: cargo check --manifest-path sdk/python/Cargo.toml

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [4.2.1] - 2026-06-23
9+
10+
### Fixed
11+
12+
- Python SDK wheel build (and therefore the PyPI + GitHub Release publish steps)
13+
failed for 4.2.0: the `PyAutoDelegationConfig → AutoDelegationConfig`
14+
conversion used a struct literal that omitted the `allow_manual_delegation`
15+
field added to `AutoDelegationConfig`. The conversion now falls back to core
16+
defaults for fields the Python SDK does not expose, so future core fields no
17+
longer break the wheel build. (The crates.io and npm 4.2.0 artifacts were
18+
unaffected; 4.2.1 completes the release across all channels.)
19+
20+
## [4.2.0] - 2026-06-23
21+
22+
### Added
23+
24+
- Native structured-output enforcement. `LlmClient` gains
25+
`native_structured_support()`, `complete_structured()`, and
26+
`complete_streaming_structured()` (all with non-breaking default impls). The
27+
structured engine now forces the provider `tool_choice` for tool mode and
28+
requests native `response_format` (`json_schema` / `json_object`) where the
29+
provider supports it, instead of merely offering a tool the model could ignore.
30+
31+
### Fixed
32+
33+
- Stabilized JSON-object generation. Forced `tool_choice` on both the blocking
34+
and streaming paths guarantees the model emits the structured object rather
35+
than prose or malformed tool arguments.
36+
- Hardened the planner / pre-analysis JSON parsing: it now reuses the robust
37+
shared extractor (markdown fences, surrounding prose, braces inside strings)
38+
and adds one repair retry, replacing the previous naive first-`{`/last-`}`
39+
slice that hard-errored on fenced or prose-wrapped output.
40+
841
## [4.1.0] - 2026-06-23
942

1043
### Changed

Cargo.lock

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

README.md

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ hooks, and budget guard with typed objects instead of raw backend strings.
3535

3636
The surrounding A3S project uses that runtime across these layers:
3737

38-
| Name | What it is | Primary repo |
39-
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
40-
| **A3S Code / `a3s-code`** | Rust core plus Node.js and Python SDKs for embedding coding-agent sessions in products. | [https://github.com/AI45Lab/Code](https://github.com/AI45Lab/Code) |
41-
| **`a3s code` TUI** | The interactive terminal coding agent. It is shipped by the`a3s` CLI, drives `a3s-code-core`, and renders the event stream with the `a3s-tui` framework. | [https://github.com/A3S-Lab/Cli](https://github.com/A3S-Lab/Cli) |
42-
| **`a3s-tui`** | Terminal UI framework used by the CLI. It is a UI layer, not the agent runtime. | [https://github.com/A3S-Lab/TUI](https://github.com/A3S-Lab/TUI) |
43-
| **A3S monorepo** | Product docs, submodule pins, release orchestration, and related crates. | [https://github.com/A3S-Lab/a3s](https://github.com/A3S-Lab/a3s) |
38+
| Name | What it is | Primary repo |
39+
| --- | --- | --- |
40+
| **A3S Code / `a3s-code`** | Rust core plus Node.js and Python SDKs for embedding coding-agent sessions in products. | <https://github.com/A3S-Lab/Code> |
41+
| **`a3s code` TUI** | The interactive terminal coding agent. It is shipped by the `a3s` CLI, drives `a3s-code-core`, and renders the event stream with the `a3s-tui` framework. | <https://github.com/A3S-Lab/Cli> |
42+
| **`a3s-tui`** | Terminal UI framework used by the CLI. It is a UI layer, not the agent runtime. | <https://github.com/A3S-Lab/TUI> |
43+
| **A3S monorepo** | Product docs, submodule pins, release orchestration, and related crates. | <https://github.com/A3S-Lab/a3s> |
4444

4545
Use `a3s code` when you want a ready interactive coding agent in a terminal.
4646
Use the A3S Code SDKs when you are building your own harness, IDE extension,
@@ -53,33 +53,33 @@ server worker, workflow runner, or product UI.
5353
A3S Code is deliberately split into surfaces so products can adopt the runtime
5454
without inheriting the terminal UX:
5555

56-
| Surface | What you use | What it gives you |
57-
| ----------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
58-
| Runtime sessions | Rust core, Node.js SDK, Python SDK | `send`, `run`, `stream`, direct tools, cancellation, persistence, verification, lifecycle cleanup. |
59-
| Filesystem-first agents | `AGENTS.md`, `agent.acl`, `.a3s/agents/`, `.a3s/skills/`, AgentDir | Git-reviewable instructions, model policy, worker roles, reusable skills, directory-scoped tools, and schedules. |
60-
| Terminal app | `a3s code` from the `a3s` CLI | Ready TUI with streamed events, tool activity, approvals, memory/git/file panels, and session controls. |
61-
| Host extension points | Typed stores, workspaces, providers, hooks, MCP/AHP, command registry | Product-specific storage, sandboxing, tools, controls, observability, and slash-command behavior without forking the loop. |
56+
| Surface | What you use | What it gives you |
57+
| --- | --- | --- |
58+
| Runtime sessions | Rust core, Node.js SDK, Python SDK | `send`, `run`, `stream`, direct tools, cancellation, persistence, verification, lifecycle cleanup. |
59+
| Filesystem-first agents | `AGENTS.md`, `agent.acl`, `.a3s/agents/`, `.a3s/skills/`, AgentDir | Git-reviewable instructions, model policy, worker roles, reusable skills, directory-scoped tools, and schedules. |
60+
| Terminal app | `a3s code` from the `a3s` CLI | Ready TUI with streamed events, tool activity, approvals, memory/git/file panels, and session controls. |
61+
| Host extension points | Typed stores, workspaces, providers, hooks, MCP/AHP, command registry | Product-specific storage, sandboxing, tools, controls, observability, and slash-command behavior without forking the loop. |
6262

6363
## Capability Map
6464

65-
| Area | Current capability |
66-
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
67-
| Agent API | `Agent` and `AgentSession` expose `send`, `run`, `stream`, object-shaped requests, explicit-history calls, attachments, direct tool calls, run state, cancellation, persistence, and lifecycle cleanup. |
68-
| Agent loop | Streaming text/tool events, tool-call repair, bounded parse-error recovery, compaction, planning modes, budget guards, active-tool state, and deterministic direct calls. |
69-
| Config | ACL config files or inline ACL source; provider/model selection; skill and agent directories; storage, search, MCP, and delegation settings. |
70-
| LLM clients | Built-in Anthropic, OpenAI-compatible, and Zhipu-compatible clients, plus`SessionOptions::with_llm_client(...)` for host-supplied clients. |
71-
| Tools | Files, search, shell, git, web fetch/search, batch, structured output, programmatic QuickJS tool calling, skills, MCP tools, and task delegation. |
72-
| Commands | Built-in slash commands and a host command registry for product-specific`/command` handlers; the TUI layers its own terminal commands over the same session path. |
73-
| Filesystem-first | `AGENTS.md`, `.a3s/agents/`, `.a3s/skills/`, AgentDir `instructions.md`, `agent.acl`, `tools/`, and `schedules/` make agent behavior reviewable, diffable, and reusable as files. |
74-
| Context | Project instructions, prompt slots, filesystem context, recent-file/ripgrep providers, memory recall, skills, MCP, and run observations. |
75-
| Safety | Permission policies, human confirmation, workspace path checks, tool timeouts, sandbox handle for`bash`, security providers, prompt boundary injection, and redaction-aware logging paths. |
76-
| Delegation | Built-in worker roles, custom Markdown/YAML agents,`task`, `parallel_task`, automatic delegation controls, and subagent task tracking. |
77-
| Orchestration | Programmable fan-out, pipelines, resumable checkpoints, workflow phases, loop caps, and shared workflow budget ledgers. |
78-
| Serving | `serveAgentDir` / `serve_agent_dir` load AgentDir schedules as full harness turns with stable `schedule:<name>` sessions. |
79-
| Workspaces | Local filesystem by default; typed workspace services for custom hosts; optional S3-compatible backend and HTTP/JSON remote-git backend. |
80-
| Persistence | Memory and file session stores, session IDs, auto-save, run snapshots/events, trace artifacts, memory store integration, loop/workflow checkpoints, and retention caps. |
81-
| Verification | `verifyCommands`, verification presets, structured reports, summaries, run events, artifacts, and trace APIs for replayable evidence. |
82-
| Integration | MCP client/manager, AHP hook integration, lifecycle hooks, lane queue options, OpenTelemetry feature flag, Node SDK, Python SDK, and the`a3s code` TUI. |
65+
| Area | Current capability |
66+
| --- | --- |
67+
| Agent API | `Agent` and `AgentSession` expose `send`, `run`, `stream`, object-shaped requests, explicit-history calls, attachments, direct tool calls, run state, cancellation, persistence, and lifecycle cleanup. |
68+
| Agent loop | Streaming text/tool events, tool-call repair, bounded parse-error recovery, compaction, planning modes, budget guards, active-tool state, and deterministic direct calls. |
69+
| Config | ACL config files or inline ACL source; provider/model selection; skill and agent directories; storage, search, MCP, and delegation settings. |
70+
| LLM clients | Built-in Anthropic, OpenAI-compatible, and Zhipu-compatible clients, plus `SessionOptions::with_llm_client(...)` for host-supplied clients. |
71+
| Tools | Files, search, shell, git, web fetch/search, batch, structured output, programmatic QuickJS tool calling, skills, MCP tools, and task delegation. |
72+
| Commands | Built-in slash commands and a host command registry for product-specific `/command` handlers; the TUI layers its own terminal commands over the same session path. |
73+
| Filesystem-first | `AGENTS.md`, `.a3s/agents/`, `.a3s/skills/`, AgentDir `instructions.md`, `agent.acl`, `tools/`, and `schedules/` make agent behavior reviewable, diffable, and reusable as files. |
74+
| Context | Project instructions, prompt slots, filesystem context, recent-file/ripgrep providers, memory recall, skills, MCP, and run observations. |
75+
| Safety | Permission policies, human confirmation, workspace path checks, tool timeouts, sandbox handle for `bash`, security providers, prompt boundary injection, and redaction-aware logging paths. |
76+
| Delegation | Built-in worker roles, custom Markdown/YAML agents, `task`, `parallel_task`, automatic delegation controls, and subagent task tracking. |
77+
| Orchestration | Programmable fan-out, pipelines, resumable checkpoints, workflow phases, loop caps, and shared workflow budget ledgers. |
78+
| Serving | `serveAgentDir` / `serve_agent_dir` load AgentDir schedules as full harness turns with stable `schedule:<name>` sessions. |
79+
| Workspaces | Local filesystem by default; typed workspace services for custom hosts; optional S3-compatible backend and HTTP/JSON remote-git backend. |
80+
| Persistence | Memory and file session stores, session IDs, auto-save, run snapshots/events, trace artifacts, memory store integration, loop/workflow checkpoints, and retention caps. |
81+
| Verification | `verifyCommands`, verification presets, structured reports, summaries, run events, artifacts, and trace APIs for replayable evidence. |
82+
| Integration | MCP client/manager, AHP hook integration, lifecycle hooks, lane queue options, OpenTelemetry feature flag, Node SDK, Python SDK, and the `a3s code` TUI. |
8383

8484
## Install
8585

@@ -502,4 +502,4 @@ cargo build -p a3s-code-py
502502

503503
## License
504504

505-
MIT
505+
MIT

core/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-code-core"
3-
version = "4.1.0"
3+
version = "4.2.8"
44
edition = "2021"
55
authors = ["A3S Lab Team"]
66
license = "MIT"
@@ -13,10 +13,10 @@ path = "src/lib.rs"
1313

1414
[dependencies]
1515
# Internal crates
16-
a3s-common = "0.1.1"
17-
a3s-memory = "0.1.1"
18-
a3s-lane = "0.4"
19-
a3s-search = { version = "1.2.3", default-features = false, features = ["lightpanda"] }
16+
a3s-common = { version = "0.1.1", path = "../../common" }
17+
a3s-memory = { version = "0.1.1", path = "../../memory" }
18+
a3s-lane = { version = "0.4", path = "../../lane" }
19+
a3s-search = { version = "1.2.3", path = "../../search", default-features = false, features = ["lightpanda"] }
2020

2121
# Async runtime
2222
tokio = { version = "1.35", features = [

core/src/agent/execution_state.rs

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::AgentResult;
2-
use crate::llm::{Message, TokenUsage};
2+
use crate::llm::{ContentBlock, Message, TokenUsage};
33
use crate::verification::VerificationReport;
44
use serde_json::Value;
55
use std::time::Instant;
@@ -211,16 +211,24 @@ impl ExecutionLoopState {
211211
}
212212
}
213213

214-
/// Finish a host-cancelled (Esc) turn. Unlike an `Err` return — which the
215-
/// run lifecycle never records, dropping the turn from history — this returns
216-
/// a normal result so the partial exchange is committed and the next turn
217-
/// remembers what was asked. The committed history must end on an assistant
218-
/// message; if the turn was cancelled before any assistant reply, append a
219-
/// short marker so user/assistant alternation stays valid.
214+
/// Build a result from a turn that was cancelled mid-generation. Keeps the
215+
/// conversation accumulated so far (the user's message above all) so the next
216+
/// turn remembers it. Appends a short assistant marker when the log would
217+
/// otherwise end on a user message, so the next user turn still alternates.
220218
pub(super) fn finish_interrupted(mut self) -> AgentResult {
221-
if self.messages.last().map(|m| m.role.as_str()) == Some("user") {
222-
self.messages
223-
.push(Message::assistant("(Response interrupted)"));
219+
let ends_on_user = self
220+
.messages
221+
.last()
222+
.map(|m| m.role != "assistant")
223+
.unwrap_or(false);
224+
if ends_on_user {
225+
self.messages.push(Message {
226+
role: "assistant".to_string(),
227+
content: vec![ContentBlock::Text {
228+
text: "(Response interrupted by the user.)".to_string(),
229+
}],
230+
reasoning_content: None,
231+
});
224232
}
225233
AgentResult {
226234
text: String::new(),
@@ -246,15 +254,24 @@ mod tests {
246254
use serde_json::json;
247255

248256
#[test]
249-
fn finish_interrupted_appends_marker_when_history_ends_on_user() {
250-
// Cancelled before any assistant reply: commit the user turn + a marker
251-
// so the next turn remembers it and role alternation stays valid.
252-
let state = ExecutionLoopState::new(&[Message::user("do X")]);
257+
fn finish_interrupted_keeps_user_message_and_alternates() {
258+
// A cancelled turn must keep the user's message (so the next turn
259+
// remembers it) and end on an assistant message (so it still alternates).
260+
let mut state = ExecutionLoopState::new(&[]);
261+
state.messages.push(Message::user("what is the plan?"));
253262
let result = state.finish_interrupted();
254-
assert_eq!(result.messages.len(), 2);
255-
assert_eq!(result.messages[0].role.as_str(), "user");
256-
assert_eq!(result.messages[1].role.as_str(), "assistant");
257-
assert!(result.messages[1].text().contains("interrupted"));
263+
assert!(
264+
result
265+
.messages
266+
.iter()
267+
.any(|m| m.role == "user" && m.text().contains("what is the plan?")),
268+
"user message must survive the interrupt"
269+
);
270+
assert_eq!(
271+
result.messages.last().unwrap().role,
272+
"assistant",
273+
"history must end on an assistant message to alternate"
274+
);
258275
assert!(result.text.is_empty());
259276
}
260277

0 commit comments

Comments
 (0)