@vellumai/assistant 0.4.49 → 0.4.51
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/ARCHITECTURE.md +24 -33
- package/README.md +3 -3
- package/docs/architecture/integrations.md +2 -2
- package/docs/architecture/keychain-broker.md +6 -6
- package/docs/architecture/memory.md +180 -119
- package/knip.json +32 -0
- package/package.json +3 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/btw-routes.test.ts +61 -5
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/config-watcher.test.ts +8 -0
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +8 -7
- package/src/__tests__/credential-vault-unit.test.ts +23 -18
- package/src/__tests__/credential-vault.test.ts +30 -18
- package/src/__tests__/credentials-cli.test.ts +257 -82
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
- package/src/__tests__/integration-status.test.ts +31 -30
- package/src/__tests__/invite-redemption-service.test.ts +166 -13
- package/src/__tests__/invite-routes-http.test.ts +166 -5
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/list-messages-attachments.test.ts +193 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +824 -31
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +363 -17
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +55 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
- package/src/__tests__/secure-keys.test.ts +78 -18
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skills.test.ts +2 -2
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/slack-channel-config.test.ts +10 -8
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/twilio-config.test.ts +11 -10
- package/src/__tests__/twilio-provider.test.ts +9 -4
- package/src/__tests__/voice-invite-redemption.test.ts +85 -5
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +134 -3
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +44 -6
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +5 -4
- package/src/calls/twilio-provider.ts +14 -9
- package/src/calls/twilio-rest.ts +10 -7
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/config.ts +14 -9
- package/src/cli/commands/contacts.ts +3 -0
- package/src/cli/commands/credentials.ts +170 -174
- package/src/cli/commands/doctor.ts +11 -8
- package/src/cli/commands/keys.ts +9 -9
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +68 -10
- package/src/cli/commands/oauth/connections.ts +475 -105
- package/src/cli/commands/oauth/index.ts +3 -3
- package/src/cli/commands/oauth/providers.ts +18 -4
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +20 -22
- package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-skills/contacts/SKILL.md +35 -11
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
- package/src/config/bundled-skills/messaging/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
- package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/loader.ts +6 -42
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/contacts/contact-store.ts +39 -2
- package/src/contacts/contacts-write.ts +9 -0
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +55 -2
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/config-ingress.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +59 -39
- package/src/daemon/handlers/config-telegram.ts +23 -14
- package/src/daemon/handlers/session-history.ts +1 -358
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/shared.ts +3 -17
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +39 -4
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -42
- package/src/daemon/server.ts +6 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-slash.ts +3 -5
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/email/providers/index.ts +2 -2
- package/src/instrument.ts +61 -1
- package/src/media/avatar-router.ts +1 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +25 -83
- package/src/memory/db-init.ts +32 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/invite-store.ts +19 -0
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/157-invite-contact-id.ts +104 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/provider.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +1 -1
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
- package/src/messaging/providers/whatsapp/adapter.ts +13 -9
- package/src/messaging/registry.ts +9 -5
- package/src/oauth/byo-connection.test.ts +40 -25
- package/src/oauth/connect-orchestrator.ts +4 -10
- package/src/oauth/connection-resolver.ts +20 -6
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +183 -31
- package/src/oauth/platform-connection.test.ts +1 -1
- package/src/oauth/provider-behaviors.ts +503 -4
- package/src/oauth/seed-providers.ts +214 -8
- package/src/oauth/token-persistence.ts +31 -16
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/channel-readiness-service.ts +48 -40
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/invite-redemption-service.ts +72 -12
- package/src/runtime/invite-service.ts +43 -0
- package/src/runtime/middleware/twilio-validation.ts +1 -1
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/btw-routes.ts +10 -5
- package/src/runtime/routes/conversation-routes.ts +56 -11
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/integrations/slack/channel.ts +2 -2
- package/src/runtime/routes/integrations/telegram.ts +2 -2
- package/src/runtime/routes/integrations/twilio.ts +17 -17
- package/src/runtime/routes/invite-routes.ts +29 -4
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/secret-routes.ts +17 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +3 -3
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +9 -4
- package/src/runtime/routes/workspace-utils.ts +8 -2
- package/src/schedule/integration-status.ts +26 -19
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/oauth2.ts +6 -7
- package/src/security/secure-keys.ts +44 -19
- package/src/security/token-manager.ts +46 -39
- package/src/services/vercel-deploy.ts +0 -24
- package/src/signals/confirm.ts +78 -0
- package/src/signals/mcp-reload.ts +18 -0
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +22 -7
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/network/script-proxy/session-manager.ts +8 -8
- package/src/tools/schedule/create.ts +10 -3
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/watcher/provider-types.ts +1 -1
- package/src/watcher/providers/github.ts +1 -1
- package/src/watcher/providers/gmail.ts +3 -3
- package/src/watcher/providers/google-calendar.ts +3 -3
- package/src/watcher/providers/linear.ts +1 -1
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
import { eq } from "drizzle-orm";
|
|
2
|
-
|
|
3
|
-
import { getConfig } from "../config/loader.js";
|
|
4
|
-
import {
|
|
5
|
-
createTimeout,
|
|
6
|
-
extractToolUse,
|
|
7
|
-
getConfiguredProvider,
|
|
8
|
-
userMessage,
|
|
9
|
-
} from "../providers/provider-send-message.js";
|
|
10
|
-
import { getLogger } from "../util/logger.js";
|
|
11
|
-
import { truncate } from "../util/truncate.js";
|
|
12
|
-
import { areStatementsCoherent } from "./conflict-intent.js";
|
|
13
|
-
import {
|
|
14
|
-
isConflictKindEligible,
|
|
15
|
-
isStatementConflictEligible,
|
|
16
|
-
} from "./conflict-policy.js";
|
|
17
|
-
import { createOrUpdatePendingConflict } from "./conflict-store.js";
|
|
18
|
-
import { getDb, getSqlite, rawAll } from "./db.js";
|
|
19
|
-
import { enqueueMemoryJob } from "./jobs-store.js";
|
|
20
|
-
import { memoryItems } from "./schema.js";
|
|
21
|
-
import { clampUnitInterval } from "./validation.js";
|
|
22
|
-
|
|
23
|
-
const log = getLogger("memory-contradiction-checker");
|
|
24
|
-
|
|
25
|
-
const CONTRADICTION_LLM_TIMEOUT_MS = 15_000;
|
|
26
|
-
|
|
27
|
-
type Relationship =
|
|
28
|
-
| "contradiction"
|
|
29
|
-
| "update"
|
|
30
|
-
| "complement"
|
|
31
|
-
| "ambiguous_contradiction";
|
|
32
|
-
|
|
33
|
-
interface ClassifyResult {
|
|
34
|
-
relationship: Relationship;
|
|
35
|
-
explanation: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const CONTRADICTION_SYSTEM_PROMPT = `You are a memory consistency checker. Given two statements about the same subject, determine their relationship.
|
|
39
|
-
|
|
40
|
-
Classify the relationship as one of:
|
|
41
|
-
- "contradiction": The new statement directly contradicts the old statement. They cannot both be true at the same time. Example: "User prefers dark mode" vs "User prefers light mode".
|
|
42
|
-
- "update": The new statement provides updated or more specific information that supersedes the old statement, but does not contradict it. Example: "User works at Acme" vs "User works at Acme as a senior engineer".
|
|
43
|
-
- "complement": The statements are compatible and provide different, non-overlapping information. Both can coexist. Example: "User likes TypeScript" vs "User prefers functional programming".
|
|
44
|
-
- "ambiguous_contradiction": The statements appear to conflict, but there is not enough confidence to invalidate either statement without user clarification.
|
|
45
|
-
|
|
46
|
-
Be conservative: only classify as "contradiction" when the statements are genuinely incompatible. Prefer "complement" when in doubt.`;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Check a newly extracted memory item against existing items for contradictions.
|
|
50
|
-
* Searches for existing active items with similar subject/statement, then uses
|
|
51
|
-
* LLM to classify the relationship and handle accordingly.
|
|
52
|
-
*/
|
|
53
|
-
export async function checkContradictions(newItemId: string): Promise<void> {
|
|
54
|
-
const db = getDb();
|
|
55
|
-
const newItem = db
|
|
56
|
-
.select()
|
|
57
|
-
.from(memoryItems)
|
|
58
|
-
.where(eq(memoryItems.id, newItemId))
|
|
59
|
-
.get();
|
|
60
|
-
|
|
61
|
-
if (!newItem || newItem.status !== "active") {
|
|
62
|
-
log.debug(
|
|
63
|
-
{ newItemId },
|
|
64
|
-
"Skipping contradiction check — item not found or not active",
|
|
65
|
-
);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Find existing active items with similar kind + subject
|
|
70
|
-
const candidates = findSimilarItems(newItem);
|
|
71
|
-
if (candidates.length === 0) {
|
|
72
|
-
log.debug(
|
|
73
|
-
{ newItemId, subject: newItem.subject },
|
|
74
|
-
"No similar items found for contradiction check",
|
|
75
|
-
);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const provider = getConfiguredProvider();
|
|
80
|
-
if (!provider) {
|
|
81
|
-
log.debug("Configured provider unavailable for contradiction checking");
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const config = getConfig();
|
|
86
|
-
|
|
87
|
-
if (!isConflictKindEligible(newItem.kind, config.memory.conflicts)) {
|
|
88
|
-
log.debug(
|
|
89
|
-
{ newItemId, kind: newItem.kind },
|
|
90
|
-
"Skipping contradiction check — kind not eligible for conflicts",
|
|
91
|
-
);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Skip if the new item's statement is transient/non-durable
|
|
96
|
-
if (
|
|
97
|
-
!isStatementConflictEligible(
|
|
98
|
-
newItem.kind,
|
|
99
|
-
newItem.statement,
|
|
100
|
-
config.memory.conflicts,
|
|
101
|
-
)
|
|
102
|
-
) {
|
|
103
|
-
log.debug(
|
|
104
|
-
{ newItemId, kind: newItem.kind },
|
|
105
|
-
"Skipping contradiction check — statement is transient or non-durable",
|
|
106
|
-
);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
for (const existing of candidates) {
|
|
111
|
-
// Skip candidate if its statement is transient/non-durable
|
|
112
|
-
if (
|
|
113
|
-
!isStatementConflictEligible(
|
|
114
|
-
existing.kind,
|
|
115
|
-
existing.statement,
|
|
116
|
-
config.memory.conflicts,
|
|
117
|
-
)
|
|
118
|
-
) {
|
|
119
|
-
log.debug(
|
|
120
|
-
{ existingId: existing.id },
|
|
121
|
-
"Skipping candidate — statement is transient or non-durable",
|
|
122
|
-
);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Skip pairs with zero topical overlap — they are not real contradictions
|
|
127
|
-
if (!areStatementsCoherent(existing.statement, newItem.statement)) {
|
|
128
|
-
log.debug(
|
|
129
|
-
{ existingId: existing.id, newId: newItem.id },
|
|
130
|
-
"Skipping candidate — zero statement overlap (incoherent pair)",
|
|
131
|
-
);
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
const result = await classifyRelationship(existing, newItem);
|
|
137
|
-
const mutated = handleRelationship(result, existing, newItem);
|
|
138
|
-
// Only stop when the new item itself was actually invalidated (update case)
|
|
139
|
-
// or gated (ambiguous_contradiction). For contradiction, the old item is
|
|
140
|
-
// invalidated but the new item remains active and should continue to be
|
|
141
|
-
// checked against remaining candidates. Skip the break when the transaction
|
|
142
|
-
// detected stale data and performed no mutation.
|
|
143
|
-
if (
|
|
144
|
-
mutated &&
|
|
145
|
-
(result.relationship === "update" ||
|
|
146
|
-
result.relationship === "ambiguous_contradiction")
|
|
147
|
-
)
|
|
148
|
-
break;
|
|
149
|
-
} catch (err) {
|
|
150
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
151
|
-
log.warn(
|
|
152
|
-
{ err: message, newItemId, existingId: existing.id },
|
|
153
|
-
"Contradiction classification failed for pair",
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
interface MemoryItemRow {
|
|
160
|
-
id: string;
|
|
161
|
-
kind: string;
|
|
162
|
-
subject: string;
|
|
163
|
-
statement: string;
|
|
164
|
-
status: string;
|
|
165
|
-
confidence: number;
|
|
166
|
-
importance: number | null;
|
|
167
|
-
scopeId: string;
|
|
168
|
-
lastSeenAt: number;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Find existing active items that are similar to the given item.
|
|
173
|
-
* Uses LIKE queries on subject and keyword overlap on statement.
|
|
174
|
-
*/
|
|
175
|
-
function findSimilarItems(item: MemoryItemRow): MemoryItemRow[] {
|
|
176
|
-
// Extract significant words from subject for LIKE matching
|
|
177
|
-
const subjectWords = item.subject
|
|
178
|
-
.toLowerCase()
|
|
179
|
-
.split(/[^a-z0-9_.-]+/g)
|
|
180
|
-
.filter((w) => w.length >= 3);
|
|
181
|
-
|
|
182
|
-
// Extract significant words from statement for additional matching
|
|
183
|
-
const statementWords = item.statement
|
|
184
|
-
.toLowerCase()
|
|
185
|
-
.split(/[^a-z0-9_.-]+/g)
|
|
186
|
-
.filter((w) => w.length >= 3);
|
|
187
|
-
|
|
188
|
-
if (subjectWords.length === 0 && statementWords.length === 0) return [];
|
|
189
|
-
|
|
190
|
-
// Build LIKE clauses for subject similarity
|
|
191
|
-
const likeClauses: string[] = [];
|
|
192
|
-
for (const word of subjectWords) {
|
|
193
|
-
const escaped = escapeSqlLike(word);
|
|
194
|
-
likeClauses.push(`LOWER(subject) LIKE '%${escaped}%'`);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Also match on statement keywords (top 5 longest words for specificity)
|
|
198
|
-
const topStatementWords = statementWords
|
|
199
|
-
.sort((a, b) => b.length - a.length)
|
|
200
|
-
.slice(0, 5);
|
|
201
|
-
for (const word of topStatementWords) {
|
|
202
|
-
const escaped = escapeSqlLike(word);
|
|
203
|
-
likeClauses.push(`LOWER(statement) LIKE '%${escaped}%'`);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (likeClauses.length === 0) return [];
|
|
207
|
-
|
|
208
|
-
const sqlQuery = `
|
|
209
|
-
SELECT id, kind, subject, statement, status, confidence, importance, scope_id, last_seen_at
|
|
210
|
-
FROM memory_items
|
|
211
|
-
WHERE status = 'active'
|
|
212
|
-
AND invalid_at IS NULL
|
|
213
|
-
AND kind = ?
|
|
214
|
-
AND id <> ?
|
|
215
|
-
AND scope_id = ?
|
|
216
|
-
AND (${likeClauses.join(" OR ")})
|
|
217
|
-
ORDER BY last_seen_at DESC
|
|
218
|
-
LIMIT 10
|
|
219
|
-
`;
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
interface SimilarItemRow {
|
|
223
|
-
id: string;
|
|
224
|
-
kind: string;
|
|
225
|
-
subject: string;
|
|
226
|
-
statement: string;
|
|
227
|
-
status: string;
|
|
228
|
-
confidence: number;
|
|
229
|
-
importance: number | null;
|
|
230
|
-
scope_id: string;
|
|
231
|
-
last_seen_at: number;
|
|
232
|
-
}
|
|
233
|
-
const rows = rawAll<SimilarItemRow>(
|
|
234
|
-
sqlQuery,
|
|
235
|
-
item.kind,
|
|
236
|
-
item.id,
|
|
237
|
-
item.scopeId,
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
return rows.map((row) => ({
|
|
241
|
-
id: row.id,
|
|
242
|
-
kind: row.kind,
|
|
243
|
-
subject: row.subject,
|
|
244
|
-
statement: row.statement,
|
|
245
|
-
status: row.status,
|
|
246
|
-
confidence: row.confidence,
|
|
247
|
-
importance: row.importance,
|
|
248
|
-
scopeId: row.scope_id,
|
|
249
|
-
lastSeenAt: row.last_seen_at,
|
|
250
|
-
}));
|
|
251
|
-
} catch (err) {
|
|
252
|
-
log.warn({ err }, "Failed to search for similar memory items");
|
|
253
|
-
return [];
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Use LLM to classify the relationship between two memory items.
|
|
259
|
-
*/
|
|
260
|
-
async function classifyRelationship(
|
|
261
|
-
existingItem: MemoryItemRow,
|
|
262
|
-
newItem: MemoryItemRow,
|
|
263
|
-
): Promise<ClassifyResult> {
|
|
264
|
-
const provider = getConfiguredProvider()!;
|
|
265
|
-
|
|
266
|
-
const userContent = [
|
|
267
|
-
`Subject: ${newItem.subject}`,
|
|
268
|
-
"",
|
|
269
|
-
`Old statement: ${existingItem.statement}`,
|
|
270
|
-
`New statement: ${newItem.statement}`,
|
|
271
|
-
].join("\n");
|
|
272
|
-
|
|
273
|
-
const { signal, cleanup } = createTimeout(CONTRADICTION_LLM_TIMEOUT_MS);
|
|
274
|
-
try {
|
|
275
|
-
const response = await provider.sendMessage(
|
|
276
|
-
[userMessage(userContent)],
|
|
277
|
-
[
|
|
278
|
-
{
|
|
279
|
-
name: "classify_relationship",
|
|
280
|
-
description:
|
|
281
|
-
"Classify the relationship between two memory statements",
|
|
282
|
-
input_schema: {
|
|
283
|
-
type: "object" as const,
|
|
284
|
-
properties: {
|
|
285
|
-
relationship: {
|
|
286
|
-
type: "string",
|
|
287
|
-
enum: [
|
|
288
|
-
"contradiction",
|
|
289
|
-
"update",
|
|
290
|
-
"complement",
|
|
291
|
-
"ambiguous_contradiction",
|
|
292
|
-
],
|
|
293
|
-
description:
|
|
294
|
-
"The relationship between the old and new statements",
|
|
295
|
-
},
|
|
296
|
-
explanation: {
|
|
297
|
-
type: "string",
|
|
298
|
-
description:
|
|
299
|
-
"Brief explanation of why this relationship was chosen",
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
required: ["relationship", "explanation"],
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
],
|
|
306
|
-
CONTRADICTION_SYSTEM_PROMPT,
|
|
307
|
-
{
|
|
308
|
-
config: {
|
|
309
|
-
modelIntent: "latency-optimized",
|
|
310
|
-
max_tokens: 256,
|
|
311
|
-
tool_choice: { type: "tool" as const, name: "classify_relationship" },
|
|
312
|
-
},
|
|
313
|
-
signal,
|
|
314
|
-
},
|
|
315
|
-
);
|
|
316
|
-
cleanup();
|
|
317
|
-
|
|
318
|
-
const toolBlock = extractToolUse(response);
|
|
319
|
-
if (!toolBlock) {
|
|
320
|
-
throw new Error("No tool_use block in contradiction check response");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const input = toolBlock.input as {
|
|
324
|
-
relationship?: string;
|
|
325
|
-
explanation?: string;
|
|
326
|
-
};
|
|
327
|
-
const relationship = input.relationship as Relationship;
|
|
328
|
-
if (
|
|
329
|
-
![
|
|
330
|
-
"contradiction",
|
|
331
|
-
"update",
|
|
332
|
-
"complement",
|
|
333
|
-
"ambiguous_contradiction",
|
|
334
|
-
].includes(relationship)
|
|
335
|
-
) {
|
|
336
|
-
throw new Error(`Invalid relationship type: ${relationship}`);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return {
|
|
340
|
-
relationship,
|
|
341
|
-
explanation: truncate(String(input.explanation ?? ""), 500, ""),
|
|
342
|
-
};
|
|
343
|
-
} finally {
|
|
344
|
-
cleanup();
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Handle the classified relationship between an existing and new memory item.
|
|
350
|
-
*
|
|
351
|
-
* Wrapped in a SQLite transaction so that multi-row mutations (e.g. invalidating
|
|
352
|
-
* the old item AND setting validFrom on the new one) are atomic. The transaction
|
|
353
|
-
* also re-verifies both items are still active before mutating, preventing a
|
|
354
|
-
* TOCTOU race when multiple workers process contradictions concurrently.
|
|
355
|
-
*/
|
|
356
|
-
function handleRelationship(
|
|
357
|
-
result: ClassifyResult,
|
|
358
|
-
existingItem: MemoryItemRow,
|
|
359
|
-
newItem: MemoryItemRow,
|
|
360
|
-
): boolean {
|
|
361
|
-
if (result.relationship === "complement") {
|
|
362
|
-
log.debug(
|
|
363
|
-
{
|
|
364
|
-
existingId: existingItem.id,
|
|
365
|
-
newId: newItem.id,
|
|
366
|
-
explanation: result.explanation,
|
|
367
|
-
},
|
|
368
|
-
"Complement detected — keeping both items",
|
|
369
|
-
);
|
|
370
|
-
return false;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return getSqlite()
|
|
374
|
-
.transaction(() => {
|
|
375
|
-
const db = getDb();
|
|
376
|
-
const now = Date.now();
|
|
377
|
-
|
|
378
|
-
// Re-check both items inside the transaction to guard against concurrent mutations
|
|
379
|
-
const freshExisting = db
|
|
380
|
-
.select()
|
|
381
|
-
.from(memoryItems)
|
|
382
|
-
.where(eq(memoryItems.id, existingItem.id))
|
|
383
|
-
.get();
|
|
384
|
-
const freshNew = db
|
|
385
|
-
.select()
|
|
386
|
-
.from(memoryItems)
|
|
387
|
-
.where(eq(memoryItems.id, newItem.id))
|
|
388
|
-
.get();
|
|
389
|
-
|
|
390
|
-
if (
|
|
391
|
-
!freshExisting ||
|
|
392
|
-
freshExisting.status !== "active" ||
|
|
393
|
-
freshExisting.invalidAt != null
|
|
394
|
-
) {
|
|
395
|
-
log.debug(
|
|
396
|
-
{ existingId: existingItem.id },
|
|
397
|
-
"Existing item no longer active — skipping",
|
|
398
|
-
);
|
|
399
|
-
return false;
|
|
400
|
-
}
|
|
401
|
-
if (
|
|
402
|
-
!freshNew ||
|
|
403
|
-
(freshNew.status !== "active" &&
|
|
404
|
-
result.relationship !== "ambiguous_contradiction") ||
|
|
405
|
-
freshNew.invalidAt != null
|
|
406
|
-
) {
|
|
407
|
-
log.debug(
|
|
408
|
-
{ newId: newItem.id },
|
|
409
|
-
"New item no longer active — skipping",
|
|
410
|
-
);
|
|
411
|
-
return false;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
switch (result.relationship) {
|
|
415
|
-
case "contradiction": {
|
|
416
|
-
log.info(
|
|
417
|
-
{
|
|
418
|
-
existingId: existingItem.id,
|
|
419
|
-
newId: newItem.id,
|
|
420
|
-
explanation: result.explanation,
|
|
421
|
-
},
|
|
422
|
-
"Contradiction detected — invalidating old item",
|
|
423
|
-
);
|
|
424
|
-
db.update(memoryItems)
|
|
425
|
-
.set({ invalidAt: now })
|
|
426
|
-
.where(eq(memoryItems.id, existingItem.id))
|
|
427
|
-
.run();
|
|
428
|
-
db.update(memoryItems)
|
|
429
|
-
.set({ validFrom: now })
|
|
430
|
-
.where(eq(memoryItems.id, newItem.id))
|
|
431
|
-
.run();
|
|
432
|
-
return true;
|
|
433
|
-
}
|
|
434
|
-
case "update": {
|
|
435
|
-
log.debug(
|
|
436
|
-
{
|
|
437
|
-
existingId: existingItem.id,
|
|
438
|
-
newId: newItem.id,
|
|
439
|
-
explanation: result.explanation,
|
|
440
|
-
},
|
|
441
|
-
"Update detected — merging into existing item",
|
|
442
|
-
);
|
|
443
|
-
db.update(memoryItems)
|
|
444
|
-
.set({
|
|
445
|
-
statement: newItem.statement,
|
|
446
|
-
lastSeenAt: Math.max(
|
|
447
|
-
freshExisting.lastSeenAt,
|
|
448
|
-
freshNew!.lastSeenAt,
|
|
449
|
-
),
|
|
450
|
-
confidence: clampUnitInterval(
|
|
451
|
-
Math.max(freshExisting.confidence, freshNew!.confidence),
|
|
452
|
-
),
|
|
453
|
-
})
|
|
454
|
-
.where(eq(memoryItems.id, existingItem.id))
|
|
455
|
-
.run();
|
|
456
|
-
enqueueMemoryJob("embed_item", { itemId: existingItem.id });
|
|
457
|
-
db.update(memoryItems)
|
|
458
|
-
.set({ invalidAt: now })
|
|
459
|
-
.where(eq(memoryItems.id, newItem.id))
|
|
460
|
-
.run();
|
|
461
|
-
return true;
|
|
462
|
-
}
|
|
463
|
-
case "ambiguous_contradiction": {
|
|
464
|
-
log.info(
|
|
465
|
-
{
|
|
466
|
-
existingId: existingItem.id,
|
|
467
|
-
newId: newItem.id,
|
|
468
|
-
explanation: result.explanation,
|
|
469
|
-
},
|
|
470
|
-
"Ambiguous contradiction detected — gating candidate pending clarification",
|
|
471
|
-
);
|
|
472
|
-
db.update(memoryItems)
|
|
473
|
-
.set({ status: "pending_clarification" })
|
|
474
|
-
.where(eq(memoryItems.id, newItem.id))
|
|
475
|
-
.run();
|
|
476
|
-
createOrUpdatePendingConflict({
|
|
477
|
-
scopeId: newItem.scopeId,
|
|
478
|
-
existingItemId: existingItem.id,
|
|
479
|
-
candidateItemId: newItem.id,
|
|
480
|
-
relationship: "ambiguous_contradiction",
|
|
481
|
-
clarificationQuestion: buildClarificationQuestion(
|
|
482
|
-
existingItem.statement,
|
|
483
|
-
newItem.statement,
|
|
484
|
-
),
|
|
485
|
-
});
|
|
486
|
-
return true;
|
|
487
|
-
}
|
|
488
|
-
default:
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
})
|
|
492
|
-
.immediate();
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function escapeSqlLike(s: string): string {
|
|
496
|
-
return s.replace(/'/g, "''").replace(/%/g, "").replace(/_/g, "");
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function buildClarificationQuestion(
|
|
500
|
-
existingStatement: string,
|
|
501
|
-
candidateStatement: string,
|
|
502
|
-
): string {
|
|
503
|
-
const normalize = (input: string): string =>
|
|
504
|
-
truncate(input.replace(/\s+/g, " ").trim(), 180, "");
|
|
505
|
-
return `Pending conflict: "${normalize(
|
|
506
|
-
existingStatement,
|
|
507
|
-
)}" vs "${normalize(candidateStatement)}"`;
|
|
508
|
-
}
|