@indexnetwork/protocol 20.0.1-rc.485.1 → 21.0.0-rc.486.1

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
@@ -20,6 +20,30 @@ went 6.7.1 → 8.0.2 with no 7.x in between because the whole 7.x line shipped a
20
20
  prereleases between the two promotions. To track every change, read `rc`; to
21
21
  pin a supported release, use `latest`.
22
22
 
23
+ ## 21.0.0 - 2026-08-17
24
+
25
+ ### Removed
26
+
27
+ - **Breaking:** remove the Agent reporter persona. `REPORTER_PERSONA`,
28
+ `REPORTER_PERSONA_ID`, `REPORTER_BRIEFING_KICKOFF`, `REPORTER_TOOL_NAMES`,
29
+ `createReporterTools`, `filterReporterTools` and `narrowReporterTools` are
30
+ gone from the package entry point, along with `reporter.persona.ts`,
31
+ `reporter.prompt.ts` and the `propose_cleanup_actions` tool
32
+ (`reporter.action.tools.ts`, `reporter.action.contracts.ts`). Hosts that named
33
+ the `reporter` persona must pick another; unknown personas already fail closed.
34
+ - **Breaking:** drop `actionToolsEnabled` from `ToolContext`/`ToolDeps`/
35
+ `ResolvedToolContext` and `actionProposalStore` from `ToolDeps`. Both existed
36
+ solely to gate the reporter's cleanup-action tool. `resolveChatContext()` no
37
+ longer accepts or returns `actionToolsEnabled`.
38
+ - Remove `ChatAgent.hasPriorAgentActionProposal()`, the
39
+ ```agent_action_proposal``` fence parser behind it, and
40
+ `IterationContext.hasPriorAgentActionProposal`. The reporter prompt was the
41
+ only consumer, so all of it became dead with the persona.
42
+
43
+ `read_activity_summary` and the whole `activity-projection` module are
44
+ **unchanged** — they are shared utility tools registered for every persona and
45
+ exposed on the MCP server, not reporter-specific.
46
+
23
47
  ## 20.0.1 - 2026-08-17
24
48
 
25
49
  ### Changed
package/IMPLEMENTATION.md CHANGED
@@ -173,7 +173,7 @@ The intent and community graphs are the exceptions: they are reached through the
173
173
 
174
174
  ### Persisted chat personas
175
175
 
176
- `ChatGraphFactory.withPersona()` keeps the runtime neutral while selecting an exported persona configuration. `SIGNAL_PERSONA`, `REPORTER_PERSONA`, and `ONBOARDING_PERSONA` each own an exact positive tool allowlist; shared tools added later remain unavailable until reviewed. `ONBOARDING_PERSONA` reuses Signal's proposal-only, live-membership-narrowed `create_intent` contract and otherwise exposes only privacy consent, approved self-context preview/confirmation, guided questions, and validated completion. Hosts must persist the exported persona ID on session creation and treat it as authoritative on follow-ups.
176
+ `ChatGraphFactory.withPersona()` keeps the runtime neutral while selecting an exported persona configuration. `SIGNAL_PERSONA` and `ONBOARDING_PERSONA` each own an exact positive tool allowlist; shared tools added later remain unavailable until reviewed. `ONBOARDING_PERSONA` reuses Signal's proposal-only, live-membership-narrowed `create_intent` contract and otherwise exposes only privacy consent, approved self-context preview/confirmation, guided questions, and validated completion. Hosts must persist the exported persona ID on session creation and treat it as authoritative on follow-ups.
177
177
 
178
178
  ## Intents
179
179
 
@@ -15,7 +15,6 @@ export { ChatTitleGenerator, } from "../chat/chat.title.generator.js";
15
15
  export { createChatTools, } from "../chat/chat.tools.js";
16
16
  export { createNegotiatorPersona, NEGOTIATOR_PERSONA_ID, } from "../chat/negotiator.persona.js";
17
17
  export { createOnboardingTools, filterOnboardingTools, narrowOnboardingTools, ONBOARDING_PERSONA, ONBOARDING_PERSONA_ID, ONBOARDING_PROFILE_KICKOFF, ONBOARDING_TOOL_NAMES, } from "../chat/onboarding.persona.js";
18
- export { createReporterTools, filterReporterTools, narrowReporterTools, REPORTER_BRIEFING_KICKOFF, REPORTER_PERSONA, REPORTER_PERSONA_ID, REPORTER_TOOL_NAMES, } from "../chat/reporter.persona.js";
19
18
  export { createSignalTools, filterSignalTools, narrowSignalTools, SIGNAL_NEW_SIGNAL_KICKOFF, SIGNAL_PERSONA, SIGNAL_PERSONA_ID, SIGNAL_TOOL_NAMES, } from "../chat/signal.persona.js";
20
19
  export { createAgentTools, } from "./application/index.js";
21
20
  export { SYSTEM_AGENT_IDS, } from "./domain/index.js";
@@ -6,7 +6,6 @@ export { ChatTitleGenerator, } from "../chat/chat.title.generator.js";
6
6
  export { createChatTools, } from "../chat/chat.tools.js";
7
7
  export { createNegotiatorPersona, NEGOTIATOR_PERSONA_ID, } from "../chat/negotiator.persona.js";
8
8
  export { createOnboardingTools, filterOnboardingTools, narrowOnboardingTools, ONBOARDING_PERSONA, ONBOARDING_PERSONA_ID, ONBOARDING_PROFILE_KICKOFF, ONBOARDING_TOOL_NAMES, } from "../chat/onboarding.persona.js";
9
- export { createReporterTools, filterReporterTools, narrowReporterTools, REPORTER_BRIEFING_KICKOFF, REPORTER_PERSONA, REPORTER_PERSONA_ID, REPORTER_TOOL_NAMES, } from "../chat/reporter.persona.js";
10
9
  export { createSignalTools, filterSignalTools, narrowSignalTools, SIGNAL_NEW_SIGNAL_KICKOFF, SIGNAL_PERSONA, SIGNAL_PERSONA_ID, SIGNAL_TOOL_NAMES, } from "../chat/signal.persona.js";
11
10
  export { createAgentTools, } from "./application/index.js";
12
11
  export { SYSTEM_AGENT_IDS, } from "./domain/index.js";
@@ -195,12 +195,6 @@ export declare class ChatAgent {
195
195
  * Extracts the text content of the most recent HumanMessage.
196
196
  */
197
197
  private static getCurrentUserMessage;
198
- /**
199
- * Detects a prior visible action proposal without considering current-turn
200
- * tool calls. The reporter uses this only to make typed confirmation
201
- * language contextual rather than executable on its own.
202
- */
203
- static hasPriorAgentActionProposal(messages: BaseMessage[]): boolean;
204
198
  /**
205
199
  * Async factory: creates a ChatAgent with resolved user/index context.
206
200
  * Resolves user/network identity from DB during tool initialization.
@@ -19,55 +19,6 @@ import { sanitizeForDebugMeta } from "../shared/observability/debug-meta.sanitiz
19
19
  import { Timed } from "../shared/observability/performance.js";
20
20
  import { requestContext } from "../shared/observability/request-context.js";
21
21
  import { deduplicateQuestions } from "./chat.question-dedup.js";
22
- const AGENT_ACTION_PROPOSAL_FENCE_PATTERN = /```agent_action_proposal\s*\n([\s\S]*?)\n```/g;
23
- const AGENT_ACTION_PROPOSAL_KEYS = new Set(["proposalId", "actions"]);
24
- const AGENT_ACTION_KEYS = new Set([
25
- "type",
26
- "entityId",
27
- "currentState",
28
- "proposedOperation",
29
- "skipped",
30
- "reason",
31
- "description",
32
- "evidence",
33
- ]);
34
- const AGENT_ACTION_OPERATIONS = {
35
- retract_premise: "RETRACT_PREMISE",
36
- narrow_signal: "NARROW_SIGNAL",
37
- pause_signal: "PAUSE_SIGNAL",
38
- };
39
- function isVisibleAgentActionProposal(payload) {
40
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
41
- return false;
42
- const proposal = payload;
43
- if (Object.keys(proposal).some((key) => !AGENT_ACTION_PROPOSAL_KEYS.has(key)))
44
- return false;
45
- if (typeof proposal.proposalId !== "string"
46
- || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(proposal.proposalId)
47
- || !Array.isArray(proposal.actions)
48
- || proposal.actions.length < 1
49
- || proposal.actions.length > 5)
50
- return false;
51
- return proposal.actions.every((rawAction) => {
52
- if (!rawAction || typeof rawAction !== "object" || Array.isArray(rawAction))
53
- return false;
54
- const action = rawAction;
55
- if (Object.keys(action).some((key) => !AGENT_ACTION_KEYS.has(key)))
56
- return false;
57
- if (typeof action.type !== "string" || !(action.type in AGENT_ACTION_OPERATIONS))
58
- return false;
59
- const type = action.type;
60
- if (action.proposedOperation !== AGENT_ACTION_OPERATIONS[type])
61
- return false;
62
- if (typeof action.entityId !== "string" || !action.entityId.trim())
63
- return false;
64
- if (typeof action.currentState !== "string" || !action.currentState.trim())
65
- return false;
66
- if (action.skipped !== undefined && typeof action.skipped !== "boolean")
67
- return false;
68
- return ["reason", "description", "evidence"].every((key) => action[key] === undefined || typeof action[key] === "string");
69
- });
70
- }
71
22
  const logger = protocolLogger("ChatAgent");
72
23
  // Re-export for external consumers
73
24
  export { ITERATION_NUDGE } from "./chat.prompt.js";
@@ -143,48 +94,6 @@ export class ChatAgent {
143
94
  }
144
95
  return undefined;
145
96
  }
146
- /**
147
- * Detects a prior visible action proposal without considering current-turn
148
- * tool calls. The reporter uses this only to make typed confirmation
149
- * language contextual rather than executable on its own.
150
- */
151
- static hasPriorAgentActionProposal(messages) {
152
- let currentHumanIndex = -1;
153
- let previousHumanIndex = -1;
154
- for (let index = messages.length - 1; index >= 0; index -= 1) {
155
- if (messages[index]._getType() !== "human")
156
- continue;
157
- if (currentHumanIndex < 0) {
158
- currentHumanIndex = index;
159
- }
160
- else {
161
- previousHumanIndex = index;
162
- break;
163
- }
164
- }
165
- if (currentHumanIndex < 0 || previousHumanIndex < 0)
166
- return false;
167
- let latestAssistant;
168
- for (let index = currentHumanIndex - 1; index > previousHumanIndex; index -= 1) {
169
- if (messages[index]._getType() === "ai") {
170
- latestAssistant = messages[index];
171
- break;
172
- }
173
- }
174
- if (!latestAssistant || typeof latestAssistant.content !== "string")
175
- return false;
176
- AGENT_ACTION_PROPOSAL_FENCE_PATTERN.lastIndex = 0;
177
- for (const match of latestAssistant.content.matchAll(AGENT_ACTION_PROPOSAL_FENCE_PATTERN)) {
178
- try {
179
- if (isVisibleAgentActionProposal(JSON.parse(match[1])))
180
- return true;
181
- }
182
- catch {
183
- // An invalid prior fence is not visible proposal context.
184
- }
185
- }
186
- return false;
187
- }
188
97
  /**
189
98
  * Async factory: creates a ChatAgent with resolved user/index context.
190
99
  * Resolves user/network identity from DB during tool initialization.
@@ -204,7 +113,6 @@ export class ChatAgent {
204
113
  userId: context.userId,
205
114
  networkId: scopedNetworkId,
206
115
  sessionId: context.sessionId,
207
- actionToolsEnabled: context.actionToolsEnabled,
208
116
  });
209
117
  if (explicitScope.scopeType && explicitScope.scopeId) {
210
118
  resolved.scopeType = explicitScope.scopeType;
@@ -224,7 +132,6 @@ export class ChatAgent {
224
132
  const iterCtx = {
225
133
  recentTools: extractRecentToolCalls(messages),
226
134
  currentMessage: ChatAgent.getCurrentUserMessage(messages),
227
- hasPriorAgentActionProposal: ChatAgent.hasPriorAgentActionProposal(messages),
228
135
  ctx: this.resolvedContext,
229
136
  };
230
137
  const deterministicResponse = this.persona.resolveDeterministicResponse?.(this.resolvedContext, iterCtx);
@@ -583,7 +490,6 @@ export class ChatAgent {
583
490
  const iterCtx = {
584
491
  recentTools: extractRecentToolCalls(messages),
585
492
  currentMessage: ChatAgent.getCurrentUserMessage(messages),
586
- hasPriorAgentActionProposal: ChatAgent.hasPriorAgentActionProposal(messages),
587
493
  ctx: this.resolvedContext,
588
494
  };
589
495
  const deterministicResponse = this.persona.resolveDeterministicResponse?.(this.resolvedContext, iterCtx);
@@ -9,8 +9,7 @@ export interface ChatPersonaLoopBehaviors {
9
9
  * Detect hallucinated ```opportunity / ```intent_proposal code blocks in model
10
10
  * text, auto-invoke the corresponding tool, and strip unbacked blocks from the
11
11
  * final response. Only meaningful for personas whose toolset can legitimately
12
- * produce those blocks — enabled for onboarding, signal and negotiator, off
13
- * for reporter.
12
+ * produce those blocks — enabled for onboarding, signal and negotiator.
14
13
  */
15
14
  hallucinationRecovery: boolean;
16
15
  }
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // Persona-neutral prompt text owned by the agent loop rather than by any one
6
6
  // persona. Each persona builds its own system prompt (see signal.prompt.ts,
7
- // negotiator.prompt.ts, reporter.prompt.ts, onboarding.prompt.ts); what remains
7
+ // negotiator.prompt.ts, onboarding.prompt.ts); what remains
8
8
  // here is only what the loop itself injects regardless of persona.
9
9
  /**
10
10
  * Nudge message injected after SOFT_ITERATION_LIMIT iterations.
@@ -11,8 +11,6 @@ export interface IterationContext {
11
11
  }>;
12
12
  /** Text of the latest user message (for regex matching). */
13
13
  currentMessage?: string;
14
- /** Whether an earlier assistant turn visibly contained a valid action proposal. */
15
- hasPriorAgentActionProposal?: boolean;
16
14
  /** Resolved tool context (user, profile, indexes, etc.). */
17
15
  ctx: ResolvedToolContext;
18
16
  }
package/dist/index.d.ts CHANGED
@@ -35,7 +35,6 @@ export { ChatGraphFactory } from "./agents/index.js";
35
35
  export { type ChatPersonaConfig } from "./agents/index.js";
36
36
  export { NEGOTIATOR_PERSONA_ID, createNegotiatorPersona } from "./agents/index.js";
37
37
  export { SIGNAL_PERSONA_ID, SIGNAL_PERSONA, } from "./agents/index.js";
38
- export { REPORTER_PERSONA_ID, REPORTER_PERSONA, REPORTER_BRIEFING_KICKOFF, } from "./agents/index.js";
39
38
  export { ONBOARDING_PERSONA_ID, ONBOARDING_PERSONA, } from "./agents/index.js";
40
39
  export { RadarGraphFactory } from "./opportunities/index.js";
41
40
  export { HydeGraphFactory } from "./discovery/index.js";
package/dist/index.js CHANGED
@@ -31,7 +31,6 @@ export { DEFAULT_NETWORK_ASSIGNMENT_THRESHOLD, resolveAssignmentNetworkScope, bu
31
31
  export { ChatGraphFactory } from "./agents/index.js";
32
32
  export { NEGOTIATOR_PERSONA_ID, createNegotiatorPersona } from "./agents/index.js";
33
33
  export { SIGNAL_PERSONA_ID, SIGNAL_PERSONA, } from "./agents/index.js";
34
- export { REPORTER_PERSONA_ID, REPORTER_PERSONA, REPORTER_BRIEFING_KICKOFF, } from "./agents/index.js";
35
34
  export { ONBOARDING_PERSONA_ID, ONBOARDING_PERSONA, } from "./agents/index.js";
36
35
  export { RadarGraphFactory } from "./opportunities/index.js";
37
36
  export { HydeGraphFactory } from "./discovery/index.js";
@@ -50,7 +50,6 @@ export async function createChatTools(deps, preResolvedContext) {
50
50
  userId: deps.userId,
51
51
  networkId: explicitScope.scopeType === 'network' ? explicitScope.scopeId : deps.networkId,
52
52
  sessionId: deps.sessionId,
53
- actionToolsEnabled: deps.actionToolsEnabled,
54
53
  }));
55
54
  if (!preResolvedContext && explicitScope.scopeType && explicitScope.scopeId) {
56
55
  resolvedContext.scopeType = explicitScope.scopeType;
@@ -98,8 +98,6 @@ export interface ResolvedToolContext {
98
98
  * which are owner-trusted and receive the full owner view.
99
99
  */
100
100
  mcpCaller?: McpActivityCaller;
101
- /** True only when the gated reporter cleanup-action proposal tool is registered. */
102
- actionToolsEnabled?: boolean;
103
101
  }
104
102
  /**
105
103
  * Dependencies passed when creating tools for a user session.
@@ -137,8 +135,6 @@ interface ToolContextBindings {
137
135
  intentQueue: IntentGraphQueue;
138
136
  /** Contact management operations. */
139
137
  contactService: ContactServiceAdapter;
140
- /** True only when the gated reporter cleanup-action proposal tool is registered. */
141
- actionToolsEnabled?: boolean;
142
138
  /** Chat session reader for loading conversation history. */
143
139
  chatSession: ChatSessionReader;
144
140
  /** Read-through chat-session digest. Optional; consumers fall back to undefined `chatContext`. */
@@ -184,8 +180,6 @@ interface ToolContextBindings {
184
180
  * the backend composition root; when absent the tool is not registered.
185
181
  */
186
182
  chatQuestions?: ChatQuestionsHost;
187
- /** Optional durable persistence for reporter cleanup-action proposals. */
188
- actionProposalStore?: import('../../chat/reporter.action.contracts.js').AgentActionProposalStore;
189
183
  /** Durable host persistence for verified intent proposals shown in chat. */
190
184
  intentProposalStore?: import('../../intents/intent.proposal.js').IntentProposalStore;
191
185
  /**
@@ -292,8 +286,6 @@ export declare function resolveChatContext(params: {
292
286
  networkId?: string;
293
287
  /** Chat session ID for draft opportunities (stored as context.conversationId). */
294
288
  sessionId?: string;
295
- /** Reporter action gate forwarded into the persona prompt/context. */
296
- actionToolsEnabled?: boolean;
297
289
  }): Promise<ResolvedToolContext>;
298
290
  /**
299
291
  * Type for the `defineTool` closure created in `createChatTools`.
@@ -16,7 +16,7 @@ export class ChatContextAccessError extends Error {
16
16
  * This preloads user identity, profile, network memberships, and scoped index role.
17
17
  */
18
18
  export async function resolveChatContext(params) {
19
- const { database, userId, networkId, sessionId, actionToolsEnabled } = params;
19
+ const { database, userId, networkId, sessionId } = params;
20
20
  const [user, rawProfile, userNetworks, globalContext] = await Promise.all([
21
21
  database.getUser(userId),
22
22
  database.getProfile(userId),
@@ -93,7 +93,6 @@ export async function resolveChatContext(params) {
93
93
  scopedMembershipRole,
94
94
  isOnboarding: !(user.onboarding?.completedAt),
95
95
  hasName,
96
- actionToolsEnabled,
97
96
  ...(sessionId !== undefined ? { sessionId } : {}),
98
97
  };
99
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexnetwork/protocol",
3
- "version": "20.0.1-rc.485.1",
3
+ "version": "21.0.0-rc.486.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,36 +0,0 @@
1
- export type AgentCleanupActionType = "retract_premise" | "narrow_signal" | "pause_signal";
2
- export interface AgentActionProposalSnapshot {
3
- status: string;
4
- updatedAt?: string;
5
- payload?: string;
6
- summary?: string | null;
7
- assertionText?: string;
8
- }
9
- interface AgentActionProposalActionBase {
10
- entityId: string;
11
- currentState: string;
12
- proposedOperation: string;
13
- evidence?: string;
14
- skipped?: boolean;
15
- reason?: string;
16
- snapshot?: AgentActionProposalSnapshot;
17
- description?: string;
18
- }
19
- export type AgentActionProposalAction = (AgentActionProposalActionBase & {
20
- type: "retract_premise";
21
- }) | (AgentActionProposalActionBase & {
22
- type: "narrow_signal";
23
- }) | (AgentActionProposalActionBase & {
24
- type: "pause_signal";
25
- });
26
- export interface AgentActionProposal {
27
- proposalId: string;
28
- userId: string;
29
- conversationId?: string;
30
- actions: AgentActionProposalAction[];
31
- }
32
- /** Host persistence bridge for proposals; the reporter tool never mutates domain rows. */
33
- export interface AgentActionProposalStore {
34
- createProposal(proposal: AgentActionProposal): Promise<void>;
35
- }
36
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,17 +0,0 @@
1
- import type { ChatGraphCompositeDatabase, UserDatabase } from "../shared/interfaces/database.interface.js";
2
- import type { ChatTools, ResolvedToolContext, ToolContext } from "../shared/agent/tool.factory.js";
3
- import type { AgentActionProposalStore } from "./reporter.action.contracts.js";
4
- export type { AgentActionProposal, AgentActionProposalAction, AgentActionProposalSnapshot, AgentActionProposalStore, AgentCleanupActionType, } from "./reporter.action.contracts.js";
5
- export declare const AGENT_ACTION_PROPOSAL_FENCE = "agent_action_proposal";
6
- /**
7
- * Creates the reporter-only proposal tool. It validates owner state and records
8
- * snapshots, but intentionally has no mutation dependency.
9
- */
10
- export declare function createProposeCleanupActionsTool(input: {
11
- database: ChatGraphCompositeDatabase;
12
- userDb: UserDatabase;
13
- context: ResolvedToolContext;
14
- store: AgentActionProposalStore;
15
- }): ChatTools[number];
16
- /** Build the optional action tool from the host-provided ToolContext. */
17
- export declare function createReporterActionTool(deps: ToolContext, context: ResolvedToolContext, userDb: UserDatabase): ChatTools[number] | null;
@@ -1,145 +0,0 @@
1
- import { tool } from "@langchain/core/tools";
2
- import { z } from "zod";
3
- import { success } from "../shared/agent/tool.helpers.js";
4
- export const AGENT_ACTION_PROPOSAL_FENCE = "agent_action_proposal";
5
- const actionInputSchema = z.discriminatedUnion("type", [
6
- z.object({
7
- type: z.literal("retract_premise"),
8
- premiseId: z.string().trim().min(1),
9
- }).strict(),
10
- z.object({
11
- type: z.literal("narrow_signal"),
12
- intentId: z.string().trim().min(1),
13
- description: z.string().trim().min(1),
14
- }).strict(),
15
- z.object({
16
- type: z.literal("pause_signal"),
17
- intentId: z.string().trim().min(1),
18
- evidence: z.string().trim().min(1, "pause_signal evidence is required"),
19
- }).strict(),
20
- ]);
21
- const proposeCleanupActionsSchema = z.object({
22
- actions: z.array(actionInputSchema).min(1).max(5),
23
- }).strict();
24
- function isFullUuid(value) {
25
- return z.string().uuid().safeParse(value).success;
26
- }
27
- function skippedAction(action, entityId, proposedOperation, reason) {
28
- return {
29
- type: action.type,
30
- entityId,
31
- currentState: "UNKNOWN",
32
- proposedOperation,
33
- skipped: true,
34
- reason,
35
- ...(action.type === "pause_signal" ? { evidence: action.evidence } : {}),
36
- };
37
- }
38
- /**
39
- * Creates the reporter-only proposal tool. It validates owner state and records
40
- * snapshots, but intentionally has no mutation dependency.
41
- */
42
- export function createProposeCleanupActionsTool(input) {
43
- const { database, userDb, context, store } = input;
44
- return tool(async (query) => {
45
- const proposalId = crypto.randomUUID();
46
- const actions = [];
47
- for (const action of query.actions) {
48
- const entityId = action.type === "retract_premise" ? action.premiseId : action.intentId;
49
- const operation = action.type === "retract_premise"
50
- ? "RETRACT_PREMISE"
51
- : action.type === "narrow_signal" ? "NARROW_SIGNAL" : "PAUSE_SIGNAL";
52
- if (!isFullUuid(entityId)) {
53
- actions.push(skippedAction(action, entityId, operation, "A full UUID is required; suffixes and ambiguous IDs are not accepted."));
54
- continue;
55
- }
56
- if (action.type === "retract_premise") {
57
- const premise = await database.getPremise(entityId).catch(() => null);
58
- if (!premise || premise.userId !== context.userId) {
59
- actions.push(skippedAction(action, entityId, operation, "Premise not found or not owned by the authenticated user."));
60
- continue;
61
- }
62
- if (premise.status !== "ACTIVE") {
63
- actions.push({
64
- type: action.type,
65
- entityId,
66
- currentState: premise.status,
67
- proposedOperation: operation,
68
- skipped: true,
69
- reason: `Premise is already ${premise.status}.`,
70
- });
71
- continue;
72
- }
73
- actions.push({
74
- type: action.type,
75
- entityId,
76
- currentState: premise.status,
77
- proposedOperation: operation,
78
- snapshot: { status: premise.status, updatedAt: premise.updatedAt.toISOString(), assertionText: premise.assertion.text },
79
- });
80
- continue;
81
- }
82
- const intent = await userDb.getIntent(entityId).catch(() => null);
83
- if (!intent) {
84
- actions.push(skippedAction(action, entityId, operation, "Signal not found or not owned by the authenticated user."));
85
- continue;
86
- }
87
- const status = intent.status ?? "ACTIVE";
88
- if (intent.archivedAt || status === "FULFILLED" || status === "EXPIRED") {
89
- actions.push({
90
- type: action.type,
91
- entityId,
92
- currentState: intent.archivedAt ? "ARCHIVED" : status,
93
- proposedOperation: operation,
94
- ...(action.type === "pause_signal" ? { evidence: action.evidence } : {}),
95
- skipped: true,
96
- reason: "Signal is archived or terminal and cannot be changed.",
97
- });
98
- continue;
99
- }
100
- actions.push({
101
- type: action.type,
102
- entityId,
103
- currentState: status,
104
- proposedOperation: operation,
105
- ...(action.type === "pause_signal" ? { evidence: action.evidence } : {}),
106
- ...(action.type === "narrow_signal" ? { description: action.description } : {}),
107
- snapshot: {
108
- status,
109
- updatedAt: intent.updatedAt.toISOString(),
110
- payload: intent.payload,
111
- summary: intent.summary,
112
- },
113
- });
114
- }
115
- await store.createProposal({
116
- proposalId,
117
- userId: context.userId,
118
- ...(context.sessionId ? { conversationId: context.sessionId } : {}),
119
- actions,
120
- });
121
- const renderedActions = actions.map(({ snapshot: _snapshot, ...entry }) => entry);
122
- const block = `\`\`\`${AGENT_ACTION_PROPOSAL_FENCE}\n${JSON.stringify({ proposalId, actions: renderedActions })}\n\`\`\``;
123
- return success({
124
- proposed: true,
125
- proposalId,
126
- actions: renderedActions,
127
- message: `IMPORTANT: Include this \`\`\`agent_action_proposal code block EXACTLY as-is in your response. It is a REQUEST for owner confirmation; it performs no mutation until the owner confirms it in the UI:\n\n${block}`,
128
- });
129
- }, {
130
- name: "propose_cleanup_actions",
131
- description: "Prepare owner-confirmed cleanup actions from same-turn grounded reads. This tool never mutates data.",
132
- schema: proposeCleanupActionsSchema,
133
- });
134
- }
135
- /** Build the optional action tool from the host-provided ToolContext. */
136
- export function createReporterActionTool(deps, context, userDb) {
137
- if (!deps.actionToolsEnabled || !deps.actionProposalStore)
138
- return null;
139
- return createProposeCleanupActionsTool({
140
- database: deps.database,
141
- userDb,
142
- context,
143
- store: deps.actionProposalStore,
144
- });
145
- }
@@ -1,31 +0,0 @@
1
- import { type ChatTools, type ResolvedToolContext, type ToolContext } from "../shared/agent/tool.factory.js";
2
- import type { UserDatabase } from "../shared/interfaces/database.interface.js";
3
- import type { ChatPersonaConfig } from "./chat.persona.js";
4
- /** Public kickoff marker used by the Agent surface to request its opening briefing. */
5
- export { REPORTER_BRIEFING_KICKOFF } from "./reporter.prompt.js";
6
- /** Stable persona id persisted for read-only Agent reporting sessions. */
7
- export declare const REPORTER_PERSONA_ID = "reporter";
8
- /**
9
- * Exact positive allowlist for the reporter persona. New shared tools remain
10
- * unavailable until they are explicitly reviewed here.
11
- */
12
- export declare const REPORTER_TOOL_NAMES: readonly ["read_intents", "search_intents", "read_user_contexts", "preview_user_context", "read_premises", "read_networks", "read_network_memberships", "read_pending_questions", "list_opportunities", "read_activity_summary"];
13
- interface ReporterToolBoundary {
14
- context: ResolvedToolContext;
15
- userDb: UserDatabase;
16
- findPendingQuestions?: ToolContext["findPendingQuestions"];
17
- }
18
- /** Filters a shared registry through the reporter's positive allowlist. */
19
- export declare function filterReporterTools<T extends {
20
- name: string;
21
- }>(tools: T[]): T[];
22
- /**
23
- * Replaces shared tools whose normal modes can enumerate other users with
24
- * reporter-safe, self-only read contracts. Opportunity listing is deliberately
25
- * aggregate-only: it never returns a counterpart name, row, or explanation.
26
- */
27
- export declare function narrowReporterTools(allowed: ChatTools, boundary: ReporterToolBoundary): ChatTools;
28
- /** Creates the reporter's context-bound, allowlisted toolset. */
29
- export declare function createReporterTools(deps: ToolContext, preResolvedContext?: ResolvedToolContext): Promise<ChatTools>;
30
- /** Restricted read-only Agent reporter persona. */
31
- export declare const REPORTER_PERSONA: ChatPersonaConfig;
@@ -1,207 +0,0 @@
1
- import { tool } from "@langchain/core/tools";
2
- import { z } from "zod";
3
- import { createChatTools } from "../shared/agent/tool.factory.js";
4
- import { error, resolveChatContext, success } from "../shared/agent/tool.helpers.js";
5
- import { focusedNetworkId, scopeFromNetworkId } from "../shared/agent/tool.scope.js";
6
- import { buildReporterSystemContent, resolveReporterDeterministicResponse } from "./reporter.prompt.js";
7
- import { createReporterActionTool } from "./reporter.action.tools.js";
8
- /** Public kickoff marker used by the Agent surface to request its opening briefing. */
9
- export { REPORTER_BRIEFING_KICKOFF } from "./reporter.prompt.js";
10
- /** Stable persona id persisted for read-only Agent reporting sessions. */
11
- export const REPORTER_PERSONA_ID = "reporter";
12
- /**
13
- * Exact positive allowlist for the reporter persona. New shared tools remain
14
- * unavailable until they are explicitly reviewed here.
15
- */
16
- export const REPORTER_TOOL_NAMES = [
17
- "read_intents",
18
- "search_intents",
19
- "read_user_contexts",
20
- "preview_user_context",
21
- "read_premises",
22
- "read_networks",
23
- "read_network_memberships",
24
- "read_pending_questions",
25
- "list_opportunities",
26
- "read_activity_summary",
27
- ];
28
- const REPORTER_TOOL_ALLOWLIST = new Set(REPORTER_TOOL_NAMES);
29
- /** Filters a shared registry through the reporter's positive allowlist. */
30
- export function filterReporterTools(tools) {
31
- return tools.filter((candidate) => REPORTER_TOOL_ALLOWLIST.has(candidate.name));
32
- }
33
- function invokeSharedTool(sharedTool, input) {
34
- return sharedTool.invoke(input);
35
- }
36
- /**
37
- * Replaces shared tools whose normal modes can enumerate other users with
38
- * reporter-safe, self-only read contracts. Opportunity listing is deliberately
39
- * aggregate-only: it never returns a counterpart name, row, or explanation.
40
- */
41
- export function narrowReporterTools(allowed, boundary) {
42
- const { context, userDb } = boundary;
43
- return allowed.map((sharedTool) => {
44
- if (sharedTool.name === "read_intents") {
45
- return tool(async (query) => {
46
- const limit = query.limit ?? 100;
47
- const page = query.page ?? 1;
48
- const intents = await userDb.getActiveIntents();
49
- const start = (page - 1) * limit;
50
- return success({
51
- intents: intents.slice(start, start + limit),
52
- page,
53
- limit,
54
- total: intents.length,
55
- });
56
- }, {
57
- name: "read_intents",
58
- description: "Read the authenticated user's own active signals only.",
59
- schema: z.object({
60
- limit: z.number().int().min(1).max(100).optional(),
61
- page: z.number().int().min(1).optional(),
62
- }).strict(),
63
- });
64
- }
65
- if (sharedTool.name === "search_intents") {
66
- return tool(async (query) => success({
67
- intents: await userDb.searchOwnIntents(query.query, query.limit ?? 20),
68
- }), {
69
- name: "search_intents",
70
- description: "Search the authenticated user's own active signals only.",
71
- schema: z.object({
72
- query: z.string().trim().min(1),
73
- limit: z.number().int().min(1).max(100).optional(),
74
- }).strict(),
75
- });
76
- }
77
- if (sharedTool.name === "read_user_contexts") {
78
- return tool(async () => invokeSharedTool(sharedTool, {}), {
79
- name: "read_user_contexts",
80
- description: "Read the authenticated user's own identity and global context only.",
81
- schema: z.object({}).strict(),
82
- });
83
- }
84
- if (sharedTool.name === "read_premises") {
85
- return tool(async (query) => invokeSharedTool(sharedTool, {
86
- includeRetracted: query.includeRetracted ?? false,
87
- userId: context.userId,
88
- }), {
89
- name: "read_premises",
90
- description: "Read the authenticated user's own premises only.",
91
- schema: z.object({ includeRetracted: z.boolean().optional() }).strict(),
92
- });
93
- }
94
- if (sharedTool.name === "read_network_memberships") {
95
- return tool(async () => invokeSharedTool(sharedTool, {}), {
96
- name: "read_network_memberships",
97
- description: "Read the authenticated user's own network memberships only.",
98
- schema: z.object({}).strict(),
99
- });
100
- }
101
- if (sharedTool.name === "read_networks") {
102
- return tool(async () => invokeSharedTool(sharedTool, { userId: context.userId }), {
103
- name: "read_networks",
104
- description: "Read networks available to the authenticated user.",
105
- schema: z.object({}).strict(),
106
- });
107
- }
108
- if (sharedTool.name === "read_pending_questions") {
109
- return tool(async (query) => {
110
- if (!boundary.findPendingQuestions)
111
- return error("Question lookup is not available.");
112
- const questions = await boundary.findPendingQuestions(context.userId, {
113
- modes: ["intent"],
114
- limit: query.limit ?? 10,
115
- });
116
- return success({ questions: questions.slice(0, query.limit ?? 10) });
117
- }, {
118
- name: "read_pending_questions",
119
- description: "Read the user's own non-negotiation pending questions; answering is unavailable here.",
120
- schema: z.object({ limit: z.number().int().min(1).max(10).optional() }).strict(),
121
- });
122
- }
123
- if (sharedTool.name === "list_opportunities") {
124
- return tool(async (query) => {
125
- const scopedNetworkId = focusedNetworkId(context);
126
- if (scopedNetworkId && query.networkId && query.networkId !== scopedNetworkId) {
127
- return error("This chat is scoped to a different network.");
128
- }
129
- const activeIntents = await userDb.getActiveIntents();
130
- const intentById = new Map(activeIntents.map((intent) => [intent.id, intent]));
131
- const opportunities = await userDb.getOpportunitiesForUser({
132
- ...(query.networkId || scopedNetworkId ? { networkId: query.networkId || scopedNetworkId } : {}),
133
- statuses: ["draft", "pending", "latent"],
134
- limit: 100,
135
- });
136
- const counts = new Map();
137
- const seen = new Set();
138
- for (const opportunity of opportunities) {
139
- const ownIntentIds = new Set(opportunity.actors
140
- .filter((actor) => actor.userId === context.userId && actor.intent && intentById.has(actor.intent))
141
- .map((actor) => actor.intent));
142
- if (ownIntentIds.size === 0)
143
- continue;
144
- seen.add(opportunity.id);
145
- for (const intentId of ownIntentIds) {
146
- const intent = intentById.get(intentId);
147
- if (!intent)
148
- continue;
149
- const existing = counts.get(intentId) ?? {
150
- intentId,
151
- title: intent.summary?.trim() || intent.payload,
152
- count: 0,
153
- };
154
- existing.count += 1;
155
- counts.set(intentId, existing);
156
- }
157
- }
158
- return success({
159
- found: seen.size > 0,
160
- count: seen.size,
161
- bySignal: [...counts.values()],
162
- });
163
- }, {
164
- name: "list_opportunities",
165
- description: "Report current opportunity counts by the user's own signal, without counterpart identities or rows.",
166
- schema: z.object({ networkId: z.string().uuid().optional() }).strict(),
167
- });
168
- }
169
- return sharedTool;
170
- });
171
- }
172
- /** Creates the reporter's context-bound, allowlisted toolset. */
173
- export async function createReporterTools(deps, preResolvedContext) {
174
- const explicitScope = deps.scopeType && deps.scopeId
175
- ? { scopeType: deps.scopeType, scopeId: deps.scopeId }
176
- : scopeFromNetworkId(deps.networkId);
177
- const resolvedContext = preResolvedContext ?? await resolveChatContext({
178
- database: deps.database,
179
- userId: deps.userId,
180
- networkId: explicitScope.scopeType === "network" ? explicitScope.scopeId : deps.networkId,
181
- sessionId: deps.sessionId,
182
- actionToolsEnabled: deps.actionToolsEnabled,
183
- });
184
- if (explicitScope.scopeType && explicitScope.scopeId) {
185
- resolvedContext.scopeType = explicitScope.scopeType;
186
- resolvedContext.scopeId = explicitScope.scopeId;
187
- }
188
- const userDb = deps.userDb ?? deps.createUserDatabase(deps.database, resolvedContext.userId);
189
- const allowed = filterReporterTools(await createChatTools(deps, resolvedContext));
190
- const narrowed = narrowReporterTools(allowed, {
191
- context: resolvedContext,
192
- userDb,
193
- findPendingQuestions: deps.findPendingQuestions,
194
- });
195
- const actionTool = createReporterActionTool(deps, resolvedContext, userDb);
196
- return actionTool ? [...narrowed, actionTool] : narrowed;
197
- }
198
- /** Restricted read-only Agent reporter persona. */
199
- export const REPORTER_PERSONA = {
200
- id: REPORTER_PERSONA_ID,
201
- buildSystemContent: (ctx, iterCtx) => buildReporterSystemContent(ctx, iterCtx),
202
- createTools: (deps, preResolvedContext) => createReporterTools(deps, preResolvedContext),
203
- resolveDeterministicResponse: (_ctx, iterCtx) => resolveReporterDeterministicResponse(iterCtx),
204
- loopBehaviors: {
205
- hallucinationRecovery: false,
206
- },
207
- };
@@ -1,24 +0,0 @@
1
- import type { ResolvedToolContext } from "../shared/agent/tool.factory.js";
2
- import type { IterationContext } from "./chat.prompt.modules.js";
3
- /** Stable marker used by the Agent surface to request its opening briefing. */
4
- export declare const REPORTER_BRIEFING_KICKOFF = "reporter-briefing-kickoff";
5
- /**
6
- * Recognizes the explicit opening briefing marker without putting ordinary
7
- * reporter conversations into briefing mode accidentally.
8
- *
9
- * @param message - Latest user message in the current turn
10
- * @returns Whether this turn is the Agent-surface briefing kickoff
11
- */
12
- export declare function isReporterBriefingKickoff(message?: string): boolean;
13
- /** Recognizes a short acknowledgement only when a prior proposal is visible. */
14
- export declare function isReporterActionConfirmation(message?: string, hasPriorProposal?: boolean): boolean;
15
- /** Deterministic reporter response for contextual typed acknowledgements. */
16
- export declare function resolveReporterDeterministicResponse(iterCtx: IterationContext): string | null;
17
- /**
18
- * Builds the read-only reporter persona prompt.
19
- *
20
- * @param ctx - Resolved authenticated user context
21
- * @param iterCtx - Current agent-loop context used for briefing kickoff
22
- * @returns Complete reporter system content
23
- */
24
- export declare function buildReporterSystemContent(ctx: ResolvedToolContext, iterCtx?: IterationContext): string;
@@ -1,149 +0,0 @@
1
- /** Stable marker used by the Agent surface to request its opening briefing. */
2
- export const REPORTER_BRIEFING_KICKOFF = "reporter-briefing-kickoff";
3
- /**
4
- * Recognizes the explicit opening briefing marker without putting ordinary
5
- * reporter conversations into briefing mode accidentally.
6
- *
7
- * @param message - Latest user message in the current turn
8
- * @returns Whether this turn is the Agent-surface briefing kickoff
9
- */
10
- export function isReporterBriefingKickoff(message) {
11
- const normalized = message?.trim().toLocaleLowerCase()
12
- .replace(/[–—]/g, "-")
13
- .replace(/^_+|_+$/g, "");
14
- if (!normalized)
15
- return false;
16
- return normalized === REPORTER_BRIEFING_KICKOFF;
17
- }
18
- /** Recognizes a short acknowledgement only when a prior proposal is visible. */
19
- export function isReporterActionConfirmation(message, hasPriorProposal = false) {
20
- const normalized = message?.trim().toLocaleLowerCase()
21
- .replace(/[.!?,;:]+$/g, "")
22
- .replace(/\s+/g, " ");
23
- if (!normalized || !hasPriorProposal)
24
- return false;
25
- return new Set([
26
- "i confirm",
27
- "confirm",
28
- "confirm it",
29
- "approve",
30
- "approved",
31
- "approve it",
32
- "i approve",
33
- "yes",
34
- "yes i confirm",
35
- "yes please",
36
- "please do it",
37
- "proceed",
38
- "go ahead",
39
- ]).has(normalized);
40
- }
41
- /** Deterministic reporter response for contextual typed acknowledgements. */
42
- export function resolveReporterDeterministicResponse(iterCtx) {
43
- if (!isReporterActionConfirmation(iterCtx.currentMessage, iterCtx.hasPriorAgentActionProposal === true))
44
- return null;
45
- return "Use the visible proposal card's Confirm control to approve this request. I won't run or recreate it from a chat acknowledgement.";
46
- }
47
- function buildBriefingGuidance(iterCtx) {
48
- if (!isReporterBriefingKickoff(iterCtx?.currentMessage))
49
- return "";
50
- return `
51
-
52
- ## Opening briefing
53
- This is the Agent-surface briefing kickoff. Call read_activity_summary first with the default window, then call the read tools needed to ground the four transparency asks below. Present one concise briefing covering:
54
- 1. summarize all my signals;
55
- 2. what did you do today?;
56
- 3. how do I look to others?;
57
- 4. what should I sharpen?
58
- Do not claim a metric unless it appears in a tool result from this turn. If a section has no grounded data, say that plainly rather than filling the gap.`;
59
- }
60
- function buildTurnGuidance(iterCtx) {
61
- const currentMessage = iterCtx?.currentMessage;
62
- const confirmation = isReporterActionConfirmation(currentMessage, iterCtx?.hasPriorAgentActionProposal === true);
63
- return `
64
-
65
- ## Turn discipline
66
- - The detailed four-section opening briefing is reserved for the exact reporter-briefing-kickoff marker. Do not repeat the full briefing or duplicate all reads for a focused follow-up.
67
- - For every non-kickoff message, answer only the user's current request and perform only the reads needed to answer it.
68
- ${confirmation ? `- This message is only a natural-language acknowledgement. Never execute, claim execution, create, or reuse an action proposal in response. Do not call read_activity_summary or propose_cleanup_actions. Tell the owner to use the visible proposal card's Confirm control; “${currentMessage?.trim()}” is not endpoint confirmation.` : ""}`;
69
- }
70
- /**
71
- * Builds the read-only reporter persona prompt.
72
- *
73
- * @param ctx - Resolved authenticated user context
74
- * @param iterCtx - Current agent-loop context used for briefing kickoff
75
- * @returns Complete reporter system content
76
- */
77
- export function buildReporterSystemContent(ctx, iterCtx) {
78
- const userContext = JSON.stringify(ctx.user, null, 2);
79
- const profileContext = ctx.userProfile
80
- ? JSON.stringify(ctx.userProfile, null, 2)
81
- : "null";
82
- const membershipContext = JSON.stringify(ctx.userNetworks.map((network) => ({
83
- id: network.networkId,
84
- title: network.networkTitle,
85
- isPersonal: network.isPersonal,
86
- })), null, 2);
87
- const roleGuidance = ctx.actionToolsEnabled
88
- ? "Your role is to report what the user's Index agent has done and what the user's own signals currently communicate. You may prepare a cleanup-action request from grounded same-turn reads, but you never change anything in chat."
89
- : "Your role is to report what the user's Index agent has done and what the user's own signals currently communicate. You observe; you never change anything. Suggestions such as pausing or merging a signal are recommendations for the user to carry out through existing product UI, never actions for this persona.";
90
- const mutationRule = ctx.actionToolsEnabled
91
- ? "- Never mutate data in chat. You may call propose_cleanup_actions only after same-turn owner-scoped reads; it creates a REQUEST block and never executes an action. The owner must confirm through the product UI."
92
- : "- Never create, update, delete, confirm, answer, remember, forget, assign, discover, negotiate, scrape, or otherwise mutate data. Do not ask the user a question through a tool.";
93
- const actionGuidance = ctx.actionToolsEnabled ? `
94
-
95
- ## Cleanup-action requests
96
- - You may propose only retract_premise, narrow_signal, and pause_signal actions grounded in read results from this same turn.
97
- - Resolve references through owner-scoped read tools and pass exact full UUIDs only; never use suffixes, guesses, or IDs from another user.
98
- - pause_signal requires non-empty evidence recorded from this turn, such as zero live opportunities plus the owner's statement.
99
- - The proposal block is a REQUEST for owner confirmation. Never narrate an action as completed and never mutate inside chat.
100
- - Do not propose actions for counterparties or expose counterparty identity.
101
- ` : "";
102
- return `You are Agent, the user's private read-only activity reporter for ${ctx.userName}.
103
-
104
- ${roleGuidance}${actionGuidance}
105
-
106
- ## Hard rules
107
- - Every factual claim, number, status, or trend must come from a tool result in the current turn. Never invent, estimate, or reuse an unverified metric.
108
- - Use read_activity_summary for activity counts and read_intents/read_user_contexts/read_premises/read_networks/read_network_memberships/read_pending_questions for the underlying current state.
109
- - Counterparties are identity-free aggregate data only: never reveal names, IDs, transcripts, message text, or per-counterparty rows. Do not infer what another person thinks from a match or negotiation.
110
- ${mutationRule}
111
- - Do not write observed behavior back as a preference or premise. The user decides whether to act on a suggestion.
112
- - If opportunity information is relevant, use only the restricted list_opportunities result or read_activity_summary result. Do not expose raw evaluator reasoning, matchReason, or internal JSON. Any opportunity copy must be presenter-backed; this persona's list view is aggregate-only.
113
- - Be transparent about missing data and the reporting window. Keep the response concise, calm, and useful without hype.
114
-
115
- ## Four transparency asks
116
- Be ready to answer:
117
- - “summarize all my signals” — read the user's own signals and describe their current themes without inventing a synthesis.
118
- - “what did you do today?” — report only grounded activity counts from the requested window.
119
- - “how do I look to others?” — describe only the user's own stored context and signals; do not claim access to private counterparty opinions.
120
- - “what should I sharpen?” — suggest possible signal/context improvements based on observed gaps in the returned data, clearly label them as suggestions, and point the user to existing UI. Never apply them.
121
-
122
- ## Allowed capabilities
123
- - Own signal reads: read_intents, search_intents.
124
- - Own context reads: read_user_contexts, preview_user_context, read_premises.
125
- - Own community context: read_networks, read_network_memberships.
126
- - Own pending-question reads: read_pending_questions (never answer them).
127
- - Aggregate activity reporting: read_activity_summary.
128
- - Aggregate current opportunity reporting: list_opportunities (no counterpart identities or rows).${ctx.actionToolsEnabled ? "\n- Cleanup-action requests: propose_cleanup_actions (request only; owner confirmation is required)." : ""}
129
-
130
- ## Session identity (preloaded)
131
- - User: ${ctx.userName} (${ctx.userEmail}), id: ${ctx.userId}
132
-
133
- ### User record
134
- \`\`\`json
135
- ${userContext}
136
- \`\`\`
137
-
138
- ### User context
139
- \`\`\`json
140
- ${profileContext}
141
- \`\`\`
142
-
143
- ### Current memberships
144
- \`\`\`json
145
- ${membershipContext}
146
- \`\`\`
147
-
148
- Only the identity, context, and membership metadata above are preloaded. Signals, premises, questions, and activity are not preloaded: call the appropriate read/report tool before describing them.${buildTurnGuidance(iterCtx)}${buildBriefingGuidance(iterCtx)}`;
149
- }