pi-roundtable 0.7.18 → 0.7.19

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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.7.19] - 2026-10-07
9
+
10
+ ### Fixed
11
+
12
+ - Behavior change: in a conversation outside the agent server, such as a plugin's persona conversation run through `context.turns`, a member or admin speaker now reads and changes their own memory. Before, every such turn carried the owner's core facts and upcoming events in its system prompt, whoever spoke, and `memory_add`, `memory_search`, and `memory_remove` (member-tier tools, when a turn's selection offered them) read and changed the owner's memory. The prompt section of such a turn is headed `Memory of <name>` and lists only that speaker's facts. A speaker of the owner tier, the owner or remote MCP's speaker, keeps the owner's memory, and the owner's own conversations, agent conversations, and every tool description read as before. No data moves: facts a member stored before this release sit in the owner's memory, and the owner may remove them with `memory_remove`.
13
+
8
14
  ## [0.7.18] - 2026-10-05
9
15
 
10
16
  ### Fixed
package/docs/plugins.md CHANGED
@@ -1658,6 +1658,7 @@ The runtime reads `prompt()` when it creates the conversation's session, so mess
1658
1658
  The host merges every plugin's list and keeps each name once.
1659
1659
  - A turn whose kind has no persona is refused with an error naming `personas`.
1660
1660
  The Pi runtime refuses when it makes the session; a runtime of your own does the same, as the example's does.
1661
+ - With the memory addon on, a turn's `speaker` decides whose memory the system prompt carries and the memory tools change: a member's or an admin's own, and the owner's for a speaker of the owner tier.
1661
1662
 
1662
1663
  Call `context.turns.run(input)` inside your claim's queue task to run a turn in a conversation it owns.
1663
1664
  It shows typing and the stop control on the channel's surface, runs the turn, and emits `turnStarted` and `turnEnded` with the turn's `kind`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-roundtable",
3
- "version": "0.7.18",
3
+ "version": "0.7.19",
4
4
  "description": "A plugin-driven Pi agent server for Discord",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -4,7 +4,8 @@ import { PgMemoryStore } from "../modules/memory/owner-memory-store.ts";
4
4
  import { memoryPrecheckRegistry } from "../modules/schedules/prechecks.ts";
5
5
  import { PgScheduleStore } from "../modules/schedules/schedule-store.ts";
6
6
  import type { RoundtablePlugin } from "../plugin.ts";
7
- import { MEMORY, PRECHECKS, SCHEDULES } from "../services.ts";
7
+ import { MEMORY, type MemoryStore, PRECHECKS, SCHEDULES } from "../services.ts";
8
+ import type { SessionTool } from "../sessions.ts";
8
9
  import { THE_SPEAKER, type Tier } from "../speakers.ts";
9
10
  import { fixed } from "./session-tool.ts";
10
11
 
@@ -14,12 +15,37 @@ export interface MemoryOptions {
14
15
  }
15
16
 
16
17
  /** Each speaker keeps a memory of their own, so every tier may use the tools; the owner's reaches only the owner's turns. */
17
- const MEMORY_TIERS: Readonly<Record<string, Tier>> = {
18
+ export const MEMORY_TIERS: Readonly<Record<string, Tier>> = {
18
19
  memory_add: "member",
19
20
  memory_search: "member",
20
21
  memory_remove: "member",
21
22
  };
22
23
 
24
+ /** The memory tools and prompt block of every conversation, over the given store. */
25
+ export function memorySessionTool(
26
+ memory: MemoryStore,
27
+ owner: MemoryOptions["owner"],
28
+ ): SessionTool {
29
+ return fixed("owner-memory", (session) => {
30
+ // An agent session serves every speaker, so its tools name none.
31
+ if (session.agent)
32
+ return ownerMemoryExtension(
33
+ memory,
34
+ owner.id,
35
+ THE_SPEAKER,
36
+ session.speaker,
37
+ );
38
+ // Any other conversation may admit a member or an admin, who reads and changes their own
39
+ // memory; an owner-tier speaker, such as remote MCP, speaks as the owner.
40
+ const speaker = () => {
41
+ const current = session.speaker();
42
+ return current?.tier === "owner" ? undefined : current;
43
+ };
44
+ // Its tools keep the owner's words, so the owner's own chats read as before.
45
+ return ownerMemoryExtension(memory, owner.id, owner, speaker, false);
46
+ });
47
+ }
48
+
23
49
  /**
24
50
  * The remembered facts of each speaker: the table, the store over it provided as `MEMORY`, and
25
51
  * the memory tools and prompt block of every conversation. An addon; `memory: false` leaves it
@@ -35,18 +61,7 @@ export function memoryPlugin(options: MemoryOptions): RoundtablePlugin {
35
61
  const memory = PgMemoryStore.attach(database(), owner.id);
36
62
  services.provide(MEMORY, memory);
37
63
  return {
38
- sessionTools: [
39
- fixed("owner-memory", (session) =>
40
- ownerMemoryExtension(
41
- memory,
42
- owner.id,
43
- // An agent session serves every speaker, so its tools name none.
44
- session.agent ? THE_SPEAKER : owner,
45
- // The owner's own chats have one speaker; the agent server's have several.
46
- session.agent ? session.speaker : undefined,
47
- ),
48
- ),
49
- ],
64
+ sessionTools: [memorySessionTool(memory, owner)],
50
65
  toolTiers: MEMORY_TIERS,
51
66
  };
52
67
  },
@@ -47,10 +47,12 @@ export function ownerMemoryExtension(
47
47
  ownerId: string,
48
48
  owner: OwnerIdentity,
49
49
  /**
50
- * The person the running turn is for, in a session that serves several. Their memory, not
51
- * the owner's, is what the turn reads and changes; without it every turn is the owner's.
50
+ * The person the running turn is for, when it may be someone other than the owner. Their
51
+ * memory, not the owner's, is what the turn reads and changes; without it every turn is the owner's.
52
52
  */
53
53
  speaker?: () => Speaker | undefined,
54
+ /** Whether the tools tell the model that each speaker has a memory of their own. */
55
+ describesSpeakers = speaker !== undefined,
54
56
  ): ExtensionFactory {
55
57
  const o = ownerWords(owner);
56
58
  /** The running turn's other speaker; undefined when the owner's own memory applies. */
@@ -59,7 +61,7 @@ export function ownerMemoryExtension(
59
61
  return current && current.id !== ownerId ? current : undefined;
60
62
  };
61
63
  const storeOf = () => memories.forSpeaker(other()?.id ?? ownerId);
62
- const shared = speaker
64
+ const shared = describesSpeakers
63
65
  ? " Whoever is speaking has a memory of their own, shared by every agent; this reads and changes theirs, not someone else's."
64
66
  : "";
65
67
  return (pi) => {