Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ data/
.env
.env.*
!.env.example
.claude/
CLAUDE.md
package-lock.json
pnpm-lock.yaml
yarn.lock

200 changes: 61 additions & 139 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,45 @@
# AgentOS architecture

AgentOS is an agent operating system built on the [iii engine](https://github.com/iii-hq/iii).
The repo ships **narrow workers** (one binary per domain), declarative
configuration (hands, integrations, agents), and surfaces (`cli`, `tui`).
Everything coordinates through iii primitives — `register_function`,
`register_trigger`, `iii.trigger` — over the engine's WebSocket on port
49134. There is no agent runtime that lives outside iii.
AgentOS is an agent operating system built on the [iii engine](https://github.com/iii-hq/iii). The repo ships **65 narrow workers** (one binary per domain), declarative config (hands, integrations, agents), and two surfaces (`crates/cli`, `crates/tui`). Everything coordinates through iii primitives — `register_function`, `register_trigger`, `iii.trigger` — over the engine's WebSocket on port 49134.

## Repository layout

```
agentos/
├── workers/ Narrow iii workers (one binary each)
│ ├── agent-core/ ReAct loop agent::*
│ ├── bridge/ External runtime adapters bridge::*
│ ├── council/ Proposals + activity log council::*
│ ├── directive/ Hierarchical goal alignment directive::*
│ ├── embedding/ SentenceTransformers (Python) embedding::*
│ ├── hierarchy/ Org graph (cycle-safe) hierarchy::*
│ ├── ledger/ Budget + spend tracking ledger::*
│ ├── llm-router/ Provider routing llm::*
│ ├── memory/ Narrow agent memory memory::*
│ ├── mission/ Task lifecycle mission::*
│ ├── pulse/ Scheduled invocation pulse::*
│ ├── realm/ Multi-tenant isolation realm::*
│ ├── security/ Combined guardrails + audit security::*
│ └── wasm-sandbox/ wasmtime fuel-metered wasm::*
├── crates/ Surfaces (clients, not workers)
│ ├── cli/ Command-line interface
│ └── tui/ 21-screen terminal dashboard
├── src/ TypeScript workers (54 files, ~23k LOC)
│ └── ... a2a, browser, cron, evolve, swarm, etc.
├── hands/ Agent personas as TOML config (consumed by hand-runner)
├── integrations/ MCP server configs as TOML
├── agents/ Agent templates as markdown
├── config.yaml iii engine boot config (workers: + modules:)
└── .github/workflows/ci.yml End-to-end CI (build, test, e2e smoke, namespace check)
├── workers/ 64 Rust workers + 1 Python worker
├── crates/
│ ├── cli/ Command-line client (HTTP → iii-http on 3111)
│ └── tui/ Terminal dashboard
├── e2e/ vitest end-to-end suite (live engine + workers)
├── tests/ Rust integration tests
├── hands/ Agent personas (TOML, consumed by hand-runner)
├── integrations/ MCP server configs (TOML, consumed by mcp-client)
├── agents/ Agent templates (markdown)
├── workflows/ Pre-defined workflow YAMLs
├── plugin/ Reusable agent/command/skill/hook bundles
├── config.yaml iii engine boot config
└── .github/workflows/ci.yml Build + test + e2e
```
Comment on lines 7 to 22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced layout block.

Line 7 uses an unlabeled code fence, which triggers markdownlint MD040.

Suggested fix
-```
+```text
 agentos/
 ├── workers/                  64 Rust workers + 1 Python worker
 ├── crates/
 │   ├── cli/                  Command-line client (HTTP → iii-http on 3111)
 │   └── tui/                  Terminal dashboard
 ├── e2e/                      vitest end-to-end suite (live engine + workers)
 ├── tests/                    Rust integration tests
 ├── hands/                    Agent personas (TOML, consumed by hand-runner)
 ├── integrations/             MCP server configs (TOML, consumed by mcp-client)
 ├── agents/                   Agent templates (markdown)
 ├── workflows/                Pre-defined workflow YAMLs
 ├── plugin/                   Reusable agent/command/skill/hook bundles
 ├── config.yaml               iii engine boot config
 └── .github/workflows/ci.yml  Build + test + e2e
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ARCHITECTURE.md` around lines 7 - 22, The unlabeled fenced block in
ARCHITECTURE.md that contains the directory tree starting with "agentos/"
triggers markdownlint MD040; fix it by adding a language tag to the opening
fence (e.g., change ``` to ```text) so the block becomes a labeled code fence;
update the single fenced block containing the directory layout to use ```text as
the opener.


## Workers

| Group | Workers | Function namespaces |
|---|---|---|
| Reasoning | `agent-core` `llm-router` `council` `swarm` `directive` `mission` | `agent::*` `llm::*` `council::*` `swarm::*` `directive::*` `mission::*` |
| State | `realm` `memory` `ledger` `vault` `context-manager` `context-cache` | `realm::*` `memory::*` `ledger::*` `vault::*` `context::*` |
| Coordination | `orchestrator` `workflow` `hierarchy` `coordination` `task-decomposer` | `orchestrator::*` `workflow::*` `hierarchy::*` `task::*` |
| Execution | `wasm-sandbox` `browser` `code-agent` `hand-runner` `lsp-tools` | `wasm::*` `browser::*` `code::*` `hand::*` `lsp::*` |
| Safety | `security` `security-headers` `security-map` `security-zeroize` `skill-security` `approval` `approval-tiers` `rate-limiter` `loop-guard` | `security::*` `approval::*` `rate::*` `loop::*` |
| Surfaces | `a2a` `a2a-cards` `mcp-client` `skillkit-bridge` `bridge` `streaming` | `a2a::*` `mcp::*` `skillkit::*` `bridge::*` `stream::*` |
| Channels | `channel-{bluesky, discord, email, linkedin, mastodon, matrix, reddit, signal, slack, teams, telegram, twitch, webex, whatsapp}` | `channel::*` |
| Telemetry | `telemetry` `pulse` `session-lifecycle` `session-replay` `feedback` `eval` `evolve` `hashline` `hooks` `cron` | `telemetry::*` `pulse::*` `session::*` `eval::*` `feedback::*` |
| Embeddings | `embedding` (Python) | `embedding::*` |

Total: 257 registered functions across 65 workers (64 Rust + 1 Python).

## Worker manifest

Every directory under `workers/` ships an `iii.worker.yaml` that declares
its registry shape:
Every directory under `workers/` ships an `iii.worker.yaml`:

```yaml
iii: v1
Expand All @@ -59,42 +53,11 @@ description: ...

CI's `validate iii.worker.yaml` job enforces this on every PR.

## Function namespaces

| namespace | worker | shape |
|---|---|---|
| `agent::` | agent-core | `chat`, `create`, `list`, `delete`, `list_tools` |
| `bridge::` | bridge | `register`, `invoke`, `run`, `list`, `cancel` |
| `council::` | council | `submit`, `decide`, `override`, `proposals`, `activity`, `verify` |
| `directive::` | directive | `create`, `get`, `update`, `list`, `ancestry` |
| `embedding::` | embedding (py) | `generate` |
| `hierarchy::` | hierarchy | `set`, `tree`, `chain`, `find`, `remove` |
| `ledger::` | ledger | `set_budget`, `spend`, `check`, `summary` |
| `llm::` | llm-router | `route`, `complete`, `usage`, `providers` |
| `memory::` | memory | `store`, `recall`, `consolidate`, `evict`, `delete` |
| `mission::` | mission | `create`, `list`, `checkout`, `transition`, `comment`, `release` |
| `pulse::` | pulse | `register`, `tick`, `invoke`, `status`, `toggle` |
| `realm::` | realm | `create`, `get`, `update`, `delete`, `list`, `import`, `export` |
| `security::` | security | `audit`, `scan`, `scan_injection`, `check_capability`, `set_capabilities`, `verify_audit`, `docker_exec`, `sign_manifest`, `verify_manifest` |
| `wasm::` | wasm-sandbox | `execute`, `validate`, `list_modules` |

`sandbox::*` is reserved for the **builtin iii-sandbox** worker (iii
v0.11.4-next.4), which boots ephemeral microVMs from OCI rootfs.
AgentOS workers never register under that namespace; the
`no sandbox::* clash with builtin` CI job enforces this.

## Engine boot

`config.yaml` (iii v0.11.4 schema) declares the seven baseline workers
the engine spawns: `iii-http`, `iii-state`, `iii-stream`, `iii-queue`,
`iii-pubsub`, `iii-cron`, `iii-observability`. AgentOS workers are
spawned alongside as separate processes — each connects to the engine
WebSocket via `register_worker("ws://localhost:49134", ...)` and stays
resident.
`config.yaml` (iii v0.11.4 schema) declares the seven baseline modules the engine spawns: `iii-http`, `iii-state`, `iii-stream`, `iii-queue`, `iii-pubsub`, `iii-cron`, `iii-observability`. AgentOS workers spawn alongside as separate processes — each connects to the engine WebSocket via `register_worker("ws://localhost:49134", ...)` and stays resident.

The engine WebSocket port (49134) is configurable per-worker via the
`III_WS_URL` environment variable, with `ws://localhost:49134` as the
default. Containerized deploys override.
The engine WebSocket port is configurable via `III_WS_URL` (default `ws://localhost:49134`).

## Calling a function from another worker

Expand All @@ -107,7 +70,7 @@ iii.trigger(TriggerRequest {
}).await?
```

Or fire-and-forget:
Fire-and-forget:

```rust
let iii_c = iii.clone();
Expand All @@ -121,79 +84,63 @@ tokio::spawn(async move {
});
```

This is the only inter-worker contract. There is no shared in-process
state; everything goes through `iii.trigger`.
This is the only inter-worker contract. There is no shared in-process state.

## Sandbox primitives — two surfaces

| namespace | worker | semantics |
|---|---|---|
| `sandbox::create` / `sandbox::exec` / `sandbox::list` / `sandbox::stop` | **builtin** iii-sandbox (v0.11.4-next.4) | Ephemeral microVMs from OCI rootfs (Python, Node presets). Full Linux. |
| `wasm::execute` / `wasm::validate` / `wasm::list_modules` | agentos `wasm-sandbox` | wasmtime, fuel-metered, sub-millisecond cold start. |

## TypeScript workers (src/)
CI's `no sandbox::* clash with builtin` job greps the workspace to ensure no agentos worker registers `sandbox::*`.

The 54 TypeScript files in `src/` predate the Rust `workers/`
decomposition. Several are duplicates of Rust workers
(`src/memory.ts` ↔ `workers/memory`, `src/security.ts` ↔
`workers/security`, `src/llm-router.ts` ↔ `workers/llm-router`,
`src/agent-core.ts` ↔ `workers/agent-core`); the rest are TypeScript-
only features (`a2a`, `swarm`, `eval`, `evolve`, `feedback`,
`hand-runner`, etc).
## Atomic state ops

Long-term these will collapse into `workers/` as well, with the Rust
versions becoming canonical for the duplicated domains and the
TypeScript-only features moving each to `workers/<name>/` with
`language: node, deploy: image` manifests. That migration is a
follow-up — not in this iteration.
iii v0.11.4 exposes `state::update` / `stream::update` with `UpdateOp::set`, `UpdateOp::increment`, `UpdateOp::append`, plus nested shallow-merge paths. Workers prefer these over `state::list + state::set` race patterns when mutating lists or counters.

`council::activity` still uses a manual hash-chain on `state::list + state::set` — a separate refactor will move it onto `UpdateOp::append` once the chain protocol tolerates concurrent appends without compare-and-swap.

## Surfaces (cli, tui)

`crates/cli` and `crates/tui` are clients, not workers. They speak
HTTP to `iii-http` on port 3111. They register no functions and
declare no `iii.worker.yaml`. Future work should move them onto the
iii client SDK directly so they call workers via `iii.trigger` instead
of REST.
`crates/cli` and `crates/tui` are clients, not workers. They speak HTTP to `iii-http` on port 3111. They register no functions. Future work moves them onto the iii client SDK so they call workers via `iii.trigger` directly.

## Hands, integrations, agents
## Hands, integrations, agents, workflows

These are **declarative config**, not workers:

- `hands/<name>/HAND.toml` — agent persona (system prompt, allowed
tools, schedule, dashboard metrics) consumed at runtime by
`src/hand-runner.ts`.
- `integrations/<name>.toml` — MCP server connection details (transport,
command, OAuth scopes), consumed by `src/mcp-client.ts`.
- `agents/<name>/...` — markdown templates for spawning agent
personas.
- `hands/<name>/HAND.toml` — agent persona (system prompt, allowed tools, schedule), consumed by the `hand-runner` worker.
- `integrations/<name>.toml` — MCP server connection details (transport, command, OAuth scopes), consumed by the `mcp-client` worker.
- `agents/<name>/...` — markdown templates for spawning agent personas.
- `workflows/<name>.yaml` — pre-defined workflow definitions for the `workflow` worker.

None of these ship as registered functions; they configure workers that
do.
None ship as registered functions; they configure workers that do.

## Versioning

- iii engine: **v0.11.4-next.4**
- iii-sdk (Rust): **=0.11.4-next.4** in workspace `Cargo.toml`
- iii-sdk (Node): **0.11.4-next.4** in root `package.json` (e2e tests only)
- iii-sdk (Python): **>=0.11.3** in `workers/embedding/pyproject.toml`
- agentos workspace: `version = "0.0.1"` (reserved for behavioral proof
against live infra, not feature completeness)
- agentos workspace: `version = "0.0.1"` (reserved for behavioral proof against live infra, not feature completeness)

## CI

Five jobs run on every PR:

| job | gate |
|---|---|
| `rust build + test` | `cargo build --release` + `cargo test --workspace` (831 tests) |
| `rust build + test` | `cargo build --release` + `cargo test --workspace` (1281 tests) |
| `validate iii.worker.yaml` | every `workers/<name>/iii.worker.yaml` parses and matches its folder |
| `no sandbox::* clash with builtin` | grep ensures no agentos worker registers `sandbox::*` |
| `e2e smoke` | starts engine + 13 workers, asserts ports listen, ≥30 functions register, no namespace clash |
| `e2e smoke` | starts engine + workers, asserts ports listen, ≥30 functions register, no namespace clash |
| `e2e full` | runs vitest e2e suite against the live stack — gated on `AGENTOS_API_KEY` secret |

Plus `.github/workflows/vercel-deploy.yml` (separate workflow): pushes
to `main` touching `website/**` trigger a Vercel Deploy Hook so the
docs site stays current with main.
Plus `.github/workflows/vercel-deploy.yml`: pushes to `main` touching `website/**` trigger a Vercel Deploy Hook.

## Dependencies (declarative chain-install)

iii v0.11.4-next.4 added a `dependencies:` map in `iii.worker.yaml`
that lets `iii worker add ./workers/agent-core` chain-install
`llm-router`, `memory`, `security` from the registry. AgentOS workers
do not yet declare deps because they aren't published to the registry
— once registry publishing lands, agent-core gets:
iii v0.11.4-next.4 added a `dependencies:` map in `iii.worker.yaml` that lets `iii worker add ./workers/agent-core` chain-install `llm-router`, `memory`, `security` from the registry. AgentOS workers do not yet declare deps because they aren't published to the registry — once publishing lands, agent-core gets:

```yaml
dependencies:
Expand All @@ -202,31 +149,6 @@ dependencies:
security: ^0.0.1
```

## Sandbox primitives — the two distinct surfaces

| namespace | worker | semantics |
|---|---|---|
| `sandbox::create` / `sandbox::exec` / `sandbox::list` / `sandbox::stop` | **builtin** iii-sandbox (v0.11.4-next.4) | Ephemeral microVMs booted from OCI rootfs (Python, Node presets). Full Linux. |
| `wasm::execute` / `wasm::validate` / `wasm::list_modules` | agentos `wasm-sandbox` | wasmtime fuel-metered, instruction-level, sub-millisecond cold start. |

Different cost profiles. The CI namespace-clash job ensures the two
never collide.

## Atomic state ops (iii v0.11.4)

iii now exposes `state::update` / `stream::update` with `UpdateOp::set`,
`UpdateOp::increment`, `UpdateOp::append`, plus nested shallow-merge
paths. Workers should prefer these over `state::list + state::set` race
patterns when adding to a list or counter.

`council::activity` still uses the manual hash-chain on `state::list +
state::set` — a separate refactor will move it onto `UpdateOp::append`
once the chain protocol is redesigned to tolerate concurrent appends
without compare-and-swap.

## File-by-file responsibilities

For deeper detail on any worker, read its `src/main.rs` (Rust) or
`main.py` (Python). Each is intentionally small (5-10 registered
functions, 300-2000 LOC) so it's possible to hold the whole worker in
your head before touching it.
For deeper detail on any worker, read its `src/main.rs` (Rust) or `main.py` (Python). Each is intentionally small (5–10 registered functions, 300–2000 LOC).
54 changes: 0 additions & 54 deletions CLAUDE.md

This file was deleted.

Loading
Loading