diff --git a/.changeset/microvm-doc-purge.md b/.changeset/microvm-doc-purge.md new file mode 100644 index 000000000..4c149c6cb --- /dev/null +++ b/.changeset/microvm-doc-purge.md @@ -0,0 +1,41 @@ +--- +"@smooai/smooth": patch +--- + +Fix the broken dev loop and purge the microVM-era leftovers that were misleading +agents and humans reading this repo (th-e827ba). + +- **`pnpm install:th` was broken — it exited 101 for everyone.** It ran `cargo + install --path crates/smooth-operative`, a directory deleted with the microVM + stack. `th` itself installs first, so the failure landed at the end: the + binary looked fine while the documented dev loop (CLAUDE.md §2) returned + non-zero, failing any hook or CI step gated on it. It now installs + `smooth-daemon` — what `th daemon` / `th up` actually need resolvable on PATH, + since `th` deliberately doesn't link the daemon in. Same fix for + `install:th:full`. Verified by running to completion. +- **Deleted `scripts/bench.sh`** — a wrapper around `smooai-smooth-bench`, also + deleted with the microVM stack. +- **`CLAUDE.md` §1/§4 rewritten against the real tree.** The workspace structure + and "Key Crates" list documented six crates that no longer exist + (`smooth-bigsmooth`, `smooth-narc`, `smooth-scribe`, `smooth-archivist`, + `smooth-operative`, and `smooth-operator` — which now lives in its own repo) + while omitting seven that do. §4's module table, dispatch description, and + security architecture all described the deleted `smooth-bigsmooth` and are now + written against `smooth-daemon` (permission hook → Narc → kernel sandbox). +- **`smooth-goalie` documented as it is now**: not the in-VM Wonk-delegating + network proxy, but the daemon's egress boundary via `AuditLogger` + + `run_proxy_local`. +- **Broken paths fixed**: `crates/smooth-cli/src/api/` (doesn't exist) → + `src/smooai/`; `th operators` → `th operatives`; the `th cache` / + `~/.smooth/project-cache` docs described a command that no longer exists. +- **`rusqlite` 0.32 → 0.40.** The pin existed to unify `libsqlite3-sys` with + microsandbox's sea-orm→sqlx tree; microsandbox is gone and rusqlite is now the + only crate linking sqlite3. No API changes needed. +- **User-facing strings corrected**: `th code` cold-start printed "starting + Safehouse microVM" and "cast online (wonk · goalie · narc · scribe · archivist + · diver · groove)"; boot failures blamed a "Safehouse microVM" that hasn't + existed for weeks. +- Stale `//!` doc comments and crate manifest descriptions swept across + `smooth-goalie`, `smooth-tools`, `smooth-policy`, `smooth-diver`, + `smooth-daemon`, and `smooth-cli`, plus the README workspace tree and the dead + microsandbox image / project-cache sections. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85fa629ec..4f954bcf5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -224,10 +224,12 @@ jobs: - name: Install macOS build deps if: runner.os == 'macOS' - # protobuf: smooth-narc's prost build.rs needs `protoc` (the - # Linux step above installs it via apt; macOS runners have - # neither protoc nor it on PATH, so the cross-platform binary - # build failed with "Could not find protoc"). Pearl th-14bddf. + # protobuf: originally for smooth-narc's prost build.rs, which + # failed the macOS cross-platform build with "Could not find + # protoc" (pearl th-14bddf). That crate went with the microVM + # stack (2026-07/th-f4a801) and no build.rs in the tree shells + # out to protoc today — kept as cheap insurance because a + # release build is the worst place to discover otherwise. run: brew install protobuf - name: Install pnpm diff --git a/CLAUDE.md b/CLAUDE.md index abbe69a7e..8b45a6f63 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,20 +10,28 @@ This file provides guidance to Claude Code when working with code in this reposi Smooth is the Smoo AI CLI and orchestration platform — a **single Rust binary** (`th`) that coordinates Smooth Operators (AI agents). Zero runtime dependencies. -> **microVM stack removed 2026-07 (pearl th-f4a801).** Big Smooth dispatched tasks into per-task microsandbox microVMs (a per-VM cast of Wonk/Goalie/Narc/Scribe). That stack was deleted — dispatch now runs the operative as a host subprocess, in-process, with Narc tool surveillance still applied. See git history at this PR's parent commit (or pearls th-c89c2a / th-515a13) to resurrect the VM path. +> **microVM stack removed 2026-07 (pearl th-f4a801).** Big Smooth used to dispatch tasks into per-task microsandbox microVMs (a per-VM cast of Wonk/Goalie/Narc/Scribe). All of it — the VMs, the `smooth-operative` worker binary, the `smooth-bigsmooth` / `smooth-narc` / `smooth-scribe` / `smooth-archivist` crates — is gone. Big Smooth today **is** `smooth-daemon`, and the agent engine is the external `smooth-operator` crate (its own repo, SmooAI/smooth-operator). Git history at the removal PR has the VM path if it ever needs resurrecting; [ADR-004](docs/Decisions/ADR-004-remove-microvm-sandbox-stack.md) is the record. --- ## 1. Workspace Structure +Twelve crates. `ls crates/` is the source of truth; this list is kept in sync with it. + ``` smooth/ ├── crates/ -│ ├── smooth-cli/ # Binary — clap CLI -│ ├── smooth-bigsmooth/ # Library — orchestrator, policy generation, API server -│ ├── smooth-policy/ # Library — shared policy types, TOML parsing -│ ├── smooth-narc/ # Library — tool surveillance + LLM judge (in-process) -│ ├── smooth-code/ # Library — ratatui terminal dashboard +│ ├── smooth-cli/ # Binary `th` — clap entry point (53 top-level commands) +│ ├── smooth-daemon/ # Binary + lib — Big Smooth: the always-on personal-agent daemon +│ ├── smooth-tools/ # Library — agent tools (fs/grep/bash) + the kernel OS sandbox +│ ├── smooth-policy/ # Library — policy types, TOML parsing, auto-mode, ext trust +│ ├── smooth-goalie/ # Library + bin — HTTP forward proxy = the egress boundary +│ ├── smooth-pearls/ # Library — Dolt-backed pearl tracker, memories, agent registry +│ ├── smooth-cast/ # Library — coding-harness bits the published engine dropped +│ ├── smooth-code/ # Library — `th code` ratatui coding TUI +│ ├── smooth-diver/ # Library — pearl lifecycle manager + Jira sync +│ ├── smooth-tmux/ # Library — tmux driver (drives Claude Code for `th claude`) +│ ├── smooth-api-client/ # Library — generated api.smoo.ai client + auth wrapper │ └── smooth-web/ # Library — embedded Vite SPA via rust-embed │ └── web/ # React + Vite source (TypeScript) ├── Cargo.toml # Workspace root @@ -34,18 +42,20 @@ smooth/ ### Key Crates -- **smooth-cli** (`crates/smooth-cli/`): clap entry point, 27 commands including `th access` for policy control -- **smooth-bigsmooth** (`crates/smooth-bigsmooth/`): axum server, 20+ routes, orchestrator (state reporting), policy generation, session management, pearls/jira/tailscale, in-process task dispatch -- **smooth-operator** (`crates/smooth-operator/`): Rust-native AI agent framework — LLM client, tool system with hooks, agent loop, conversation management, built-in checkpointing (Groove) -- **smooth-policy** (`crates/smooth-policy/`): shared policy types (network, filesystem, pearls, tools, MCP), TOML parsing, glob matching, phase defaults -- **smooth-pearls** (`crates/smooth-pearls/`): built-in pearl tracker (dependency-graph work items). Dolt-backed via `smooth-dolt` Go binary for version control and git sync. Types: `Pearl`, `PearlStore`, `PearlStatus`, `PearlUpdate`, `PearlQuery`, `SmoothDolt`, `Registry`. Also stores session messages, orchestrator snapshots, and memories. -- **smooth-narc** (`crates/smooth-narc/`): tool surveillance via ToolHook (runs in-process on the operative's tool registry), secret detection (10 patterns), prompt injection guard (6 patterns), write guard, severity-based alerts -- **smooth-operative** (`crates/smooth-operative/`): the worker binary for a dispatched pearl. Big Smooth execs it as a host subprocess (native build, resolved from `~/.cargo/bin`). Runs the `smooth-operator` engine's agent loop + file/bash tools + NarcHook, streams JSON-lines `AgentEvent`s on stdout. (Distinct from the `smooth-operator` engine crate above, and from the public `smooth-operator` service.) Its tool modules are the substrate the smooth-daemon epic (th-c89c2a) reuses. -- **smooth-scribe** (`crates/smooth-scribe/`): structured logging service, LogEntry with trace context, query/filter support -- **smooth-archivist** (`crates/smooth-archivist/`): central log aggregator, batch ingest from Scribes, cross-service query, stats, SSE event stream -- Removed 2026-07 (pearl th-f4a801, in git history): **smooth-wonk** (in-VM access authority), **smooth-goalie** (in-VM network/FS proxy), **smooth-bootstrap-bill** (host-side microsandbox broker), **smooth-host-stub** (VM credential broker), **smooth-credential-helper**. -- **smooth-code** (`crates/smooth-code/`): ratatui AI coding TUI — streaming chat, tool calls, file browser, git, sessions, model picker, extensions -- **smooth-web** (`crates/smooth-web/`): rust-embed serves compiled Vite SPA +- **smooth-cli** (`crates/smooth-cli/`): the `th` binary. clap entry point in `src/main.rs`, 53 top-level commands. Platform (api.smoo.ai) subcommands live in `src/smooai/`; cross-org admin in `src/admin/`. +- **smooth-daemon** (`crates/smooth-daemon/`): **Big Smooth.** The always-on, single-tenant personal-agent daemon (EPIC th-c89c2a). It hosts smooth-operator's `LocalServer` in-process — canonical WS protocol, no bespoke agent loop — with durable SQLite storage, scheduled/proactive turns, web push, tailnet exposure, and the security hooks. `th daemon` runs it directly; `th up` also launches it. +- **smooth-operator**: the agent engine (LLM client, agent loop, tool registry + hooks, conversation, checkpointing, cast, permissions, `DenyPolicy`). **It is not in this workspace** — it's a git/crates.io dependency from the separate `SmooAI/smooth-operator` repo. Don't look for `crates/smooth-operator/`. +- **smooth-tools** (`crates/smooth-tools/`): the reusable agent tool surface the daemon registers — `read_file`, `write_file`, `edit_file`, `list_files`, `grep`, `bash`, `cd`, `crawl`, `web_search`, `knowledge_search`, `remember`, `th`, `create_skill`. Every filesystem path goes through `path::resolve_workspace_path`; `bash` runs only inside `sandbox.rs`'s kernel OS sandbox. +- **smooth-policy** (`crates/smooth-policy/`): shared policy types (network, filesystem, pearls, tools, MCP), TOML parsing, glob matching, phase defaults, plus `auto_mode` (permission modes/allow-lists), `ext_trust`, and `smooth_alias`. +- **smooth-goalie** (`crates/smooth-goalie/`): HTTP forward proxy with an exact-host allowlist and JSON-lines audit logging. **Repurposed, not removed** — the microVM-era in-VM/Wonk-delegating mode is dead code paths; what the daemon actually uses is `AuditLogger` + `run_proxy_local` from `start_egress_proxy` (`crates/smooth-daemon/src/lib.rs`), making it the daemon's **egress boundary**. Enabled by `SMOOTH_EGRESS_ALLOWLIST`; the sandbox points `HTTP(S)_PROXY` at it and kernel-denies direct outbound. +- **smooth-pearls** (`crates/smooth-pearls/`): built-in pearl tracker (dependency-graph work items). Dolt-backed via the `smooth-dolt` Go binary for version control and git sync. Types: `Pearl`, `PearlStore`, `PearlStatus`, `PearlUpdate`, `PearlQuery`, `SmoothDolt`, `Registry`. Also stores session messages, memories, the agent registry, and agent-to-agent messaging. +- **smooth-cast** (`crates/smooth-cast/`): the coding-harness specifics the published generic engine dropped — `coding_workflow` (the `th code` outer loop), `skills` discovery, the four harness cast roles (fixer / oracle / chief / intent_classifier), and field-preserving `providers.json` editing. +- **smooth-code** (`crates/smooth-code/`): `th code` — ratatui AI coding TUI: streaming chat, tool calls, file browser, git, sessions, model picker, extensions. +- **smooth-diver** (`crates/smooth-diver/`): Pearl Diver — pearl lifecycle (create on dispatch, close on completion, sub-pearls, deps/labels/costs) plus the bidirectional Jira client. +- **smooth-tmux** (`crates/smooth-tmux/`): dependency-light tmux driver (per-driver socket isolation, bracketed-paste send, full scrollback capture) — how `th claude` supervises Claude Code. +- **smooth-api-client** (`crates/smooth-api-client/`): api.smoo.ai client generated at build time by progenitor from `openapi.json`, plus the auth wrapper (token store, bearer middleware, refresh-on-401). +- **smooth-web** (`crates/smooth-web/`): rust-embed serves the compiled Vite SPA. +- Removed 2026-07 (pearl th-f4a801, in git history): **smooth-bigsmooth** (its role is now smooth-daemon), **smooth-operative** (the per-task worker binary), **smooth-narc** (re-homed as `smooth-daemon/src/hooks/narc.rs`), **smooth-scribe**, **smooth-archivist**, **smooth-wonk**, **smooth-bootstrap-bill**, **smooth-host-stub**, **smooth-credential-helper**. --- @@ -79,9 +89,10 @@ th jira sync / status # Pearls (the only spelling — no `th issues` / `th beads` aliases) th pearls create / ready / list / show / update / close / push / pull -# Worktrees, sandbox/operators, audit, cache, service +# Worktrees, daemon/operatives, audit, service th worktree create / list / merge / remove -th up / down / status / run / operators / access / inbox +th daemon · th up / down / status +th run / pause / resume / steer / cancel / approve / operatives / access / inbox th audit tail · th doctor · th service install th cast models ``` @@ -91,7 +102,7 @@ th cast models ``` Need to call api.smoo.ai? ├── Per-org resource (acts on your active org) -│ └── th api → crates/smooth-cli/src/api/.rs +│ └── th api → crates/smooth-cli/src/smooai/.rs ├── Cross-org / requires admin grants │ └── th admin → crates/smooth-cli/src/admin/ (paired API pearl required) └── Purely local (no api.smoo.ai roundtrip) @@ -104,7 +115,7 @@ Need to call api.smoo.ai? | Authenticated as M2M client or regular dashboard user | Authenticated as **admin-grant dashboard user** | | Backed by `/organizations/{org_id}/…` | Backed by `/admin/…` (paired endpoints don't exist yet) | | `agents`, `knowledge`, `members`, `config`, `jobs`, `keys`, `observability` | `onboard-customer`, `mint-key`, `set-secret`, `org list/show`, `feature-flag set` | -| **Adding one**: file under `src/api/` + clap subcommand | **Adding one**: API endpoint + CLI subcommand together | +| **Adding one**: file under `src/smooai/` + clap subcommand | **Adding one**: API endpoint + CLI subcommand together | ### What does NOT belong in `th` @@ -118,7 +129,7 @@ Need to call api.smoo.ai? 1. **Search** — `rg "th api " crates/`; someone may have started it 2. **Pearl** — `th pearls create --title="th api X: add Y" --type=feature --priority=2` 3. **Worktree** — `th worktree create th--…` -4. **Code** — clone the nearest sibling under `crates/smooth-cli/src/api/` (they all follow the same shape), register in `src/api/mod.rs` + parent `Commands` enum +4. **Code** — clone the nearest sibling under `crates/smooth-cli/src/smooai/` (they all follow the same shape), register in `src/smooai/mod.rs` + parent `Commands` enum 5. **Test exhaustively** — colocated `#[cfg(test)]`, happy + error paths (§8 is non-negotiable) 6. **Doc** — update help text **and** `docs/Engineering/Using-th-CLI.md` 7. **Gate** — `cargo fmt && cargo clippy && cargo test && pnpm install:th` @@ -153,11 +164,11 @@ Override with ` # th-curl-hint:ack reason=…` if you genuinely need raw curl. * ```bash cargo build # Build all crates -cargo test # Run all tests (200+ across 10 crates) +cargo test # Run all tests (2000+ across the 12 crates) cargo fmt # Format (rustfmt.toml: 160 width) cargo clippy # Lint (pedantic + nursery) cargo build --release -p smooth-cli # Release binary (~10MB) -pnpm install:th # Build web bundle + install th (CLI + native operative) FROM LOCAL SOURCE (the dev test loop) +pnpm install:th # Build web bundle + install th FROM LOCAL SOURCE (the dev test loop) pnpm install:th:brew # Install the latest RELEASED th via Homebrew (no source build; ignores local changes) pnpm build:web # Just rebuild the embedded web SPA ``` @@ -188,73 +199,73 @@ pnpm dev # Vite dev server at :3100 --- -## 4. Key Modules (smooth-bigsmooth) +## 4. Key Modules (smooth-daemon) + +Big Smooth has **no bespoke server and no bespoke agent loop**. It hosts +smooth-operator's `LocalServer` (canonical WS protocol + widget) and adds its +own routes through the engine's `serve_routes` seam. Entry point: +`serve_local_flavor` in `operator.rs`. | Module | Purpose | |---|---| -| `server.rs` | axum router, all API routes (20+), access control routes, in-process dispatch (`dispatch_ws_task_direct`) | -| `orchestrator.rs` | Lightweight work-loop state reporter (status/TUI surface). The VM-spawning state machine was removed 2026-07 (pearl th-f4a801). | -| `tools.rs` | Tool registry + hooks (secret detection, prompt injection) | -| `policy.rs` | Policy generation, phase defaults, access request handling | -| `pearls.rs` | `PearlStore` wrapper (list, create, update, close, comment) | -| `search.rs` | @ autocomplete (pearls + globwalk files + path expansion) | -| `audit.rs` | Rotating file appender at ~/.smooth/audit/ | -| `db.rs` | rusqlite: memories, worker_runs, config tables | -| `jira.rs` | Jira REST client + bidirectional sync | -| `tailscale.rs` | tailscale CLI status wrapper | -| `session.rs` | Session persistence, message history, orchestrator snapshots, inbox | -| `ws.rs` | WebSocket message types | +| `lib.rs` | Crate root; `serve_local_flavor` re-export + `start_egress_proxy` (the goalie egress boundary) | +| `operator.rs` | The local deployment flavor — builds and runs the operator `LocalServer` in-process, wires tool providers and hooks | +| `operator_storage.rs` | Durable SQLite `StorageAdapter` so conversations/sessions survive restart (no Postgres) | +| `hooks/mod.rs` | The two engine `ToolHook`s installed on every per-turn registry: permission gate, then Narc | +| `hooks/narc.rs` | `NarcHook` — regex detectors on tool args (secrets, prompt injection, dangerous shell), LLM-judge escalation, secret redaction in `post_call` | +| `config.rs` | Daemon config + LLM credential resolution (env → providers.json → gateway), egress config | +| `schedule.rs` / `scheduler.rs` | Proactive/scheduled turns; `SqliteScheduleStore` persists them, the tick loop fires them via a `TurnDriver` | +| `search.rs` | `GET /search` — the `@`-mention autocomplete backend for the web composer | +| `cwd_route.rs` | `GET`/`POST /api/session/cwd` — the UI's `/cd` and `/pwd` | +| `auth_login.rs` | Browser OAuth2 + PKCE sign-in to Smoo AI, routed through the daemon (works over a tailnet origin) | +| `push.rs` | Web Push — VAPID-signed notifications to the installed PWA | +| `tailscale.rs` | Best-effort `tailscale serve` exposure of the loopback listener | ### Dispatch -Big Smooth's WebSocket `TaskStart` handler dispatches a task by exec'ing the -native `smooth-operative` binary as a host subprocess (`dispatch_ws_task_direct` -in `server.rs`). The operative hosts the agent loop, NarcHook tool surveillance, -and file/bash tools against the host filesystem; it streams `AgentEvent`s as -JSON-lines on stdout, which Big Smooth parses and forwards to WebSocket clients. +There is no per-task worker process. A message arrives on the operator's +canonical WebSocket, the engine runs the turn in-process, and tools execute +against the host filesystem through `smooth-tools` — `bash` inside the kernel +sandbox, egress through the goalie proxy. Events stream back over the same +canonical WS to every client (`th code`, the web SPA, SDK clients). > **microVM sandboxed dispatch removed 2026-07 (pearl th-f4a801).** Big Smooth > used to spawn a per-task microsandbox microVM (mounting a cross-compiled > `smooth-operative` at `/opt/smooth/bin`, bind-mounting the workspace) with a > per-VM Wonk/Goalie/Narc/Scribe cast enforcing network + filesystem policy. -> That path — and the "Big Smooth is strictly READ-ONLY" guarantee it provided — -> is gone; git history at this PR's parent commit has it. The smooth-daemon epic -> (th-c89c2a) is the forward path, and the auto-mode permission model -> (th-515a13) will rebuild policy enforcement on `smooth-policy`. +> The interim host-subprocess `smooth-operative` dispatch that replaced it is +> also gone. Git history and +> [ADR-004](docs/Decisions/ADR-004-remove-microvm-sandbox-stack.md) have the +> details. ### Security Architecture -Tool-call surveillance still runs **in-process** on the operative: - -- **Auto-mode permission engine** (`smooth-bigsmooth/src/auto_mode.rs`, pearl - th-515a13) — the **primary enforcement layer** now that Wonk/Goalie are gone. - A Claude-Code-style `ToolHook` (added FIRST, so its verdict gates before Narc - and before the tool runs) giving every call an allow / deny / **ask** verdict. - `ask` blocks on the shared `AccessStore` queue (surfaced via - `/api/access/{pending,approve,deny,stream}` + the TUI) and **fails closed** on - timeout/headless. Modes via `SMOOTH_AUTO_MODE`: `ask` (default) / `accept-edits` - / `deny` (headless, unmatched→deny) / `bypass` (keeps the hard circuit-breakers). - Allow-lists (`wonk-allow.toml`, user + project, project-wins) persist approvals. - See [`docs/Engineering/Auto-Mode-Permissions.md`](docs/Engineering/Auto-Mode-Permissions.md). -- **Narc** — tool surveillance + prompt injection guard (regex + optional LLM - judge), applied as a `ToolHook` on the operative's tool registry. -- **Archivist** / **Scribe** — log aggregation + structured logging (crates kept). -- **Groove** — LLM checkpointing + session resume (built into smooth-operator). +Three layers, in the order a tool call meets them: + +1. **Permission gate** — the engine's `permission::PermissionHook`, built in + `smooth-daemon/src/operator::permission_hook`, layered with the daemon's + embedded declarative `DenyPolicy` circuit-breakers. Installed **FIRST**, so a + policy deny short-circuits before surveillance and before the tool runs. + Modes/allow-lists live in `smooth-policy/src/auto_mode.rs`; see + [`docs/Engineering/Auto-Mode-Permissions.md`](docs/Engineering/Auto-Mode-Permissions.md). +2. **Narc** (`smooth-daemon/src/hooks/narc.rs`) — surveillance. `pre_call` regex + detectors (secret exfiltration, prompt injection, dangerous shell ops) with + fail-closed LLM-judge escalation on ambiguous hits; `post_call` redacts + detected secrets out of the tool result in place. +3. **Kernel OS sandbox** (`smooth-tools/src/sandbox.rs`) — the load-bearing + layer, because an agent can talk its way past a userspace check but not past + the kernel. `bash` subprocesses get filesystem **writes** confined to the + workspace (plus explicit denies on `.git/hooks` and `.git/config`) and + **reads** denied on credential stores (`~/.ssh`, `~/.aws`, `~/.config/gh`, + `~/.kube`, `~/.docker`, `~/.gnupg`, `~/.netrc`, and the daemon's own + `~/.smooth` secrets). With a proxy configured it is also the **egress + boundary**: direct outbound is kernel-denied except loopback, so traffic must + pass goalie's exact-host allowlist. `SandboxedCommand` is the only way `bash` + builds a subprocess — there is no plain-`Command` constructor. Removed with the microVM stack (2026-07, pearl th-f4a801; see git history): -**Wonk** (per-VM access authority), **Goalie** (per-VM network + FUSE proxy, -iptables-enforced), and the "Big Smooth is READ-ONLY inside The Safehouse VM" -isolation model. - -### smooth-operator (Agent Framework) - -| Module | Purpose | -|---|---| -| `agent.rs` | Observe → think → act loop, event emission, checkpoint integration | -| `llm.rs` | OpenAI-compatible chat completion client, streaming-ready | -| `tool.rs` | Tool trait + ToolRegistry with pre/post hooks (Narc integration) | -| `conversation.rs` | Message history, context window management, token estimation | -| `checkpoint.rs` | Checkpoint + CheckpointStore trait, configurable strategies | +**Wonk** (per-VM access authority), Goalie's per-VM FUSE + iptables enforcement, +and the "Big Smooth is READ-ONLY inside The Safehouse VM" isolation model. --- @@ -304,7 +315,7 @@ Tables: `pearls`, `pearl_dependencies`, `pearl_labels`, `pearl_comments`, - `smooth.db` — Legacy SQLite (migrate with `th pearls migrate-from-sqlite`) - `audit/` — Rotating tool usage logs per actor - `providers.json` — LLM credentials -- `project-cache/` — Project-scoped operator VM cache (keyed by workspace path hash). Bound into the sandbox at `/opt/smooth/cache` so repeated runs on the same repo share `pnpm install` / `cargo fetch` state. Manage via `th cache list|prune|clear`. +- `auth/smooai.json` — cached Smoo AI JWT (`th api login`) - `mcp.toml` — MCP server configs (see `docs/extending.md`) - `plugins//plugin.toml` — CLI-wrapper tool manifests @@ -393,7 +404,7 @@ Never edit source code or commit directly on `main`. Always use worktrees. - `cargo fmt -- --check` must pass before commit - Test categories: - **Unit tests**: every public function, every error path, every edge case - - **Integration tests**: cross-module interactions (e.g., policy → sandbox, wonk → goalie) + - **Integration tests**: cross-module interactions (e.g., policy → sandbox, sandbox → goalie egress) - **Property tests**: where applicable (e.g., policy round-trip serialization) - When adding a new module: write tests FIRST or alongside, never "add tests later" - When fixing a bug: add a regression test that fails without the fix diff --git a/Cargo.lock b/Cargo.lock index 8ecb5e15a..927331d24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,18 +58,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -1782,15 +1770,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.15.5" @@ -1811,13 +1790,22 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", +] + [[package]] name = "hashlink" -version = "0.9.1" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "32069d97bb81e38fa67eab65e3393bf804bb85969f2bc06bf13f64aef5aba248" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.17.1", ] [[package]] @@ -2645,9 +2633,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.30.1" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +checksum = "f6c19a05435c21ac299d71b6a9c13db3e3f47c520517d58990a462a1397a61db" dependencies = [ "cc", "pkg-config", @@ -4154,11 +4142,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.18", +] + [[package]] name = "rusqlite" -version = "0.32.1" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +checksum = "11438310b19e3109b6446c33d1ed5e889428cf2e278407bc7896bc4aaea43323" dependencies = [ "bitflags 2.11.0", "fallible-iterator", @@ -4166,6 +4164,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", + "sqlite-wasm-rs", ] [[package]] @@ -5175,6 +5174,18 @@ dependencies = [ "der 0.8.1", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index 8877cd129..dffe517ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] resolver = "2" members = ["crates/*"] -exclude = ["crates/smooth-bigsmooth/tests/fixtures"] [workspace.package] version = "0.22.0" @@ -36,12 +35,13 @@ axum-extra = { version = "0.10", features = ["typed-header"] } tower = "0.5" tower-http = { version = "0.6", features = ["cors", "fs", "trace"] } -# Database — pinned to 0.32 so `libsqlite3-sys 0.30` is the single version in -# the tree. `microsandbox` (via sea-orm → sqlx → libsqlite3-sys 0.30) and -# `rusqlite` both link the `sqlite3` native library, so their versions must -# unify. rusqlite 0.32 uses the same APIs we care about (Connection, params!, -# query, prepare, Row, types::ToSql). -rusqlite = { version = "0.32", features = ["bundled"] } +# Database — `smooth-daemon`'s durable local storage (schedules, operator +# state). `bundled` compiles SQLite into the binary, so there is no system +# `sqlite3` to match. rusqlite is the only crate in the tree that links it +# (`cargo tree -i libsqlite3-sys`); the old 0.32 pin existed to unify with +# microsandbox's sea-orm → sqlx → libsqlite3-sys 0.30, and died with the +# microVM stack (2026-07, pearl th-f4a801). +rusqlite = { version = "0.40", features = ["bundled"] } # SMOODEV-1468: sync Postgres checkpoint store (smooth-operator `postgres` feature) # + testcontainers-backed integration tests. Sync `postgres` crate (not async # sqlx) because the CheckpointStore trait is synchronous, mirroring rusqlite. @@ -69,15 +69,15 @@ serde_json = "1" serde_yml = "0.0.12" # HTTP client -# Use rustls-tls-webpki-roots so the crate ships its own CA bundle (no system -# certs needed). This is critical for smooth-operative running inside -# alpine microVMs which don't have ca-certificates installed by default. +# Use rustls-tls-webpki-roots so the crate ships its own CA bundle rather than +# reading the system trust store — `th` is distributed as a standalone binary +# and must work on hosts with no `ca-certificates` installed. # -# `hickory-dns` enables a pure-Rust DNS resolver instead of getaddrinfo. This -# is critical for the in-VM runner because alpine's musl getaddrinfo has been -# observed to return EAI_AGAIN against microsandbox's intercepted DNS — the -# pure-Rust hickory resolver reads /etc/resolv.conf and sends UDP queries -# directly, which the microsandbox DNS interceptor handles correctly. +# `hickory-dns` swaps getaddrinfo for a pure-Rust resolver that reads +# /etc/resolv.conf and sends UDP queries itself. Originally needed because +# alpine musl's getaddrinfo returned EAI_AGAIN against the microVM's +# intercepted DNS (that stack is gone, 2026-07/th-f4a801); kept because it +# keeps resolution independent of the host's libc and NSS config. reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls-webpki-roots", "charset", "http2", "hickory-dns"] } # File search diff --git a/README.md b/README.md index 771847387..7f3e619fd 100644 --- a/README.md +++ b/README.md @@ -305,8 +305,8 @@ See [`docs/extending.md`](docs/extending.md) for the full guide. ### Run a pearl (`th run`) Dispatch a pearl (or ad-hoc prompt) to a Smooth operative. Big Smooth -(`th up`) execs the operative as a host subprocess against your current -directory and streams agent events to stdout. +(`th up`) runs the agent turn against your current directory and streams +agent events to stdout. ```bash # First ready pearl @@ -323,39 +323,13 @@ th operatives list th operatives kill ``` -> The `--image`, `--memory-mb`, and `--keep-alive` flags (and the -> `smooai/smooth-operative` microVM image) went away with the microVM -> stack, 2026-07 (pearl `th-f4a801`). The operative now runs directly on -> the host and uses your real toolchain — no per-VM image or cache mount. - -Build locally: - -```bash -scripts/build-smooth-operative-image.sh -``` - -Override via `--image` or `SMOOTH_OPERATIVE_IMAGE` env if you want a -custom variant (e.g. a version pinned for CI reproducibility). - -**Microsandbox image resolution.** Locally-built images live in -your Docker Desktop image store; `microsandbox` pulls from registries -by default, so if its pull can't see your local build, push it -first (`docker push smooai/smooth-operative:0.2.0`) or set -`SMOOTH_OPERATIVE_IMAGE` to something microsandbox can reach. - -**Project cache.** Each workspace path hashes to its own cache, -mounted at `/opt/smooth/cache` inside the VM. Subsequent runs on the -same repo share mise installs + language stores (pnpm-store, cargo -registry, uv cache, etc.). Backed by a first-class microsandbox -Volume by default (`~/.microsandbox/volumes/smooth-cache-/`); -set `SMOOTH_USE_VOLUMES=0` to fall back to the legacy bind-mount -(`~/.smooth/project-cache//`). Manage with: - -```bash -th cache list # shows entries from both backends, tagged -th cache prune --older-than 30 # evict caches idle > N days -th cache clear /path/to/project # remove entry for a specific workspace -``` +> The `--image`, `--memory-mb`, and `--keep-alive` flags, the +> `smooai/smooth-operative` microVM image, the image-build script, and +> the per-workspace VM cache (`th cache`) all went away with the microVM +> stack, 2026-07 (pearl `th-f4a801`). Work now runs directly on the host +> against your real toolchain — no image, no cache mount. `bash` tool +> calls are confined by the kernel OS sandbox and egress goes through the +> allowlist proxy. ### Background service @@ -509,24 +483,26 @@ See [`docs/extending.md`](docs/extending.md) and [`SECURITY.md`](SECURITY.md). ``` smooth/ ├── crates/ -│ ├── smooth-cli/ # Binary — clap CLI, the `th` entry point -│ ├── smooth-bigsmooth/ # Library — orchestrator, policy gen, session mgmt, in-process dispatch -│ ├── smooth-operator/ # Library — Rust-native AI agent framework -│ ├── smooth-operative/ # Binary — agent loop for a dispatched pearl (host subprocess) +│ ├── smooth-cli/ # Binary `th` — clap CLI, the entry point +│ ├── smooth-daemon/ # Binary + lib — Big Smooth, the always-on agent daemon +│ ├── smooth-tools/ # Library — agent tools + the kernel OS sandbox │ ├── smooth-policy/ # Library — shared policy types, TOML parsing -│ ├── smooth-narc/ # Library — tool surveillance + secret detection -│ ├── smooth-scribe/ # Library — structured logging -│ # (removed 2026-07, pearl th-f4a801: smooth-bootstrap-bill, smooth-wonk, -│ # smooth-goalie, smooth-host-stub, smooth-credential-helper — see git history) -│ ├── smooth-archivist/ # Library — central log aggregator +│ ├── smooth-goalie/ # Library + bin — HTTP forward proxy (egress boundary) │ ├── smooth-pearls/ # Library — Dolt-backed pearl tracker -│ ├── smooth-plugin/ # Library — CLI-wrapper plugin manifests +│ ├── smooth-cast/ # Library — coding-harness cast roles, skills, workflow +│ ├── smooth-code/ # Library — `th code` ratatui coding TUI │ ├── smooth-diver/ # Library — pearl lifecycle manager + Jira sync -│ ├── smooth-tunnel/ # Library — th.smoo.ai reverse-tunnel client -│ ├── smooth-bench/ # Binary — coding-benchmark harness (aider-polyglot, SWE-bench, …) -│ ├── smooth-code/ # Library — ratatui terminal dashboard +│ ├── smooth-tmux/ # Library — tmux driver (supervises Claude Code) +│ ├── smooth-api-client/ # Library — generated api.smoo.ai client + auth │ └── smooth-web/ # Library — embedded Vite SPA │ └── web/ # React + Vite source +│ +│ # The agent engine `smooth-operator` is NOT in this workspace — it lives in +│ # the separate SmooAI/smooth-operator repo and is consumed as a dependency. +│ # Removed 2026-07 (pearl th-f4a801, see git history): smooth-bigsmooth, +│ # smooth-operative, smooth-narc, smooth-scribe, smooth-archivist, +│ # smooth-wonk, smooth-bootstrap-bill, smooth-host-stub, +│ # smooth-credential-helper. ├── Cargo.toml # Workspace root ├── rustfmt.toml # Format config └── install.sh # Curl installer diff --git a/crates/smooth-cli/src/boot_ui.rs b/crates/smooth-cli/src/boot_ui.rs index 963708409..912c366bb 100644 --- a/crates/smooth-cli/src/boot_ui.rs +++ b/crates/smooth-cli/src/boot_ui.rs @@ -3,15 +3,15 @@ //! Pearl th-7840d8 — replaces the bare `println!("Starting Smooth...")` //! cold-boot line (and the matching path inside `th up`) with a //! per-step spinner cascade so the user can see what's happening -//! while the Safehouse microVM and in-VM cast services come up. +//! while Big Smooth (the daemon) comes up. //! //! Visuals: //! //! ```text //! ✻ Smooth booting -//! ⠋ starting Safehouse microVM… -//! ⠋ cast online (wonk · goalie · narc · scribe · archivist · diver · groove)… -//! ⠋ operative pool warm… +//! ⠋ starting Big Smooth… +//! ⠋ dolt store online… +//! ⠋ dispatch ready… //! ⠋ health check… //! ``` //! @@ -160,7 +160,7 @@ mod tests { #[test] fn step_ok_completes_cleanly() { let ind = BootIndicator::new(); - let step = ind.step("starting Safehouse microVM"); + let step = ind.step("starting Big Smooth"); // No real work — just check the lifecycle. step.ok(); ind.finish(); @@ -179,9 +179,9 @@ mod tests { #[test] fn multiple_steps_finalize_in_arbitrary_order() { let ind = BootIndicator::new(); - let s1 = ind.step("starting Safehouse microVM"); - let s2 = ind.step("cast online (wonk · goalie · narc · scribe · archivist · diver · groove)"); - let s3 = ind.step("operative pool warm"); + let s1 = ind.step("starting Big Smooth"); + let s2 = ind.step("dolt store online"); + let s3 = ind.step("dispatch ready"); let s4 = ind.step("health check"); // Finalize out of order to confirm the API doesn't require @@ -198,8 +198,8 @@ mod tests { #[test] fn step_update_is_safe() { let ind = BootIndicator::new(); - let step = ind.step("cast online"); - step.update("cast online (3/7 services up)"); + let step = ind.step("dolt store online"); + step.update("dolt store online (pearls)"); step.ok(); ind.finish(); } @@ -208,7 +208,7 @@ mod tests { #[test] fn indicator_drops_without_finish_call() { let ind = BootIndicator::new(); - let step = ind.step("starting Safehouse microVM"); + let step = ind.step("starting Big Smooth"); step.ok(); // No ind.finish() — Drop should handle it. } diff --git a/crates/smooth-cli/src/daemon_launcher.rs b/crates/smooth-cli/src/daemon_launcher.rs index df017fc69..059cf1bf8 100644 --- a/crates/smooth-cli/src/daemon_launcher.rs +++ b/crates/smooth-cli/src/daemon_launcher.rs @@ -6,7 +6,7 @@ //! operator (axum + the engine + adapters + the widget bundle). The daemon is a //! separate artifact, resolved at first use and (if absent) downloaded from the //! GitHub release — the same "auxiliary native binary" shape `th` already uses -//! for `smooth-dolt` / `smooth-operative`. +//! for `smooth-dolt`. //! //! `th daemon ` is a **pure passthrough**: the args are forwarded to //! `smooth-daemon `, which owns the full daemon CLI (`run` / `operator` / diff --git a/crates/smooth-cli/src/main.rs b/crates/smooth-cli/src/main.rs index fcea7bf15..041eedf47 100644 --- a/crates/smooth-cli/src/main.rs +++ b/crates/smooth-cli/src/main.rs @@ -1950,7 +1950,7 @@ async fn cmd_status() -> Result<()> { let (icon, label) = status_indicator(db_status); println!(" {icon} {:<16} {} {}", "Dolt store", label, "(pearls + config)".dimmed()); - // Smooth operatives (sandboxed AI agents in microVMs) + // Smooth operatives (in-process agent turns; bash under the kernel sandbox) let sandbox_status = body["sandbox"].as_str().or_else(|| body["sandboxes"].as_str()).unwrap_or("healthy"); let active = body["sandbox_active"].as_u64().or_else(|| body["sandboxes_active"].as_u64()).unwrap_or(0); let max = body["sandbox_max"].as_u64().or_else(|| body["sandboxes_max"].as_u64()).unwrap_or(3); @@ -2826,7 +2826,7 @@ fn print_explainer() { println!("{}", "What it does".bold().bright_yellow()); println!(" • Interactive AI coding TUI {}", "th code".bright_cyan()); println!( - " • microVM orchestration via Big {} + cast {}", + " • Always-on agent daemon: Big {} {}", gradient::smooth(), "th up / th down / th status".bright_cyan() ); @@ -3027,34 +3027,30 @@ async fn cmd_code( } } - // Check if Big Smooth is running. If not, boot the Safehouse — - // the same daemonized sandboxed path `th up` takes. This is the - // only auto-start path; we never start a bare in-process Big - // Smooth on the host, because that bypasses every guarantee of - // the Safehouse (microsandbox isolation, in-VM cast, SAFEHOUSE_MODE - // dispatch routing). See ADR-001 + ADR-003 + the user-facing rule - // that this is dev tooling, not a release artifact — no fallback - // to the legacy host-bind path. + // Check if Big Smooth is running. If not, boot it via the same + // daemonized path `th up` takes — one auto-start route, so the + // daemon always comes up with the same env, log file, and pid file + // regardless of which command triggered it. let client = reqwest::Client::builder().timeout(std::time::Duration::from_secs(2)).build()?; let health = client.get("http://localhost:4400/health").send().await; if health.is_err() || !health.as_ref().is_ok_and(|r| r.status().is_success()) { // Pearl th-7840d8 — animated boot indicator (was a bare - // `Starting Smooth...`). The Safehouse daemonization - // happens in the background via `th up`; the parent polls - // `/health` and advances steps based on observable signals. + // `Starting Smooth...`). Daemonization happens in the + // background via `th up`; the parent polls `/health` and + // advances steps based on observable signals. Labels match the + // `th up` path so both cold-start routes look identical. let indicator = boot_ui::BootIndicator::new(); - let step_vm = indicator.step("starting Safehouse microVM"); - let step_cast = indicator.step("cast online (wonk · goalie · narc · scribe · archivist · diver · groove)"); - let step_runner = indicator.step("operative pool warm"); + let step_vm = indicator.step("starting Big Smooth"); + let step_cast = indicator.step("dolt store online"); + let step_runner = indicator.step("dispatch ready"); let step_health = indicator.step("health check"); - // Re-exec ourselves as `th up` so the Safehouse daemonizes - // exactly the way it would if the user had typed `th up`. - // The child detaches its stdio to ~/.smooth/smooth.log, - // writes ~/.smooth/smooth.pid, returns immediately, and the - // safehouse microVM keeps running in the background until - // `th down`. + // Re-exec ourselves as `th up` so Big Smooth daemonizes exactly + // the way it would if the user had typed `th up`. The child + // detaches its stdio to ~/.smooth/smooth.log, writes + // ~/.smooth/smooth.pid, returns immediately, and the daemon + // keeps running in the background until `th down`. let exe = std::env::current_exe()?; let status = std::process::Command::new(exe) .arg("up") @@ -3062,7 +3058,7 @@ async fn cmd_code( .stderr(std::process::Stdio::null()) .stdin(std::process::Stdio::null()) .status() - .context("spawn `th up` to boot the Safehouse")?; + .context("spawn `th up` to boot Big Smooth")?; if !status.success() { // The daemon spawn itself failed before the VM ever // got off the ground. Mark every step failed so the @@ -3081,11 +3077,10 @@ async fn cmd_code( // the four steps. // // The signals we can actually probe from the host: - // * VM up: TCP connect to localhost:4400 succeeds (port - // forward is plumbed). - // * cast online + runner pool: implied once /health - // responds; the safehouse only flips the listener on - // after its internal init is done. + // * daemon up: TCP connect to localhost:4400 succeeds. + // * store + dispatch ready: implied once /health responds; + // the daemon only flips the listener on after its internal + // init is done. // // So we drive step_vm off the TCP probe, and once /health // returns 200 we cascade the remaining three. This is @@ -3111,14 +3106,14 @@ async fn cmd_code( step_runner.fail("not reached"); step_health.fail("not reached"); indicator.finish(); - anyhow::bail!("Safehouse microVM never opened :4400 — check ~/.smooth/smooth.log"); + anyhow::bail!("Big Smooth never opened :4400 — check ~/.smooth/smooth.log"); } step_vm.ok(); - // Step 2 + 3: wait for /health to respond at all (any - // response means the safehouse listener is up; the cast + - // runner-pool init is what's gating that listener flipping - // on). We split them visually for the receipt. + // Step 2 + 3: wait for /health to respond at all (any response + // means the daemon's listener is up; store + dispatch init is + // what gates that listener flipping on). We split them + // visually for the receipt. let cast_deadline = std::time::Instant::now() + TIMEOUT_PER_STEP; let mut listener_up = false; while std::time::Instant::now() < cast_deadline { @@ -3133,7 +3128,7 @@ async fn cmd_code( step_runner.fail("not reached"); step_health.fail("not reached"); indicator.finish(); - anyhow::bail!("Safehouse :4400 accepted TCP but never answered HTTP — check ~/.smooth/smooth.log"); + anyhow::bail!("Big Smooth :4400 accepted TCP but never answered HTTP — check ~/.smooth/smooth.log"); } step_cast.ok(); step_runner.ok(); @@ -3152,7 +3147,7 @@ async fn cmd_code( if !ready { step_health.fail("timeout"); indicator.finish(); - anyhow::bail!("Safehouse booted but :4400 never became healthy — check ~/.smooth/smooth.log"); + anyhow::bail!("Big Smooth booted but :4400 never became healthy — check ~/.smooth/smooth.log"); } step_health.ok(); indicator.finish(); diff --git a/crates/smooth-cli/src/mcp_config.rs b/crates/smooth-cli/src/mcp_config.rs index db46901d6..ed12d469c 100644 --- a/crates/smooth-cli/src/mcp_config.rs +++ b/crates/smooth-cli/src/mcp_config.rs @@ -1,9 +1,9 @@ -//! MCP server config — TOML schema shared with `smooth-operative`. +//! MCP server config — the TOML schema for `~/.smooth/mcp.toml`. //! -//! The runner is the consumer; this module exists so `th mcp` commands can -//! manage `~/.smooth/mcp.toml` without pulling rmcp into the CLI binary. -//! Keep the schema in lockstep with `crates/smooth-operative/src/mcp.rs` -//! (`McpConfig` / `McpServerConfig`) — they round-trip through the same file. +//! The agent runtime is the consumer; this module exists so `th mcp` commands +//! can manage the file without pulling rmcp into the CLI binary. Anything else +//! that reads `mcp.toml` must round-trip the same `McpConfig` / +//! `McpServerConfig` shape. //! //! ## Shipped defaults //! diff --git a/crates/smooth-daemon/Cargo.toml b/crates/smooth-daemon/Cargo.toml index 3feba7b26..b04c4282b 100644 --- a/crates/smooth-daemon/Cargo.toml +++ b/crates/smooth-daemon/Cargo.toml @@ -10,19 +10,19 @@ description = "Big Smooth, reborn — the always-on, single-tenant personal-agen [lib] name = "smooth_daemon" -# The always-on daemon binary. Runnable standalone (`smooth-daemon`); later -# wired behind `th up` / `th service` (a separate, cross-crate pearl). +# The always-on daemon binary — Big Smooth. Runnable standalone +# (`smooth-daemon`); `th daemon` is a passthrough to it and `th up` launches it. +# Deliberately NOT linked into `th`: most users install the CLI for +# api/jira/pearls/config and shouldn't pay for axum + the engine + the widget +# bundle (see crates/smooth-cli/src/daemon_launcher.rs). [[bin]] name = "smooth-daemon" path = "src/main.rs" -# th-f30175: Phase 0 scaffold. The always-on daemon is a clean rewrite of -# smooth-bigsmooth on the smooth-operator engine, dropping the microsandbox -# substrate in favour of a kernel OS-sandbox + egress proxy + auto-mode -# permission engine. It lives ALONGSIDE smooth-bigsmooth so `th up` keeps -# working on the old path until the new daemon reaches parity. Dependencies are -# intentionally lean here; Phase 1 (th-64fbe8) adds the axum server, session -# store, and SessionRunCoordinator wiring. +# th-f30175: the always-on daemon replaced smooth-bigsmooth (deleted with the +# microVM stack, 2026-07/th-f4a801). It runs on the smooth-operator engine, with +# security from a kernel OS-sandbox + egress proxy + the engine's permission +# hook rather than a microVM substrate. [dependencies] smooth-operator.workspace = true # EPIC th-c89c2a: the OPERATOR (local deployment flavor) embedded in-process — diff --git a/crates/smooth-daemon/src/lib.rs b/crates/smooth-daemon/src/lib.rs index 4dd0fd94d..cc89d87ad 100644 --- a/crates/smooth-daemon/src/lib.rs +++ b/crates/smooth-daemon/src/lib.rs @@ -3,9 +3,10 @@ //! `smooth-daemon` runs **smooth-operator's local deployment flavor** as its //! one and only agent runtime. It targets a **single trusted operator** //! self-hosting their own instance (hermes-style) on a personal machine -//! reachable over SSH/Tailscale — NOT a multi-tenant service. The microsandbox -//! microVM substrate is gone; security is a kernel-enforced OS sandbox on tool -//! subprocesses + an egress allowlist proxy (EPIC th-c89c2a). +//! reachable over SSH/Tailscale — NOT a multi-tenant service. Security is a +//! kernel-enforced OS sandbox on tool subprocesses + an egress allowlist proxy +//! (EPIC th-c89c2a); the microVM substrate it replaced was removed in 2026-07 +//! (pearl th-f4a801). //! //! # Shape — one operator, one protocol (the north star) //! diff --git a/crates/smooth-diver/src/lib.rs b/crates/smooth-diver/src/lib.rs index 6ad794eda..900d0f38c 100644 --- a/crates/smooth-diver/src/lib.rs +++ b/crates/smooth-diver/src/lib.rs @@ -1,6 +1,6 @@ //! # Smooth Diver //! -//! The Pearl Diver — Safehouse cast member that owns the pearl lifecycle. +//! The Pearl Diver — owns the pearl lifecycle. //! Creates pearls on dispatch, closes on completion, tracks sub-pearls, //! manages the work model (parent-child, deps, labels, costs), and syncs //! bidirectionally with Jira. diff --git a/crates/smooth-goalie/Cargo.toml b/crates/smooth-goalie/Cargo.toml index b256868f9..06798d438 100644 --- a/crates/smooth-goalie/Cargo.toml +++ b/crates/smooth-goalie/Cargo.toml @@ -8,7 +8,7 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -description = "Smooth Goalie — in-VM HTTP forward proxy, delegates access decisions to Wonk" +description = "Smooth Goalie — HTTP forward proxy with an exact-host allowlist and audit log; the daemon's egress boundary" readme = "README.md" [lib] diff --git a/crates/smooth-goalie/src/lib.rs b/crates/smooth-goalie/src/lib.rs index ef988505a..d2325912f 100644 --- a/crates/smooth-goalie/src/lib.rs +++ b/crates/smooth-goalie/src/lib.rs @@ -1,10 +1,17 @@ -//! Smooth Goalie — in-VM HTTP forward proxy. Delegates every access decision -//! to Wonk and writes JSON-lines audit entries for every allowed or blocked +//! Smooth Goalie — HTTP forward proxy. Decides every request against an +//! allowlist and writes JSON-lines audit entries for every allowed or blocked //! request. //! -//! This library surface exists so integration tests (and, in the future, an -//! in-process use of Goalie by Big Smooth for local-only agents) can spin up -//! the proxy without going through the `smooth-goalie` binary. +//! **How it is used today:** `smooth-daemon`'s `start_egress_proxy` runs +//! [`run_proxy_local`] in-process with an [`EgressAllowlist`] and an +//! [`AuditLogger`]. That loopback proxy is the daemon's **egress boundary** — +//! `smooth-tools`' kernel sandbox denies direct outbound network and points +//! `HTTP(S)_PROXY` at it, so off-box traffic must clear the allowlist here. +//! +//! The [`wonk`] delegation path and the `smooth-goalie` binary are leftovers +//! from the removed microVM stack (2026-07, pearl th-f4a801), where Goalie ran +//! inside the VM and asked Wonk for each decision. Nothing in the tree drives +//! that path now. New callers want [`run_proxy_local`] / [`run_proxy_with`]. pub mod allowlist; pub mod audit; diff --git a/crates/smooth-policy/Cargo.toml b/crates/smooth-policy/Cargo.toml index c098508d4..50810e712 100644 --- a/crates/smooth-policy/Cargo.toml +++ b/crates/smooth-policy/Cargo.toml @@ -8,7 +8,7 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -description = "Smooth policy types — TOML-based access control for Microsandbox operators" +description = "Smooth policy types — TOML-based access control for Smooth operatives" readme = "README.md" keywords = ["policy", "access-control", "toml", "security", "ai-agents"] categories = ["config", "authentication"] diff --git a/crates/smooth-policy/src/ext_trust.rs b/crates/smooth-policy/src/ext_trust.rs index 30d829cf0..a02ca20a5 100644 --- a/crates/smooth-policy/src/ext_trust.rs +++ b/crates/smooth-policy/src/ext_trust.rs @@ -1,11 +1,10 @@ //! SEP extension trust store — the content-hashed allow-list that decides which //! discovered extensions a host may load. //! -//! Lives in `smooth-policy` (a leaf crate) so **both** frontends that host -//! extensions can share one trust surface: `smooth-code` (the TUI, which writes -//! it via `th ext trust`) and `smooth-operative` (the dispatched worker, which -//! reads it to load only pre-trusted extensions in an unattended run). Keeping it -//! here avoids the operative depending on the TUI crate. +//! Lives in `smooth-policy` (a leaf crate) so every host that loads extensions +//! shares one trust surface without depending on the TUI crate: `smooth-code` +//! writes it via `th ext trust`, and unattended hosts read it to load only +//! pre-trusted extensions. //! //! Trust is keyed by extension name and pinned to the content hash of the //! extension's `extension.toml` at the moment trust was granted; any change to diff --git a/crates/smooth-policy/src/lib.rs b/crates/smooth-policy/src/lib.rs index 7be65f4af..783a002f7 100644 --- a/crates/smooth-policy/src/lib.rs +++ b/crates/smooth-policy/src/lib.rs @@ -35,8 +35,12 @@ pub type Result = std::result::Result; /// Maps a guest (in-VM) path prefix to the corresponding host path. /// -/// Wonk uses these mappings to translate guest paths before checking -/// filesystem deny patterns, which are expressed in host-relative terms. +/// **Vestigial.** Wonk used these mappings to translate guest paths before +/// checking host-relative filesystem deny patterns. The microVM stack (and +/// Wonk) went away in 2026-07 (pearl th-f4a801); nothing translates paths +/// today because tools run on the host. Kept because `Policy` serializes a +/// `mounts` table and dropping the field would break parsing of existing +/// policy TOML. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] pub struct MountMapping { pub guest_path: String, diff --git a/crates/smooth-tools/Cargo.toml b/crates/smooth-tools/Cargo.toml index cab21f4ed..33cb31e9c 100644 --- a/crates/smooth-tools/Cargo.toml +++ b/crates/smooth-tools/Cargo.toml @@ -10,11 +10,10 @@ description = "Reusable agent tools (filesystem, grep, bash) with workspace-scop [lib] name = "smooth_tools" -# th-ed6604: clean reimplementations of the core agent tools the smooth-operative -# binary defines inline, packaged as a lib the smooth-daemon (and, later, -# smooth-operative) can register on a ToolRegistry. The workspace-path scoping -# (path.rs) mirrors smooth-operative/src/tool_support.rs::resolve_workspace_path -# exactly — it is security-critical and exhaustively tested here. +# th-ed6604: the core agent tools as a lib any host can register on a +# ToolRegistry — today that's the smooth-daemon. The workspace-path scoping +# (path.rs) and the kernel OS sandbox (sandbox.rs) are security-critical and +# exhaustively tested here. [dependencies] smooth-operator.workspace = true smooth-policy.workspace = true diff --git a/crates/smooth-tools/src/lib.rs b/crates/smooth-tools/src/lib.rs index 4aade23bf..a14f2a2c4 100644 --- a/crates/smooth-tools/src/lib.rs +++ b/crates/smooth-tools/src/lib.rs @@ -1,8 +1,7 @@ //! `smooth-tools` — reusable agent tools with workspace-confined paths. //! -//! Clean reimplementations of the core tools the `smooth-operative` binary -//! defines inline, packaged so the `smooth-daemon` (and, later, the operative -//! itself) can register them on a [`ToolRegistry`] with one call +//! The core agent tool set, packaged so a host — today the `smooth-daemon` — +//! can register them all on a [`ToolRegistry`] with one call //! ([`register_default_tools`]). //! //! Every filesystem tool routes user paths through diff --git a/crates/smooth-tools/src/path.rs b/crates/smooth-tools/src/path.rs index 214ae0786..e048298f5 100644 --- a/crates/smooth-tools/src/path.rs +++ b/crates/smooth-tools/src/path.rs @@ -1,6 +1,5 @@ //! Workspace path confinement — the security floor for every filesystem tool. //! -//! Replicated from `smooth-operative/src/tool_support.rs::resolve_workspace_path`. //! Every tool that touches the filesystem MUST route user-supplied paths //! through [`resolve_workspace_path`] so a prompt-injected agent can't read or //! write outside the workspace. The confinement is **lexical** (no diff --git a/package.json b/package.json index 6f38c5b6b..c11baad63 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "build:smooth-dolt": "bash scripts/build-smooth-dolt.sh", "build:smooth-dolt:if-stale": "bash scripts/build-smooth-dolt.sh --if-stale", "install:smooth-dolt": "bash scripts/install-smooth-dolt-to-cargo-bin.sh", - "install:th": "pnpm build:web && pnpm build:smooth-dolt:if-stale && pnpm install:smooth-dolt && cargo install --path crates/smooth-cli --features admin --force && cargo install --path crates/smooth-operative --force", - "install:th:full": "pnpm build:web && pnpm build:smooth-dolt && pnpm install:smooth-dolt && cargo install --path crates/smooth-cli --features admin --force && cargo install --path crates/smooth-operative --force", + "install:th": "pnpm build:web && pnpm build:smooth-dolt:if-stale && pnpm install:smooth-dolt && cargo install --path crates/smooth-cli --features admin --force && cargo install --path crates/smooth-daemon --force", + "install:th:full": "pnpm build:web && pnpm build:smooth-dolt && pnpm install:smooth-dolt && cargo install --path crates/smooth-cli --features admin --force && cargo install --path crates/smooth-daemon --force", "install:th:brew": "brew upgrade SmooAI/tools/th || brew install SmooAI/tools/th", "install:skills": "bash scripts/install-skills.sh", "dev:service": "pnpm install:th && th service restart", diff --git a/scripts/bench.sh b/scripts/bench.sh deleted file mode 100755 index db9f0ba17..000000000 --- a/scripts/bench.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -# Thin wrapper for the internal Smooth benchmark harness. -# -# Not part of the user-facing `th` binary. Requires Big Smooth to -# be running locally (`th up`) so the harness can dispatch tasks -# over WebSocket. -# -# Example: -# scripts/bench.sh aider-polyglot --task grade-school --lang python -set -euo pipefail -cd "$(dirname "$0")/.." -exec cargo run --release -p smooai-smooth-bench -- "$@"