From 9ec6656f9e567183b2b62628419fe7186bb79b70 Mon Sep 17 00:00:00 2001 From: Maxime Date: Tue, 5 May 2026 17:52:31 -0700 Subject: [PATCH 1/3] docs(9-ai-sessions): document profile memory bank Add a new page explaining the per-profile memory bank that user AI Sessions can use as the user-scoped equivalent of the org-scoped ai_memory hive. Covers mounting semantics, naming rules, limits, REST endpoints, lifecycle (cascade on profile delete, decoupled from session suspend), and the trust model for runner-driven syncs. Cross-links from the existing AI Memory and User Sessions pages, and adds the new endpoints to the API reference. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/9-ai-sessions/api-reference.md | 67 ++++++++++++++ docs/9-ai-sessions/memory.md | 2 + docs/9-ai-sessions/profile-memory.md | 132 +++++++++++++++++++++++++++ docs/9-ai-sessions/user-sessions.md | 6 ++ mkdocs.yml | 1 + 5 files changed, 208 insertions(+) create mode 100644 docs/9-ai-sessions/profile-memory.md diff --git a/docs/9-ai-sessions/api-reference.md b/docs/9-ai-sessions/api-reference.md index f6b9e0427..1f4b3e010 100644 --- a/docs/9-ai-sessions/api-reference.md +++ b/docs/9-ai-sessions/api-reference.md @@ -332,6 +332,73 @@ POST /v1/sessions/{sessionId}/capture-profile --- +### Profile Memory Bank + +Each profile can carry a small set of markdown files that get mounted into the runner's `/workspace/.memory/` whenever a session is launched from the profile. See [Profile Memory Bank](profile-memory.md) for an overview and limits. + +#### List memory entries + +```text +GET /v1/profiles/{profileId}/memories +``` + +Returns metadata only — bodies are excluded so the response stays small for profiles with many entries. + +```json +{ + "memories": [ + { + "path": "preferences.md", + "size": 412, + "content_hash": "9b2f…", + "created_at": "2026-05-01T10:14:32Z", + "updated_at": "2026-05-04T08:02:11Z" + } + ] +} +``` + +#### Read a memory entry + +```text +GET /v1/profiles/{profileId}/memories/content?path= +``` + +The `path` is URL-encoded; it must satisfy the [naming rules](profile-memory.md#layout-and-limits). + +#### Create or update a memory entry + +```text +PUT /v1/profiles/{profileId}/memories/content?path= +``` + +**Request Body:** + +```json +{ + "content": "## Acme Corp\n- single-tenant deployment\n- compliance: SOC2" +} +``` + +Returns `201` on insert, `200` on update or no-op (when the supplied content matches the existing body byte-for-byte). + +| Failure | Response | +|---|---| +| Invalid path | `400 invalid_memory_path` | +| Body exceeds 64 KiB | `413 memory_content_too_large` | +| Profile already holds 100 entries | `409 max_memories_reached` | +| Profile aggregate would exceed 5 MiB | `409 memory_quota_exceeded` | + +#### Delete a memory entry + +```text +DELETE /v1/profiles/{profileId}/memories/content?path= +``` + +Deleting an entry is also done implicitly when its profile is deleted — every entry in the bank is cascaded. + +--- + ### Claude Authentication #### Start OAuth Flow diff --git a/docs/9-ai-sessions/memory.md b/docs/9-ai-sessions/memory.md index f08d0a35f..5c515c9da 100644 --- a/docs/9-ai-sessions/memory.md +++ b/docs/9-ai-sessions/memory.md @@ -2,6 +2,8 @@ AI Memory is a per-agent key/value store for content that should outlive a single AI Session. Where [Skills](skills.md) capture *how* an agent works, memory captures *what it has learned* — facts about the environment, prior decisions, ongoing investigations, anything the agent should be able to recall the next time it runs. +> Looking for the per-user equivalent for [User Sessions](user-sessions.md)? See [Profile Memory Bank](profile-memory.md) — a smaller, profile-scoped store that mounts directly into the session workspace. + Each agent owns one record, keyed by an agent identifier you pick. Inside that record, individual memories are addressed by filesystem-style names (`notes/today`, `cases/INC-123/timeline`, `runtime/last-seen-host`, …). Writes are partial: setting a single named memory does not require reading the rest of the record back, and concurrent writes against different memory names on the same agent do not need to coordinate. ## How writes merge diff --git a/docs/9-ai-sessions/profile-memory.md b/docs/9-ai-sessions/profile-memory.md new file mode 100644 index 000000000..2ba7e11e9 --- /dev/null +++ b/docs/9-ai-sessions/profile-memory.md @@ -0,0 +1,132 @@ +title: Profile Memory Bank · LimaCharlie AI Sessions + +# Profile Memory Bank + +User-scoped AI Sessions are not associated with any specific organisation, so they cannot use the org-scoped [`ai_memory` Hive](memory.md). To give Claude durable context across user sessions, each [Session Profile](user-sessions.md#session-profiles) carries a **memory bank**: a small set of markdown files mounted into the runner's workspace at `/workspace/.memory/` whenever a session is launched from that profile. + +The memory bank is the user-side analog of `ai_memory`, scoped per profile rather than per org. Use it for things like: + +- preferences ("always run terraform plans against `staging` first"), +- recurring projects (a running plan, status notes, decision logs), +- learned facts about your environment (host inventories, compliance constraints, runbook pointers). + +Each profile gets its own bank, so you can keep an "investigations" profile, a "reporting" profile, and a "research" profile with independent memories. + +## How memories are mounted + +When a session starts from a profile that has memories: + +1. The session manager loads the bank, decrypts it, and ships it inside the encrypted session config. +2. The runner writes each memory to `/workspace/.memory/` before Claude starts. +3. While the session runs, the runner watches `/workspace/.memory/`. Whenever Claude writes, edits, or deletes a file there, the change is debounced and synced back to the bank — so anything the model "learns" persists for the next session that uses this profile. + +The directory belongs entirely to the bank: do not store transient files in `.memory/`, they will be wiped on the next session start. + +## Layout and limits + +Memory paths use the same shape as the `ai_memory` Hive: + +- relative path, forward slashes only, +- characters limited to `A–Z`, `a–z`, `0–9`, `.`, `_`, `-`, `/`, +- no leading-dot segments (so `.swp`/`~`/`.#…` editor temp files are easy to filter out), +- no `..` traversal, +- maximum depth: 5 segments, +- maximum path length: 256 characters. + +Per-profile caps: + +| Limit | Value | +|---|---| +| Entries per profile | 100 | +| Bytes per entry | 64 KiB | +| Aggregate bytes per profile | 5 MiB | + +Bank entries are stored encrypted at rest using your user-specific key — the same scheme that protects environment variables and MCP server credentials in your profiles. + +## Lifecycle + +- The memory bank is **decoupled from session lifecycle**. Deleting a session has no effect on its profile's bank. +- The bank is **not part of session suspend archives**. When a session resumes, it remounts the *current* bank — so any edits you make through the API while a session was dormant take effect on resume. +- **Deleting a profile cascades** the deletion to every entry in its bank. + +## Managing memories + +### REST API + +Authenticate with your LimaCharlie JWT (`Authorization: Bearer $LC_JWT`). + +#### List entries + +Returns metadata only — fetch bodies separately. + +```bash +curl https://ai-sessions.limacharlie.io/v1/profiles/$PROFILE_ID/memories \ + -H "Authorization: Bearer $LC_JWT" +``` + +```json +{ + "memories": [ + { + "path": "preferences.md", + "size": 412, + "content_hash": "9b2f…", + "created_at": "2026-05-01T10:14:32Z", + "updated_at": "2026-05-04T08:02:11Z" + } + ] +} +``` + +#### Read an entry + +```bash +curl --get https://ai-sessions.limacharlie.io/v1/profiles/$PROFILE_ID/memories/content \ + --data-urlencode "path=projects/acme.md" \ + -H "Authorization: Bearer $LC_JWT" +``` + +#### Create or update an entry + +The body is JSON; the markdown content goes inside the `content` field. + +```bash +curl -X PUT https://ai-sessions.limacharlie.io/v1/profiles/$PROFILE_ID/memories/content \ + -H "Authorization: Bearer $LC_JWT" \ + -H "Content-Type: application/json" \ + --data-urlencode "path=projects/acme.md" \ + -d '{"content": "## Acme Corp\n- single-tenant deployment\n- compliance: SOC2"}' +``` + +The response includes a `created` flag (true on first insert) and a `changed` flag (false when the supplied content matched the existing entry — in that case the call is a no-op). + +#### Delete an entry + +```bash +curl -X DELETE --get https://ai-sessions.limacharlie.io/v1/profiles/$PROFILE_ID/memories/content \ + --data-urlencode "path=projects/old-deal.md" \ + -H "Authorization: Bearer $LC_JWT" +``` + +### From inside a session + +Claude can simply write to `/workspace/.memory/.md` (or read from it) the same way it would any other file. Edits are synced back to the bank automatically — no special API call required from the model. + +## Profile memory vs. AI Memory hive + +Both stores share a "filesystem-style entry within an owner record" model, but they sit at different scopes: + +| | Profile memory bank | [AI Memory Hive](memory.md) | +|---|---|---| +| Scope | One bank per profile (per-user) | One record per agent (per-org) | +| Auth | LC user JWT | LC org/user creds with `ai_memory.{get,set,del}` | +| Mounted into runner | Yes, at `/workspace/.memory/` | No — accessed via `limacharlie ai-memory` CLI | +| Best for | Per-user durable context for interactive sessions | Cross-session agent state for D&R-driven sessions | + +If your session has both an LC org binding *and* a profile memory bank, you can use them together: the bank for personal preferences and project notes, the hive for org-scoped operational state. + +## Security + +- The runner is treated as untrusted code. It cannot make privileged API calls to the session manager. +- Memory writes from inside the runner travel over the same authenticated WebSocket the runner uses to talk to the proxy. The proxy validates path/size limits at the trust boundary and forwards to the session manager, which authoritatively re-derives the (user, profile) target from the persisted session record. A compromised runner cannot redirect writes into another user's profile. +- Bodies are encrypted at rest with your user-specific key. diff --git a/docs/9-ai-sessions/user-sessions.md b/docs/9-ai-sessions/user-sessions.md index 189738417..1a72ad3e6 100644 --- a/docs/9-ai-sessions/user-sessions.md +++ b/docs/9-ai-sessions/user-sessions.md @@ -167,6 +167,12 @@ curl -X POST https://ai-sessions.limacharlie.io/v1/sessions/{sessionId}/capture- -d '{"name": "My Session Config"}' ``` +### Profile Memory Bank + +Each profile can carry a small set of markdown files that are mounted into the session's workspace at `/workspace/.memory/` whenever a session is launched from that profile. Edits Claude makes there during the session are synced back automatically. This is the user-side analog of the org-scoped [AI Memory hive](memory.md). + +See [Profile Memory Bank](profile-memory.md) for the full reference, including REST endpoints, naming rules, and limits. + ## Session Lifecycle ### Session States diff --git a/mkdocs.yml b/mkdocs.yml index 8ba880e2d..364b2beed 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -529,6 +529,7 @@ nav: - Runner Environment: 9-ai-sessions/runner-environment.md - AI Skills: 9-ai-sessions/skills.md - AI Memory: 9-ai-sessions/memory.md + - Profile Memory Bank: 9-ai-sessions/profile-memory.md - Command Line Interface: 9-ai-sessions/cli.md - Alternative Providers: 9-ai-sessions/alternative-providers.md - API Reference: 9-ai-sessions/api-reference.md From 1c96f12ee0918b8c01e39315953e015d5f5603b8 Mon Sep 17 00:00:00 2001 From: Maxime Date: Tue, 5 May 2026 18:50:36 -0700 Subject: [PATCH 2/3] docs(9-ai-sessions): user-level Bedrock/Vertex + system_prompt_suffix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document the two remaining features that ship in refractionPOINT/ai-sessions#71 alongside the profile memory bank: 1. User-level Bedrock and Vertex providers — adds Options C and D under "Step 2: Store Claude Credentials" on user-sessions.md, and the two new `POST /v1/auth/claude/bedrock` and `POST /v1/auth/claude/vertex` endpoints on api-reference.md. Updates `GET /v1/auth/claude/status` to show the extended `credential_type` enum (api_key | oauth | bedrock | vertex). Cross-links from alternative-providers.md to the user-side path so readers landing on the org-scoped page can find the user-scoped one. 2. Per-profile system_prompt_suffix — new row in the user-sessions Profile Options table, plus a worked example and the new `system_prompt_suffix_too_long` error response on api-reference.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/9-ai-sessions/alternative-providers.md | 7 ++ docs/9-ai-sessions/api-reference.md | 79 +++++++++++++++++++++ docs/9-ai-sessions/user-sessions.md | 55 +++++++++++++- 3 files changed, 140 insertions(+), 1 deletion(-) diff --git a/docs/9-ai-sessions/alternative-providers.md b/docs/9-ai-sessions/alternative-providers.md index 3b750a1d1..02944b7da 100644 --- a/docs/9-ai-sessions/alternative-providers.md +++ b/docs/9-ai-sessions/alternative-providers.md @@ -8,6 +8,13 @@ This is useful when: - You need to keep AI traffic within specific regions for compliance - You want to consolidate billing through your existing cloud account +## Where to configure + +Both providers can be configured at two scopes: + +- **Org-scoped (D&R rules and integrations)** — the `bedrock:` / `vertex:` blocks on an `ai_agent` Hive record, or directly on a `SessionRequest`. This page is primarily about that path. +- **User-scoped (per-user BYOK sessions)** — the same provider blocks, posted to `POST /v1/auth/claude/bedrock` and `POST /v1/auth/claude/vertex`. See [User AI Sessions — Step 2: Store Claude Credentials](user-sessions.md#step-2-store-claude-credentials) for the user-side flow. The IAM, region, and model ID guidance in the rest of this page applies to that path as well — only the credential entry mechanism differs. + ## Two configuration formats There are two ways to point a session at a non-Anthropic provider: diff --git a/docs/9-ai-sessions/api-reference.md b/docs/9-ai-sessions/api-reference.md index 1f4b3e010..d0cc2a383 100644 --- a/docs/9-ai-sessions/api-reference.md +++ b/docs/9-ai-sessions/api-reference.md @@ -259,6 +259,7 @@ POST /v1/profiles "model": "claude-sonnet-4-20250514", "max_turns": 100, "max_budget_usd": 10.0, + "system_prompt_suffix": "You are assisting the SOC team. Always cite sensor IDs in findings.", "mcp_servers": { "limacharlie": { "type": "http", @@ -272,6 +273,12 @@ POST /v1/profiles } ``` +The `system_prompt_suffix` is free-form text appended to the agent's system prompt for sessions launched from this profile (max 16 KB). Snapshotted onto the session at creation time, so editing the profile later does not retroactively affect already-running sessions. + +**Profile-specific Error Responses:** + +- `400 system_prompt_suffix_too_long`: the supplied suffix exceeds 16384 bytes (also returned by `PUT /v1/profiles/{profileId}`) + ##### Response: 201 Created ```json @@ -490,6 +497,76 @@ POST /v1/auth/claude/apikey } ``` +#### Store Bedrock Credentials + +```text +POST /v1/auth/claude/bedrock +``` + +Routes Claude through AWS Bedrock for this user. The body matches the `BedrockConfig` struct used by the org-side `SessionRequest`. Supply either `(access_key_id + secret_access_key)` (with optional `session_token` for STS / SSO temporary credentials) or `bearer_token`. `region` is always required. + +**Request Body:** + +```json +{ + "region": "us-east-1", + "access_key_id": "AKIA...", + "secret_access_key": "...", + "session_token": "...", + "bearer_token": "..." +} +``` + +##### Response: 200 OK + +```json +{ + "success": true, + "message": "Bedrock config stored successfully" +} +``` + +**Error Responses:** + +- `400 invalid_bedrock_config`: missing `region`, missing both credential pair and bearer token, mismatched access-key pair, or `session_token` without the access-key pair +- `400 not_registered`: user has not called `POST /v1/register` yet + +> Storing Bedrock credentials replaces any previously stored API key, OAuth token, or Vertex config — only one provider is active per user. See [Alternative AI Providers](alternative-providers.md#amazon-bedrock) for IAM, region, and model ID details. + +#### Store Vertex Credentials + +```text +POST /v1/auth/claude/vertex +``` + +Routes Claude through Google Cloud Vertex AI for this user. `service_account_json` is the literal contents of the service-account JSON key — the entire downloaded file as a JSON string. + +**Request Body:** + +```json +{ + "project_id": "my-gcp-project", + "region": "us-east5", + "service_account_json": "{\"type\":\"service_account\",\"project_id\":\"...\",\"private_key\":\"...\"}" +} +``` + +##### Response: 200 OK + +```json +{ + "success": true, + "message": "Vertex config stored successfully" +} +``` + +**Error Responses:** + +- `400 invalid_vertex_config`: missing `project_id`, `region`, or `service_account_json` +- `400 not_registered`: user has not called `POST /v1/register` yet + +> Storing Vertex credentials replaces any previously stored API key, OAuth token, or Bedrock config. The service-account JSON is encrypted at rest and is never returned by the status endpoint. See [Alternative AI Providers](alternative-providers.md#google-cloud-vertex-ai) for GCP-side setup, region selection, and model ID format. + #### Get Credential Status ```text @@ -506,6 +583,8 @@ GET /v1/auth/claude/status } ``` +`credential_type` is one of `api_key`, `oauth`, `bedrock`, or `vertex` — whichever provider was most recently stored. Secrets themselves (API keys, OAuth tokens, AWS keys, service-account JSON) are never returned. + #### Delete Credentials ```text diff --git a/docs/9-ai-sessions/user-sessions.md b/docs/9-ai-sessions/user-sessions.md index 1a72ad3e6..2709b39f9 100644 --- a/docs/9-ai-sessions/user-sessions.md +++ b/docs/9-ai-sessions/user-sessions.md @@ -30,7 +30,9 @@ curl -X POST https://ai-sessions.limacharlie.io/v1/register \ ### Step 2: Store Claude Credentials -AI Sessions uses a Bring Your Own Key (BYOK) model. You provide your Anthropic credentials—either an API key or via Claude Max OAuth. +AI Sessions uses a Bring Your Own Key (BYOK) model. You provide credentials for one of four supported Claude providers — Anthropic API key, Claude Max OAuth, Amazon Bedrock, or Google Cloud Vertex AI. Only one provider is active per user at a time; storing a credential for a different provider replaces the previous one. + +The `credential_type` field returned by `GET /v1/auth/claude/status` is one of `api_key`, `oauth`, `bedrock`, or `vertex`. #### Option A: API Key @@ -73,6 +75,56 @@ curl -X POST https://ai-sessions.limacharlie.io/v1/auth/claude/code \ -d '{"session_id": "", "code": ""}' ``` +#### Option C: Amazon Bedrock + +Route Claude through your AWS account. The Bedrock model IDs differ from the standard Anthropic IDs (e.g. `us.anthropic.claude-sonnet-4-5-20250929-v1:0`); set the appropriate ID on your profile via the `model` field. + +You must supply **either** the access-key pair (`access_key_id` + `secret_access_key`, with optional `session_token` for STS / SSO) **or** a `bearer_token`. `region` is always required. + +```bash +# Access-key pair +curl -X POST https://ai-sessions.limacharlie.io/v1/auth/claude/bedrock \ + -H "Authorization: Bearer $LC_JWT" \ + -H "Content-Type: application/json" \ + -d '{ + "region": "us-east-1", + "access_key_id": "AKIA...", + "secret_access_key": "..." + }' + +# Bearer token +curl -X POST https://ai-sessions.limacharlie.io/v1/auth/claude/bedrock \ + -H "Authorization: Bearer $LC_JWT" \ + -H "Content-Type: application/json" \ + -d '{ + "region": "us-east-1", + "bearer_token": "..." + }' +``` + +The runner sets `CLAUDE_CODE_USE_BEDROCK=1` and the appropriate `AWS_*` environment variables on the Claude subprocess automatically. + +See [Alternative AI Providers — Amazon Bedrock](alternative-providers.md#amazon-bedrock) for the IAM permissions, region selection, and model ID format. Those instructions apply to the user-side flow too; only the credential entry path differs (this endpoint instead of an `ai_agent` Hive record). + +#### Option D: Google Cloud Vertex AI + +Route Claude through your GCP project. Authentication is a service-account JSON key with the appropriate Vertex AI permissions; the runner materializes it on disk and points `GOOGLE_APPLICATION_CREDENTIALS` at it for each session. Set the Vertex-style model ID on your profile (e.g. `claude-sonnet-4-5@20250929`). + +```bash +curl -X POST https://ai-sessions.limacharlie.io/v1/auth/claude/vertex \ + -H "Authorization: Bearer $LC_JWT" \ + -H "Content-Type: application/json" \ + -d '{ + "project_id": "my-gcp-project", + "region": "us-east5", + "service_account_json": "{\"type\":\"service_account\",\"project_id\":\"...\",\"private_key\":\"...\"}" + }' +``` + +`service_account_json` is the **literal contents** of the service-account JSON key file, embedded as a JSON string. The credential is encrypted at rest; it is never returned by `GET /v1/auth/claude/status`. + +See [Alternative AI Providers — Google Cloud Vertex AI](alternative-providers.md#google-cloud-vertex-ai) for GCP-side setup, region selection, and model ID format. + ### Step 3: Create a Session Create a new session to start working with Claude: @@ -148,6 +200,7 @@ curl -X POST https://ai-sessions.limacharlie.io/v1/profiles \ | `ttl_seconds` | integer | Maximum session lifetime in seconds | | `environment` | map | Environment variables passed to the session | | `mcp_servers` | map | MCP server configurations | +| `system_prompt_suffix` | string | Free-form text appended to the agent's system prompt for sessions launched from this profile. Use it to attach a persona, project context, or house rules without forking the runner. Maximum 16 KB. The suffix is snapshotted onto the session at creation time, so editing the profile later does not retroactively affect already-running sessions. | ### Setting a Default Profile From d754fc1a6bd9099f1018b92490a30cfb1d73784f Mon Sep 17 00:00:00 2001 From: Maxime Date: Tue, 5 May 2026 19:02:47 -0700 Subject: [PATCH 3/3] docs(9-ai-sessions): describe profile memory bank system-prompt injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document the new "## Profile Memory Bank" system-prompt section the runner injects for user-based sessions. Without this hint Claude has no reason to look in /workspace/.memory/ — the Agent SDK has no built-in auto-discovery for arbitrary workspace subdirs. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/9-ai-sessions/profile-memory.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/9-ai-sessions/profile-memory.md b/docs/9-ai-sessions/profile-memory.md index 2ba7e11e9..595a6c715 100644 --- a/docs/9-ai-sessions/profile-memory.md +++ b/docs/9-ai-sessions/profile-memory.md @@ -18,7 +18,10 @@ When a session starts from a profile that has memories: 1. The session manager loads the bank, decrypts it, and ships it inside the encrypted session config. 2. The runner writes each memory to `/workspace/.memory/` before Claude starts. -3. While the session runs, the runner watches `/workspace/.memory/`. Whenever Claude writes, edits, or deletes a file there, the change is debounced and synced back to the bank — so anything the model "learns" persists for the next session that uses this profile. +3. The runner injects a `## Profile Memory Bank` section into Claude's system prompt that lists the current entry paths and explains the persistence semantics — so the model knows the directory exists, what's in it, and that edits there persist across sessions. Without this section Claude has no reason to look in `.memory/`; the directory does not have any built-in convention in the Agent SDK. +4. While the session runs, the runner watches `/workspace/.memory/`. Whenever Claude writes, edits, or deletes a file there, the change is debounced and synced back to the bank — so anything the model "learns" persists for the next session that uses this profile. + +The system-prompt section is only injected for user-based sessions (D&R / org-scoped sessions never carry a profile and never see the bank). For empty banks the section instructs Claude to create the first entry rather than listing entries. The directory belongs entirely to the bank: do not store transient files in `.memory/`, they will be wiped on the next session start.