Drop a coin, grab a profile.
An OpenClaw hook that automatically injects contact and group profile files into agent context at bootstrap - so your agent already knows who it's talking to before the conversation starts.
When a session bootstraps, the hook parses the sessionKey to determine the channel, chat type, and user/group ID, then looks for a matching profile file in your workspace:
| Chat type | Lookup path | Injected as |
|---|---|---|
| Direct message | memory/contacts/<channel>-<id>.md |
CONTACT_PROFILE.md |
| Group / channel | memory/groups/<channel>-<id>.md |
CHANNEL_PROFILE.md |
If the file exists, its contents are injected into the agent's bootstrap context automatically.
When groupInclusion is enabled, the hook reads the group file's frontmatter members list (a flat array of sender IDs) and injects each member's contact file alongside the group profile. Your agent walks into every group chat knowing the room.
Members are maintained automatically via the auto-roster: when someone sends a message in a group, their ID is added to the group file's members list if it isn't there already. No manual maintenance required.
β οΈ The handler must be compiled before it can run.handler.tsis the source - OpenClaw needshandler.js.
git clone https://github.com/clawSean/claw-machine.git
cd claw-machine
bash install.shThat script:
- Compiles
handler.tsβhandler.jsviaesbuild - Copies
handler.js+HOOK.mdinto~/.openclaw/hooks/profile-injector/ - Prints confirmation
Then enable and restart:
openclaw hooks enable profile-injector
openclaw gateway restartnpx esbuild handler.ts --bundle --platform=node --format=esm --outfile=handler.js --external:node:fs --external:node:path
mkdir -p ~/.openclaw/hooks/profile-injector
cp handler.js HOOK.md ~/.openclaw/hooks/profile-injector/
openclaw hooks enable profile-injector
openclaw gateway restartopenclaw hooks list
# Should show: π€ profile-injector β readyIn a chat, run /context or /status to confirm profile files are being injected.
Add to your openclaw.json under hooks.internal.entries:
{
"hooks": {
"internal": {
"entries": {
"profile-injector": {
"enabled": true,
"createOnMiss": false,
"autoRoster": true,
"contactTemplate": "memory/contacts/_EXAMPLE-contact.md",
"channelTemplate": "memory/groups/_EXAMPLE-channel.md",
"groupInclusion": {
"enabled": true,
"maxContacts": 10,
"profileDepth": "full"
}
}
}
}
}
}| Option | Type | Default | Description |
|---|---|---|---|
createOnMiss |
boolean |
false |
Create a new profile from template if none exists |
autoRoster |
boolean |
true |
Auto-add new group senders to group file's members list |
contactTemplate |
string |
memory/contacts/_EXAMPLE-contact.md |
Workspace-relative path to contact template |
channelTemplate |
string |
memory/groups/_EXAMPLE-channel.md |
Workspace-relative path to group template |
groupInclusion.enabled |
boolean |
false |
Inject member contact profiles in group sessions |
groupInclusion.maxContacts |
number |
10 |
Max member profiles to inject per group session |
groupInclusion.profileDepth |
string |
"full" |
"full", "medium" (40 lines), or "small" (15 lines) |
The hook is strict about naming conventions and frontmatter format. Deviations silently fail - no crash, no error, just no injection.
Files must follow <channel>-<id>.md, derived directly from the session key:
memory/contacts/telegram-6566057320.md β
memory/groups/telegram--1003813189624.md β
(double dash = negative group ID)
memory/contacts/tg-6566057320.md β (wrong channel prefix)
memory/contacts/jpop.md β (no channel-id pattern)
Telegram group IDs are negative, so group filenames always have a double dash (e.g., telegram--1003813189624.md). This is correct - don't "fix" it.
The members key must be a YAML list of ID strings inside the frontmatter:
---
id: "telegram:-1003813189624"
name: "My Group"
type: "group"
members:
- "6566057320"
- "123456"
---Rules:
- Each entry is just a sender ID - no names, roles, or paths needed
- Must be inside the
---frontmatter block - Each entry on its own
- "id"line - Both quoted strings and bare numbers work
Will NOT work:
members: ["6566057320", "123456"] # inline array
members: # object entries
- id: "6566057320"
name: "JPop"<workspace>/
βββ memory/
βββ contacts/
β βββ telegram-6566057320.md
β βββ discord-789012.md
β βββ _EXAMPLE-contact.md # template (optional)
βββ groups/
βββ telegram--1003707644960.md # note: double dash
βββ discord-456789.md
βββ _EXAMPLE-channel.md # template (optional)
- File exists? Check the exact path -
ls memory/contacts/telegram-<id>.md - Filename correct? Must be
<channel>-<id>.mdwith the right channel prefix - For groups -
groupInclusion.enabled: true? Default isfalse - For groups - members in frontmatter? Check
members:list exists and has IDs - Hook loaded?
openclaw hooks listshould showπ€ profile-injector β - Restarted? Hook changes need a gateway restart
- Group file must already exist (auto-roster updates existing files, doesnβt create new ones)
- Check
autoRosterisnβt explicitly set tofalsein config - Channel must provide
metadata.senderIdin themessage:receivedevent - Chat type is derived from
event.sessionKey, notmetadata.chatType(which OpenClaw does not provide) - Workspace dir is NOT
process.cwd()β gateway cwd is/root, not the workspace. The hook readsworkspace.dirfrom~/.openclaw/openclaw.jsonor falls back to~/.openclaw/workspace - Config and hook settings are read from
~/.openclaw/openclaw.jsondirectly sincemessage:receivedevents donβt includecfg - Group must be in
channels.telegram.groupsconfig withrequireMention: falseβ unconfigured groups default to mention-required, and unmentioned messages are dropped before the hook fires - Auto-roster also fires on
command:new(/new), ensuring members are rostered before bootstrap reads them
Each member profile counts toward bootstrapMaxChars. For large groups:
- Set
profileDepth: "small"(15 lines per profile) - Lower
maxContacts(e.g., 5 instead of 10) - Or increase
agents.defaults.bootstrapMaxCharsin config
SIGUSR1 hot-reload can occasionally crash during active processing. If the bot stops responding:
- Check:
pgrep openclaw-gateway - It auto-restarts, but messages during the ~5s crash window are lost
- Safer: deploy during low-traffic periods or use full
openclaw gateway restart
MIT - grab it, use it, claw away.