@romiluz/clawmongo 2026.3.27 → 2026.3.28
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 +72 -49
- package/dist/{accounts-boH28BFM.js → accounts-lL2y51Ag.js} +46 -46
- package/dist/{acp-cli-DLaTb29R.js → acp-cli-BZvVHRY8.js} +46 -46
- package/dist/{action-runtime-Bk-7pvN2.js → action-runtime-DMYRUL4q.js} +10 -10
- package/dist/{actions.runtime-Y529miLb.js → actions.runtime-Bd-NMAq6.js} +46 -46
- package/dist/{actions.runtime-DVQ4pmcp.js → actions.runtime-gPSeXscW.js} +49 -49
- package/dist/{agent-scope-BROpmzKv.js → agent-scope-DXZH506l.js} +1 -1
- package/dist/{agents-DZqtbDMD.js → agents-Bt25bmDR.js} +13 -13
- package/dist/{agents-BbO-i90j.js → agents-CyJmttnS.js} +114 -114
- package/dist/{agents.config-CDS6iAat.js → agents.config-BWm70Hr6.js} +2 -2
- package/dist/{agents.config-iY500LY-.js → agents.config-UsCw0kOR.js} +4 -4
- package/dist/{audit-Cmj7BNcZ.js → audit-Dg8ZiJrJ.js} +9 -9
- package/dist/{audit-DmXeao7s.js → audit-DhXhWoiv.js} +1 -1
- package/dist/{audit-channel.collect.runtime-DhP09fjv.js → audit-channel.collect.runtime-CvPUHy1X.js} +2 -2
- package/dist/{audit-channel.runtime-C-watgwg.js → audit-channel.runtime-B6ZnOPFc.js} +46 -46
- package/dist/{audit-extra.async-B-7dzgcz.js → audit-extra.async-826UZZhJ.js} +3 -3
- package/dist/{audit-membership-runtime-CbLi3-5b.js → audit-membership-runtime-DLQrV7Rr.js} +46 -46
- package/dist/{audit.deep.runtime-D5VgrTOT.js → audit.deep.runtime-DQDtV7Bc.js} +4 -4
- package/dist/{audit.nondeep.runtime-B7M0-7FZ.js → audit.nondeep.runtime-H6B-2xal.js} +4 -4
- package/dist/{audit.runtime-BUknqGaP.js → audit.runtime-D2ZRC2A_.js} +47 -47
- package/dist/{auth-choice-DfJRosb4.js → auth-choice-CEowrO66.js} +57 -57
- package/dist/{auth-choice-CpB99A-L.js → auth-choice-DiufM1b8.js} +55 -55
- package/dist/{auth-choice-Bnnngmkx.js → auth-choice-YrGxwB9A.js} +5 -5
- package/dist/{auth-choice-options-DvqJHFOp.js → auth-choice-options-n3_uFdT5.js} +2 -2
- package/dist/{auth-choice-prompt-DtTjWfPT.js → auth-choice-prompt-DYFzoWa6.js} +50 -50
- package/dist/{auth-choice-prompt-Cxz8du6E.js → auth-choice-prompt-sLET2Bkw.js} +1 -1
- package/dist/{auth-choice.apply-helpers-Bm5p2ZX7.js → auth-choice.apply-helpers-GU6nZntg.js} +1 -1
- package/dist/{auth-choice.plugin-providers.runtime-Dsu6-vb8.js → auth-choice.plugin-providers.runtime-BNqOEEtb.js} +47 -47
- package/dist/{auth-profiles-2-OV37OF.js → auth-profiles-BWBQJ6X_.js} +67 -76
- package/dist/{auth-profiles.runtime-HcQMBs3K.js → auth-profiles.runtime-DfKRAmQR.js} +46 -46
- package/dist/{backend-config-avgzgS5Z.js → backend-config--L236wE2.js} +26 -1
- package/dist/{backup-create-B9F0M7-J.js → backup-create-Bll6DgoT.js} +2 -2
- package/dist/{base-session-key-DyLtSGkj.js → base-session-key-CA4SoGLF.js} +1 -1
- package/dist/{bluebubbles-BTR7u4zE.js → bluebubbles-BsX68JOY.js} +6 -6
- package/dist/{browser-cli-tmytvFaa.js → browser-cli-B5euA0cQ.js} +8 -8
- package/dist/build-info.json +3 -3
- package/dist/bundled/boot-md/handler.js +46 -46
- package/dist/bundled/bootstrap-extra-files/handler.js +1 -1
- package/dist/bundled/session-memory/handler.js +47 -47
- package/dist/{call-Cqem-bCB.js → call-C-_XQxw5.js} +1 -1
- package/dist/{call-CxVOiYz-.js → call-lPTaDgAg.js} +6 -6
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/{channel-Br8MF00M.js → channel-5o-oIU2B.js} +1 -1
- package/dist/{channel-BvRm4jSj.js → channel-C1zZg_8b.js} +2 -2
- package/dist/{channel-CqkxOhz2.js → channel-CEytBPH-.js} +4 -4
- package/dist/{channel-D_h7IfYK.js → channel-Cm65A1lo.js} +7 -7
- package/dist/{channel-ngZRDeE3.js → channel-D4K0MVgr.js} +5 -5
- package/dist/{channel-account-context-DZhZw4oo.js → channel-account-context-CNZZPDSe.js} +1 -1
- package/dist/{channel-plugin-resolution-CXm5HIcm.js → channel-plugin-resolution-WWO690RK.js} +3 -3
- package/dist/{channel-reply-pipeline-DdDsf-Lc.js → channel-reply-pipeline-B17-tBLU.js} +1 -1
- package/dist/{channel-shared-VIRIADYn.js → channel-shared-RH6YsZ0I.js} +1 -1
- package/dist/{channel-summary-BTJxLPN8.js → channel-summary-Cy7WDRjQ.js} +7 -7
- package/dist/{channel-summary-DdJtHlvH.js → channel-summary-DV4_b3HZ.js} +2 -2
- package/dist/{channel-tFDEkWt9.js → channel-yCiI29Ju.js} +6 -6
- package/dist/{channel.runtime-DGnRryi2.js → channel.runtime-C3m1vacV.js} +47 -47
- package/dist/{channel.runtime-CdPEA_wb.js → channel.runtime-CUSE7Dn0.js} +47 -47
- package/dist/{channel.runtime-0n3_Hz_Y.js → channel.runtime-CtWi36oj.js} +49 -49
- package/dist/{channel.runtime-CkHcsWQ3.js → channel.runtime-LUpQZiWg.js} +49 -49
- package/dist/{channel.runtime-msXRrVzC.js → channel.runtime-QOkM4mJV.js} +51 -51
- package/dist/{channel.runtime-khqV1QRG.js → channel.runtime-rYuUs2po.js} +4 -4
- package/dist/{channels-DNza27NY.js → channels-4VTbN2Gt.js} +103 -103
- package/dist/{channels-CLZoq3SY.js → channels-BUMnLXLQ.js} +1 -1
- package/dist/{channels-cli-Bhsnjsix.js → channels-cli-BYmLj8zw.js} +55 -55
- package/dist/{clawbot-cli-9YJNF9pF.js → clawbot-cli-CIYY1u-6.js} +47 -47
- package/dist/cli/daemon-cli.js +1 -1
- package/dist/{cli-FF5823nM.js → cli-DUA0FvhM.js} +46 -46
- package/dist/{command-registry-Bow_kWfU.js → command-registry-B1ECZip_.js} +13 -13
- package/dist/{command-registry-BEExVzem.js → command-registry-DOAwIs0Y.js} +2 -2
- package/dist/{command-secret-gateway-CzHSHmXG.js → command-secret-gateway-Big-n6JY.js} +46 -46
- package/dist/{compact.runtime-Bye-pm7Z.js → compact.runtime-BKnPPv9X.js} +46 -46
- package/dist/{completion-cli-B4hJ8HjW.js → completion-cli-BeLNvVTI.js} +3 -3
- package/dist/{completion-cli-DQrts0Ip.js → completion-cli-CHwHDPuI.js} +2 -2
- package/dist/{config-Ca7pLyBD.js → config-BOWZMmDA.js} +1 -1
- package/dist/{config-GKOWhVMv.js → config-C68iwvDj.js} +5 -5
- package/dist/{config-cli-CxPspN-S.js → config-cli-BnC336Lu.js} +48 -48
- package/dist/{config-guard-BK2hWyPi.js → config-guard-Bn6rZnzK.js} +6 -6
- package/dist/{config-validation-DO6o_Kt_.js → config-validation-BBuJZ-WH.js} +2 -2
- package/dist/{configure-CODqvxU-.js → configure-CHJDsjOD.js} +120 -120
- package/dist/{configure-Dg65gBEw.js → configure-Cctw7tyJ.js} +19 -23
- package/dist/{connection-auth-zhL6PVn0.js → connection-auth-C2XaPpF5.js} +1 -1
- package/dist/{control-ui-shared-_VwcOcc_.js → control-ui-shared-BC-g150y.js} +1 -1
- package/dist/{core-CzwQmKcn.js → core-Cb3XxL6S.js} +1 -1
- package/dist/{cron-cli-Dp_EQt2v.js → cron-cli-C10feOtH.js} +7 -7
- package/dist/{daemon-cli-Dgpv6Y0R.js → daemon-cli-Ds8mu2VZ.js} +4 -4
- package/dist/{daemon-install-Dq1Ix-FO.js → daemon-install-B__sP_7j.js} +49 -49
- package/dist/{delegate-BVapk4rY.js → delegate-ByCLviVI.js} +1 -1
- package/dist/{deliver-runtime-DPzMjhNU.js → deliver-Co7G5ubS.js} +46 -46
- package/dist/{deliver-C32Gg-xU.js → deliver-runtime-2svrtAf1.js} +46 -46
- package/dist/{devices-cli-3WN_wB1C.js → devices-cli-CH8nWgcU.js} +6 -6
- package/dist/{diagnostic-BbXfhLMi.js → diagnostic-DHTG43d_.js} +1 -1
- package/dist/{directory-cli-D_5BYdXm.js → directory-cli-B0REFlKo.js} +48 -48
- package/dist/{directory.static-CsajbHw1.js → directory.static-wnw_R8dE.js} +1 -1
- package/dist/{discord-C-4y9vOT.js → discord-Cws9MsXn.js} +4 -4
- package/dist/{discord-B3mmVXuG.js → discord-b8Ff_BKB.js} +46 -46
- package/dist/{dns-cli-CdAnUzRi.js → dns-cli-aRTleL5e.js} +5 -5
- package/dist/{doctor-completion-Dk8sgMOd.js → doctor-completion-KQWBMnEi.js} +1 -1
- package/dist/{doctor-config-preflight-BJ0lGLTO.js → doctor-config-preflight-BDYe5di1.js} +2 -2
- package/dist/{doctor-config-preflight-D32oVOWD.js → doctor-config-preflight-Dn5a_Dbr.js} +6 -6
- package/dist/{doctor-state-migrations-BklDIib1.js → doctor-state-migrations-BI6HzFyq.js} +47 -47
- package/dist/{doctor-state-migrations-Cdk3ep_f.js → doctor-state-migrations-DUPe9zNr.js} +2 -2
- package/dist/entry.js +1 -1
- package/dist/{exec-approvals-D8OOtgGc.js → exec-approvals-CZz2LqIW.js} +1 -1
- package/dist/{exec-approvals-cli-dGISWHE3.js → exec-approvals-cli-KbFpgd-p.js} +9 -9
- package/dist/extensionAPI.js +46 -46
- package/dist/extensions/amazon-bedrock/index.js +46 -46
- package/dist/extensions/anthropic/index.js +46 -46
- package/dist/extensions/bluebubbles/index.js +50 -50
- package/dist/extensions/bluebubbles/setup-entry.js +48 -48
- package/dist/extensions/byteplus/index.js +46 -46
- package/dist/extensions/chutes/index.js +48 -48
- package/dist/extensions/cloudflare-ai-gateway/index.js +47 -47
- package/dist/extensions/device-pair/index.js +46 -46
- package/dist/extensions/discord/index.js +48 -48
- package/dist/extensions/discord/node_modules/.package-lock.json +3 -3
- package/dist/extensions/discord/node_modules/hono/dist/adapter/service-worker/index.js +1 -3
- package/dist/extensions/discord/node_modules/hono/dist/cjs/adapter/service-worker/index.js +1 -3
- package/dist/extensions/discord/node_modules/hono/dist/cjs/helper/ssg/ssg.js +1 -1
- package/dist/extensions/discord/node_modules/hono/dist/cjs/middleware/cors/index.js +5 -2
- package/dist/extensions/discord/node_modules/hono/dist/cjs/request.js +1 -1
- package/dist/extensions/discord/node_modules/hono/dist/helper/ssg/ssg.js +1 -1
- package/dist/extensions/discord/node_modules/hono/dist/middleware/cors/index.js +5 -2
- package/dist/extensions/discord/node_modules/hono/dist/request.js +1 -1
- package/dist/extensions/discord/node_modules/hono/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/extensions/discord/node_modules/hono/dist/types/client/index.d.ts +1 -1
- package/dist/extensions/discord/node_modules/hono/dist/types/client/types.d.ts +20 -0
- package/dist/extensions/discord/node_modules/hono/dist/types/request.d.ts +1 -3
- package/dist/extensions/discord/node_modules/hono/package.json +1 -1
- package/dist/extensions/discord/setup-entry.js +48 -48
- package/dist/extensions/elevenlabs/index.js +46 -46
- package/dist/extensions/fal/index.js +46 -46
- package/dist/extensions/feishu/index.js +55 -55
- package/dist/extensions/feishu/setup-entry.js +51 -51
- package/dist/extensions/firecrawl/index.js +46 -46
- package/dist/extensions/github-copilot/index.js +47 -47
- package/dist/extensions/google/index.js +46 -46
- package/dist/extensions/huggingface/index.js +47 -47
- package/dist/extensions/imessage/index.js +49 -49
- package/dist/extensions/imessage/setup-entry.js +49 -49
- package/dist/extensions/irc/index.js +49 -49
- package/dist/extensions/irc/setup-entry.js +48 -48
- package/dist/extensions/kilocode/index.js +47 -47
- package/dist/extensions/kimi-coding/index.js +47 -47
- package/dist/extensions/line/index.js +9 -9
- package/dist/extensions/line/setup-entry.js +8 -8
- package/dist/extensions/llm-task/index.js +46 -46
- package/dist/extensions/lobster/index.js +5 -5
- package/dist/extensions/mattermost/index.js +49 -49
- package/dist/extensions/mattermost/setup-entry.js +48 -48
- package/dist/extensions/microsoft/index.js +46 -46
- package/dist/extensions/minimax/index.js +48 -48
- package/dist/extensions/mistral/index.js +47 -47
- package/dist/extensions/modelstudio/index.js +47 -47
- package/dist/extensions/moonshot/index.js +46 -46
- package/dist/extensions/nextcloud-talk/index.js +50 -50
- package/dist/extensions/nextcloud-talk/setup-entry.js +49 -49
- package/dist/extensions/ollama/index.js +5 -5
- package/dist/extensions/openai/index.js +47 -47
- package/dist/extensions/opencode/index.js +47 -47
- package/dist/extensions/opencode-go/index.js +47 -47
- package/dist/extensions/openrouter/index.js +47 -47
- package/dist/extensions/openshell/index.js +46 -46
- package/dist/extensions/qianfan/index.js +47 -47
- package/dist/extensions/qwen-portal-auth/index.js +47 -47
- package/dist/extensions/sglang/index.js +46 -46
- package/dist/extensions/signal/index.js +49 -49
- package/dist/extensions/signal/setup-entry.js +49 -49
- package/dist/extensions/slack/index.js +50 -50
- package/dist/extensions/slack/setup-entry.js +50 -50
- package/dist/extensions/synology-chat/index.js +9 -9
- package/dist/extensions/synology-chat/setup-entry.js +8 -8
- package/dist/extensions/synthetic/index.js +47 -47
- package/dist/extensions/talk-voice/index.js +46 -46
- package/dist/extensions/tavily/index.js +46 -46
- package/dist/extensions/telegram/index.js +48 -48
- package/dist/extensions/telegram/setup-entry.js +48 -48
- package/dist/extensions/together/index.js +47 -47
- package/dist/extensions/venice/index.js +47 -47
- package/dist/extensions/vercel-ai-gateway/index.js +47 -47
- package/dist/extensions/vllm/index.js +46 -46
- package/dist/extensions/voice-call/index.js +46 -46
- package/dist/extensions/volcengine/index.js +46 -46
- package/dist/extensions/xai/index.js +46 -46
- package/dist/extensions/xiaomi/index.js +47 -47
- package/dist/extensions/zai/index.js +47 -47
- package/dist/extensions/zalo/index.js +49 -49
- package/dist/extensions/zalo/setup-entry.js +48 -48
- package/dist/{feishu-C637DYYe.js → feishu-D2fuiOiy.js} +4 -4
- package/dist/{gateway-cli-BAueAYsk.js → gateway-cli-nOgyXtih.js} +72 -106
- package/dist/{gateway-install-token-DlcVm1rP.js → gateway-install-token-nvsTK8KN.js} +4 -4
- package/dist/{gateway-rpc-siKa8KpM.js → gateway-rpc-DrH142-v.js} +1 -1
- package/dist/{gateway-runtime-roiTaOKO.js → gateway-runtime-CQXKGzrv.js} +2 -2
- package/dist/{github-copilot-auth-DJljh3gq.js → github-copilot-auth-C4z2AAd8.js} +3 -3
- package/dist/{health-DU_pBiid.js → health-DEgLPyA9.js} +7 -7
- package/dist/{health-B1OK1kJg.js → health-H5nbiXvA.js} +48 -48
- package/dist/{heartbeat-summary-CMwt9qam.js → heartbeat-summary-CNCptWmA.js} +1 -1
- package/dist/{hooks-cli-D42hQEUb.js → hooks-cli-jBhsJaOg.js} +46 -46
- package/dist/{identity-file-8MUyIAOy.js → identity-file-CYQnH12I.js} +1 -1
- package/dist/{imessage-zqZjecTU.js → imessage-DYqgZpLc.js} +6 -6
- package/dist/{imessage-CZb_pS8U.js → imessage-Dau6WdE3.js} +46 -46
- package/dist/{inbound-reply-dispatch-B73AkQh4.js → inbound-reply-dispatch-BFE3xy1S.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{internal-Bg_k56Pk.js → internal-DLE9baj7.js} +0 -24
- package/dist/{io-DE9vm7Eo.js → io-Du0b8gMO.js} +2 -2
- package/dist/{io-B7rO2wud.js → io-dJgqp8U8.js} +4 -4
- package/dist/{irc-BWzsfr2V.js → irc-BRaOVUse.js} +2 -2
- package/dist/{kb-cli-BOPGkNfJ.js → kb-cli-Cdif_vwn.js} +12 -12
- package/dist/{library-i-2ymInt.js → library-BwjFIWw3.js} +46 -46
- package/dist/{lifecycle-core-DwIo1PfK.js → lifecycle-core-DNO4MItg.js} +1 -1
- package/dist/line/send.js +6 -6
- package/dist/{line-DbTKdVgY.js → line-BCXUGrVz.js} +2 -2
- package/dist/{llm-slug-generator-Dm30OM2W.js → llm-slug-generator-B55FZ1aG.js} +3 -3
- package/dist/llm-slug-generator.js +47 -47
- package/dist/{logging-BM4mQ53W.js → logging-BvOzg0cM.js} +1 -1
- package/dist/{logging-0tvlVvFG.js → logging-nWyKsj9d.js} +5 -5
- package/dist/{login-qr-DQPSabxI.js → login-qr-C0QA3j4x.js} +46 -46
- package/dist/{logs-cli-ZnHk5eku.js → logs-cli-thlXBA9n.js} +7 -7
- package/dist/{manager.runtime-D3vjUOZF.js → manager.runtime-SoTq-tT4.js} +46 -46
- package/dist/{matrix-migration-snapshot-CRKiHq6Y.js → matrix-migration-snapshot-_6Rg-iLO.js} +2 -2
- package/dist/{mattermost-Bqn7Fd5U.js → mattermost-2nv5O_jQ.js} +2 -2
- package/dist/{mcp-cli-7aN2vJv-.js → mcp-cli-1vpy_yti.js} +5 -5
- package/dist/{mcp-config-NVrqojpu.js → mcp-config-Bmi2GUum.js} +1 -1
- package/dist/{media-understanding.runtime-DesF8Xhs.js → media-understanding.runtime-Wh-_SFK_.js} +46 -46
- package/dist/{memory-cli-CWqIJQS5.js → memory-cli-COJRHkaJ.js} +46 -46
- package/dist/{memory-search-DyxoUpq2.js → memory-search-CGmdrdM1.js} +1 -1
- package/dist/{memory-search-CgoWYj0V.js → memory-search-Cy36nO15.js} +3 -3
- package/dist/{model-picker-DX9P5pDr.js → model-picker-CYWvoS3z.js} +48 -48
- package/dist/{model-picker-Ds7-VqDS.js → model-picker-_32ihKqK.js} +4 -4
- package/dist/{model-picker.runtime-DqDt4s3S.js → model-picker.runtime-DBT5uEFi.js} +49 -49
- package/dist/{model-selection-DXFVauys.js → model-selection-014eeYCV.js} +1 -1
- package/dist/{model-suppression.runtime-8mU0VJs6.js → model-suppression.runtime-GTI9Rm85.js} +46 -46
- package/dist/{models-GXQSWyBX.js → models-Be9EWpVm.js} +54 -54
- package/dist/{models-CVG-VMBa.js → models-CX35daXC.js} +14 -14
- package/dist/{models-cli-Cvy8rGqU.js → models-cli-BYjac3oq.js} +54 -54
- package/dist/{models-config-BdanqM9X.js → models-config-Agv-ThDH.js} +46 -46
- package/dist/{models-config.providers.discovery-BHZ3WglC.js → models-config.providers.discovery-BJwjDHsg.js} +1 -1
- package/dist/{mongodb-analytics-BszyEhsq.js → mongodb-analytics-HJKYeseb.js} +2 -2
- package/dist/{mongodb-analytics-CcSYo_ae.js → mongodb-analytics-mnKJih5x.js} +1 -1
- package/dist/{mongodb-auto-setup-QcwgZSN9.js → mongodb-auto-setup-CCElSwNy.js} +2 -2
- package/dist/{mongodb-kb-C-VmGOq7.js → mongodb-kb-1tSOXXb9.js} +3 -3
- package/dist/{mongodb-kb-BotgVda9.js → mongodb-kb-CdSvNe3w.js} +2 -2
- package/dist/{mongodb-kb-search-CoIAFX8n.js → mongodb-kb-search-Dq6E746K.js} +1 -1
- package/dist/{mongodb-kb-search-BBYY_d_r.js → mongodb-kb-search-DvsXIUy6.js} +3 -3
- package/dist/{mongodb-manager-BVfTfpHf.js → mongodb-manager-DEZIQXuo.js} +10 -10
- package/dist/{mongodb-manager-BJ0PAYF_.js → mongodb-manager-NkerSpvH.js} +1183 -331
- package/dist/{mongodb-procedures-DNPmmW5r.js → mongodb-procedures-DI-_Dakz.js} +7 -3
- package/dist/{mongodb-procedures-D3M2Ph8E.js → mongodb-procedures-tjhGdBV7.js} +3 -3
- package/dist/{mongodb-schema-JpDXcAUV.js → mongodb-schema-DN3aspLa.js} +1 -1
- package/dist/{mongodb-schema-BI4Ze_ZV.js → mongodb-schema-Dt6jxcPL.js} +298 -4
- package/dist/{mongodb-search-VFKd72IZ.js → mongodb-search-DMsUyfpa.js} +2 -2
- package/dist/{mongodb-structured-memory-DnwUJ6SA.js → mongodb-structured-memory-D-3SEfTu.js} +58 -2
- package/dist/{mongodb-structured-memory-BySGexcm.js → mongodb-structured-memory-lpyyW0kn.js} +3 -3
- package/dist/{monitor-DGE0bx3s.js → monitor-BZFy1RTb.js} +47 -47
- package/dist/{monitor-Dfc0KXMD.js → monitor-DPYGxeY_.js} +5 -5
- package/dist/{monitor-CZZlkQUU.js → monitor-Duykki-j.js} +51 -51
- package/dist/{monitor-B61bVnMW.js → monitor-gq31SALd.js} +8 -8
- package/dist/{nextcloud-talk-CnwOv1rG.js → nextcloud-talk-C_-LfGeq.js} +2 -2
- package/dist/{node-cli-Avub0RKe.js → node-cli-NpJzvfQW.js} +12 -12
- package/dist/{nodes-cli-qSE-cmgX.js → nodes-cli-O5HciXeP.js} +10 -10
- package/dist/{nodes-screen-DQw8F5rA.js → nodes-screen-BIwiB35h.js} +1 -1
- package/dist/{oauth.runtime-825pMCgB.js → oauth.runtime-BxjW8DFO.js} +46 -46
- package/dist/{oauth.runtime-BCwH9-Yl.js → oauth.runtime-DKU2STuA.js} +46 -46
- package/dist/{oauth.runtime-BSddaUzc.js → oauth.runtime-mFizWM72.js} +46 -46
- package/dist/{onboard-CUbnVxXL.js → onboard-B6H0qDtg.js} +1 -1
- package/dist/{onboard-BF8cNljJ.js → onboard-C1AmXQ6f.js} +8 -8
- package/dist/{onboard-Cw65XmEU.js → onboard-DPdj0IBu.js} +2 -2
- package/dist/{onboard-channels-BYNDTtRI.js → onboard-channels-Cmj_5qXQ.js} +24 -24
- package/dist/{onboard-channels-CIR1ZSii.js → onboard-channels-Wf3nY9Bf.js} +97 -97
- package/dist/{onboard-custom-FImARgQC.js → onboard-custom-DILgXkiq.js} +50 -50
- package/dist/{onboard-custom-C4qOVxtG.js → onboard-custom-DlDCjvIw.js} +4 -4
- package/dist/{onboard-helpers-DW9Mskds.js → onboard-helpers-CqpD9TVU.js} +48 -48
- package/dist/{onboard-helpers-D3xV_T96.js → onboard-helpers-D-PLdtHT.js} +3 -3
- package/dist/{onboard-hooks-B0gJVtBY.js → onboard-hooks-MQ8QOguv.js} +2 -2
- package/dist/{onboard-remote-BMLo6WKH.js → onboard-remote-BZKWgKVw.js} +50 -50
- package/dist/{onboard-remote-XUah3N3G.js → onboard-remote-Bnad5LMv.js} +2 -2
- package/dist/{onboard-search-DHCzqxgq.js → onboard-search-Dyv1IuQ4.js} +46 -46
- package/dist/{onboard-skills-LXCIpyir.js → onboard-skills-DdCu1_hE.js} +1 -1
- package/dist/{onboard-skills-CH6q90Pf.js → onboard-skills-W_252asS.js} +49 -49
- package/dist/{onboarding-memory-CTB-Vkbl.js → onboarding-memory-BRHSmwx3.js} +6 -6
- package/dist/{outbound-media-CqfP0r4a.js → outbound-media-CHc08Sj8.js} +1 -1
- package/dist/{pairing-cli-BdCkQG2S.js → pairing-cli-oNWkjwCt.js} +5 -5
- package/dist/{persistent-dedupe-DNnh7hrR.js → persistent-dedupe-CS_uyVoq.js} +1 -1
- package/dist/{pi-model-discovery-runtime-DyT9C3Ap.js → pi-model-discovery-runtime-BJVREOXf.js} +46 -46
- package/dist/{pi-tools.before-tool-call.runtime-CA2js1Ig.js → pi-tools.before-tool-call.runtime-w-Q4HA_S.js} +6 -6
- package/dist/{plugin-install-DgvJfz7X.js → plugin-install-BysMw7Sl.js} +47 -47
- package/dist/{plugin-install-BKuy_--2.js → plugin-install-EyES0vGP.js} +2 -2
- package/dist/{plugin-registry-D-6OBqUU.js → plugin-registry-BenF9SYR.js} +47 -47
- package/dist/{plugin-registry-Dsx_6z3N.js → plugin-registry-CRV_Bc8K.js} +3 -3
- package/dist/plugin-sdk/account-resolution.js +46 -46
- package/dist/plugin-sdk/acp-runtime.js +46 -46
- package/dist/plugin-sdk/agent-runtime.js +46 -46
- package/dist/plugin-sdk/channel-inbound.js +46 -46
- package/dist/plugin-sdk/channel-reply-pipeline.js +3 -3
- package/dist/plugin-sdk/channel-runtime.js +46 -46
- package/dist/plugin-sdk/channel-setup.js +1 -1
- package/dist/plugin-sdk/command-auth.js +46 -46
- package/dist/plugin-sdk/compat.js +5 -5
- package/dist/plugin-sdk/config-runtime.js +46 -46
- package/dist/plugin-sdk/conversation-runtime.js +46 -46
- package/dist/plugin-sdk/core.js +5 -5
- package/dist/plugin-sdk/directory-runtime.js +1 -1
- package/dist/plugin-sdk/discord.js +46 -46
- package/dist/plugin-sdk/gateway-runtime.js +8 -8
- package/dist/plugin-sdk/image-generation.js +46 -46
- package/dist/plugin-sdk/index.js +46 -46
- package/dist/plugin-sdk/infra-runtime.js +46 -46
- package/dist/plugin-sdk/llm-task.js +46 -46
- package/dist/plugin-sdk/matrix-runtime-heavy.js +50 -50
- package/dist/plugin-sdk/media-runtime.js +46 -46
- package/dist/plugin-sdk/media-understanding-runtime.js +46 -46
- package/dist/plugin-sdk/media-understanding.js +46 -46
- package/dist/plugin-sdk/ollama-setup.js +8 -8
- package/dist/plugin-sdk/plugin-runtime.js +46 -46
- package/dist/plugin-sdk/provider-auth-api-key.js +46 -46
- package/dist/plugin-sdk/provider-auth-login.js +1 -1
- package/dist/plugin-sdk/provider-auth.js +46 -46
- package/dist/plugin-sdk/provider-models.js +5 -5
- package/dist/plugin-sdk/provider-onboard.js +5 -5
- package/dist/plugin-sdk/provider-setup.js +49 -49
- package/dist/plugin-sdk/provider-stream.js +46 -46
- package/dist/plugin-sdk/provider-usage.js +4 -4
- package/dist/plugin-sdk/reply-history.js +3 -3
- package/dist/plugin-sdk/reply-runtime.js +46 -46
- package/dist/plugin-sdk/routing.js +3 -3
- package/dist/plugin-sdk/sandbox.js +46 -46
- package/dist/plugin-sdk/self-hosted-provider-setup.js +48 -48
- package/dist/plugin-sdk/setup-runtime.js +1 -1
- package/dist/plugin-sdk/setup.js +1 -1
- package/dist/plugin-sdk/speech-runtime.js +46 -46
- package/dist/plugin-sdk/speech.js +46 -46
- package/dist/plugin-sdk/src/agents/workspace.d.ts +1 -3
- package/dist/plugin-sdk/src/config/types.memory.d.ts +44 -0
- package/dist/plugin-sdk/src/memory/backend-config.d.ts +24 -0
- package/dist/plugin-sdk/src/memory/index.d.ts +12 -4
- package/dist/plugin-sdk/src/memory/mongodb-entity-extractor.d.ts +33 -0
- package/dist/plugin-sdk/src/memory/mongodb-episodes.d.ts +16 -0
- package/dist/plugin-sdk/src/memory/mongodb-events.d.ts +9 -0
- package/dist/plugin-sdk/src/memory/mongodb-graph.d.ts +30 -3
- package/dist/plugin-sdk/src/memory/mongodb-manager.d.ts +18 -0
- package/dist/plugin-sdk/src/memory/mongodb-mutations.d.ts +38 -0
- package/dist/plugin-sdk/src/memory/mongodb-procedures.d.ts +32 -0
- package/dist/plugin-sdk/src/memory/mongodb-profile.d.ts +71 -0
- package/dist/plugin-sdk/src/memory/mongodb-query-cache.d.ts +68 -0
- package/dist/plugin-sdk/src/memory/mongodb-query-rewriter.d.ts +39 -0
- package/dist/plugin-sdk/src/memory/mongodb-reranker.d.ts +32 -0
- package/dist/plugin-sdk/src/memory/mongodb-schema.d.ts +3 -0
- package/dist/plugin-sdk/src/memory/mongodb-telemetry.d.ts +69 -0
- package/dist/plugin-sdk/text-runtime.js +6 -6
- package/dist/plugin-sdk/web-media.js +3 -3
- package/dist/plugin-sdk/zalo.js +47 -47
- package/dist/plugin-sdk/zalouser.js +47 -47
- package/dist/plugins/build-smoke-entry.js +46 -46
- package/dist/plugins/runtime/index.js +46 -46
- package/dist/{plugins-cli-C6wGAkD9.js → plugins-cli-BUiP4x7l.js} +46 -46
- package/dist/{policy-CcgojW9R.js → policy-DnUfJkOZ.js} +1 -1
- package/dist/{preflight-audio.runtime-Cyz9Zu89.js → preflight-audio.runtime-D3_iaSgF.js} +46 -46
- package/dist/{probe-auth-BmluVNJZ.js → probe-auth-BbNdYwb0.js} +1 -1
- package/dist/{probe-auth-DrgTJ7ki.js → probe-auth-CB9y2dLl.js} +7 -7
- package/dist/{program-BjkVOzbF.js → program-uHYlUP3c.js} +4 -4
- package/dist/{prompt-select-styled-6vDD4Gjv.js → prompt-select-styled-DoTAWghe.js} +25 -51
- package/dist/{provider-api-key-auth.runtime-AT16DTa8.js → provider-api-key-auth.runtime-B3H0dODg.js} +46 -46
- package/dist/{provider-auth-choice-UhIkLc6q.js → provider-auth-choice-HRfxXEHk.js} +6 -6
- package/dist/{provider-auth-choice-helpers-CXWmWOdR.js → provider-auth-choice-helpers-2jZnBKMm.js} +1 -1
- package/dist/{provider-auth-choice-preference-TgulgKlz.js → provider-auth-choice-preference-CqKryjBB.js} +6 -6
- package/dist/{provider-auth-choice.runtime-FTNgi8Yh.js → provider-auth-choice.runtime-BaFSZgQG.js} +48 -48
- package/dist/{provider-auth-choice.runtime-CP91b3vK.js → provider-auth-choice.runtime-DPHuj1_l.js} +2 -2
- package/dist/{provider-auth-choices-BfWvatIg.js → provider-auth-choices-CqFh00uK.js} +1 -1
- package/dist/{provider-auth-guidance-D4-q7IHl.js → provider-auth-guidance-DmdBoDfS.js} +2 -2
- package/dist/{provider-auth-input-DnQ-RiX5.js → provider-auth-input-FZYEJh19.js} +46 -46
- package/dist/{provider-auth-login-D7eLUZwy.js → provider-auth-login-B-XedaK5.js} +1 -1
- package/dist/{provider-auth-login.runtime-BG_qqxZk.js → provider-auth-login.runtime-f9dxfJUB.js} +49 -49
- package/dist/{provider-model-allowlist-CC0lIQ6w.js → provider-model-allowlist-C16pn0B8.js} +1 -1
- package/dist/{provider-models-DbK6xrje.js → provider-models-PaymKOme.js} +1 -1
- package/dist/{provider-ollama-setup-B4RWr9oj.js → provider-ollama-setup-k5ozCUi2.js} +3 -3
- package/dist/{provider-onboarding-config-W5sF0oD8.js → provider-onboarding-config-qCltsaVl.js} +1 -1
- package/dist/{provider-runtime.runtime-Cva8S4wx.js → provider-runtime.runtime-CzVe0WBb.js} +46 -46
- package/dist/{provider-self-hosted-setup-jAzqLaqv.js → provider-self-hosted-setup-zxYnIftX.js} +3 -3
- package/dist/{provider-usage-CEgSCMW4.js → provider-usage-C9KdAhuS.js} +46 -46
- package/dist/{provider-usage-CCIC3SdY.js → provider-usage-DexJNI_0.js} +1 -1
- package/dist/{provider-wizard-DQd5eYhX.js → provider-wizard-D4RbMR2D.js} +2 -2
- package/dist/{push-apns-DQN5pcl6.js → push-apns-BAc0-Jy5.js} +1 -1
- package/dist/{pw-ai-BiXz8un2.js → pw-ai-C8v0s5wH.js} +6 -6
- package/dist/{qr-cli-B8bBpCd2.js → qr-cli-BpqLcmYC.js} +47 -47
- package/dist/{qr-cli-DOuhqzho.js → qr-cli-D0zqiQMl.js} +4 -4
- package/dist/{reactions-BkyHqIw4.js → reactions-CjJbniYK.js} +1 -1
- package/dist/{read-only-account-inspect-Cfaj7PFb.js → read-only-account-inspect-CEJ1r7AW.js} +3 -3
- package/dist/{read-only-account-inspect.discord.runtime-kreMwVgq.js → read-only-account-inspect.discord.runtime-C1n8r_Kj.js} +46 -46
- package/dist/{read-only-account-inspect.slack.runtime-DgV5VZ-n.js → read-only-account-inspect.slack.runtime-DAOzA9ke.js} +46 -46
- package/dist/{read-only-account-inspect.telegram.runtime-BQaHHCIS.js → read-only-account-inspect.telegram.runtime-CfNlnogC.js} +46 -46
- package/dist/{redact-snapshot-PisBVQFW.js → redact-snapshot-qQFn3bz8.js} +3 -3
- package/dist/{register.agent-CXTssAWM.js → register.agent-DC5QbIE7.js} +114 -114
- package/dist/{register.backup-MRTb86c6.js → register.backup-BRxspFkM.js} +6 -6
- package/dist/{register.configure-BfE3p-3h.js → register.configure-B7zhRBla.js} +120 -120
- package/dist/{register.maintenance-CjOZ-w_0.js → register.maintenance-CCmppLW1.js} +65 -65
- package/dist/{register.message-s1z2QSP2.js → register.message-DSBPJS4x.js} +47 -47
- package/dist/{register.onboard-GSGJTcJk.js → register.onboard-C4D9pjf7.js} +54 -54
- package/dist/{register.setup-BD774nEM.js → register.setup-C4pDa0HW.js} +52 -52
- package/dist/{register.status-health-sessions-D-Xp8ae7.js → register.status-health-sessions-ClxLTjC5.js} +55 -55
- package/dist/{register.subclis-BlqPR8UO.js → register.subclis-BauDruMA.js} +30 -30
- package/dist/{register.subclis-CeG9qCbt.js → register.subclis-WyYRaoa-.js} +1 -1
- package/dist/{replies-DQwDaTBP.js → replies-CZGcXYVE.js} +1 -1
- package/dist/{reply-history-DVLrt4Es.js → reply-history-1yT9Tzib.js} +1 -1
- package/dist/{routes-CBWjQBhD.js → routes-D8CYDLDF.js} +5 -5
- package/dist/{rpc-O719GSVf.js → rpc-D8I_Qzen.js} +1 -1
- package/dist/{run-main-C4Box4Zo.js → run-main-BwEbDjI-.js} +19 -19
- package/dist/{runtime-COpSaRtb.js → runtime-CmSMHqwN.js} +1 -1
- package/dist/{runtime-PbP8BVEV.js → runtime-DQaeo-1L.js} +2 -2
- package/dist/{runtime-discord-ops.runtime-CmStqYK4.js → runtime-discord-ops.runtime-8w-055sR.js} +46 -46
- package/dist/{runtime-slack-ops.runtime-Cm1935hR.js → runtime-slack-ops.runtime-XJJrByCY.js} +48 -48
- package/dist/{runtime-telegram-ops.runtime-CKozoVxf.js → runtime-telegram-ops.runtime-wOdrd2eH.js} +46 -46
- package/dist/{sandbox-cli-BIyJ-1r-.js → sandbox-cli-CvFmY6Kq.js} +46 -46
- package/dist/{search-manager-BVE1EPHv.js → search-manager-ZNiamJHa.js} +6 -6
- package/dist/{search-manager-DMTYqpmg.js → search-manager-zaZ4tAYz.js} +5 -5
- package/dist/{secrets-cli-CMMtWzst.js → secrets-cli-D5a_lvAP.js} +47 -47
- package/dist/{security-cli-Bw4cVkTN.js → security-cli-DJovxmST.js} +47 -47
- package/dist/{send-CfI8y11o.js → send-C3KhjjcU.js} +1 -1
- package/dist/{send-C8HDmafP.js → send-CIkS5fLj.js} +2 -2
- package/dist/{server-0n2tu0ff.js → server-B1z10Ohi.js} +6 -6
- package/dist/{server-node-events-J5-mGOea.js → server-node-events-D3_b2nU-.js} +47 -47
- package/dist/{server-startup-matrix-migration-CMBUtdN2.js → server-startup-matrix-migration-DDRaKUPQ.js} +3 -3
- package/dist/{session-cost-usage-CrPCO4HN.js → session-cost-usage-BwIud0lY.js} +46 -46
- package/dist/{sessions-n_Z6bcbr.js → sessions-B5Pffwj6.js} +3 -3
- package/dist/{sessions-BK_M3Nu1.js → sessions-ClamZkvP.js} +47 -47
- package/dist/{setup-CjQJLNqB.js → setup-B8gsbmH4.js} +18 -18
- package/dist/{setup-core-BVBCTInv.js → setup-core-B6csKf5s.js} +2 -2
- package/dist/{setup-core-z8Q1sgUU.js → setup-core-DkgiVKzw.js} +2 -2
- package/dist/{setup-entry-CvuaW-4I.js → setup-entry-C8XH3da9.js} +2 -2
- package/dist/{setup-entry-Jf6V23s6.js → setup-entry-CMSlnfft.js} +3 -3
- package/dist/{setup-entry-Bo3chgoG.js → setup-entry-CZVAwPKZ.js} +2 -2
- package/dist/{setup-entry-Co-YP41_.js → setup-entry-Duh-ewBV.js} +3 -3
- package/dist/{setup-entry-BnSZlMP1.js → setup-entry-ksUWY45r.js} +2 -2
- package/dist/{setup-entry-BNSYJMzp.js → setup-entry-vQgAuveW.js} +2 -2
- package/dist/{setup-group-access-BYV3O8yu.js → setup-group-access-CTlnq-M9.js} +1 -1
- package/dist/{setup-surface-B6XBqyMJ.js → setup-surface-B7uYEI9F.js} +2 -2
- package/dist/{setup-surface-fixjWejm.js → setup-surface-CMgQr2R9.js} +5 -5
- package/dist/{setup-surface-CzzV3QV6.js → setup-surface-Yd-YLGfa.js} +46 -46
- package/dist/{setup-wizard-proxy-DFDQV7FN.js → setup-wizard-proxy-CHGAY6ob.js} +1 -1
- package/dist/{setup.finalize-ChczeW6V.js → setup.finalize-C2bq4D8j.js} +58 -58
- package/dist/{setup.gateway-config-Bp50kjo3.js → setup.gateway-config-BGgVOvQf.js} +48 -48
- package/dist/{shared-CyR3Ki--.js → shared-BEMZg0vb.js} +5 -5
- package/dist/{shared-DXzPv5D7.js → shared-BYRyVoVx.js} +5 -5
- package/dist/{shared-DCOVJ3QB.js → shared-Bj3fwdyM.js} +4 -4
- package/dist/{shared-B8_JHpU2.js → shared-CSydqOgE.js} +4 -4
- package/dist/{shared-C3fKAUbw.js → shared-DLAg-pKT.js} +3 -3
- package/dist/{signal-D16eMrBf.js → signal-Dh6z2uaT.js} +5 -5
- package/dist/{signal-oGrspg1k.js → signal-FEtxekyc.js} +46 -46
- package/dist/{skills-cli-CY6lUTRv.js → skills-cli-9NuRJd3Z.js} +5 -5
- package/dist/{slack-ueYeeNF7.js → slack-Ci1VArkq.js} +48 -48
- package/dist/{slack-B1GgT1_u.js → slack-CjslLjTc.js} +5 -5
- package/dist/{slash-commands.runtime-1q-llUpe.js → slash-commands.runtime-COXzcGjL.js} +46 -46
- package/dist/{slash-dispatch.runtime-BqJ1FZZ6.js → slash-dispatch.runtime-D5j8d7dg.js} +47 -47
- package/dist/{slash-skill-commands.runtime-D-BEZgQ1.js → slash-skill-commands.runtime-6U65Nany.js} +46 -46
- package/dist/{status-CnKcjD2h.js → status-BWUft8B5.js} +16 -16
- package/dist/{status-C1BR9Hft.js → status-BjV1ysoW.js} +50 -50
- package/dist/{status-D_1nxvoi.js → status-CM77XYRR.js} +4 -4
- package/dist/{status-Dp3OriYl.js → status-D7XHAzLz.js} +46 -46
- package/dist/{status-DoSH4nEL.js → status-DwO4xEHI.js} +54 -54
- package/dist/{status-json-DwNPzoCk.js → status-json-BYDBfkr3.js} +19 -19
- package/dist/{status-B45v-A8b.js → status-yoA_KX2O.js} +1 -1
- package/dist/{status.link-channel-Cz8GoHZ2.js → status.link-channel-DkISMfW8.js} +2 -2
- package/dist/{status.scan.deps.runtime-BQGei2B_.js → status.scan.deps.runtime-B6__B0kL.js} +14 -14
- package/dist/{status.scan.runtime-D4HkKTZu.js → status.scan.runtime-BAkcX3vO.js} +2 -2
- package/dist/{status.summary-Dxo5qDuD.js → status.summary-DNaMhaXy.js} +8 -8
- package/dist/{subagent-orphan-recovery-BPa7yoId.js → subagent-orphan-recovery-DtK8inah.js} +46 -46
- package/dist/{subagent-registry-runtime-B6homy_H.js → subagent-registry-runtime-02s7QJo3.js} +46 -46
- package/dist/{synology-chat-DY8xNv_g.js → synology-chat-QURTpvMf.js} +2 -2
- package/dist/{system-cli-CkWRbLu4.js → system-cli-BXd7evEt.js} +7 -7
- package/dist/{system-run-command-jY6Is_ri.js → system-run-command-CQCx0f24.js} +1 -1
- package/dist/telegram/audit.js +1 -1
- package/dist/telegram/token.js +46 -46
- package/dist/{telegram-uQdpyLeS.js → telegram-Btwvg0PX.js} +46 -46
- package/dist/{telegram-Bnigf1Qe.js → telegram-Dh5gM1mc.js} +7 -7
- package/dist/{tool-policy-match-BLASiL8y.js → tool-policy-match-B6J0bSfQ.js} +1 -1
- package/dist/{tui-Dj52JiFV.js → tui-CKGcNyM8.js} +6 -6
- package/dist/{tui-cli-B4SN4wzX.js → tui-cli-D3CLnnB9.js} +47 -47
- package/dist/{update-cli-mBB0aHxZ.js → update-cli-DQ4j_nIh.js} +69 -69
- package/dist/{update-offset-store-Bp1RjZK2.js → update-offset-store-RxVdgbyo.js} +46 -46
- package/dist/{upsert-with-lock-CFIy4b7I.js → upsert-with-lock-BqkAnFym.js} +1 -1
- package/dist/{web-media-BJ-azd7j.js → web-media-BM1Mg6AN.js} +1 -1
- package/dist/{webhook-shared-DYKTXPVw.js → webhook-shared-UDKLpQnU.js} +1 -1
- package/dist/{webhooks-cli-CEBJbLqS.js → webhooks-cli-Ca3PlK8h.js} +5 -5
- package/dist/{whatsapp-63jqHVrE.js → whatsapp-CHEvDa2g.js} +46 -46
- package/dist/{with-timeout-CvaBvNie.js → with-timeout-CvBH67nj.js} +2 -2
- package/dist/{workspace-DuH1agxz.js → workspace-uCYzunu4.js} +5 -33
- package/dist/{zalo-CTrI2C4i.js → zalo-Dr4ysJLW.js} +1 -1
- package/dist/{zalo-Sw_XHo3-.js → zalo-keqX1Wnx.js} +2 -2
- package/docs/concepts/agent-workspace.md +0 -5
- package/docs/concepts/memory.md +6 -12
- package/docs/plans/2026-03-22-semantic-cache-telemetry-docs-plan.md +1431 -0
- package/docs/plans/2026-03-22-supermemory-steals-plan.md +1679 -0
- package/docs/plans/2026-03-23-almost-perfect-sprint-plan.md +1080 -0
- package/docs/plans/2026-03-23-master-steal-list.md +224 -0
- package/docs/plans/2026-03-23-memory-md-deprecation-plan.md +632 -0
- package/docs/plans/2026-03-23-production-readiness-e2e-plan.md +659 -0
- package/docs/plans/2026-03-23-supermemory-audit-fixes-plan.md +1291 -0
- package/docs/reference/clawmongo-vs-default-memory.md +2 -2
- package/docs/reference/heart-brain-boundary.md +8 -10
- package/docs/reference/memory-config.md +3 -4
- package/docs/reference/mongodb-capabilities.md +246 -9
- package/docs/reference/templates/AGENTS.md +5 -10
- package/docs/research/2026-03-22-atlas-local-preview-web.md +41 -31
- package/docs/research/2026-03-23-agent-management-ui-github.md +482 -0
- package/docs/research/2026-03-23-almost-perfect-mongodb-docs.md +255 -0
- package/docs/research/2026-03-23-company-os-agent-ui-web.md +511 -0
- package/docs/research/2026-03-23-supermemory-audit-fixes-mongodb-research.md +995 -0
- package/docs/start/clawmongo-getting-started.md +1 -1
- package/package.json +1 -1
- /package/dist/{memory-BAVM2Jxh.js → memory-CcbELE82.js} +0 -0
|
@@ -0,0 +1,1679 @@
|
|
|
1
|
+
# Supermemory-Inspired Features: Profile Synthesis, Cross-Encoder Re-ranking, Query Rewriting, LLM Entity Extraction
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED: Follow this plan task-by-task using TDD.
|
|
4
|
+
> **Research:** 4 sub-agents (Supermemory deep-dive, ClawMongo deep-dive, architecture-fit analysis, MongoDB best-practices research).
|
|
5
|
+
|
|
6
|
+
**Goal:** Add 4 high-value features inspired by Supermemory research into ClawMongo's memory system. All validated as native fits with zero conflicts, zero new collections, zero breaking changes.
|
|
7
|
+
|
|
8
|
+
**Architecture:** All features integrate at different stages of the existing pipeline — profile is read-only aggregation, reranking is post-search, query rewriting is pre-search, entity extraction is write-side. No feature touches another's stage.
|
|
9
|
+
|
|
10
|
+
**Tech Stack:** MongoDB Community + mongot, TypeScript ESM, Vitest, Voyage AI (autoEmbed + rerank-2.5 API), fire-and-forget telemetry.
|
|
11
|
+
|
|
12
|
+
**Prerequisites:** All v2 base + enhancements + consolidation + cache + telemetry COMPLETE. 22 collections, 58 standard indexes, 9 search indexes. Published @romiluz/clawmongo@2026.3.22.
|
|
13
|
+
|
|
14
|
+
**Plan Mode:** execution_plan
|
|
15
|
+
**Verification Rigor:** standard
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Relevant Codebase Files
|
|
20
|
+
|
|
21
|
+
### Files to Modify
|
|
22
|
+
|
|
23
|
+
- `src/memory/mongodb-manager.ts` — searchV2() hook points for rewriting + reranking, synthesizeProfile() delegation
|
|
24
|
+
- `src/memory/mongodb-graph.ts` — extractAndUpsertEntities() gets optional extractor parameter
|
|
25
|
+
- `src/memory/mongodb-telemetry.ts` — add new TelemetryOperation values
|
|
26
|
+
- `src/config/types.memory.ts` — add queryRewriting, reranking, graph.entityExtraction config sections
|
|
27
|
+
- `src/memory/backend-config.ts` — resolve new config sections with defaults
|
|
28
|
+
- `src/memory/index.ts` — barrel exports for new modules
|
|
29
|
+
|
|
30
|
+
### New Files to Create
|
|
31
|
+
|
|
32
|
+
- `src/memory/mongodb-profile.ts` — profile synthesis
|
|
33
|
+
- `src/memory/mongodb-profile.test.ts` — tests
|
|
34
|
+
- `src/memory/mongodb-reranker.ts` — cross-encoder reranking
|
|
35
|
+
- `src/memory/mongodb-reranker.test.ts` — tests
|
|
36
|
+
- `src/memory/mongodb-query-rewriter.ts` — query expansion
|
|
37
|
+
- `src/memory/mongodb-query-rewriter.test.ts` — tests
|
|
38
|
+
- `src/memory/mongodb-entity-extractor.ts` — pluggable extraction interface
|
|
39
|
+
- `src/memory/mongodb-entity-extractor.test.ts` — tests
|
|
40
|
+
|
|
41
|
+
### Patterns to Follow
|
|
42
|
+
|
|
43
|
+
- `src/memory/mongodb-ops.ts` — standalone function pattern (db, prefix, ...)
|
|
44
|
+
- `src/memory/mongodb-query-cache.ts` — fire-and-forget writes, telemetry emission
|
|
45
|
+
- `src/memory/mongodb-structured-memory.ts` — scope-aware queries, revision pattern
|
|
46
|
+
- `src/memory/mongodb-graph.ts` — entity CRUD, $graphLookup, extractAndUpsertEntities
|
|
47
|
+
- `src/memory/mongodb-search.ts` — buildVectorSearchStage, fusion methods
|
|
48
|
+
- `src/memory/mongodb-hybrid.ts` — score normalization, RRF merge
|
|
49
|
+
- `src/memory/mongodb-retrieval-planner.ts` — pure-function retrieval path planner
|
|
50
|
+
- `src/memory/mongodb-telemetry.ts` — emitTelemetry fire-and-forget pattern
|
|
51
|
+
|
|
52
|
+
### Validated MongoDB Syntax (from research)
|
|
53
|
+
|
|
54
|
+
**$facet for multi-collection profile synthesis:**
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// $facet sub-pipelines run sequentially (NOT parallel)
|
|
58
|
+
// Pre-filter aggressively with $match BEFORE $facet to stay under 100MB RAM limit
|
|
59
|
+
// Use correlated $lookup with pipeline + $limit for bounded joins
|
|
60
|
+
const pipeline = [
|
|
61
|
+
{ $match: { agentId, scope, scopeRef, state: "active" } },
|
|
62
|
+
{
|
|
63
|
+
$facet: {
|
|
64
|
+
preferences: [{ $match: { type: "preference" } }, { $limit: 20 }],
|
|
65
|
+
decisions: [{ $match: { type: "decision" } }, { $limit: 20 }],
|
|
66
|
+
facts: [{ $match: { type: "fact" } }, { $limit: 20 }],
|
|
67
|
+
todos: [{ $match: { type: "todo", state: "active" } }, { $limit: 10 }],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Voyage rerank-2.5 API call:**
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const response = await fetch("https://api.voyageai.com/v1/rerank", {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: {
|
|
79
|
+
"Content-Type": "application/json",
|
|
80
|
+
Authorization: `Bearer ${voyageApiKey}`,
|
|
81
|
+
},
|
|
82
|
+
body: JSON.stringify({
|
|
83
|
+
model: "rerank-2.5",
|
|
84
|
+
query: queryText,
|
|
85
|
+
documents: candidateTexts,
|
|
86
|
+
top_k: topK,
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
// Response: { object: "list", data: [{ index: number, relevance_score: number }], model: "..." }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**EntityExtractor interface pattern:**
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
export interface EntityExtractor {
|
|
96
|
+
extract(content: string, context?: EntityExtractionContext): Promise<ExtractedEntity[]>;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Open Decisions
|
|
103
|
+
|
|
104
|
+
None — all decisions pre-answered by research.
|
|
105
|
+
|
|
106
|
+
## Differences from Agreement
|
|
107
|
+
|
|
108
|
+
- Previous decision (activeContext.md ## Decisions) rejected cross-encoder reranking and LLM entity extraction. User explicitly REVERSED this after Supermemory deep-dive research.
|
|
109
|
+
- Previous deferred item "reranked scores not exposed in search results metadata" — this plan addresses it via the reranker.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Phase 1: Config Foundation + Telemetry Operations
|
|
114
|
+
|
|
115
|
+
> **Exit Criteria:** New config sections for queryRewriting, reranking, graph.entityExtraction added to types and resolved with defaults. New TelemetryOperation values added. 15+ tests pass.
|
|
116
|
+
|
|
117
|
+
### Task 1.1: Add queryRewriting config to MemoryMongoDBConfig
|
|
118
|
+
|
|
119
|
+
**Files:**
|
|
120
|
+
|
|
121
|
+
- Modify: `src/config/types.memory.ts` (inside MemoryMongoDBConfig type, after `cache` section)
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
/** Query rewriting configuration */
|
|
125
|
+
queryRewriting?: {
|
|
126
|
+
/** Enable query rewriting before search. Default: false */
|
|
127
|
+
enabled?: boolean;
|
|
128
|
+
/** Rewriting strategy. Default: "synonym-expansion" */
|
|
129
|
+
method?: "synonym-expansion" | "llm" | "hyde";
|
|
130
|
+
/** Maximum rewritten query length in tokens. Default: 128 */
|
|
131
|
+
maxTokens?: number;
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Task 1.2: Add reranking config to MemoryMongoDBConfig
|
|
136
|
+
|
|
137
|
+
**Files:**
|
|
138
|
+
|
|
139
|
+
- Modify: `src/config/types.memory.ts` (after queryRewriting section)
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
/** Cross-encoder re-ranking configuration */
|
|
143
|
+
reranking?: {
|
|
144
|
+
/** Enable cross-encoder re-ranking. Default: false */
|
|
145
|
+
enabled?: boolean;
|
|
146
|
+
/** Re-ranking model. Default: "rerank-2.5" */
|
|
147
|
+
model?: "rerank-2.5" | "rerank-2.5-lite";
|
|
148
|
+
/** Maximum documents to send to reranker. Default: 20 */
|
|
149
|
+
topN?: number;
|
|
150
|
+
/** Minimum retrieval score to be eligible for re-ranking. Default: 0.1 */
|
|
151
|
+
minScore?: number;
|
|
152
|
+
/** Voyage API key. Env fallback: VOYAGE_API_KEY */
|
|
153
|
+
voyageApiKey?: string;
|
|
154
|
+
/** Optional instruction prepended to query for rerank-2.5 instruction-following (8-11% accuracy boost). */
|
|
155
|
+
instruction?: string;
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Task 1.3: Add entityExtraction config to graph section
|
|
160
|
+
|
|
161
|
+
**Files:**
|
|
162
|
+
|
|
163
|
+
- Modify: `src/config/types.memory.ts` (expand existing `graph` section)
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
/** Graph projection config */
|
|
167
|
+
graph?: {
|
|
168
|
+
/** Enable graph projection. Default: true */
|
|
169
|
+
enabled?: boolean;
|
|
170
|
+
/** Max depth for $graphLookup. Default: 2 */
|
|
171
|
+
maxGraphDepth?: number;
|
|
172
|
+
/** Entity extraction configuration */
|
|
173
|
+
entityExtraction?: {
|
|
174
|
+
/** Extraction strategy. Default: "regex" */
|
|
175
|
+
method?: "regex" | "llm";
|
|
176
|
+
/** LLM model for extraction (when method="llm"). Uses agent default if omitted */
|
|
177
|
+
model?: string;
|
|
178
|
+
/** Timeout for LLM extraction in ms. Default: 5000 */
|
|
179
|
+
timeoutMs?: number;
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Task 1.4: Add resolved config sections to ResolvedMongoDBConfig
|
|
185
|
+
|
|
186
|
+
**Files:**
|
|
187
|
+
|
|
188
|
+
- Modify: `src/memory/backend-config.ts` (add to ResolvedMongoDBConfig type)
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
queryRewriting: {
|
|
192
|
+
enabled: boolean;
|
|
193
|
+
method: "synonym-expansion" | "llm" | "hyde";
|
|
194
|
+
maxTokens: number;
|
|
195
|
+
};
|
|
196
|
+
reranking: {
|
|
197
|
+
enabled: boolean;
|
|
198
|
+
model: "rerank-2.5" | "rerank-2.5-lite";
|
|
199
|
+
topN: number;
|
|
200
|
+
minScore: number;
|
|
201
|
+
voyageApiKey: string;
|
|
202
|
+
};
|
|
203
|
+
// Expand existing graph section:
|
|
204
|
+
graph: {
|
|
205
|
+
enabled: boolean;
|
|
206
|
+
maxGraphDepth: number;
|
|
207
|
+
entityExtraction: {
|
|
208
|
+
method: "regex" | "llm";
|
|
209
|
+
model?: string;
|
|
210
|
+
timeoutMs: number;
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Task 1.5: Resolve new config sections with defaults
|
|
216
|
+
|
|
217
|
+
**Files:**
|
|
218
|
+
|
|
219
|
+
- Modify: `src/memory/backend-config.ts` (in resolveMemoryBackendConfig, after `cache` resolution)
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
queryRewriting: {
|
|
223
|
+
enabled: mongoCfg?.queryRewriting?.enabled === true, // disabled by default
|
|
224
|
+
method: mongoCfg?.queryRewriting?.method ?? "synonym-expansion",
|
|
225
|
+
maxTokens: mongoCfg?.queryRewriting?.maxTokens ?? 128,
|
|
226
|
+
},
|
|
227
|
+
reranking: {
|
|
228
|
+
enabled: mongoCfg?.reranking?.enabled === true, // disabled by default
|
|
229
|
+
model: mongoCfg?.reranking?.model ?? "rerank-2.5",
|
|
230
|
+
topN: mongoCfg?.reranking?.topN ?? 20,
|
|
231
|
+
minScore: mongoCfg?.reranking?.minScore ?? 0.1,
|
|
232
|
+
voyageApiKey: mongoCfg?.reranking?.voyageApiKey ?? process.env.VOYAGE_API_KEY ?? "",
|
|
233
|
+
},
|
|
234
|
+
// Expand existing graph resolution:
|
|
235
|
+
graph: {
|
|
236
|
+
enabled: mongoCfg?.graph?.enabled !== false,
|
|
237
|
+
maxGraphDepth: mongoCfg?.graph?.maxGraphDepth ?? 2,
|
|
238
|
+
entityExtraction: {
|
|
239
|
+
method: mongoCfg?.graph?.entityExtraction?.method ?? "regex",
|
|
240
|
+
model: mongoCfg?.graph?.entityExtraction?.model,
|
|
241
|
+
timeoutMs: mongoCfg?.graph?.entityExtraction?.timeoutMs ?? 5000,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Note:** `queryRewriting` and `reranking` use `=== true` (disabled by default), unlike `cache`/`graph`/`episodes` which use `!== false` (enabled by default). This is intentional — these features add latency and cost.
|
|
247
|
+
|
|
248
|
+
### Task 1.6: Add new TelemetryOperation values
|
|
249
|
+
|
|
250
|
+
**Files:**
|
|
251
|
+
|
|
252
|
+
- Modify: `src/memory/mongodb-telemetry.ts` (expand TelemetryOperation union)
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
export type TelemetryOperation =
|
|
256
|
+
| "search"
|
|
257
|
+
| "event-write"
|
|
258
|
+
| "projection-run"
|
|
259
|
+
| "cache-check"
|
|
260
|
+
| "graph-expansion"
|
|
261
|
+
| "profile-synthesis"
|
|
262
|
+
| "rerank"
|
|
263
|
+
| "query-rewrite"
|
|
264
|
+
| "entity-extraction";
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Add optional fields to TelemetryDocument:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
export type TelemetryDocument = {
|
|
271
|
+
// ... existing fields ...
|
|
272
|
+
rerankModel?: string;
|
|
273
|
+
rerankLatencyMs?: number;
|
|
274
|
+
queryRewritten?: boolean;
|
|
275
|
+
rewriteMethod?: string;
|
|
276
|
+
extractionMethod?: string;
|
|
277
|
+
entitiesExtracted?: number;
|
|
278
|
+
};
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Task 1.7: Write config tests
|
|
282
|
+
|
|
283
|
+
**Files:**
|
|
284
|
+
|
|
285
|
+
- Modify: `src/memory/backend-config.test.ts`
|
|
286
|
+
|
|
287
|
+
**Tests:**
|
|
288
|
+
|
|
289
|
+
1. `resolves queryRewriting defaults (disabled, synonym-expansion, 128)`
|
|
290
|
+
2. `resolves queryRewriting with explicit values`
|
|
291
|
+
3. `resolves reranking defaults (disabled, rerank-2.5, topN=20, minScore=0.1)`
|
|
292
|
+
4. `resolves reranking with explicit values`
|
|
293
|
+
5. `resolves reranking.voyageApiKey from env fallback`
|
|
294
|
+
6. `resolves graph.entityExtraction defaults (regex, timeoutMs=5000)`
|
|
295
|
+
7. `resolves graph.entityExtraction with llm method`
|
|
296
|
+
8. `preserves existing graph.enabled and maxGraphDepth behavior`
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
pnpm test -- src/memory/backend-config.test.ts
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Commit after Phase 1:**
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
Feat: add config sections for query rewriting, cross-encoder reranking, and LLM entity extraction
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Phase 2: Profile Synthesis
|
|
311
|
+
|
|
312
|
+
> **Exit Criteria:** `mongodb-profile.ts` module with synthesizeProfile() function. Reads structured_mem, entities, relations, episodes, events. Returns ProfileSynthesis object. Telemetry emitted. 18+ tests pass.
|
|
313
|
+
|
|
314
|
+
### Task 2.1: Create mongodb-profile.ts with types
|
|
315
|
+
|
|
316
|
+
**Files:**
|
|
317
|
+
|
|
318
|
+
- Create: `src/memory/mongodb-profile.ts`
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import type { Db, Document } from "mongodb";
|
|
322
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
323
|
+
import type { MemoryScope } from "../config/types.memory.js";
|
|
324
|
+
import {
|
|
325
|
+
structuredMemCollection,
|
|
326
|
+
entitiesCollection,
|
|
327
|
+
relationsCollection,
|
|
328
|
+
episodesCollection,
|
|
329
|
+
eventsCollection,
|
|
330
|
+
} from "./mongodb-schema.js";
|
|
331
|
+
import { emitTelemetry } from "./mongodb-telemetry.js";
|
|
332
|
+
|
|
333
|
+
const log = createSubsystemLogger("memory:mongodb:profile");
|
|
334
|
+
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
// Types
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
export type ProfileSynthesis = {
|
|
340
|
+
agentId: string;
|
|
341
|
+
scope: MemoryScope;
|
|
342
|
+
scopeRef: string;
|
|
343
|
+
/** Structured memory grouped by type */
|
|
344
|
+
preferences: ProfileMemoryItem[];
|
|
345
|
+
decisions: ProfileMemoryItem[];
|
|
346
|
+
facts: ProfileMemoryItem[];
|
|
347
|
+
todos: ProfileMemoryItem[];
|
|
348
|
+
/** Top entities by relation count */
|
|
349
|
+
topEntities: ProfileEntity[];
|
|
350
|
+
/** Most recent episode summaries */
|
|
351
|
+
recentEpisodes: ProfileEpisode[];
|
|
352
|
+
/** Activity patterns derived from events */
|
|
353
|
+
activityPatterns: ActivityPatterns;
|
|
354
|
+
/** Synthesis timestamp */
|
|
355
|
+
synthesizedAt: Date;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
export type ProfileMemoryItem = {
|
|
359
|
+
key: string;
|
|
360
|
+
value: string;
|
|
361
|
+
salience: string;
|
|
362
|
+
updatedAt: Date;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
export type ProfileEntity = {
|
|
366
|
+
name: string;
|
|
367
|
+
type: string;
|
|
368
|
+
relationCount: number;
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
export type ProfileEpisode = {
|
|
372
|
+
title: string;
|
|
373
|
+
summary: string;
|
|
374
|
+
type: string;
|
|
375
|
+
timeRange: { start: Date; end: Date };
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
export type ActivityPatterns = {
|
|
379
|
+
/** Distribution of events by role (user, assistant, system, tool) */
|
|
380
|
+
roleDistribution: Record<string, number>;
|
|
381
|
+
/** Total event count in the analysis window */
|
|
382
|
+
totalEvents: number;
|
|
383
|
+
/** Most recent event timestamp */
|
|
384
|
+
lastActive: Date | null;
|
|
385
|
+
};
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Task 2.2: Implement synthesizeProfile()
|
|
389
|
+
|
|
390
|
+
**Files:**
|
|
391
|
+
|
|
392
|
+
- Modify: `src/memory/mongodb-profile.ts`
|
|
393
|
+
|
|
394
|
+
**Implementation approach:**
|
|
395
|
+
|
|
396
|
+
1. Query structured_mem with $facet for 4 types (preferences, decisions, facts, todos) — pre-filtered by {agentId, scope, scopeRef, state: "active"}, each sub-pipeline limited to 20 items, sorted by salience priority then updatedAt desc
|
|
397
|
+
2. Query entities with $lookup on relations to get relation count — find top 10 entities by relation count for this agent/scope
|
|
398
|
+
3. Query episodes — find most recent 10 episodes sorted by timeRange.start desc
|
|
399
|
+
4. Query events with $group — aggregate roleDistribution and totalEvents for activity patterns (last 30 days window)
|
|
400
|
+
5. Assemble ProfileSynthesis object
|
|
401
|
+
6. Emit telemetry (profile-synthesis operation)
|
|
402
|
+
|
|
403
|
+
**Salience sort order:** critical > high > normal > low (use a priority map: { critical: 0, high: 1, normal: 2, low: 3 })
|
|
404
|
+
|
|
405
|
+
**Key patterns:**
|
|
406
|
+
|
|
407
|
+
- All queries filter by `{ agentId, scope, scopeRef }`
|
|
408
|
+
- Use existing collection accessors from mongodb-schema.ts
|
|
409
|
+
- $facet on structured_mem for the 4 types (most efficient — single pass)
|
|
410
|
+
- $lookup + $group on entities → relations for relation count
|
|
411
|
+
- Simple find + sort + limit for episodes
|
|
412
|
+
- $group on events for activity patterns
|
|
413
|
+
- Pre-filter aggressively before $facet to stay under 100MB RAM limit
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
export async function synthesizeProfile(params: {
|
|
417
|
+
db: Db;
|
|
418
|
+
prefix: string;
|
|
419
|
+
agentId: string;
|
|
420
|
+
scope: MemoryScope;
|
|
421
|
+
scopeRef: string;
|
|
422
|
+
/** Max items per structured memory type. Default: 20 */
|
|
423
|
+
maxPerType?: number;
|
|
424
|
+
/** Max entities to return. Default: 10 */
|
|
425
|
+
maxEntities?: number;
|
|
426
|
+
/** Max episodes to return. Default: 10 */
|
|
427
|
+
maxEpisodes?: number;
|
|
428
|
+
/** Activity window in ms. Default: 30 days */
|
|
429
|
+
activityWindowMs?: number;
|
|
430
|
+
}): Promise<ProfileSynthesis> {
|
|
431
|
+
const profileStart = Date.now();
|
|
432
|
+
const {
|
|
433
|
+
db,
|
|
434
|
+
prefix,
|
|
435
|
+
agentId,
|
|
436
|
+
scope,
|
|
437
|
+
scopeRef,
|
|
438
|
+
maxPerType = 20,
|
|
439
|
+
maxEntities = 10,
|
|
440
|
+
maxEpisodes = 10,
|
|
441
|
+
activityWindowMs = 30 * 24 * 60 * 60 * 1000,
|
|
442
|
+
} = params;
|
|
443
|
+
|
|
444
|
+
const scopeFilter = { agentId, scope, scopeRef };
|
|
445
|
+
|
|
446
|
+
// 1. Structured memory via $facet (single pass over collection)
|
|
447
|
+
const structuredResults = await structuredMemCollection(db, prefix)
|
|
448
|
+
.aggregate([
|
|
449
|
+
{ $match: { ...scopeFilter, state: "active" } },
|
|
450
|
+
{
|
|
451
|
+
$facet: {
|
|
452
|
+
preferences: [
|
|
453
|
+
{ $match: { type: "preference" } },
|
|
454
|
+
{ $sort: { updatedAt: -1 } },
|
|
455
|
+
{ $limit: maxPerType },
|
|
456
|
+
{ $project: { key: 1, value: 1, salience: 1, updatedAt: 1 } },
|
|
457
|
+
],
|
|
458
|
+
decisions: [
|
|
459
|
+
{ $match: { type: "decision" } },
|
|
460
|
+
{ $sort: { updatedAt: -1 } },
|
|
461
|
+
{ $limit: maxPerType },
|
|
462
|
+
{ $project: { key: 1, value: 1, salience: 1, updatedAt: 1 } },
|
|
463
|
+
],
|
|
464
|
+
facts: [
|
|
465
|
+
{ $match: { type: "fact" } },
|
|
466
|
+
{ $sort: { updatedAt: -1 } },
|
|
467
|
+
{ $limit: maxPerType },
|
|
468
|
+
{ $project: { key: 1, value: 1, salience: 1, updatedAt: 1 } },
|
|
469
|
+
],
|
|
470
|
+
todos: [
|
|
471
|
+
{ $match: { type: "todo" } },
|
|
472
|
+
{ $sort: { updatedAt: -1 } },
|
|
473
|
+
{ $limit: maxPerType },
|
|
474
|
+
{ $project: { key: 1, value: 1, salience: 1, updatedAt: 1 } },
|
|
475
|
+
],
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
])
|
|
479
|
+
.toArray();
|
|
480
|
+
|
|
481
|
+
const structured = structuredResults[0] ?? {
|
|
482
|
+
preferences: [],
|
|
483
|
+
decisions: [],
|
|
484
|
+
facts: [],
|
|
485
|
+
todos: [],
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// 2. Top entities by relation count
|
|
489
|
+
const entityResults = await entitiesCollection(db, prefix)
|
|
490
|
+
.aggregate([
|
|
491
|
+
{ $match: scopeFilter },
|
|
492
|
+
{
|
|
493
|
+
$lookup: {
|
|
494
|
+
from: `${prefix}relations`,
|
|
495
|
+
let: { eid: "$entityId" },
|
|
496
|
+
pipeline: [
|
|
497
|
+
{
|
|
498
|
+
$match: {
|
|
499
|
+
$expr: {
|
|
500
|
+
$or: [{ $eq: ["$fromEntityId", "$$eid"] }, { $eq: ["$toEntityId", "$$eid"] }],
|
|
501
|
+
},
|
|
502
|
+
...scopeFilter,
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
{ $count: "cnt" },
|
|
506
|
+
],
|
|
507
|
+
as: "rels",
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
$addFields: {
|
|
512
|
+
relationCount: { $ifNull: [{ $arrayElemAt: ["$rels.cnt", 0] }, 0] },
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
{ $sort: { relationCount: -1 } },
|
|
516
|
+
{ $limit: maxEntities },
|
|
517
|
+
{ $project: { name: 1, type: 1, relationCount: 1 } },
|
|
518
|
+
])
|
|
519
|
+
.toArray();
|
|
520
|
+
|
|
521
|
+
// 3. Recent episodes
|
|
522
|
+
const episodeResults = await episodesCollection(db, prefix)
|
|
523
|
+
.find(scopeFilter)
|
|
524
|
+
.sort({ "timeRange.start": -1 })
|
|
525
|
+
.limit(maxEpisodes)
|
|
526
|
+
.project({ title: 1, summary: 1, type: 1, timeRange: 1 })
|
|
527
|
+
.toArray();
|
|
528
|
+
|
|
529
|
+
// 4. Activity patterns from events (last N days)
|
|
530
|
+
const activitySince = new Date(Date.now() - activityWindowMs);
|
|
531
|
+
const activityResults = await eventsCollection(db, prefix)
|
|
532
|
+
.aggregate([
|
|
533
|
+
{ $match: { ...scopeFilter, timestamp: { $gte: activitySince } } },
|
|
534
|
+
{
|
|
535
|
+
$group: {
|
|
536
|
+
_id: "$role",
|
|
537
|
+
count: { $sum: 1 },
|
|
538
|
+
lastTs: { $max: "$timestamp" },
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
])
|
|
542
|
+
.toArray();
|
|
543
|
+
|
|
544
|
+
const roleDistribution: Record<string, number> = {};
|
|
545
|
+
let totalEvents = 0;
|
|
546
|
+
let lastActive: Date | null = null;
|
|
547
|
+
for (const r of activityResults) {
|
|
548
|
+
roleDistribution[r._id as string] = r.count as number;
|
|
549
|
+
totalEvents += r.count as number;
|
|
550
|
+
const ts = r.lastTs as Date;
|
|
551
|
+
if (!lastActive || ts > lastActive) lastActive = ts;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const durationMs = Date.now() - profileStart;
|
|
555
|
+
emitTelemetry(db, prefix, {
|
|
556
|
+
meta: { agentId, operation: "profile-synthesis" },
|
|
557
|
+
durationMs,
|
|
558
|
+
ok: true,
|
|
559
|
+
resultCount: totalEvents,
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
agentId,
|
|
564
|
+
scope,
|
|
565
|
+
scopeRef,
|
|
566
|
+
preferences: mapMemoryItems(structured.preferences),
|
|
567
|
+
decisions: mapMemoryItems(structured.decisions),
|
|
568
|
+
facts: mapMemoryItems(structured.facts),
|
|
569
|
+
todos: mapMemoryItems(structured.todos),
|
|
570
|
+
topEntities: entityResults.map((e) => ({
|
|
571
|
+
name: e.name as string,
|
|
572
|
+
type: e.type as string,
|
|
573
|
+
relationCount: e.relationCount as number,
|
|
574
|
+
})),
|
|
575
|
+
recentEpisodes: episodeResults.map((e) => ({
|
|
576
|
+
title: e.title as string,
|
|
577
|
+
summary: e.summary as string,
|
|
578
|
+
type: e.type as string,
|
|
579
|
+
timeRange: e.timeRange as { start: Date; end: Date },
|
|
580
|
+
})),
|
|
581
|
+
activityPatterns: { roleDistribution, totalEvents, lastActive },
|
|
582
|
+
synthesizedAt: new Date(),
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function mapMemoryItems(items: Document[]): ProfileMemoryItem[] {
|
|
587
|
+
return items.map((i) => ({
|
|
588
|
+
key: i.key as string,
|
|
589
|
+
value: i.value as string,
|
|
590
|
+
salience: (i.salience as string) ?? "normal",
|
|
591
|
+
updatedAt: i.updatedAt as Date,
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Task 2.3: Write tests for mongodb-profile.ts
|
|
597
|
+
|
|
598
|
+
**Files:**
|
|
599
|
+
|
|
600
|
+
- Create: `src/memory/mongodb-profile.test.ts`
|
|
601
|
+
|
|
602
|
+
**Tests (18+):**
|
|
603
|
+
|
|
604
|
+
1. `synthesizeProfile returns empty profile when no data exists`
|
|
605
|
+
2. `synthesizeProfile groups structured memory by type via $facet`
|
|
606
|
+
3. `synthesizeProfile limits items per type to maxPerType`
|
|
607
|
+
4. `synthesizeProfile sorts structured memory by updatedAt desc`
|
|
608
|
+
5. `synthesizeProfile filters by state: active only`
|
|
609
|
+
6. `synthesizeProfile returns top entities by relation count`
|
|
610
|
+
7. `synthesizeProfile limits entities to maxEntities`
|
|
611
|
+
8. `synthesizeProfile returns recent episodes sorted by timeRange.start desc`
|
|
612
|
+
9. `synthesizeProfile limits episodes to maxEpisodes`
|
|
613
|
+
10. `synthesizeProfile calculates activity patterns from events`
|
|
614
|
+
11. `synthesizeProfile uses activityWindowMs for event filter`
|
|
615
|
+
12. `synthesizeProfile returns null lastActive when no events`
|
|
616
|
+
13. `synthesizeProfile filters all queries by agentId, scope, scopeRef`
|
|
617
|
+
14. `synthesizeProfile emits profile-synthesis telemetry`
|
|
618
|
+
15. `synthesizeProfile handles empty structured_mem collection`
|
|
619
|
+
16. `synthesizeProfile handles empty entities collection`
|
|
620
|
+
17. `synthesizeProfile handles empty episodes collection`
|
|
621
|
+
18. `synthesizeProfile handles empty events collection`
|
|
622
|
+
|
|
623
|
+
**Mock pattern:** Mock all 5 collection accessors (structuredMemCollection, entitiesCollection, relationsCollection, episodesCollection, eventsCollection) with vi.fn() returning mock aggregate/find chains.
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
pnpm test -- src/memory/mongodb-profile.test.ts
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### Task 2.4: Export from barrel and verify build
|
|
630
|
+
|
|
631
|
+
**Files:**
|
|
632
|
+
|
|
633
|
+
- Modify: `src/memory/index.ts`
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
export {
|
|
637
|
+
synthesizeProfile,
|
|
638
|
+
type ProfileSynthesis,
|
|
639
|
+
type ProfileMemoryItem,
|
|
640
|
+
type ProfileEntity,
|
|
641
|
+
type ProfileEpisode,
|
|
642
|
+
type ActivityPatterns,
|
|
643
|
+
} from "./mongodb-profile.js";
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
```bash
|
|
647
|
+
pnpm build
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
**Commit after Phase 2:**
|
|
651
|
+
|
|
652
|
+
```
|
|
653
|
+
Feat: add profile synthesis from structured memory, entities, episodes, and events
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
---
|
|
657
|
+
|
|
658
|
+
## Phase 3: Cross-Encoder Re-ranking
|
|
659
|
+
|
|
660
|
+
> **Exit Criteria:** `mongodb-reranker.ts` module with crossEncoderRerank() function. Calls Voyage rerank-2.5 API. Falls back to input order on failure. Wired into searchV2() after heuristic reranking. 15+ tests pass.
|
|
661
|
+
|
|
662
|
+
### Task 3.1: Create mongodb-reranker.ts with types
|
|
663
|
+
|
|
664
|
+
**Files:**
|
|
665
|
+
|
|
666
|
+
- Create: `src/memory/mongodb-reranker.ts`
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
670
|
+
import type { Db } from "mongodb";
|
|
671
|
+
import { emitTelemetry } from "./mongodb-telemetry.js";
|
|
672
|
+
import type { MemorySearchResult } from "./types.js";
|
|
673
|
+
|
|
674
|
+
const log = createSubsystemLogger("memory:mongodb:reranker");
|
|
675
|
+
|
|
676
|
+
export type RerankConfig = {
|
|
677
|
+
enabled: boolean;
|
|
678
|
+
model: "rerank-2.5" | "rerank-2.5-lite";
|
|
679
|
+
topN: number;
|
|
680
|
+
minScore: number;
|
|
681
|
+
voyageApiKey: string;
|
|
682
|
+
/** Optional instruction prepended to query for rerank-2.5 instruction-following. */
|
|
683
|
+
instruction?: string;
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
export type RerankResult = {
|
|
687
|
+
results: MemorySearchResult[];
|
|
688
|
+
reranked: boolean;
|
|
689
|
+
latencyMs: number;
|
|
690
|
+
};
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Task 3.2: Implement crossEncoderRerank()
|
|
694
|
+
|
|
695
|
+
**Files:**
|
|
696
|
+
|
|
697
|
+
- Modify: `src/memory/mongodb-reranker.ts`
|
|
698
|
+
|
|
699
|
+
**Implementation:**
|
|
700
|
+
|
|
701
|
+
1. If config.enabled is false or no results, return input unchanged
|
|
702
|
+
2. Filter results above config.minScore
|
|
703
|
+
3. Slice to config.topN candidates
|
|
704
|
+
4. Extract snippet text from each candidate
|
|
705
|
+
5. Call Voyage rerank API: POST https://api.voyageai.com/v1/rerank
|
|
706
|
+
6. Map Voyage response scores back onto MemorySearchResult objects (update .score field)
|
|
707
|
+
7. Re-sort by new score descending
|
|
708
|
+
8. Append any results that were below minScore (not sent to reranker) at the end
|
|
709
|
+
9. On ANY error: log.warn, return input unchanged (never crash search)
|
|
710
|
+
10. Emit rerank telemetry
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
const VOYAGE_RERANK_URL = "https://api.voyageai.com/v1/rerank";
|
|
714
|
+
|
|
715
|
+
export async function crossEncoderRerank(params: {
|
|
716
|
+
db: Db;
|
|
717
|
+
prefix: string;
|
|
718
|
+
agentId: string;
|
|
719
|
+
query: string;
|
|
720
|
+
results: MemorySearchResult[];
|
|
721
|
+
config: RerankConfig;
|
|
722
|
+
}): Promise<RerankResult> {
|
|
723
|
+
const { db, prefix, agentId, query, results, config } = params;
|
|
724
|
+
const rerankStart = Date.now();
|
|
725
|
+
|
|
726
|
+
if (!config.enabled || results.length === 0 || !config.voyageApiKey) {
|
|
727
|
+
return { results, reranked: false, latencyMs: 0 };
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Split into candidates (above minScore) and remainder
|
|
731
|
+
const candidates = results.filter((r) => r.score >= config.minScore).slice(0, config.topN);
|
|
732
|
+
const remainder = results.filter((r) => r.score < config.minScore);
|
|
733
|
+
|
|
734
|
+
if (candidates.length <= 1) {
|
|
735
|
+
return { results, reranked: false, latencyMs: 0 };
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
try {
|
|
739
|
+
const documents = candidates.map((r) => r.snippet);
|
|
740
|
+
const response = await fetch(VOYAGE_RERANK_URL, {
|
|
741
|
+
method: "POST",
|
|
742
|
+
headers: {
|
|
743
|
+
"Content-Type": "application/json",
|
|
744
|
+
Authorization: `Bearer ${config.voyageApiKey}`,
|
|
745
|
+
},
|
|
746
|
+
body: JSON.stringify({
|
|
747
|
+
model: config.model,
|
|
748
|
+
// rerank-2.5 supports instruction-following: prepend instruction to query for 8-11% accuracy boost
|
|
749
|
+
// Example instruction: "This is agent conversation memory. Prioritize recent and contextually relevant results."
|
|
750
|
+
query: config.instruction ? `${config.instruction}\n${query}` : query,
|
|
751
|
+
documents,
|
|
752
|
+
top_k: candidates.length,
|
|
753
|
+
}),
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
if (!response.ok) {
|
|
757
|
+
log.warn("rerank API returned non-OK status", { status: response.status });
|
|
758
|
+
return { results, reranked: false, latencyMs: Date.now() - rerankStart };
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const body = (await response.json()) as {
|
|
762
|
+
data: Array<{ index: number; relevance_score: number }>;
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
if (!body.data || !Array.isArray(body.data)) {
|
|
766
|
+
log.warn("rerank API returned unexpected response shape");
|
|
767
|
+
return { results, reranked: false, latencyMs: Date.now() - rerankStart };
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Map scores back onto candidate results (clamp to [0,1] — docs don't guarantee range)
|
|
771
|
+
const reranked = body.data
|
|
772
|
+
.sort((a, b) => b.relevance_score - a.relevance_score)
|
|
773
|
+
.map((r) => {
|
|
774
|
+
const original = candidates[r.index];
|
|
775
|
+
return { ...original, score: Math.min(1, Math.max(0, r.relevance_score)) };
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
const latencyMs = Date.now() - rerankStart;
|
|
779
|
+
emitTelemetry(db, prefix, {
|
|
780
|
+
meta: { agentId, operation: "rerank" },
|
|
781
|
+
durationMs: latencyMs,
|
|
782
|
+
ok: true,
|
|
783
|
+
resultCount: reranked.length,
|
|
784
|
+
rerankModel: config.model,
|
|
785
|
+
rerankLatencyMs: latencyMs,
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
return {
|
|
789
|
+
results: [...reranked, ...remainder],
|
|
790
|
+
reranked: true,
|
|
791
|
+
latencyMs,
|
|
792
|
+
};
|
|
793
|
+
} catch (err) {
|
|
794
|
+
log.warn("rerank failed, falling back to input order", { error: err });
|
|
795
|
+
return { results, reranked: false, latencyMs: Date.now() - rerankStart };
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
### Task 3.3: Add rerankConfig + queryRewriteConfig to searchV2 context and V2SearchMetadata
|
|
801
|
+
|
|
802
|
+
**Files:**
|
|
803
|
+
|
|
804
|
+
- Modify: `src/memory/mongodb-manager.ts`
|
|
805
|
+
|
|
806
|
+
**CRITICAL: searchV2() is a standalone function with NO access to resolvedConfig.** Config must be passed via the existing `context.searchOptions` parameter.
|
|
807
|
+
|
|
808
|
+
**Step 1:** Add to V2SearchMetadata type (line ~2467):
|
|
809
|
+
|
|
810
|
+
```typescript
|
|
811
|
+
export type V2SearchMetadata = {
|
|
812
|
+
plan: RetrievalPlan;
|
|
813
|
+
pathsExecuted: RetrievalPath[];
|
|
814
|
+
resultsByPath: Record<string, number>;
|
|
815
|
+
reranked?: boolean; // NEW
|
|
816
|
+
queryRewritten?: boolean; // NEW
|
|
817
|
+
};
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
**Step 2:** Add to searchV2's `context.searchOptions` type:
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
// Inside the context parameter of searchV2():
|
|
824
|
+
searchOptions?: {
|
|
825
|
+
// ... existing fields (numCandidates, capabilities, fusionMethod, etc.) ...
|
|
826
|
+
rerankConfig?: import("./mongodb-reranker.js").RerankConfig;
|
|
827
|
+
queryRewriteConfig?: import("./mongodb-query-rewriter.js").QueryRewriteConfig;
|
|
828
|
+
};
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
**Step 3:** Pass config from the caller in MongoDBMemoryManager.search() (line ~770):
|
|
832
|
+
|
|
833
|
+
```typescript
|
|
834
|
+
// In MongoDBMemoryManager.search(), where context is constructed:
|
|
835
|
+
const v2 = await searchV2(this.db, this.prefix, cleaned, this.agentId, {
|
|
836
|
+
// ... existing context fields ...
|
|
837
|
+
searchOptions: {
|
|
838
|
+
// ... existing searchOptions ...
|
|
839
|
+
rerankConfig: mongoCfg.reranking,
|
|
840
|
+
queryRewriteConfig: mongoCfg.queryRewriting,
|
|
841
|
+
},
|
|
842
|
+
});
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
### Task 3.4: Wire crossEncoderRerank into searchV2()
|
|
846
|
+
|
|
847
|
+
**Files:**
|
|
848
|
+
|
|
849
|
+
- Modify: `src/memory/mongodb-manager.ts`
|
|
850
|
+
|
|
851
|
+
**Hook location:** In searchV2(), AFTER `const reranked = rerankResults(deduped, query)` (line ~2966) and BEFORE the return.
|
|
852
|
+
|
|
853
|
+
```typescript
|
|
854
|
+
import { crossEncoderRerank } from "./mongodb-reranker.js";
|
|
855
|
+
|
|
856
|
+
// After heuristic reranking, before final slice:
|
|
857
|
+
const rerankCfg = context.searchOptions?.rerankConfig;
|
|
858
|
+
let finalResults = reranked;
|
|
859
|
+
let wasReranked = false;
|
|
860
|
+
if (rerankCfg?.enabled) {
|
|
861
|
+
const rerankResult = await crossEncoderRerank({
|
|
862
|
+
db,
|
|
863
|
+
prefix,
|
|
864
|
+
agentId,
|
|
865
|
+
query,
|
|
866
|
+
results: reranked,
|
|
867
|
+
config: rerankCfg,
|
|
868
|
+
});
|
|
869
|
+
if (rerankResult.reranked) {
|
|
870
|
+
finalResults = rerankResult.results;
|
|
871
|
+
wasReranked = true;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return {
|
|
875
|
+
results: finalResults.slice(0, maxResults),
|
|
876
|
+
metadata: { ...metadata, reranked: wasReranked },
|
|
877
|
+
};
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
### Task 3.4: Write tests for mongodb-reranker.ts
|
|
881
|
+
|
|
882
|
+
**Files:**
|
|
883
|
+
|
|
884
|
+
- Create: `src/memory/mongodb-reranker.test.ts`
|
|
885
|
+
|
|
886
|
+
**Tests (15+):**
|
|
887
|
+
|
|
888
|
+
1. `crossEncoderRerank returns input unchanged when disabled`
|
|
889
|
+
2. `crossEncoderRerank returns input unchanged when no results`
|
|
890
|
+
3. `crossEncoderRerank returns input unchanged when no API key`
|
|
891
|
+
4. `crossEncoderRerank returns input unchanged when single result`
|
|
892
|
+
5. `crossEncoderRerank calls Voyage API with correct payload`
|
|
893
|
+
6. `crossEncoderRerank maps scores back onto correct results`
|
|
894
|
+
7. `crossEncoderRerank re-sorts by relevance_score descending`
|
|
895
|
+
8. `crossEncoderRerank appends below-minScore results at end`
|
|
896
|
+
9. `crossEncoderRerank slices candidates to topN`
|
|
897
|
+
10. `crossEncoderRerank falls back on API error (non-OK status)`
|
|
898
|
+
11. `crossEncoderRerank falls back on network error`
|
|
899
|
+
12. `crossEncoderRerank falls back on JSON parse error`
|
|
900
|
+
13. `crossEncoderRerank emits rerank telemetry on success`
|
|
901
|
+
14. `crossEncoderRerank reports reranked:false on fallback`
|
|
902
|
+
15. `crossEncoderRerank uses correct model from config`
|
|
903
|
+
|
|
904
|
+
**Mock pattern:** Mock global fetch with vi.fn(). Mock emitTelemetry.
|
|
905
|
+
|
|
906
|
+
```bash
|
|
907
|
+
pnpm test -- src/memory/mongodb-reranker.test.ts
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
### Task 3.5: Export from barrel and verify build
|
|
911
|
+
|
|
912
|
+
**Files:**
|
|
913
|
+
|
|
914
|
+
- Modify: `src/memory/index.ts`
|
|
915
|
+
|
|
916
|
+
```typescript
|
|
917
|
+
export { crossEncoderRerank, type RerankConfig, type RerankResult } from "./mongodb-reranker.js";
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
```bash
|
|
921
|
+
pnpm build
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
**Commit after Phase 3:**
|
|
925
|
+
|
|
926
|
+
```
|
|
927
|
+
Feat: add cross-encoder re-ranking via Voyage rerank-2.5 with fallback
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
---
|
|
931
|
+
|
|
932
|
+
## Phase 4: Query Rewriting
|
|
933
|
+
|
|
934
|
+
> **Exit Criteria:** `mongodb-query-rewriter.ts` module with rewriteQuery() function. Synonym-expansion tier is deterministic (zero latency). Wired into searchV2() AFTER planRetrieval() but BEFORE path execution. Cache key uses original query. 16+ tests pass.
|
|
935
|
+
|
|
936
|
+
### Task 4.1: Create mongodb-query-rewriter.ts with types and synonym map
|
|
937
|
+
|
|
938
|
+
**Files:**
|
|
939
|
+
|
|
940
|
+
- Create: `src/memory/mongodb-query-rewriter.ts`
|
|
941
|
+
|
|
942
|
+
```typescript
|
|
943
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
944
|
+
import type { Db } from "mongodb";
|
|
945
|
+
import { emitTelemetry } from "./mongodb-telemetry.js";
|
|
946
|
+
|
|
947
|
+
const log = createSubsystemLogger("memory:mongodb:query-rewriter");
|
|
948
|
+
|
|
949
|
+
export type QueryRewriteConfig = {
|
|
950
|
+
enabled: boolean;
|
|
951
|
+
method: "synonym-expansion" | "llm" | "hyde";
|
|
952
|
+
maxTokens: number;
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
export type QueryRewriteResult = {
|
|
956
|
+
originalQuery: string;
|
|
957
|
+
rewrittenQuery: string;
|
|
958
|
+
rewritten: boolean;
|
|
959
|
+
method: string;
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Domain-specific synonym map for agent memory queries.
|
|
964
|
+
* Bidirectional: each key expands to its values.
|
|
965
|
+
*/
|
|
966
|
+
const SYNONYM_MAP: Record<string, string[]> = {
|
|
967
|
+
auth: ["authentication", "login", "oauth"],
|
|
968
|
+
db: ["database", "mongodb", "collection"],
|
|
969
|
+
api: ["endpoint", "route", "rest"],
|
|
970
|
+
ui: ["interface", "frontend", "component"],
|
|
971
|
+
bug: ["issue", "error", "defect"],
|
|
972
|
+
perf: ["performance", "latency", "speed"],
|
|
973
|
+
config: ["configuration", "settings", "options"],
|
|
974
|
+
deps: ["dependencies", "packages", "modules"],
|
|
975
|
+
deploy: ["deployment", "release", "publish"],
|
|
976
|
+
docs: ["documentation", "readme", "guide"],
|
|
977
|
+
test: ["testing", "tests", "spec"],
|
|
978
|
+
refactor: ["restructure", "reorganize", "cleanup"],
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
/** Abbreviation expansions (unidirectional: abbreviation -> full form) */
|
|
982
|
+
const ABBREVIATION_MAP: Record<string, string> = {
|
|
983
|
+
ts: "typescript",
|
|
984
|
+
js: "javascript",
|
|
985
|
+
py: "python",
|
|
986
|
+
env: "environment",
|
|
987
|
+
var: "variable",
|
|
988
|
+
fn: "function",
|
|
989
|
+
cb: "callback",
|
|
990
|
+
req: "request",
|
|
991
|
+
res: "response",
|
|
992
|
+
err: "error",
|
|
993
|
+
msg: "message",
|
|
994
|
+
ctx: "context",
|
|
995
|
+
impl: "implementation",
|
|
996
|
+
repo: "repository",
|
|
997
|
+
};
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
### Task 4.2: Implement rewriteQuery() with synonym-expansion tier
|
|
1001
|
+
|
|
1002
|
+
**Files:**
|
|
1003
|
+
|
|
1004
|
+
- Modify: `src/memory/mongodb-query-rewriter.ts`
|
|
1005
|
+
|
|
1006
|
+
```typescript
|
|
1007
|
+
/**
|
|
1008
|
+
* Rewrite a query for improved vector search recall.
|
|
1009
|
+
*
|
|
1010
|
+
* CRITICAL: The retrieval planner must ALWAYS see the ORIGINAL query.
|
|
1011
|
+
* This function is called AFTER planRetrieval() and BEFORE search execution.
|
|
1012
|
+
* The cache key must also use the ORIGINAL query.
|
|
1013
|
+
*
|
|
1014
|
+
* Tier 1 (synonym-expansion): Deterministic, zero latency.
|
|
1015
|
+
* - Expand known abbreviations
|
|
1016
|
+
* - Add synonyms for recognized terms
|
|
1017
|
+
* - Preserve original terms (expansion, not replacement)
|
|
1018
|
+
*/
|
|
1019
|
+
export async function rewriteQuery(params: {
|
|
1020
|
+
db: Db;
|
|
1021
|
+
prefix: string;
|
|
1022
|
+
agentId: string;
|
|
1023
|
+
query: string;
|
|
1024
|
+
config: QueryRewriteConfig;
|
|
1025
|
+
}): Promise<QueryRewriteResult> {
|
|
1026
|
+
const { db, prefix, agentId, query, config } = params;
|
|
1027
|
+
const rewriteStart = Date.now();
|
|
1028
|
+
|
|
1029
|
+
if (!config.enabled || !query.trim()) {
|
|
1030
|
+
return { originalQuery: query, rewrittenQuery: query, rewritten: false, method: "none" };
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
let rewritten: string;
|
|
1034
|
+
let method: string;
|
|
1035
|
+
|
|
1036
|
+
switch (config.method) {
|
|
1037
|
+
case "synonym-expansion":
|
|
1038
|
+
rewritten = expandSynonyms(query);
|
|
1039
|
+
method = "synonym-expansion";
|
|
1040
|
+
break;
|
|
1041
|
+
case "llm":
|
|
1042
|
+
case "hyde":
|
|
1043
|
+
// LLM and HyDE tiers are future work — fall back to synonym expansion
|
|
1044
|
+
log.warn(
|
|
1045
|
+
`query rewrite method "${config.method}" not yet implemented, falling back to synonym-expansion`,
|
|
1046
|
+
);
|
|
1047
|
+
rewritten = expandSynonyms(query);
|
|
1048
|
+
method = "synonym-expansion-fallback";
|
|
1049
|
+
break;
|
|
1050
|
+
default:
|
|
1051
|
+
rewritten = query;
|
|
1052
|
+
method = "none";
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const wasRewritten = rewritten !== query;
|
|
1056
|
+
if (wasRewritten) {
|
|
1057
|
+
// Truncate to maxTokens (rough approximation: 1 token ≈ 4 chars)
|
|
1058
|
+
const maxChars = config.maxTokens * 4;
|
|
1059
|
+
if (rewritten.length > maxChars) {
|
|
1060
|
+
rewritten = rewritten.slice(0, maxChars).trimEnd();
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
emitTelemetry(db, prefix, {
|
|
1065
|
+
meta: { agentId, operation: "query-rewrite" },
|
|
1066
|
+
durationMs: Date.now() - rewriteStart,
|
|
1067
|
+
ok: true,
|
|
1068
|
+
queryRewritten: wasRewritten,
|
|
1069
|
+
rewriteMethod: method,
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
return { originalQuery: query, rewrittenQuery: rewritten, rewritten: wasRewritten, method };
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Deterministic synonym expansion.
|
|
1077
|
+
* For each word in the query:
|
|
1078
|
+
* 1. Check if it's an abbreviation → add full form
|
|
1079
|
+
* 2. Check if it matches a synonym group → add all synonyms
|
|
1080
|
+
* Original words are always preserved.
|
|
1081
|
+
*/
|
|
1082
|
+
export function expandSynonyms(query: string): string {
|
|
1083
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1084
|
+
const expanded = new Set(words);
|
|
1085
|
+
|
|
1086
|
+
for (const word of words) {
|
|
1087
|
+
// Abbreviation expansion
|
|
1088
|
+
if (ABBREVIATION_MAP[word]) {
|
|
1089
|
+
expanded.add(ABBREVIATION_MAP[word]);
|
|
1090
|
+
}
|
|
1091
|
+
// Synonym expansion
|
|
1092
|
+
if (SYNONYM_MAP[word]) {
|
|
1093
|
+
for (const syn of SYNONYM_MAP[word]) {
|
|
1094
|
+
expanded.add(syn);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return [...expanded].join(" ");
|
|
1100
|
+
}
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
### Task 4.3: Wire rewriteQuery into searchV2()
|
|
1104
|
+
|
|
1105
|
+
**Files:**
|
|
1106
|
+
|
|
1107
|
+
- Modify: `src/memory/mongodb-manager.ts`
|
|
1108
|
+
|
|
1109
|
+
**Hook location:** In searchV2(), AFTER `const plan = planRetrieval(query, ...)` (line ~2625) and BEFORE the path execution `for` loop (line ~2669).
|
|
1110
|
+
|
|
1111
|
+
**Note:** searchV2 receives queryRewriteConfig via `context.searchOptions.queryRewriteConfig` (added in Phase 3 Task 3.3).
|
|
1112
|
+
|
|
1113
|
+
```typescript
|
|
1114
|
+
import { rewriteQuery } from "./mongodb-query-rewriter.js";
|
|
1115
|
+
|
|
1116
|
+
// After planRetrieval (planner sees ORIGINAL query):
|
|
1117
|
+
const plan = planRetrieval(query, { entities: knownEntityNames });
|
|
1118
|
+
|
|
1119
|
+
// Rewrite query for search execution (NOT for planner or cache key):
|
|
1120
|
+
const qrConfig = context.searchOptions?.queryRewriteConfig;
|
|
1121
|
+
let searchQuery = query;
|
|
1122
|
+
let wasRewritten = false;
|
|
1123
|
+
if (qrConfig?.enabled) {
|
|
1124
|
+
const rewriteResult = await rewriteQuery({
|
|
1125
|
+
db,
|
|
1126
|
+
prefix,
|
|
1127
|
+
agentId,
|
|
1128
|
+
query,
|
|
1129
|
+
config: qrConfig,
|
|
1130
|
+
});
|
|
1131
|
+
if (rewriteResult.rewritten) {
|
|
1132
|
+
searchQuery = rewriteResult.rewrittenQuery;
|
|
1133
|
+
wasRewritten = true;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
**CRITICAL: Two variables, two purposes:**
|
|
1139
|
+
|
|
1140
|
+
- `query` (original) → used for: `planRetrieval()` (already called above), `checkCache()`, `writeCache()`
|
|
1141
|
+
- `searchQuery` (rewritten) → used for: all path search calls
|
|
1142
|
+
|
|
1143
|
+
**Step 2: Replace `query` with `searchQuery` in EVERY path execution call (6 locations):**
|
|
1144
|
+
|
|
1145
|
+
- Line ~2677: `searchStructuredMemory(db, prefix, searchQuery, ...)` (active-critical path)
|
|
1146
|
+
- Line ~2697: `searchStructuredMemory(db, prefix, searchQuery, ...)` (structured path)
|
|
1147
|
+
- Line ~2790: `searchEpisodes(db, prefix, searchQuery, ...)` (episodic path)
|
|
1148
|
+
- Line ~2835: `mongoSearch({ ..., query: searchQuery, ... })` (hybrid path)
|
|
1149
|
+
- Line ~2879: `searchKB({ ..., query: searchQuery, ... })` (kb path)
|
|
1150
|
+
- Line ~2948: `mongoSearch({ ..., query: searchQuery, ... })` (hybrid BACKSTOP recursive call — don't miss this one!)
|
|
1151
|
+
|
|
1152
|
+
**Step 3: Add to metadata return:**
|
|
1153
|
+
|
|
1154
|
+
```typescript
|
|
1155
|
+
metadata: { ...metadata, reranked: wasReranked, queryRewritten: wasRewritten },
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
### Task 4.4: Write tests for mongodb-query-rewriter.ts
|
|
1159
|
+
|
|
1160
|
+
**Files:**
|
|
1161
|
+
|
|
1162
|
+
- Create: `src/memory/mongodb-query-rewriter.test.ts`
|
|
1163
|
+
|
|
1164
|
+
**Tests (16+):**
|
|
1165
|
+
|
|
1166
|
+
1. `rewriteQuery returns original when disabled`
|
|
1167
|
+
2. `rewriteQuery returns original for empty query`
|
|
1168
|
+
3. `expandSynonyms expands known abbreviations`
|
|
1169
|
+
4. `expandSynonyms adds synonyms for recognized terms`
|
|
1170
|
+
5. `expandSynonyms preserves original words`
|
|
1171
|
+
6. `expandSynonyms handles multiple words`
|
|
1172
|
+
7. `expandSynonyms is case-insensitive`
|
|
1173
|
+
8. `expandSynonyms returns unchanged query when no matches`
|
|
1174
|
+
9. `expandSynonyms deduplicates expanded terms`
|
|
1175
|
+
10. `rewriteQuery truncates to maxTokens`
|
|
1176
|
+
11. `rewriteQuery emits query-rewrite telemetry`
|
|
1177
|
+
12. `rewriteQuery reports rewritten:false when no expansion`
|
|
1178
|
+
13. `rewriteQuery falls back to synonym-expansion for llm method`
|
|
1179
|
+
14. `rewriteQuery falls back to synonym-expansion for hyde method`
|
|
1180
|
+
15. `rewriteQuery handles single-word query`
|
|
1181
|
+
16. `rewriteQuery handles query with all known abbreviations`
|
|
1182
|
+
|
|
1183
|
+
```bash
|
|
1184
|
+
pnpm test -- src/memory/mongodb-query-rewriter.test.ts
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
### Task 4.5: Export from barrel and verify build
|
|
1188
|
+
|
|
1189
|
+
**Files:**
|
|
1190
|
+
|
|
1191
|
+
- Modify: `src/memory/index.ts`
|
|
1192
|
+
|
|
1193
|
+
```typescript
|
|
1194
|
+
export {
|
|
1195
|
+
rewriteQuery,
|
|
1196
|
+
expandSynonyms,
|
|
1197
|
+
type QueryRewriteConfig,
|
|
1198
|
+
type QueryRewriteResult,
|
|
1199
|
+
} from "./mongodb-query-rewriter.js";
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
```bash
|
|
1203
|
+
pnpm build
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
**Commit after Phase 4:**
|
|
1207
|
+
|
|
1208
|
+
```
|
|
1209
|
+
Feat: add query rewriting with synonym expansion for improved search recall
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
---
|
|
1213
|
+
|
|
1214
|
+
## Phase 5: LLM Entity Extraction
|
|
1215
|
+
|
|
1216
|
+
> **Exit Criteria:** `mongodb-entity-extractor.ts` module with EntityExtractor interface, RegexEntityExtractor (extracted from mongodb-graph.ts), and LLMEntityExtractor stub. extractAndUpsertEntities() accepts optional extractor param. 14+ tests pass.
|
|
1217
|
+
|
|
1218
|
+
### Task 5.1: Create mongodb-entity-extractor.ts with interface and regex implementation
|
|
1219
|
+
|
|
1220
|
+
**Files:**
|
|
1221
|
+
|
|
1222
|
+
- Create: `src/memory/mongodb-entity-extractor.ts`
|
|
1223
|
+
|
|
1224
|
+
**Implementation:**
|
|
1225
|
+
|
|
1226
|
+
1. Define `EntityExtractor` interface with `extract(content, context?)` method
|
|
1227
|
+
2. Define `EntityExtractionContext` type (agentId, scope, scopeRef, existingEntities?)
|
|
1228
|
+
3. Define `ExtractedEntity` type (matches existing shape in mongodb-graph.ts)
|
|
1229
|
+
4. Extract `RegexEntityExtractor` from the existing regex logic in mongodb-graph.ts (lines 867-871: MENTION_REGEX, TAG_REGEX, URL_REGEX, FILE_PATH_REGEX, QUOTED_NAME_REGEX)
|
|
1230
|
+
5. Create `LLMEntityExtractor` stub that accepts a callable LLM function, with timeout and fallback to regex
|
|
1231
|
+
|
|
1232
|
+
```typescript
|
|
1233
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
1234
|
+
|
|
1235
|
+
const log = createSubsystemLogger("memory:mongodb:entity-extractor");
|
|
1236
|
+
|
|
1237
|
+
// Import the canonical EntityType from mongodb-graph.ts — do NOT redefine it
|
|
1238
|
+
import type { EntityType } from "./mongodb-graph.js";
|
|
1239
|
+
|
|
1240
|
+
// Extended types for LLM extraction (beyond the base EntityType union)
|
|
1241
|
+
// These are accepted by MongoDB since ENTITIES_SCHEMA validates type as bsonType:"string" (not enum)
|
|
1242
|
+
// The TypeScript EntityType union in mongodb-graph.ts should be expanded to include these
|
|
1243
|
+
export type ExtendedEntityType = EntityType | "location" | "system" | "concept";
|
|
1244
|
+
|
|
1245
|
+
export type ExtractedEntity = {
|
|
1246
|
+
name: string;
|
|
1247
|
+
type: string; // string (not EntityType) to allow LLM-extracted extended types
|
|
1248
|
+
confidence?: number;
|
|
1249
|
+
extractionMethod: "regex" | "llm";
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
export type EntityExtractionContext = {
|
|
1253
|
+
agentId: string;
|
|
1254
|
+
scope: string;
|
|
1255
|
+
scopeRef: string;
|
|
1256
|
+
existingEntityNames?: string[];
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
export interface EntityExtractor {
|
|
1260
|
+
extract(content: string, context?: EntityExtractionContext): Promise<ExtractedEntity[]>;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// Regex patterns (extracted from mongodb-graph.ts)
|
|
1264
|
+
const MENTION_REGEX = /@(\w{3,})/g;
|
|
1265
|
+
const TAG_REGEX = /#(\w{3,})/g;
|
|
1266
|
+
const URL_REGEX = /https?:\/\/[^\s)]+/g; // Excludes ) — matches actual mongodb-graph.ts
|
|
1267
|
+
const FILE_PATH_REGEX = /(?:^|\s)((?:[\w.-]+\/)+[\w.-]+\.\w+)/g; // + quantifier — matches actual
|
|
1268
|
+
const QUOTED_NAME_REGEX = /"([^"]{3,})"/g;
|
|
1269
|
+
|
|
1270
|
+
// STOP_WORDS: MUST be copied VERBATIM from mongodb-graph.ts (lines 803-864) at build time.
|
|
1271
|
+
// DO NOT hardcode a different list here — the original source is the single source of truth.
|
|
1272
|
+
// At implementation time: read the exact STOP_WORDS set from mongodb-graph.ts and paste it here.
|
|
1273
|
+
// This ensures RegexEntityExtractor filters identically to the original inline extraction.
|
|
1274
|
+
import { STOP_WORDS } from "./mongodb-graph.js";
|
|
1275
|
+
// NOTE: STOP_WORDS must be exported from mongodb-graph.ts first (it's currently module-private).
|
|
1276
|
+
// Add `export` to the existing `const STOP_WORDS = new Set([...])` in mongodb-graph.ts.
|
|
1277
|
+
|
|
1278
|
+
export class RegexEntityExtractor implements EntityExtractor {
|
|
1279
|
+
async extract(content: string): Promise<ExtractedEntity[]> {
|
|
1280
|
+
const entities: ExtractedEntity[] = [];
|
|
1281
|
+
const seen = new Set<string>();
|
|
1282
|
+
|
|
1283
|
+
const addEntity = (name: string, type: string) => {
|
|
1284
|
+
// Apply stop-word filter for non-URL/non-path entities (matches original behavior)
|
|
1285
|
+
if (type !== "document" && STOP_WORDS.has(name.toLowerCase())) return;
|
|
1286
|
+
const key = `${name.toLowerCase()}:${type}`;
|
|
1287
|
+
if (!seen.has(key)) {
|
|
1288
|
+
seen.add(key);
|
|
1289
|
+
entities.push({ name, type, confidence: 0.5, extractionMethod: "regex" });
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
for (const match of content.matchAll(MENTION_REGEX)) {
|
|
1294
|
+
addEntity(match[1], "person");
|
|
1295
|
+
}
|
|
1296
|
+
for (const match of content.matchAll(TAG_REGEX)) {
|
|
1297
|
+
addEntity(match[1], "topic");
|
|
1298
|
+
}
|
|
1299
|
+
for (const match of content.matchAll(URL_REGEX)) {
|
|
1300
|
+
addEntity(match[0], "document");
|
|
1301
|
+
}
|
|
1302
|
+
for (const match of content.matchAll(FILE_PATH_REGEX)) {
|
|
1303
|
+
addEntity(match[1], "document");
|
|
1304
|
+
}
|
|
1305
|
+
for (const match of content.matchAll(QUOTED_NAME_REGEX)) {
|
|
1306
|
+
addEntity(match[1], "person");
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
return entities;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
export type LLMFunction = (prompt: string) => Promise<string>;
|
|
1314
|
+
|
|
1315
|
+
export class LLMEntityExtractor implements EntityExtractor {
|
|
1316
|
+
private llmFn: LLMFunction;
|
|
1317
|
+
private timeoutMs: number;
|
|
1318
|
+
private fallback: RegexEntityExtractor;
|
|
1319
|
+
|
|
1320
|
+
constructor(llmFn: LLMFunction, timeoutMs = 5000) {
|
|
1321
|
+
this.llmFn = llmFn;
|
|
1322
|
+
this.timeoutMs = timeoutMs;
|
|
1323
|
+
this.fallback = new RegexEntityExtractor();
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
async extract(content: string, context?: EntityExtractionContext): Promise<ExtractedEntity[]> {
|
|
1327
|
+
try {
|
|
1328
|
+
const result = await Promise.race([
|
|
1329
|
+
this.extractWithLLM(content, context),
|
|
1330
|
+
new Promise<never>((_, reject) =>
|
|
1331
|
+
setTimeout(() => reject(new Error("LLM extraction timeout")), this.timeoutMs),
|
|
1332
|
+
),
|
|
1333
|
+
]);
|
|
1334
|
+
return result;
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
log.warn("LLM entity extraction failed, falling back to regex", { error: err });
|
|
1337
|
+
return this.fallback.extract(content, context);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
private async extractWithLLM(
|
|
1342
|
+
content: string,
|
|
1343
|
+
context?: EntityExtractionContext,
|
|
1344
|
+
): Promise<ExtractedEntity[]> {
|
|
1345
|
+
const prompt = buildExtractionPrompt(content, context);
|
|
1346
|
+
const response = await this.llmFn(prompt);
|
|
1347
|
+
return parseExtractionResponse(response);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
export function buildExtractionPrompt(content: string, context?: EntityExtractionContext): string {
|
|
1352
|
+
const existingHint = context?.existingEntityNames?.length
|
|
1353
|
+
? `\nKnown entities in this context: ${context.existingEntityNames.join(", ")}`
|
|
1354
|
+
: "";
|
|
1355
|
+
|
|
1356
|
+
return `Extract named entities from the following text. Return a JSON array of objects with "name", "type", and "confidence" fields.
|
|
1357
|
+
|
|
1358
|
+
Valid types: person, org, project, topic, feature, issue, document, location, system, concept
|
|
1359
|
+
|
|
1360
|
+
Rules:
|
|
1361
|
+
- Only extract entities explicitly mentioned in the text
|
|
1362
|
+
- Do not invent entities that are not present
|
|
1363
|
+
- Confidence should be 0.0-1.0 based on how certain you are
|
|
1364
|
+
- Normalize names (capitalize properly, no leading/trailing whitespace)
|
|
1365
|
+
${existingHint}
|
|
1366
|
+
|
|
1367
|
+
Text:
|
|
1368
|
+
${content}
|
|
1369
|
+
|
|
1370
|
+
Response (JSON array only):`;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
export function parseExtractionResponse(response: string): ExtractedEntity[] {
|
|
1374
|
+
try {
|
|
1375
|
+
// Find JSON array in response (may be wrapped in markdown code block)
|
|
1376
|
+
const jsonMatch = response.match(/\[[\s\S]*\]/);
|
|
1377
|
+
if (!jsonMatch) return [];
|
|
1378
|
+
|
|
1379
|
+
const parsed = JSON.parse(jsonMatch[0]) as Array<{
|
|
1380
|
+
name?: string;
|
|
1381
|
+
type?: string;
|
|
1382
|
+
confidence?: number;
|
|
1383
|
+
}>;
|
|
1384
|
+
|
|
1385
|
+
if (!Array.isArray(parsed)) return [];
|
|
1386
|
+
|
|
1387
|
+
return parsed
|
|
1388
|
+
.filter((e) => e.name && typeof e.name === "string" && e.name.trim().length >= 2)
|
|
1389
|
+
.map((e) => ({
|
|
1390
|
+
name: e.name!.trim(),
|
|
1391
|
+
type: (e.type as EntityType) ?? "custom",
|
|
1392
|
+
confidence: typeof e.confidence === "number" ? Math.min(1, Math.max(0, e.confidence)) : 0.7,
|
|
1393
|
+
extractionMethod: "llm" as const,
|
|
1394
|
+
}));
|
|
1395
|
+
} catch {
|
|
1396
|
+
log.warn("failed to parse LLM extraction response");
|
|
1397
|
+
return [];
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
### Task 5.2: Modify extractAndUpsertEntities() to accept optional extractor
|
|
1403
|
+
|
|
1404
|
+
**Files:**
|
|
1405
|
+
|
|
1406
|
+
- Modify: `src/memory/mongodb-graph.ts`
|
|
1407
|
+
|
|
1408
|
+
**CRITICAL: The extractor returns `{name, type}` but the existing code needs `{entityId, name, type}`.** The `entityId` is computed by `makeEntityId()` INSIDE `extractAndUpsertEntities()`, NOT by the extractor. The bridge pattern:
|
|
1409
|
+
|
|
1410
|
+
1. Extractor returns: `{ name, type, confidence?, extractionMethod }`
|
|
1411
|
+
2. `extractAndUpsertEntities()` calls extractor, then computes entityId for each result via existing `makeEntityId()` logic
|
|
1412
|
+
3. Everything downstream (upsertEntity, upsertRelation, upsertEntityLink) is unchanged
|
|
1413
|
+
|
|
1414
|
+
**Change the function signature:**
|
|
1415
|
+
|
|
1416
|
+
```typescript
|
|
1417
|
+
import {
|
|
1418
|
+
type EntityExtractor,
|
|
1419
|
+
RegexEntityExtractor,
|
|
1420
|
+
type ExtractedEntity as ExtractorResult,
|
|
1421
|
+
} from "./mongodb-entity-extractor.js";
|
|
1422
|
+
|
|
1423
|
+
const defaultExtractor = new RegexEntityExtractor();
|
|
1424
|
+
|
|
1425
|
+
export async function extractAndUpsertEntities(params: {
|
|
1426
|
+
db: Db;
|
|
1427
|
+
prefix: string;
|
|
1428
|
+
agentId: string;
|
|
1429
|
+
eventContent: string;
|
|
1430
|
+
scope: MemoryScope;
|
|
1431
|
+
scopeRef?: string;
|
|
1432
|
+
sourceEventId?: string;
|
|
1433
|
+
extractor?: EntityExtractor; // NEW optional param
|
|
1434
|
+
}): Promise<{ entities: ExtractedEntity[]; relationsCreated: number }> {
|
|
1435
|
+
const extractor = params.extractor ?? defaultExtractor;
|
|
1436
|
+
|
|
1437
|
+
// Replace the existing inline regex extraction with:
|
|
1438
|
+
const extractorResults = await extractor.extract(params.eventContent);
|
|
1439
|
+
|
|
1440
|
+
// Bridge: compute entityId for each extracted entity (existing makeEntityId logic)
|
|
1441
|
+
const entities: ExtractedEntity[] = extractorResults.map((r) => ({
|
|
1442
|
+
entityId: makeEntityId(r.name, r.type, params.agentId, params.scope, params.scopeRef ?? ""),
|
|
1443
|
+
name: r.name,
|
|
1444
|
+
type: r.type as EntityType,
|
|
1445
|
+
}));
|
|
1446
|
+
|
|
1447
|
+
// ... rest of the function unchanged (upsertEntity, upsertRelation, upsertEntityLink) ...
|
|
1448
|
+
}
|
|
1449
|
+
```
|
|
1450
|
+
|
|
1451
|
+
**Note:** Remove the existing inline regex patterns (MENTION_REGEX, TAG_REGEX, URL_REGEX, FILE_PATH_REGEX, QUOTED_NAME_REGEX) from mongodb-graph.ts — they now live in RegexEntityExtractor. Keep `makeEntityId()` in mongodb-graph.ts since it's used for the bridge.
|
|
1452
|
+
|
|
1453
|
+
**IMPORTANT: Use the EXACT regex from mongodb-graph.ts in RegexEntityExtractor:**
|
|
1454
|
+
|
|
1455
|
+
```typescript
|
|
1456
|
+
// Copy these EXACTLY from mongodb-graph.ts lines 867-871:
|
|
1457
|
+
const MENTION_REGEX = /@(\w{3,})/g;
|
|
1458
|
+
const TAG_REGEX = /#(\w{3,})/g;
|
|
1459
|
+
const URL_REGEX = /https?:\/\/[^\s)]+/g; // Note: excludes ) — matches actual code
|
|
1460
|
+
const FILE_PATH_REGEX = /(?:^|\s)((?:[\w.-]+\/)+[\w.-]+\.\w+)/g;
|
|
1461
|
+
const QUOTED_NAME_REGEX = /"([^"]{3,})"/g;
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1464
|
+
### Task 5.3: Wire extractor into writeEventAndProject()
|
|
1465
|
+
|
|
1466
|
+
**Files:**
|
|
1467
|
+
|
|
1468
|
+
- Modify: `src/memory/mongodb-manager.ts`
|
|
1469
|
+
|
|
1470
|
+
**CRITICAL: writeEventAndProject() is a standalone function with NO access to resolvedConfig.** The extractor instance must be passed as a new optional parameter.
|
|
1471
|
+
|
|
1472
|
+
**Step 1:** Add optional `extractor` parameter to writeEventAndProject:
|
|
1473
|
+
|
|
1474
|
+
```typescript
|
|
1475
|
+
export async function writeEventAndProject(
|
|
1476
|
+
db: Db, prefix: string, event: CanonicalEventInput,
|
|
1477
|
+
options?: { extractor?: EntityExtractor }, // NEW
|
|
1478
|
+
): Promise<...> {
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
**Step 2:** Pass it through to extractAndUpsertEntities:
|
|
1482
|
+
|
|
1483
|
+
```typescript
|
|
1484
|
+
await extractAndUpsertEntities({
|
|
1485
|
+
db, prefix,
|
|
1486
|
+
agentId: event.agentId,
|
|
1487
|
+
eventContent: event.body,
|
|
1488
|
+
scope: event.scope as MemoryScope,
|
|
1489
|
+
scopeRef: written.scopeRef,
|
|
1490
|
+
sourceEventId: written.eventId,
|
|
1491
|
+
extractor: options?.extractor, // NEW — passes through, defaults to regex inside
|
|
1492
|
+
}).catch((projErr) => { ... });
|
|
1493
|
+
```
|
|
1494
|
+
|
|
1495
|
+
**Step 3:** The MongoDBMemoryManager class creates the extractor once during initialization and passes it through calls:
|
|
1496
|
+
|
|
1497
|
+
```typescript
|
|
1498
|
+
// In MongoDBMemoryManager constructor or create():
|
|
1499
|
+
import { RegexEntityExtractor, LLMEntityExtractor } from "./mongodb-entity-extractor.js";
|
|
1500
|
+
|
|
1501
|
+
// NOTE: LLMEntityExtractor requires an LLMFunction (prompt: string) => Promise<string>.
|
|
1502
|
+
// For now, the LLM path is a stub — the manager does not currently have an LLM callable.
|
|
1503
|
+
// When method="llm" is configured but no LLM function is available, fall back to regex.
|
|
1504
|
+
// The LLM function injection point will be wired when the agent runtime's LLM interface
|
|
1505
|
+
// is formalized (deferred — regex is the working default).
|
|
1506
|
+
const extractorInstance =
|
|
1507
|
+
mongoCfg.graph.entityExtraction.method === "llm"
|
|
1508
|
+
? new RegexEntityExtractor() // TODO: wire LLM function from agent runtime when available
|
|
1509
|
+
: new RegexEntityExtractor();
|
|
1510
|
+
|
|
1511
|
+
// In any call to writeEventAndProject:
|
|
1512
|
+
await writeEventAndProject(this.db, this.prefix, event, {
|
|
1513
|
+
extractor: this.extractorInstance,
|
|
1514
|
+
});
|
|
1515
|
+
```
|
|
1516
|
+
|
|
1517
|
+
### Task 5.4: Write tests for mongodb-entity-extractor.ts
|
|
1518
|
+
|
|
1519
|
+
**Files:**
|
|
1520
|
+
|
|
1521
|
+
- Create: `src/memory/mongodb-entity-extractor.test.ts`
|
|
1522
|
+
|
|
1523
|
+
**Tests (14+):**
|
|
1524
|
+
|
|
1525
|
+
1. `RegexEntityExtractor extracts @mentions as person`
|
|
1526
|
+
2. `RegexEntityExtractor extracts #tags as topic`
|
|
1527
|
+
3. `RegexEntityExtractor extracts URLs as document`
|
|
1528
|
+
4. `RegexEntityExtractor extracts file paths as document`
|
|
1529
|
+
5. `RegexEntityExtractor extracts quoted names as person`
|
|
1530
|
+
6. `RegexEntityExtractor deduplicates entities`
|
|
1531
|
+
7. `RegexEntityExtractor returns empty array for no matches`
|
|
1532
|
+
8. `RegexEntityExtractor sets extractionMethod to regex`
|
|
1533
|
+
9. `LLMEntityExtractor calls LLM function with extraction prompt`
|
|
1534
|
+
10. `LLMEntityExtractor parses JSON array response`
|
|
1535
|
+
11. `LLMEntityExtractor falls back to regex on LLM error`
|
|
1536
|
+
12. `LLMEntityExtractor falls back to regex on timeout`
|
|
1537
|
+
13. `LLMEntityExtractor handles markdown-wrapped JSON`
|
|
1538
|
+
14. `parseExtractionResponse filters invalid entries`
|
|
1539
|
+
15. `parseExtractionResponse clamps confidence to [0,1]`
|
|
1540
|
+
16. `buildExtractionPrompt includes existing entity names`
|
|
1541
|
+
|
|
1542
|
+
```bash
|
|
1543
|
+
pnpm test -- src/memory/mongodb-entity-extractor.test.ts
|
|
1544
|
+
```
|
|
1545
|
+
|
|
1546
|
+
### Task 5.5: Export from barrel and verify build
|
|
1547
|
+
|
|
1548
|
+
**Files:**
|
|
1549
|
+
|
|
1550
|
+
- Modify: `src/memory/index.ts`
|
|
1551
|
+
|
|
1552
|
+
```typescript
|
|
1553
|
+
export {
|
|
1554
|
+
type EntityExtractor,
|
|
1555
|
+
type ExtractedEntity as ExtractedEntityV2,
|
|
1556
|
+
type EntityExtractionContext,
|
|
1557
|
+
type LLMFunction,
|
|
1558
|
+
RegexEntityExtractor,
|
|
1559
|
+
LLMEntityExtractor,
|
|
1560
|
+
buildExtractionPrompt,
|
|
1561
|
+
parseExtractionResponse,
|
|
1562
|
+
} from "./mongodb-entity-extractor.js";
|
|
1563
|
+
```
|
|
1564
|
+
|
|
1565
|
+
```bash
|
|
1566
|
+
pnpm build
|
|
1567
|
+
```
|
|
1568
|
+
|
|
1569
|
+
**Commit after Phase 5:**
|
|
1570
|
+
|
|
1571
|
+
```
|
|
1572
|
+
Feat: add pluggable entity extraction with regex default and LLM upgrade path
|
|
1573
|
+
```
|
|
1574
|
+
|
|
1575
|
+
---
|
|
1576
|
+
|
|
1577
|
+
## Phase 6: Final Integration + Validation
|
|
1578
|
+
|
|
1579
|
+
> **Exit Criteria:** All 4 features integrated, all tests pass, build clean. README updated. Barrel exports complete.
|
|
1580
|
+
|
|
1581
|
+
### Task 6.1: Update README.md capabilities table
|
|
1582
|
+
|
|
1583
|
+
**Files:**
|
|
1584
|
+
|
|
1585
|
+
- Modify: `README.md`
|
|
1586
|
+
|
|
1587
|
+
Add 4 new rows to the capabilities table:
|
|
1588
|
+
|
|
1589
|
+
```markdown
|
|
1590
|
+
| 15 | **Profile Synthesis** | Dynamic agent profile from structured memory, entities, episodes, and events | $facet + $lookup aggregation across 5 collections, ~5-50ms |
|
|
1591
|
+
| 16 | **Cross-Encoder Re-ranking** | Voyage rerank-2.5 precision pass on search results | Two-stage: $vectorSearch recall → rerank-2.5 precision, 13.89% accuracy improvement |
|
|
1592
|
+
| 17 | **Query Rewriting** | Synonym expansion for improved vector search recall | Deterministic abbreviation + synonym expansion before embedding |
|
|
1593
|
+
| 18 | **Pluggable Entity Extraction** | Regex default with LLM upgrade path for richer knowledge graphs | EntityExtractor interface, RegexEntityExtractor + LLMEntityExtractor |
|
|
1594
|
+
```
|
|
1595
|
+
|
|
1596
|
+
Update capability count from 14 to 18.
|
|
1597
|
+
|
|
1598
|
+
### Task 6.2: Run full validation
|
|
1599
|
+
|
|
1600
|
+
```bash
|
|
1601
|
+
# All new test files pass
|
|
1602
|
+
pnpm test -- src/memory/mongodb-profile.test.ts src/memory/mongodb-reranker.test.ts src/memory/mongodb-query-rewriter.test.ts src/memory/mongodb-entity-extractor.test.ts
|
|
1603
|
+
|
|
1604
|
+
# Backend config tests pass with new sections
|
|
1605
|
+
pnpm test -- src/memory/backend-config.test.ts
|
|
1606
|
+
|
|
1607
|
+
# Existing tests not regressed
|
|
1608
|
+
pnpm test -- src/memory/mongodb-manager.test.ts src/memory/mongodb-graph.test.ts
|
|
1609
|
+
|
|
1610
|
+
# Build clean
|
|
1611
|
+
pnpm build
|
|
1612
|
+
|
|
1613
|
+
# Full test suite
|
|
1614
|
+
pnpm test
|
|
1615
|
+
```
|
|
1616
|
+
|
|
1617
|
+
**Commit after Phase 6:**
|
|
1618
|
+
|
|
1619
|
+
```
|
|
1620
|
+
Docs: update README with 18 capabilities including profile, reranking, query rewriting, entity extraction
|
|
1621
|
+
```
|
|
1622
|
+
|
|
1623
|
+
---
|
|
1624
|
+
|
|
1625
|
+
## Risks
|
|
1626
|
+
|
|
1627
|
+
| Risk | P (1-5) | I (1-5) | Score | Mitigation |
|
|
1628
|
+
| --------------------------------------------- | ------- | ------- | ----- | -------------------------------------------------------------------- |
|
|
1629
|
+
| Voyage rerank API unavailable/slow | 3 | 2 | 6 | Fallback to heuristic order on any error |
|
|
1630
|
+
| LLM entity extraction hallucination | 3 | 3 | 9 | Validate extracted entities, confidence threshold, fallback to regex |
|
|
1631
|
+
| Query rewriting false expansions | 2 | 2 | 4 | Planner never sees rewritten query, cache key uses original |
|
|
1632
|
+
| $facet 100MB RAM limit on profile | 1 | 3 | 3 | Pre-filter with $match, limit each sub-pipeline to 20 items |
|
|
1633
|
+
| LLM extraction timeout on slow models | 3 | 2 | 6 | Configurable timeout (default 5s), fallback to regex |
|
|
1634
|
+
| Existing extractAndUpsertEntities tests break | 3 | 2 | 6 | RegexEntityExtractor preserves exact same behavior as default |
|
|
1635
|
+
| Score semantics confusion (rerank vs vector) | 2 | 2 | 4 | Rerank scores replace .score field, both [0,1] range |
|
|
1636
|
+
|
|
1637
|
+
---
|
|
1638
|
+
|
|
1639
|
+
## Success Criteria
|
|
1640
|
+
|
|
1641
|
+
- [ ] Profile synthesis returns ProfileSynthesis from 5 collections via $facet + $lookup
|
|
1642
|
+
- [ ] Cross-encoder reranking calls Voyage rerank-2.5 and improves precision
|
|
1643
|
+
- [ ] Cross-encoder falls back gracefully on any error
|
|
1644
|
+
- [ ] Query rewriting expands abbreviations and synonyms before search
|
|
1645
|
+
- [ ] Query rewriting never affects retrieval planner or cache key
|
|
1646
|
+
- [ ] Pluggable EntityExtractor interface with regex default
|
|
1647
|
+
- [ ] LLMEntityExtractor with timeout and regex fallback
|
|
1648
|
+
- [ ] All 4 features disabled-by-default (except profile which is on-demand)
|
|
1649
|
+
- [ ] Config sections added with sensible defaults
|
|
1650
|
+
- [ ] Telemetry emitted for all 4 operations
|
|
1651
|
+
- [ ] 60+ new tests pass
|
|
1652
|
+
- [ ] `pnpm build` exit 0
|
|
1653
|
+
- [ ] `pnpm test` — no regressions
|
|
1654
|
+
- [ ] Barrel exports complete in index.ts
|
|
1655
|
+
|
|
1656
|
+
---
|
|
1657
|
+
|
|
1658
|
+
## Acceptance Checks
|
|
1659
|
+
|
|
1660
|
+
```bash
|
|
1661
|
+
# Phase 1: Config tests
|
|
1662
|
+
pnpm test -- src/memory/backend-config.test.ts
|
|
1663
|
+
|
|
1664
|
+
# Phase 2: Profile synthesis tests
|
|
1665
|
+
pnpm test -- src/memory/mongodb-profile.test.ts
|
|
1666
|
+
|
|
1667
|
+
# Phase 3: Reranker tests
|
|
1668
|
+
pnpm test -- src/memory/mongodb-reranker.test.ts
|
|
1669
|
+
|
|
1670
|
+
# Phase 4: Query rewriter tests
|
|
1671
|
+
pnpm test -- src/memory/mongodb-query-rewriter.test.ts
|
|
1672
|
+
|
|
1673
|
+
# Phase 5: Entity extractor tests
|
|
1674
|
+
pnpm test -- src/memory/mongodb-entity-extractor.test.ts
|
|
1675
|
+
|
|
1676
|
+
# Phase 6: Full validation
|
|
1677
|
+
pnpm build
|
|
1678
|
+
pnpm test
|
|
1679
|
+
```
|