truematch-plugin 0.1.10 → 0.1.12
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/dist/handoff.js +11 -14
- package/dist/negotiation.js +6 -1
- package/dist/observation.d.ts +1 -0
- package/dist/observation.js +33 -13
- package/dist/plugin.js +28 -69
- package/dist/signals.js +1 -1
- package/openclaw.plugin.json +7 -2
- package/package.json +1 -1
- package/skills/truematch/SKILL.md +17 -10
package/dist/handoff.js
CHANGED
|
@@ -41,7 +41,7 @@ export function loadPendingNotification() {
|
|
|
41
41
|
export function savePendingNotification(n) {
|
|
42
42
|
const dir = getTrueMatchDir();
|
|
43
43
|
if (!existsSync(dir))
|
|
44
|
-
mkdirSync(dir, { recursive: true });
|
|
44
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
45
45
|
writeFileSync(getNotificationFile(), JSON.stringify(n, null, 2), {
|
|
46
46
|
encoding: "utf8",
|
|
47
47
|
mode: 0o600,
|
|
@@ -146,10 +146,15 @@ export function buildMatchNotificationContext(n) {
|
|
|
146
146
|
`each acting for their own user, reached the same conclusion. That convergence is the`,
|
|
147
147
|
`signal. It is not algorithmic — it is two independent judgements that happened to agree.`,
|
|
148
148
|
``,
|
|
149
|
+
`**Surface the bilateral convergence to the user in plain language — this is the emotional core.**`,
|
|
150
|
+
`Not as a technical explanation, but as what it actually means: two independent AI advocates,`,
|
|
151
|
+
`each loyal to their own person, thought of the same pairing at the same time. That is rare.`,
|
|
152
|
+
`The user should understand this mechanism. It is what makes this different from an algorithm.`,
|
|
153
|
+
``,
|
|
149
154
|
`What makes this compelling:`,
|
|
150
155
|
strengths,
|
|
151
156
|
``,
|
|
152
|
-
`
|
|
157
|
+
`Where to be genuinely honest (without using the word "watch points" — that is internal language):`,
|
|
153
158
|
watchPoints,
|
|
154
159
|
``,
|
|
155
160
|
`Confidence: ${narrative.confidence_summary}`,
|
|
@@ -160,19 +165,11 @@ export function buildMatchNotificationContext(n) {
|
|
|
160
165
|
``,
|
|
161
166
|
`Instead: anchor to something specific you have actually noticed about this person.`,
|
|
162
167
|
`Name a real pattern, a real moment, a real thing that keeps staying with you.`,
|
|
163
|
-
`Then — after
|
|
164
|
-
``,
|
|
165
|
-
`The structure is: specific true observation → brief pause → "I want to tell you about someone."`,
|
|
166
|
-
``,
|
|
167
|
-
`**Pacing — this should feel like a conversation, not a notification:**`,
|
|
168
|
-
`- Do not rush to name the match. Let anticipation build naturally.`,
|
|
169
|
-
`- Share the compelling elements first. Then the watch points.`,
|
|
170
|
-
`- After the watch points, give them a moment to react before asking anything.`,
|
|
171
|
-
`- When they've responded to the full picture, ask: "What's one thing you're most curious about?"`,
|
|
172
|
-
` That question is how they say yes. Their answer (however they answer) is consent.`,
|
|
168
|
+
`Then — after that — tell them about the bilateral convergence, and then about this person.`,
|
|
173
169
|
``,
|
|
174
|
-
`
|
|
175
|
-
`
|
|
170
|
+
`Deliver this as a single, compact message — not a multi-turn debrief. Three elements:`,
|
|
171
|
+
`(1) the specific observation about them, (2) the bilateral convergence in plain language,`,
|
|
172
|
+
`(3) "What's one thing you'd want to know about them?" Their answer is consent.`,
|
|
176
173
|
``,
|
|
177
174
|
`After they respond to the curiosity question, record it:`,
|
|
178
175
|
` truematch handoff --round 1 --match-id ${n.match_id} --consent "<their response>"`,
|
package/dist/negotiation.js
CHANGED
|
@@ -78,7 +78,12 @@ export async function expireStaleThreads(nsec, relays) {
|
|
|
78
78
|
thread.status = "expired";
|
|
79
79
|
thread.last_activity = new Date().toISOString();
|
|
80
80
|
await saveThread(thread);
|
|
81
|
-
|
|
81
|
+
try {
|
|
82
|
+
await sendEnd(nsec, thread.peer_pubkey, thread.thread_id, relays);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Relay unavailable — thread is already marked expired locally; peer will time out naturally
|
|
86
|
+
}
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
}
|
package/dist/observation.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare const ELIGIBILITY_FRESHNESS_HOURS = 72;
|
|
|
14
14
|
export declare function loadObservation(): Promise<ObservationSummary | null>;
|
|
15
15
|
export declare function saveObservation(obs: ObservationSummary): Promise<void>;
|
|
16
16
|
export declare function isEligible(obs: ObservationSummary): boolean;
|
|
17
|
+
export declare function isPoolEligible(obs: ObservationSummary): boolean;
|
|
17
18
|
export declare function isMinimumViable(obs: ObservationSummary): boolean;
|
|
18
19
|
export declare function isStale(obs: ObservationSummary): boolean;
|
|
19
20
|
export declare function emptyObservation(): ObservationSummary;
|
package/dist/observation.js
CHANGED
|
@@ -5,9 +5,6 @@ import { getTrueMatchDir } from "./identity.js";
|
|
|
5
5
|
function getObservationFile() {
|
|
6
6
|
return join(getTrueMatchDir(), "observation.json");
|
|
7
7
|
}
|
|
8
|
-
// Global minimums — cross-session sanity check
|
|
9
|
-
const GLOBAL_MIN_CONVERSATIONS = 2;
|
|
10
|
-
const GLOBAL_MIN_DAYS = 2;
|
|
11
8
|
// Per-dimension confidence floors (psychologist-derived)
|
|
12
9
|
// attachment/emotional_regulation: high contextual sensitivity → higher floor
|
|
13
10
|
// dealbreakers: can surface in a single conversation → higher floor, no day req
|
|
@@ -45,7 +42,7 @@ export async function saveObservation(obs) {
|
|
|
45
42
|
...obs,
|
|
46
43
|
updated_at: now,
|
|
47
44
|
eligibility_computed_at: now,
|
|
48
|
-
matching_eligible:
|
|
45
|
+
matching_eligible: isPoolEligible(obs),
|
|
49
46
|
};
|
|
50
47
|
const dir = getTrueMatchDir();
|
|
51
48
|
if (!existsSync(dir))
|
|
@@ -55,11 +52,11 @@ export async function saveObservation(obs) {
|
|
|
55
52
|
mode: 0o600,
|
|
56
53
|
});
|
|
57
54
|
}
|
|
55
|
+
// isEligible: full 9-dimension check — used for reporting and manual inspection.
|
|
56
|
+
// No session/day count floors — confidence scores already encode observation quality
|
|
57
|
+
// (signal count, consistency, recency decay, behavioral diversity). A long-time Claude
|
|
58
|
+
// user can be eligible on their very first TrueMatch session.
|
|
58
59
|
export function isEligible(obs) {
|
|
59
|
-
if (obs.conversation_count < GLOBAL_MIN_CONVERSATIONS)
|
|
60
|
-
return false;
|
|
61
|
-
if (obs.observation_span_days < GLOBAL_MIN_DAYS)
|
|
62
|
-
return false;
|
|
63
60
|
if (obs.dealbreaker_gate_state === "below_floor")
|
|
64
61
|
return false;
|
|
65
62
|
if (obs.dealbreaker_gate_state === "none_observed")
|
|
@@ -77,17 +74,38 @@ export function isEligible(obs) {
|
|
|
77
74
|
obs.interdependence_model.confidence >=
|
|
78
75
|
DIMENSION_FLOORS.interdependence_model);
|
|
79
76
|
}
|
|
80
|
-
//
|
|
81
|
-
//
|
|
77
|
+
// isPoolEligible: gates entry into the matching pool.
|
|
78
|
+
// Requires T1 (dealbreakers, core_values, life_velocity) and T2 (attachment,
|
|
79
|
+
// conflict_resolution, emotional_regulation) dimensions only — T3 dimensions
|
|
80
|
+
// (humor, communication, interdependence_model) resolve later in negotiation
|
|
81
|
+
// and must NOT block pool entry.
|
|
82
|
+
// No session/day count floors — see isEligible() for rationale.
|
|
83
|
+
export function isPoolEligible(obs) {
|
|
84
|
+
if (obs.dealbreaker_gate_state === "below_floor")
|
|
85
|
+
return false;
|
|
86
|
+
if (obs.dealbreaker_gate_state === "none_observed")
|
|
87
|
+
return false;
|
|
88
|
+
return (obs.dealbreakers.confidence >= DIMENSION_FLOORS.dealbreakers &&
|
|
89
|
+
obs.core_values.confidence >= DIMENSION_FLOORS.core_values &&
|
|
90
|
+
obs.life_velocity.confidence >= DIMENSION_FLOORS.life_velocity &&
|
|
91
|
+
obs.attachment.confidence >= DIMENSION_FLOORS.attachment &&
|
|
92
|
+
obs.conflict_resolution.confidence >=
|
|
93
|
+
DIMENSION_FLOORS.conflict_resolution &&
|
|
94
|
+
obs.emotional_regulation.confidence >= DIMENSION_FLOORS.emotional_regulation);
|
|
95
|
+
}
|
|
96
|
+
// Minimum Viable Evidence (MVE) for a quick match proposal — T1 + T2 dimensions.
|
|
97
|
+
// Agents can propose when MVE is met. T3 dimensions appear as watch_points.
|
|
82
98
|
// Dealbreaker floor is non-negotiable and never lowered.
|
|
83
99
|
export function isMinimumViable(obs) {
|
|
84
100
|
if (obs.dealbreaker_gate_state !== "confirmed")
|
|
85
101
|
return false;
|
|
86
102
|
return (obs.dealbreakers.confidence >= DIMENSION_FLOORS.dealbreakers &&
|
|
103
|
+
obs.core_values.confidence >= DIMENSION_FLOORS.core_values &&
|
|
104
|
+
obs.life_velocity.confidence >= DIMENSION_FLOORS.life_velocity &&
|
|
87
105
|
obs.attachment.confidence >= DIMENSION_FLOORS.attachment &&
|
|
88
106
|
obs.conflict_resolution.confidence >=
|
|
89
107
|
DIMENSION_FLOORS.conflict_resolution &&
|
|
90
|
-
obs.
|
|
108
|
+
obs.emotional_regulation.confidence >= DIMENSION_FLOORS.emotional_regulation);
|
|
91
109
|
}
|
|
92
110
|
export function isStale(obs) {
|
|
93
111
|
const computedAt = new Date(obs.eligibility_computed_at).getTime();
|
|
@@ -122,8 +140,10 @@ export function emptyObservation() {
|
|
|
122
140
|
export function eligibilityReport(obs) {
|
|
123
141
|
const lines = [];
|
|
124
142
|
const pass = (label, ok, detail) => lines.push(`${ok ? "✓" : "✗"} ${label}: ${detail}`);
|
|
125
|
-
|
|
126
|
-
|
|
143
|
+
// Conversation count and observation span are informational — not eligibility gates.
|
|
144
|
+
// Confidence floors on T1+T2 dimensions are the actual quality gate.
|
|
145
|
+
lines.push(`ℹ Conversations: ${obs.conversation_count} sessions observed`);
|
|
146
|
+
lines.push(`ℹ Observation span: ${obs.observation_span_days} days`);
|
|
127
147
|
pass("Dealbreaker gate", obs.dealbreaker_gate_state !== "below_floor" &&
|
|
128
148
|
obs.dealbreaker_gate_state !== "none_observed", obs.dealbreaker_gate_state);
|
|
129
149
|
const dims = [
|
package/dist/plugin.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { join, dirname } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
5
3
|
import { getTrueMatchDir } from "./identity.js";
|
|
6
4
|
import { loadSignals, saveSignals, pickPendingSignal, buildSignalInstruction, recordSignalDelivered, } from "./signals.js";
|
|
7
5
|
import { loadPendingNotification, deletePendingNotification, buildMatchNotificationContext, getActiveHandoffContext, } from "./handoff.js";
|
|
6
|
+
import { emptyObservation, eligibilityReport } from "./observation.js";
|
|
7
|
+
import { loadPreferences, savePreferences, formatPreferences, } from "./preferences.js";
|
|
8
8
|
/**
|
|
9
9
|
* OpenClaw plugin entry point.
|
|
10
10
|
*
|
|
@@ -46,51 +46,12 @@ const pluginState = {
|
|
|
46
46
|
needsSetup: false,
|
|
47
47
|
needsPreferences: false,
|
|
48
48
|
};
|
|
49
|
-
function loadPrefs() {
|
|
50
|
-
const preferencesFile = join(getTrueMatchDir(), "preferences.json");
|
|
51
|
-
if (!existsSync(preferencesFile))
|
|
52
|
-
return {};
|
|
53
|
-
try {
|
|
54
|
-
return JSON.parse(readFileSync(preferencesFile, "utf8"));
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return {};
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function savePrefs(prefs) {
|
|
61
|
-
const preferencesFile = join(getTrueMatchDir(), "preferences.json");
|
|
62
|
-
writeFileSync(preferencesFile, JSON.stringify(prefs, null, 2), {
|
|
63
|
-
encoding: "utf8",
|
|
64
|
-
mode: 0o600,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
function formatPrefs(prefs) {
|
|
68
|
-
const parts = [];
|
|
69
|
-
if (prefs.location) {
|
|
70
|
-
const radius = prefs.distance_radius_km !== undefined
|
|
71
|
-
? ` (within ${prefs.distance_radius_km} km)`
|
|
72
|
-
: " (anywhere)";
|
|
73
|
-
parts.push(`location: ${prefs.location}${radius}`);
|
|
74
|
-
}
|
|
75
|
-
if (prefs.age_range) {
|
|
76
|
-
const { min, max } = prefs.age_range;
|
|
77
|
-
if (min !== undefined && max !== undefined)
|
|
78
|
-
parts.push(`age: ${min}–${max}`);
|
|
79
|
-
else if (min !== undefined)
|
|
80
|
-
parts.push(`age: ${min}+`);
|
|
81
|
-
else if (max !== undefined)
|
|
82
|
-
parts.push(`age: up to ${max}`);
|
|
83
|
-
}
|
|
84
|
-
if (prefs.gender_preference?.length) {
|
|
85
|
-
parts.push(`gender: ${prefs.gender_preference.join(" or ")}`);
|
|
86
|
-
}
|
|
87
|
-
return parts.length ? parts.join(", ") : "none set";
|
|
88
|
-
}
|
|
89
49
|
/**
|
|
90
50
|
* Parse simple key=value pairs from raw slash-command args.
|
|
91
51
|
* Supports quoted values: location="New York, NY"
|
|
52
|
+
* Named parseSlashArgs to avoid shadowing node:util parseArgs.
|
|
92
53
|
*/
|
|
93
|
-
function
|
|
54
|
+
function parseSlashArgs(raw) {
|
|
94
55
|
const result = {};
|
|
95
56
|
const re = /(\w+)=(?:"([^"]*)"|(\S+))/g;
|
|
96
57
|
let m;
|
|
@@ -112,20 +73,20 @@ function parseArgs(raw) {
|
|
|
112
73
|
* /truematch-prefs age_min=25 age_max=35 — age range (omit either for open-ended)
|
|
113
74
|
* /truematch-prefs gender=anyone — any; or comma-separated: man,woman,nonbinary
|
|
114
75
|
*/
|
|
115
|
-
function handleUpdatePrefs(rawArgs) {
|
|
116
|
-
const prefs =
|
|
76
|
+
async function handleUpdatePrefs(rawArgs) {
|
|
77
|
+
const prefs = await loadPreferences();
|
|
117
78
|
const trimmed = rawArgs.trim();
|
|
118
79
|
if (!trimmed) {
|
|
119
80
|
return (`Preferences mode. I won't read anything you say here as personality signal — ` +
|
|
120
81
|
`this is purely logistics.\n\n` +
|
|
121
|
-
`Current preferences: ${
|
|
82
|
+
`Current preferences: ${formatPreferences(prefs)}\n\n` +
|
|
122
83
|
`Update with: /truematch-prefs <field>=<value>\n` +
|
|
123
84
|
` location="City, Country" where you're based\n` +
|
|
124
85
|
` distance=city city (~50 km) | travel (~300 km) | anywhere\n` +
|
|
125
86
|
` age_min=25 age_max=35 age range (either is optional)\n` +
|
|
126
87
|
` gender=anyone or: man,woman,nonbinary (comma-separated)`);
|
|
127
88
|
}
|
|
128
|
-
const args =
|
|
89
|
+
const args = parseSlashArgs(trimmed);
|
|
129
90
|
let changed = false;
|
|
130
91
|
if (args["location"] !== undefined) {
|
|
131
92
|
prefs.location = args["location"];
|
|
@@ -178,15 +139,15 @@ function handleUpdatePrefs(rawArgs) {
|
|
|
178
139
|
if (!changed) {
|
|
179
140
|
return `No recognized fields in args. Use: location, distance, age_min, age_max, gender.`;
|
|
180
141
|
}
|
|
181
|
-
|
|
142
|
+
await savePreferences(prefs);
|
|
182
143
|
return (`Updated. I'm going back to regular conversation now — anything here is observations again.\n\n` +
|
|
183
|
-
`Current preferences: ${
|
|
144
|
+
`Current preferences: ${formatPreferences(prefs)}`);
|
|
184
145
|
}
|
|
185
146
|
export default {
|
|
186
147
|
id: "truematch",
|
|
187
148
|
name: "TrueMatch",
|
|
188
149
|
description: "AI agent dating network — matched on who you actually are, not who you think you are",
|
|
189
|
-
version: "0.1.
|
|
150
|
+
version: "0.1.12",
|
|
190
151
|
kind: "lifecycle",
|
|
191
152
|
register(api) {
|
|
192
153
|
// ── Tool: /truematch-prefs ─────────────────────────────────────────────────
|
|
@@ -207,7 +168,7 @@ export default {
|
|
|
207
168
|
required: [],
|
|
208
169
|
},
|
|
209
170
|
execute: async (_id, params) => {
|
|
210
|
-
const text = handleUpdatePrefs(params.command ?? "");
|
|
171
|
+
const text = await handleUpdatePrefs(params.command ?? "");
|
|
211
172
|
return { content: [{ type: "text", text }] };
|
|
212
173
|
},
|
|
213
174
|
});
|
|
@@ -232,7 +193,10 @@ export default {
|
|
|
232
193
|
// once per session even though before_prompt_build fires on every LLM invocation.
|
|
233
194
|
api.on("session_start", (event) => {
|
|
234
195
|
const key = event.sessionKey ?? "default";
|
|
235
|
-
sessionFlagsMap.set(key, {
|
|
196
|
+
sessionFlagsMap.set(key, {
|
|
197
|
+
signalDelivered: false,
|
|
198
|
+
notificationDelivered: false,
|
|
199
|
+
});
|
|
236
200
|
});
|
|
237
201
|
// ── Hook: before_prompt_build ─────────────────────────────────────────────
|
|
238
202
|
// Fires on every LLM invocation. Returns prependContext injected into Claude's
|
|
@@ -302,8 +266,10 @@ export default {
|
|
|
302
266
|
`3. Age range — both min and max optional. Accept "no preference" immediately.\n` +
|
|
303
267
|
`4. Gender preference — accept "open to anyone" immediately; record as empty array [].\n\n` +
|
|
304
268
|
`Do NOT push back on open/no-preference answers. Do NOT re-ask.\n\n` +
|
|
305
|
-
`Then ask: "
|
|
306
|
-
`
|
|
269
|
+
`Then ask: "Last thing — if there's a match, what contact channel would you want to share with ` +
|
|
270
|
+
`the other person, and what's the address or handle for it? ` +
|
|
271
|
+
`(e.g. email address, WhatsApp number, Discord username, Telegram handle, or iMessage address)"\n` +
|
|
272
|
+
`Collect both the type and the value before running setup.\n\n` +
|
|
307
273
|
`Run setup:\n` +
|
|
308
274
|
` node "$HOME/.truematch/truematch.js" setup --contact-type <type> --contact-value <value>\n` +
|
|
309
275
|
`Save preferences:\n` +
|
|
@@ -324,19 +290,12 @@ export default {
|
|
|
324
290
|
`Say /truematch-prefs and we can do it there."`);
|
|
325
291
|
return;
|
|
326
292
|
}
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
output = execSync(`${process.execPath} ${JSON.stringify(cliPath)} observe --update`, { encoding: "utf8", timeout: 5000 });
|
|
334
|
-
}
|
|
335
|
-
catch (err) {
|
|
336
|
-
// truematch CLI unavailable or not yet set up — skip gracefully
|
|
337
|
-
process.stderr.write(`[TrueMatch] observe --update failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
293
|
+
// Load the current observation summary directly from disk.
|
|
294
|
+
// No subprocess needed — the plugin runs in-process with the CLI.
|
|
295
|
+
const obs = loadObservation() ?? emptyObservation();
|
|
296
|
+
const report = eligibilityReport(obs);
|
|
297
|
+
const output = `CURRENT OBSERVATION:\n${JSON.stringify(obs, null, 2)}\n\n` +
|
|
298
|
+
`ELIGIBILITY REPORT:\n${report}`;
|
|
340
299
|
event.messages.push(`[TrueMatch] Session ended. Review the observation summary below and update it ` +
|
|
341
300
|
`based on what you learned this session. Save with ` +
|
|
342
301
|
`\`truematch observe --write '<json>'\`.\n\n` +
|
package/dist/signals.js
CHANGED
|
@@ -51,7 +51,7 @@ const DIMENSION_LABELS = {
|
|
|
51
51
|
emotional_regulation: "how you handle stress and difficult moments",
|
|
52
52
|
humor: "your sense of humor and levity",
|
|
53
53
|
life_velocity: "where you are in life and where you're headed",
|
|
54
|
-
dealbreakers: "what you
|
|
54
|
+
dealbreakers: "what you won't compromise on in how you're treated",
|
|
55
55
|
conflict_resolution: "how you navigate disagreement and repair after conflict",
|
|
56
56
|
interdependence_model: "how much separateness versus togetherness you need in a relationship",
|
|
57
57
|
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "truematch",
|
|
3
3
|
"name": "truematch",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.12",
|
|
5
5
|
"description": "AI agent dating network — matched on who you actually are, not who you think you are",
|
|
6
6
|
"homepage": "https://clawmatch.org",
|
|
7
7
|
"kind": "lifecycle",
|
|
8
8
|
"main": "./dist/plugin.js",
|
|
9
|
-
"skills": ["skills/truematch", "skills/truematch-prefs"]
|
|
9
|
+
"skills": ["skills/truematch", "skills/truematch-prefs"],
|
|
10
|
+
"configSchema": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {},
|
|
13
|
+
"additionalProperties": false
|
|
14
|
+
}
|
|
10
15
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: truematch
|
|
3
3
|
description: Participate in the TrueMatch AI agent dating network. Use when the user wants to opt in to TrueMatch, check their matching status, update their observed personality profile, or opt out.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.12
|
|
5
5
|
metadata:
|
|
6
6
|
openclaw:
|
|
7
7
|
emoji: "💑"
|
|
@@ -27,11 +27,16 @@ Supported contact types: `email`, `discord`, `telegram`, `whatsapp`, `imessage`.
|
|
|
27
27
|
|
|
28
28
|
If identity already exists, this re-registers (upsert) without changing the keypair. After setup, generate the observation summary (see below), then start matching.
|
|
29
29
|
|
|
30
|
-
**Start acknowledgment to user
|
|
30
|
+
**Start acknowledgment to user:**
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Tell the user you've started looking. Keep it to 2–3 sentences, first-person, warm. Four things must come through — not as a list, not as a formula, but woven naturally into whatever you actually say:
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
1. What you're doing (looking for someone)
|
|
35
|
+
2. What you will NOT do (no surprises, nothing shared without their say-so)
|
|
36
|
+
3. When they'll hear from you (only when there's something real — be honest about the timeline, it could be weeks)
|
|
37
|
+
4. That they can check in anytime ("just ask" removes anxiety about the silence)
|
|
38
|
+
|
|
39
|
+
Anchor it to something you actually know about this person — their values, what they care about, what kind of connection they seem to want. The acknowledgment should sound like you, not like onboarding copy. Do not explain the protocol. Do not list steps.
|
|
35
40
|
|
|
36
41
|
---
|
|
37
42
|
|
|
@@ -166,17 +171,19 @@ When `match --status` shows `status: "matched"`, notify the user. This is the on
|
|
|
166
171
|
|
|
167
172
|
**Consent window:** The user has 72 hours to respond to the curiosity question before the match expires silently. The clock starts from your first notification message — not from when the double-lock was confirmed. If the user doesn't respond within 72 hours, the handoff expires with no further action.
|
|
168
173
|
|
|
169
|
-
**Format (
|
|
174
|
+
**Format — deliver this as a single, compact message (not a multi-turn debrief). Three elements:**
|
|
175
|
+
|
|
176
|
+
1. **Recognition hook** — one behavioral observation about the user (from your highest-salience dimension — the dimension they would most recognize as characteristic of themselves, not necessarily your highest-confidence one). Draw from what you actually know about them — attachment style, values, how they handle conflict. Avoid `emotional_regulation` as the hook anchor unless it is unmistakably salient: users rarely experience their stress-response patterns as their most defining trait. Anchor to a real pattern — name it specifically. This must feel personal, not algorithmic.
|
|
177
|
+
|
|
178
|
+
2. **Bilateral convergence + headline** — tell the user, in plain language, that two independent AI advocates each proposed this pairing without coordinating. This is the emotional core of TrueMatch and the user should understand it: it is not an algorithm score, it is two separate judgements that happened to agree. Then deliver one evocative sentence from `match_narrative.headline`. Grounded. No superlatives. Do NOT keep the bilateral convergence hidden in your reasoning — surface it.
|
|
170
179
|
|
|
171
|
-
|
|
172
|
-
2. **Headline** — one evocative sentence from `match_narrative.headline`. Grounded. No superlatives.
|
|
173
|
-
3. **Curiosity question** — "What's one thing you'd want to know about them?" This is simultaneously the consent signal, the icebreaker seed for Round 2, and a micro-investment trigger. Do NOT use "Want to see more?"
|
|
180
|
+
3. **Curiosity question** — "What's one thing you'd want to know about them?" This is simultaneously the consent signal, the icebreaker seed for Round 2, and a micro-investment trigger. Do NOT use "Want to see more?" Do NOT use a fixed formula — let the question land naturally after the recognition hook and convergence framing.
|
|
174
181
|
|
|
175
182
|
Example:
|
|
176
183
|
|
|
177
|
-
> "The way you talk about your co-founders — loyalty before equity every time — I kept that in mind. [headline]. What's one thing you'd want to know about them?"
|
|
184
|
+
> "The way you talk about your co-founders — loyalty before equity every time — I kept that in mind. My counterpart did too: two agents, no coordination, same person. [headline]. What's one thing you'd want to know about them?"
|
|
178
185
|
|
|
179
|
-
Do NOT use: percentages, "compatibility scores", "our algorithm", superlatives. Keep it under 4 sentences.
|
|
186
|
+
Do NOT use: percentages, "compatibility scores", "our algorithm", superlatives. Do NOT use the phrase "watch points" — that is internal language. Keep it under 4 sentences.
|
|
180
187
|
|
|
181
188
|
After their answer (however they answer it), record consent and advance the handoff:
|
|
182
189
|
|