From 9467594f6db63d2c68b979f1784d3f95a07bfde7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 25 Sep 2026 12:13:12 +0000 Subject: [PATCH] docs: community presence for the AgentOS oasis Rewrite README for engineers, add VISION/architecture/good-first-issues, Code of Conduct, Security, issue/PR templates, CI workflow, and repository About paste checklist. Bump to v0.2.0 (Phase 1). Co-authored-by: New World Coder --- .github/ISSUE_TEMPLATE/bug_report.md | 26 +++++ .github/ISSUE_TEMPLATE/config.yml | 11 ++ .github/ISSUE_TEMPLATE/feature_request.md | 16 +++ .github/ISSUE_TEMPLATE/good_first_issue.md | 20 ++++ .github/PULL_REQUEST_TEMPLATE.md | 14 +++ .github/repository-meta.md | 43 ++++++++ .github/workflows/ci.yml | 38 +++++++ CODE_OF_CONDUCT.md | 31 ++++++ CONTRIBUTING.md | 23 ++-- Cargo.lock | 18 ++-- Cargo.toml | 2 +- LIFECYCLE.md | 4 +- README.md | 118 +++++++++++++++------ SECURITY.md | 28 +++++ VISION.md | 45 ++++++++ crates/agentos-api/src/lib.rs | 2 +- crates/agentos-cli/src/main.rs | 21 ++-- crates/agentos-core/src/journal.rs | 30 ++++-- crates/agentos-core/src/run.rs | 11 +- crates/agentos-runtime/src/engine.rs | 40 ++++--- crates/agentos-store/src/memory.rs | 5 +- crates/agentos-store/src/sqlite.rs | 31 ++++-- docs/ARCHITECTURE.md | 57 ++++++++++ docs/GOOD_FIRST_ISSUES.md | 35 ++++++ docs/RELEASE_NOTES.md | 17 +++ 25 files changed, 576 insertions(+), 110 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/good_first_issue.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/repository-meta.md create mode 100644 .github/workflows/ci.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 SECURITY.md create mode 100644 VISION.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/GOOD_FIRST_ISSUES.md create mode 100644 docs/RELEASE_NOTES.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..2260e18 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,26 @@ +--- +name: Bug report +about: Something broken in the runtime, store, policy, or API +title: "[bug] " +labels: bug +--- + +## What happened + +## What you expected + +## Repro + +```bash +cargo run -p agentos-cli -- ... +``` + +## Environment + +- OS: +- `rustc --version`: +- AgentOS commit / tag: + +## Journal / logs + +Paste relevant journal event types or errors (redact secrets). diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..365d63e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: Vision & oasis movement + url: https://github.com/new-world-coder/agentos/blob/main/VISION.md + about: Why AgentOS exists and how we grow as a community + - name: Good first issues guide + url: https://github.com/new-world-coder/agentos/blob/main/docs/GOOD_FIRST_ISSUES.md + about: Starter tasks before you open a large PR + - name: Lifecycle (honest phases) + url: https://github.com/new-world-coder/agentos/blob/main/LIFECYCLE.md + about: What is done vs not claimed yet diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..8b0bb70 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature request +about: Propose work aligned with LIFECYCLE.md +title: "[feat] " +labels: enhancement +--- + +## Problem + +## Proposal + +## Lifecycle phase + +Which phase does this belong to? (0–4) Link exit criteria if extending them. + +## Alternatives considered diff --git a/.github/ISSUE_TEMPLATE/good_first_issue.md b/.github/ISSUE_TEMPLATE/good_first_issue.md new file mode 100644 index 0000000..2e29548 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/good_first_issue.md @@ -0,0 +1,20 @@ +--- +name: Good first issue +about: Mentored starter task for new contributors +title: "[good first] " +labels: good first issue, help wanted +--- + +## Task + +## Why it’s a good first issue + +## Acceptance criteria + +- [ ] +- [ ] +- [ ] `cargo test --workspace` and clippy `-D warnings` pass + +## Mentorship notes + +Point to crates/files to touch. Link [docs/GOOD_FIRST_ISSUES.md](../../docs/GOOD_FIRST_ISSUES.md). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4aa76aa --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +## Summary + +## Lifecycle / ADR impact + +- [ ] No phase claim change +- [ ] Updates LIFECYCLE.md / ADR (linked) + +## Test plan + +- [ ] `cargo test --workspace` +- [ ] `cargo clippy --workspace --all-targets -- -D warnings` +- [ ] (if runtime/store) crash-resume or journal verify covered + +## Notes diff --git a/.github/repository-meta.md b/.github/repository-meta.md new file mode 100644 index 0000000..f24bbe8 --- /dev/null +++ b/.github/repository-meta.md @@ -0,0 +1,43 @@ +# Repository About metadata + +GitHub Apps used by cloud agents often **cannot** PATCH description/topics (403). +A maintainer should copy this into the GitHub UI once. + +## Settings → General → About + +**Description** (short): + +```text +Durable Rust agent runtime: hash-chained effect journal, policy-before-effect, HITL, crash-resume. Apache-2.0 oasis for auditable agents. +``` + +**Website** (optional): + +```text +https://github.com/new-world-coder/agentos +``` + +**Topics** (add as tags): + +```text +rust +agents +ai-agents +llm +agent-runtime +durable-execution +crash-recovery +sqlite +axum +hitl +policy-engine +apache-2 +open-source +``` + +Check: ☑ Issues · ☑ Preserve this repository | Discussions (optional, recommended later) + +## Releases + +Tags `v0.1.0` (Phase 0) and `v0.2.0` (Phase 1) should appear under **Releases**. +If a tag exists without a Release, click **Draft a release** → choose the tag → publish using notes in `docs/RELEASE_NOTES.md`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..52f4374 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -D warnings + +jobs: + test: + name: test + clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: "1.85.0" + components: clippy, rustfmt + + - uses: Swatinem/rust-cache@v2 + + - name: fmt + run: cargo fmt --all -- --check + + - name: clippy + run: cargo clippy --workspace --all-targets -- -D warnings + + - name: test + run: cargo test --workspace + + - name: doctor + run: cargo run -p agentos-cli -- doctor diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c46cb2b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,31 @@ +# Code of Conduct + +## Our pledge + +We pledge to make participation in AgentOS a harassment-free experience for everyone, +regardless of age, body size, visible or invisible disability, ethnicity, sex +characteristics, gender identity and expression, experience level, education, +socio-economic status, nationality, personal appearance, race, caste, color, religion, +or sexual identity and orientation. + +## Our standards + +Examples of behavior that contributes to a positive environment: + +- Demonstrating empathy and kindness +- Giving and gracefully accepting constructive feedback +- Focusing on what is best for the community and the runtime’s invariants +- Crediting others’ ideas + +Examples of unacceptable behavior: + +- Harassment, trolling, insulting or derogatory comments +- Publishing others’ private information +- Sexualized language or imagery, or unwanted sexual attention +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement + +Report issues to the maintainers via GitHub (private security contact in [SECURITY.md](SECURITY.md) +for sensitive matters). Maintainers may warn, limit, or ban contributors who violate +this Code. This project follows the spirit of the [Contributor Covenant](https://www.contributor-covenant.org/) v2.1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 395bbe9..03d7de5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,21 +1,26 @@ # Contributing to AgentOS -Thanks for helping build a durable agent runtime. +Thanks for helping grow the [oasis](VISION.md)—a durable, auditable agent runtime. + +## Before you code + +1. Skim [VISION.md](VISION.md) and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). +2. Prefer a [good first issue](docs/GOOD_FIRST_ISSUES.md) or file one with the template. +3. Keep phase claims honest per [LIFECYCLE.md](LIFECYCLE.md). ## Development ```bash cargo test --workspace cargo clippy --workspace --all-targets -- -D warnings -cargo fmt --all +cargo fmt --all -- --check cargo run -p agentos-cli -- doctor ``` -Use a SQLite path when testing crash-resume: +Durable resume smoke: ```bash cargo run -p agentos-cli -- --db /tmp/agentos.db run "goal" -# kill -9 the process mid-run in real tests, then: cargo run -p agentos-cli -- --db /tmp/agentos.db resume ``` @@ -28,12 +33,12 @@ cargo run -p agentos-cli -- --db /tmp/agentos.db resume ## Pull requests -- Keep PRs focused; prefer small crates-scoped changes. +- Use the PR template; keep changes focused. - Include tests for journal, policy, and resume paths you touch. -- Follow Apache-2.0; do not add incompatible licenses. -- Link any ADR updates in `docs/adr/`. +- Apache-2.0 only—no incompatible licenses. +- Link ADR updates under `docs/adr/`. ## Code of conduct -Be respectful. No harassment, personal attacks, or gatekeeping. Maintainers may -moderate discussions and reject contributions that violate these norms. +See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Maintainers may moderate and reject +contributions that violate community standards. diff --git a/Cargo.lock b/Cargo.lock index 44ebded..82d0547 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,10 +1,10 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "agentos-api" -version = "0.1.0" +version = "0.2.0" dependencies = [ "agentos-core", "agentos-policy", @@ -25,7 +25,7 @@ dependencies = [ [[package]] name = "agentos-cli" -version = "0.1.0" +version = "0.2.0" dependencies = [ "agentos-core", "agentos-policy", @@ -43,7 +43,7 @@ dependencies = [ [[package]] name = "agentos-core" -version = "0.1.0" +version = "0.2.0" dependencies = [ "chrono", "hex", @@ -56,7 +56,7 @@ dependencies = [ [[package]] name = "agentos-policy" -version = "0.1.0" +version = "0.2.0" dependencies = [ "agentos-core", "serde", @@ -66,7 +66,7 @@ dependencies = [ [[package]] name = "agentos-provider" -version = "0.1.0" +version = "0.2.0" dependencies = [ "agentos-core", "async-trait", @@ -78,7 +78,7 @@ dependencies = [ [[package]] name = "agentos-runtime" -version = "0.1.0" +version = "0.2.0" dependencies = [ "agentos-core", "agentos-policy", @@ -96,7 +96,7 @@ dependencies = [ [[package]] name = "agentos-store" -version = "0.1.0" +version = "0.2.0" dependencies = [ "agentos-core", "anyhow", @@ -113,7 +113,7 @@ dependencies = [ [[package]] name = "agentos-tools" -version = "0.1.0" +version = "0.2.0" dependencies = [ "agentos-core", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 668a944..5ef9a4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ default-members = [ ] [workspace.package] -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "Apache-2.0" authors = ["AgentOS Contributors"] diff --git a/LIFECYCLE.md b/LIFECYCLE.md index 4d1c375..61ecf44 100644 --- a/LIFECYCLE.md +++ b/LIFECYCLE.md @@ -59,7 +59,9 @@ application, journal a hash chain, pause for HITL, and expose a mock-backed CLI | Tag | Meaning | |-----|---------| | `v0.1.0` | Phase 0 complete | -| `v0.2.0` | Phase 1 complete | +| `v0.2.0` | Phase 1 complete (+ community presence) | | `v0.3.0+` | Later phases | Phase numbers in docs must stay honest: never claim Phase 2–4 done until exit criteria land. + +Release narrative for humans: [docs/RELEASE_NOTES.md](docs/RELEASE_NOTES.md). diff --git a/README.md b/README.md index 5285fbe..0f756d7 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,108 @@ # AgentOS -Durable Rust agent runtime with an append-only effect journal, policy-before-effect -gates, human-in-the-loop approvals, and crash-resume. +**A durable agent runtime for people who refuse to ship flaky loops.** -**License:** Apache-2.0 · **Status:** Phase 1 (SQLite store + crash-resume) +AgentOS is an Apache-2.0 **Rust** workspace where every side effect is proposed, policy-checked, optionally human-approved, applied, and recorded on a **hash-chained journal**—so a killed process can **resume** instead of gaslighting you. -## Crates +[![CI](https://github.com/new-world-coder/agentos/actions/workflows/ci.yml/badge.svg)](https://github.com/new-world-coder/agentos/actions/workflows/ci.yml) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) +[![Rust](https://img.shields.io/badge/rust-1.85%2B-orange.svg)](rust-toolchain.toml) +[![Release](https://img.shields.io/github/v/release/new-world-coder/agentos?include_prereleases)](https://github.com/new-world-coder/agentos/releases) -| Crate | Role | -|-------|------| -| `agentos-core` | Runs, effects, journal entries, hash chain | -| `agentos-store` | `Store` trait, in-memory + SQLite backends | -| `agentos-provider` | Provider trait + mock LLM | -| `agentos-tools` | Tool registry (`echo`, `add`) | -| `agentos-policy` | Policy-before-effect engine | -| `agentos-runtime` | Durable propose → policy → HITL → apply loop | -| `agentos-api` | Axum HTTP API | -| `agentos-cli` | CLI (`doctor`, `run`, `resume`, …) | +> **The oasis idea:** the agent ecosystem is a desert of prompt spaghetti, silent tool calls, and “it worked on my laptop” demos. AgentOS is building an **oasis**—durable execution, auditable journals, and policy that actually runs *before* the effect. Join the movement: [VISION.md](VISION.md) · [good first issues](docs/GOOD_FIRST_ISSUES.md) · [Contributing](CONTRIBUTING.md) + +--- + +## Why engineers care -## Quick start +| Usual agent loop | AgentOS | +|------------------|---------| +| State dies with the process | SQLite-backed runs + crash-resume | +| “Please don’t do bad things” in the system prompt | **Policy-before-effect** in-process | +| Mystery side effects | Append-only **effect journal** + SHA-256 chain | +| HITL as a Slack hack | First-class `awaiting_approval` status | +| One giant Python file | Clear Rust crates you can embed | + +Honest scope: **Phase 0 + Phase 1 are done** (`v0.1.0` / Phase 1 complete). Real model providers, multi-node workers, and a full control plane are **later**—see [LIFECYCLE.md](LIFECYCLE.md). We advertise what we ship. + +--- + +## 60-second start ```bash -cargo test --workspace -cargo clippy --workspace --all-targets -- -D warnings +git clone https://github.com/new-world-coder/agentos.git +cd agentos cargo run -p agentos-cli -- doctor -# Ephemeral run (in-memory) -cargo run -p agentos-cli -- run "say hello" - -# Durable run (SQLite) +# Durable run (survives process death) cargo run -p agentos-cli -- --db ./agentos.db run "say hello" cargo run -p agentos-cli -- --db ./agentos.db resume # HTTP API cargo run -p agentos-api -- --db ./agentos.db --bind 127.0.0.1:8080 +curl -s localhost:8080/health +``` + +```bash +cargo test --workspace +cargo clippy --workspace --all-targets -- -D warnings +``` + +--- + +## Architecture (one picture) + +```mermaid +flowchart LR + Provider -->|ProposedEffect| Policy + Policy -->|Allow| Apply + Policy -->|Deny| Fail + Policy -->|RequireApproval| HITL + HITL -->|Approve| Apply + Apply --> Journal + Journal -->|hash chain| Store[(SQLite / Memory)] + Store -->|resume| Runtime ``` -## Design highlights +**Invariant:** no tool runs until policy (or a human) allows it. Every lifecycle event is journaled; resume verifies the chain first. + +Deeper dive: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) · ADRs in [docs/adr/](docs/adr/) + +--- + +## Workspace + +| Crate | Role | +|-------|------| +| `agentos-core` | Runs, effects, journal, hash chain | +| `agentos-store` | `Store` trait · memory + **SQLite** | +| `agentos-provider` | Provider trait · **mock** LLM | +| `agentos-tools` | Tool registry (`echo`, `add`, …) | +| `agentos-policy` | Allow / deny / require-approval | +| `agentos-runtime` | Propose → policy → HITL → apply | +| `agentos-api` | Axum HTTP (`/v1/runs`, drive, resume, approve) | +| `agentos-cli` | `doctor`, `run`, `resume`, `approve`, … | + +--- + +## Contribute — grow the oasis + +We want systems engineers, Rustaceans, and agent-infra folks—not drive-by README edits alone. + +1. Read [VISION.md](VISION.md) (the movement) and [LIFECYCLE.md](LIFECYCLE.md) (what’s honest). +2. Pick a [good first issue](docs/GOOD_FIRST_ISSUES.md) or open a discussion issue. +3. Follow [CONTRIBUTING.md](CONTRIBUTING.md) · [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) · [GOVERNANCE.md](GOVERNANCE.md). + +**High-leverage Phase 2 help:** real `Provider` adapters, richer tools + schemas, streaming, stronger policy profiles. See lifecycle Phase 2+. + +--- + +## Repo “About” (GitHub UI) -- **Effect journal + hash chain** — every lifecycle event is appended and chained with SHA-256. -- **Policy-before-effect** — tools never run until the policy engine allows (or HITL approves). -- **HITL** — runs pause at `awaiting_approval` and resume after approve/reject. -- **Crash-resume** — SQLite persists runs/effects/journal; `resume` verifies the chain and continues. +This token can’t edit GitHub **Description / Topics** from CI. Maintainers: paste from [`.github/repository-meta.md`](.github/repository-meta.md) into **Settings → General → About** (takes ~30 seconds). -See [LIFECYCLE.md](LIFECYCLE.md) for phases, [docs/](docs/) for ADRs, and -[canvases/](canvases/) for the competitive landscape. +--- -## What this is not (yet) +## License -Phases 2–4 are **not** done: no multi-tenant control plane, no real model providers in -production posture, no distributed workers. See LIFECYCLE.md. +[Apache-2.0](LICENSE) — use it, embed it, fork it, build on it. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..9873664 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,28 @@ +# Security Policy + +## Supported versions + +| Version | Supported | +|---------|-----------| +| `v0.2.x` (Phase 1) | ✅ | +| `v0.1.x` | ✅ (security fixes cherry-picked when practical) | +| unreleased `main` | best-effort | + +## What counts as a vulnerability + +Please report: + +- Ways to bypass **policy-before-effect** (tool runs without allow/HITL) +- Journal / hash-chain integrity bugs that allow silent tampering +- Crash-resume corruption or privilege issues in the SQLite store +- RCE or path traversal via tools/API once real tools land + +Out of scope for now: “the mock provider is not a real LLM” and missing Phase 2+ features. + +## How to report + +Open a **private** GitHub Security Advisory on this repository if available, or email the +repository owner listed on the GitHub profile. Do **not** file a public issue for +exploitable bugs until a fix or mitigation is ready. + +We aim to acknowledge within 7 days and coordinate disclosure. diff --git a/VISION.md b/VISION.md new file mode 100644 index 0000000..ed5c184 --- /dev/null +++ b/VISION.md @@ -0,0 +1,45 @@ +# Vision — the AgentOS oasis + +## The desert + +Most “agents” today are: + +- a `while` loop around a chat API, +- tools invoked on trust from a prompt, +- state that evaporates when the process dies, +- audits that are log lines if you’re lucky. + +That works for demos. It fails for products that must be **resumable**, **reviewable**, and **constrainable**. + +## The oasis + +AgentOS is a bet that agent infrastructure should look more like **durable execution** and less like improv theatre: + +1. **Effects are first-class** — proposed, decided, applied, recorded. +2. **Policy runs before the effect** — deny / allow / human approval in-process. +3. **The journal is the source of truth** — append-only, hash-chained, verifiable on resume. +4. **Rust crates you can embed** — not a hosted black box you can’t reason about. + +An oasis is not a mirage: we only claim phases that pass [LIFECYCLE.md](LIFECYCLE.md) exit criteria. + +## The movement + +“Oasis” means a community that: + +- prefers **invariants** over vibes, +- documents architecture with **ADRs**, +- welcomes contributors who care about **crash-resume, policy, and audit**, +- stays **Apache-2.0** and fork-friendly, +- grows by shipping boring, correct runtime pieces—not hype launches. + +If that resonates, you belong here. Start at [docs/GOOD_FIRST_ISSUES.md](docs/GOOD_FIRST_ISSUES.md). + +## Non-goals (for now) + +- Being the biggest prompt framework +- Hiding policy inside the model +- Claiming Phases 2–4 before they exist + +## North star + +An engineer can kill `-9` an AgentOS process mid-tool-loop, restart, **verify the journal**, resume, and show a reviewer *exactly* what was proposed, allowed, and applied. diff --git a/crates/agentos-api/src/lib.rs b/crates/agentos-api/src/lib.rs index 145023a..b00adfa 100644 --- a/crates/agentos-api/src/lib.rs +++ b/crates/agentos-api/src/lib.rs @@ -4,7 +4,7 @@ mod routes; mod state; pub use routes::router; -pub use state::{AppState, build_runtime}; +pub use state::{build_runtime, AppState}; use std::net::SocketAddr; use std::path::PathBuf; diff --git a/crates/agentos-cli/src/main.rs b/crates/agentos-cli/src/main.rs index 0ef6534..e4b8115 100644 --- a/crates/agentos-cli/src/main.rs +++ b/crates/agentos-cli/src/main.rs @@ -34,13 +34,9 @@ enum Commands { drive: bool, }, /// Drive an existing run - Drive { - run_id: String, - }, + Drive { run_id: String }, /// Resume a run after crash / restart - Resume { - run_id: String, - }, + Resume { run_id: String }, /// Approve or reject a pending effect Approve { run_id: String, @@ -51,13 +47,9 @@ enum Commands { reason: Option, }, /// Show run status - Status { - run_id: String, - }, + Status { run_id: String }, /// Print journal for a run - Journal { - run_id: String, - }, + Journal { run_id: String }, } fn build_runtime(db: Option) -> anyhow::Result { @@ -200,7 +192,10 @@ async fn doctor(db: Option) -> anyhow::Result<()> { } let journal = rt.store().list_journal(&run.id).await?; agentos_core::verify_chain(&journal)?; - println!("journal: ok ({} entries, hash chain valid)", journal.len()); + println!( + "journal: ok ({} entries, hash chain valid)", + journal.len() + ); } println!("policy: policy-before-effect enabled"); diff --git a/crates/agentos-core/src/journal.rs b/crates/agentos-core/src/journal.rs index 7282456..5a71990 100644 --- a/crates/agentos-core/src/journal.rs +++ b/crates/agentos-core/src/journal.rs @@ -110,17 +110,35 @@ mod tests { #[test] fn chain_verifies() { - let e0 = JournalEntry::append("r1", 0, "run_created", json!({"goal": "x"}), Hash::genesis()) - .unwrap(); - let e1 = JournalEntry::append("r1", 1, "effect_proposed", json!({"id": "e1"}), e0.hash.clone()) - .unwrap(); + let e0 = JournalEntry::append( + "r1", + 0, + "run_created", + json!({"goal": "x"}), + Hash::genesis(), + ) + .unwrap(); + let e1 = JournalEntry::append( + "r1", + 1, + "effect_proposed", + json!({"id": "e1"}), + e0.hash.clone(), + ) + .unwrap(); verify_chain(&[e0, e1]).unwrap(); } #[test] fn tampered_payload_fails() { - let mut e0 = JournalEntry::append("r1", 0, "run_created", json!({"goal": "x"}), Hash::genesis()) - .unwrap(); + let mut e0 = JournalEntry::append( + "r1", + 0, + "run_created", + json!({"goal": "x"}), + Hash::genesis(), + ) + .unwrap(); e0.payload = json!({"goal": "tampered"}); assert!(e0.verify_hash().is_err()); } diff --git a/crates/agentos-core/src/run.rs b/crates/agentos-core/src/run.rs index 25dd06c..1d80285 100644 --- a/crates/agentos-core/src/run.rs +++ b/crates/agentos-core/src/run.rs @@ -62,10 +62,7 @@ impl RunStatus { } pub fn is_resumable(self) -> bool { - matches!( - self, - Self::Pending | Self::Running | Self::AwaitingApproval - ) + matches!(self, Self::Pending | Self::Running | Self::AwaitingApproval) } } @@ -124,7 +121,11 @@ impl Message { } } - pub fn tool(tool_call_id: impl Into, name: impl Into, content: impl Into) -> Self { + pub fn tool( + tool_call_id: impl Into, + name: impl Into, + content: impl Into, + ) -> Self { Self { role: Role::Tool, content: content.into(), diff --git a/crates/agentos-runtime/src/engine.rs b/crates/agentos-runtime/src/engine.rs index 33d9e23..a193ed8 100644 --- a/crates/agentos-runtime/src/engine.rs +++ b/crates/agentos-runtime/src/engine.rs @@ -79,7 +79,8 @@ impl Runtime { pub async fn create_run(&self, goal: impl Into) -> Result { let goal = goal.into(); let mut run = Run::new(goal.clone()); - run.messages.push(Message::system(&self.config.system_prompt)); + run.messages + .push(Message::system(&self.config.system_prompt)); run.messages.push(Message::user(&goal)); self.store.create_run(&run).await?; let entry = JournalEntry::append( @@ -102,12 +103,11 @@ impl Runtime { let mut steps = 0u32; loop { if steps >= self.config.max_steps { - return self - .fail_run(run_id, "max steps exceeded") - .await - .map(|_| StepOutcome::Failed { + return self.fail_run(run_id, "max steps exceeded").await.map(|_| { + StepOutcome::Failed { error: "max steps exceeded".into(), - }); + } + }); } steps += 1; @@ -131,10 +131,9 @@ impl Runtime { } if run.status == RunStatus::AwaitingApproval { - let effect_id = run - .pending_effect_id - .clone() - .ok_or_else(|| CoreError::InvalidState("awaiting approval without effect".into()))?; + let effect_id = run.pending_effect_id.clone().ok_or_else(|| { + CoreError::InvalidState("awaiting approval without effect".into()) + })?; return Ok(StepOutcome::AwaitingApproval { effect_id }); } @@ -244,12 +243,8 @@ impl Runtime { effect.status = EffectStatus::Approved; effect.touch(); self.store.put_effect(&effect).await?; - self.journal( - &run, - "effect_approved", - json!({ "effect_id": effect_id }), - ) - .await?; + self.journal(&run, "effect_approved", json!({ "effect_id": effect_id })) + .await?; run.status = RunStatus::Running; run.touch(); self.store.update_run(&run).await?; @@ -486,7 +481,12 @@ impl Runtime { Ok(()) } - async fn journal(&self, run: &Run, event_type: &str, payload: serde_json::Value) -> Result { + async fn journal( + &self, + run: &Run, + event_type: &str, + payload: serde_json::Value, + ) -> Result { // Re-read tip to stay consistent under concurrent writers (single-writer for now). let tip = self.store.tip_hash(&run.id).await?; let journal = self.store.list_journal(&run.id).await?; @@ -623,10 +623,8 @@ mod tests { let run_id = { let store = Arc::new(SqliteStore::open(&path).unwrap()); - let provider = MockProvider::new(vec![MockScript::tool( - "echo", - json!({"text": "step1"}), - )]); + let provider = + MockProvider::new(vec![MockScript::tool("echo", json!({"text": "step1"}))]); // After tool, mock defaults to complete — so we need to stop before second turn. // Approach: manually craft a Running run after first effect via drive with // a custom provider that only returns the tool once, then we kill before drive continues diff --git a/crates/agentos-store/src/memory.rs b/crates/agentos-store/src/memory.rs index 253dc0b..0024f44 100644 --- a/crates/agentos-store/src/memory.rs +++ b/crates/agentos-store/src/memory.rs @@ -81,10 +81,7 @@ impl Store for MemoryStore { async fn append_journal(&self, entry: &JournalEntry) -> Result<()> { let mut g = self.inner.write().await; - let chain = g - .journal - .entry(entry.run_id.clone()) - .or_default(); + let chain = g.journal.entry(entry.run_id.clone()).or_default(); let expected_prev = chain .last() .map(|e| e.hash.clone()) diff --git a/crates/agentos-store/src/sqlite.rs b/crates/agentos-store/src/sqlite.rs index 78ff66c..a23241e 100644 --- a/crates/agentos-store/src/sqlite.rs +++ b/crates/agentos-store/src/sqlite.rs @@ -50,7 +50,10 @@ impl SqliteStore { } fn migrate(&self) -> Result<()> { - let conn = self.conn.lock().map_err(|e| CoreError::Store(e.to_string()))?; + let conn = self + .conn + .lock() + .map_err(|e| CoreError::Store(e.to_string()))?; conn.execute_batch( r#" CREATE TABLE IF NOT EXISTS runs ( @@ -209,8 +212,10 @@ impl Store for SqliteStore { run.id.0, run.status.as_str(), run.goal, - serde_json::to_string(&run.messages).map_err(|e| CoreError::Store(e.to_string()))?, - serde_json::to_string(&run.steps).map_err(|e| CoreError::Store(e.to_string()))?, + serde_json::to_string(&run.messages) + .map_err(|e| CoreError::Store(e.to_string()))?, + serde_json::to_string(&run.steps) + .map_err(|e| CoreError::Store(e.to_string()))?, run.tip_hash.as_str(), run.updated_at.to_rfc3339(), run.pending_effect_id, @@ -382,7 +387,8 @@ impl Store for SqliteStore { entry.run_id, entry.seq as i64, entry.event_type, - serde_json::to_string(&entry.payload).map_err(|e| CoreError::Store(e.to_string()))?, + serde_json::to_string(&entry.payload) + .map_err(|e| CoreError::Store(e.to_string()))?, entry.prev_hash.as_str(), entry.hash.as_str(), entry.at.to_rfc3339(), @@ -464,7 +470,8 @@ fn row_to_run(row: &rusqlite::Row<'_>) -> Result { id: RunId(row.get(0).map_err(|e| CoreError::Store(e.to_string()))?), status: status_from_str(&status)?, goal: row.get(2).map_err(|e| CoreError::Store(e.to_string()))?, - messages: serde_json::from_str(&messages_json).map_err(|e| CoreError::Store(e.to_string()))?, + messages: serde_json::from_str(&messages_json) + .map_err(|e| CoreError::Store(e.to_string()))?, steps: serde_json::from_str(&steps_json).map_err(|e| CoreError::Store(e.to_string()))?, tip_hash: Hash(tip), created_at: chrono::DateTime::parse_from_rfc3339(&created) @@ -516,7 +523,8 @@ fn row_to_journal(row: &rusqlite::Row<'_>) -> Result { run_id: row.get(0).map_err(|e| CoreError::Store(e.to_string()))?, seq: seq as u64, event_type: row.get(2).map_err(|e| CoreError::Store(e.to_string()))?, - payload: serde_json::from_str(&payload_json).map_err(|e| CoreError::Store(e.to_string()))?, + payload: serde_json::from_str(&payload_json) + .map_err(|e| CoreError::Store(e.to_string()))?, prev_hash: Hash(row.get(4).map_err(|e| CoreError::Store(e.to_string()))?), hash: Hash(row.get(5).map_err(|e| CoreError::Store(e.to_string()))?), at: chrono::DateTime::parse_from_rfc3339(&at) @@ -541,9 +549,14 @@ mod tests { let run = Run::new("persist me"); run_id = run.id.clone(); store.create_run(&run).await.unwrap(); - let e0 = - JournalEntry::append(&run.id.0, 0, "run_created", json!({"goal": run.goal}), Hash::genesis()) - .unwrap(); + let e0 = JournalEntry::append( + &run.id.0, + 0, + "run_created", + json!({"goal": run.goal}), + Hash::genesis(), + ) + .unwrap(); store.append_journal(&e0).await.unwrap(); let mut run = store.get_run(&run_id).await.unwrap().unwrap(); run.status = RunStatus::Running; diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..aa4ab27 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,57 @@ +# Architecture + +AgentOS separates **proposal**, **authorization**, **application**, and **durability**. + +## Control flow + +```mermaid +sequenceDiagram + participant U as User / API + participant R as Runtime + participant P as Provider + participant Pol as Policy + participant T as Tools + participant S as Store + + U->>R: create_run(goal) + R->>S: persist run + journal run_created + U->>R: drive / resume + R->>P: complete(messages, tools) + P-->>R: ProposedEffect + R->>S: journal effect_proposed + R->>Pol: evaluate(effect) + alt Deny + R->>S: journal policy_denied / fail run + else RequireApproval + R->>S: status awaiting_approval + U->>R: approve / reject + else Allow + R->>T: invoke (if tool) + R->>S: journal effect_applied + update tip_hash + end +``` + +## Hash chain + +Each `JournalEntry` hashes a canonical record including `prev_hash`. Stores reject +mismatched `seq` / `prev_hash`. `Runtime::resume` calls `verify_chain` before continuing. + +## Crate dependency direction + +```text +cli / api + ↓ +runtime → policy, provider, tools, store + ↓ +core +``` + +Edges never depend on each other. Swapping SQLite for another `Store` should not +require changing policy or providers. + +## Related ADRs + +- [0001 Rust workspace](adr/0001-rust-workspace.md) +- [0002 Effect journal](adr/0002-effect-journal.md) +- [0003 Policy before effect](adr/0003-policy-before-effect.md) +- [0004 SQLite store](adr/0004-sqlite-store.md) diff --git a/docs/GOOD_FIRST_ISSUES.md b/docs/GOOD_FIRST_ISSUES.md new file mode 100644 index 0000000..5e06467 --- /dev/null +++ b/docs/GOOD_FIRST_ISSUES.md @@ -0,0 +1,35 @@ +# Good first issues + +Welcome. These are sized for a first PR while teaching the real invariants. + +## Starter checklist + +- [ ] `cargo test --workspace` and `cargo clippy --workspace --all-targets -- -D warnings` pass locally +- [ ] Read [VISION.md](../VISION.md) and [ARCHITECTURE.md](ARCHITECTURE.md) +- [ ] Open an issue (or claim one) before a large design change + +## Ideas (file an issue, then PR) + +### Docs & DX + +1. **API curl cookbook** — document create → drive → journal → approve with example JSON. +2. **Architecture SVG** — export the mermaid diagram to a checked-in figure for GitHub mobile. +3. **Windows / macOS doctor notes** — if anything breaks outside Linux CI. + +### Runtime & store + +4. **API integration test** — spin Axum with `SqliteStore::in_memory` / tempfile; create + drive. +5. **Journal export** — CLI subcommand `journal --verify` already verifies; add `--jsonl` dump. +6. **Idempotent `put_effect` tests** — concurrent updates / status transitions. + +### Policy & tools + +7. **JSON Schema for tools** — describe `echo` / `add` inputs; validate before invoke. +8. **Policy fixture table** — data-driven allow/deny/approval cases in `agentos-policy`. + +### Phase 2 (slightly larger) + +9. **OpenAI-compatible `Provider`** behind env flag (no default network in tests). +10. **Streaming stub** — trait method + mock that yields chunks (no real HTTP required). + +When you open the issue, label it `good first issue` and `help wanted` in the GitHub UI. diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md new file mode 100644 index 0000000..c04816d --- /dev/null +++ b/docs/RELEASE_NOTES.md @@ -0,0 +1,17 @@ +# Release notes + +## v0.2.0 — Phase 1 (SQLite + crash-resume) + +- `SqliteStore` with WAL, runs / effects / journal tables +- `Runtime::resume` verifies hash chain then continues +- Crash-resume + HITL reopen tests +- CLI `--db` for durable runs +- Docs: architecture, vision (oasis), community health files, CI + +## v0.1.0 — Phase 0 (durable core) + +- Apache-2.0 Rust workspace: core, store, provider, tools, policy, runtime, api, cli +- Effect journal + SHA-256 hash chain +- Policy-before-effect and HITL gates +- Mock provider, in-memory store, Axum API, CLI `doctor` +- LIFECYCLE, ADRs, COMPETITORS, CONTRIBUTING, GOVERNANCE