From 4acd78d45fc22d5315c1b14e8da99077aa7c4738 Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:21:10 -0400 Subject: [PATCH] chore(docs): Add Documentation for Scopes (#1086) * chore(docs): Add Documentation for Scopes * chore(docs): add scopes to API reference, architecture, and design patterns - Add the seven /scopes routes and their schemas to openapi.json, plus the scope/kind fields on chat, representation, session-create, and peer-list schemas; generate the endpoint pages and register a scopes nav group - Add a Scopes subsection and diagram node to the architecture data model - Add scope guidance to design patterns: quick-reference rows, an isolation boundary comparison (workspace / scope / session allowlist), and common mistakes (scope-per-reader, scopes-as-access-control) - Replace the "Underneath the Facade" section in scopes.mdx with behavioral guardrails and pointers to the implementation source * chore(docs): tighten scopes doc to decision-level detail - Drop the recall-resolution diagram (restated the Two Arms table) - Replace the enumerated Rules table with prose; caps and error shapes now live in the API reference schema descriptions - Trim backfill/removal internals to observable behavior and note that a backfilled scope deepens through subsequent dreams * chore(docs): reserve "scope" for the scopes feature Using it as a verb for session design, recall filters, and CLI targeting collides with the named-session-set feature. * chore(docs): clarify the scopes page and document create/status responses The page now leads with projection rather than partition and points at the scopes API; OpenAPI declares the 201/409/404 those routes actually return. * chore(docs): fix broken anchor and core-concepts link The rebase reintroduced a link to a renamed anchor in scopes.mdx, and unified-memory-setup pointed at /core-concepts/, which has no index page. * chore(docs): correct scope arms, listing, and read-surface pointers The Accepts row mixed named-scope with the allowlist arm, kind=scope on the peers list does not return facade ids, and chat/context/search never mentioned scope=. * chore(docs): drop the 1k-token session batching narrative Reasoning no longer waits on a per-session token threshold, so product docs should not tell people to size sessions around that gate. * chore: minor fix --- docs/docs.json | 19 +- .../endpoint/scopes/add-sessions-to-scope.mdx | 3 + .../endpoint/scopes/get-or-create-scope.mdx | 3 + .../endpoint/scopes/get-scope-sessions.mdx | 3 + .../endpoint/scopes/get-scope-status.mdx | 3 + .../endpoint/scopes/get-scope.mdx | 3 + .../endpoint/scopes/get-scopes.mdx | 3 + .../scopes/remove-session-from-scope.mdx | 3 + docs/v3/contributing/troubleshooting.mdx | 1 - .../core-concepts/architecture.mdx | 10 +- .../core-concepts/design-patterns.mdx | 42 +- .../documentation/core-concepts/reasoning.mdx | 12 +- ...es.mdx => directional-representations.mdx} | 10 +- .../features/advanced/overview.mdx | 3 +- .../features/advanced/peer-card.mdx | 2 +- .../features/advanced/queue-status.mdx | 8 +- .../advanced/reasoning-configuration.mdx | 2 +- .../features/advanced/scopes.mdx | 355 +++++++++++++ .../features/advanced/search.mdx | 14 + .../features/advanced/using-filters.mdx | 36 +- docs/v3/documentation/features/chat.mdx | 8 +- .../v3/documentation/features/get-context.mdx | 19 +- docs/v3/documentation/reference/cli.mdx | 4 +- docs/v3/guides/community/pi-honcho-memory.mdx | 2 +- docs/v3/guides/integrations/paperclip.mdx | 6 +- .../guides/recipes/unified-memory-setup.mdx | 17 +- docs/v3/openapi.json | 469 +++++++++++++++++- 27 files changed, 992 insertions(+), 68 deletions(-) create mode 100644 docs/v3/api-reference/endpoint/scopes/add-sessions-to-scope.mdx create mode 100644 docs/v3/api-reference/endpoint/scopes/get-or-create-scope.mdx create mode 100644 docs/v3/api-reference/endpoint/scopes/get-scope-sessions.mdx create mode 100644 docs/v3/api-reference/endpoint/scopes/get-scope-status.mdx create mode 100644 docs/v3/api-reference/endpoint/scopes/get-scope.mdx create mode 100644 docs/v3/api-reference/endpoint/scopes/get-scopes.mdx create mode 100644 docs/v3/api-reference/endpoint/scopes/remove-session-from-scope.mdx rename docs/v3/documentation/features/advanced/{representation-scopes.mdx => directional-representations.mdx} (95%) create mode 100644 docs/v3/documentation/features/advanced/scopes.mdx diff --git a/docs/docs.json b/docs/docs.json index b9d0e498..56fc9448 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -10,6 +10,10 @@ { "source": "/v3/guides/integrations/claudecode", "destination": "/v3/guides/integrations/claude-code" + }, + { + "source": "/v3/documentation/features/advanced/representation-scopes", + "destination": "/v3/documentation/features/advanced/directional-representations" } ], "colors": { @@ -62,7 +66,8 @@ "v3/documentation/features/advanced/reasoning-configuration", "v3/documentation/features/advanced/summarizer", "v3/documentation/features/advanced/peer-card", - "v3/documentation/features/advanced/representation-scopes", + "v3/documentation/features/advanced/directional-representations", + "v3/documentation/features/advanced/scopes", "v3/documentation/features/advanced/dreaming", "v3/documentation/features/advanced/queue-status", "v3/documentation/features/advanced/webhooks", @@ -208,6 +213,18 @@ "v3/api-reference/endpoint/sessions/search-session" ] }, + { + "group": "scopes", + "pages": [ + "v3/api-reference/endpoint/scopes/get-or-create-scope", + "v3/api-reference/endpoint/scopes/get-scopes", + "v3/api-reference/endpoint/scopes/get-scope", + "v3/api-reference/endpoint/scopes/add-sessions-to-scope", + "v3/api-reference/endpoint/scopes/get-scope-sessions", + "v3/api-reference/endpoint/scopes/remove-session-from-scope", + "v3/api-reference/endpoint/scopes/get-scope-status" + ] + }, { "group": "messages", "pages": [ diff --git a/docs/v3/api-reference/endpoint/scopes/add-sessions-to-scope.mdx b/docs/v3/api-reference/endpoint/scopes/add-sessions-to-scope.mdx new file mode 100644 index 00000000..e03b8941 --- /dev/null +++ b/docs/v3/api-reference/endpoint/scopes/add-sessions-to-scope.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v3/workspaces/{workspace_id}/scopes/{scope_id}/sessions +--- diff --git a/docs/v3/api-reference/endpoint/scopes/get-or-create-scope.mdx b/docs/v3/api-reference/endpoint/scopes/get-or-create-scope.mdx new file mode 100644 index 00000000..9908c7bb --- /dev/null +++ b/docs/v3/api-reference/endpoint/scopes/get-or-create-scope.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v3/workspaces/{workspace_id}/scopes +--- diff --git a/docs/v3/api-reference/endpoint/scopes/get-scope-sessions.mdx b/docs/v3/api-reference/endpoint/scopes/get-scope-sessions.mdx new file mode 100644 index 00000000..d3e9840d --- /dev/null +++ b/docs/v3/api-reference/endpoint/scopes/get-scope-sessions.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v3/workspaces/{workspace_id}/scopes/{scope_id}/sessions/list +--- diff --git a/docs/v3/api-reference/endpoint/scopes/get-scope-status.mdx b/docs/v3/api-reference/endpoint/scopes/get-scope-status.mdx new file mode 100644 index 00000000..489ce9f6 --- /dev/null +++ b/docs/v3/api-reference/endpoint/scopes/get-scope-status.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v3/workspaces/{workspace_id}/scopes/{scope_id}/status +--- diff --git a/docs/v3/api-reference/endpoint/scopes/get-scope.mdx b/docs/v3/api-reference/endpoint/scopes/get-scope.mdx new file mode 100644 index 00000000..192fc74b --- /dev/null +++ b/docs/v3/api-reference/endpoint/scopes/get-scope.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v3/workspaces/{workspace_id}/scopes/{scope_id} +--- diff --git a/docs/v3/api-reference/endpoint/scopes/get-scopes.mdx b/docs/v3/api-reference/endpoint/scopes/get-scopes.mdx new file mode 100644 index 00000000..6362d59d --- /dev/null +++ b/docs/v3/api-reference/endpoint/scopes/get-scopes.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v3/workspaces/{workspace_id}/scopes/list +--- diff --git a/docs/v3/api-reference/endpoint/scopes/remove-session-from-scope.mdx b/docs/v3/api-reference/endpoint/scopes/remove-session-from-scope.mdx new file mode 100644 index 00000000..164e912c --- /dev/null +++ b/docs/v3/api-reference/endpoint/scopes/remove-session-from-scope.mdx @@ -0,0 +1,3 @@ +--- +openapi: delete /v3/workspaces/{workspace_id}/scopes/{scope_id}/sessions/{session_id} +--- diff --git a/docs/v3/contributing/troubleshooting.mdx b/docs/v3/contributing/troubleshooting.mdx index e40917ab..76d9a268 100644 --- a/docs/v3/contributing/troubleshooting.mdx +++ b/docs/v3/contributing/troubleshooting.mdx @@ -109,7 +109,6 @@ Messages are stored but no observations, summaries, or representations are being ```bash DERIVER_WORKERS=4 ``` -5. **Representation Batching** — By default the deriver buffers representation work until a work unit has accumulated enough tokens, set via `DERIVER_REPRESENTATION_BATCH_WORK_UNIT_TARGET_TOKENS` (`0` disables the accumulation gate). A separate setting, `DERIVER_REPRESENTATION_BATCH_TARGET_INPUT_TOKENS`, caps the conversation window fed to each deriver LLM call when draining a claimed work unit. Sub-threshold tails become eligible after `DERIVER_REPRESENTATION_BATCH_MAX_AGE_SECONDS` (default 1800 seconds), so quiet sessions eventually flush without disabling batching globally. Set the age to `0` for legacy behavior where sub-threshold tails wait indefinitely. See [token batching](/v3/documentation/core-concepts/reasoning#token-batching) for more details ## Alternative Provider Issues diff --git a/docs/v3/documentation/core-concepts/architecture.mdx b/docs/v3/documentation/core-concepts/architecture.mdx index b3f0f7a1..5035d968 100644 --- a/docs/v3/documentation/core-concepts/architecture.mdx +++ b/docs/v3/documentation/core-concepts/architecture.mdx @@ -34,7 +34,7 @@ Honcho has a hierarchical data model centered around the entities below. Workspaces are the top-level containers in Honcho. They provide complete isolation between different applications or environments, essentially serving as a namespace to keep different workloads separate. You might use separate workspaces for development, staging, and production environments, or to isolate different product lines. They also enable multi-tenant SaaS applications where each customer gets their own isolated workspace with complete data separation. -Authentication is scoped to the workspace level, and configuration settings can be applied workspace-wide to control behavior across all peers and sessions within that workspace. +Authentication is issued at the workspace level, and configuration settings can be applied workspace-wide to control behavior across all peers and sessions within that workspace. --- @@ -50,12 +50,14 @@ You can use peers for any entity that persists over time--individual users in ch ### Sessions -Sessions represent interaction threads or contexts between peers. A session can involve multiple peers and provides temporal boundaries for when a set of interactions starts and ends. This lets you scope context and memory to specific interactions while still maintaining longer-term peer representations that span sessions. +Sessions represent interaction threads or contexts between peers. A session can involve multiple peers and provides temporal boundaries for when a set of interactions starts and ends. This lets you confine context and memory to specific interactions while still maintaining longer-term peer representations that span sessions. -Use sessions to scope things like support tickets, meeting transcripts, learning sessions, or conversations. You can also use single-peer sessions as a way to import external data--create a session with just one peer and structure emails, documents, or files as messages to enrich that peer's representation. +Use sessions for things like support tickets, meeting transcripts, learning sessions, or conversations. You can also use single-peer sessions as a way to import external data--create a session with just one peer and structure emails, documents, or files as messages to enrich that peer's representation. Session-level configuration gives you fine-grained control over perspective-taking behavior. You can configure whether a peer should form representations of other peers in the session, and whether other peers should form representations of them. +Sessions are also the unit of visibility: when one peer's history spans contexts that shouldn't inform each other, you can group sessions into named [scopes](/v3/documentation/features/advanced/scopes) that bound recall to just those sessions. + --- ### Messages @@ -84,7 +86,7 @@ Honcho runs as two cooperating processes: an **API server** that handles request **Write path (synchronous).** A message is stored and a reasoning task is enqueued in the same request; the API returns immediately. Nothing about the reasoning that follows blocks the caller. -**Deriver + Summarizer (async, per-message).** The worker picks up queued tasks in small batches. The Deriver reads new messages and extracts conclusions about the peer--explicit statements and direct deductions. In parallel, the Summarizer periodically rolls up recent messages into short- and long-form session summaries. Both run per-message (well, per-batch) rather than on a schedule. +**Deriver + Summarizer (async, per-message).** The worker picks up queued tasks. The Deriver reads new messages and extracts conclusions about the peer--explicit statements and direct deductions. In parallel, the Summarizer periodically rolls up recent messages into short- and long-form session summaries. Both run per-message rather than on a schedule. **Dreamer (periodic).** On a schedule (or triggered on demand), the Dreamer revisits existing conclusions to consolidate and deepen them: removing redundant or stale ones, drawing inductive conclusions across patterns that span multiple messages, and updating peer cards--compact biographical summaries of a peer. This is where memory gets richer over time, not just larger. diff --git a/docs/v3/documentation/core-concepts/design-patterns.mdx b/docs/v3/documentation/core-concepts/design-patterns.mdx index 9893d3f4..481dd769 100644 --- a/docs/v3/documentation/core-concepts/design-patterns.mdx +++ b/docs/v3/documentation/core-concepts/design-patterns.mdx @@ -12,22 +12,26 @@ Ready to add Honcho to your codebase? The **`/honcho-integration` skill** applie ## Quick Reference -**Workspaces isolate, peers persist, and sessions scope the active context.** +**Workspaces isolate, peers persist, and sessions bound the active context.** | Decision | Recommendation | |----------|---------------| | How many workspaces? | One workspace per application, tool, tenant, or collaboration boundary. Split workspaces only when you need hard isolation between products, customers, environments, or agents. | | When should agents share a workspace? | When agents collaborate over the same product, project, team, user, customer, or game state. Separate them when they should not see or influence each other's memory. | | Who should be a peer? | Any persistent participant whose messages should be attributed or reasoned about: users, agents, assistants, NPCs, students, or customers. Use one peer for the same entity across sessions and platforms. | -| How should I scope sessions? | Scope sessions to the active interaction: per-conversation, per-channel, per-task run, per-project, per-import, or other bounded context. Reuse a session when local context should keep accumulating. | +| How should I divide sessions? | Match each session to the active interaction: per-conversation, per-channel, per-task run, per-project, per-import, or other bounded context. Reuse a session when local context should keep accumulating. | | How does cross-session reasoning work? | Session memory stays local to one session. Peer representations accumulate across every session where the peer is included, and `session.context()` becomes cross-session when you include a peer target. | | Should I set `observe_me: false`? | Yes, for deterministic peers Honcho does not need to model, like bots or tool agents. Still save their messages so other peers have session context. Keep it enabled for users and evolving agents. | | Do I need `observe_others`? | Only when a peer needs its own perspective on another participant, such as in games, multi-agent systems, or parent/subagent workflows. | +| When do I need a scope? | When one peer's history spans contexts that must not leak into each other's recall — but you still want one workspace and one unified peer. Group the confidential sessions into a [scope](/v3/documentation/features/advanced/scopes) and pass it at query time. | +| Perspectives or scopes? | `observe_others` gives a *participant* its own view of another peer. A scope bounds recall to *where things were said*, for a reader that isn't a participant. If the reader is in the session, use perspectives; if you're fencing off a set of sessions, use a scope. | ## Workspace Design A workspace is a hard isolation boundary. **Default to one workspace per application,** and split only at a real privacy, compliance, or product boundary (e.g. per-tenant SaaS, or a tool that needs intentionally isolated memory). Agents that collaborate over the same product, user, or game state belong in the *same* workspace so each can retrieve what the others produced. +If what you actually need is "this part of a peer's history shouldn't inform that assistant," don't split the workspace — that severs the peer's identity too. Use a [scope](/v3/documentation/features/advanced/scopes) instead: the peer stays whole, and recall through the scope sees only its member sessions. + Honcho plugins default to one workspace *per host* (`hermes`, `claude_code`, `cursor`, `opencode`). To unify memory across them, point each at the same workspace — see [Unified Memory Setup](/v3/guides/recipes/unified-memory-setup). @@ -48,11 +52,11 @@ For unified context across Honcho plugins, set the same user peer ID (`peerName` ## Session Design -Sessions define the temporal boundaries of an interaction. How you scope them affects how summaries are generated, how context is retrieved, and when reasoning fires. +Sessions define the temporal boundaries of an interaction. Where you draw those boundaries affects how summaries are generated and how context is retrieved. **Common session patterns** -| Pattern | Session scoped to | Example | +| Pattern | Session covers | Example | |---------|-------------------|---------| | Per-conversation | Each new chat thread | ChatGPT or Claude Code style UI where each thread is a session | | Per-channel | A persistent channel or room | Discord channel, Slack thread | @@ -62,10 +66,6 @@ Sessions define the temporal boundaries of an interaction. How you scope them af Create a **new** session when context resets (new conversation, new day, new topic); **reuse** one when context should keep accumulating (ongoing channel, persistent thread). - -**Don't scope sessions too thin.** Honcho batches reasoning until a peer accumulates ~1,000 tokens *within a single session*, with a default age-based flush for quiet tails ([token batching](/v3/documentation/core-concepts/reasoning#token-batching)). Low-volume or trickle inputs should still append to one ongoing session rather than fragment across many, so reasoning runs with useful context instead of many small delayed batches. - - **How cross-session reasoning works** - **Session memory** is local to an interaction — summaries and recent-message context describe only what happened there. @@ -75,14 +75,33 @@ So you can start a session fresh or pull in a peer's long-term memory. [`session --- +## Choosing an Isolation Boundary + +Honcho gives you three boundaries at different strengths. Pick the weakest one that solves your problem: + +| Boundary | Strength | Use when | +|----------|----------|----------| +| **Workspace** | Hard isolation — nothing crosses, including the peer itself | Different products, tenants, or environments | +| **[Scope](/v3/documentation/features/advanced/scopes)** | Recall boundary — one peer, but queries through the scope see only its sessions | One peer's contexts must not leak into each other (clinical vs. billing, per-reseller support) | +| **Session allowlist** (`sessions=[...]`) | Ad-hoc recall restriction, decided per request | The session set varies per query, or you need a quick boundary without provisioning anything | + +Two things scopes are **not**: + +- **Not authorization.** A workspace key reads any session, scoped or not. A scope constrains queries that name it; it doesn't protect data from queries that don't. +- **Not topic filtering.** Scopes bound recall by *where something was said*, not what it's about. A therapy detail mentioned in a billing session lands in the billing scope. If you might ever need a scope boundary, align your session boundaries with your confidentiality boundaries from the start — the session is the unit scopes can enforce. + +--- + ## Common Mistakes - **Splitting one identity across peer IDs** -- If the same user is `alice`, `alice-discord`, and `alice-cursor`, Honcho builds separate representations. Use one stable peer ID when you want unified memory. -- **Too many tiny sessions** -- Summaries and recent messages are session-scoped, and reasoning only fires past ~1,000 tokens per session. Splitting a continuous conversation across many sessions fragments local context and can stall reasoning. Reuse a session when context should flow continuously. +- **Too many tiny sessions** -- Summaries and recent messages are local to one session. Splitting a continuous conversation across many sessions fragments that local context. Reuse a session when context should flow continuously. - **Separating agents that should collaborate** -- If agents need shared product, customer, or team context, put them in the same workspace. Separate workspaces are hard isolation boundaries. - **Leaving `observe_me` on for assistants** -- Wastes reasoning compute on a peer you control. Deterministic behavior doesn't need to be modeled. - **Turning on `observe_others` everywhere** -- Directional representations are powerful, but they add complexity. Use them when peers need distinct perspectives, not just because a session has multiple peers. -- **Forgetting `peer_target` on session context** -- `session.context()` defaults to the active session's summary and recent messages, which are session-scoped. It becomes cross-session only through adding a peer_target which includes the peer representation. +- **A scope per reader** -- Scopes should map to real confidentiality boundaries, not to consumers. If every assistant gets its own scope, you've rebuilt workspace fragmentation inside one workspace, and each projection reasons over a thin slice. Fewer, boundary-shaped scopes; many readers can share one. +- **Treating scopes as access control** -- A scope bounds *recall*, not *access*. Enforce who may query what in your application layer; use scopes to keep the answers themselves from drawing on out-of-bounds sessions. +- **Forgetting `peer_target` on session context** -- `session.context()` defaults to the active session's summary and recent messages, which are local to that session. It becomes cross-session only through adding a peer_target which includes the peer representation. - **Blocking on processing** -- Messages are processed asynchronously in the background. Don't poll or wait for reasoning to complete before continuing your application flow. ## Next Steps @@ -94,6 +113,9 @@ So you can start a session fresh or pull in a peer's long-term memory. [`session Retrieve formatted context from sessions for your LLM + + Bound recall to named sets of sessions + Query Honcho about your peers with natural language diff --git a/docs/v3/documentation/core-concepts/reasoning.mdx b/docs/v3/documentation/core-concepts/reasoning.mdx index aa4de900..cc2add5b 100644 --- a/docs/v3/documentation/core-concepts/reasoning.mdx +++ b/docs/v3/documentation/core-concepts/reasoning.mdx @@ -66,21 +66,11 @@ The reasoning outputs--conclusions, summaries, peer cards--are stored as part of The diagram above shows how agents write messages to Honcho, which triggers reasoning that updates peer representations. Agents can then query representations to get additional context for their next response. -### Token Batching - -Rather than running inference on every individual message, Honcho accumulates messages in the queue and processes them as a batch once the total token count of pending messages for a given peer representation crosses a threshold--roughly **1,000 tokens** at the current batch size. This keeps ingestion costs down, since Honcho charges based on reasoning passes, and ensures each pass has a meaningful amount of context to work with. At ~1,000 tokens the batch comfortably fits in the context window of any modern LLM, so no content is lost. - -If a user sends several short messages in a row (e.g., "yes", "ok", "sounds good"), those messages sit in the queue until enough content has accumulated. Once the threshold is met, the full batch is processed together in a single reasoning call. - - -This batching only applies to **representation** tasks (conclusion extraction). Summary and dream tasks have their own scheduling logic and are not subject to the token threshold. - - ## Balances & Design Choices Off-the-shelf LLMs can perform formal logical reasoning, but they aren't optimized for it. Honcho uses custom models trained specifically for logical rigor (following formal reasoning rules rather than plausible-sounding text), structured output (consistent JSON schema with premises and conclusions), and efficiency (smaller, faster models tuned for this specific task). This allows Honcho to reason more reliably and at lower cost than general-purpose frontier LLMs. -The approach balances quality with practical constraints. Custom models are smaller and cheaper to run, scaffolded conclusions are more token-efficient than raw conversation history, and we batch where appropriate to optimize update frequency. +The approach balances quality with practical constraints. Custom models are smaller and cheaper to run, and scaffolded conclusions are more token-efficient than raw conversation history. Honcho's reasoning capabilities are actively being improved. Current areas of development include enhanced inductive and abductive reasoning, multi-hop and temporal reasoning, and expanded file types and modalities. The system is designed to be extensible--new reasoning capabilities can be added without breaking existing functionality. diff --git a/docs/v3/documentation/features/advanced/representation-scopes.mdx b/docs/v3/documentation/features/advanced/directional-representations.mdx similarity index 95% rename from docs/v3/documentation/features/advanced/representation-scopes.mdx rename to docs/v3/documentation/features/advanced/directional-representations.mdx index 30e2ae32..7a0c9562 100644 --- a/docs/v3/documentation/features/advanced/representation-scopes.mdx +++ b/docs/v3/documentation/features/advanced/directional-representations.mdx @@ -1,6 +1,6 @@ --- -title: 'Representation Scopes' -description: 'Advanced configuration and querying for representations' +title: 'Directional Representations' +description: 'How peers build and query representations of other peers' icon: 'circle' --- @@ -214,7 +214,7 @@ Most applications don't need directional representations. Start with the default Under the hood, Honcho stores representations as (observer, observed) pairs in internal collections: - **Collection**: A unique (observer, observed, workspace) tuple containing documents -- **Documents**: Individual conclusions and artifacts (deductive, inductive, abductive conclusions, summaries, peer cards) with session scoping +- **Documents**: Individual conclusions and artifacts (deductive, inductive, abductive conclusions, summaries, peer cards) with per-session filtering When you retrieve with `target`, Honcho fetches documents from the specific (observer, observed) collection. When you retrieve without `target`, it fetches from the (peer, peer) collection—the peer's self-representation. @@ -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 — 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: +Both `representation()` and `chat()` support semantic filtering to retrieve a subset of relevant conclusions. You can optionally filter by session — pass `session` to restrict to a single session, or use the REST-only [session allowlist](/v3/documentation/features/advanced/using-filters#scoping-recall-to-sessions) to restrict to a set of sessions: | Parameter | Type | Description | |-----------|------|-------------| @@ -265,7 +265,7 @@ Directional representations update automatically through the reasoning pipeline 2. The message sender has `observe_me=true` (or session-level equivalent) 3. Other peers in the session have `observe_others=true` -The pipeline respects scoping—Honcho's representations reason over messages across all sessions, while directional representations only reason over messages from sessions where the observer was an active participant. +The pipeline respects these boundaries—Honcho's representations reason over messages across all sessions, while directional representations only reason over messages from sessions where the observer was an active participant. ### Peer Join Order Matters diff --git a/docs/v3/documentation/features/advanced/overview.mdx b/docs/v3/documentation/features/advanced/overview.mdx index 09f92fea..d0d7cc2c 100644 --- a/docs/v3/documentation/features/advanced/overview.mdx +++ b/docs/v3/documentation/features/advanced/overview.mdx @@ -12,7 +12,8 @@ Advanced features give you fine-grained control over Honcho's behavior and imple - [Configuration](/v3/documentation/features/advanced/reasoning-configuration) - Configure reasoning models and behavior - [Summarizer](/v3/documentation/features/advanced/summarizer) - Automatic session summarization - [Peer Card](/v3/documentation/features/advanced/peer-card) - Quick-reference profile of stable biographical facts about a peer -- [Representation Scopes](/v3/documentation/features/advanced/representation-scopes) - Directional representations for multi-peer scenarios +- [Directional Representations](/v3/documentation/features/advanced/directional-representations) - How peers build separate representations of each other +- [Scopes](/v3/documentation/features/advanced/scopes) - Named sets of sessions that act as visibility boundaries for recall - [Dreaming](/v3/documentation/features/advanced/dreaming) - Autonomous memory consolidation and self-improvement - [Queue Status](/v3/documentation/features/advanced/queue-status) - Monitor background processing and reasoning tasks diff --git a/docs/v3/documentation/features/advanced/peer-card.mdx b/docs/v3/documentation/features/advanced/peer-card.mdx index fbc08772..5db6a1db 100644 --- a/docs/v3/documentation/features/advanced/peer-card.mdx +++ b/docs/v3/documentation/features/advanced/peer-card.mdx @@ -79,7 +79,7 @@ console.log(card); ## Directional Peer Cards -Peer cards follow the same observer-observed model as [representations](/v3/documentation/features/advanced/representation-scopes). When `observe_others` is enabled, a peer can have a **different** card for each peer it observes. +Peer cards follow the same observer-observed model as [representations](/v3/documentation/features/advanced/directional-representations). When `observe_others` is enabled, a peer can have a **different** card for each peer it observes. For example, if Alice and Bob are in a session together and Alice has `observe_others: true`, Alice will build her own peer card for Bob--separate from Honcho's peer card for Bob. You can read and write these directional cards using the `target` parameter. diff --git a/docs/v3/documentation/features/advanced/queue-status.mdx b/docs/v3/documentation/features/advanced/queue-status.mdx index 7c4d363c..809c2e9f 100644 --- a/docs/v3/documentation/features/advanced/queue-status.mdx +++ b/docs/v3/documentation/features/advanced/queue-status.mdx @@ -8,9 +8,9 @@ Whenever messages are stored in Honcho, background processes kick off to [reason Reasoning is an asynchronous process and will not immediately generate insights for the latest message you've sent. This is -by design: we want to reason efficiently over batches of messages -rather than assessing each message in a vacuum. Honcho provides -several utilities to check the status of the queue. +by design: Honcho reasons in the background rather than on the +write path. Honcho provides several utilities to check the status +of the queue. ```python Python @@ -95,7 +95,7 @@ not the total number of items ever processed. The `queue_status` method can take additional -parameters to scope the status to a specific work unit: +parameters to filter the status by a matching observer, sender, or session: ```python Python diff --git a/docs/v3/documentation/features/advanced/reasoning-configuration.mdx b/docs/v3/documentation/features/advanced/reasoning-configuration.mdx index 8642254a..492dd4b9 100644 --- a/docs/v3/documentation/features/advanced/reasoning-configuration.mdx +++ b/docs/v3/documentation/features/advanced/reasoning-configuration.mdx @@ -157,7 +157,7 @@ You may therefore disable observation of a peer by setting the `observe_me` flag If the peer has a session-level configuration, it will override this configuration. If the flag is not set, or is set to `true`, the peer will be observed. -For session-level observation controls and local representations (where peers build separate models of each other), see [Representation Scopes](/v3/documentation/features/advanced/representation-scopes). +For session-level observation controls and local representations (where peers build separate models of each other), see [Directional Representations](/v3/documentation/features/advanced/directional-representations). diff --git a/docs/v3/documentation/features/advanced/scopes.mdx b/docs/v3/documentation/features/advanced/scopes.mdx new file mode 100644 index 00000000..4fa827fd --- /dev/null +++ b/docs/v3/documentation/features/advanced/scopes.mdx @@ -0,0 +1,355 @@ +--- +title: 'Scopes' +description: 'Named sets of sessions that act as visibility boundaries for recall' +icon: 'shield-halved' +--- + +A **scope** is a named set of sessions that acts as a visibility boundary. Recall +performed through a scope sees only what happened in that scope's sessions, +while the peer keeps its single unified representation of everything it has ever +participated in. + +Use scopes when one peer's history spans contexts that must not leak into each +other — a therapy app where the clinical sessions must not inform the billing +assistant, a support product where a reseller's agent may only answer from its +own tickets, a multi-tenant deployment where one human works across tenants. + +## Projection, Not Partition + +The peer keeps one representation. A scope is a **projection** of it: a view +built only from evidence in the member sessions. + +```mermaid +graph TB + P[Peer: user-123
one unified representation] + + P --> S1[session: therapy-1] + P --> S2[session: therapy-2] + P --> S3[session: billing-1] + P --> S4[session: onboarding-1] + + SC1[scope: therapy] -.->|projects| S1 + SC1 -.->|projects| S2 + SC2[scope: billing] -.->|projects| S3 + + style P fill:#B6DBFF,stroke:#333,color:#000 + style S1 fill:#B6DBFF,stroke:#333,color:#000 + style S2 fill:#B6DBFF,stroke:#333,color:#000 + style S3 fill:#B6DBFF,stroke:#333,color:#000 + style S4 fill:#B6DBFF,stroke:#333,color:#000 + style SC1 fill:#FFE0B2,stroke:#333,color:#000 + style SC2 fill:#FFE0B2,stroke:#333,color:#000 +``` + +- **Sessions can belong to more than one scope.** Membership is many-to-many. +- **Sessions can belong to no scope.** `onboarding-1` above is reachable + by an unscoped request and by nothing else. +- **An unscoped request still sees everything.** A scope constrains the requests + that name it; it does not hide the sessions from requests that don't. + + +Scopes are a recall boundary, not an authorization boundary. Who may call the +API is still governed by workspace, session, and peer keys. + + +## The Two Arms + +There are two ways to confine recall, and they behave differently. Picking the +wrong one is the most common mistake with this feature. + +| | `scope="therapy"` (named scope) | `sessions=[...]` / `scope=["a","b"]` (allowlist) | +|---|---|---| +| **Mechanism** | Reads the scope's own representation of the peer | Restricts the peer's own representation to a set of sessions | +| **Conclusions** | All levels — `explicit`, plus `deductive` / `inductive` reasoned **within** the scope | `explicit` only | +| **Reasoning chains** | Available | Unavailable | +| **Setup required** | Yes — create the scope, add sessions, wait for backfill | None — pass session IDs ad hoc | +| **Accepts** | One scope name | A list of up to 100 scope names, or up to 1,000 session IDs | + +### Named scope: depth + +Passing a **single** scope name swaps the observer. Recall runs against the +scope's own view of the target peer, which the deriver and dreamer have been +building from the scope's member sessions all along. That view contains +higher-order inferences — but only ones reasoned from evidence inside the scope. + +```python +answer = user.chat("What is stressing them out?", scope="therapy") +``` + +This is the arm you want for a durable, meaningful boundary. + +### Allowlist: breadth + +Passing a **list** of scopes, or a bare list of session IDs, keeps the peer as +the observer and restricts recall to the union of those sessions. Because a +dream-derived conclusion is synthesized across sessions, it cannot be attributed +to any one of them — so this arm recalls `explicit` conclusions only, and answers +from directly-stated facts rather than inference. + +```python +answer = user.chat("What did they say about billing?", sessions=[s1, s2]) +answer = user.chat("What did they say?", scope=["therapy", "intake"]) +``` + +Reach for this when the set of sessions is decided per-request, or when you want +a quick boundary without provisioning a scope. See +[Scoping Recall to Sessions](/v3/documentation/features/advanced/using-filters#scoping-recall-to-sessions) +for the full allowlist rules. + + +A list of scopes is the allowlist arm, not "several named scopes at once". It +gives you the union of their *sessions*, at explicit-only depth — it does not +give you the union of their reasoned views. If you need depth, query one scope. + + +## Creating a Scope and Managing Membership + + +```python Python +from honcho import Honcho + +honcho = Honcho(workspace_id="my-app") + +# Get or create — idempotent; passing metadata updates the existing scope +therapy = honcho.scope("therapy") + +# Add existing sessions (max 100 per call) +therapy.add_sessions(["therapy-session-1", "therapy-session-2"]) + +# Or attach at session creation — the scope is created if it doesn't exist +session = honcho.session("therapy-session-3", scopes=["therapy"]) + +# Inspect +for s in therapy.sessions(): + print(s.id) + +therapy.remove_session("therapy-session-1") + +for scope in honcho.scopes(): + print(scope.id, scope.metadata) +``` + +```typescript TypeScript +import { Honcho } from "@honcho-ai/sdk"; + +const honcho = new Honcho({ workspaceId: "my-app" }); + +// Get or create — idempotent; passing metadata updates the existing scope +const therapy = await honcho.scope("therapy"); + +// Add existing sessions (max 100 per call) +await therapy.addSessions(["therapy-session-1", "therapy-session-2"]); + +// Or attach at session creation — the scope is created if it doesn't exist +const session = await honcho.session("therapy-session-3", { + scopes: ["therapy"], +}); + +// Inspect +for await (const s of await therapy.sessions()) { + console.log(s.id); +} + +await therapy.removeSession("therapy-session-1"); + +for await (const scope of await honcho.scopes()) { + console.log(scope.id, scope.metadata); +} +``` + +```bash REST +# Get or create (201 created / 200 existing) +curl -X POST "$HONCHO_URL/v3/workspaces/my-app/scopes" \ + -H "Authorization: Bearer $HONCHO_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"id": "therapy"}' + +# Add sessions +curl -X POST "$HONCHO_URL/v3/workspaces/my-app/scopes/therapy/sessions" \ + -H "Authorization: Bearer $HONCHO_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"session_ids": ["therapy-session-1", "therapy-session-2"]}' + +# List membership +curl -X POST "$HONCHO_URL/v3/workspaces/my-app/scopes/therapy/sessions/list" \ + -H "Authorization: Bearer $HONCHO_API_KEY" + +# Remove one session +curl -X DELETE "$HONCHO_URL/v3/workspaces/my-app/scopes/therapy/sessions/therapy-session-1" \ + -H "Authorization: Bearer $HONCHO_API_KEY" +``` + + +Scope IDs are unprefixed, must match `^[a-zA-Z0-9_-]+$`, and are at most 506 +characters. Get-or-create is idempotent: if the scope already exists, the same +call returns it, and any `metadata` you pass is written onto it. + + +Every scopes route — and every read that passes `scope` — requires a +**workspace-level or admin key**. A scope's membership can exceed any single +peer's own session membership, so peer- and session-scoped keys are rejected +with `401`. + + +## Membership Changes Copy, They Don't Re-Derive + +A session added to a scope while empty needs nothing special: messages sent +after the change flow into the scope through the normal deriver fan-out. + +A session that **already has messages** is handled retroactively by a background +job rather than by re-running the LLM over its history: adding it copies the +session's existing `explicit` conclusions into the scope, and removing it +retracts that session's contributions — including conclusions derived from them. +Copying rather than re-deriving is why membership changes are cheap and +deterministic — and why they are also **asynchronous**. It also means a freshly +backfilled scope starts at explicit depth and accrues deeper reasoning through +subsequent dreams. + +Poll `status()` to tell "the scope hasn't caught up yet" apart from "the scope +has caught up and there is genuinely nothing to recall": + + +```python Python +therapy.add_sessions(["old-session-with-history"]) + +status = therapy.status() +# {"old-session-with-history": {"state": "pending", "updated_at": "..."}} +# → later: {"state": "completed", "docs_copied": 42, "updated_at": "..."} +``` + +```typescript TypeScript +await therapy.addSessions(["old-session-with-history"]); + +const status = await therapy.status(); +// { "old-session-with-history": { state: "pending", updatedAt: "..." } } +``` + +```bash REST +curl "$HONCHO_URL/v3/workspaces/my-app/scopes/therapy/status" \ + -H "Authorization: Bearer $HONCHO_API_KEY" +``` + + +`state` is `pending`, `completed`, or `failed`; `docs_copied` appears once a +backfill completes. Only sessions that have had a backfill enqueued appear, so an +empty result means none have — not that the scope is empty. + +## Reading Through a Scope + +`scope` is accepted on these surfaces: + +| Surface | Accepts | Notes | +|---------|---------|-------| +| [`peer.chat()`](/v3/documentation/features/chat) | one scope or a list | Confines both conclusion recall and the messages the agent reads | +| `peer.representation()` | one scope or a list | Confines conclusion recall | +| [`session.context()`](/v3/documentation/features/get-context) | one scope only | Perspective source for `peer_target`'s representation and card. Requires `peer_target`; mutually exclusive with `peer_perspective` | +| `honcho.search()` | one scope only | Restricts message search to the scope's member sessions | +| `honcho.chat()` | one scope or a list | Always the allowlist arm — even a single name. There is no observer to swap | + + +```python Python +# Chat — answered only from the therapy sessions +answer = user.chat("What is stressing them out?", scope="therapy") + +# Representation +rep = user.representation(scope="therapy") + +# Session context, using the scope as the perspective source +ctx = session.context(peer_target="user-123", scope="therapy") + +# Message search, restricted to the scope's sessions +messages = honcho.search("insomnia", scope="therapy") +``` + +```typescript TypeScript +// Chat — answered only from the therapy sessions +const answer = await user.chat("What is stressing them out?", { + scope: "therapy", +}); + +// Representation +const rep = await user.representation({ scope: "therapy" }); + +// Session context, using the scope as the perspective source +const ctx = await session.context({ + peerTarget: "user-123", + scope: "therapy", +}); + +// Message search, restricted to the scope's sessions +const messages = await honcho.search("insomnia", { scope: "therapy" }); +``` + +```bash REST +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 is stressing them out?", "scope": "therapy"}' +``` + + +### Rules + +`scope` is mutually exclusive with `filters`, `sessions`, and `session` / +`session_id` — and on session context, with `peer_perspective` (where it also +requires `peer_target`). Like the session allowlist, it **fails closed**: a +contradiction is rejected with a `422` rather than silently widened, a scope +with no member sessions recalls nothing, and an empty list (`scope=[]`) is +rejected rather than treated as "no boundary". Per-surface caps and error +shapes are in the [API reference](/v3/api-reference/endpoint/scopes/get-or-create-scope). + +## Provenance, Not Topic + +A scope is defined by **where a fact was said**, not what it is about. + +If a user mentions a therapy detail in a billing session, that conclusion is +formed from the billing session and lands in the `billing` scope. Querying +`scope="therapy"` will not find it, and querying `scope="billing"` will. + + +Scopes give you provenance-based privacy, not topic-based privacy. If you need +"no clinical content in the billing assistant's answers" regardless of where it +was said, that is content classification and has to be enforced above Honcho — +by controlling what reaches which session in the first place, or by filtering +the answer. + + +Design accordingly: keep the session boundary aligned with the confidentiality +boundary you actually care about, since that session boundary is the one scopes +can enforce. + +## Guardrails + +A few behaviors follow from how scopes are built: + +- **The `scope.` prefix is reserved.** Creating a peer, or adding a peer to a + session, with a `scope.`-prefixed name is rejected. +- **List scopes through the scopes surface.** `honcho.scopes()` / + `POST /scopes/list` returns unprefixed ids. Peer listings hide scopes by + default; `kind="scope"` on `POST /peers/list` returns the backing peers named + `scope.`, and `kind="all"` includes both regular peers and those backing + peers. +- **A scope can't be observed.** No representation is formed *of* a scope, so a + scope is rejected in any `target` / observed position, including as a dream + target. +- **Membership is managed only through the scopes surface.** The session + add-peers, set-peers, and remove-peers routes reject scope names and point you + at `/scopes/{scope_id}/sessions` or the `scopes` field on session create. + +If you want the exact mechanics for scopes, read: [`src/routers/scopes.py`](https://github.com/plastic-labs/honcho/blob/main/src/routers/scopes.py), +[`src/crud/scope.py`](https://github.com/plastic-labs/honcho/blob/main/src/crud/scope.py), +and [`src/deriver/scope_backfill.py`](https://github.com/plastic-labs/honcho/blob/main/src/deriver/scope_backfill.py). + +## Limits + +| Limit | Value | +|-------|-------| +| Scope ID length | 506 characters | +| Scope ID charset | `^[a-zA-Z0-9_-]+$` | +| Sessions per membership call | 100 | +| Scopes in one `scope` read option | 100 | +| Scopes on session create | 100 | +| Sessions in a resolved allowlist | 1,000 | + +Full request and response shapes are in the +[API reference](/v3/api-reference/endpoint/scopes/get-or-create-scope). diff --git a/docs/v3/documentation/features/advanced/search.mdx b/docs/v3/documentation/features/advanced/search.mdx index a95fff15..cf40648c 100644 --- a/docs/v3/documentation/features/advanced/search.mdx +++ b/docs/v3/documentation/features/advanced/search.mdx @@ -49,6 +49,20 @@ import { Honcho } from "@honcho-ai/sdk"; ```
+Pass `scope` on workspace search to restrict matches to that +[scope](/v3/documentation/features/advanced/scopes)'s member sessions. A scope +with no members returns nothing. + + +```python Python +results = honcho.search("budget planning", scope="therapy") +``` + +```typescript TypeScript +const results = await honcho.search("budget planning", { scope: "therapy" }); +``` + + ### Session Search Search within a specific session's conversation history: diff --git a/docs/v3/documentation/features/advanced/using-filters.mdx b/docs/v3/documentation/features/advanced/using-filters.mdx index 4a1eefec..e28096ab 100644 --- a/docs/v3/documentation/features/advanced/using-filters.mdx +++ b/docs/v3/documentation/features/advanced/using-filters.mdx @@ -727,7 +727,7 @@ messages = session.messages(filters={ ### Filtering Conclusions -Conclusions are scoped to an observer/observed peer pair (accessed via +Conclusions belong to an observer/observed peer pair (accessed via `peer.conclusions` for self-conclusions or `peer.conclusions_of(target)` for conclusions about another peer). The observer and observed are filled in automatically by the scope, so the `filters` you pass add to them. @@ -843,7 +843,7 @@ 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` +This is how you restrict 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: @@ -875,10 +875,34 @@ curl -X POST "$HONCHO_URL/v3/workspaces/my-app/peers/user-123/representation" \ ```
+Both SDKs expose this as a `sessions` option, which goes on the wire as the +`filters` body above: + + +```python Python +answer = user.chat("What did the user ask about billing?", + sessions=["support-chat-1", "support-chat-2"]) + +rep = user.representation(sessions=["support-chat-1", "support-chat-2"]) +``` + +```typescript TypeScript +const answer = await user.chat("What did the user ask about billing?", { + sessions: ["support-chat-1", "support-chat-2"], +}); + +const rep = await user.representation({ + sessions: ["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. +If the same set of sessions is a boundary you reuse, name it: a +[scope](/v3/documentation/features/advanced/scopes) is a persistent version of +this allowlist, and querying a single scope recalls at full depth rather than +`explicit`-only. `sessions` is the right tool when the set is decided +per-request. ### Rules @@ -907,7 +931,7 @@ can only narrow. ### What Changes Under an Allowlist -Scoping recall by session narrows what the reasoning agent can draw on: +Restricting 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 diff --git a/docs/v3/documentation/features/chat.mdx b/docs/v3/documentation/features/chat.mdx index b6e7e96c..c83a0ac2 100644 --- a/docs/v3/documentation/features/chat.mdx +++ b/docs/v3/documentation/features/chat.mdx @@ -110,11 +110,17 @@ const answer = await peer.chat("What did the user ask about?", { session: sessio ```
-To scope a request to a *set* of sessions, use the session allowlist — a +To restrict 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. +Pass `scope="therapy"` to answer from that [scope](/v3/documentation/features/advanced/scopes)'s +own representation of the peer. A list (`scope=["therapy", "intake"]`) is an +allowlist of those scopes' sessions, not named-scope depth. +`honcho.chat(scope=)` is always the allowlist arm, even with one name. Details +are on the [scopes page](/v3/documentation/features/advanced/scopes#the-two-arms). + ## 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/features/get-context.mdx b/docs/v3/documentation/features/get-context.mdx index 60ff45bc..1fa9fec1 100644 --- a/docs/v3/documentation/features/get-context.mdx +++ b/docs/v3/documentation/features/get-context.mdx @@ -99,7 +99,7 @@ context = session.context(summary=False, tokens=2000) ### Peer Representation in Context -You can include a peer's [representation](/v3/documentation/core-concepts/representation) and peer card in the context by specifying `peer_target`. This is useful for providing the LLM with knowledge about a specific peer. +You can include a peer's [representation](/v3/documentation/core-concepts/representation) and peer card in the context by specifying `peer_target`. This is useful for providing the LLM with knowledge about a specific peer. Pass `scope` with `peer_target` to use a [named scope](/v3/documentation/features/advanced/scopes) as the perspective source (`scope` is mutually exclusive with `peer_perspective` and requires a workspace-level or admin-level key). ```python Python @@ -119,6 +119,14 @@ context = session.context( peer_target="user-123", peer_perspective="assistant" # From assistant's viewpoint ) + +# Or use a named scope as the perspective source (requires peer_target; +# mutually exclusive with peer_perspective) +context = session.context( + tokens=2000, + peer_target="user-123", + scope="therapy", +) ``` ```typescript TypeScript @@ -139,6 +147,14 @@ context = session.context( peerTarget: "user-123", peerPerspective: "assistant" // From assistant's viewpoint }); + + // Or use a named scope as the perspective source (requires peerTarget; + // mutually exclusive with peerPerspective) + const scopedContext = await session.context({ + tokens: 2000, + peerTarget: "user-123", + scope: "therapy", + }); })(); ``` @@ -211,6 +227,7 @@ context = session.context( | `tokens` | `int` | Maximum tokens to include | | `peer_target` | `str` | Peer ID to include representation for | | `peer_perspective` | `str` | Peer ID for perspective (requires peer_target) | +| `scope` | `str` | Named scope as the perspective source for `peer_target`'s representation and card. Requires `peer_target` and a workspace-level or admin-level key; mutually exclusive with `peer_perspective`. See [Scopes](/v3/documentation/features/advanced/scopes) | | `search_query` | `str` | Query for semantic search (requires peer_target) | | `limit_to_session` | `bool` | Limit to session conclusions only | | `search_top_k` | `int` | Semantic search results to include (1-100) | diff --git a/docs/v3/documentation/reference/cli.mdx b/docs/v3/documentation/reference/cli.mdx index 9686199d..74b5fb34 100644 --- a/docs/v3/documentation/reference/cli.mdx +++ b/docs/v3/documentation/reference/cli.mdx @@ -89,14 +89,14 @@ and are stored under `oauth` without deleting a shared `apiKey`. } ``` -Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-s` +Per-command targeting (workspace / peer / session) is handled via `-w` / `-p` / `-s` flags or `HONCHO_*` env vars. **Not** persisted as CLI defaults. This is deliberate: every invocation is explicit about what it operates on. ### Runtime overrides -Workspace, peer, and session scoping are **per-command only** — pass flags or +Workspace, peer, and session targeting are **per-command only** — pass flags or `HONCHO_*` env vars on every invocation. ```bash diff --git a/docs/v3/guides/community/pi-honcho-memory.mdx b/docs/v3/guides/community/pi-honcho-memory.mdx index 7974a94b..6ef1c5e0 100644 --- a/docs/v3/guides/community/pi-honcho-memory.mdx +++ b/docs/v3/guides/community/pi-honcho-memory.mdx @@ -23,7 +23,7 @@ The Honcho plugin is a community integration. See the [plugin README](https://gi ## How It Works -The extension hooks into pi's extension system. It automatically syncs user and assistant messages to Honcho after each agent response, injects cached user profile and project context into the system prompt with zero network latency, and exposes LLM tools (`honcho_search`, `honcho_chat`, `honcho_remember`) for active memory operations. Session scoping is configurable — memory can be shared per repo, per git branch, or per directory. If Honcho is unavailable, pi continues working normally. +The extension hooks into pi's extension system. It automatically syncs user and assistant messages to Honcho after each agent response, injects cached user profile and project context into the system prompt with zero network latency, and exposes LLM tools (`honcho_search`, `honcho_chat`, `honcho_remember`) for active memory operations. Session mapping is configurable — memory can be shared per repo, per git branch, or per directory. If Honcho is unavailable, pi continues working normally. ## Next Steps diff --git a/docs/v3/guides/integrations/paperclip.mdx b/docs/v3/guides/integrations/paperclip.mdx index 3b2aa156..bbda60f3 100644 --- a/docs/v3/guides/integrations/paperclip.mdx +++ b/docs/v3/guides/integrations/paperclip.mdx @@ -61,11 +61,11 @@ In practice, that means agent peers can both be observed by Honcho and form repr ## How It Works -### Identity And Scope +### Identity And Mapping The integration breaks down into four parts: -- **Identity and scope** - each Paperclip company maps to a Honcho workspace, agents and human actors map to peers, and issues map to sessions. +- **Identity and mapping** - each Paperclip company maps to a Honcho workspace, agents and human actors map to peers, and issues map to sessions. - **What gets copied into Honcho** - issue comments and document revisions sync into Honcho, with document content sectioned and normalized message content capped before ingestion. - **What operators get** - operators get a plugin settings page, migration preview/status data, including a per-issue migration mapping preview, repair tools, and an issue-level `Memory` tab. - **What agents get** - agents get Honcho retrieval and peer-chat tools inside Paperclip. @@ -130,7 +130,7 @@ The plugin registers the following Honcho tools for Paperclip agents: Review how workspaces, peers, and sessions fit together. - + Review how `observe_me` and `observe_others` change what peers can model. diff --git a/docs/v3/guides/recipes/unified-memory-setup.mdx b/docs/v3/guides/recipes/unified-memory-setup.mdx index ea921cc3..14ad61a8 100644 --- a/docs/v3/guides/recipes/unified-memory-setup.mdx +++ b/docs/v3/guides/recipes/unified-memory-setup.mdx @@ -109,8 +109,9 @@ and `aiPeer` there. See the [Hermes guide](/v3/guides/integrations/hermes) for t A scheduled job feeds external data (emails, meeting notes, CRM records) into Honcho. Attribute the messages to the peer the data is *about* — not to an agent — and group -them into a session. **How you scope that session is the main decision here**, because -it controls when Honcho reasons over the data (more on that below). +them into a session. Match the session to how you want that import's local context +to accumulate: a per-run session like `email-import-{date}`, or one ongoing +per-source session like `email-import-gmail`. ```python from datetime import datetime, timezone @@ -131,18 +132,6 @@ for i in range(0, len(messages), 100): session.add_messages(messages[i:i + 100]) ``` -Honcho batches reasoning until a peer accumulates ~1,000 tokens *within a single session*, -with a default age-based flush for quiet tails -([token batching](/v3/documentation/core-concepts/reasoning#token-batching)). Scope the -session to the volume you ingest: - -- **High-volume runs** (a day of emails, a CRM export) clear the threshold easily — a - per-run session like `email-import-{date}` is fine. -- **Low-volume or trickle imports** (a few short records at a time) should append to - one **ongoing per-source session** (e.g. `email-import-gmail`), so content - accumulates across runs instead of fragmenting into thin sessions that each flush - later with little context. - The [Gmail](/v3/guides/gmail) and [Granola](/v3/guides/granola) guides are related import examples. diff --git a/docs/v3/openapi.json b/docs/v3/openapi.json index 43ecc6a4..b2bc0adf 100644 --- a/docs/v3/openapi.json +++ b/docs/v3/openapi.json @@ -1574,7 +1574,7 @@ "get": { "tags": ["sessions"], "summary": "Get Peer Config", - "description": "Get the configuration for a Peer in a Session.\n\nMember-read lets a peer-scoped key reach this route, but a peer may only\nread its own per-session config — not a co-member's. Workspace/admin and\nsession-scoped tokens (which already span the whole session) are unaffected.", + "description": "Get the configuration for a Peer in a Session.\n\nMember-read lets a peer-scoped key reach this route, but a peer may only\nread its own per-session config \u2014 not a co-member's. Workspace/admin and\nsession-scoped tokens (which already span the whole session) are unaffected.", "operationId": "get_peer_config_v3_workspaces__workspace_id__sessions__session_id__peers__peer_id__config_get", "security": [{ "HTTPBearer": [] }], "parameters": [ @@ -2234,6 +2234,343 @@ } } }, + "/v3/workspaces/{workspace_id}/scopes": { + "post": { + "tags": ["scopes"], + "summary": "Get Or Create Scope", + "description": "Get a Scope by ID or create a new Scope with the given ID.\n\nReturns 201 when the scope is created and 200 when it already exists.\nA pre-existing peer occupying the scope's reserved internal name is never\nadopted; that conflict returns 409.", + "operationId": "get_or_create_scope_v3_workspaces__workspace_id__scopes_post", + "security": [{ "HTTPBearer": [] }], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Workspace Id" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScopeCreate", + "description": "Scope creation parameters" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/Scope" } + } + } + }, + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/Scope" } + } + } + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/v3/workspaces/{workspace_id}/scopes/list": { + "post": { + "tags": ["scopes"], + "summary": "Get Scopes", + "description": "Get all Scopes for a Workspace. Results are paginated.", + "operationId": "get_scopes_v3_workspaces__workspace_id__scopes_list_post", + "security": [{ "HTTPBearer": [] }], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Workspace Id" } + }, + { + "name": "reverse", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "description": "Whether to reverse the order of results", + "default": false, + "title": "Reverse" + }, + "description": "Whether to reverse the order of results" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/Page_Scope_" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/v3/workspaces/{workspace_id}/scopes/{scope_id}": { + "get": { + "tags": ["scopes"], + "summary": "Get Scope", + "description": "Get a single Scope by ID.", + "operationId": "get_scope_v3_workspaces__workspace_id__scopes__scope_id__get", + "security": [{ "HTTPBearer": [] }], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Workspace Id" } + }, + { + "name": "scope_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Scope Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/Scope" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/v3/workspaces/{workspace_id}/scopes/{scope_id}/sessions": { + "post": { + "tags": ["scopes"], + "summary": "Add Sessions To Scope", + "description": "Add Sessions to a Scope.\n\nAll named sessions must already exist (404 otherwise). Adding a session that\nis already a member is a no-op. List the resulting membership with\n`POST /scopes/{scope_id}/sessions/list`.\n\nNote: any added session that already has messages triggers an asynchronous\nbackfill-by-copy of its existing documents into the scope; track progress\nvia ``GET /scopes/{scope_id}/status``.", + "operationId": "add_sessions_to_scope_v3_workspaces__workspace_id__scopes__scope_id__sessions_post", + "security": [{ "HTTPBearer": [] }], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Workspace Id" } + }, + { + "name": "scope_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Scope Id" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScopeSessionsAdd", + "description": "IDs of the sessions to add to the scope" + } + } + } + }, + "responses": { + "204": { "description": "Successful Response" }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/v3/workspaces/{workspace_id}/scopes/{scope_id}/sessions/{session_id}": { + "delete": { + "tags": ["scopes"], + "summary": "Remove Session From Scope", + "description": "Remove a Session from a Scope.\n\nNote: documents copied/derived while the session was a member are\nreconciled asynchronously \u2014 the session's explicit copies are soft-deleted\nfrom the scope, dependent derived documents follow (fail-closed), and the\nscope's card is rebuilt from the remaining evidence.", + "operationId": "remove_session_from_scope_v3_workspaces__workspace_id__scopes__scope_id__sessions__session_id__delete", + "security": [{ "HTTPBearer": [] }], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Workspace Id" } + }, + { + "name": "scope_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Scope Id" } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Session Id" } + } + ], + "responses": { + "204": { "description": "Successful Response" }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/v3/workspaces/{workspace_id}/scopes/{scope_id}/sessions/list": { + "post": { + "tags": ["scopes"], + "summary": "Get Scope Sessions", + "description": "Get the Sessions that are members of a Scope, paginated.\n\nOrdered by how long each session has been a member: longest-standing member\nfirst, or most recently added first when `reverse` is true.", + "operationId": "get_scope_sessions_v3_workspaces__workspace_id__scopes__scope_id__sessions_list_post", + "security": [{ "HTTPBearer": [] }], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Workspace Id" } + }, + { + "name": "scope_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Scope Id" } + }, + { + "name": "reverse", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "description": "Whether to reverse the order of results", + "default": false, + "title": "Reverse" + }, + "description": "Whether to reverse the order of results" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/Page_Session_" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/v3/workspaces/{workspace_id}/scopes/{scope_id}/status": { + "get": { + "tags": ["scopes"], + "summary": "Get Scope Status", + "description": "Get the backfill/reconciliation job status for a Scope.\n\nReturns a per-session map of the backfill job state (pending / completed /\nfailed) with the number of documents copied once complete. Empty when no\nbackfill has ever been enqueued for the scope.", + "operationId": "get_scope_status_v3_workspaces__workspace_id__scopes__scope_id__status_get", + "security": [{ "HTTPBearer": [] }], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Workspace Id" } + }, + { + "name": "scope_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Scope Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ScopeStatus" } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, "/v3/workspaces/{workspace_id}/conclusions": { "post": { "tags": ["conclusions"], @@ -2917,6 +3254,20 @@ "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." }, + "scope": { + "anyOf": [ + { "type": "string" }, + { + "items": { "type": "string" }, + "type": "array", + "maxItems": 100, + "minItems": 1 + }, + { "type": "null" } + ], + "title": "Scope", + "description": "Optional (unprefixed) scope name(s) to confine recall. A single scope answers from the scope's own representation of the target peer: conclusion recall is confined to what the scope observed and message recall to the scope's member sessions. A list of scopes restricts recall to the union of the scopes' member sessions (explicit allowlist, fail-closed: an empty union recalls nothing). Mutually exclusive with `filters` and `session_id`. Requires a workspace- or admin-level key." + }, "target": { "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "Target", @@ -3174,6 +3525,22 @@ "required": ["items", "total", "page", "size", "pages"], "title": "Page[Peer]" }, + "Page_Scope_": { + "properties": { + "items": { + "items": { "$ref": "#/components/schemas/Scope" }, + "type": "array", + "title": "Items" + }, + "total": { "type": "integer", "minimum": 0.0, "title": "Total" }, + "page": { "type": "integer", "minimum": 1.0, "title": "Page" }, + "size": { "type": "integer", "minimum": 1.0, "title": "Size" }, + "pages": { "type": "integer", "minimum": 0.0, "title": "Pages" } + }, + "type": "object", + "required": ["items", "total", "page", "size", "pages"], + "title": "Page[Scope]" + }, "Page_Session_": { "properties": { "items": { @@ -3356,6 +3723,14 @@ { "type": "null" } ], "title": "Filters" + }, + "kind": { + "anyOf": [ + { "type": "string", "enum": ["scope", "all"] }, + { "type": "null" } + ], + "title": "Kind", + "description": "Which kinds of peers to list. Omitted (default): regular peers only (scope peers are excluded). 'scope': scope peers only. 'all': every peer." } }, "type": "object", @@ -3376,6 +3751,20 @@ "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." }, + "scope": { + "anyOf": [ + { "type": "string" }, + { + "items": { "type": "string" }, + "type": "array", + "maxItems": 100, + "minItems": 1 + }, + { "type": "null" } + ], + "title": "Scope", + "description": "Optional (unprefixed) scope name(s) to confine the representation. A single scope reads the scope's own representation of the target peer, formed only from the scope's member sessions. A list of scopes restricts the representation to conclusions from the union of the scopes' member sessions (explicit allowlist, fail-closed: an empty union yields an empty representation). Mutually exclusive with `filters` and `session_id`. Requires a workspace- or admin-level key." + }, "target": { "anyOf": [{ "type": "string" }, { "type": "null" }], "title": "Target", @@ -3542,6 +3931,72 @@ "required": ["observer", "dream_type"], "title": "ScheduleDreamRequest" }, + "Scope": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "metadata": { + "additionalProperties": true, + "type": "object", + "title": "Metadata" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + } + }, + "type": "object", + "required": ["id", "created_at"], + "title": "Scope", + "description": "Scope response \u2014 external view of the peer backing a scope.\n\nThe ``id`` is the unprefixed scope name; the reserved peer-name prefix is\nan internal implementation detail and never surfaces here." + }, + "ScopeCreate": { + "properties": { + "id": { "type": "string", "minLength": 1, "title": "Id" }, + "metadata": { + "anyOf": [ + { "additionalProperties": true, "type": "object" }, + { "type": "null" } + ], + "title": "Metadata" + } + }, + "type": "object", + "required": ["id"], + "title": "ScopeCreate", + "description": "Schema for creating (or getting) a scope by its unprefixed name." + }, + "ScopeSessionsAdd": { + "properties": { + "session_ids": { + "items": { "type": "string" }, + "type": "array", + "maxItems": 100, + "minItems": 1, + "title": "Session Ids", + "description": "IDs of existing sessions to add to the scope" + } + }, + "type": "object", + "required": ["session_ids"], + "title": "ScopeSessionsAdd", + "description": "Schema for adding sessions to a scope." + }, + "ScopeStatus": { + "properties": { + "backfill_status": { + "additionalProperties": { + "additionalProperties": true, + "type": "object" + }, + "type": "object", + "title": "Backfill Status" + } + }, + "type": "object", + "title": "ScopeStatus", + "description": "Per-session backfill/reconciliation job status for a scope.\n\n``backfill_status`` maps each session that has had a backfill enqueued to\nits current job state: ``{state, updated_at[, docs_copied]}`` where\n``state`` is ``pending``/``completed``/``failed`` and ``docs_copied`` is\npresent once a backfill completes." + }, "Session": { "properties": { "id": { "type": "string", "title": "Id" }, @@ -3669,6 +4124,18 @@ { "$ref": "#/components/schemas/SessionConfiguration" }, { "type": "null" } ] + }, + "scopes": { + "anyOf": [ + { + "items": { "type": "string" }, + "type": "array", + "maxItems": 100 + }, + { "type": "null" } + ], + "title": "Scopes", + "description": "Optional list of (unprefixed) scope names to add this session to. Each scope is created if it does not exist yet. If the session already has messages, its existing documents are backfilled into the scope asynchronously." } }, "type": "object",