Drop a coin, grab the right profile.
Claw Machine is an OpenClaw internal hook that injects human-readable contact and room profiles into agent bootstrap context. Version 4 lets one person keep one canonical profile across Telegram, iMessage, SMS, Slack, Discord, and other explicit channel identities.
It is intentionally local and boring: Markdown files, exact IDs, one small in-memory index, no database, no LLM lookup, and no fuzzy identity matching.
| Conversation | Bootstrap context |
|---|---|
| Direct message | CONTACT_PROFILE.md from the resolved canonical contact file |
| Group/channel | CHANNEL_PROFILE.md plus bounded member contact profiles |
Group frontmatter keeps a flat members: roster. The hook auto-adds senders on
message:received and command:new, then resolves every member through the
same contact index used for DMs.
Choose one canonical contact file and list only identities you have verified:
---
id: "telegram:123456789"
identities:
- "telegram:123456789"
- "imessage:+12065550100"
- "sms:+12065550100"
- "slack:U01234567"
name: "Example Person"
---Lookup order:
- Normalize the incoming
channel:idwithout changing channels or guessing. - Resolve it through
identities:plus the legacy scalarid:. - If two files claim it, refuse to inject either and log the collision.
- If no alias claims it, fall back to the legacy exact filename
memory/contacts/<channel>-<id>.md. - Create a new exact-path profile only after both lookup paths miss.
Names, usernames, phone similarity, and profile prose are never used to merge
people. session.identityLinks is also deliberately separate: it changes
OpenClaw session routing, while Claw Machine changes only profile resolution.
Provider IDs that are workspace/account-scoped must be unique in this contact
set; current bootstrap session keys do not expose a separate account scope.
The contact directory is scanned once on the first lookup. The resulting
Map<identity, canonical-file> is:
- reused by every DM and every member in a group bootstrap;
- invalidated when the contact directory changes;
- rebuilt after a TTL as a self-healing fallback for missed filesystem events;
- bounded by
identityResolution.maxFiles; - stored only in memory, never as a second persisted source of truth.
Warm identity resolution is an in-memory map lookup plus the same one profile read the legacy hook already needed. Group member context also has a separate character budget so a large roster cannot consume the entire bootstrap budget.
Run the included benchmark against a real workspace:
npm run benchmark -- /path/to/workspace/memory/contacts 100Requires Node.js >=22.13 and an OpenClaw version with agent:bootstrap
internal hooks.
git clone https://github.com/clawSean/claw-machine.git
cd claw-machine
bash install.shThe installer runs the tests, builds handler.js with Node's built-in
TypeScript transformer, and copies handler.js plus HOOK.md into
~/.openclaw/hooks/profile-injector/.
Then enable the hook and restart OpenClaw according to your operator approval and rollback policy:
openclaw hooks enable profile-injector
openclaw gateway restartHook code is loaded at Gateway start; a config hot reload does not replace the already-imported handler module.
{
"hooks": {
"internal": {
"entries": {
"profile-injector": {
"enabled": true,
"createOnMiss": false,
"autoRoster": true,
"contactDir": "memory/contacts",
"groupDir": "memory/groups",
"contactTemplate": "memory/contacts/_EXAMPLE-contact.md",
"channelTemplate": "memory/groups/_EXAMPLE-channel.md",
"identityResolution": {
"enabled": true,
"cacheTtlMs": 60000,
"maxFiles": 1000,
"scanConcurrency": 4
},
"groupInclusion": {
"enabled": true,
"maxContacts": 10,
"profileDepth": "full",
"maxTotalChars": 30000
}
}
}
}
}
}| Option | Default | Purpose |
|---|---|---|
createOnMiss |
false |
Create a rendered profile only after alias and exact lookup miss |
autoRoster |
true |
Serialize and atomically persist new group senders |
contactDir |
memory/contacts |
Workspace-relative canonical contact directory |
groupDir |
memory/groups |
Workspace-relative room-profile directory |
identityResolution.enabled |
true |
Enable frontmatter alias resolution |
identityResolution.cacheTtlMs |
60000 |
Maximum cache age; filesystem edits invalidate sooner |
identityResolution.maxFiles |
1000 |
Fail-safe scan bound; exact lookup remains available on index failure |
identityResolution.scanConcurrency |
4 |
Bounded parallel reads while rebuilding the tiny index |
groupInclusion.enabled |
false |
Inject rostered member profiles |
groupInclusion.maxContacts |
10 |
Maximum roster entries considered per bootstrap |
groupInclusion.profileDepth |
full |
full, medium (40 lines), or small (15 lines) |
groupInclusion.maxTotalChars |
30000 |
Aggregate character cap for member profiles only |
Configured directories and templates must remain inside the workspace.
Block lists and inline JSON arrays are accepted. IDs may be numeric or platform-native strings such as Slack IDs, phone handles, and emails.
---
id: "slack:C01234567"
members:
- "U01234567"
- "U07654321"
---Auto-roster writes are serialized per room and replaced atomically, preventing concurrent inbound messages from losing each other's IDs.
Audit before adding aliases:
npm run audit -- /path/to/workspace/memory/contactsThen, per real person:
- Pick the canonical file after reviewing every duplicate's facts and trust.
- Merge only verified facts into it.
- Add every verified
channel:idtoidentities:. - Remove or archive duplicate live profiles; do not leave two claimants.
- Re-run the audit until
explicitIdentityCollisionsis empty. - Deploy with rollback ready, restart, and prove both DM and group-member paths.
Do not bulk-merge from names or usernames. A wrong alias can inject another person's private memory and authority posture; a collision is safer than a guess.
npm test
npm run audit -- /path/to/contacts
npm run benchmark -- /path/to/contacts 100The project has no runtime dependency. Small ordered modules under src/ are
the canonical source; handler.js is generated and committed for managed-hook
installs.
- Run
openclaw hooks checkandopenclaw hooks info profile-injector. - Check logs for
Identity collision; the listed files must be reconciled. - Confirm aliases use exact
channel:idvalues from OpenClaw session keys. - Confirm configured paths stay inside the workspace.
- If aliases were edited, the watcher should invalidate immediately; the TTL guarantees a later rebuild even if the platform misses the event.
- If group context is large, lower
maxContacts,profileDepth, ormaxTotalCharsbefore raising OpenClaw's global bootstrap budget.
MIT — grab it, use it, claw away.