Scopes Phase 2b: scope option on chat, representation, context, and search#897
Open
VVoruganti wants to merge 7 commits into
Open
Scopes Phase 2b: scope option on chat, representation, context, and search#897VVoruganti wants to merge 7 commits into
scope option on chat, representation, context, and search#897VVoruganti wants to merge 7 commits into
Conversation
session_name was only applied to the recent-documents query in RepresentationManager; the semantic and most-derived paths ignored it, so limit_to_session leaked cross-session conclusions into perspectives. - Thread a session allowlist (session_names) uniformly through all three query paths; pushed down to pgvector and external vector stores - Accept a list so the upcoming session-allowlist API reuses this path - Fail closed on an empty allowlist (downstream stores drop empty IN clauses, which would silently widen scope) Fixes DEV-1994 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
{"session_id": ["s1", "s2"]} is now shorthand for
{"session_id": {"in": [...]}} on regular columns, generically
(peer_id, etc.). JSONB metadata columns are excluded — a bare list
there keeps JSONB containment semantics, unchanged.
Previously a bare list on a regular column compiled to a type-mismatched
equality that matched nothing, so this is strictly additive.
Also translates the same shape in the turbopuffer/lancedb filter
builders, and fixes lancedb dropping empty IN clauses (fail-open) —
an empty membership list now emits an always-false condition.
Groundwork for DEV-1995 (session allowlist via the existing filters
DSL, no new API params)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a constrained 'filters' body to peer.chat and /representation —
the same DSL search and conclusions already accept, supporting only the
session_id key (a session id, a bare list, or {"in": [...]}).
Unsupported keys and shapes are rejected with 422, never silently
ignored. Composes with session_id (must be included in the allowlist
when both are given). Capped at 1,000 sessions per request.
Enforcement is uniform at every recall chokepoint, fail-closed:
- dialectic prefetch + search_memory: conclusion recall restricted to
the allowlist; dream docs (session_name IS NULL) excluded
- message tools (search/grep/date-range/temporal/context/history):
strict intersection of allowlist and observer session membership
- get_reasoning_chain: unavailable under an allowlist (chains traverse
provenance across sessions and cannot be scoped without leaking)
- empty allowlist short-circuits to empty results everywhere
Auth: workspace keys pass the allowlist as-given; peer-scoped JWTs must
be a member of every allowlisted session (403 otherwise), mirroring the
existing single-session check.
Fixes DEV-1995
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # src/routers/peers.py
Add resolve_scope_peers (crud/scope.py) mapping unprefixed scope names to their backing scope peers, 404 when missing and 422 when a non-scope peer squats the reserved name. Add the `scope` option to DialecticOptions and PeerRepresentationGet, and a WorkspaceMessageSearchOptions with `scope`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chat and representation: a single scope swaps the observer to the scope
peer (recall confined to the scoped collection and the scope's sessions by
existing observer semantics); a scope list resolves to the union of member
sessions and rides the DEV-1995 dynamic allowlist arm with the path peer as
observer. `scope` is mutually exclusive with `filters`/`session_id` (422)
and requires a workspace/admin key (403 for peer-scoped JWTs).
Session context: `scope` swaps the perspective source for both the working
representation and the peer-card fetch. Workspace search: `scope` injects
the scope's session set into the message-search filter (empty scope -> no
results). Close the carry-over guardrail gap: scope peers are rejected as
peer_target/peer_perspective in session context and as the peer/target in
GET /peers/{id}/context.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validation (404/422/403), single-scope observer swap vs. union allowlist, scoped representation and session-context (scoped collection + scoped card), workspace search restricted to a scope's sessions, and guardrail closure for scope peers on the peer/session context surfaces. Patch the workspaces tracked_db import site so scope resolution reads the per-test database. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the user-facing
scoperead option on top of the scopes facade (Phase 2a) and the session allowlist (DEV-1995).Design
Observer-swap resolution. A scope is already an observer peer under the hood, so a single
scopeneeds no new recall plumbing:POST /peers/{id}/chat) and representation (POST /peers/{id}/representation):scope: "x"runs the read withobserver = scope__x,observed = target ?? peer_id. Conclusion recall is confined to the(scope, observed)collection and message tools to the scope's session membership by existing observer semantics. The chat preflight's card fetch (get_peer_card(observer=scope, observed=...)) automatically reads the scoped peer card.GET /sessions/{id}/context):scopeswaps the perspective source — the scope peer becomes the observer for both the working representation and the peer-card fetch;observed = peer_target. Requirespeer_target; mutually exclusive withpeer_perspective.POST /workspaces/{id}/search):scoperesolves to the scope's session set and injectsfilters["session_id"] = {"in": [...]}into the existing message-search filter path. An empty scope returns no results (fail-closed), andscope+ a user-suppliedsession_idfilter is 422.Union semantics.
scopeas a list (chat/representation) resolves every scope, takes the union of their member sessions, enforcesMAX_SESSION_ALLOWLIST_ENTRIESon the union (422), and executes via the DEV-1995 dynamic arm:observer = peer_id(not a scope peer) withsession_names = union. Explicit-only, fail-closed: an empty union recalls nothing.Resolution helper.
crud.resolve_scope_peers(db, workspace, names)maps unprefixed scope names to backing scope peers — 404ResourceNotFoundExceptionwhen a scope doesn't exist, 422ValidationExceptionwhen a non-scope peer squats the reserved name.Exclusions and auth (v1 kept small).
scope+filters→ 422;scope+session_id→ 422.scoperequires a workspace- or admin-level key: peer-scoped JWTs get 403 on chat/representation, and peer-/session-scoped keys get 403 on session-context (a scope's sessions may exceed the caller's own membership). Note: the codebase'sAuthenticationExceptionis 401, so these useAuthorizationException(403) — its first use insrc/.Guardrail-gap closure. Phase 2a only covered chat/representation targets. This PR also rejects scope peers (422) as
peer_target/peer_perspectiveon session-context and as the path peer ortargetonGET /peers/{id}/context.OpenAI-compat endpoint: none exists in the repo yet, so the
X-Honcho-Scopeheader is intentionally skipped.Tests
tests/routes/test_scope_reads.py(24 tests): 404/422/403 validation for all four routes, single-scope observer swap (scoped collection + scoped card, proven via seeded documents andset_peer_card), union allowlist over the global collection (proving the dynamic arm), workspace search restriction + fail-closed empty scope, and the guardrail closure. Plus one conftest line registeringsrc.routers.workspaces.tracked_dbfor the per-test DB.Local runs:
tests/routes/+tests/test_session_allowlist.py+tests/test_advanced_filters.py— 387 passed.ruff check/format,basedpyright(0 errors, 0 warnings), and pre-commit all clean.Fixes DEV-1998 (part of DEV-1970 Scopes RFC)
🤖 Generated with Claude Code