Snapshots
A snapshot is an immutable record of retrieved chunks, kept for replay and audit. Snapshots, and frozen lenses, are write-once in the database: an update or delete is refused during the organisation's life. Private (personal) chunks are never included.
| Method | Path | What |
|---|---|---|
POST | /api/v1/context/snapshots | Create a snapshot (201): {"spaceId", "chunkIds": [...], "lensId"?} |
GET | /api/v1/context/snapshots | The snapshots whose space the caller may read, newest first, at most 100; summaries only, never chunk content |
GET | /api/v1/context/snapshots/{id} | One snapshot, filtered for the reader (context:space:search on its spaces) |
- A snapshot made from a lens is exactly as visible as the lens: unsharing the lens closes its snapshots.
- Viewing a snapshot re-scans its stored content with the stricter of the captured classification and the source's current one.
- A person may create 200 snapshots an hour (
KEYSTONE_CONTEXT_SNAPSHOTS_PER_HOUR), freezing a lens included.
Handoffs
A handoff passes working context, as a snapshot, from one person to others, usually the reviewers of a pull request.
The reviewer flow
The author's Editor creates a snapshot of the context it used.
It creates a handoff with the pull request URL and the reviewers as recipients:
POST /api/v1/context/handoffs Content-Type: application/json {"snapshotId": "<id>", "workItemKind": "pr", "workItemId": "42", "workItemUrl": "https://github.com/acme/api/pull/42", "message": "Context I used for the refund changes", "recipients": [{"kind": "user", "id": "<reviewer id>"}, {"kind": "team", "id": "<team id>"}]}workItemKindispr,issueortask;workItemIdis required (at most 512 characters);messageis at most 4000 characters. The message and the work item reference are scanned with the organisation's DLP patterns, and a finding refuses the handoff without echoing it.The reviewer's Editor finds it with
GET /api/v1/context/handoffs?workItemUrl=<the PR URL as the reviewer has it>or the inboxGET /api/v1/context/handoffs/inbox.GET /api/v1/context/handoffs/{id}returns the handoff with the author's snapshot, filtered for the reviewer: chunks of spaces the reviewer may not search are withheld and DLP is applied.
Who sees a handoff
- With recipients (up to 50 users, groups and teams of the organisation): the author, the recipients (groups include nested groups) and administrators.
- Without recipients: everyone who may read the snapshot's space.
In both cases the reader must also be able to read the snapshot's space, and the snapshot is shown through the reader's own authorisation.
Finding a handoff by work item
The URL is matched exactly on a normalised form: scheme and host in lower case, default port, fragment and tracking parameters removed, and the path of a recognised forge cut at the item number. So .../pull/42/files#diff and .../pull/42?utm_x=1 both find the handoff recorded for .../pull/42. Recognised forms: GitHub pull and issues, GitLab merge_requests and issues, Bitbucket pull-requests, Azure DevOps pullrequest and _workitems. A URL that is not http(s), carries credentials or exceeds 2048 characters is 422.
Lists take limit (1 to 100, default 50) and cursor; the response body is the array and the next cursor is in the X-Next-Cursor header.
The collaboration policy governs handoffs: the handoffs switch, the classification ceiling of the snapshot's spaces, and the scope and number of recipients. A handoff above the policy's approval line is refused with approval_required (handoffs have no pending form). A recipient that no longer complies with the author's policy is closed at read time and listed with its violations, never deleted.
Pair sessions
A pair session is live, shared context: one lens that every participant retrieves with, and a recording of the retrievals made in it.
| Method | Path | What |
|---|---|---|
POST | /api/v1/context/sessions | Start a session (201): {"spaceId", "lensId"?, "recorded", "inviteOnly"?}; inviteOnly: true admits only invited people and link holders |
GET | /api/v1/context/sessions | Sessions the caller hosts or joined (?state=active|ended) |
GET | /api/v1/context/sessions/invited | Live sessions the caller is invited to and has not joined |
GET | /api/v1/context/sessions/{id} | One session (participants only) |
POST | /api/v1/context/sessions/{id}/invitations | Host: invite targets and/or create a join link |
GET | /api/v1/context/sessions/{id}/invitations | Host: invitations with expired, revoked, effective and violations |
DELETE | /api/v1/context/sessions/{id}/invitations/{invitationId} | Host: revoke at once; people who already joined stay |
POST | /api/v1/context/sessions/{id}/join | Join; optional {"token": "<join token>"} |
POST | /api/v1/context/sessions/{id}/leave | Leave (the host ends the session instead) |
PUT | /api/v1/context/sessions/{id}/lens | Change the session's lens: {"lensId": "..."} ("" removes it) |
POST | /api/v1/context/sessions/{id}/end | End the session |
GET | /api/v1/context/sessions/{id}/recording | The recorded queries |
Invitations and join links
POST /api/v1/context/sessions/{id}/invitations
{"targets": [{"kind": "group", "id": "<group id>"}], "link": true, "ttlSeconds": 3600}
- Up to 50 targets per request;
ttlSecondsdefaults to 86400 (60 seconds to 7 days). Inviting a target again renews it. - With
link: truethe answer carriesjoinTokenonce. Only its SHA-256 is stored, and it works only for that session in that organisation. - Whoever joins must still be allowed to read the session's space. A session that is not invite-only can be joined by anyone who may read its space.
- Without a live invitation, joining an invite-only session is
403 context.session.invite_required.
Inside a session
- A participant uses the session's lens within the session without the lens being shared with them; outside the session it stays invisible to them.
- Changing the lens notifies the others (
session.lens_changed); when the lens owner adds or removes a source, participants hearsession.sources_changedon the real-time feed. - In the recording, other participants' chunks are shown only for spaces the reader may read now (
chunksWithheldcounts the rest), and their query text passes the DLP gate of the session space (queryRedacted,queryWithheld). - Ending and leaving a session stay possible when the policy switches pair sessions off.
The host cannot remove a participant, and revoking an invitation does not remove someone who already joined through it.