docs: AT Protocol messaging research (#80) #95

Closed
starsetbyte wants to merge 0 commits from feat/messaging-research into main
Owner

What

Research into AT Protocol messaging (chat.bsky.*) to determine feasibility and implementation path for adding DMs to Peregrine.

Docs

  • 01-fundamentals.md — Full endpoint inventory (23 chat.bsky.convo + 16 chat.bsky.group), data model, auth requirements, real-time polling strategy, known limitations
  • 02-competitive-landscape.md — Every client with DM support mapped, standalone projects, community protocol efforts, gap analysis
  • 03-implementation-path.md — 10-step plan from dependency addition through Compose UI, Room entities, polling, optimistic sends, nav integration
  • 04-decisions.md — 10 architecture decisions with rationale (scope, tech stack, phases, OAuth migration, group preparedness)

Key findings

  • 1:1 DMs are production-ready via chat.bsky.convo.* (23 endpoints)
  • Group chat lexicons exist (chat.bsky.group.*, 16 endpoints) but not live yet (Discussion #5095)
  • No native Android client has DM support — first-mover opportunity
  • Real-time is polling on getLog (60s intervals), not WebSocket — DMs are private
  • Requires atproto-proxy header routing to did:web:api.bsky.chat#bsky_chat
  • OAuth scope must include transition:chat.bsky

Phase A (next step): Infrastructure + read-only conversation list
Phase B: Message send, reactions, full chat UI
Phase C: Group chat + push notifications (when server-side lands)

Closes #80

## What Research into AT Protocol messaging (chat.bsky.*) to determine feasibility and implementation path for adding DMs to Peregrine. ## Docs - **01-fundamentals.md** — Full endpoint inventory (23 chat.bsky.convo + 16 chat.bsky.group), data model, auth requirements, real-time polling strategy, known limitations - **02-competitive-landscape.md** — Every client with DM support mapped, standalone projects, community protocol efforts, gap analysis - **03-implementation-path.md** — 10-step plan from dependency addition through Compose UI, Room entities, polling, optimistic sends, nav integration - **04-decisions.md** — 10 architecture decisions with rationale (scope, tech stack, phases, OAuth migration, group preparedness) ## Key findings - **1:1 DMs are production-ready** via `chat.bsky.convo.*` (23 endpoints) - **Group chat lexicons exist** (`chat.bsky.group.*`, 16 endpoints) but **not live** yet (Discussion #5095) - **No native Android client has DM support** — first-mover opportunity - **Real-time is polling** on `getLog` (60s intervals), not WebSocket — DMs are private - **Requires `atproto-proxy` header** routing to `did:web:api.bsky.chat#bsky_chat` - **OAuth scope** must include `transition:chat.bsky` ## Recommended approach **Phase A** (next step): Infrastructure + read-only conversation list **Phase B**: Message send, reactions, full chat UI **Phase C**: Group chat + push notifications (when server-side lands) Closes #80
Author
Owner

Review verdict: REQUEST CHANGES

The strategic conclusion is sound — Peregrine should pursue chat.bsky.*, use PDS proxying with did:web:api.bsky.chat#bsky_chat, and request transition:chat.bsky — but this PR is not mergeable or safe to implement from yet.

Blocking

  1. PR #95 currently has an empty diff and would merge as a no-op. Head 6632288 is already an ancestor of main; main committed the research and then reverted it in c31980e. Verified: the three-dot diff is 0 bytes and the left/right commit count is 2 0. Rebuild the branch from current main with a new commit reintroducing the reviewed docs.

  2. The atproto-kotlin examples use class names that do not exist. 03-implementation-path.md uses ChatBskyConvoListConvos.Response, ChatBskyConvoDefsMessageInput, and ChatBskyConvoSendMessage.Request. v9.7.5 generates package-scoped types such as io.github.kikin81.atproto.chat.bsky.convo.ListConvosResponse, MessageInput, SendMessageRequest, and GetLogResponse. The examples do not compile as written; add a dependency spike/compile proof.

  3. Group-chat status contradicts itself. 01 line 4 says group chats launched June 11, while lines 8 and 185–225 say they are not live. 02 says the official app and Skyscraper have full groups; 04 says group endpoints return 404. Discussion #5095 still asks for an API timetable. Pick one sourced status and remove unsupported claims. Current upstream has 17 JSON files under chat/bsky/group including defs.json, not 16; distinguish endpoints from total files.

  4. Phase A scope conflicts across documents. 03 puts ChatPoller/getLog in Phase B; 04 and the handoff require polling in Phase A. Resolve before task extraction. A read-only list can refresh with listConvos; incremental event processing is a separate larger slice.

  5. The proposed UI state violates the architecture. ChatUiState.Success contains LazyPagingItems<Conversation>. That is a Compose UI object created in a composable and must not live in ViewModel state. Expose Flow<PagingData<Conversation>> or a plain immutable list/state.

  6. The “group-prepared” Room design is not group-prepared. ConversationEntity hard-codes two members while also defining a normalized member table, creating two sources of truth and failing for arbitrary groups. Use the member table as canonical and derive the direct-conversation counterpart.

Important

  1. No Room migration plan despite current schema v12. Define MIGRATION_12_13, register it in ALL_MIGRATIONS, commit schema 13 JSON, and execute a MigrationTestHelper device test.

  2. Reaction storage is malformed. reactionValue: String? // Latest reaction state, JSON array is contradictory and lossy. Defer reactions or model them relationally.

  3. OAuth migration can be detected locally. OAuthSession.grantedScope is already persisted. Gate Chat from the stored scope and offer reauthorization before provoking a guaranteed Bad token scope.

  4. Chat routing belongs at the existing routing boundary. PdsRoutingInterceptor already rewrites /xrpc/ and owns AppView proxy headers. Prefer extending it for chat.bsky.*, or prove separate interceptor ordering, so DPoP signs the final PDS URL.

  5. The 60-second cadence needs a source. getLog existence is specified; cadence is not. Cite a stable source permalink or label 60 seconds as Peregrine policy.

  6. Privacy/retention needs an explicit decision. Room-cached DM plaintext is more sensitive than public timeline cache. Document at-rest behavior, backup/notification-preview policy, logout clearing, and whether Phase A stores full bodies or only previews.

Verified correct

  • Authenticated chat calls proxy through the user’s PDS.
  • Proxy target: did:web:api.bsky.chat#bsky_chat.
  • OAuth add-on scope: transition:chat.bsky with transition:generic.
  • Message limits: maxLength 10,000 and maxGraphemes 1,000.
  • Returned replyTo embeds only one level.
  • atproto-kotlin v9.7.5 and io.github.kikin81.atproto:models:9.7.5 exist.

Required revision path

  1. Recreate the research change from current main so the PR has a real diff.
  2. Correct contradictions and generated Kotlin API names.
  3. Replace speculative schema/UI snippets with project-compatible architecture.
  4. Make Phase A coherent: proxy + scope gate + typed service + additive Room migration + read-only list + tests. Keep getLog/event processing in Phase B unless explicitly promoted with its full event-state scope.
  5. Re-review before implementation begins.
## Review verdict: REQUEST CHANGES The strategic conclusion is sound — Peregrine should pursue `chat.bsky.*`, use PDS proxying with `did:web:api.bsky.chat#bsky_chat`, and request `transition:chat.bsky` — but this PR is not mergeable or safe to implement from yet. ### Blocking 1. **PR #95 currently has an empty diff and would merge as a no-op.** Head `6632288` is already an ancestor of `main`; `main` committed the research and then reverted it in `c31980e`. Verified: the three-dot diff is 0 bytes and the left/right commit count is `2 0`. Rebuild the branch from current `main` with a new commit reintroducing the reviewed docs. 2. **The atproto-kotlin examples use class names that do not exist.** `03-implementation-path.md` uses `ChatBskyConvoListConvos.Response`, `ChatBskyConvoDefsMessageInput`, and `ChatBskyConvoSendMessage.Request`. v9.7.5 generates package-scoped types such as `io.github.kikin81.atproto.chat.bsky.convo.ListConvosResponse`, `MessageInput`, `SendMessageRequest`, and `GetLogResponse`. The examples do not compile as written; add a dependency spike/compile proof. 3. **Group-chat status contradicts itself.** `01` line 4 says group chats launched June 11, while lines 8 and 185–225 say they are not live. `02` says the official app and Skyscraper have full groups; `04` says group endpoints return 404. Discussion #5095 still asks for an API timetable. Pick one sourced status and remove unsupported claims. Current upstream has 17 JSON files under `chat/bsky/group` including `defs.json`, not 16; distinguish endpoints from total files. 4. **Phase A scope conflicts across documents.** `03` puts `ChatPoller/getLog` in Phase B; `04` and the handoff require polling in Phase A. Resolve before task extraction. A read-only list can refresh with `listConvos`; incremental event processing is a separate larger slice. 5. **The proposed UI state violates the architecture.** `ChatUiState.Success` contains `LazyPagingItems<Conversation>`. That is a Compose UI object created in a composable and must not live in ViewModel state. Expose `Flow<PagingData<Conversation>>` or a plain immutable list/state. 6. **The “group-prepared” Room design is not group-prepared.** `ConversationEntity` hard-codes two members while also defining a normalized member table, creating two sources of truth and failing for arbitrary groups. Use the member table as canonical and derive the direct-conversation counterpart. ### Important 7. **No Room migration plan despite current schema v12.** Define `MIGRATION_12_13`, register it in `ALL_MIGRATIONS`, commit schema 13 JSON, and execute a `MigrationTestHelper` device test. 8. **Reaction storage is malformed.** `reactionValue: String? // Latest reaction state, JSON array` is contradictory and lossy. Defer reactions or model them relationally. 9. **OAuth migration can be detected locally.** `OAuthSession.grantedScope` is already persisted. Gate Chat from the stored scope and offer reauthorization before provoking a guaranteed `Bad token scope`. 10. **Chat routing belongs at the existing routing boundary.** `PdsRoutingInterceptor` already rewrites `/xrpc/` and owns AppView proxy headers. Prefer extending it for `chat.bsky.*`, or prove separate interceptor ordering, so DPoP signs the final PDS URL. 11. **The 60-second cadence needs a source.** `getLog` existence is specified; cadence is not. Cite a stable source permalink or label 60 seconds as Peregrine policy. 12. **Privacy/retention needs an explicit decision.** Room-cached DM plaintext is more sensitive than public timeline cache. Document at-rest behavior, backup/notification-preview policy, logout clearing, and whether Phase A stores full bodies or only previews. ### Verified correct - Authenticated chat calls proxy through the user’s PDS. - Proxy target: `did:web:api.bsky.chat#bsky_chat`. - OAuth add-on scope: `transition:chat.bsky` with `transition:generic`. - Message limits: `maxLength` 10,000 and `maxGraphemes` 1,000. - Returned `replyTo` embeds only one level. - atproto-kotlin v9.7.5 and `io.github.kikin81.atproto:models:9.7.5` exist. ### Required revision path 1. Recreate the research change from current `main` so the PR has a real diff. 2. Correct contradictions and generated Kotlin API names. 3. Replace speculative schema/UI snippets with project-compatible architecture. 4. Make Phase A coherent: proxy + scope gate + typed service + additive Room migration + read-only list + tests. Keep getLog/event processing in Phase B unless explicitly promoted with its full event-state scope. 5. Re-review before implementation begins.
Author
Owner

This branch's tip is an ancestor of main, but its content was reverted on main at c31980e. The four research documents (docs/research/01..04-*.md) were independently restored by 69adf55 and are on main today, with a sourced re-verification table added to 04-decisions.md recording the four AT Protocol sources checked on 2026-07-25. Branch deleted; the content lives on main.

Reconciled in docs/reviews/2026-07-25-branch-pr-reconciliation.md, which landed on main via #103.

This branch's tip is an ancestor of `main`, but its content was reverted on `main` at `c31980e`. The four research documents (`docs/research/01..04-*.md`) were independently restored by `69adf55` and are on `main` today, with a sourced re-verification table added to `04-decisions.md` recording the four AT Protocol sources checked on 2026-07-25. Branch deleted; the content lives on `main`. Reconciled in `docs/reviews/2026-07-25-branch-pr-reconciliation.md`, which landed on `main` via #103.
starsetbyte closed this pull request 2026-07-26 03:22:33 +00:00
Some checks failed
Peregrine CI / Build & Test (JDK 17) (push) Successful in 4m5s
Peregrine CI / Instrumented Tests (API 29+) (push) Has been cancelled
Peregrine CI / Signed Minified Release Build (push) Has been cancelled
peregrine-ci / assembleDebug Build succeeded (3s)
Required
Details
Peregrine CI / Build & Test (JDK 17) (pull_request) Successful in 4m4s
Peregrine CI / Signed Minified Release Build (pull_request) Successful in 3m48s
Peregrine CI / Instrumented Tests (API 29+) (pull_request) Has been skipped

Pull request closed

Sign in to join this conversation.
No description provided.