From 57ae7ef4d8520d5afc185e11dce2bc0cf6df6d95 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:35:51 -0400 Subject: [PATCH] August Changelog Docs Sync (#1009) * chore(docs): Sync Docs with latest Changelog features * chore: nit on sentence style * chore: simplifying language. --------- Co-authored-by: ajspig --- CHANGELOG.md | 2 +- docs/changelog/introduction.mdx | 2 +- docs/docs.json | 1 + .../advanced/representation-scopes.mdx | 12 +- .../features/advanced/using-filters.mdx | 114 +++++++++ .../features/advanced/webhooks.mdx | 217 ++++++++++++++++++ docs/v3/documentation/features/chat.mdx | 21 ++ docs/v3/documentation/reference/platform.mdx | 1 + docs/v3/openapi.json | 34 ++- 9 files changed, 394 insertions(+), 10 deletions(-) create mode 100644 docs/v3/documentation/features/advanced/webhooks.mdx diff --git a/CHANGELOG.md b/CHANGELOG.md index a25954ac..e314f867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added -- Session allowlist on the Dialectic and representation via a constrained `filters` body on `POST /peers/{peer_id}/chat` and `/representation`, supporting only the `session_id` key (a session id, a bare list, or `{"in": [...]}`). Unsupported keys and shapes are rejected with 422 rather than silently ignored, it composes with `session_id` (which must be included in the allowlist when both are given), and it is capped at 1,000 sessions per request. Enforcement is uniform and fail-closed at every recall chokepoint: scoped conclusion recall is restricted to `level == "explicit"` (dream-derived conclusions carry a single `session_name` but are synthesized across all sessions, so that stamp can't be scoped on), `get_reasoning_chain` is unavailable under an allowlist, and an empty allowlist short-circuits to empty results everywhere. Workspace keys pass the allowlist as-given; peer-scoped JWTs must be an active member of every allowlisted session (403 otherwise) (#882) +- Session allowlist on the Dialectic and representation via a constrained `filters` body on `POST /peers/{peer_id}/chat` and `/representation`, supporting only the `session_id` key (a session id, a bare list, or `{"in": [...]}`). Unsupported keys and shapes are rejected with 422 rather than silently ignored, it composes with `session_id` (which must be included in the allowlist when both are given), and it is capped at 1,000 sessions per request. Enforcement is uniform and fail-closed at every recall chokepoint: scoped conclusion recall is restricted to `level == "explicit"` (dream-derived conclusions carry a single `session_name` but are synthesized across all sessions, so that stamp can't be scoped on), `get_reasoning_chain` is unavailable under an allowlist, and an empty allowlist short-circuits to empty results everywhere. Workspace keys pass the allowlist as-given; peer-scoped JWTs must be an active member of every allowlisted session (401 otherwise) (#882) - Bare-list membership sugar in the filter DSL: `{"session_id": ["s1", "s2"]}` is now shorthand for `{"session_id": {"in": [...]}}` on regular columns generically. JSONB metadata columns are excluded and keep containment semantics. Strictly additive, since a bare list on a regular column previously compiled to a type-mismatched equality that matched nothing (#881) - Optional structured outputs on the Dialectic: `response_format` (a JSON Schema with root type `object`) on peer chat makes `content` a JSON string conforming to that schema. Only a conservative subset of JSON Schema is supported, with DoS guards and non-recursive `$ref` support (#896) - Combined tool calling and structured output in the LLM transport layer, with per-backend request shaping: OpenAI routes tool-carrying structured requests through `create()` with an explicit `json_schema` response format (`parse()` 500s on non-strict function tools), Anthropic skips the `{` JSON prefill when tools are present so `tool_use` blocks stay reachable, and Gemini injects a schema instruction into the final turn instead of using native `response_schema` (rejected alongside function calling before Gemini 3). All backends skip structured-output parsing on tool-call turns, which carry no consumable content (#907) diff --git a/docs/changelog/introduction.mdx b/docs/changelog/introduction.mdx index 0a5ffda8..356c1eb5 100644 --- a/docs/changelog/introduction.mdx +++ b/docs/changelog/introduction.mdx @@ -30,7 +30,7 @@ Welcome to the Honcho changelog! This section documents all notable changes to t ### Added - - Session allowlist on the Dialectic and representation via a constrained `filters` body on `POST /peers/{peer_id}/chat` and `/representation`, supporting only the `session_id` key (a session id, a bare list, or `{"in": [...]}`). Unsupported keys and shapes are rejected with 422 rather than silently ignored, it composes with `session_id` (which must be included in the allowlist when both are given), and it is capped at 1,000 sessions per request. Enforcement is uniform and fail-closed at every recall chokepoint: scoped conclusion recall is restricted to `level == "explicit"` (dream-derived conclusions carry a single `session_name` but are synthesized across all sessions, so that stamp can't be scoped on), `get_reasoning_chain` is unavailable under an allowlist, and an empty allowlist short-circuits to empty results everywhere. Workspace keys pass the allowlist as-given; peer-scoped JWTs must be an active member of every allowlisted session (403 otherwise) (#882) + - Session allowlist on the Dialectic and representation via a constrained `filters` body on `POST /peers/{peer_id}/chat` and `/representation`, supporting only the `session_id` key (a session id, a bare list, or `{"in": [...]}`). Unsupported keys and shapes are rejected with 422 rather than silently ignored, it composes with `session_id` (which must be included in the allowlist when both are given), and it is capped at 1,000 sessions per request. Enforcement is uniform and fail-closed at every recall chokepoint: scoped conclusion recall is restricted to `level == "explicit"` (dream-derived conclusions carry a single `session_name` but are synthesized across all sessions, so that stamp can't be scoped on), `get_reasoning_chain` is unavailable under an allowlist, and an empty allowlist short-circuits to empty results everywhere. Workspace keys pass the allowlist as-given; peer-scoped JWTs must be an active member of every allowlisted session (401 otherwise) (#882) - Bare-list membership sugar in the filter DSL: `{"session_id": ["s1", "s2"]}` is now shorthand for `{"session_id": {"in": [...]}}` on regular columns generically. JSONB metadata columns are excluded and keep containment semantics. Strictly additive, since a bare list on a regular column previously compiled to a type-mismatched equality that matched nothing (#881) - Optional structured outputs on the Dialectic: `response_format` (a JSON Schema with root type `object`) on peer chat makes `content` a JSON string conforming to that schema. Only a conservative subset of JSON Schema is supported, with DoS guards and non-recursive `$ref` support (#896) - Combined tool calling and structured output in the LLM transport layer, with per-backend request shaping: OpenAI routes tool-carrying structured requests through `create()` with an explicit `json_schema` response format (`parse()` 500s on non-strict function tools), Anthropic skips the `{` JSON prefill when tools are present so `tool_use` blocks stay reachable, and Gemini injects a schema instruction into the final turn instead of using native `response_schema` (rejected alongside function calling before Gemini 3). All backends skip structured-output parsing on tool-call turns, which carry no consumable content (#907) diff --git a/docs/docs.json b/docs/docs.json index e3cf908d..36fef957 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -65,6 +65,7 @@ "v3/documentation/features/advanced/representation-scopes", "v3/documentation/features/advanced/dreaming", "v3/documentation/features/advanced/queue-status", + "v3/documentation/features/advanced/webhooks", "v3/documentation/features/advanced/search", "v3/documentation/features/advanced/using-filters", "v3/documentation/features/advanced/structured-outputs", diff --git a/docs/v3/documentation/features/advanced/representation-scopes.mdx b/docs/v3/documentation/features/advanced/representation-scopes.mdx index d7c478d7..30e2ae32 100644 --- a/docs/v3/documentation/features/advanced/representation-scopes.mdx +++ b/docs/v3/documentation/features/advanced/representation-scopes.mdx @@ -22,7 +22,7 @@ You can retrieve a subset of conclusions from a peer's representation using `rep alice_rep = session.representation("alice") # Or via chat -response = alice.chat("What are Alice's main interests?", session_id=session.id) +response = alice.chat("What are Alice's main interests?", session=session.id) ``` This is sufficient for most applications—Honcho reasons over every message written to the peer, storing conclusions that any part of your system can retrieve. @@ -160,13 +160,13 @@ The `target` parameter also works with the chat endpoint: # Query using conclusions from Honcho's representation (across all sessions) honcho_answer = alice.chat( "What did Bob say about breakfast?", - session_id=session.id + session=session.id ) # Query using conclusions from Alice's representation of Bob (from Alice's sessions only) alice_answer = alice.chat( "What did Bob say about breakfast?", - session_id=session.id, + session=session.id, target="bob" ) ``` @@ -175,13 +175,13 @@ alice_answer = alice.chat( // Query using conclusions from Honcho's representation (across all sessions) const honchoAnswer = await alice.chat( "What did Bob say about breakfast?", - { sessionId: session.id } + { session: session.id } ); // Query using conclusions from Alice's representation of Bob (from Alice's sessions only) const aliceAnswer = await alice.chat( "What did Bob say about breakfast?", - { sessionId: session.id, target: "bob" } + { session: session.id, target: "bob" } ); ``` @@ -225,7 +225,7 @@ This architecture enables: ## Semantic Search Parameters -Both `representation()` and `chat()` support semantic filtering to retrieve a subset of relevant conclusions. You can optionally filter by session to retrieve only conclusions from specific session context: +Both `representation()` and `chat()` support semantic filtering to retrieve a subset of relevant conclusions. You can optionally filter by session — pass `session` to scope to a single session, or use the REST-only [session allowlist](/v3/documentation/features/advanced/using-filters#scoping-recall-to-sessions) to scope to a set of sessions: | Parameter | Type | Description | |-----------|------|-------------| diff --git a/docs/v3/documentation/features/advanced/using-filters.mdx b/docs/v3/documentation/features/advanced/using-filters.mdx index 2a015c03..7d6caacd 100644 --- a/docs/v3/documentation/features/advanced/using-filters.mdx +++ b/docs/v3/documentation/features/advanced/using-filters.mdx @@ -304,6 +304,33 @@ sessions = honcho.sessions(filters={ ### List Membership +A bare list is shorthand for `in`, so `{"peer_id": ["alice", "bob"]}` and +`{"peer_id": {"in": ["alice", "bob"]}}` are equivalent: + + +```python Python +# Shorthand: a bare list means "any of these" +messages = session.messages(filters={ + "peer_id": ["alice", "bob", "charlie"] +}) +``` + +```typescript TypeScript +(async () => { + // Shorthand: a bare list means "any of these" + const messages = await session.messages({ + filters: { peer_id: ["alice", "bob", "charlie"] } + }); +})(); +``` + + + +Bare lists behave differently inside metadata — use `{"in": [...]}` there for OR matching. + + +The explicit form, plus the other comparison operators: + ```python Python # Find messages from specific peers in a session @@ -673,6 +700,93 @@ bob_explicit = peer.conclusions_of("bob").list(filters={"level": "explicit"}) ``` +## Scoping Recall to Sessions + +The [chat endpoint](/v3/documentation/features/chat) and the representation +endpoint accept a `filters` body too, but a deliberately narrow one: it defines +a **session allowlist**, restricting what the request can recall to the sessions +you name — conclusions on both endpoints, and on chat the messages the agent +reads as well. + +This is how you scope recall to more than one session. The `session_id` +parameter pins a request to exactly one session; an allowlist accepts a set. + +Only the `session_id` key is supported here, in three shapes: + +```json +{"filters": {"session_id": "support-chat-1"}} +{"filters": {"session_id": ["support-chat-1", "support-chat-2"]}} +{"filters": {"session_id": {"in": ["support-chat-1", "support-chat-2"]}}} +``` + + +```bash Chat +curl -X POST "$HONCHO_URL/v3/workspaces/my-app/peers/user-123/chat" \ + -H "Authorization: Bearer $HONCHO_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "query": "What did the user ask about billing?", + "filters": { "session_id": ["support-chat-1", "support-chat-2"] } + }' +``` + +```bash Representation +curl -X POST "$HONCHO_URL/v3/workspaces/my-app/peers/user-123/representation" \ + -H "Authorization: Bearer $HONCHO_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "filters": { "session_id": ["support-chat-1", "support-chat-2"] } + }' +``` + + + +The session allowlist is REST-only today. The SDKs cover the single-session case +with `session`, but do not yet expose the allowlist — call the endpoint directly +when you need a set of sessions. + + +### Rules + +Unlike the list endpoints above, this filter **fails closed**: an unrecognized +key or shape is rejected with `422` rather than ignored, because a silently +dropped filter here would widen recall instead of narrowing it. + +| Rule | Behavior | +|------|----------| +| Any key other than `session_id` | `422` | +| A shape other than a string, a list of strings, or `{"in": [...]}` | `422` | +| More than 1,000 sessions | `422` | +| `session_id` set alongside `filters` | The `session_id` must appear in the allowlist, else `422` | +| An empty allowlist (`[]`) | Valid, and recalls nothing | +| A peer-scoped key naming a session its peer isn't an active member of | `401` on chat — see below | + + +On chat, a peer-scoped key must be an active member of every session it names — +the allowlist reaches message recall there — and the request is rejected with +`401` otherwise. The representation endpoint runs no membership check: key scope +already confines the caller to its own peer's representation, which an allowlist +can only narrow. + + +### What Changes Under an Allowlist + +Scoping recall by session narrows what the reasoning agent can draw on: + +- **Only `explicit` conclusions are recalled.** Dream-derived conclusions + (`deductive`, `inductive`) are synthesized across sessions, so they can't be + attributed to one session and are excluded. +- **Reasoning-chain traversal is unavailable**, since it walks into those + derived conclusions. +- **Message recall is restricted to the allowlisted sessions** across every + search path — semantic, keyword, and date-range. + + +Because of this, an allowlisted request answers from directly-stated facts +rather than higher-order inferences. If you want the full representation, omit +`filters` and let the agent search everything. + + ## Error Handling Handle filter errors gracefully: diff --git a/docs/v3/documentation/features/advanced/webhooks.mdx b/docs/v3/documentation/features/advanced/webhooks.mdx new file mode 100644 index 00000000..8976750a --- /dev/null +++ b/docs/v3/documentation/features/advanced/webhooks.mdx @@ -0,0 +1,217 @@ +--- +title: 'Webhooks' +description: 'Receive push notifications when Honcho finishes background work' +icon: 'satellite-dish' +--- + +Honcho's reasoning runs in the background, so a message you just created is not +immediately reflected in the peer's representation. Instead of polling +[queue status](/v3/documentation/features/advanced/queue-status), you can +register a webhook endpoint and have Honcho notify you when the work it queued +for a session has drained. + +Webhooks are registered per workspace. Every event for that workspace is +delivered to every endpoint registered on it. + +## Registering an Endpoint + + +```bash Register +curl -X POST "$HONCHO_URL/v3/workspaces/my-app/webhooks" \ + -H "Authorization: Bearer $HONCHO_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"url": "https://example.com/honcho/webhook"}' +``` + +```bash List +curl -X GET "$HONCHO_URL/v3/workspaces/my-app/webhooks" \ + -H "Authorization: Bearer $HONCHO_API_KEY" +``` + +```bash Test +curl -X GET "$HONCHO_URL/v3/workspaces/my-app/webhooks/test" \ + -H "Authorization: Bearer $HONCHO_API_KEY" +``` + +```bash Delete +curl -X DELETE "$HONCHO_URL/v3/workspaces/my-app/webhooks/$ENDPOINT_ID" \ + -H "Authorization: Bearer $HONCHO_API_KEY" +``` + + +Registration is get-or-create: a URL already registered on the workspace +returns `200` with the existing endpoint, a new one returns `201`. The test +route emits a `test.event` to every endpoint on the workspace, which is the +quickest way to confirm your receiver and signature check work end to end. + +Webhook routes accept an admin key or a workspace-scoped key for that +workspace. Peer- and session-scoped keys cannot manage webhooks. + + +Webhook management is also available in the dashboard on the +[Webhooks](https://app.honcho.dev/webhooks) page. + + +### URL Requirements + +A webhook URL must be absolute and use `http` or `https`. URLs whose host is an +IP literal in a private, loopback, link-local, reserved, multicast, or +unspecified range are rejected with `422`. + + +This check inspects IP literals only — hostnames are accepted without +resolution. If you self-host, treat network-level egress controls, not this +validation, as your defense against internal-address delivery. + + +Each workspace can register up to `WEBHOOK_MAX_WORKSPACE_LIMIT` endpoints +(default 10). Exceeding the limit returns `409`. + +## Events + +| Event | When it fires | `data` fields | +|-------|---------------|---------------| +| `queue.empty` | A unit of queued background work finished draining | `workspace_id`, `queue_type` (`representation` or `summary`), `session_id`, `observer`, `observed` | +| `test.event` | You called `GET /webhooks/test` | `workspace_id` | + + +`queue.empty` is scoped to a single unit of work — one task type for one +session and observer/observed pair — not to the workspace as a whole. Other +work may still be queued elsewhere in the workspace when it fires. A session +whose messages produce both representation and summary work emits one event per +task type. + + +## Payload + +Every delivery is a `POST` with a `Content-Type: application/json` body in this +envelope: + +```json +{ + "type": "queue.empty", + "data": { + "workspace_id": "my-app", + "queue_type": "representation", + "session_id": "support-chat-1", + "observer": "assistant", + "observed": "user-123" + }, + "timestamp": "2026-08-10T18:24:05.123456Z" +} +``` + +**`data` is event-specific — its keys differ by event type.** A `test.event` +carries only `workspace_id`: + +```json +{ + "type": "test.event", + "data": { "workspace_id": "my-app" }, + "timestamp": "2026-08-10T18:24:05.123456Z" +} +``` + +Within one event type, an optional field with no value is sent as an explicit +`null` — on `queue.empty`, that's `session_id`, `observer`, and `observed` for +work that isn't tied to a session or an observer pair. Across event types the key +is simply absent. + +Parse defensively: branch on `type` as the discriminator, treat every `data` key +as optional rather than required, and tolerate new event types and new fields. +A parser that requires the `queue.empty` keys on every event will break on a +`test.event`. + +## Verifying Signatures + +Each delivery carries an `X-Honcho-Signature` header: the hex-encoded +HMAC-SHA256 of the **raw request body**, keyed with your deployment's +`WEBHOOK_SECRET`. Always compare with a constant-time function, and always sign +the bytes you received — Honcho serializes the body compactly with sorted keys, +so re-serializing your parsed JSON will not reliably reproduce it. + + +```python Python +import hashlib +import hmac +import json +import os + +def verify(raw_body: bytes, signature: str) -> bool: + expected = hmac.new( + os.environ["WEBHOOK_SECRET"].encode(), + raw_body, + hashlib.sha256, + ).hexdigest() + return hmac.compare_digest(expected, signature) + +# FastAPI — read the raw body, not a parsed model +@app.post("/honcho/webhook") +async def handle(request: Request): + raw = await request.body() + if not verify(raw, request.headers.get("X-Honcho-Signature", "")): + raise HTTPException(status_code=401) + event = json.loads(raw) + ... +``` + +```typescript TypeScript +import crypto from 'node:crypto'; + +function verify(rawBody: Buffer, signature: string): boolean { + const expected = crypto + .createHmac('sha256', process.env.WEBHOOK_SECRET!) + .update(rawBody) + .digest('hex'); + const a = Buffer.from(expected); + const b = Buffer.from(signature); + return a.length === b.length && crypto.timingSafeEqual(a, b); +} + +// Express — note express.raw(), not express.json() +app.post('/honcho/webhook', express.raw({ type: 'application/json' }), (req, res) => { + if (!verify(req.body, req.header('X-Honcho-Signature') ?? '')) { + return res.sendStatus(401); + } + const event = JSON.parse(req.body.toString()); + res.sendStatus(200); +}); +``` + + +## Delivery Semantics + +Delivery is best-effort and fire-and-forget: + +- Events fan out to all of the workspace's endpoints concurrently. +- Each request has a 30-second timeout. +- **There are no retries.** A non-2xx response, a timeout, or a connection + error is logged on the server and the event is dropped. + +Design your receiver accordingly: treat the event as a hint to re-read state +from the API rather than as the state itself, and fall back to +[queue status](/v3/documentation/features/advanced/queue-status) polling if you +need a guarantee. + +## Self-Hosting Requirements + + +`WEBHOOK_SECRET` must be set, or nothing is delivered. Honcho signs every +payload before sending it; with no secret configured, signing fails and the +event is dropped after being logged. Registration still succeeds, so a missing +secret looks like silence rather than an error. + + +Webhook delivery is queued work handled by the deriver process, so a deriver +worker must be running for events to be sent. See +[Configuration](/v3/contributing/configuration#webhooks) for +`WEBHOOK_SECRET` and `WEBHOOK_MAX_WORKSPACE_LIMIT`. + + + + Poll background processing state instead of waiting for a push + + + Full request and response schemas for the webhook endpoints + + diff --git a/docs/v3/documentation/features/chat.mdx b/docs/v3/documentation/features/chat.mdx index 6aab6996..b6e7e96c 100644 --- a/docs/v3/documentation/features/chat.mdx +++ b/docs/v3/documentation/features/chat.mdx @@ -94,6 +94,27 @@ for await (const chunk of responseStream.iter_text()) { Streaming is useful for displaying real-time responses in chat interfaces or when asking complex questions that require longer answers. +## Scoping to Sessions + +By default the chat endpoint reasons over everything Honcho knows about the +peer. Pass `session` (`session_id` on the REST body) to restrict it to one +session: + + +```python Python +answer = peer.chat("What did the user ask about?", session=session.id) +``` + +```typescript TypeScript +const answer = await peer.chat("What did the user ask about?", { session: session.id }); +``` + + +To scope a request to a *set* of sessions, use the session allowlist — a +constrained `filters` body on the endpoint. See +[Scoping Recall to Sessions](/v3/documentation/features/advanced/using-filters#scoping-recall-to-sessions) +for the accepted shapes and for what an allowlist changes about the answer. + ## Structured Outputs When your application needs a machine-readable answer instead of prose, pass a schema as `response_format` and the answer is guaranteed to conform to it: diff --git a/docs/v3/documentation/reference/platform.mdx b/docs/v3/documentation/reference/platform.mdx index abf3c646..3ed501d5 100644 --- a/docs/v3/documentation/reference/platform.mdx +++ b/docs/v3/documentation/reference/platform.mdx @@ -67,6 +67,7 @@ Scoped keys are authorized by their narrowest claim and never widen to the whole - A **peer-scoped** key acts on its own peer, plus **read-only** access to the sessions its peer is an active member of (context, summaries, peers, its own per-session config, search, and message reads). It cannot write to those sessions or act on other peers. - A **session-scoped** key is confined to its own session and cannot reach peer routes. - Peer- and session-scoped keys **must carry their parent workspace** — creating one without a workspace is rejected. +- On the chat endpoint, a peer-scoped key can only name sessions its peer is an active member of — both the `session_id` and every session in a [session allowlist](/v3/documentation/features/advanced/using-filters#scoping-recall-to-sessions). Naming any other session returns `401`. Workspace and admin keys pass the allowlist through as given. API Key Management Dashboard diff --git a/docs/v3/openapi.json b/docs/v3/openapi.json index 85e4891a..7fa1d194 100644 --- a/docs/v3/openapi.json +++ b/docs/v3/openapi.json @@ -9,7 +9,7 @@ "url": "https://honcho.dev/", "email": "hello@plasticlabs.ai" }, - "version": "3.0.11" + "version": "3.0.12" }, "servers": [ { @@ -2909,6 +2909,14 @@ "title": "Session Id", "description": "ID of the session to scope the representation to" }, + "filters": { + "anyOf": [ + { "additionalProperties": true, "type": "object" }, + { "type": "null" } + ], + "title": "Filters", + "description": "Optional filters to scope recall. This endpoint supports only the 'session_id' key: a session id, a list of session ids, or {\"in\": [...]}. Recall (conclusions and messages) is restricted to the allowlist; unsupported keys are rejected. When session_id is also set, it must be included in the allowlist." + }, "target": { "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "Target", @@ -2928,6 +2936,14 @@ "title": "Reasoning Level", "description": "Level of reasoning to apply: minimal, low, medium, high, or max", "default": "low" + }, + "response_format": { + "anyOf": [ + { "additionalProperties": true, "type": "object" }, + { "type": "null" } + ], + "title": "Response Format", + "description": "Optional JSON Schema (root type 'object') the response must conform to. When provided, `content` is a JSON string matching this schema. Only a conservative subset of JSON Schema is supported; unsupported schemas are rejected with 422. Constraint keywords (minItems, maxLength, ...) are hints to the model, not enforced server-side." } }, "type": "object", @@ -2947,7 +2963,7 @@ }, "DreamType": { "type": "string", - "enum": ["omni"], + "enum": ["omni", "card_refresh"], "title": "DreamType", "description": "Types of dreams that can be triggered." }, @@ -3352,6 +3368,14 @@ "title": "Session Id", "description": "Optional session ID within which to scope the representation" }, + "filters": { + "anyOf": [ + { "additionalProperties": true, "type": "object" }, + { "type": "null" } + ], + "title": "Filters", + "description": "Optional filters to scope the representation. This endpoint supports only the 'session_id' key: a session id, a list of session ids, or {\"in\": [...]}. When session_id is also set, it must be included in the allowlist." + }, "target": { "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "Target", @@ -3506,6 +3530,12 @@ "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "Session Id", "description": "Session ID to scope the dream to if specified" + }, + "rebuild": { + "type": "boolean", + "title": "Rebuild", + "description": "card_refresh dreams only: rebuild the peer card solely from observations currently in the collection, without injecting the existing card (use after removals)", + "default": false } }, "type": "object",