Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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
39 changes: 10 additions & 29 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,35 +198,16 @@ Verified live:

**When a third data class lands** (e.g. payments-audit per arch.md §15.6): mint two more endpoints (`/v1/cap/payaudit-store` + `/v1/cap/payaudit-fetch`), add `DataClass::PaymentsAudit` variant, plumb to the new worker. The pattern is closed-extension: existing data classes don't need to know about the new one.

## Agent-side wire demo — REAL memory only (`harness/phase1-wire-demo.sh`)

The agent-side wire demo (`agentkeys wire hermes` inside the aiosandbox) MUST exercise the **real memory worker only**. Run it `--real`: the MCP server uses `--backend http`, and every `agentkeys.memory.get/put` goes broker cap-mint → per-actor STS relay (`X-Aws-*`) → `memory.litentry.org` → S3 (`bots/<actor>/memory/`). **Never use `--light` / `--backend in-memory` for any demo memory assertion** — that backend auto-seeds a fake Chengdu fixture (actor `0xa0c7…`) and is a dev-loop convenience only, NOT a real-memory proof. When demoing or QA-ing the agent's memory, assert against the real worker (`--real`), or directly: `agentkeys hook memory-inject --namespaces travel </dev/null` (returns the real S3 content) / the live S3 object `bots/<actor>/memory/memory.enc`.

**Three distinct memory systems — never conflate them:**
1. **Real AgentKeys memory** (the only one the demo proves): MCP `http` backend → worker → S3. Source of truth.
2. **In-memory fixture** (light mode): fake, dev-only. Forbidden in demo assertions.
3. **Hermes native session memory** (`recall` / `session_search`): the runtime's own store — **NOT** AgentKeys memory. Wiping Hermes "session memory" does not touch the real worker, and Hermes' native `recall` will never return AgentKeys content.

**No conflict with "passive injection":** passive injection (the `pre_llm_call` hook prepending memory each turn) is the *delivery mechanism* (when/how memory reaches Hermes), orthogonal to the *source*. In `--real` mode the passively-injected block IS the real worker memory — they are the same bytes, just delivered automatically. The rule is only about the SOURCE: real worker, never the in-memory fixture, never Hermes-native.

## Development Workflow (Anthropic Harness Pattern)

On every session start:
1. `jj log --limit 10 && cat harness/progress.json && bash harness/init.sh $(jq -r .current_stage harness/progress.json)`
2. Read the milestone scope for the current milestone in `docs/plan/milestones-roadmap.md` (the v1/v2 stage framing is archived at `docs/archived/development-stages-v2-2026-04.md`)
3. Pick the HIGHEST-PRIORITY incomplete deliverable from `harness/features.json`
4. Implement ONE deliverable
5. Run tests: `cargo test -p <crate>` for the affected crate
6. Describe: `jj describe -m "agentkeys: stage N -- <deliverable name>"`
7. Update `harness/features.json` (set `implemented: true`) and `harness/progress.json`
8. New change: `jj new -m "harness: update progress"`

## Stage Completion Protocol
1. Run `bash harness/stage-N-done.sh` -- must exit 0
2. `jj bookmark create stage-N-done` (bookmark marks the completion point)
3. Update `harness/progress.json`: set stage status to "complete"
4. `jj describe -m "harness: stage N complete"`
5. `jj new` (start fresh change for next stage)
## Harness rules → [`harness/CLAUDE.md`](harness/CLAUDE.md)

Harness-specific rules now live in [`harness/CLAUDE.md`](harness/CLAUDE.md) (loaded
when you work on `harness/`, so they don't bloat the global context) — extracted from
here: the **agent-side wire demo REAL-memory-only** rule, the **Development Workflow
(Anthropic harness pattern)**, and the **Stage Completion Protocol**, alongside the
harness authoring contract + the orchestrator inventory. How to RUN the demos is the
operator runbook [`docs/operator-runbook-harness.md`](docs/operator-runbook-harness.md).
**Every harness-script change must update that runbook + `harness/CLAUDE.md` in the
same change** (the keep-the-docs-in-sync rule).

## Heima EVM compatibility level — keep `evm_version = "london"` in foundry.toml (but NOT because Heima is "London")

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 17 additions & 3 deletions apps/parent-control/app/_components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,24 @@ export function App() {
const r = await client.plantMemory(PREPARED_MEMORY);
if (r.ok) {
const listed = await client.listMasterMemory();
if (listed.ok) setMemories(listed.data.map(toPreserved));
showToast(`Prepared memory planted · ${r.data.planted} new, ${r.data.skipped} deduped.`);
if (listed.ok) {
setMemories(listed.data.map(toPreserved));
// Show BOTH counts: the plant wrote to S3 + the daemon cache; the list reads
// the cache. If "N new" but "0 in view", the cache wasn't repopulated (e.g.
// daemon restarted) — the data is still safe in S3.
showToast(`Planted · ${r.data.planted} new, ${r.data.skipped} deduped · ${listed.data.length} in the memory view.`);
} else {
showToast(`Planted ${r.data.planted} new, but the memory list didn't load — ${listed.status.detail ?? 'reload the page'}.`);
}
} else {
showToast('Connect a daemon to plant prepared memory.');
// The plant button only renders when the daemon is connected, so a failure
// here is almost never "no daemon" — surface the daemon's ACTUAL reason
// (e.g. 409 "no master session — complete onboarding first" / "master device
// not registered on chain yet", or a 502 worker error) instead of masking it.
const detail = r.status.detail ?? '';
const m = detail.match(/\{"error":"([^"]+)"\}/);
const reason = m ? m[1] : detail || 'connect a daemon, then complete onboarding (login + K11 enroll) first';
showToast(`Plant failed — ${reason}`);
}
};

Expand Down
28 changes: 28 additions & 0 deletions crates/agentkeys-broker-server/src/handlers/cap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ impl CapOp {
pub enum DataClass {
Credentials,
Memory,
/// Policy / memory-types taxonomy (#178 §7). Master-only; its own bucket +
/// role per §17.2. `/v1/cap/config-*` mints this; cred + memory workers
/// reject a Config cap via `verify::check_data_class`.
Config,
}

/// Cap payload — the signed-over portion of a cap-token. The worker
Expand Down Expand Up @@ -212,6 +216,30 @@ pub async fn cap_memory_get(
.map(Json)
}

// Config cap-mint endpoints (#178 P1 / config-data-class-memory-list plan): the
// policy / memory-types taxonomy data class. The minted cap carries
// data_class=Config; the cred + memory workers reject it via
// verify::check_data_class. Master-only (the governed agent has no config cap).
pub async fn cap_config_store(
State(state): State<SharedState>,
headers: HeaderMap,
Json(req): Json<CapRequest>,
) -> Result<Json<CapToken>, CapError> {
mint_cap(state, headers, req, CapOp::Store, DataClass::Config)
.await
.map(Json)
}

pub async fn cap_config_fetch(
State(state): State<SharedState>,
headers: HeaderMap,
Json(req): Json<CapRequest>,
) -> Result<Json<CapToken>, CapError> {
mint_cap(state, headers, req, CapOp::Fetch, DataClass::Config)
.await
.map(Json)
}

// ─── cap construction ──────────────────────────────────────────────────

async fn mint_cap(
Expand Down
11 changes: 11 additions & 0 deletions crates/agentkeys-broker-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod jwt;
pub mod metrics;
pub mod oidc;
pub mod plugins;
pub mod sponsor;
pub mod state;
pub mod storage;
pub mod sts;
Expand Down Expand Up @@ -53,6 +54,16 @@ pub fn create_router(state: SharedState) -> Router {
// memory worker accepts and the cred worker rejects.
.route("/v1/cap/memory-put", post(handlers::cap::cap_memory_put))
.route("/v1/cap/memory-get", post(handlers::cap::cap_memory_get))
// Per-data-class CONFIG caps (#178 P1 / config-data-class-memory-list).
// data_class=Config — the policy / memory-types taxonomy; master-only.
.route(
"/v1/cap/config-store",
post(handlers::cap::cap_config_store),
)
.route(
"/v1/cap/config-fetch",
post(handlers::cap::cap_config_fetch),
)
// Stage 7 §3.5 — pluggable auth surface.
.route(
"/v1/auth/wallet/start",
Expand Down
Loading
Loading