Skip to content

Commit 6e33653

Browse files
ZhiXiao-Linclaude
andauthored
feat(serve): filesystem-first agents (eve parity) — agent-dir, cron schedules, serve daemon, tools/ (#73)
* feat(config): P0 — filesystem-first agent directory convention eve-style: a single directory defines a durable agent by convention. `AgentDir::load(dir)` SYNTHESIZES existing config objects (no new runtime): - instructions.md (required) -> SystemPromptSlots.role (a prompt SLOT, NOT a system-prompt override — BOUNDARIES/response-format/verification stay harness-owned) - agent.acl (optional) -> CodeConfig (else default) - skills/ -> appended to CodeConfig.skill_dirs (existing format) - schedules/*.md -> Vec<ScheduleSpec> (YAML frontmatter `cron:` + body prompt) - channels/*.{md,acl} -> Vec<ChannelSpec> (parsed; adapters not yet built) Distinct from the existing agent_dirs/register_agent_dir (worker-subagent dirs). Reuses CodeConfig::from_file, SystemPromptSlots, the skills *.md format. Tests: loads the convention into slots + specs; missing instructions.md errors; frontmatter split. Build + fmt + clippy clean. First slice of eve-style filesystem-first durable agents (P0). Next: P1 cron schedules, then the serve daemon. * feat(serve): P1 — cron schedules for filesystem-first agents Behind a new `serve` feature (default off → library embedders pay nothing). - ScheduledJob::parse — compiles a ScheduleSpec's cron (accepts standard 5-field `min hour dom mon dow` and 6-field `sec min hour dom mon dow`); next_fire_after. - Scheduler — skips disabled specs, rejects bad cron, drives one tokio loop per job (sleep-until-next-fire → fire → repeat) until a CancellationToken fires. - ScheduleSink trait — the seam the serve daemon implements to route the schedule's markdown prompt through AgentSession::send (a FULL harness turn: context, tool visibility, safety gate, verification — never a raw model call). Adds `cron` as an optional dep gated by `serve`. Tests: 5-field cron next-fire (deterministic), invalid-cron error, disabled-skip, and an every-second cron that fires >= 1 in ~2.2s then stops on cancel. Default build unaffected; clippy clean. Second slice of eve-style filesystem-first durable agents (P1). Next: the serve daemon (session registry + wire the sink to AgentSession::send + rehydrate). * feat(serve): serve daemon driving cron schedules into per-schedule sessions serve_agent_dir + SessionScheduleSink build one durable AgentSession per enabled schedule (stable id schedule:<name>); each fire is a FULL harness turn via AgentSession::send, never a raw model call. Gated behind the `serve` feature. Static audit + cargo check --features serve clean. * feat(serve): expose serve_agent_dir + ServeHandle in Node and Python SDKs Mirrors the add_mcp_server binding pattern. Agent.serve_agent_dir(dir, workspace, options?) -> ServeHandle (stop()/is_stopped()); enables the core `serve` feature + tokio-util in both SDK crates. cargo check -j1 clean for a3s-code-node and a3s-code-py. * docs(serve): tools->MCP mapping and channels design (design-only) tools/ -> MCP-or-sandboxed-QuickJS mapping honoring the harness guardrail (tool definition from the dir; visibility+safety stay harness-owned). ChannelAdapter/ChannelSink seam for HTTP/Slack/Discord where every inbound message is a full session.send turn. No implementation. * feat(serve): tools/ — register declarative MCP tool servers into sessions AgentDir::load now parses tools/*.md (kind: mcp) into ToolSpec::Mcp(McpServerConfig) — lenient deserialize, dedup by name, unknown-kind fails closed. install_agent_dir_tools registers each into the per-schedule session via the existing add_mcp_server path, so tools land as mcp__<server>__* gated by the session permission policy: tool definition from the dir, visibility+safety stay harness-owned. Sandboxed-script (kind: script) backend is the next increment. cargo check + 5 parser + 7 serve tests pass (-j1). * test(serve): integration tests for tools/ install + real-LLM schedule fire Closes the cross-module integration gap the in-crate unit tests stubbed out: - test_agent_dir_tools.rs (hermetic, --features serve): AgentDir::load + install_agent_dir_tools fail CLOSED when an MCP server command can't be spawned; plus the empty-install Ok path. 2/2 pass in normal CI. - test_serve_agent_dir_real_llm.rs (#[ignore], real provider): a cron schedule fires a FULL real AgentSession::send harness turn (recording-sink asserts non-empty returned text), and serve_agent_dir runs a real fire then stops cleanly on cancel. 2/2 pass against .a3s/config.acl (glm5.1, 46s). Adds async-trait dev-dep for the ScheduleSink impl in the integration test. * test(sdk-python): serve binding tests — ServeHandle lifecycle + real-LLM schedule fire Hermetic unit test (inline ACL): serve a no-schedule dir, assert the returned ServeHandle reports not-stopped, stop() is idempotent and flips is_stopped(). Integration test (real provider, skipped without .a3s/config.acl): an every-second schedule fires real harness turns through the daemon — the PyO3 boundary must survive them (a panic would abort the process) — then stop() shuts down clean. Both pass against the rebuilt module (glm5.1). * test(sdk-node): serve binding tests + regenerate JS/TS glue for serveAgentDir The napi build regenerated index.js + generated.d.ts to include serveAgentDir + ServeHandle — the earlier binding commit added the Rust but not the generated glue, so the API was not reachable from JS/TS until now. test_serve.mjs: hermetic ServeHandle lifecycle (exports + stop/isStopped idempotent) and a real-provider integration test (every-second schedule fires real harness turns through the daemon — a napi panic would abort the process — then clean stop; self-skips without .a3s/config.acl). Wired into npm test. Both pass; existing smoke still green. --------- Co-authored-by: Claude <claude@anthropic.com>
1 parent fd4d068 commit 6e33653

24 files changed

Lines changed: 2357 additions & 4 deletions

Cargo.lock

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

core/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ uuid = { version = "1.6", features = ["v4", "serde"] }
102102

103103
# Time handling
104104
chrono = { version = "0.4", features = ["serde"] }
105+
# Cron parsing for the `serve` schedules (gated behind the `serve` feature).
106+
cron = { version = "0.12", optional = true }
105107
rquickjs = { version = "0.11.0", features = ["futures"] }
106108

107109
# S3-compatible workspace backend (optional, gated by `s3` feature)
@@ -137,6 +139,9 @@ s3 = [
137139
"dep:aws-smithy-types",
138140
"dep:aws-smithy-runtime-api",
139141
]
142+
# Enable the durable serve layer: cron schedules + (later) channels + serve daemon
143+
# for filesystem-first agents. Library-only embedders pay nothing without it.
144+
serve = ["dep:cron"]
140145

141146
[dev-dependencies]
142147
# AHP for integration tests
@@ -147,3 +152,5 @@ wiremock = "0.6"
147152
# Self-signed cert generation for mTLS happy-path tests. Production code does
148153
# not depend on rcgen.
149154
rcgen = "0.13"
155+
# Implement the async ScheduleSink trait in serve integration tests.
156+
async-trait = "0.1"

0 commit comments

Comments
 (0)