Skip to content

feat(serve): filesystem-first agents (eve parity) — agent-dir, cron schedules, serve daemon, tools/#73

Merged
ZhiXiao-Lin merged 9 commits into
mainfrom
feat/filesystem-first-agents
Jun 17, 2026
Merged

feat(serve): filesystem-first agents (eve parity) — agent-dir, cron schedules, serve daemon, tools/#73
ZhiXiao-Lin merged 9 commits into
mainfrom
feat/filesystem-first-agents

Conversation

@ZhiXiao-Lin

Copy link
Copy Markdown
Contributor

Summary

Adds a filesystem-first agent layer (vercel/eve parity, harness-respecting): a single directory defines a durable agent by convention, served by a cron daemon, with declarative tools/. Exposed in Rust + Python + TypeScript SDKs.

Built as a thin layer over existing primitives — no new runtime, no new prompt system. Gated behind the serve Cargo feature, so library-only embedders pay nothing.

The agent directory

my-agent/
├── instructions.md   (required)  → SystemPromptSlots.role (a prompt SLOT, not a system-prompt override)
├── agent.acl         (optional)  → CodeConfig
├── skills/           (optional)  → skill_dirs
├── schedules/*.md    (optional)  → cron jobs (YAML frontmatter `cron:` + body prompt)
├── tools/*.md        (optional)  → kind: mcp → MCP servers registered into the session
└── channels/         (optional)  → parsed; adapters design-only

Harness guardrails (the deliberate divergence from eve)

  • instructions.md is a prompt slot, not a system-prompt override — BOUNDARIES / response-format / verification stay authoritative.
  • Every schedule/tool runs through the full harness: a scheduled turn is AgentSession::send (context, tool visibility, safety gate, verification), never a raw model call.
  • tools/ definition comes from the directory, but visibility + safety stay harness-owned: an mcp spec is registered via the normal add_mcp_server path (mcp__<server>__<tool>, permission-gated), never as arbitrary host code.

What's included

  • Core (serve feature): AgentDir::load convention loader; Scheduler/ScheduledJob (5/6-field cron); serve_agent_dir daemon (one durable session per schedule, stable id schedule:<name>); tools/install_agent_dir_tools.
  • SDKs: serveAgentDir/serve_agent_dir + ServeHandle (stop/isStopped) in both Node (napi) and Python (PyO3). Node JS/TS glue regenerated so the API is reachable from JS/TS.
  • Docs: user guide (agent-dir.mdx, in the monorepo root) + tools/→MCP and channels design docs.

Tests

  • Rust: 12 unit + 4 integration (2 hermetic + 2 real-LLM #[ignore]).
  • Python SDK: ServeHandle lifecycle (unit) + real-LLM schedule fire (integration).
  • Node SDK: same, wired into npm test (real part self-skips without config).

Real-LLM integration tests pass against .a3s/config.acl (glm5.1). All commits cargo fmt-clean.

Out of scope (documented as next-increment)

  • channels/ adapters (HTTP/Slack/Discord) — design-only (manual/CHANNELS_DESIGN.md).
  • Sandboxed-script tools/ backend (kind: script) — design-only (manual/AGENT_DIR_TOOLS_DESIGN.md).
  • User-facing apps/docs pages live in the a3s monorepo root (separate from this submodule PR).

claude added 9 commits June 17, 2026 21:46
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.
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).
…ssions

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.
…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.
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.
…ions

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).
… 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.
…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).
…AgentDir

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.
@ZhiXiao-Lin ZhiXiao-Lin merged commit 6e33653 into main Jun 17, 2026
@ZhiXiao-Lin ZhiXiao-Lin deleted the feat/filesystem-first-agents branch June 17, 2026 20:49
ZhiXiao-Lin pushed a commit to A3S-Lab/a3s that referenced this pull request Jun 17, 2026
…rve feature

Adds the Filesystem-First Agents user doc (apps/docs/.../agent-dir.mdx) + sidebar nav entry, and advances the crates/code submodule fd4d068 -> 6e33653 (AI45Lab/Code#73: agent-dir convention, cron schedules, serve daemon, tools/ MCP backend, Python+Node SDK bindings, full unit + integration tests).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants