diff --git a/README.md b/README.md index 7aeea48..4f8839b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ A persona engine for AI coding agents. Install behavioral identities as versione **Status:** The CLI, pack system, orchestrator, watch daemon, marketplace server, and web frontend all work. You can clone this repo and use the personas today via the CLI, or publish and browse packs against the live marketplace. +**Desktop app:** [download.frameshift.syntheos.dev](https://download.frameshift.syntheos.dev/) -- a proprietary companion app for browsing the marketplace and managing personas with one click. This repo is the engine it drives; the app ships separately with Ed25519-signed self-updates. + Personas are not instruction lists. They are complete behavioral frames -- identity, rules, skills, operating posture -- that survive long sessions, surprising inputs, and the slow drift that turns careful agents into sloppy ones around turn 200. Same model, different frame. ## Quickstart @@ -87,7 +89,7 @@ filesystem_scope = "project-only" memory_required = "none" ``` -`description` and `tags` feed registry search; `capability_manifest` declares the tools, network egress, filesystem scope, and memory requirement the persona expects (see Capabilities and Memory below). `extends` and `mixin` drive composition. `parent_hash` and `conformance_baseline` track version lineage. +`description` and `tags` feed registry search; `capability_manifest` declares the tools, network egress, filesystem scope, and memory requirement the persona expects (see Capabilities and Memory below). `extends` and `mixin` drive composition. `parent_hash` tracks version lineage; `conformance_baseline` feeds the install-time regression gate (see Conformance below). ### Content addressing and signatures @@ -97,11 +99,15 @@ Signing is Ed25519 over that 32-byte hash; the signature travels alongside the p ### Trust: handle-bound keys, no central authority -There is no central signing authority. An author **claims a handle** (e.g. `ghost-frame`) with a signed request, and the registry binds that handle to the key that signed it, first-claim-wins. Key rotation must be signed by the current key -- the old key authorizes its own replacement. At publish, the server checks that the live signer owns the handle and that the pack signature verifies against that registered key. On install from the registry, the client verifies the pack signature against the key in the **registry's record** for that version, not the key embedded in the manifest, so a tampered manifest cannot smuggle in a different key. Installing directly from a local path verifies a signature if one is present, and installs unsigned local packs as-is. +There is no central signing authority. An author **claims a handle** (e.g. `ghost-frame`) with a signed request, and the registry binds that handle to the key that signed it, first-claim-wins. Key rotation must be signed by the current key -- the old key authorizes its own replacement. At publish, the server checks that the live signer owns the handle and that the pack signature verifies against that registered key. On install from the registry, the client verifies the pack signature against the key in the **registry's record** for that version, not the key embedded in the manifest, so a tampered manifest cannot smuggle in a different key. Installing directly from a local path verifies a signature if one is present, and installs unsigned local packs as-is. Registered authors are publicly listable via `GET /v1/authors`. ### Downloads -`frameshift install` and `publish --server` fetch pack bytes from the direct, unauthenticated `GET /v1/packs/{name}/versions/{version}/pack` route -- this is the supported download path today. The server also implements a signed-download flow: `POST .../download-url` mints a short-lived, HMAC-signed `/dl/{hash}` URL (gated on the `DOWNLOAD_SECRET` env var, disabled when it is unset). That flow is fully built and tested server-side but has no caller yet in the CLI or client, so treat it as experimental / not-yet-default until something mints and follows those URLs. +`frameshift install` fetches pack bytes from the direct, unauthenticated `GET /v1/packs/{name}/versions/{version}/pack` route -- this is the supported download path today. The server also implements a signed-download flow: `POST .../download-url` mints a short-lived, HMAC-signed `/dl/{hash}` URL (gated on the `DOWNLOAD_SECRET` env var, disabled when it is unset). That flow is fully built and tested server-side but has no caller yet in the CLI or client, so treat it as experimental / not-yet-default until something mints and follows those URLs. + +### Admin + +The registry server exposes one operator endpoint: `POST /v1/admin/packs/{name}/{version}/tombstone`, which marks a published version as removed from public availability. Like the handle-claim and rotation calls, it requires a signed request; the signer's key must also appear on `FRAMESHIFT_ADMIN_PUBKEYS`, a separate comma-separated allowlist of Ed25519 public keys. An empty allowlist disables the endpoint outright (`404`, indistinguishable from an unmapped route); a signed request from a key not on the list gets `403`. No CLI or client command calls this endpoint today -- it's reached directly. ### Install and the central store @@ -122,11 +128,23 @@ projects// personas// source/ The pack's own files rendered/{claude,codex,gemini,generic}/ Per-agent rendered output - growth.md Append-only local growth log + growth.md growth.jsonl Append-only growth log: markdown + JSONL ``` The lockfile records each installed persona as name, version, author handle, author pubkey, and canonical hash. Re-running `install`/`sync` rebuilds the per-project `personas/` tree from the content-addressed cache to match the lockfile. +### Conformance + +A pack's `pack.toml` can ship a `[conformance_baseline]` -- a score from 0.0 to 1.0, plus the hash of the conformance bundle it was measured against. When `install` would overwrite an already-installed version of the same persona in the current project, the client compares the incoming pack's baseline against the installed one, without re-running any tests: + +- **Pass** -- the incoming score meets or exceeds the installed score. +- **Regression** -- the incoming score is lower. Warn-only; the install proceeds. +- **MissingBaseline** -- either side ships no baseline. Non-fatal; baselines are optional. +- **InvalidScore** -- a score is non-finite or outside 0.0-1.0. Warn-only. +- **IntegrityFailure** -- the incoming pack's declared `bundle_hash` doesn't match the hash of its own shipped conformance bundle, or it ships none at all. This **hard-blocks the install** -- an unverifiable score can't be trusted regardless of what it claims. Override with `FRAMESHIFT_ALLOW_CONFORMANCE_INTEGRITY_FAILURE=1`. + +A fresh install of a persona with no prior version in the project skips the comparison entirely. `frameshift verify` (see CLI, below) is what produces a baseline in the first place: it runs a persona's conformance bundle through a runner and scores the results. + ### Rendering: one source, per-agent outputs A persona's typed source is four TOML files -- `persona.toml` (identity, voice, anchors), `rules.toml`, `skills.toml`, and `patterns.toml`. Rules carry a **layer**: L1 (non-negotiable invariants), L2 (contextual defaults, overridable with explicit justification), L3 (preferences). @@ -160,7 +178,7 @@ frameshift grow summary --persona rust --scope project The same selection engine backs every surface: - **CLI** -- `frameshift ` (see below). -- **Stdio MCP server** -- a JSON-RPC server exposing tools (install, activate, list, select, use, automate, prefs, grow, capabilities) and prompts (`active_persona`, `select_persona`, `automate_status`) as slash commands in any MCP-capable agent. +- **Stdio MCP server** -- a JSON-RPC server exposing tools (install, activate, list, select, use, automate, prefs, grow, capabilities, search) and prompts (`active_persona`, `select_persona`, `automate_status`) as slash commands in any MCP-capable agent. - **Watch daemon** -- an optional background service over a peer-authenticated Unix socket, offering install/activate/sync/gc operations to editor integrations. - **Registry / marketplace HTTP server** -- publish, search, download, and author/handle registration. @@ -182,7 +200,7 @@ frameshift use --from Install + activate + frameshift list List installed personas and mark the active one frameshift sync Reconcile the central store with the lockfile frameshift gc Remove unreferenced cache entries -frameshift migrate Move legacy files into the central store +frameshift migrate Move legacy files into the central store (also migrates growth logs to JSONL) ``` Selection and automate mode: @@ -261,7 +279,7 @@ cargo run -p frameshift-cli -- select --task "optimize a hot loop" --format json ### Server -All variables are read with no prefix (e.g. `BIND_ADDR`, not `FRAMESHIFT_BIND_ADDR`); see `crates/frameshift-server/src/config.rs` for the authoritative parser. +All variables are read with no prefix (e.g. `BIND_ADDR`, not `FRAMESHIFT_BIND_ADDR`) -- except `FRAMESHIFT_ADMIN_PUBKEYS`, deliberately prefixed so it can't be confused with an unrelated `ADMIN_PUBKEYS` some other tool in the deployment might own; see `crates/frameshift-server/src/config.rs` for the authoritative parser. | Variable | Default | Purpose | |---|---|---| @@ -287,6 +305,7 @@ All variables are read with no prefix (e.g. `BIND_ADDR`, not `FRAMESHIFT_BIND_AD | `R2_SECRET_ACCESS_KEY` | `""` | Secret access key | | `TRUST_FORWARDED_FOR` | `false` | Trust `X-Forwarded-For` for rate-limit key extraction; set `true` only behind a trusted proxy | | `SIGNED_REQUEST_MAX_SKEW_SECS` | `300` | Max clock skew (seconds) allowed between a signed write request's timestamp and server time | +| `FRAMESHIFT_ADMIN_PUBKEYS` | `""` | Comma-separated base64url-no-pad Ed25519 public keys allowed to call `/v1/admin/*` endpoints; empty disables all admin endpoints (404) | | `MEMORY_BACKEND` | `none` | `none`, `http`, or `sqlite` | | `MEMORY_HTTP_ENDPOINT` | `""` | Base URL for the HTTP memory endpoint; used when `MEMORY_BACKEND=http` | | `MEMORY_HTTP_AUTH` | `none` | `none` or `bearer:`; used when `MEMORY_BACKEND=http` | diff --git a/crates/frameshift-pack/src/manifest.rs b/crates/frameshift-pack/src/manifest.rs index 690f9c9..796ee9c 100644 --- a/crates/frameshift-pack/src/manifest.rs +++ b/crates/frameshift-pack/src/manifest.rs @@ -50,8 +50,11 @@ pub struct PackManifest { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub mixin: Vec, /// Conformance baseline: minimum score the pack version asserts on its own test bundle. - /// The runtime conformance runner (M4) gates upgrades on this; if a newer version - /// scores below baseline on the OLD bundle, the upgrade is blocked. + /// The client's install-time cross-version gate compares baselines between the + /// installed and incoming versions: a score drop or missing baseline is warn-only, + /// while a baseline whose `bundle_hash` does not match the bundle the pack actually + /// ships blocks the install (operator-overridable). See + /// `frameshift_conformance::RegressionGate::evaluate_cross_version`. #[serde(default, skip_serializing_if = "Option::is_none")] pub conformance_baseline: Option, /// One-line human-readable summary of what the persona is for. Consumed by the diff --git a/personas/cryptographic/pack.toml b/personas/cryptographic/pack.toml index 640c3c0..97b6321 100644 --- a/personas/cryptographic/pack.toml +++ b/personas/cryptographic/pack.toml @@ -16,7 +16,7 @@ memory_required_ops = [] # Conformance baseline: a signed, tamper-evident claim about the persona's # behavior. `bundle_hash` is the sha256 of the canonical TOML for this persona's -# private conformance bundle (personas-private/cryptographic/conformance/bundle.toml, +# private conformance bundle (not shipped; # 6 regex cases covering audited-over-custom, constant-time comparison, # RustCrypto-not-OpenSSL, OsRng key generation, spec citation, secret wrapping). # The public catalog is manifest-only, so the bundle itself is not shipped here; diff --git a/personas/rust/pack.toml b/personas/rust/pack.toml index 56e9c31..44f7e82 100644 --- a/personas/rust/pack.toml +++ b/personas/rust/pack.toml @@ -16,7 +16,7 @@ memory_required_ops = [] # Signed, tamper-evident behavioral claim. bundle_hash is the sha256 of the # canonical TOML for this persona's private conformance bundle -# (personas-private/rust/conformance/bundle.toml, 5 regex cases: +# (not shipped; 5 regex cases: # no-unwrap-in-library, thiserror-not-anyhow, Axum, tracing-not-log, # api-surface-first). The bundle is not shipped in the manifest-only public # catalog; this claim travels with the pack and the regression gate rejects a diff --git a/personas/security/pack.toml b/personas/security/pack.toml index 68bf392..67fa8d4 100644 --- a/personas/security/pack.toml +++ b/personas/security/pack.toml @@ -16,9 +16,9 @@ memory_required_ops = [] # Signed, tamper-evident behavioral claim. bundle_hash is the sha256 of the # canonical TOML for this persona's private conformance bundle -# (personas-private/security/conformance/bundle.toml, 5 regex cases: +# (not shipped; 5 regex cases: # noise-level classification, reject-undetectable, authorization-scope gate, -# cred-not-hardcode, quietest-sufficient). The bundle is not shipped in the +# secrets-manager-not-hardcode, quietest-sufficient). The bundle is not shipped in the # manifest-only public catalog; this claim travels with the pack and the # regression gate rejects a version bump whose bundle no longer hashes to this. # score is the measured floor: 1.0 across 2 runs (Gemini 3.1 Pro, 2026-07-07), all cases pass. diff --git a/personas/systems/pack.toml b/personas/systems/pack.toml index 2c88c6e..3bc39fe 100644 --- a/personas/systems/pack.toml +++ b/personas/systems/pack.toml @@ -16,9 +16,9 @@ memory_required_ops = [] # Signed, tamper-evident behavioral claim. bundle_hash is the sha256 of the # canonical TOML for this persona's private conformance bundle -# (personas-private/systems/conformance/bundle.toml, 5 regex cases: -# state-check-before-change, never-reboot-OVH, ssh-as-zan-via-alias, -# classify-blast-radius, heredoc-truncates-use-scp). The bundle is not shipped +# (not shipped; 5 regex cases: +# state-check-before-change, no-reboot-on-encrypted-hosts, ssh-via-config-alias, +# classify-blast-radius, file-transfer-integrity). The bundle is not shipped # in the manifest-only public catalog; this claim travels with the pack and the # regression gate rejects a version bump whose bundle no longer hashes to this. # score is the measured floor: 1.0 across 2 runs (Gemini 3.1 Pro, 2026-07-07), all cases pass.