Merge v3-t63: docs/RUNNING.md, the cutover guide (T63)
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
|
||||
You can see how the sausage is made.
|
||||
|
||||
**v3:** Read [Run Sausage v3 on Citadel](docs/RUNNING.md) for the console, approvals, and current bootstrap gaps.
|
||||
The rest of this README describes v2.
|
||||
|
||||
Sausage is a world of small WASI programs ("plugins") that you grow by
|
||||
talking to it. A plugin is a `.wasm` file with JSON-RPC methods. Each call
|
||||
to a plugin starts a new process. The agent is a plugin too: it writes new
|
||||
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
# Run Sausage v3 on Citadel
|
||||
|
||||
The console is the UI. The browser handles credential approvals, not chat.
|
||||
|
||||
**TL;DR:** Use a separate v3 world and the release host. Keep its complete folder between starts.
|
||||
The development bundle now seeds a complete source tree, including agents, skills, and extensions.
|
||||
The Nix package does not yet supply that bundle. Verify the remaining cutover steps below before production use.
|
||||
The rest of the root README describes v2.
|
||||
|
||||
## Build and start
|
||||
|
||||
Run host commands from a checkout of the `v3` branch on Citadel.
|
||||
Use Nix with flakes. The package supplies the native Rust builder and its tools.
|
||||
|
||||
The package entry point is:
|
||||
|
||||
```sh
|
||||
nix run . -- run "$HOME/sausage-v3" --userland "$SAUSAGE_USERLAND"
|
||||
```
|
||||
|
||||
`SAUSAGE_USERLAND` must name an assembled bundle with `bin/` and `src/` directories.
|
||||
It is not a world folder. The CLI also accepts this variable without `--userland`.
|
||||
The package does not set it for you.
|
||||
|
||||
The source-tree bundle builder is:
|
||||
|
||||
```sh
|
||||
nix develop -c nu userland/bundle.nu
|
||||
```
|
||||
|
||||
It builds the world commands and creates `target/userland/bundle` by default.
|
||||
It snapshots committed source, not uncommitted edits.
|
||||
The source archive includes the complete `agents/`, `skills/`, and `extensions/` directories.
|
||||
The world copier preserves symlinks, including dangling links in the source tree.
|
||||
A fresh development world no longer needs a manual source copy.
|
||||
|
||||
For a release host from the checkout:
|
||||
|
||||
```sh
|
||||
nix develop -c cargo build -p sausage --release --locked
|
||||
```
|
||||
|
||||
Cargo puts the host under `target/release/sausage`, unless `CARGO_TARGET_DIR` selects another directory.
|
||||
Use that binary with the same `run WORLD --userland BUNDLE` arguments.
|
||||
Use the repository's build slots and shared executor target directory for agent validation builds.
|
||||
|
||||
Choose `$HOME/sausage-v3`, not the live v2 `~/world`.
|
||||
For the actual cutover, let v2 finish its work before you stop it.
|
||||
Do not run two production services. This guide does not stop or replace the deployed service.
|
||||
The host creates this layout:
|
||||
|
||||
| Host path | Purpose |
|
||||
| --- | --- |
|
||||
| `sausage-v3/world/` | The world root `/`, including programs and editable source. |
|
||||
| `sausage-v3/host/` | Private host state, including credentials, harness storage, undo, and builder caches. |
|
||||
|
||||
The host creates `/run` in memory. It generates `/proc` at runtime.
|
||||
It copies a bundle only when `WORLD/world` does not exist.
|
||||
A later `--userland` does not update an existing world.
|
||||
Even a failed initial start can create `WORLD/world`. Do not assume another start will seed it.
|
||||
Keep both `world/` and `host/` for recovery. Never copy secrets into the world.
|
||||
|
||||
Sources: [`main.rs`](../host/src/main.rs), [`world.rs`](../host/src/world.rs),
|
||||
[`flake.nix`](../flake.nix), and [`bundle.nu`](../userland/bundle.nu).
|
||||
|
||||
## Use and stop the console
|
||||
|
||||
Each nonempty console line submits a message to the durable root conversation.
|
||||
Assistant text appears on stdout. This is not a shell or the v2 command dispatcher.
|
||||
Text such as `quit`, `help`, and `core.restart` has no special host meaning.
|
||||
Ask the agent to execute world commands when you need tools.
|
||||
|
||||
Press **Ctrl-C** to stop the host. SIGTERM also requests orderly shutdown.
|
||||
EOF only detaches the console input. It does not stop the host.
|
||||
Keep the foreground terminal available if you want to continue chat.
|
||||
|
||||
After a crash, repeat the same start command with the same world folder.
|
||||
The host opens `host/harness.sqlite`, restores extensions, and resumes durable work.
|
||||
It does not replay the historical transcript into the console.
|
||||
It cannot restore ordinary process memory or reverse completed network effects.
|
||||
There is no automatic restart supervisor in this guide.
|
||||
The existing flake loop still contains v2 assumptions. Do not use its no-argument mode for v3.
|
||||
|
||||
Sources: [`input.rs`](../host/src/harness/input.rs), [`main.ts`](../host/js/main.ts),
|
||||
and [`harness/mod.rs`](../host/src/harness/mod.rs).
|
||||
|
||||
## Reach the approval pages
|
||||
|
||||
The host listens on `127.0.0.1:8081` by default.
|
||||
If another service owns port 8081, choose another host port with `--api-listen`.
|
||||
Adjust the tunnel's remote port, but retain laptop port 8081 for the default origin.
|
||||
From the laptop, create an SSH tunnel to Citadel:
|
||||
|
||||
```sh
|
||||
ssh -N -L 8081:127.0.0.1:8081 citadel.vpn.surma.technology
|
||||
```
|
||||
|
||||
Open **http://localhost:8081/register** on the laptop.
|
||||
Keep the laptop port at 8081 to match the configured browser origin.
|
||||
The defaults are `--api-rp-id localhost` and `--api-origin http://localhost:8081`.
|
||||
|
||||
For direct VPN access, the default loopback listener is insufficient.
|
||||
You need a reachable listener, HTTPS termination, and matching RP ID and origin options.
|
||||
Plain HTTP on the VPN hostname does not provide the browser's secure context for passkeys.
|
||||
The SSH tunnel avoids that extra setup for localhost tests.
|
||||
**To check with Surma:** the laptop tunnel, browser passkey registration, secret entry, and request approval.
|
||||
The planned production origin is `https://sausage.apps.surma.technology`, with RP ID `sausage.apps.surma.technology`.
|
||||
Passkeys registered for `localhost` do not transfer to that RP ID. Plan a separate production registration.
|
||||
Do not expose the listener before the first registration to people who must not approve requests.
|
||||
|
||||
### Register, create an item, enter a secret
|
||||
|
||||
1. Open `/register` and choose a label for the first passkey.
|
||||
2. Complete the browser's passkey ceremony.
|
||||
3. Ask the agent to file an item creation request through `POST /cred/requests`.
|
||||
4. Review the request on `/approve/REQUEST_ID` and approve it with your passkey.
|
||||
5. Read the item's public metadata to find its item ID and stable field ID.
|
||||
6. Open `/entry/ITEM_ID/FIELD_ID` on the host origin.
|
||||
7. Enter the secret in the blank host form and complete its passkey ceremony.
|
||||
|
||||
Until the first passkey exists, D53 permits unlocked decisions and first registration.
|
||||
After registration, decisions require a passkey. Additional registrations require an existing passkey.
|
||||
Secret entry requires a passkey even during bootstrap.
|
||||
The browser encrypts the secret before submission. The world never receives its plaintext.
|
||||
Do not paste secrets into chat or world files.
|
||||
|
||||
There is no item editor or credential dashboard yet.
|
||||
Item creation uses `kind: "edit"` and a stable request key.
|
||||
Its request has `item: null`, `base_version: null`, and a `change` array.
|
||||
The creation operation is `{op: "create_item", config: ITEM}`.
|
||||
`ITEM` must contain the complete public item configuration, including its fields, rules, and transformation.
|
||||
Further edits add fields and rules. Inspect the typed operations before you construct those edits.
|
||||
Public reads use `/cred/items` and `/cred/items/ITEM_ID`.
|
||||
They do not return stored secret values.
|
||||
|
||||
### Approve a request
|
||||
|
||||
The agent or world UI obtains pending requests from `/harness/credential-requests` or `/cred/requests?state=pending`.
|
||||
Follow the approval link on the host origin through the tunnel.
|
||||
Review the stored summary and public details, choose limits when applicable, then approve or deny.
|
||||
The world can file a request. It cannot approve its own request.
|
||||
|
||||
Sources: [`host API`](../host/src/api/README.md), [`pages.rs`](../host/src/api/pages.rs),
|
||||
[`edits.rs`](../host/src/cred/edits.rs), and [`app.js`](../host/src/api/app.js).
|
||||
|
||||
## Models
|
||||
|
||||
The host requires an `llm-proxy` credential before the harness starts.
|
||||
On an unlocked new store, it can bootstrap that item from these host environment variables:
|
||||
|
||||
- `PI_PROXY_BASE_URL`: the proxy base URL, without the appended `/v1`.
|
||||
- `PI_PROXY_API_KEY`: the proxy API key.
|
||||
|
||||
Supply them through your host secret mechanism. Do not print them or commit them.
|
||||
Do not put either value in a world configuration file.
|
||||
Once the item exists, the host uses it rather than replacing it from the environment.
|
||||
The bootstrap is not a general credential import interface for a locked store.
|
||||
|
||||
World model families live at `/src/sausage/agents/models.json`.
|
||||
Agent definitions live in `/src/sausage/agents/*.md`.
|
||||
Project overlays live in `<repo>/.sausage/agents/`.
|
||||
The models extension reads these files at each run boundary. Edits affect the next run without a restart.
|
||||
An explicit model override can take precedence over a family.
|
||||
The initial console root has a host-defined model. Do not assume a family edit changes every existing conversation.
|
||||
All real model requests retain the host proxy transport.
|
||||
|
||||
Sources: [`credentials.rs`](../host/src/harness/credentials.rs), [`main.ts`](../host/js/main.ts),
|
||||
and [`models`](../extensions/models/README.md).
|
||||
|
||||
## Change extensions
|
||||
|
||||
The world checkout is `/src/sausage`. The loader follows its **`main`** branch, not the host checkout's `v3` branch.
|
||||
Each extension lives at `extensions/NAME/server.ts` inside that checkout.
|
||||
Commit extension edits to `main` with world git. A saved, uncommitted edit does not reload an extension.
|
||||
Use `--no-gpg-sign` for commits.
|
||||
|
||||
The loader imports a detached snapshot under `/run/sausage/extensions/COMMIT`.
|
||||
It validates the entire candidate before it replaces the live registry.
|
||||
A broken candidate leaves the previous good commit active.
|
||||
An active tool call retains its implementation. The next call uses the new implementation.
|
||||
|
||||
Read `/proc/extensions` through the agent's file tools to inspect `current`, `good`, and `error`.
|
||||
To restore the last good branch ref, ask the agent to execute:
|
||||
|
||||
```sh
|
||||
extensions-revert
|
||||
```
|
||||
|
||||
The command preserves editable files. It changes `refs/heads/main`, which triggers the normal reload.
|
||||
At startup, the loader tries the tip and then the last good commit before it resumes work.
|
||||
|
||||
Source: [`extensions`](../extensions/README.md) and [`extensions.ts`](../host/js/extensions.ts).
|
||||
|
||||
## Everyday tools inside the world
|
||||
|
||||
These are world programs, not console commands. Ask the agent to run them.
|
||||
|
||||
| Tool | Use and limits |
|
||||
| --- | --- |
|
||||
| `cargo` | Build, run, and test Rust programs. The host copies the workspace through the VFS, fetches dependencies, then builds offline in namespaces. It publishes WASI artifacts into the world. Other targets are unsupported. |
|
||||
| `box` | Build non-Rust projects or run browser tests in disposable containers. The default service is `citadel.vpn.surma.technology:8093`. It requires network access and service availability. Remove boxes after use. |
|
||||
| `undo` | Inspect tags and restore filesystem changes. Later edits can conflict. Network effects and native builds are not reversible. Retention is seven days or 4 GiB of unique content. |
|
||||
| `systemctl` | Control world init services, not Citadel's systemd. Persistent units live under `/etc/systemd/system`. Init observes edits without daemon reload. |
|
||||
| `journalctl` | Read world service logs under `/var/log/journal`. Each unit retains a 1 MiB log and one rotated file. |
|
||||
| `git` | Manage world repositories. HTTPS credential rules can inject authorization through the host HTTP hook. SSH remotes use host signing. |
|
||||
| `ssh` | Execute a remote command without a PTY. Public identities and signatures come from the host. Private keys never enter the world. Host keys live in the world's known_hosts file. |
|
||||
|
||||
Init starts when the host starts the runtime, if `/bin/init.wasm` exists.
|
||||
Transient units under `/run/systemd/transient` disappear after restart.
|
||||
A missing credential grant causes a pending host request, not an automatic approval.
|
||||
A changed SSH host key fails even with the accept-new policy.
|
||||
|
||||
Command reference and implementation evidence:
|
||||
[`builder`](../host/src/builder/README.md), [`box`](../userland/box/README.md),
|
||||
[`undo`](../userland/undo/README.md), [`init`](../userland/init/README.md),
|
||||
[`git`](../userland/git/README.md), and [`ssh`](../userland/ssh/README.md).
|
||||
|
||||
## Build a world UI with the host API
|
||||
|
||||
World programs call **`http://host.sausage`** through standard WASI HTTP.
|
||||
The listener on port 8081 does **not** expose harness routes.
|
||||
A browser UI needs a world server that forwards these requests.
|
||||
Keep credential approval and secret entry on host pages.
|
||||
|
||||
| Method and route | Result or request |
|
||||
| --- | --- |
|
||||
| `GET /harness/conversations` | Conversation records, titles, ownership, ancestry, and state. |
|
||||
| `GET /harness/conversations/ID/entries?after=0` | `{entries:[...]}` in ascending order. |
|
||||
| `GET /harness/conversations/ID/stream?after=0` | NDJSON entries, replay first, then live commits. |
|
||||
| `POST /harness/conversations/ID/submit` | `{content:"text",requestId:"stable-id"}` returns 202 and a submission ID. Reuse the request ID for retries. |
|
||||
| `POST /harness/conversations/ID/stop` | Stops active turns and owned work. Queued input remains. |
|
||||
| `GET /harness/events?after=0` | NDJSON events, replay first, then live commits. |
|
||||
| `POST /harness/extensions/NAME/actions/ACTION` | `{args:{...},conversationId?:number}` invokes an exported user action. |
|
||||
| `GET /harness/models` | Public model catalog without authentication data. |
|
||||
| `GET /harness/families` | World model families. |
|
||||
| `GET /harness/extensions` | Loaded extension status. |
|
||||
| `GET /harness/credential-requests` | Pending conversation requests with host approval links. |
|
||||
|
||||
The `after` cursor is exclusive. Reconnect with the last record ID.
|
||||
Streams close when the client disconnects or the harness stops.
|
||||
Do not render a stream as one JSON response.
|
||||
An unavailable harness returns 503. Invalid input returns 400. Missing resources return 404.
|
||||
|
||||
Source: [`host API contract`](../host/src/api/README.md),
|
||||
[`harness-api.ts`](../host/js/harness-api.ts), and [`harness/http.rs`](../host/src/harness/http.rs).
|
||||
|
||||
## Known gaps before cutover
|
||||
|
||||
- The Nix package wraps the host and builder tools but does not ship or select the default userland bundle.
|
||||
- The development bundle passes fresh-world startup checks. The Nix package start remains unverified until its bundle integration lands.
|
||||
- Laptop access, passkey ceremonies, secret entry, and browser approval remain **to check with Surma**.
|
||||
- Startup checks use fake proxy credentials. They do not verify a real model session or external credential injection.
|
||||
- A usable `llm-proxy` credential is mandatory even before the first chat.
|
||||
- The browser has approval, passkey, and secret-entry pages, but no item editor, dashboard, or chat UI.
|
||||
- The console has no shell commands, transcript navigation, or special quit command.
|
||||
- `sausage sh` requires `-c`. Its interactive shell is not implemented.
|
||||
- The flake's runtime-toolchain check and loop comments still describe v2 behavior.
|
||||
- This guide does not migrate a v2 world. Keep the live v2 folder untouched.
|
||||
|
||||
A successful development start does not verify the Nix package, external services, or the production passkey origin.
|
||||
Reference in New Issue
Block a user