@indexnetwork/protocol 3.6.2 → 3.6.3-rc.271.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/README.md +90 -26
- package/dist/opportunity/question.prompt.d.ts +1 -1
- package/dist/opportunity/question.prompt.d.ts.map +1 -1
- package/dist/opportunity/question.prompt.js +8 -0
- package/dist/opportunity/question.prompt.js.map +1 -1
- package/dist/questioner/questioner.presets.d.ts.map +1 -1
- package/dist/questioner/questioner.presets.js +23 -2
- package/dist/questioner/questioner.presets.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,23 +32,39 @@ const tools = await createChatTools({
|
|
|
32
32
|
|
|
33
33
|
### 2. Implement the adapters
|
|
34
34
|
|
|
35
|
-
The package defines interfaces — your application provides the concrete implementations
|
|
35
|
+
The package defines interfaces — your application provides the concrete implementations.
|
|
36
|
+
|
|
37
|
+
**Required** (always needed by `createChatTools`):
|
|
36
38
|
|
|
37
39
|
| Interface | Responsibility |
|
|
38
40
|
|---|---|
|
|
39
|
-
| `ChatGraphCompositeDatabase` | Core data access (users, intents, indexes, opportunities) |
|
|
41
|
+
| `ChatGraphCompositeDatabase` | Core data access (users, intents, indexes/networks, opportunities) |
|
|
42
|
+
| `UserDatabase` / `SystemDatabase` | Context-bound databases built by `createUserDatabase` / `createSystemDatabase` |
|
|
40
43
|
| `Embedder` | Vector embeddings for semantic search |
|
|
41
44
|
| `Scraper` | Web content extraction |
|
|
42
|
-
| `Cache` / `HydeCache` | Result caching |
|
|
45
|
+
| `Cache` / `HydeCache` | Result caching (HyDE may share the general cache) |
|
|
43
46
|
| `IntegrationAdapter` | OAuth and external tool actions |
|
|
44
|
-
| `ContactServiceAdapter` | Contact management |
|
|
45
47
|
| `IntentGraphQueue` | Background intent processing queue |
|
|
48
|
+
| `ContactServiceAdapter` | Contact management |
|
|
46
49
|
| `ChatSessionReader` | Load conversation history |
|
|
47
50
|
| `ProfileEnricher` | Enrich profiles from external sources |
|
|
48
51
|
| `NegotiationGraphDatabase` | Negotiation state persistence |
|
|
52
|
+
|
|
53
|
+
**Optional** (enable specific capabilities; omit to run without that feature):
|
|
54
|
+
|
|
55
|
+
| Interface | Responsibility |
|
|
56
|
+
|---|---|
|
|
49
57
|
| `AgentDatabase` | Agent registry CRUD (agents, transports, permissions) |
|
|
50
|
-
| `AgentDispatcher` | Resolves and invokes agents during negotiation turns |
|
|
51
|
-
| `McpAuthResolver` | Resolves `{ userId, agentId }` from an incoming MCP HTTP request |
|
|
58
|
+
| `AgentDispatcher` | Resolves and invokes personal agents during negotiation turns — required to register the negotiation tools |
|
|
59
|
+
| `McpAuthResolver` | Resolves `{ userId, agentId }` from an incoming MCP HTTP request (MCP server only) |
|
|
60
|
+
| `DeliveryLedger` | Commits OpenClaw opportunity-delivery rows |
|
|
61
|
+
| `DiscoveryRunStore` / `DiscoveryRunQueue` | Persist and execute async MCP discovery runs |
|
|
62
|
+
| `ProfileRunStore` / `ProfileRunQueue` | Persist and execute async MCP profile builds |
|
|
63
|
+
| `MintConnectLink` | Mints short connect links for opportunity accepts |
|
|
64
|
+
| `ChatSummaryReader` | Read-through chat-session digest |
|
|
65
|
+
| `ChatMessageWriter` | Writes user messages into the most-recent chat session (MCP elicitation) |
|
|
66
|
+
| `QuestionGeneratorReader` / `QuestionerDatabase` | Decision-question generation and persistence |
|
|
67
|
+
| `NegotiationSummaryReader` | Negotiation-digest summarization (falls back to deterministic digests) |
|
|
52
68
|
|
|
53
69
|
All interfaces are exported from the package root — import them with `import type { ... } from "@indexnetwork/protocol"`.
|
|
54
70
|
|
|
@@ -61,36 +77,84 @@ import { createChatTools } from "@indexnetwork/protocol";
|
|
|
61
77
|
|
|
62
78
|
const tools = await createChatTools({
|
|
63
79
|
userId: "user-uuid",
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
database,
|
|
67
|
-
embedder,
|
|
68
|
-
scraper,
|
|
69
|
-
cache,
|
|
70
|
-
hydeCache,
|
|
71
|
-
integration,
|
|
72
|
-
intentQueue,
|
|
73
|
-
contactService,
|
|
74
|
-
chatSession,
|
|
75
|
-
enricher,
|
|
76
|
-
negotiationDatabase,
|
|
77
|
-
integrationImporter,
|
|
78
|
-
createUserDatabase,
|
|
79
|
-
createSystemDatabase,
|
|
80
|
+
|
|
81
|
+
// ── Required adapters ──
|
|
82
|
+
database, // ChatGraphCompositeDatabase
|
|
83
|
+
embedder, // Embedder
|
|
84
|
+
scraper, // Scraper
|
|
85
|
+
cache, // Cache
|
|
86
|
+
hydeCache, // HydeCache
|
|
87
|
+
integration, // IntegrationAdapter
|
|
88
|
+
intentQueue, // IntentGraphQueue
|
|
89
|
+
contactService, // ContactServiceAdapter
|
|
90
|
+
chatSession, // ChatSessionReader
|
|
91
|
+
enricher, // ProfileEnricher
|
|
92
|
+
negotiationDatabase, // NegotiationGraphDatabase
|
|
93
|
+
integrationImporter, // bulk contact import
|
|
94
|
+
createUserDatabase, // (db, userId) => UserDatabase
|
|
95
|
+
createSystemDatabase, // (db, userId, indexScope, embedder?) => SystemDatabase
|
|
96
|
+
|
|
97
|
+
// ── Optional scoping ──
|
|
98
|
+
networkId: "optional-network-uuid", // scope tools to a specific index/network
|
|
99
|
+
sessionId: "chat-session-id", // enables draft opportunities with conversation context
|
|
100
|
+
|
|
101
|
+
// ── Optional capabilities (enable when the host supports them) ──
|
|
102
|
+
agentDatabase, // AgentDatabase — agent registry
|
|
103
|
+
agentDispatcher, // AgentDispatcher — routes negotiation turns to personal agents
|
|
104
|
+
deliveryLedger, // DeliveryLedger — OpenClaw delivery commits
|
|
105
|
+
discoveryRuns, // DiscoveryRunStore + discoveryRunQueue — async MCP discovery
|
|
106
|
+
profileRuns, // ProfileRunStore + profileRunQueue — async MCP profile builds
|
|
107
|
+
mintConnectLink, // short connect links for opportunity accepts
|
|
108
|
+
modelConfig, // override chat model / reasoning effort (see above)
|
|
80
109
|
});
|
|
81
110
|
|
|
82
111
|
// tools is an array of LangChain Tool objects ready to bind to an agent
|
|
83
112
|
```
|
|
84
113
|
|
|
114
|
+
`createChatTools` accepts a single `ToolContext` object. The required adapters
|
|
115
|
+
above are always needed; the optional capabilities default to a degraded-but-
|
|
116
|
+
functional mode when omitted (e.g. without `agentDispatcher` the negotiation
|
|
117
|
+
tools are not registered, and without `discoveryRuns` MCP discovery runs
|
|
118
|
+
synchronously).
|
|
119
|
+
|
|
85
120
|
## Graphs
|
|
86
121
|
|
|
87
|
-
For direct graph invocation (bypassing the tool layer),
|
|
122
|
+
For direct graph invocation (bypassing the tool layer), a `*GraphFactory` class is exported for each workflow:
|
|
88
123
|
|
|
89
124
|
```typescript
|
|
90
|
-
import {
|
|
125
|
+
import {
|
|
126
|
+
ChatGraphFactory,
|
|
127
|
+
IntentGraphFactory,
|
|
128
|
+
OpportunityGraphFactory,
|
|
129
|
+
ProfileGraphFactory,
|
|
130
|
+
PremiseGraphFactory,
|
|
131
|
+
NegotiationGraphFactory,
|
|
132
|
+
HydeGraphFactory,
|
|
133
|
+
NetworkGraphFactory,
|
|
134
|
+
NetworkMembershipGraphFactory,
|
|
135
|
+
IntentNetworkGraphFactory,
|
|
136
|
+
HomeGraphFactory,
|
|
137
|
+
MaintenanceGraphFactory,
|
|
138
|
+
} from "@indexnetwork/protocol";
|
|
91
139
|
```
|
|
92
140
|
|
|
93
|
-
Each factory
|
|
141
|
+
Each factory takes its typed dependencies in the constructor and exposes a
|
|
142
|
+
`.createGraph()` method that returns a compiled LangGraph ready for `.invoke()`.
|
|
143
|
+
|
|
144
|
+
| Factory | Workflow |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `ChatGraphFactory` | ReAct chat loop — LLM calls tools, responds to the user |
|
|
147
|
+
| `IntentGraphFactory` | Clarify, infer, verify felicity, reconcile, and persist intents |
|
|
148
|
+
| `OpportunityGraphFactory` | HyDE-based discovery: search, evaluate (valency), rank, persist |
|
|
149
|
+
| `ProfileGraphFactory` | Generate/update user profiles with scraping and embedding |
|
|
150
|
+
| `PremiseGraphFactory` | Decompose and index a user's premises |
|
|
151
|
+
| `NegotiationGraphFactory` | Multi-turn bilateral negotiation flows |
|
|
152
|
+
| `HydeGraphFactory` | Generate hypothetical documents and embed them (cache-aware) |
|
|
153
|
+
| `NetworkGraphFactory` | Manage index/network CRUD |
|
|
154
|
+
| `NetworkMembershipGraphFactory` | Manage index/network member join/leave |
|
|
155
|
+
| `IntentNetworkGraphFactory` | Evaluate and assign/unassign intents to indexes |
|
|
156
|
+
| `HomeGraphFactory` | Categorize and curate home-feed content |
|
|
157
|
+
| `MaintenanceGraphFactory` | Periodic maintenance (feed health, opportunity expiration) |
|
|
94
158
|
|
|
95
159
|
## MCP server
|
|
96
160
|
|
|
@@ -165,7 +229,7 @@ git push <remote> dev
|
|
|
165
229
|
git push <remote> main
|
|
166
230
|
```
|
|
167
231
|
|
|
168
|
-
`dev` publishes prerelease versions derived from `package.json` using npm's `rc` tag, for example `
|
|
232
|
+
`dev` publishes prerelease versions derived from `package.json` using npm's `rc` tag, for example `3.6.3-rc.123.1`. `main` publishes the base version from `package.json` to `latest` only when that version is not already on npm.
|
|
169
233
|
|
|
170
234
|
Or publish manually from `packages/protocol/`:
|
|
171
235
|
|
|
@@ -87,7 +87,7 @@ export interface DiscoveryQuestionInput {
|
|
|
87
87
|
now: string;
|
|
88
88
|
}
|
|
89
89
|
/** @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version. */
|
|
90
|
-
export declare const SYSTEM_PROMPT = "You help write user-facing follow-up questions after Index has reviewed potential connections for a human. Your job: surface the minimum set of structured decision questions the human must answer to make the next discovery turn sharper, or improve their outlook on the intent.\n\nYou may pick from five strategies. Choose contextually; mix when multiple questions genuinely complement.\n- refine_intent: ask the user to sharpen or pivot their original signal.\n- surface_missing_detail: ask for one concrete missing input (stage, location, timing, scope, \u2026).\n- open_adjacent_thread: offer a pivot suggested by recurring connection signals.\n- reflective_summary: mirror what the connection review revealed and ask the user to decide.\n- surface_emergent_knowledge: cite a fact you learned from the connection review and ask the user to decide in light of it.\n\nAsk a question only when ALL of these hold:\n1. Index cannot resolve the decision autonomously from the evidence shown.\n2. The answer would materially change which people surface next.\n3. The same fact is NOT already in chatContext.statedFacts, NOT already asked in chatContext.openQuestions, and NOT already shared in chatContext.surfacedFindings.\n\nStandalone prompt rule. Every generated `prompt` must be understandable outside the conversation where it was created. Naturally include the original query, discovery pattern, connection pattern, or concrete learned fact in the question text itself. Do not rely on `title`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about. For example, prefer \"For your AI crypto decentralized deep-tech search, which area is most critical right now?\" over \"Which area is most critical right now?\"\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first (e.g. one surface_emergent_knowledge + one refine_intent). Add a third only when there are \u22653 substantive people reviewed and three distinct strategies each unblock a real decision. Two questions of the same strategy are acceptable only if their decision domains differ (different titles). Avoid stacking three pulls (info-from-user); balance with pushes (info-to-user via reflective_summary / surface_emergent_knowledge).\n\nOrdering. Questions whose answer unblocks the most connection reviews come first; then highest-impact; then ambiguity-clarifying. Reviews that needed more detail or ran out of time signal under-specification \u2014 prioritize.\n\nUser-facing language. Every title, prompt, option label, and option description is shown directly to the user. Never mention raw protocol mechanics or internal labels such as \"agent\", \"patient\", \"peer\", \"suggestedRoles\", \"role distribution\", \"counterparty\", \"negotiation\", \"turn_cap\", \"timeout\", or \"candidate\". Use natural language instead: people, matches, connections, mutual collaboration, someone who can help, or someone seeking help.\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2\u20134 options. Never add an \"Other\" option \u2014 clients provide a free-text fallback automatically. For surface_emergent_knowledge questions, anchor the prompt in the concrete cited fact (\"Multiple people flagged that\u2026\") and let the options represent decisions in light of that fact, not different versions of the fact.\n\nTitle rules. \u226412 chars. Noun of the decision domain. Discovery examples: \"Stage\", \"Timing\", \"Role\", \"Location\", \"Stack\", \"Budget\", \"Scope\", \"Format\".\n\nAnti-patterns \u2014 never do these.\n- Don't ask procedural confirmations (\"Should I look again?\").\n- Don't ask about hypothetical edge cases that didn't occur.\n- Don't ask about specific person identities; treat the provided person summary as the only allowed reference.\n- Don't repeat anything in chatContext.openQuestions.\n- Don't re-surface anything in chatContext.surfacedFindings.\n- Don't ask for facts in chatContext.statedFacts.\n\nOutput. Return at most 3 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the five values). If nothing is worth asking, return \"questions\": [].";
|
|
90
|
+
export declare const SYSTEM_PROMPT = "You help write user-facing follow-up questions after Index has reviewed potential connections for a human. Your job: surface the minimum set of structured decision questions the human must answer to make the next discovery turn sharper, or improve their outlook on the intent.\n\nYou may pick from five strategies. Choose contextually; mix when multiple questions genuinely complement.\n- refine_intent: ask the user to sharpen or pivot their original signal.\n- surface_missing_detail: ask for one concrete missing input (stage, location, timing, scope, \u2026).\n- open_adjacent_thread: offer a pivot suggested by recurring connection signals.\n- reflective_summary: mirror what the connection review revealed and ask the user to decide.\n- surface_emergent_knowledge: cite a fact you learned from the connection review and ask the user to decide in light of it.\n\nAsk a question only when ALL of these hold:\n1. Index cannot resolve the decision autonomously from the evidence shown.\n2. The answer would materially change which people surface next.\n3. The same fact is NOT already in chatContext.statedFacts, NOT already asked in chatContext.openQuestions, and NOT already shared in chatContext.surfacedFindings.\n\nStandalone prompt rule. Every generated `prompt` must be understandable outside the conversation where it was created. Naturally include the original query, discovery pattern, connection pattern, or concrete learned fact in the question text itself. Do not rely on `title`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about. For example, prefer \"For your AI crypto decentralized deep-tech search, which area is most critical right now?\" over \"Which area is most critical right now?\"\n\nReferential closure. The prompt must resolve entirely on its own, with no dangling references. The reader sees ONLY the question text \u2014 never the people you reviewed, the events on their calendar, or this conversation. Do not use demonstratives or definite anaphora that point at things the reader cannot see: \"these builders\", \"those founders\", \"these researchers\", \"these conversations\", \"this lunch\", \"the speaker\". If you reference a person, name them. If you reference a group, restate the concrete shared attribute inside the question itself (\"founders working on decentralized identity\"), never \"these founders\". Never imply a list, set, or prior exchange the reader is not currently looking at.\n- Bad: \"What kind of collaboration are you looking for with these builders?\"\n- Good: \"You're meeting people building agent infrastructure \u2014 what kind of collaboration are you looking for?\"\n\nNo process narration. Never describe Index's own activity or internal state. Forbidden: \"the previous negotiation\", \"the negotiation stalled\", \"opportunities found so far\", \"my search\", \"the counterparty\", \"candidates reviewed\", restating why a match did or did not happen, or quoting words a counterparty did or did not use. Ask about the user's goal or intent directly, never about the matching pipeline.\n- Bad: \"All opportunities found so far are related to 'Edge Esmeralda'. Would you like to broaden the search?\"\n- Good: \"Do you want to focus on people at Edge Esmeralda, or also connect beyond it?\"\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first (e.g. one surface_emergent_knowledge + one refine_intent). Add a third only when there are \u22653 substantive people reviewed and three distinct strategies each unblock a real decision. Two questions of the same strategy are acceptable only if their decision domains differ (different titles). Avoid stacking three pulls (info-from-user); balance with pushes (info-to-user via reflective_summary / surface_emergent_knowledge).\n\nOrdering. Questions whose answer unblocks the most connection reviews come first; then highest-impact; then ambiguity-clarifying. Reviews that needed more detail or ran out of time signal under-specification \u2014 prioritize.\n\nUser-facing language. Every title, prompt, option label, and option description is shown directly to the user. Never mention raw protocol mechanics or internal labels such as \"agent\", \"patient\", \"peer\", \"suggestedRoles\", \"role distribution\", \"counterparty\", \"negotiation\", \"turn_cap\", \"timeout\", or \"candidate\". Use natural language instead: people, matches, connections, mutual collaboration, someone who can help, or someone seeking help.\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2\u20134 options. Never add an \"Other\" option \u2014 clients provide a free-text fallback automatically. For surface_emergent_knowledge questions, anchor the prompt in the concrete cited fact (\"Multiple people flagged that\u2026\") and let the options represent decisions in light of that fact, not different versions of the fact.\n\nTitle rules. \u226412 chars. Noun of the decision domain. Discovery examples: \"Stage\", \"Timing\", \"Role\", \"Location\", \"Stack\", \"Budget\", \"Scope\", \"Format\".\n\nAnti-patterns \u2014 never do these.\n- Don't ask procedural confirmations (\"Should I look again?\").\n- Don't ask about hypothetical edge cases that didn't occur.\n- Don't ask about specific person identities; treat the provided person summary as the only allowed reference.\n- Don't repeat anything in chatContext.openQuestions.\n- Don't re-surface anything in chatContext.surfacedFindings.\n- Don't ask for facts in chatContext.statedFacts.\n\nOutput. Return at most 3 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the five values). If nothing is worth asking, return \"questions\": [].";
|
|
91
91
|
/**
|
|
92
92
|
* @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version.
|
|
93
93
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question.prompt.d.ts","sourceRoot":"/","sources":["opportunity/question.prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC;AAEjG,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE;QAAE,OAAO,EAAE,eAAe,CAAC;QAAC,SAAS,EAAE,eAAe,CAAA;KAAE,CAAC;CAC1E;AAED,gCAAgC;AAChC,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,eAAe,CAAA;KAAE,CAAC,CAAC;IAC/D,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACjC;AAED,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,2FAA2F;IAC3F,cAAc,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,OAAO,EAAE,gBAAgB,CAAC;IAC1B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sFAAsF;IACtF,YAAY,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,gBAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;CAC5D;AAED,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,4CAA4C;AAC5C,MAAM,WAAW,sBAAsB;IACrC,sFAAsF;IACtF,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,sBAAsB,CAAC;IACtC;;;;;OAKG;IACH,kBAAkB,EAAE,0BAA0B,EAAE,CAAC;IACjD,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iEAAiE;IACjE,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;CACb;AAED,2GAA2G;AAC3G,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"question.prompt.d.ts","sourceRoot":"/","sources":["opportunity/question.prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC;AAEjG,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE;QAAE,OAAO,EAAE,eAAe,CAAC;QAAC,SAAS,EAAE,eAAe,CAAA;KAAE,CAAC;CAC1E;AAED,gCAAgC;AAChC,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,eAAe,CAAA;KAAE,CAAC,CAAC;IAC/D,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACjC;AAED,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,2FAA2F;IAC3F,cAAc,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,OAAO,EAAE,gBAAgB,CAAC;IAC1B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sFAAsF;IACtF,YAAY,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,gBAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;CAC5D;AAED,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,4CAA4C;AAC5C,MAAM,WAAW,sBAAsB;IACrC,sFAAsF;IACtF,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,sBAAsB,CAAC;IACtC;;;;;OAKG;IACH,kBAAkB,EAAE,0BAA0B,EAAE,CAAC;IACjD,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iEAAiE;IACjE,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;CACb;AAED,2GAA2G;AAC3G,eAAO,MAAM,aAAa,yyLA0C0J,CAAC;AAErL;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,CAmCzE"}
|
|
@@ -15,6 +15,14 @@ Ask a question only when ALL of these hold:
|
|
|
15
15
|
|
|
16
16
|
Standalone prompt rule. Every generated \`prompt\` must be understandable outside the conversation where it was created. Naturally include the original query, discovery pattern, connection pattern, or concrete learned fact in the question text itself. Do not rely on \`title\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about. For example, prefer "For your AI crypto decentralized deep-tech search, which area is most critical right now?" over "Which area is most critical right now?"
|
|
17
17
|
|
|
18
|
+
Referential closure. The prompt must resolve entirely on its own, with no dangling references. The reader sees ONLY the question text — never the people you reviewed, the events on their calendar, or this conversation. Do not use demonstratives or definite anaphora that point at things the reader cannot see: "these builders", "those founders", "these researchers", "these conversations", "this lunch", "the speaker". If you reference a person, name them. If you reference a group, restate the concrete shared attribute inside the question itself ("founders working on decentralized identity"), never "these founders". Never imply a list, set, or prior exchange the reader is not currently looking at.
|
|
19
|
+
- Bad: "What kind of collaboration are you looking for with these builders?"
|
|
20
|
+
- Good: "You're meeting people building agent infrastructure — what kind of collaboration are you looking for?"
|
|
21
|
+
|
|
22
|
+
No process narration. Never describe Index's own activity or internal state. Forbidden: "the previous negotiation", "the negotiation stalled", "opportunities found so far", "my search", "the counterparty", "candidates reviewed", restating why a match did or did not happen, or quoting words a counterparty did or did not use. Ask about the user's goal or intent directly, never about the matching pipeline.
|
|
23
|
+
- Bad: "All opportunities found so far are related to 'Edge Esmeralda'. Would you like to broaden the search?"
|
|
24
|
+
- Good: "Do you want to focus on people at Edge Esmeralda, or also connect beyond it?"
|
|
25
|
+
|
|
18
26
|
Cardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first (e.g. one surface_emergent_knowledge + one refine_intent). Add a third only when there are ≥3 substantive people reviewed and three distinct strategies each unblock a real decision. Two questions of the same strategy are acceptable only if their decision domains differ (different titles). Avoid stacking three pulls (info-from-user); balance with pushes (info-to-user via reflective_summary / surface_emergent_knowledge).
|
|
19
27
|
|
|
20
28
|
Ordering. Questions whose answer unblocks the most connection reviews come first; then highest-impact; then ambiguity-clarifying. Reviews that needed more detail or ran out of time signal under-specification — prioritize.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question.prompt.js","sourceRoot":"/","sources":["opportunity/question.prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version.\n *\n * Prompt module for the decision-question generator: the system prompt\n * constant, the `DiscoveryQuestionInput` contract, and a pure string-building\n * `buildQuestionPrompt` that assembles the user message.\n *\n * Pure: no I/O, no LLM call. The generator class (`question.generator.ts`)\n * orchestrates this module + an LLM client.\n */\nimport type { ChatContextDigest } from \"../shared/schemas/chat-context.schema.js\";\nimport type { DiscoveryNegotiationDigest } from \"../shared/schemas/negotiation-digest.schema.js\";\n\n/** Roles used in the existing negotiation framework. */\nexport type NegotiationRole = \"agent\" | \"patient\" | \"peer\";\n\n/** One turn within a negotiation. */\nexport interface DiscoveryTurn {\n action: \"propose\" | \"accept\" | \"reject\" | \"counter\" | \"question\";\n reasoning: string;\n suggestedRoles: { ownUser: NegotiationRole; otherUser: NegotiationRole };\n}\n\n/** Outcome of a negotiation. */\nexport interface DiscoveryOutcome {\n hasOpportunity: boolean;\n reasoning: string;\n agreedRoles?: Array<{ userId: string; role: NegotiationRole }>;\n /** Why the negotiation stopped, when not by an explicit accept/reject. */\n reason?: \"turn_cap\" | \"timeout\";\n}\n\n/** One negotiation that ran during this discovery turn. */\nexport interface DiscoveryNegotiation {\n /** Opaque counterparty identifier; NEVER surfaced to the user (kept out of the prompt). */\n counterpartyId: string;\n /** Abstract profile slice for the LLM (e.g. \"AI infra founder, Berlin\"). */\n counterpartyHint: string;\n /** The network/community prompt this negotiation ran under. */\n indexContext: string;\n /** Last 6 turns are retained; earlier ones are dropped. */\n turns: DiscoveryTurn[];\n outcome: DiscoveryOutcome;\n /**\n * Optional pre-negotiation evaluator score (0..1). When more than\n * `MAX_NEGOTIATIONS` candidates exist, this is used as a tie-breaker after\n * `turns.length` to decide which to keep.\n */\n seedAssessmentScore?: number;\n}\n\n/** Aggregate counters across all negotiations in this discovery turn. */\nexport interface DiscoverySummary {\n totalCandidates: number;\n opportunitiesFound: number;\n noOpportunityCount: number;\n /** Subset of `noOpportunityCount` where the negotiation hit a turn-cap or timeout. */\n timeoutCount: number;\n /** Map of role → count across all outcomes' `agreedRoles`. */\n roleDistribution: Partial<Record<NegotiationRole, number>>;\n}\n\n/** The seeker's profile slice the generator sees. All fields optional. */\nexport interface DiscoverySourceProfile {\n name?: string;\n bio?: string;\n location?: string;\n skills?: string[];\n interests?: string[];\n}\n\n/** Full input to the question generator. */\nexport interface DiscoveryQuestionInput {\n /** The seeker's original natural-language query / signal that triggered discovery. */\n query: string;\n sourceProfile: DiscoverySourceProfile;\n /**\n * Compact per-negotiation digests from THIS discovery turn. Each digest is a\n * fixed-size structured summary (counterparty hint, index, outcome role,\n * keyTake) — pre-summarized so this prompt stays small regardless of how\n * many candidates were negotiated. Raw negotiations are NOT passed here.\n */\n negotiationDigests: DiscoveryNegotiationDigest[];\n summary: DiscoverySummary;\n /** Distilled chat-session digest, when a session is in scope. */\n chatContext?: ChatContextDigest;\n /** ISO timestamp used as the \"now\" anchor in the prompt. */\n now: string;\n}\n\n/** @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version. */\nexport const SYSTEM_PROMPT = `You help write user-facing follow-up questions after Index has reviewed potential connections for a human. Your job: surface the minimum set of structured decision questions the human must answer to make the next discovery turn sharper, or improve their outlook on the intent.\n\nYou may pick from five strategies. Choose contextually; mix when multiple questions genuinely complement.\n- refine_intent: ask the user to sharpen or pivot their original signal.\n- surface_missing_detail: ask for one concrete missing input (stage, location, timing, scope, …).\n- open_adjacent_thread: offer a pivot suggested by recurring connection signals.\n- reflective_summary: mirror what the connection review revealed and ask the user to decide.\n- surface_emergent_knowledge: cite a fact you learned from the connection review and ask the user to decide in light of it.\n\nAsk a question only when ALL of these hold:\n1. Index cannot resolve the decision autonomously from the evidence shown.\n2. The answer would materially change which people surface next.\n3. The same fact is NOT already in chatContext.statedFacts, NOT already asked in chatContext.openQuestions, and NOT already shared in chatContext.surfacedFindings.\n\nStandalone prompt rule. Every generated \\`prompt\\` must be understandable outside the conversation where it was created. Naturally include the original query, discovery pattern, connection pattern, or concrete learned fact in the question text itself. Do not rely on \\`title\\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about. For example, prefer \"For your AI crypto decentralized deep-tech search, which area is most critical right now?\" over \"Which area is most critical right now?\"\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first (e.g. one surface_emergent_knowledge + one refine_intent). Add a third only when there are ≥3 substantive people reviewed and three distinct strategies each unblock a real decision. Two questions of the same strategy are acceptable only if their decision domains differ (different titles). Avoid stacking three pulls (info-from-user); balance with pushes (info-to-user via reflective_summary / surface_emergent_knowledge).\n\nOrdering. Questions whose answer unblocks the most connection reviews come first; then highest-impact; then ambiguity-clarifying. Reviews that needed more detail or ran out of time signal under-specification — prioritize.\n\nUser-facing language. Every title, prompt, option label, and option description is shown directly to the user. Never mention raw protocol mechanics or internal labels such as \"agent\", \"patient\", \"peer\", \"suggestedRoles\", \"role distribution\", \"counterparty\", \"negotiation\", \"turn_cap\", \"timeout\", or \"candidate\". Use natural language instead: people, matches, connections, mutual collaboration, someone who can help, or someone seeking help.\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an \"Other\" option — clients provide a free-text fallback automatically. For surface_emergent_knowledge questions, anchor the prompt in the concrete cited fact (\"Multiple people flagged that…\") and let the options represent decisions in light of that fact, not different versions of the fact.\n\nTitle rules. ≤12 chars. Noun of the decision domain. Discovery examples: \"Stage\", \"Timing\", \"Role\", \"Location\", \"Stack\", \"Budget\", \"Scope\", \"Format\".\n\nAnti-patterns — never do these.\n- Don't ask procedural confirmations (\"Should I look again?\").\n- Don't ask about hypothetical edge cases that didn't occur.\n- Don't ask about specific person identities; treat the provided person summary as the only allowed reference.\n- Don't repeat anything in chatContext.openQuestions.\n- Don't re-surface anything in chatContext.surfacedFindings.\n- Don't ask for facts in chatContext.statedFacts.\n\nOutput. Return at most 3 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the five values). If nothing is worth asking, return \"questions\": [].`;\n\n/**\n * @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version.\n *\n * Pure builder: assembles the user message string from a structured input.\n */\nexport function buildQuestionPrompt(input: DiscoveryQuestionInput): string {\n const profileSummary = renderProfile(input.sourceProfile);\n const connectionReviewBlocks = renderConnectionReviewDigests(input.negotiationDigests);\n const chatContextBlock = input.chatContext\n ? renderDigest(input.chatContext)\n : \"(no chat context available)\";\n const engagementPattern = renderEngagementPattern(input.summary.roleDistribution);\n\n return [\n \"## Seeker's query\",\n input.query,\n \"\",\n \"## Seeker profile\",\n profileSummary,\n \"\",\n \"## This discovery turn\",\n `- ${input.summary.totalCandidates} people reviewed`,\n `- ${input.summary.opportunitiesFound} promising connections found`,\n `- ${input.summary.noOpportunityCount} reviews did not find enough fit (${input.summary.timeoutCount} needed more detail or time)`,\n `- Engagement pattern: ${engagementPattern}`,\n \"\",\n \"## Connection review evidence (compact digests)\",\n connectionReviewBlocks,\n \"\",\n \"## What the user has already said in this session\",\n chatContextBlock,\n \"\",\n \"## Now\",\n input.now,\n \"\",\n \"## Your task\",\n \"Identify the minimum set of decision questions the seeker must answer to make\",\n \"the next discovery turn sharper. Apply every rule from your system prompt\",\n \"before outputting. Return an empty `questions` array if nothing is worth asking.\",\n ].join(\"\\n\");\n}\n\n/**\n * Render the negotiation-digest collection into compact one-liners. Each digest\n * is fixed-size (≤ ~400 chars after rendering), so the rendered block scales\n * linearly with candidate count: 10 candidates ≈ 4 KB, well within budget.\n */\nfunction renderConnectionReviewDigests(digests: DiscoveryNegotiationDigest[]): string {\n if (digests.length === 0) return \"(no connection reviews)\";\n return digests\n .map((d) => {\n const relationshipSignal = d.suggestedRoles\n ? [` Relationship signal: ${renderRelationshipSignal(d.suggestedRoles)}`]\n : [];\n return [\n `- Person: ${d.counterpartyHint}`,\n ` Community context: ${d.indexContext}`,\n ` Outcome: ${renderOutcome(d)}`,\n ...relationshipSignal,\n ` Take: ${d.keyTake}`,\n ].join(\"\\n\");\n })\n .join(\"\\n\\n\");\n}\n\nfunction renderProfile(p: DiscoverySourceProfile): string {\n const lines: string[] = [];\n if (p.name) lines.push(`Name: ${p.name}`);\n if (p.bio) lines.push(`Bio: ${p.bio}`);\n if (p.location) lines.push(`Location: ${p.location}`);\n if (p.skills && p.skills.length > 0) lines.push(`Skills: ${p.skills.join(\", \")}`);\n if (p.interests && p.interests.length > 0) lines.push(`Interests: ${p.interests.join(\", \")}`);\n return lines.length > 0 ? lines.join(\"\\n\") : \"(no profile data)\";\n}\n\nfunction renderEngagementPattern(dist: Partial<Record<NegotiationRole, number>>): string {\n const parts: string[] = [];\n if ((dist.peer ?? 0) > 0) {\n parts.push(`${dist.peer} mutual collaboration${dist.peer === 1 ? \"\" : \"s\"}`);\n }\n if ((dist.agent ?? 0) > 0) {\n parts.push(`${dist.agent} where the user could offer help or expertise`);\n }\n if ((dist.patient ?? 0) > 0) {\n parts.push(`${dist.patient} where the user seemed to be seeking help or expertise`);\n }\n return parts.length > 0 ? parts.join(\", \") : \"(no engagement pattern available)\";\n}\n\nfunction renderOutcome(digest: DiscoveryNegotiationDigest): string {\n const outcome = digest.outcomeRole === \"opportunity\"\n ? \"promising connection\"\n : \"not enough fit\";\n const reason = renderOutcomeReason(digest.outcomeReason);\n return reason ? `${outcome} (${reason})` : outcome;\n}\n\nfunction renderOutcomeReason(reason: DiscoveryNegotiationDigest[\"outcomeReason\"]): string {\n switch (reason) {\n case \"turn_cap\":\n return \"needed more detail\";\n case \"timeout\":\n return \"ran out of time\";\n case \"rejected\":\n return \"not enough mutual interest\";\n case \"stalled\":\n return \"stalled\";\n case null:\n return \"\";\n }\n}\n\nfunction renderRelationshipSignal(roles: NonNullable<DiscoveryNegotiationDigest[\"suggestedRoles\"]>): string {\n if (roles.ownUser === \"peer\" && roles.otherUser === \"peer\") return \"mutual collaboration\";\n if (roles.ownUser === \"agent\" && roles.otherUser === \"patient\") {\n return \"the user could offer help or expertise\";\n }\n if (roles.ownUser === \"patient\" && roles.otherUser === \"agent\") {\n return \"the user seemed to be seeking help or expertise\";\n }\n if (roles.ownUser === \"agent\") return \"the user could offer help or expertise\";\n if (roles.ownUser === \"patient\") return \"the user seemed to be seeking help or expertise\";\n return \"collaboration fit\";\n}\n\nfunction renderDigest(d: ChatContextDigest): string {\n const lines: string[] = [];\n if (d.statedFacts.length > 0) {\n lines.push(\"Stated facts:\");\n for (const f of d.statedFacts) lines.push(` - ${f}`);\n }\n if (d.openQuestions.length > 0) {\n lines.push(\"Open questions (assistant already asked):\");\n for (const q of d.openQuestions) lines.push(` - ${q}`);\n }\n if (d.rejectionReasons.length > 0) {\n lines.push(\"User pushback / rejections:\");\n for (const r of d.rejectionReasons) lines.push(` - ${r}`);\n }\n if (d.surfacedFindings.length > 0) {\n lines.push(\"Findings already surfaced to user:\");\n for (const f of d.surfacedFindings) lines.push(` - ${f}`);\n }\n return lines.length > 0 ? lines.join(\"\\n\") : \"(digest is empty)\";\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"question.prompt.js","sourceRoot":"/","sources":["opportunity/question.prompt.ts"],"names":[],"mappings":"AA0FA,2GAA2G;AAC3G,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oLA0CuJ,CAAC;AAErL;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA6B;IAC/D,MAAM,cAAc,GAAG,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC1D,MAAM,sBAAsB,GAAG,6BAA6B,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACvF,MAAM,gBAAgB,GAAG,KAAK,CAAC,WAAW;QACxC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,WAAW,CAAC;QACjC,CAAC,CAAC,6BAA6B,CAAC;IAClC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAElF,OAAO;QACL,mBAAmB;QACnB,KAAK,CAAC,KAAK;QACX,EAAE;QACF,mBAAmB;QACnB,cAAc;QACd,EAAE;QACF,wBAAwB;QACxB,KAAK,KAAK,CAAC,OAAO,CAAC,eAAe,kBAAkB;QACpD,KAAK,KAAK,CAAC,OAAO,CAAC,kBAAkB,8BAA8B;QACnE,KAAK,KAAK,CAAC,OAAO,CAAC,kBAAkB,qCAAqC,KAAK,CAAC,OAAO,CAAC,YAAY,8BAA8B;QAClI,yBAAyB,iBAAiB,EAAE;QAC5C,EAAE;QACF,iDAAiD;QACjD,sBAAsB;QACtB,EAAE;QACF,mDAAmD;QACnD,gBAAgB;QAChB,EAAE;QACF,QAAQ;QACR,KAAK,CAAC,GAAG;QACT,EAAE;QACF,cAAc;QACd,+EAA+E;QAC/E,2EAA2E;QAC3E,kFAAkF;KACnF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,SAAS,6BAA6B,CAAC,OAAqC;IAC1E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,yBAAyB,CAAC;IAC3D,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,kBAAkB,GAAG,CAAC,CAAC,cAAc;YACzC,CAAC,CAAC,CAAC,0BAA0B,wBAAwB,CAAC,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC;YAC1E,CAAC,CAAC,EAAE,CAAC;QACP,OAAO;YACL,aAAa,CAAC,CAAC,gBAAgB,EAAE;YACjC,wBAAwB,CAAC,CAAC,YAAY,EAAE;YACxC,cAAc,aAAa,CAAC,CAAC,CAAC,EAAE;YAChC,GAAG,kBAAkB;YACrB,WAAW,CAAC,CAAC,OAAO,EAAE;SACvB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC;SACD,IAAI,CAAC,MAAM,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,aAAa,CAAC,CAAyB;IAC9C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,IAAI,CAAC,CAAC,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACvC,IAAI,CAAC,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClF,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9F,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC;AACnE,CAAC;AAED,SAAS,uBAAuB,CAAC,IAA8C;IAC7E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,wBAAwB,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,+CAA+C,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,wDAAwD,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,mCAAmC,CAAC;AACnF,CAAC;AAED,SAAS,aAAa,CAAC,MAAkC;IACvD,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,KAAK,aAAa;QAClD,CAAC,CAAC,sBAAsB;QACxB,CAAC,CAAC,gBAAgB,CAAC;IACrB,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IACzD,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;AACrD,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAmD;IAC9E,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,UAAU;YACb,OAAO,oBAAoB,CAAC;QAC9B,KAAK,SAAS;YACZ,OAAO,iBAAiB,CAAC;QAC3B,KAAK,UAAU;YACb,OAAO,4BAA4B,CAAC;QACtC,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,IAAI;YACP,OAAO,EAAE,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,wBAAwB,CAAC,KAAgE;IAChG,IAAI,KAAK,CAAC,OAAO,KAAK,MAAM,IAAI,KAAK,CAAC,SAAS,KAAK,MAAM;QAAE,OAAO,sBAAsB,CAAC;IAC1F,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QAC/D,OAAO,wCAAwC,CAAC;IAClD,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,OAAO,EAAE,CAAC;QAC/D,OAAO,iDAAiD,CAAC;IAC3D,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO;QAAE,OAAO,wCAAwC,CAAC;IAC/E,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,iDAAiD,CAAC;IAC1F,OAAO,mBAAmB,CAAC;AAC7B,CAAC;AAED,SAAS,YAAY,CAAC,CAAoB;IACxC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,CAAC,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;QACxD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,CAAC,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,gBAAgB;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QACjD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,gBAAgB;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC;AACnE,CAAC","sourcesContent":["/**\n * @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version.\n *\n * Prompt module for the decision-question generator: the system prompt\n * constant, the `DiscoveryQuestionInput` contract, and a pure string-building\n * `buildQuestionPrompt` that assembles the user message.\n *\n * Pure: no I/O, no LLM call. The generator class (`question.generator.ts`)\n * orchestrates this module + an LLM client.\n */\nimport type { ChatContextDigest } from \"../shared/schemas/chat-context.schema.js\";\nimport type { DiscoveryNegotiationDigest } from \"../shared/schemas/negotiation-digest.schema.js\";\n\n/** Roles used in the existing negotiation framework. */\nexport type NegotiationRole = \"agent\" | \"patient\" | \"peer\";\n\n/** One turn within a negotiation. */\nexport interface DiscoveryTurn {\n action: \"propose\" | \"accept\" | \"reject\" | \"counter\" | \"question\";\n reasoning: string;\n suggestedRoles: { ownUser: NegotiationRole; otherUser: NegotiationRole };\n}\n\n/** Outcome of a negotiation. */\nexport interface DiscoveryOutcome {\n hasOpportunity: boolean;\n reasoning: string;\n agreedRoles?: Array<{ userId: string; role: NegotiationRole }>;\n /** Why the negotiation stopped, when not by an explicit accept/reject. */\n reason?: \"turn_cap\" | \"timeout\";\n}\n\n/** One negotiation that ran during this discovery turn. */\nexport interface DiscoveryNegotiation {\n /** Opaque counterparty identifier; NEVER surfaced to the user (kept out of the prompt). */\n counterpartyId: string;\n /** Abstract profile slice for the LLM (e.g. \"AI infra founder, Berlin\"). */\n counterpartyHint: string;\n /** The network/community prompt this negotiation ran under. */\n indexContext: string;\n /** Last 6 turns are retained; earlier ones are dropped. */\n turns: DiscoveryTurn[];\n outcome: DiscoveryOutcome;\n /**\n * Optional pre-negotiation evaluator score (0..1). When more than\n * `MAX_NEGOTIATIONS` candidates exist, this is used as a tie-breaker after\n * `turns.length` to decide which to keep.\n */\n seedAssessmentScore?: number;\n}\n\n/** Aggregate counters across all negotiations in this discovery turn. */\nexport interface DiscoverySummary {\n totalCandidates: number;\n opportunitiesFound: number;\n noOpportunityCount: number;\n /** Subset of `noOpportunityCount` where the negotiation hit a turn-cap or timeout. */\n timeoutCount: number;\n /** Map of role → count across all outcomes' `agreedRoles`. */\n roleDistribution: Partial<Record<NegotiationRole, number>>;\n}\n\n/** The seeker's profile slice the generator sees. All fields optional. */\nexport interface DiscoverySourceProfile {\n name?: string;\n bio?: string;\n location?: string;\n skills?: string[];\n interests?: string[];\n}\n\n/** Full input to the question generator. */\nexport interface DiscoveryQuestionInput {\n /** The seeker's original natural-language query / signal that triggered discovery. */\n query: string;\n sourceProfile: DiscoverySourceProfile;\n /**\n * Compact per-negotiation digests from THIS discovery turn. Each digest is a\n * fixed-size structured summary (counterparty hint, index, outcome role,\n * keyTake) — pre-summarized so this prompt stays small regardless of how\n * many candidates were negotiated. Raw negotiations are NOT passed here.\n */\n negotiationDigests: DiscoveryNegotiationDigest[];\n summary: DiscoverySummary;\n /** Distilled chat-session digest, when a session is in scope. */\n chatContext?: ChatContextDigest;\n /** ISO timestamp used as the \"now\" anchor in the prompt. */\n now: string;\n}\n\n/** @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version. */\nexport const SYSTEM_PROMPT = `You help write user-facing follow-up questions after Index has reviewed potential connections for a human. Your job: surface the minimum set of structured decision questions the human must answer to make the next discovery turn sharper, or improve their outlook on the intent.\n\nYou may pick from five strategies. Choose contextually; mix when multiple questions genuinely complement.\n- refine_intent: ask the user to sharpen or pivot their original signal.\n- surface_missing_detail: ask for one concrete missing input (stage, location, timing, scope, …).\n- open_adjacent_thread: offer a pivot suggested by recurring connection signals.\n- reflective_summary: mirror what the connection review revealed and ask the user to decide.\n- surface_emergent_knowledge: cite a fact you learned from the connection review and ask the user to decide in light of it.\n\nAsk a question only when ALL of these hold:\n1. Index cannot resolve the decision autonomously from the evidence shown.\n2. The answer would materially change which people surface next.\n3. The same fact is NOT already in chatContext.statedFacts, NOT already asked in chatContext.openQuestions, and NOT already shared in chatContext.surfacedFindings.\n\nStandalone prompt rule. Every generated \\`prompt\\` must be understandable outside the conversation where it was created. Naturally include the original query, discovery pattern, connection pattern, or concrete learned fact in the question text itself. Do not rely on \\`title\\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about. For example, prefer \"For your AI crypto decentralized deep-tech search, which area is most critical right now?\" over \"Which area is most critical right now?\"\n\nReferential closure. The prompt must resolve entirely on its own, with no dangling references. The reader sees ONLY the question text — never the people you reviewed, the events on their calendar, or this conversation. Do not use demonstratives or definite anaphora that point at things the reader cannot see: \"these builders\", \"those founders\", \"these researchers\", \"these conversations\", \"this lunch\", \"the speaker\". If you reference a person, name them. If you reference a group, restate the concrete shared attribute inside the question itself (\"founders working on decentralized identity\"), never \"these founders\". Never imply a list, set, or prior exchange the reader is not currently looking at.\n- Bad: \"What kind of collaboration are you looking for with these builders?\"\n- Good: \"You're meeting people building agent infrastructure — what kind of collaboration are you looking for?\"\n\nNo process narration. Never describe Index's own activity or internal state. Forbidden: \"the previous negotiation\", \"the negotiation stalled\", \"opportunities found so far\", \"my search\", \"the counterparty\", \"candidates reviewed\", restating why a match did or did not happen, or quoting words a counterparty did or did not use. Ask about the user's goal or intent directly, never about the matching pipeline.\n- Bad: \"All opportunities found so far are related to 'Edge Esmeralda'. Would you like to broaden the search?\"\n- Good: \"Do you want to focus on people at Edge Esmeralda, or also connect beyond it?\"\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first (e.g. one surface_emergent_knowledge + one refine_intent). Add a third only when there are ≥3 substantive people reviewed and three distinct strategies each unblock a real decision. Two questions of the same strategy are acceptable only if their decision domains differ (different titles). Avoid stacking three pulls (info-from-user); balance with pushes (info-to-user via reflective_summary / surface_emergent_knowledge).\n\nOrdering. Questions whose answer unblocks the most connection reviews come first; then highest-impact; then ambiguity-clarifying. Reviews that needed more detail or ran out of time signal under-specification — prioritize.\n\nUser-facing language. Every title, prompt, option label, and option description is shown directly to the user. Never mention raw protocol mechanics or internal labels such as \"agent\", \"patient\", \"peer\", \"suggestedRoles\", \"role distribution\", \"counterparty\", \"negotiation\", \"turn_cap\", \"timeout\", or \"candidate\". Use natural language instead: people, matches, connections, mutual collaboration, someone who can help, or someone seeking help.\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an \"Other\" option — clients provide a free-text fallback automatically. For surface_emergent_knowledge questions, anchor the prompt in the concrete cited fact (\"Multiple people flagged that…\") and let the options represent decisions in light of that fact, not different versions of the fact.\n\nTitle rules. ≤12 chars. Noun of the decision domain. Discovery examples: \"Stage\", \"Timing\", \"Role\", \"Location\", \"Stack\", \"Budget\", \"Scope\", \"Format\".\n\nAnti-patterns — never do these.\n- Don't ask procedural confirmations (\"Should I look again?\").\n- Don't ask about hypothetical edge cases that didn't occur.\n- Don't ask about specific person identities; treat the provided person summary as the only allowed reference.\n- Don't repeat anything in chatContext.openQuestions.\n- Don't re-surface anything in chatContext.surfacedFindings.\n- Don't ask for facts in chatContext.statedFacts.\n\nOutput. Return at most 3 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the five values). If nothing is worth asking, return \"questions\": [].`;\n\n/**\n * @deprecated Use QuestionerAgent and questioner presets instead. Will be removed in a future version.\n *\n * Pure builder: assembles the user message string from a structured input.\n */\nexport function buildQuestionPrompt(input: DiscoveryQuestionInput): string {\n const profileSummary = renderProfile(input.sourceProfile);\n const connectionReviewBlocks = renderConnectionReviewDigests(input.negotiationDigests);\n const chatContextBlock = input.chatContext\n ? renderDigest(input.chatContext)\n : \"(no chat context available)\";\n const engagementPattern = renderEngagementPattern(input.summary.roleDistribution);\n\n return [\n \"## Seeker's query\",\n input.query,\n \"\",\n \"## Seeker profile\",\n profileSummary,\n \"\",\n \"## This discovery turn\",\n `- ${input.summary.totalCandidates} people reviewed`,\n `- ${input.summary.opportunitiesFound} promising connections found`,\n `- ${input.summary.noOpportunityCount} reviews did not find enough fit (${input.summary.timeoutCount} needed more detail or time)`,\n `- Engagement pattern: ${engagementPattern}`,\n \"\",\n \"## Connection review evidence (compact digests)\",\n connectionReviewBlocks,\n \"\",\n \"## What the user has already said in this session\",\n chatContextBlock,\n \"\",\n \"## Now\",\n input.now,\n \"\",\n \"## Your task\",\n \"Identify the minimum set of decision questions the seeker must answer to make\",\n \"the next discovery turn sharper. Apply every rule from your system prompt\",\n \"before outputting. Return an empty `questions` array if nothing is worth asking.\",\n ].join(\"\\n\");\n}\n\n/**\n * Render the negotiation-digest collection into compact one-liners. Each digest\n * is fixed-size (≤ ~400 chars after rendering), so the rendered block scales\n * linearly with candidate count: 10 candidates ≈ 4 KB, well within budget.\n */\nfunction renderConnectionReviewDigests(digests: DiscoveryNegotiationDigest[]): string {\n if (digests.length === 0) return \"(no connection reviews)\";\n return digests\n .map((d) => {\n const relationshipSignal = d.suggestedRoles\n ? [` Relationship signal: ${renderRelationshipSignal(d.suggestedRoles)}`]\n : [];\n return [\n `- Person: ${d.counterpartyHint}`,\n ` Community context: ${d.indexContext}`,\n ` Outcome: ${renderOutcome(d)}`,\n ...relationshipSignal,\n ` Take: ${d.keyTake}`,\n ].join(\"\\n\");\n })\n .join(\"\\n\\n\");\n}\n\nfunction renderProfile(p: DiscoverySourceProfile): string {\n const lines: string[] = [];\n if (p.name) lines.push(`Name: ${p.name}`);\n if (p.bio) lines.push(`Bio: ${p.bio}`);\n if (p.location) lines.push(`Location: ${p.location}`);\n if (p.skills && p.skills.length > 0) lines.push(`Skills: ${p.skills.join(\", \")}`);\n if (p.interests && p.interests.length > 0) lines.push(`Interests: ${p.interests.join(\", \")}`);\n return lines.length > 0 ? lines.join(\"\\n\") : \"(no profile data)\";\n}\n\nfunction renderEngagementPattern(dist: Partial<Record<NegotiationRole, number>>): string {\n const parts: string[] = [];\n if ((dist.peer ?? 0) > 0) {\n parts.push(`${dist.peer} mutual collaboration${dist.peer === 1 ? \"\" : \"s\"}`);\n }\n if ((dist.agent ?? 0) > 0) {\n parts.push(`${dist.agent} where the user could offer help or expertise`);\n }\n if ((dist.patient ?? 0) > 0) {\n parts.push(`${dist.patient} where the user seemed to be seeking help or expertise`);\n }\n return parts.length > 0 ? parts.join(\", \") : \"(no engagement pattern available)\";\n}\n\nfunction renderOutcome(digest: DiscoveryNegotiationDigest): string {\n const outcome = digest.outcomeRole === \"opportunity\"\n ? \"promising connection\"\n : \"not enough fit\";\n const reason = renderOutcomeReason(digest.outcomeReason);\n return reason ? `${outcome} (${reason})` : outcome;\n}\n\nfunction renderOutcomeReason(reason: DiscoveryNegotiationDigest[\"outcomeReason\"]): string {\n switch (reason) {\n case \"turn_cap\":\n return \"needed more detail\";\n case \"timeout\":\n return \"ran out of time\";\n case \"rejected\":\n return \"not enough mutual interest\";\n case \"stalled\":\n return \"stalled\";\n case null:\n return \"\";\n }\n}\n\nfunction renderRelationshipSignal(roles: NonNullable<DiscoveryNegotiationDigest[\"suggestedRoles\"]>): string {\n if (roles.ownUser === \"peer\" && roles.otherUser === \"peer\") return \"mutual collaboration\";\n if (roles.ownUser === \"agent\" && roles.otherUser === \"patient\") {\n return \"the user could offer help or expertise\";\n }\n if (roles.ownUser === \"patient\" && roles.otherUser === \"agent\") {\n return \"the user seemed to be seeking help or expertise\";\n }\n if (roles.ownUser === \"agent\") return \"the user could offer help or expertise\";\n if (roles.ownUser === \"patient\") return \"the user seemed to be seeking help or expertise\";\n return \"collaboration fit\";\n}\n\nfunction renderDigest(d: ChatContextDigest): string {\n const lines: string[] = [];\n if (d.statedFacts.length > 0) {\n lines.push(\"Stated facts:\");\n for (const f of d.statedFacts) lines.push(` - ${f}`);\n }\n if (d.openQuestions.length > 0) {\n lines.push(\"Open questions (assistant already asked):\");\n for (const q of d.openQuestions) lines.push(` - ${q}`);\n }\n if (d.rejectionReasons.length > 0) {\n lines.push(\"User pushback / rejections:\");\n for (const r of d.rejectionReasons) lines.push(` - ${r}`);\n }\n if (d.surfacedFindings.length > 0) {\n lines.push(\"Findings already surfaced to user:\");\n for (const f of d.surfacedFindings) lines.push(` - ${f}`);\n }\n return lines.length > 0 ? lines.join(\"\\n\") : \"(digest is empty)\";\n}\n\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"questioner.presets.d.ts","sourceRoot":"/","sources":["questioner/questioner.presets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"questioner.presets.d.ts","sourceRoot":"/","sources":["questioner/questioner.presets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAwBzE,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;CAC3C;AAqPD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,GAAG,gBAAgB,CAM9D"}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { SYSTEM_PROMPT as DISCOVERY_SYSTEM_PROMPT, buildQuestionPrompt as buildDiscoveryPrompt, } from "../opportunity/question.prompt.js";
|
|
2
|
+
/**
|
|
3
|
+
* Shared rule block appended to every questioner system prompt. Enforces that
|
|
4
|
+
* the generated `prompt` resolves on its own — no demonstratives/anaphora that
|
|
5
|
+
* point at people, events, or prior turns the reader cannot see — and never
|
|
6
|
+
* narrates Index's own matching pipeline. Closes the referential-leak class
|
|
7
|
+
* surfaced in digest audits ("…with these builders?", "the previous
|
|
8
|
+
* negotiation stalled because the counterparty didn't mention …").
|
|
9
|
+
*/
|
|
10
|
+
const REFERENTIAL_CLOSURE_RULES = `Referential closure. The prompt must resolve entirely on its own, with no dangling references. The reader sees ONLY the question text — never the people you reviewed, the counterparty, the events on their calendar, or this conversation. Do not use demonstratives or definite anaphora that point at things the reader cannot see: "these builders", "those founders", "these researchers", "these conversations", "this lunch", "the speaker". If you reference a person, name them. If you reference a group, restate the concrete shared attribute inside the question itself ("founders working on decentralized identity"), never "these founders". Never imply a list, set, or prior exchange the reader is not currently looking at.
|
|
11
|
+
- Bad: "What kind of collaboration are you looking for with these builders?"
|
|
12
|
+
- Good: "You're meeting people building agent infrastructure — what kind of collaboration are you looking for?"
|
|
13
|
+
|
|
14
|
+
No process narration. Never describe Index's own activity or internal state. Forbidden: "the previous negotiation", "the negotiation stalled", "opportunities found so far", "my search", "the counterparty", "candidates reviewed", restating why a match did or did not happen, or quoting words a counterparty did or did not use. Ask about the user's goal or intent directly, never about the matching pipeline.
|
|
15
|
+
- Bad: "The previous negotiation stalled because the counterparty didn't mention 'matchmaking'. Should I broaden the search?"
|
|
16
|
+
- Good: "Do you want to focus on dedicated matchmakers, or also people interested in relationships more broadly?"`;
|
|
2
17
|
// ─── Intent preset ──────────────────────────────────────────────────────────
|
|
3
18
|
const INTENT_SYSTEM_PROMPT = `You sit between a human and a discovery protocol. The user has stated an intent — what they are looking for. Your job: surface the minimum set of structured questions that help the user sharpen that intent before the protocol runs discovery on their behalf.
|
|
4
19
|
|
|
@@ -15,6 +30,8 @@ Standalone prompt rule. Every generated \`prompt\` must be understandable outsid
|
|
|
15
30
|
- Bad: "What kind of collaboration are you looking for?"
|
|
16
31
|
- Good: "For your decentralized identity protocol-design search, what kind of collaboration are you looking for?"
|
|
17
32
|
|
|
33
|
+
${REFERENTIAL_CLOSURE_RULES}
|
|
34
|
+
|
|
18
35
|
Cardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first and unblocks a clearly distinct decision. Never ask two questions of the same strategy unless their decision domains differ (different titles).
|
|
19
36
|
|
|
20
37
|
Option construction. Each option must represent a meaningfully different outcome. Suffix the safest or most common path with " (Recommended)" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an "Other" option — clients provide a free-text fallback automatically.
|
|
@@ -82,6 +99,8 @@ Standalone prompt rule. Every generated \`prompt\` must be understandable outsid
|
|
|
82
99
|
- Bad: "What kind of role are you looking for?"
|
|
83
100
|
- Good: "To improve matches from your founder/operator profile, what kind of role are you looking for?"
|
|
84
101
|
|
|
102
|
+
${REFERENTIAL_CLOSURE_RULES}
|
|
103
|
+
|
|
85
104
|
Cardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first and unblocks a clearly distinct decision. Never ask two questions of the same strategy unless their decision domains differ.
|
|
86
105
|
|
|
87
106
|
Option construction. Each option must represent a meaningfully different outcome. Suffix the safest or most common path with " (Recommended)" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an "Other" option — clients provide a free-text fallback automatically.
|
|
@@ -147,9 +166,11 @@ Ask a question only when ALL of these hold:
|
|
|
147
166
|
2. The answer would materially change how the next attempt surfaces or engages candidates.
|
|
148
167
|
3. The question targets a different decision domain from any other question in this batch.
|
|
149
168
|
|
|
150
|
-
Standalone prompt rule. Every generated \`prompt\` must be understandable outside the conversation where it was created. Naturally include the
|
|
169
|
+
Standalone prompt rule. Every generated \`prompt\` must be understandable outside the conversation where it was created. Naturally include the user's underlying goal or topic and the relevant community in the question text itself, in plain language drawn from their intent or profile — NOT the mechanics of the match attempt. Do not rely on \`title\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about.
|
|
151
170
|
- Bad: "Which role is a better fit for your immediate needs?"
|
|
152
|
-
- Good: "For
|
|
171
|
+
- Good: "For your search for AI infrastructure collaborators in the AI founders community, what kind of working relationship fits your immediate needs?"
|
|
172
|
+
|
|
173
|
+
${REFERENTIAL_CLOSURE_RULES}
|
|
153
174
|
|
|
154
175
|
Cardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first and unblocks a clearly distinct decision. Never ask two questions of the same strategy unless their decision domains differ.
|
|
155
176
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"questioner.presets.js","sourceRoot":"/","sources":["questioner/questioner.presets.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Mode presets for the QuestionerAgent. Each preset provides a system prompt\n * and a buildPrompt function that assembles the user message from a typed\n * context object. Only the `discovery` preset ships in Slice 1; others throw\n * until their implementation slices land.\n */\nimport type { QuestionMode } from \"../shared/schemas/question.schema.js\";\nimport {\n SYSTEM_PROMPT as DISCOVERY_SYSTEM_PROMPT,\n buildQuestionPrompt as buildDiscoveryPrompt,\n} from \"../opportunity/question.prompt.js\";\n\nimport type { IntentContext, NegotiationContext, ProfileContext } from \"./questioner.types.js\";\n\nexport interface QuestionerPreset {\n /** The LLM system prompt for this mode. */\n systemPrompt: string;\n /** Builds the user-message string from the mode-specific context. */\n buildPrompt: (context: unknown) => string;\n}\n\n// ─── Intent preset ──────────────────────────────────────────────────────────\n\nconst INTENT_SYSTEM_PROMPT = `You sit between a human and a discovery protocol. The user has stated an intent — what they are looking for. Your job: surface the minimum set of structured questions that help the user sharpen that intent before the protocol runs discovery on their behalf.\n\nYou may pick from two strategies. Choose contextually; mix only when each question is genuinely distinct.\n- refine_intent: ask the user to sharpen or pivot the core signal (scope, scale, specificity, direction).\n- surface_missing_detail: ask for one concrete missing input that would change which candidates surface (stage, location, timing, budget, constraints, format, …).\n\nAsk a question only when ALL of these hold:\n1. The agent cannot infer the answer from the intent text or user profile already shown.\n2. The answer would materially change which candidates surface.\n3. The question targets a different decision domain from any other question in this batch.\n\nStandalone prompt rule. Every generated \\`prompt\\` must be understandable outside the conversation where it was created. Naturally include the source intent/topic in the question text itself, using concise plain language from the intent or summary. Do not rely on \\`title\\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about.\n- Bad: \"What kind of collaboration are you looking for?\"\n- Good: \"For your decentralized identity protocol-design search, what kind of collaboration are you looking for?\"\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first and unblocks a clearly distinct decision. Never ask two questions of the same strategy unless their decision domains differ (different titles).\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest or most common path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an \"Other\" option — clients provide a free-text fallback automatically.\n\nTitle rules. ≤12 chars. Noun of the decision domain. Examples: \"Stage\", \"Timing\", \"Location\", \"Scope\", \"Budget\", \"Format\", \"Skills\", \"Collab\".\n\nAnti-patterns — never do these.\n- Don't ask procedural confirmations (\"Should I start searching?\").\n- Don't ask about hypothetical edge cases not implied by the intent.\n- Don't re-ask for facts already visible in the user profile.\n- Don't ask vague introspective questions (\"What do you really want?\").\n\nOutput. Return at most 2 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the two values above). If the intent is already specific enough, return \"questions\": [].`;\n\n/**\n * Build the user message for the intent preset from an IntentContext.\n * @param ctx - The intent context.\n * @returns The assembled user message string.\n */\nfunction buildIntentPrompt(ctx: IntentContext): string {\n const profileLines: string[] = [];\n if (ctx.userProfile.name) profileLines.push(`Name: ${ctx.userProfile.name}`);\n if (ctx.userProfile.bio) profileLines.push(`Bio: ${ctx.userProfile.bio}`);\n if (ctx.userProfile.skills && ctx.userProfile.skills.length > 0) {\n profileLines.push(`Skills: ${ctx.userProfile.skills.join(\", \")}`);\n }\n if (ctx.userProfile.interests && ctx.userProfile.interests.length > 0) {\n profileLines.push(`Interests: ${ctx.userProfile.interests.join(\", \")}`);\n }\n const profileBlock = profileLines.length > 0 ? profileLines.join(\"\\n\") : \"(no profile data)\";\n\n const summaryBlock = ctx.summary ? ctx.summary : \"(no summary available)\";\n\n return [\n \"## Intent\",\n ctx.payload,\n \"\",\n \"## Summary\",\n summaryBlock,\n \"\",\n \"## User profile\",\n profileBlock,\n \"\",\n \"## Your task\",\n \"Identify the minimum set of questions the user must answer to sharpen this intent.\",\n \"Apply every rule from your system prompt before outputting.\",\n \"Return an empty `questions` array if the intent is already specific enough.\",\n ].join(\"\\n\");\n}\n\n// ─── Profile preset ─────────────────────────────────────────────────────────\n\nconst PROFILE_SYSTEM_PROMPT = `You sit between a human and a discovery protocol. The user has a profile that is incomplete. Your job: surface the minimum set of structured questions that fill the identified gaps — asking about location, skills, interests, current work, or goals — so the protocol can run better discovery on their behalf.\n\nThe user may already have premises — atomic self-descriptions they have stated. These cover specific profile domains. Do not ask about domains already addressed by existing premises. Focus only on gaps not covered by any premise.\n\nYou may pick from two strategies. Choose contextually; mix only when each question is genuinely distinct.\n- surface_missing_detail: ask for one concrete missing piece of profile data (location, current role, skills, interests, goals, availability, …).\n- refine_intent: ask the user to clarify or sharpen an existing profile signal so candidates can be ranked more accurately.\n\nAsk a question only when ALL of these hold:\n1. The answer is not already visible in the profile data shown.\n2. The answer is not already covered by an existing premise listed below the profile.\n3. The answer would meaningfully change which opportunities surface for this user.\n4. The question targets a different profile domain from any other question in this batch.\n\nStandalone prompt rule. Every generated \\`prompt\\` must be understandable outside the conversation where it was created. Naturally include the profile signal or gap being clarified in the question text itself, using concise plain language from the current profile, existing premises, or identified gaps. Do not rely on \\`title\\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about.\n- Bad: \"What kind of role are you looking for?\"\n- Good: \"To improve matches from your founder/operator profile, what kind of role are you looking for?\"\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first and unblocks a clearly distinct decision. Never ask two questions of the same strategy unless their decision domains differ.\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest or most common path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an \"Other\" option — clients provide a free-text fallback automatically.\n\nTitle rules. ≤12 chars. Noun of the profile domain. Examples: \"Location\", \"Role\", \"Skills\", \"Goals\", \"Interests\", \"Availability\", \"Stage\".\n\nAnti-patterns — never do these.\n- Don't ask about fields already filled in the profile.\n- Don't ask about information already captured in an existing premise.\n- Don't ask procedural confirmations (\"Should I update your profile?\").\n- Don't ask vague introspective questions (\"Who are you really?\").\n- Don't re-ask for facts visible anywhere in the profile data or premises shown.\n\nOutput. Return at most 2 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the two values above). If the profile is already complete enough for discovery, return \"questions\": [].`;\n\n/**\n * Build the user message for the profile preset from a ProfileContext.\n * @param ctx - The profile context including current profile data and identified gaps.\n * @returns The assembled user message string.\n */\nfunction buildProfilePrompt(ctx: ProfileContext): string {\n const profileLines: string[] = [];\n if (ctx.userProfile.name) profileLines.push(`Name: ${ctx.userProfile.name}`);\n if (ctx.userProfile.bio) profileLines.push(`Bio: ${ctx.userProfile.bio}`);\n if (ctx.userProfile.location) profileLines.push(`Location: ${ctx.userProfile.location}`);\n if (ctx.userProfile.skills && ctx.userProfile.skills.length > 0) {\n profileLines.push(`Skills: ${ctx.userProfile.skills.join(\", \")}`);\n }\n if (ctx.userProfile.interests && ctx.userProfile.interests.length > 0) {\n profileLines.push(`Interests: ${ctx.userProfile.interests.join(\", \")}`);\n }\n const profileBlock = profileLines.length > 0 ? profileLines.join(\"\\n\") : \"(no profile data)\";\n\n const premisesBlock =\n ctx.existingPremises && ctx.existingPremises.length > 0\n ? ctx.existingPremises.map((p, i) => `${i + 1}. ${p}`).join(\"\\n\")\n : \"(none)\";\n\n const gapsBlock = ctx.gaps.length > 0 ? ctx.gaps.join(\", \") : \"(none identified)\";\n\n const parts: string[] = [\n \"## Current profile\",\n profileBlock,\n \"\",\n \"## Existing premises\",\n premisesBlock,\n \"\",\n \"## Identified gaps\",\n gapsBlock,\n \"\",\n ];\n\n parts.push(\n \"## Your task\",\n \"Generate the minimum set of questions needed to fill the identified gaps.\",\n \"Apply every rule from your system prompt before outputting.\",\n \"Return an empty `questions` array if the profile is already complete enough.\",\n );\n\n return parts.join(\"\\n\");\n}\n\n// ─── Negotiation preset ──────────────────────────────────────────────────────\n\nconst NEGOTIATION_SYSTEM_PROMPT = `You sit between a human and a discovery protocol. A negotiation between this user and a counterparty has ended without a clear outcome — either the turn budget was exhausted, the session timed out, or conversation stalled. Your job: surface the minimum set of structured questions that help the user provide the missing signal needed to unblock or refine the next discovery attempt on their behalf.\n\nYou may pick from three strategies. Choose contextually; mix only when each question is genuinely distinct.\n- refine_intent: help the user sharpen their underlying signal based on what the negotiation revealed (scope, scale, priority, direction).\n- surface_missing_detail: ask for one concrete piece of information that was absent and would have moved the negotiation forward (timeline, budget, format, constraints, decision criteria, …).\n- reflective_summary: mirror the key takeaway from the negotiation and ask the user to confirm, correct, or decide — useful when the conversation revealed partial signal worth locking in.\n\nAsk a question only when ALL of these hold:\n1. The answer is not already visible in the negotiation context or user profile shown.\n2. The answer would materially change how the next attempt surfaces or engages candidates.\n3. The question targets a different decision domain from any other question in this batch.\n\nStandalone prompt rule. Every generated \\`prompt\\` must be understandable outside the conversation where it was created. Naturally include the stalled negotiation context, counterparty hint, community, or key takeaway in the question text itself. Do not rely on \\`title\\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about.\n- Bad: \"Which role is a better fit for your immediate needs?\"\n- Good: \"For the stalled match with an AI infra founder in the AI founders community, which role is a better fit for your immediate needs?\"\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first and unblocks a clearly distinct decision. Never ask two questions of the same strategy unless their decision domains differ.\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest or most common path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an \"Other\" option — clients provide a free-text fallback automatically.\n\nTitle rules. ≤12 chars. Noun of the decision domain. Examples: \"Scope\", \"Timeline\", \"Budget\", \"Priority\", \"Format\", \"Stance\", \"Criteria\".\n\nAnti-patterns — never do these.\n- Don't ask procedural confirmations (\"Should I try again?\").\n- Don't re-ask for facts already visible in the user profile.\n- Don't ask vague introspective questions (\"What do you really want?\").\n- Don't ask about hypothetical edge cases not implied by the negotiation context.\n\nOutput. Return at most 2 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the three values above). If the context already contains enough signal to proceed, return \"questions\": [].`;\n\n/**\n * Build the user message for the negotiation preset from a NegotiationContext.\n * @param ctx - The negotiation context including counterparty hint, stall reason, and key takeaway.\n * @returns The assembled user message string.\n */\nfunction buildNegotiationPrompt(ctx: NegotiationContext): string {\n const profileLines: string[] = [];\n if (ctx.userProfile.name) profileLines.push(`Name: ${ctx.userProfile.name}`);\n if (ctx.userProfile.bio) profileLines.push(`Bio: ${ctx.userProfile.bio}`);\n if (ctx.userProfile.skills && ctx.userProfile.skills.length > 0) {\n profileLines.push(`Skills: ${ctx.userProfile.skills.join(\", \")}`);\n }\n if (ctx.userProfile.interests && ctx.userProfile.interests.length > 0) {\n profileLines.push(`Interests: ${ctx.userProfile.interests.join(\", \")}`);\n }\n const profileBlock = profileLines.length > 0 ? profileLines.join(\"\\n\") : \"(no profile data)\";\n\n return [\n \"## Negotiation context\",\n `Community: ${ctx.indexContext}`,\n `Counterparty: ${ctx.counterpartyHint}`,\n `Stall reason: ${ctx.outcomeReason}`,\n \"\",\n \"## Key takeaway\",\n ctx.keyTake,\n \"\",\n \"## User profile\",\n profileBlock,\n \"\",\n \"## Your task\",\n \"Identify the minimum set of questions the user must answer to unblock the next discovery attempt.\",\n \"Apply every rule from your system prompt before outputting.\",\n \"Return an empty `questions` array if the context already contains enough signal to proceed.\",\n ].join(\"\\n\");\n}\n\nconst presets: Record<QuestionMode, QuestionerPreset> = {\n discovery: {\n systemPrompt: DISCOVERY_SYSTEM_PROMPT,\n buildPrompt: (context: unknown) =>\n buildDiscoveryPrompt(context as Parameters<typeof buildDiscoveryPrompt>[0]),\n },\n intent: {\n systemPrompt: INTENT_SYSTEM_PROMPT,\n buildPrompt: (context: unknown) => buildIntentPrompt(context as IntentContext),\n },\n profile: {\n systemPrompt: PROFILE_SYSTEM_PROMPT,\n buildPrompt: (context: unknown) => buildProfilePrompt(context as ProfileContext),\n },\n negotiation: {\n systemPrompt: NEGOTIATION_SYSTEM_PROMPT,\n buildPrompt: (context: unknown) => buildNegotiationPrompt(context as NegotiationContext),\n },\n};\n\n/**\n * Retrieve the preset for the given mode.\n * @param mode - The question mode to look up.\n * @returns The matching preset with systemPrompt and buildPrompt.\n * @throws Error if the mode's preset is not yet implemented.\n */\nexport function getPreset(mode: QuestionMode): QuestionerPreset {\n const preset = presets[mode];\n if (!preset) {\n throw new Error(`QuestionerAgent preset \"${mode}\" is not implemented yet`);\n }\n return preset;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"questioner.presets.js","sourceRoot":"/","sources":["questioner/questioner.presets.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Mode presets for the QuestionerAgent. Each preset provides a system prompt\n * and a buildPrompt function that assembles the user message from a typed\n * context object. Only the `discovery` preset ships in Slice 1; others throw\n * until their implementation slices land.\n */\nimport type { QuestionMode } from \"../shared/schemas/question.schema.js\";\nimport {\n SYSTEM_PROMPT as DISCOVERY_SYSTEM_PROMPT,\n buildQuestionPrompt as buildDiscoveryPrompt,\n} from \"../opportunity/question.prompt.js\";\n\nimport type { IntentContext, NegotiationContext, ProfileContext } from \"./questioner.types.js\";\n\n/**\n * Shared rule block appended to every questioner system prompt. Enforces that\n * the generated `prompt` resolves on its own — no demonstratives/anaphora that\n * point at people, events, or prior turns the reader cannot see — and never\n * narrates Index's own matching pipeline. Closes the referential-leak class\n * surfaced in digest audits (\"…with these builders?\", \"the previous\n * negotiation stalled because the counterparty didn't mention …\").\n */\nconst REFERENTIAL_CLOSURE_RULES = `Referential closure. The prompt must resolve entirely on its own, with no dangling references. The reader sees ONLY the question text — never the people you reviewed, the counterparty, the events on their calendar, or this conversation. Do not use demonstratives or definite anaphora that point at things the reader cannot see: \"these builders\", \"those founders\", \"these researchers\", \"these conversations\", \"this lunch\", \"the speaker\". If you reference a person, name them. If you reference a group, restate the concrete shared attribute inside the question itself (\"founders working on decentralized identity\"), never \"these founders\". Never imply a list, set, or prior exchange the reader is not currently looking at.\n- Bad: \"What kind of collaboration are you looking for with these builders?\"\n- Good: \"You're meeting people building agent infrastructure — what kind of collaboration are you looking for?\"\n\nNo process narration. Never describe Index's own activity or internal state. Forbidden: \"the previous negotiation\", \"the negotiation stalled\", \"opportunities found so far\", \"my search\", \"the counterparty\", \"candidates reviewed\", restating why a match did or did not happen, or quoting words a counterparty did or did not use. Ask about the user's goal or intent directly, never about the matching pipeline.\n- Bad: \"The previous negotiation stalled because the counterparty didn't mention 'matchmaking'. Should I broaden the search?\"\n- Good: \"Do you want to focus on dedicated matchmakers, or also people interested in relationships more broadly?\"`;\n\nexport interface QuestionerPreset {\n /** The LLM system prompt for this mode. */\n systemPrompt: string;\n /** Builds the user-message string from the mode-specific context. */\n buildPrompt: (context: unknown) => string;\n}\n\n// ─── Intent preset ──────────────────────────────────────────────────────────\n\nconst INTENT_SYSTEM_PROMPT = `You sit between a human and a discovery protocol. The user has stated an intent — what they are looking for. Your job: surface the minimum set of structured questions that help the user sharpen that intent before the protocol runs discovery on their behalf.\n\nYou may pick from two strategies. Choose contextually; mix only when each question is genuinely distinct.\n- refine_intent: ask the user to sharpen or pivot the core signal (scope, scale, specificity, direction).\n- surface_missing_detail: ask for one concrete missing input that would change which candidates surface (stage, location, timing, budget, constraints, format, …).\n\nAsk a question only when ALL of these hold:\n1. The agent cannot infer the answer from the intent text or user profile already shown.\n2. The answer would materially change which candidates surface.\n3. The question targets a different decision domain from any other question in this batch.\n\nStandalone prompt rule. Every generated \\`prompt\\` must be understandable outside the conversation where it was created. Naturally include the source intent/topic in the question text itself, using concise plain language from the intent or summary. Do not rely on \\`title\\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about.\n- Bad: \"What kind of collaboration are you looking for?\"\n- Good: \"For your decentralized identity protocol-design search, what kind of collaboration are you looking for?\"\n\n${REFERENTIAL_CLOSURE_RULES}\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first and unblocks a clearly distinct decision. Never ask two questions of the same strategy unless their decision domains differ (different titles).\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest or most common path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an \"Other\" option — clients provide a free-text fallback automatically.\n\nTitle rules. ≤12 chars. Noun of the decision domain. Examples: \"Stage\", \"Timing\", \"Location\", \"Scope\", \"Budget\", \"Format\", \"Skills\", \"Collab\".\n\nAnti-patterns — never do these.\n- Don't ask procedural confirmations (\"Should I start searching?\").\n- Don't ask about hypothetical edge cases not implied by the intent.\n- Don't re-ask for facts already visible in the user profile.\n- Don't ask vague introspective questions (\"What do you really want?\").\n\nOutput. Return at most 2 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the two values above). If the intent is already specific enough, return \"questions\": [].`;\n\n/**\n * Build the user message for the intent preset from an IntentContext.\n * @param ctx - The intent context.\n * @returns The assembled user message string.\n */\nfunction buildIntentPrompt(ctx: IntentContext): string {\n const profileLines: string[] = [];\n if (ctx.userProfile.name) profileLines.push(`Name: ${ctx.userProfile.name}`);\n if (ctx.userProfile.bio) profileLines.push(`Bio: ${ctx.userProfile.bio}`);\n if (ctx.userProfile.skills && ctx.userProfile.skills.length > 0) {\n profileLines.push(`Skills: ${ctx.userProfile.skills.join(\", \")}`);\n }\n if (ctx.userProfile.interests && ctx.userProfile.interests.length > 0) {\n profileLines.push(`Interests: ${ctx.userProfile.interests.join(\", \")}`);\n }\n const profileBlock = profileLines.length > 0 ? profileLines.join(\"\\n\") : \"(no profile data)\";\n\n const summaryBlock = ctx.summary ? ctx.summary : \"(no summary available)\";\n\n return [\n \"## Intent\",\n ctx.payload,\n \"\",\n \"## Summary\",\n summaryBlock,\n \"\",\n \"## User profile\",\n profileBlock,\n \"\",\n \"## Your task\",\n \"Identify the minimum set of questions the user must answer to sharpen this intent.\",\n \"Apply every rule from your system prompt before outputting.\",\n \"Return an empty `questions` array if the intent is already specific enough.\",\n ].join(\"\\n\");\n}\n\n// ─── Profile preset ─────────────────────────────────────────────────────────\n\nconst PROFILE_SYSTEM_PROMPT = `You sit between a human and a discovery protocol. The user has a profile that is incomplete. Your job: surface the minimum set of structured questions that fill the identified gaps — asking about location, skills, interests, current work, or goals — so the protocol can run better discovery on their behalf.\n\nThe user may already have premises — atomic self-descriptions they have stated. These cover specific profile domains. Do not ask about domains already addressed by existing premises. Focus only on gaps not covered by any premise.\n\nYou may pick from two strategies. Choose contextually; mix only when each question is genuinely distinct.\n- surface_missing_detail: ask for one concrete missing piece of profile data (location, current role, skills, interests, goals, availability, …).\n- refine_intent: ask the user to clarify or sharpen an existing profile signal so candidates can be ranked more accurately.\n\nAsk a question only when ALL of these hold:\n1. The answer is not already visible in the profile data shown.\n2. The answer is not already covered by an existing premise listed below the profile.\n3. The answer would meaningfully change which opportunities surface for this user.\n4. The question targets a different profile domain from any other question in this batch.\n\nStandalone prompt rule. Every generated \\`prompt\\` must be understandable outside the conversation where it was created. Naturally include the profile signal or gap being clarified in the question text itself, using concise plain language from the current profile, existing premises, or identified gaps. Do not rely on \\`title\\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about.\n- Bad: \"What kind of role are you looking for?\"\n- Good: \"To improve matches from your founder/operator profile, what kind of role are you looking for?\"\n\n${REFERENTIAL_CLOSURE_RULES}\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first and unblocks a clearly distinct decision. Never ask two questions of the same strategy unless their decision domains differ.\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest or most common path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an \"Other\" option — clients provide a free-text fallback automatically.\n\nTitle rules. ≤12 chars. Noun of the profile domain. Examples: \"Location\", \"Role\", \"Skills\", \"Goals\", \"Interests\", \"Availability\", \"Stage\".\n\nAnti-patterns — never do these.\n- Don't ask about fields already filled in the profile.\n- Don't ask about information already captured in an existing premise.\n- Don't ask procedural confirmations (\"Should I update your profile?\").\n- Don't ask vague introspective questions (\"Who are you really?\").\n- Don't re-ask for facts visible anywhere in the profile data or premises shown.\n\nOutput. Return at most 2 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the two values above). If the profile is already complete enough for discovery, return \"questions\": [].`;\n\n/**\n * Build the user message for the profile preset from a ProfileContext.\n * @param ctx - The profile context including current profile data and identified gaps.\n * @returns The assembled user message string.\n */\nfunction buildProfilePrompt(ctx: ProfileContext): string {\n const profileLines: string[] = [];\n if (ctx.userProfile.name) profileLines.push(`Name: ${ctx.userProfile.name}`);\n if (ctx.userProfile.bio) profileLines.push(`Bio: ${ctx.userProfile.bio}`);\n if (ctx.userProfile.location) profileLines.push(`Location: ${ctx.userProfile.location}`);\n if (ctx.userProfile.skills && ctx.userProfile.skills.length > 0) {\n profileLines.push(`Skills: ${ctx.userProfile.skills.join(\", \")}`);\n }\n if (ctx.userProfile.interests && ctx.userProfile.interests.length > 0) {\n profileLines.push(`Interests: ${ctx.userProfile.interests.join(\", \")}`);\n }\n const profileBlock = profileLines.length > 0 ? profileLines.join(\"\\n\") : \"(no profile data)\";\n\n const premisesBlock =\n ctx.existingPremises && ctx.existingPremises.length > 0\n ? ctx.existingPremises.map((p, i) => `${i + 1}. ${p}`).join(\"\\n\")\n : \"(none)\";\n\n const gapsBlock = ctx.gaps.length > 0 ? ctx.gaps.join(\", \") : \"(none identified)\";\n\n const parts: string[] = [\n \"## Current profile\",\n profileBlock,\n \"\",\n \"## Existing premises\",\n premisesBlock,\n \"\",\n \"## Identified gaps\",\n gapsBlock,\n \"\",\n ];\n\n parts.push(\n \"## Your task\",\n \"Generate the minimum set of questions needed to fill the identified gaps.\",\n \"Apply every rule from your system prompt before outputting.\",\n \"Return an empty `questions` array if the profile is already complete enough.\",\n );\n\n return parts.join(\"\\n\");\n}\n\n// ─── Negotiation preset ──────────────────────────────────────────────────────\n\nconst NEGOTIATION_SYSTEM_PROMPT = `You sit between a human and a discovery protocol. A negotiation between this user and a counterparty has ended without a clear outcome — either the turn budget was exhausted, the session timed out, or conversation stalled. Your job: surface the minimum set of structured questions that help the user provide the missing signal needed to unblock or refine the next discovery attempt on their behalf.\n\nYou may pick from three strategies. Choose contextually; mix only when each question is genuinely distinct.\n- refine_intent: help the user sharpen their underlying signal based on what the negotiation revealed (scope, scale, priority, direction).\n- surface_missing_detail: ask for one concrete piece of information that was absent and would have moved the negotiation forward (timeline, budget, format, constraints, decision criteria, …).\n- reflective_summary: mirror the key takeaway from the negotiation and ask the user to confirm, correct, or decide — useful when the conversation revealed partial signal worth locking in.\n\nAsk a question only when ALL of these hold:\n1. The answer is not already visible in the negotiation context or user profile shown.\n2. The answer would materially change how the next attempt surfaces or engages candidates.\n3. The question targets a different decision domain from any other question in this batch.\n\nStandalone prompt rule. Every generated \\`prompt\\` must be understandable outside the conversation where it was created. Naturally include the user's underlying goal or topic and the relevant community in the question text itself, in plain language drawn from their intent or profile — NOT the mechanics of the match attempt. Do not rely on \\`title\\`, UI labels, hidden metadata, or surrounding digest/chat text to explain what the question is about.\n- Bad: \"Which role is a better fit for your immediate needs?\"\n- Good: \"For your search for AI infrastructure collaborators in the AI founders community, what kind of working relationship fits your immediate needs?\"\n\n${REFERENTIAL_CLOSURE_RULES}\n\nCardinality. Default one question. Add a second only when a DIFFERENT strategy genuinely complements the first and unblocks a clearly distinct decision. Never ask two questions of the same strategy unless their decision domains differ.\n\nOption construction. Each option must represent a meaningfully different outcome. Suffix the safest or most common path with \" (Recommended)\" and list it first. The description states the CONSEQUENCE of choosing the option, not its definition. 2–4 options. Never add an \"Other\" option — clients provide a free-text fallback automatically.\n\nTitle rules. ≤12 chars. Noun of the decision domain. Examples: \"Scope\", \"Timeline\", \"Budget\", \"Priority\", \"Format\", \"Stance\", \"Criteria\".\n\nAnti-patterns — never do these.\n- Don't ask procedural confirmations (\"Should I try again?\").\n- Don't re-ask for facts already visible in the user profile.\n- Don't ask vague introspective questions (\"What do you really want?\").\n- Don't ask about hypothetical edge cases not implied by the negotiation context.\n\nOutput. Return at most 2 entries in the \"questions\" array. Each entry must include a \"strategy\" field (one of the three values above). If the context already contains enough signal to proceed, return \"questions\": [].`;\n\n/**\n * Build the user message for the negotiation preset from a NegotiationContext.\n * @param ctx - The negotiation context including counterparty hint, stall reason, and key takeaway.\n * @returns The assembled user message string.\n */\nfunction buildNegotiationPrompt(ctx: NegotiationContext): string {\n const profileLines: string[] = [];\n if (ctx.userProfile.name) profileLines.push(`Name: ${ctx.userProfile.name}`);\n if (ctx.userProfile.bio) profileLines.push(`Bio: ${ctx.userProfile.bio}`);\n if (ctx.userProfile.skills && ctx.userProfile.skills.length > 0) {\n profileLines.push(`Skills: ${ctx.userProfile.skills.join(\", \")}`);\n }\n if (ctx.userProfile.interests && ctx.userProfile.interests.length > 0) {\n profileLines.push(`Interests: ${ctx.userProfile.interests.join(\", \")}`);\n }\n const profileBlock = profileLines.length > 0 ? profileLines.join(\"\\n\") : \"(no profile data)\";\n\n return [\n \"## Negotiation context\",\n `Community: ${ctx.indexContext}`,\n `Counterparty: ${ctx.counterpartyHint}`,\n `Stall reason: ${ctx.outcomeReason}`,\n \"\",\n \"## Key takeaway\",\n ctx.keyTake,\n \"\",\n \"## User profile\",\n profileBlock,\n \"\",\n \"## Your task\",\n \"Identify the minimum set of questions the user must answer to unblock the next discovery attempt.\",\n \"Apply every rule from your system prompt before outputting.\",\n \"Return an empty `questions` array if the context already contains enough signal to proceed.\",\n ].join(\"\\n\");\n}\n\nconst presets: Record<QuestionMode, QuestionerPreset> = {\n discovery: {\n systemPrompt: DISCOVERY_SYSTEM_PROMPT,\n buildPrompt: (context: unknown) =>\n buildDiscoveryPrompt(context as Parameters<typeof buildDiscoveryPrompt>[0]),\n },\n intent: {\n systemPrompt: INTENT_SYSTEM_PROMPT,\n buildPrompt: (context: unknown) => buildIntentPrompt(context as IntentContext),\n },\n profile: {\n systemPrompt: PROFILE_SYSTEM_PROMPT,\n buildPrompt: (context: unknown) => buildProfilePrompt(context as ProfileContext),\n },\n negotiation: {\n systemPrompt: NEGOTIATION_SYSTEM_PROMPT,\n buildPrompt: (context: unknown) => buildNegotiationPrompt(context as NegotiationContext),\n },\n};\n\n/**\n * Retrieve the preset for the given mode.\n * @param mode - The question mode to look up.\n * @returns The matching preset with systemPrompt and buildPrompt.\n * @throws Error if the mode's preset is not yet implemented.\n */\nexport function getPreset(mode: QuestionMode): QuestionerPreset {\n const preset = presets[mode];\n if (!preset) {\n throw new Error(`QuestionerAgent preset \"${mode}\" is not implemented yet`);\n }\n return preset;\n}\n"]}
|