Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0ddc64d
plan(agent): add coding tools agent loop plan
fffonion Aug 31, 2026
6927098
build(tools): add JSON Schema validation dependency
fffonion Aug 31, 2026
366b373
feat(tools): add deterministic tool registry contracts
fffonion Aug 31, 2026
5e62a87
feat(service): snapshot tool registry in run context
fffonion Aug 31, 2026
b43653d
build(core): pin coding tools prerequisites
fffonion Sep 1, 2026
621ba22
feat(tools): add bounded terminal and process operations
fffonion Sep 1, 2026
f04932e
feat(tools): add confined file operations
fffonion Sep 1, 2026
4fd77e1
feat(agent): run serial provider tool loop
fffonion Sep 2, 2026
38c65d2
fix(tools): retry artifact flock during store teardown
fffonion Aug 24, 2026
ec7b50f
fix(agent): harden provider loop controls
fffonion Aug 24, 2026
c70b9a4
refactor(tools): unify execution contracts
fffonion Sep 1, 2026
e501303
fix(tools): use retained confined process cwd
fffonion Sep 1, 2026
72ce62b
feat(tools): add validated native dispatch
fffonion Sep 1, 2026
0355adc
fix(tools): share artifact stores safely
fffonion Sep 1, 2026
9d78262
feat(prompt): add frozen coding system prompt
fffonion Aug 24, 2026
e24d328
feat(storage): persist agent loop messages
fffonion Sep 2, 2026
ae6520d
fix(service): keep step commits durable-first and fail closed
fffonion Sep 2, 2026
4138fc4
fix(agent): pass frozen coding prompt to provider
fffonion Sep 2, 2026
f115bd9
feat(metrics): count coding agent activity
fffonion Sep 2, 2026
546a434
feat(service): run cancellable coding agent loop
fffonion Sep 3, 2026
8c3bd8a
fix(service): account coding agent activity
fffonion Sep 3, 2026
5bd812b
fix(service): harden cancellation cleanup and recovery
fffonion Sep 3, 2026
483bf89
test(e2e): cover real coding agent workflow
fffonion Sep 3, 2026
5c1d925
test(e2e): cover cancellation and output limits
fffonion Sep 3, 2026
b99ac3e
test(agent): harden coding loop end-to-end coverage
fffonion Sep 3, 2026
f9468d2
fix(service): persist provider steps before tool dispatch
fffonion Sep 3, 2026
ac1208c
test(agent): verify durable provider recovery end to end
fffonion Sep 3, 2026
cd7dd04
fix(service): fail closed on provider recovery corruption
fffonion Sep 3, 2026
d9f7a28
test(service): verify provider request recovery boundaries
fffonion Sep 3, 2026
5190028
fix(test): align provider recovery integration coverage
fffonion Sep 3, 2026
804f4c9
fix(telegram): release session gate after resumed run
fffonion Sep 3, 2026
1935add
fix(service): recover native dispatch initialization after panic
fffonion Sep 3, 2026
0fcca81
fix(agent): replay durable tools before native dispatch
fffonion Sep 3, 2026
6ac81e9
test(agent): clean fixtures and pin init panic races
fffonion Sep 3, 2026
c14796b
plan(agent): define production auth and usability roadmap
fffonion Sep 3, 2026
beca6dd
plan(tools): prioritize rss tool migration
fffonion Sep 3, 2026
5998c19
feat(tools): define rss tool registry contracts
fffonion Sep 3, 2026
195ed1b
feat(runtime): issue scoped tool capability tokens
fffonion Sep 3, 2026
833e78c
fix(runtime): close capability lifecycle durably
fffonion Sep 3, 2026
6883626
fix(runtime): fence failed tool result commits
fffonion Sep 3, 2026
45a3037
feat(runtime): add confined agent capabilities
fffonion Sep 3, 2026
beb3533
fix(runtime): enforce capability resource ceilings
fffonion Sep 3, 2026
626ee57
fix(runtime): bound capability filesystem traversal
fffonion Sep 3, 2026
a6a7b02
fix(runtime): reject malformed capability arguments
fffonion Sep 4, 2026
64e61b5
feat(tools): implement file reads in rss
fffonion Sep 4, 2026
93e66a7
fix(tools): enforce rss file tool lifecycle
fffonion Sep 4, 2026
305e813
fix(tools): preserve rss search path errors
fffonion Sep 4, 2026
517a068
fix(tools): match native rss search errors
fffonion Sep 4, 2026
8fa2f8b
fix(tools): harden rss file tool bounds
fffonion Sep 4, 2026
7f6f4ec
fix(tools): match rss file scan budgets
fffonion Sep 4, 2026
a3ddf52
fix(tools): preserve rss scan accounting
fffonion Sep 4, 2026
e6ff324
fix(tools): match rss search depth accounting
fffonion Sep 4, 2026
47388ee
fix(tools): match rss scan cap precedence
fffonion Sep 4, 2026
7cd8a31
fix(tools): reject rss search alias entries
fffonion Sep 4, 2026
f24a454
fix(tools): order rss scan budget before alias policy
fffonion Sep 4, 2026
834aef4
feat(tools): implement file mutation in rss
fffonion Sep 4, 2026
1186c2e
fix(tools): align rss file mutation lifecycle
fffonion Sep 4, 2026
ce9617d
fix(tools): harden rss file mutation parity
fffonion Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
386 changes: 381 additions & 5 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ hyper = { version = "1", features = ["client", "http1"] }
hyper-util = { version = "0.1", features = ["client-legacy", "http1", "tokio"] }
parking_lot = "0.12"
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
rustscript-vm = { package = "pd-vm", git = "https://github.com/rustscript-lang/rustscript.git", rev = "5c328b8d5c374b365a2560925204e588b575a30a", default-features = false, features = ["runtime", "http-client", "sqlite"] }
rustscript-vm = { package = "pd-vm", git = "https://github.com/rustscript-lang/rustscript.git", rev = "f9ca4143f8ba2f486e270347504c49f5ea846097", default-features = false, features = ["runtime", "http-client", "sqlite"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Meta-schema validation only; resolver features stay disabled.
jsonschema = { version = "0.52.1", default-features = false }
libc = "0.2.189"
tokio = { version = "1", features = ["full"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] }
tower = { version = "0.5", features = ["util"] }
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,16 @@ placeholder route is advertised.
| API hardening (A7): bounded per-peer-IP/per-account rate limiting, client-disconnect policy | Implemented (disabled by default; see [docs/configuration.md](docs/configuration.md)) |
| Observability (A9): bounded metrics registry, `GET /metrics`, structured terminal tracing | Implemented (see [docs/deployment.md](docs/deployment.md)) |
| Provider protocol adapters (OpenAI Chat/Responses, Anthropic Messages, provider profiles) | **Partial — blocked by core (A3)**. Wire building, standard-shape guard, marker-preservation, and structured provider errors are green; buffered response parsing, streaming, and the Responses/Anthropic adapters stay typed `not_implemented` stubs until core compiler defects are fixed. See [plans/2026-08-13_a3-provider-core-blocker.md](plans/2026-08-13_a3-provider-core-blocker.md). |
| RSS serial loop + durable compaction policies (A5) | **Policies implemented and tested** (`rss/agent/main.rss`, `rss/agent/compact.rss` with executable suites); the production entry is **not wired** into the gateway/service yet (blocked by A3/A4). See [plans/2026-08-13_a5-scope-split.md](plans/2026-08-13_a5-scope-split.md). |
| RSS serial loop + durable compaction policies (A5) | Serial loop is wired into the library `AgentService` worker via bundled `rss/agent/main.rss`. Compaction policies remain implemented and tested (`rss/agent/compact.rss`). OpenAI-compatible buffered/streaming adapters stay A3-blocked; the gateway binary still runs `RUSTSCRIPT_AGENT_SCRIPT` and does not add an OpenAI-compatible inference path. See [plans/2026-08-13_a5-scope-split.md](plans/2026-08-13_a5-scope-split.md). |
| Native coding tools + workspace-confined loop | Implemented for the library worker: `read_file`, `search_files`, `write_file`, `patch`, `terminal`, `process` run serially through the native registry. Local E2E: `cargo test --test coding_agent_e2e_tests` and `cargo test --test coding_agent_edge_e2e_tests`. See [docs/configuration.md](docs/configuration.md). Parallel tools remain excluded (A6). |
| Harness and approval machinery (A4) | Not implemented (excluded from the current milestone scope); approval **repository** CRUD exists, there is no approval flow driving runs |
| Parallel tools and subagents (A6) | Not implemented (excluded from the current milestone scope) |
| Scheduled / durable job execution | **Not implemented (explicitly excluded)**. Job CRUD, pause/resume, and latest-output routes exist, but there is no scheduler; `POST /api/jobs/{id}/run` is intentionally absent and answers `404`. |
| Telegram gateway (A8) | **Implemented**: native Bot API transport (https/rustls), deny-by-default account/chat/user allowlists, durable delivery cursors (at-least-once), bounded 429/5xx/401 retries, fail-closed first-boot drain, bounded shutdown drain. See [docs/deployment.md](docs/deployment.md). |

Current lifecycle/reliability behavior is covered by the integration
suites in `tests/` (admission, bounded delivery, terminal-commit retries,
restart recovery, storage stalls); CI runs them with
`cargo test --locked --all-features --all-targets`.
restart recovery, storage stalls, coding-agent E2E). CI runs them with
`cargo test --locked --all-features --all-targets`. The main coding
workflow E2E is `cargo test --test coding_agent_e2e_tests`. Cancellation
and output-limit edges are `cargo test --test coding_agent_edge_e2e_tests`.
124 changes: 124 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,130 @@ page bounds.
| `RUN_EPOCH_DEADLINE_TICKS` | 1 000 000 000 | Epoch budget granted to one cancellable run; the cancellation watcher jumps the epoch past it. |
| `RUN_EPOCH_CHECK_INTERVAL` | 1 000 | Interpreter operations between epoch checks on cancellable runs. |

## Coding tools and serial loop

The library `AgentService` worker compiles bundled `rss/agent/main.rss` and
drives a **serial** native tool loop. RSS builds canonical provider requests
and dispatches tools only through the native host bridges
(`agent::provider_call`, `agent::tool_dispatch`). This is not an
OpenAI-compatible inference path.

Built-in native tools, in registry order:

| Name | Toolset | Risk | Notes |
| --- | --- | --- | --- |
| `read_file` | coding | read | Bounded workspace file read. |
| `search_files` | coding | read | Bounded workspace search. |
| `write_file` | coding | write | Write complete workspace file contents. |
| `patch` | coding | write | Minimal unique-string replacement. |
| `terminal` | process | process | Direct `argv` execution; no shell command string. |
| `process` | process | process | Background/control sibling of `terminal`. |

Parallel tool calls are rejected (`unsupported_parallel`). Subagents and A6
parallel fan-out are out of scope.

## Workspace guidance, priority, and budgets

Admission freezes one coding system prompt from the run workspace. Root-level
guidance files are read in this priority, highest first: `AGENTS.md`,
`CLAUDE.md`, `.cursorrules`. Default `CodingPromptBudgets` are 16 KiB total
prompt, 8 KiB combined guidance, and 4 KiB per guidance file. Each admitted
file is length-prefixed as untrusted content so project bytes cannot forge
later contract sections. The frozen prompt is reused as the sole system
message on every subsequent provider request for that run.

## Provider profiles

`ProviderProfile` is a validated, secret-safe snapshot retained on
`AgentService`. Built-in names map protocol labels only (`local-agent` →
`local-agent`, `openai` / `openai-compatible` → `openai-chat-completions`).
Options are request-shaping controls (`profile`, `protocol`,
`reasoning_effort`, `base_url`, sampling numbers). Credential-bearing keys,
headers, and unsafe URLs are rejected rather than redacted. Profiles do not
grant network access; HTTP remains deny-by-default unless hosts **and** ports
are allowlisted.

## Run limits, deadline, and cancellation

`RunLimits` (`max_turns`, `max_tool_calls`, `max_tool_output_bytes`,
`workspace_root`) are captured at admission. `workspace_root` must be an
absolute existing directory and is canonicalized. `AgentGatewayConfig.run_timeout`
is the per-run wall-clock deadline; it is not reset per provider or tool
call. `stop` requests cooperative cancellation once: the provider call, RSS
run, and native process/terminal children share the run token.
`cancellation_grace` bounds how long the worker waits after a deadline before
the thread is abandoned. Client-disconnect policy is independent
(`keep-running` by default).

## Durable replay

`DurableProviderHost` is the production provider seam. Before each fresh inner
call it commits a sanitized `model.requested` boundary (`retry_safe` plus a
`sha256:` fingerprint; never `request`/`messages`/`prompt`/`provider_options`/
`api_key`/`headers`/`body`). Completed canonical provider steps
(`model.completed` plus the assistant message) replay on restart without an
inner call or a second `turns` increment. Pending retry-safe requests retry the
same logical turn and do not synthesize an assistant/tool parent. Pending
requests that are not retry-safe, lack a fingerprint, leak secret keys, or
already have a later tool effect fail closed (`interrupted_provider`) with no
provider or tool effect.

Native dispatch is durable-first. Assistant `tool_call` parents and user
`tool_result` messages carry `parent_message_id` and monotonic `ordinal`
values. A missing or name-mismatched parent fails closed (`missing_tool_parent`)
and does not run the executor. Replaying an already durable `ToolResult` does
not re-account metrics.

Exactly-once delivery to an external receiver is impossible: event delivery is
at-least-once. Durable replay guarantees the agent does not duplicate tool
effects or provider-step rows; subscribers may observe the same durable event
more than once.

## Coding metrics

Five saturating coding-agent counters are recorded without prompts, paths, or
raw outputs:

| Metric | Prometheus name | Counted when |
| --- | --- | --- |
| `model_calls` | `agent_model_calls_total` | Each actual `AgentProviderHost::call` |
| `tool_calls` | `agent_tool_calls_total` | Each freshly executed or failed tool dispatch |
| `tool_failures` | `agent_tool_failures_total` | Canonical `ToolResult.ok == false` |
| `turns` | `agent_turns_total` | Successful `ok: true` provider envelopes |
| `truncations` | `agent_truncations_total` | Typed `truncated` on a model envelope or tool result |

## Security confinement

Coding file tools and `terminal`/`process` are confined to the admitted
`workspace_root`. `terminal` executes `argv` directly; a `command` shell
string is rejected (`invalid_argv`). Default HTTP policy denies all hosts and
ports. The coding loop E2E uses `ScriptedProvider` as model transport and the
`local-agent` profile so it cannot fall through to an OpenAI-compatible
network adapter.

## Local coding-agent E2E

The main real coding workflow is covered by:

```bash
cargo test --test coding_agent_e2e_tests
```

Stop-during-terminal cancellation and bounded output-limit overflow are
covered by:

```bash
cargo test --test coding_agent_edge_e2e_tests
```

The main suite generates a temporary git workspace, drives the production
`AgentService` worker and bundled RSS loop, and asserts a real `read_file` →
`patch` → `terminal` argv test run. The edge suite asserts stop-during-terminal
child cleanup, exact tool lifecycle, durable parent/name/ordinal chaining,
truncated overflow artifacts, that reopening a completed run is a no-op, and
pending provider-turn restart: retry-safe replay/retry, completed-step replay
fidelity, unsafe fail-closed, and no duplicate tool effect or metric count.

## Secrets

- `RUSTSCRIPT_AGENT_BEARER_TOKEN` and `RUSTSCRIPT_AGENT_TELEGRAM_BOT_TOKEN`
Expand Down
Loading