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 f6b9e0427..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 @@ -332,6 +339,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 @@ -423,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 @@ -439,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/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..595a6c715 --- /dev/null +++ b/docs/9-ai-sessions/profile-memory.md @@ -0,0 +1,135 @@ +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. 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. + +## 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..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 @@ -167,6 +220,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