Commit 6e33653
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
File tree
- core
- src
- config
- serve
- tests
- manual
- sdk
- node
- src
- python
- src
- tests
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| 105 | + | |
| 106 | + | |
105 | 107 | | |
106 | 108 | | |
107 | 109 | | |
| |||
137 | 139 | | |
138 | 140 | | |
139 | 141 | | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
140 | 145 | | |
141 | 146 | | |
142 | 147 | | |
| |||
147 | 152 | | |
148 | 153 | | |
149 | 154 | | |
| 155 | + | |
| 156 | + | |
0 commit comments