pi-roundtable 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +35 -11
  3. package/README.zh-TW.md +27 -10
  4. package/docs/migration-0.9.md +247 -0
  5. package/docs/plugins.md +606 -78
  6. package/examples/channel-context.test.ts +88 -0
  7. package/examples/channel-context.ts +62 -0
  8. package/examples/echo-runtime.test.ts +1 -0
  9. package/examples/fake-surface.test.ts +69 -6
  10. package/examples/fake-surface.ts +73 -7
  11. package/examples/headless.ts +5 -1
  12. package/examples/prechecks.test.ts +1 -0
  13. package/examples/principals.test.ts +60 -0
  14. package/examples/principals.ts +48 -0
  15. package/examples/prompt.test.ts +1 -1
  16. package/examples/study-room.test.ts +29 -1
  17. package/examples/study-room.ts +16 -19
  18. package/examples/support-desk.test.ts +3 -1
  19. package/examples/tools.test.ts +6 -1
  20. package/package.json +5 -2
  21. package/src/cli/checks/database.ts +11 -0
  22. package/src/cli/checks/identity.ts +225 -0
  23. package/src/cli/checks/model.ts +21 -3
  24. package/src/cli/cli.ts +115 -1
  25. package/src/cli/config-edit.ts +83 -13
  26. package/src/cli/doctor.ts +7 -0
  27. package/src/cli/line-diff.ts +72 -0
  28. package/src/cli/principal.ts +412 -0
  29. package/src/cli/project.ts +5 -0
  30. package/src/cli/report.ts +12 -3
  31. package/src/cli/upgrade-rewrite.ts +102 -0
  32. package/src/cli/upgrade-source.ts +430 -0
  33. package/src/cli/upgrade.ts +176 -0
  34. package/src/core/agents/agent-claim.ts +56 -34
  35. package/src/core/agents/team-turns.ts +8 -4
  36. package/src/core/builtin/agent-server.ts +9 -7
  37. package/src/core/builtin/discord-admin.ts +4 -1
  38. package/src/core/builtin/discord.ts +71 -13
  39. package/src/core/builtin/modules.ts +140 -85
  40. package/src/core/builtin/stores.ts +40 -21
  41. package/src/core/config/access.ts +229 -0
  42. package/src/core/config/adapters.ts +64 -1
  43. package/src/core/config/config.ts +78 -41
  44. package/src/core/contract/channel-context.ts +53 -0
  45. package/src/core/contract/channels.ts +77 -9
  46. package/src/core/contract/runtime.ts +23 -7
  47. package/src/core/contract/surface.ts +22 -6
  48. package/src/core/conversations/conversation-registry.ts +14 -0
  49. package/src/core/conversations/conversation-store.ts +13 -0
  50. package/src/core/define-roundtable.ts +41 -11
  51. package/src/core/discord/agent-panel.ts +2 -2
  52. package/src/core/discord/card-audience.ts +148 -0
  53. package/src/core/discord/channel-context.ts +348 -0
  54. package/src/core/discord/connection.ts +15 -3
  55. package/src/core/discord/direct-channel.ts +54 -0
  56. package/src/core/discord/discord-owners.ts +133 -0
  57. package/src/core/discord/discord-surface.ts +69 -14
  58. package/src/core/discord/fresh-marker.ts +24 -0
  59. package/src/core/discord/inbound-message.ts +21 -7
  60. package/src/core/discord/interaction-module.ts +11 -1
  61. package/src/core/discord/owner-cards-fixture.ts +80 -0
  62. package/src/core/discord/owner-cards.ts +58 -97
  63. package/src/core/discord/owner-command.ts +27 -13
  64. package/src/core/discord/stop-button.ts +4 -4
  65. package/src/core/domain/conversation.ts +7 -2
  66. package/src/core/domain/errors.ts +5 -0
  67. package/src/core/domain/ports.ts +27 -2
  68. package/src/core/host.ts +27 -8
  69. package/src/core/i18n/cards.ts +8 -4
  70. package/src/core/identity/access-policy.ts +227 -0
  71. package/src/core/identity/actor-facts.ts +65 -0
  72. package/src/core/identity/caching-principal-store.ts +111 -0
  73. package/src/core/identity/config-owner.ts +145 -0
  74. package/src/core/identity/contact.ts +27 -0
  75. package/src/core/identity/identity-plugin.ts +56 -0
  76. package/src/core/identity/identity-schema.ts +282 -0
  77. package/src/core/identity/identity-service.ts +489 -0
  78. package/src/core/identity/identity-view.ts +77 -0
  79. package/src/core/identity/plugin-identities.ts +109 -0
  80. package/src/core/identity/principal-reads.ts +38 -0
  81. package/src/core/identity/principal-store.ts +478 -0
  82. package/src/core/identity/principal-tables.ts +53 -0
  83. package/src/core/identity/seen-throttle.ts +57 -0
  84. package/src/core/identity/ulid.ts +36 -0
  85. package/src/core/identity.ts +34 -5
  86. package/src/core/interactions/prompts.ts +127 -0
  87. package/src/core/modules/background/background-turns.ts +56 -17
  88. package/src/core/modules/background/personal-target.ts +50 -0
  89. package/src/core/modules/delegation/delegate.ts +28 -14
  90. package/src/core/modules/delegation/delegator.ts +23 -7
  91. package/src/core/modules/memory/owner-memory.ts +123 -50
  92. package/src/core/modules/notify/notify.ts +53 -13
  93. package/src/core/modules/notify/session-notify.ts +110 -0
  94. package/src/core/modules/schedules/prechecks.ts +8 -2
  95. package/src/core/modules/schedules/schedule-prechecks.ts +4 -1
  96. package/src/core/modules/schedules/schedule-store.ts +51 -20
  97. package/src/core/modules/schedules/schedule-tools.ts +122 -12
  98. package/src/core/modules/schedules/scheduler.ts +43 -2
  99. package/src/core/modules/schedules/schedules.ts +43 -14
  100. package/src/core/plugin.ts +48 -0
  101. package/src/core/presence/direct-channels.ts +104 -0
  102. package/src/core/registry/contributions.ts +63 -3
  103. package/src/core/routing/channel-router.ts +218 -17
  104. package/src/core/routing/conversation-turns.ts +38 -8
  105. package/src/core/routing/surface-port.ts +32 -2
  106. package/src/core/routing/system-turns.ts +29 -0
  107. package/src/core/runtime/bridge-guard.ts +225 -0
  108. package/src/core/runtime/compaction-tiers.ts +5 -1
  109. package/src/core/runtime/conversation-sessions.ts +124 -12
  110. package/src/core/runtime/extensions/ask-user.ts +1 -1
  111. package/src/core/runtime/extensions/confirmation-gate.ts +28 -14
  112. package/src/core/runtime/extensions/private-memory.ts +447 -0
  113. package/src/core/runtime/pending-confirmation-store.ts +30 -5
  114. package/src/core/runtime/pi-agent-runtime.ts +135 -134
  115. package/src/core/runtime/preflight-tools.ts +47 -0
  116. package/src/core/runtime/prompt-slot.ts +4 -4
  117. package/src/core/runtime/reader-history.ts +129 -0
  118. package/src/core/runtime/runtime-plugin.ts +50 -5
  119. package/src/core/runtime/runtime-types.ts +40 -12
  120. package/src/core/runtime/session-conversation.ts +143 -0
  121. package/src/core/runtime/session-factory.ts +115 -15
  122. package/src/core/runtime/session-scope.ts +78 -0
  123. package/src/core/runtime/steerable-run.ts +9 -0
  124. package/src/core/runtime/tool-call-scope.ts +43 -0
  125. package/src/core/runtime/turn-answer.ts +30 -1
  126. package/src/core/services.ts +27 -1
  127. package/src/core/sessions.ts +30 -0
  128. package/src/core/speakers.ts +21 -19
  129. package/src/core/testing/background.ts +31 -0
  130. package/src/core/testing/fixture-database.ts +55 -0
  131. package/src/core/testing/map-identity.ts +76 -0
  132. package/src/core/testing/module-sessions.ts +134 -0
  133. package/src/core/testing/modules.ts +108 -21
  134. package/src/core/testing/owner.ts +17 -0
  135. package/src/core/testing/surface-contract.ts +118 -41
  136. package/src/core/testing/test-host.ts +79 -16
  137. package/src/core/testing/tool-set.ts +32 -0
  138. package/src/core/tool-set.snapshot.json +4 -4
  139. package/src/core/tool-tiers.ts +57 -3
  140. package/src/discord/index.ts +8 -0
  141. package/src/index.ts +47 -8
  142. package/src/kit/compaction.ts +13 -0
  143. package/src/kit/index.ts +10 -0
  144. package/src/kit/support.ts +1 -1
  145. package/src/testing.ts +55 -16
  146. package/templates/.env.example +1 -1
  147. package/templates/adapters/web/README.md +7 -0
  148. package/templates/adapters/web/roundtable.config.ts +14 -10
  149. package/templates/roundtable.config.ts +12 -1
  150. package/src/core/domain/owner-prompts.ts +0 -45
@@ -0,0 +1,247 @@
1
+ # Migrating to 0.9
2
+
3
+ 0.9 makes the person behind a turn explicit: a **principal** owns memory, private conversations, schedules, and notifications, while an **identity** is an account or credential linked to that principal.
4
+ `Speaker.id` remains the external actor id; the required `Speaker.principalId` identifies the person.
5
+ This is principal isolation, not a claim of a complete multi-user service: model credentials, host files, and plugin execution remain shared.
6
+
7
+ ## Who needs to act
8
+
9
+ An ordinary single-owner Discord host using only the built-in conversation paths needs no configuration or data edits.
10
+ Its old `owner` and `speakers` settings still work with a deprecation warning, the owner's principal keeps their old id, and their memory and schedules stay theirs.
11
+ Back up the configuration, PostgreSQL, and the data directory before upgrading, run `roundtable doctor`, and check the first boot's identity backfill log.
12
+
13
+ You must act if you:
14
+
15
+ - Call `runtime.runTurn` yourself, construct speakers or background turns, implement a surface, or replace a core service port: see [plugin changes](#plugin-changes).
16
+ - Contribute a background target named `"owner"`: the core now contributes it on every host, so remove your duplicate.
17
+ - Use webchat's old `access` option: move it to the core configuration.
18
+ - Use the web console with a verifier reporting an actor: link its identity to an owner, even on a single-owner host.
19
+ - Use memory and a `claude-bridge` model in shared conversations: review the history-based turn guard and remaining risks below before admitting new readers.
20
+ A host whose agents' channels admit anyone but its owners (admins or members who talk to the agents) on `claude-bridge` should set `discord: { agentMemory: "owners" }`: a guest's turn then loads no memory, so it never makes the guard refuse the owner afterwards. It does not let the guest speak after the owner's memory turns; see the [bridge guard](#claude-bridge-guard).
21
+ - Do not want an agent's channel turn to read the server channel's other messages: set `discord: { channelContext: false }` (see [channel context](#channel-context)).
22
+ - Rely on system error or webhook report turns to schedule or delegate: they may no longer create, change, or cancel schedules, delegate tasks, or call coding's `repo_task`; they may still list schedules.
23
+
24
+ ## Upgrade the configuration
25
+
26
+ Run these from the host project, with the environment its configuration normally loads:
27
+
28
+ ```sh
29
+ bunx roundtable upgrade
30
+ bunx roundtable upgrade --write
31
+ bunx roundtable doctor
32
+ ```
33
+
34
+ The first command previews a diff without changing files.
35
+ `--write` checks that the rewritten configuration serves the same people and then writes only `roundtable.config.ts`.
36
+ It converts `owner` and `speakers` to `access`, keeps the owner's old id as their `principal`, prefixes identities and roles, scopes old `everyone` to Discord, and moves top-level `discord` to `adapters: [discord(...)]` with its import.
37
+ A second run has no diff.
38
+ Dynamic objects or spreads it cannot safely rewrite need manual edits at the reported line.
39
+ If the configuration cannot load for the equivalence check, provide its normal environment; `--write --unchecked` is an explicit opt-out with a warning, not the recommended path.
40
+ It does not update plugin source, webchat options, or credentials.
41
+ Do not combine `access` with old `owner` or `speakers`: the host refuses that configuration.
42
+ The first configured owner is the primary owner and needs an explicit principal id; Discord also needs their linked Discord identity.
43
+
44
+ Use `roundtable principal list` and `show` to inspect principals and copy identity strings.
45
+ `link <principal> <identity>` links another account to the same person's memory; `grant`, `revoke`, `disable`, and `enable` manage lasting roles and admission.
46
+ CLI changes can take up to 30 seconds to reach a running host's identity cache.
47
+ Only configuration and CLI grants confer owner; IdP roles never do.
48
+ `provisioning: "admitted"` creates a principal for a newly admitted person; `"linked"` accepts only existing links and does not claim old ids.
49
+
50
+ ## Data migration and conversation ownership
51
+
52
+ The boot migrations add identity tables and held-action attribution without rewriting existing conversation, memory, or schedule rows.
53
+ The backfill creates principals with the old person ids; an adapter's verified first contact can claim a backfilled id once.
54
+ A principal created normally, one already linked, or a configured owner is not a new identity's legacy claim.
55
+ Unlinking an identity does not restore that claim.
56
+ The 0.8 `remote-mcp` author is attributed to the primary owner, including their existing schedules, not made into a separate member.
57
+ An old non-owner schedule author gets an initial last-seen tier capped by current access rules and a 30-day background window by default.
58
+ Background execution still caps the recorded tier at current lasting roles, or at the last admitted tier for someone whose roles come only from the IdP; after `access.backgroundStaleDays` (default 30), those IdP-only background turns are skipped until the person returns.
59
+ Disabled, unknown, or unadmitted principals are skipped too.
60
+
61
+ Register a private conversation with `CONVERSATIONS` **before the first runtime call**, including a transcript read.
62
+ Registration fixes ownership on the first record; later registration does not change it.
63
+ Use `CONVERSATIONS.adopt(key, principalId)` only to migrate a pre-0.9 shared record with no principal that your plugin knows was one person's conversation.
64
+ Adoption is idempotent and does not change an existing private record; adoption of conversations created in 0.9 is outside its contract and may be refused in a later release.
65
+ Remote MCP performs this migration for its old sessions.
66
+ Other 0.8 shared rows stay shared: even a single owner's formerly shared room now uses "the speaker" tool wording.
67
+
68
+ Never flip visibility or transfer a conversation to reuse its history.
69
+ If its effective scope changes (private to another principal, private to shared, or shared to private), the runtime archives the old history, drops held actions, and starts an isolated session; this applies after a restart too.
70
+ An explicit `TurnRequest.conversation` should agree with your registry's ownership.
71
+ A private conversation refuses another principal's interactive turn both through `context.turns` and through the Pi runtime directly.
72
+ Private prompts never escalate to an owner; a held call above the person's tier expires without being shown.
73
+ In shared conversations, the principal who spoke may answer from any linked identity at the required tier, and owners may answer escalated prompts.
74
+
75
+ ## Plugin changes
76
+
77
+ - `TurnRequest.speaker` is required.
78
+ `runTurn` without one returns `{ ok: false, error }` with an `AgentRunError` before asking the model; obtain it with `IDENTITY.speakerFor(principalId)` rather than assuming owner tier.
79
+ `kind: "owner"` selects a persona, not authority.
80
+ - `BackgroundTurn.author` needs `{ principalId, id, name }`, and the turn needs an explicit `tier`.
81
+ Old shapes missing principal or tier are skipped with a reason, not elevated to owner.
82
+ A background claim runs as the router-checked `turn.speaker`.
83
+ Plugins cannot manufacture `SYSTEM_PRINCIPAL` turns; only core report paths do so.
84
+ - `PERSONAL_TARGET` replaces the deprecated `OWNER_TARGET` export (the same object until 1.0).
85
+ Its persisted name remains `"owner"`; remove a plugin's own target with that name to avoid a duplicate.
86
+ Configure optional cross-conversation limits with `background.perPrincipal.schedules` and `delegations`; neither has a default cap.
87
+ - `OwnerPrompts` is a deprecated alias of `Prompts` until 1.0.
88
+ A surface receives `PromptScope`, with `principalId`, `speakerId`, `tier`, and `escalate`, rather than treating every prompt as the primary owner's.
89
+ Old `prompts(channel, speaker)` calls remain accepted with a warning, interpreted as shared scope.
90
+ - `notify_owner` becomes `notify`.
91
+ Until 1.0 the old name works in selections, profiles, task exclusions, required tools, and tool-tier overrides, with a warning; models see the new tool name.
92
+ Notices go to the private conversation's person, or the current speaker in a shared conversation, through contributed direct channels.
93
+ - Internal prompt naming changes from `ownerWords` to `addresseeWords`, with the old internal alias retained; neither is a public package export.
94
+ Consumers should use the public `SessionContext.addressee` rather than importing core paths.
95
+ Synchronous `CommandGuard.isOwner` is deprecated and tests only the primary owner's actor id; use asynchronous `allows(actor)` to check every owner now.
96
+ - Surfaces report verified `ActorFacts`; only the router sets `InboundMessage.speaker` before admission.
97
+ A surface-supplied speaker is overwritten.
98
+ Old author fields remain a warned fallback until 1.0; no tier means no admitted person, not owner.
99
+ - A plugin's `identities: [{ identity, principal? }]` binds only `token:` credentials, defaulting to the primary owner.
100
+ Surface accounts must be linked in configuration or by CLI, not declared by plugins.
101
+ A conflicting or unknown principal stops boot; removing a declaration unlinks its plugin-owned token on the next boot.
102
+ Bind a token to a non-owner only after giving that principal a lasting CLI role: remote turns do not refresh IdP last-seen admission.
103
+ - Stand-in ports must implement the new methods of their interfaces (including registry `adopt`, conversation `runsAs`/`takesSystemReports`, and schedule-store `createWithin`).
104
+ A manually built `SessionContext` supplies `conversation`, `addressee`, and `memory`; tasks inherit the resolved memory policy and need a parent turn's tier.
105
+
106
+ See [Principals and access](plugins.md#principals-and-access) for a tested example.
107
+ Scheduling and delegation tools are present only where a background claim can answer; a no-surface session must already be registered private to a person a direct channel knows.
108
+ `DirectChannelProvider.knows` is a local capability check; actual delivery and claim reachability are rechecked when the tool runs.
109
+
110
+ ## Memory isolation and compactors
111
+
112
+ Memory is keyed by principal, never by owner tier.
113
+ Private sessions use their person's memory; shared turns project only the current speaker's memory into the request, non-persistently, and system turns in a shared session load none.
114
+ `Persona.memory: "none"` disables both memory tools and the memory prompt, including its workers.
115
+
116
+ In shared history, the isolation guarantee covers the **arguments and results together** of memory-tool calls and calls whose workers loaded a person's memory (including their enclosing exchanges).
117
+ Those exchanges are hidden from other speakers, including system turns, and from compaction summaries.
118
+ Earlier reasoning is removed with provider-aware handling of thinking blocks, encrypted reasoning, signatures, and paired message/call ids; the current turn keeps its reasoning.
119
+ User messages, public assistant replies, and arguments/results of other tools remain public: the model can disclose something it knows in them, so do not treat this as general secret-flow prevention.
120
+ Owners using the console and the operator reading storage can inspect all principals' data.
121
+
122
+ Compaction registered through `session.compaction.wrap` receives a private-memory-free projection.
123
+ A third-party compactor reading shared history outside that wrapper **must call `privateCompaction(event.preparation, branchMessages)`** from `pi-roundtable/kit` before summarizing, where `branchMessages` is the message entries of `event.branchEntries` (so a private result in the kept tail can hide its earlier arguments).
124
+ A custom tool returning private data must mark its result's `details.privateTo` with the principal id, including error results that repeat private arguments.
125
+ This is independent of the tool name: the core hides both the tagged result and the arguments of its paired call, preserving the tool-call id and name.
126
+ Do not rely on tagging to protect data repeated in public assistant replies or other untagged tools.
127
+ Hosts building their own shared Pi sessions can use `memoryProjection(messages, { shared: true, reader })` and `hidesPrivateExchange(messages, view)` from `pi-roundtable/kit`; the latter checks exchanges, not reasoning/prompt changes.
128
+ A host that builds its own shared Pi session and runs it on `claude-bridge` needs the core's reader records too, because `hidesPrivateExchange` cannot see retained prompts or reasoning: call `recordMemoryTurn(manager, reader, carriesPrivatePrompt)` before each prompt, call the marker it returns when a `message_end` event passes `carriesMemory(message)`, and refuse the turn before the provider call when `bridgeHistoryHidesMemory(manager.getBranch(), reader)` is true.
129
+ `summaryProjection(messages, history?)` and `privateCompaction(preparation, additionalMessages?)` accept the surrounding history when call/result pairs cross summary-prefix-kept boundaries.
130
+
131
+ ### claude-bridge guard
132
+
133
+ claude-bridge reuses Claude Code's stored, unfiltered history, bypassing the per-request projection.
134
+ Each shared core bridge turn checks the session's actual raw history before asking any provider, including after restart and when an agent switches to bridge.
135
+ The raw persisted branch is checked even for turns already removed from Pi's active context by compaction.
136
+ Every prompted turn, on any provider, appends a `roundtable-memory-turn` custom reader record: the actual principal (SYSTEM stays recorded as SYSTEM), a turn id, and whether its request carried private memory.
137
+ Loading core memory into that reader's prompt marks the turn private even if it calls no tools; any memory exchange or memory-loaded worker marks it private too.
138
+ A later private marker keeps the same turn id when memory is first used during the turn.
139
+ Malformed, schema-rejected, truncated, and aborted built-in memory calls, including calls with no result or ownership tag, are attributed to that turn's recorded reader.
140
+ An agent turn checks and uses one copied model/thinking snapshot; a setting changed during selection applies on the next turn rather than switching providers after the guard.
141
+ It returns an `AgentRunError` when a retained turn carried another reader's private memory, or an explicit `privateTo` exchange would be hidden from the current reader.
142
+ This refuses reasoning replay across readers even when the earlier turn only read memory from its prompt and used no tools: request projection cannot clean Claude Code's retained transcript.
143
+ A recorded turn that loaded no private memory never blocks another reader merely for having a different principal.
144
+ For this core replay check only, SYSTEM counts as the primary owner, for modern records and old exchanges alike: owner and SYSTEM can alternate over owner memory.
145
+ SYSTEM can therefore read the owner's raw retained private exchanges and reasoning on bridge, and can repeat them publicly; this is the retained compatibility design, not SYSTEM-private isolation.
146
+ Pre-0.9 history without reader records keeps the legacy rule: untagged built-in memory results before the first `roundtable-conversation` scope marker belong to the primary owner for the guard.
147
+ After that scope marker, an unowned result or outstanding memory call with no reader record still fails closed, including for owner and SYSTEM; with a reader record, the malformed exchange no longer locks its own reader out forever.
148
+ Request projection is unchanged: shared SYSTEM requests load no personal memory and redact everyone's private exchanges, but bridge bypasses that projection.
149
+ A conversation holding only a guest's private turns admits that guest, not the owner or SYSTEM.
150
+ Other providers and private conversations are unaffected by this guard.
151
+
152
+ In the agents' channels, `discord.agentMemory: "owners"` (default `"everyone"`) keeps a speaker below the owner tier out of memory: that turn has no memory in its prompt, no memory tools, and no memory-loading workers, so its reader record carries no private memory and a later owner or SYSTEM turn is not refused because of it.
153
+ The reverse stays: once an owner's turn has loaded memory, a guest's bridge turn in the same conversation is refused until the conversation starts fresh.
154
+
155
+ Admission of another person in configuration or stored roles is only a startup warning, and `roundtable doctor` warns rather than fails for that risk.
156
+ A guest admitted elsewhere but absent from this conversation does not block the owner's turns.
157
+ To share incompatible history, choose another provider or start a fresh conversation whose turns carry no private memory when readers differ.
158
+ `memory: false` or persona `memory: "none"` prevents future core memory loading; disabling memory does not bypass the guard or erase already recorded private history.
159
+ A reader record that is corrupt (not an object, or with an empty reader or a non-boolean `privateMemory`) cannot establish who owned its turn, so the guard fails closed: every later bridge turn in that conversation is refused, the owner's and SYSTEM's too, with the same error.
160
+ Records are written only by the runtime, so this takes an edited or damaged session file; the recovery is to start the conversation fresh (the runtime's `startFresh`, which the channel's start-over command calls), which archives the old history and opens one without the record.
161
+ Reader records are prospective: earlier prompt/reasoning states with no reader records cannot be reliably attributed, so start fresh before sharing an old private history with new readers.
162
+ The guard is not general secret-flow prevention: private data repeated in public replies, untagged custom tools, or pre-existing summaries can still reach later readers.
163
+
164
+ ## Official packages
165
+
166
+ ### Web console
167
+
168
+ An actor-bearing verifier must report an identity linked to a principal holding owner.
169
+ An unlinked identity receives 403 even on a one-owner host; the log gives the identity and the configuration/link instruction.
170
+ For Cloudflare Access, use `cloudflareAccessIdentity(teamDomain, sub)` in `access.owners[].identities`, or the CLI equivalent.
171
+ `legacy:` is never a console sign-in identity and is refused.
172
+ A 0.8 verifier returning `admit()` without an actor retains primary-owner compatibility with a warning, not a recommended new verifier contract.
173
+ Every admitted owner can inspect all conversations and choose any principal's notes; this is not a member self-service console.
174
+
175
+ ### Webchat
176
+
177
+ Remove `webChat({ access })`, `webAccess`, and `WebAccessMap`.
178
+ Use top-level `access`, prefix roles as `<surface>:role:<name>` (default `web:role:App.User`), replace user ids with identities, and scope old `everyone: true` to `everyone: ["<surface>"]`.
179
+ Core `everyone: true` admits every surface, not only this verifier's people.
180
+ Old OIDC principals and their private conversations remain theirs; new admitted people get opaque `p_` ids.
181
+ Tickets, quotas, connection groups, and approvals now follow the principal; token refresh to a different principal closes with 4403.
182
+ The protocol remains `roundtable.webchat.v1`, with added `notice` frames and private inbox REST endpoints (`GET <path>/notices`, `POST <path>/notices/<id>/read`).
183
+ Notices are separate from conversation history and available after reconnect.
184
+ The claim declares `takesSystemReports: false`: `ops.conversation` cannot point at webchat and fails at boot; use a shared report conversation or `ops.agent`.
185
+
186
+ ### MCP, coding, and sandbox
187
+
188
+ Remote MCP's token defaults to the primary owner's principal and its private conversation reads that person's memory.
189
+ Set `remoteMcp({ principal })` to bind it explicitly; `REMOTE_SPEAKER` is deprecated.
190
+ `/mcp/discord/<token>` channel grants are unchanged by this migration.
191
+ Coding's owner skill list is available only in a private conversation whose principal holds owner, not merely a turn whose kind is owner.
192
+ Sandbox ignores visitors whose resolved speaker has no tier and passes the admitted principal to its runtime; its sealed default's container/channel-shared memory contract is unchanged.
193
+ The opt-in `PiSandboxRuntime` now accepts `author.principalId` (falling back to actor `id` for legacy integrations where it is already the principal); host tool callbacks return `PiToolResponse.privateTo` to persist `details.privateTo` in the worker.
194
+ Its independently-created Pi sessions explicitly project requests and summaries, including the host compactor's kept messages, using the core helpers. Rebuild the worker image with matching upgraded core/sandbox code.
195
+ Tag every private custom-tool result and error; untagged custom tools, raw files, public replies and old summaries remain shared. See [sandbox private exchanges](../packages/sandbox/README.md#private-tool-exchanges-in-pi-mode).
196
+ `PiSandboxRuntimeOptions.memory.visibility` defaults to `"private"`: a nonempty prompt block records that reader's private memory even without tool calls.
197
+ For party-wide facts, explicitly set `memory.visibility: "shared"`, including for an upgraded 0.8 host, or another participant's next claude-bridge turn is refused.
198
+ This declares only the prompt block public; a tool's `privateTo` result remains private.
199
+ The worker performs the same raw reader-record check before its provider call, without the core's primary-owner/SYSTEM compatibility mapping.
200
+ It declares `privateTo` and `readerRecords` capabilities plus whether it holds private history at readiness; an incompatible image refuses private turns rather than silently dropping isolation.
201
+ The host requires those capabilities only for a private prompt, known private history, or an actual tagged host-tool response; empty/shared prompt blocks and public-only tools remain compatible with older images.
202
+ A first private response is withheld from an incompatible image and fails the entire turn, with an error naming the image to rebuild.
203
+ See the [worker handshake](../packages/sandbox/README.md#worker-capability-handshake).
204
+
205
+ ## Channel context
206
+
207
+ New in 0.9: a turn for a message in an agent's channel also reads the channel messages posted since the assistant's last post there, including those that did not address it, such as other people's and other bots' messages.
208
+ They are appended to the turn's text as a delimited block saying they were not addressed to the assistant (each author as `from`, `id` and `role` attributes; every `<` in a message's text is neutralized), so they live in the conversation's history, not in the system prompt.
209
+ It reads Discord when the turn starts, so it needs no data migration and survives restarts.
210
+ A group's round reads none, since it already carries what was said since its last turn, and direct messages never do.
211
+
212
+ The defaults fetch the 50 messages before the addressed one and keep at most the newest 15 after the assistant's latest post, merging consecutive near-repeats of one author (80% alike) and cutting long messages (500 characters, another bot's at 80).
213
+ Change them under `discord.channelContext` (`fetch`, `keep`, `similarity`, `messageChars`, `botMessageChars`), or turn it off for the host with `discord: { channelContext: false }`, in `adapters: [discord({ ... })]` or the top-level `discord`.
214
+
215
+ When a conversation is started over in a server channel or thread (any claim: an agent's channel, a plugin's claim such as a party, an owner channel), the core posts a short divider there as the bot, so the next turn does not read what was said before; the owner's ephemeral reply is unchanged, and direct messages get none. Set the text with `discord.freshMarker` (default `─── new conversation ───`), or `false` for no divider; a failed post is logged and the restart still counts.
216
+
217
+ Channel context is public channel text: it is not private memory, is never tagged `privateTo`, and the turn's reader stays the person who addressed the assistant.
218
+ People with no tier appear in it as channel text but do not become speakers.
219
+ A plugin's claim, such as one that runs a party in its own server, opts in with `DISCORD.channelContext(message)` and `withChannelContext(text, context)` from `pi-roundtable/discord`; see [Channel context](plugins.md#channel-context-what-was-said-around-an-addressed-message).
220
+ A plugin's own stand-in `DISCORD` service needs `channelContext`; `fakeDiscord()` has it.
221
+
222
+ ## Prompt changes from 0.8.0
223
+
224
+ The acceptance snapshots record these exact differences; do not expect every single-owner prompt to be byte-identical:
225
+
226
+ | Snapshot | Difference |
227
+ |---|---|
228
+ | (a) agent, (b) group seat, (c) owner's persona conversation | Only the tool name `notify_owner` becomes `notify`; system prompt, descriptions, and parameters are byte-identical. |
229
+ | (d) member in a shared persona room | `ask_user` and the three memory tools replace `Ada`/`she` with `the speaker`; `memory_remove` and `memory_search` also add "Whoever is speaking has a memory of their own…". |
230
+ | (e) direct `runTurn` without speaker | Refused; no prompt is sent. |
231
+ | (f) headless webchat-style private conversation | Byte-identical to 0.8.0; the only unchanged whole snapshot. |
232
+ | (g) web member's private conversation | The same tools no longer name the configured owner and use `the speaker`. |
233
+ | (h) agent turn with channel context | New in 0.9: the archivist's agent session, which also records the turn's own message: its text, then the [channel context](#channel-context) block. Without channel context a turn's message is its text alone, as in 0.8. |
234
+
235
+ The tool-set snapshot changes only `notify_owner` to `notify`.
236
+ For other private conversations, descriptions name their principal when known; old shared single-owner rooms retain shared "the speaker" wording until explicitly migrated where appropriate.
237
+
238
+ ## Rollback and downgrade
239
+
240
+ Stop the host and all background work before a downgrade; keep the pre-upgrade configuration, package lock, database backup, and data directory together.
241
+ Restore the old source/configuration and matching official package versions.
242
+ 0.8 does not understand `access`, principal CLI commands, plugin identity declarations, or webchat's new options/frames/inbox; it cannot load a 0.9 configuration unchanged.
243
+ It ignores the additional identity tables, principal attribution columns, and new session metadata; existing 0.8-shaped rows still use their old ids, and the additive migrations do not delete them.
244
+ But 0.8 cannot enforce principal-private session isolation, read the notice inbox, or safely interpret new `p_` principals/linked accounts as the original external speakers.
245
+ It also lacks the memory-history filtering and fail-closed turn checks, so **do not resume newly principal-isolated or shared sensitive conversations under 0.8**.
246
+ Restore the pre-upgrade database and session files when you need a faithful rollback, rather than expecting 0.8 to preserve new identity semantics.
247
+ If you intentionally run 0.8 on the additive schema and later re-upgrade, the every-boot backfill discovers person ids written during the downgrade without rewriting their rows; verify the resulting links and roles before reopening access.