cogmemory 0.0.1-beta.2

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/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "cogmemory",
3
+ "version": "0.0.1-beta.2",
4
+ "description": "Cognitive Memory for agents: a typed client for the service, plus the deterministic cognitive layer to run in-process. No model in the retrieval path.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/cognitive-memory.cjs",
8
+ "module": "./dist/cognitive-memory.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/cognitive-memory.js",
14
+ "require": "./dist/cognitive-memory.cjs"
15
+ },
16
+ "./arbiter": {
17
+ "types": "./dist/arbiter.d.ts",
18
+ "import": "./dist/arbiter.js",
19
+ "require": "./dist/arbiter.cjs"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src",
26
+ "README.md"
27
+ ],
28
+ "sideEffects": false,
29
+ "publishConfig": {
30
+ "access": "public",
31
+ "provenance": false
32
+ },
33
+ "author": "AstraCollab",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/astracollab/astracollab.git",
37
+ "directory": "packages/cognitive-memory"
38
+ },
39
+ "keywords": [
40
+ "memory",
41
+ "llm",
42
+ "agent",
43
+ "context",
44
+ "cogmem",
45
+ "cognitive-memory",
46
+ "sdk",
47
+ "ai"
48
+ ],
49
+ "scripts": {
50
+ "build": "vite build",
51
+ "dev": "vite build --watch",
52
+ "test": "vitest run",
53
+ "test:watch": "vitest",
54
+ "lint": "tsc --noEmit",
55
+ "check-types": "tsc --noEmit"
56
+ },
57
+ "peerDependencies": {
58
+ "ai": "^7.0.0",
59
+ "ofetch": "^1.4.1",
60
+ "zod": "^3.25.76 || ^4.1.8"
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^22.19.0",
64
+ "ai": "^7.0.127",
65
+ "ofetch": "^1.4.1",
66
+ "typescript": "^5.8.3",
67
+ "vite": "^5.4.19",
68
+ "vite-plugin-dts": "^4.5.4",
69
+ "vitest": "^3.2.4",
70
+ "zod": "^4.3.6"
71
+ },
72
+ "engines": {
73
+ "node": ">=22.0.0"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "ai": {
77
+ "optional": true
78
+ },
79
+ "zod": {
80
+ "optional": true
81
+ }
82
+ }
83
+ }
package/src/arbiter.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The model-backed arbiter, as a separate entry point.
3
+ *
4
+ * It needs `ai` and `zod`, and nothing else in this package does. Keeping it at
5
+ * `@astracollab/cogmem/arbiter` means a consumer who only wants the client or
6
+ * the deterministic core is not made to install them, and does not get a peer
7
+ * warning for a dependency they will never use.
8
+ *
9
+ * The arbiter is optional in the design too. Without it the engine promotes by
10
+ * content-word overlap, which is what `CognitiveMemory` does by default — so a
11
+ * deployment with no model configured still has working memory.
12
+ */
13
+ export { createModelArbiter, type CreateModelArbiterOptions } from "./cognitive/arbiter"
14
+ export type { ArbiterEvaluationResult, ArbiterFn } from "./cognitive/types"
package/src/client.ts ADDED
@@ -0,0 +1,105 @@
1
+ import { FetchError, ofetch, type $Fetch } from "ofetch"
2
+
3
+ import { CognitiveMemoryError } from "./errors"
4
+ import type { CognitiveMemoryConfig, CognitiveMemoryErrorBody } from "./types"
5
+
6
+ /**
7
+ * Configuration, as a factory rather than a constructor.
8
+ *
9
+ * A constructor with fifteen options is a dumping ground: configuration and
10
+ * instantiation get tangled, tests have to construct a whole class to check one
11
+ * header, and "the client with a different timeout" becomes a subclass. A factory
12
+ * is a plain function — no `new`, no `this`, composable, trivially mockable.
13
+ *
14
+ * The real work happens in the interceptors. Authentication, error translation
15
+ * and debug logging are configured once here instead of being repeated in each
16
+ * of the twenty-odd methods below, which is the difference between changing how
17
+ * auth works in one place and in twenty.
18
+ */
19
+ export function createHttpClient(config: CognitiveMemoryConfig): $Fetch {
20
+ const {
21
+ apiKey,
22
+ baseUrl = defaultBaseUrl(),
23
+ timeout = 30_000,
24
+ retry = 2,
25
+ debug = false,
26
+ headers: extraHeaders = {}
27
+ } = config
28
+
29
+ if (typeof apiKey !== "string" || apiKey.trim() === "") {
30
+ throw new Error(
31
+ "cognitive-memory: apiKey is required. Issue one in the dashboard — the secret is shown only once."
32
+ )
33
+ }
34
+
35
+ const inner = ofetch.create({
36
+ baseURL: `${baseUrl.replace(/\/$/, "")}/api/v1`,
37
+ timeout,
38
+ // Retries are for transport hiccups, not for a 400. ofetch already limits
39
+ // this to idempotent-looking methods, and the service is idempotent for the
40
+ // reads that matter.
41
+ retry,
42
+ headers: {
43
+ accept: "application/json",
44
+ ...extraHeaders
45
+ },
46
+
47
+ onRequest({ options, request }) {
48
+ // `options.headers` is a Headers instance by this point, not a plain
49
+ // object — assigning to it silently drops every header.
50
+ options.headers.set("authorization", `Bearer ${apiKey}`)
51
+ options.headers.set("content-type", "application/json")
52
+ if (debug) {
53
+ // eslint-disable-next-line no-console
54
+ console.log("[cognitive-memory]", options.method ?? "GET", `${options.baseURL ?? ""}${request ?? ""}`)
55
+ }
56
+ },
57
+
58
+ onResponse({ response, options, request }) {
59
+ if (debug) {
60
+ // eslint-disable-next-line no-console
61
+ console.log("[cognitive-memory]", response.status, options.method ?? "GET", request ?? "")
62
+ }
63
+ },
64
+
65
+ })
66
+
67
+ /**
68
+ * Error translation happens out here rather than in `onResponseError`.
69
+ *
70
+ * ofetch 1.4 replaces anything thrown from that hook with its own `FetchError`
71
+ * before it reaches the caller, so a hook that throws is a hook whose error
72
+ * silently disappears — the caller gets an exception with no status, no scope
73
+ * and no issues. Catching here is the only place the service's envelope can be
74
+ * read, and it leaves genuine transport failures (no response) untouched.
75
+ */
76
+ // The parameter types are taken from `$Fetch` itself rather than restated, so
77
+ // this wrapper cannot drift out of step with the client it wraps.
78
+ const wrap = async (request: unknown, options?: unknown): Promise<unknown> => {
79
+ try {
80
+ return await (inner as (r: unknown, o?: unknown) => Promise<unknown>)(request, options)
81
+ } catch (error) {
82
+ if (!(error instanceof FetchError) || !error.response) throw error
83
+ const response = error.response
84
+ // The service always answers with this envelope; a proxy or a crash in
85
+ // front of it might not, so the body is treated as untrusted.
86
+ const body = (response._data ?? undefined) as CognitiveMemoryErrorBody | undefined
87
+ throw new CognitiveMemoryError(body?.message ?? `cognitive-memory: request failed with ${response.status}`, {
88
+ status: response.status,
89
+ code: body?.error ?? "UnknownError",
90
+ ...(body === undefined ? {} : { body }),
91
+ ...(body?.requiredScope === undefined ? {} : { requiredScope: body.requiredScope }),
92
+ ...(body?.issues === undefined ? {} : { issues: body.issues })
93
+ })
94
+ }
95
+ }
96
+
97
+ return wrap as unknown as $Fetch
98
+ }
99
+
100
+ function defaultBaseUrl(): string {
101
+ if (typeof window !== "undefined") return window.location.origin
102
+ return "http://localhost:3000"
103
+ }
104
+
105
+ export type HttpClient = ReturnType<typeof createHttpClient>
package/src/cogmem.ts ADDED
@@ -0,0 +1,74 @@
1
+ import { createHttpClient, type HttpClient } from "./client"
2
+ import { ContextResource } from "./resources/context"
3
+ import { MemoriesResource } from "./resources/memories"
4
+ import { RecallResource } from "./resources/recall"
5
+ import { SelfModelResource } from "./resources/self-model"
6
+ import { StatsResource } from "./resources/stats"
7
+ import { TensionsResource } from "./resources/tensions"
8
+ import { TurnsResource } from "./resources/turns"
9
+ import type { Health, CognitiveMemoryConfig } from "./types"
10
+ import { fetchHealth } from "./resources/stats"
11
+
12
+ /**
13
+ * The client.
14
+ *
15
+ * Named `Cogmem` rather than `CognitiveMemory` because that name belongs to the
16
+ * in-process engine, and has for as long as this layer existed. Two exports
17
+ * cannot share a name, and the engine's is the older public API: quietly
18
+ * redefining it to mean an HTTP client would break every consumer of it.
19
+ *
20
+ * Resource classes rather than a flat list of functions, because
21
+ * `memory.memories.` in an editor shows every operation on memories and nothing
22
+ * else. The methods stay one-to-one with endpoints, so the class never becomes a
23
+ * second, divergent copy of the service.
24
+ *
25
+ * `readonly` on the resources is not decoration: it stops a caller reassigning
26
+ * `memory.memories` and wondering why the other half of the client stopped
27
+ * seeing the change.
28
+ */
29
+ export class Cogmem {
30
+ private readonly client: HttpClient
31
+ readonly baseUrl: string
32
+
33
+ readonly memories: MemoriesResource
34
+ readonly context: ContextResource
35
+ readonly recall: RecallResource
36
+ readonly turns: TurnsResource
37
+ readonly tensions: TensionsResource
38
+ readonly selfModel: SelfModelResource
39
+ readonly stats: StatsResource
40
+
41
+ constructor(config: CognitiveMemoryConfig) {
42
+ this.baseUrl = (config.baseUrl ?? "http://localhost:3000").replace(/\/$/, "")
43
+ this.client = createHttpClient(config)
44
+ this.memories = new MemoriesResource(this.client)
45
+ this.context = new ContextResource(this.client)
46
+ this.recall = new RecallResource(this.client)
47
+ this.turns = new TurnsResource(this.client)
48
+ this.tensions = new TensionsResource(this.client)
49
+ this.selfModel = new SelfModelResource(this.client)
50
+ this.stats = new StatsResource(this.client)
51
+ }
52
+
53
+ /**
54
+ * Liveness and limits, without spending a request on the configured key.
55
+ *
56
+ * Useful in a readiness probe: if `extractor` is `rules-only` the service is up
57
+ * but is not doing model-backed extraction, and `problems` is non-empty when a
58
+ * setting is present but unusable.
59
+ */
60
+ health(): Promise<Health> {
61
+ return fetchHealth(this.baseUrl)
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Both construction styles, deliberately.
67
+ *
68
+ * Some people prefer `new Cogmem(...)` and some prefer `createClient(...)`; there
69
+ * is no reason to make anyone rename. The factory is also the easier one to mock
70
+ * in tests, which is the whole reason `createHttpClient` is a function.
71
+ */
72
+ export function createClient(config: CognitiveMemoryConfig): Cogmem {
73
+ return new Cogmem(config)
74
+ }
@@ -0,0 +1,105 @@
1
+ import { generateObject, type LanguageModel } from "ai";
2
+ import { z } from "zod";
3
+ import type { ArbiterEvaluationResult, ArbiterFn } from "./types.js";
4
+
5
+ const ArbiterSchema = z.object({
6
+ promotions: z.array(
7
+ z.object({
8
+ memoryId: z.string(),
9
+ targetTier: z.literal("L1"),
10
+ signalType: z.enum(["anticipatory", "tension", "proprioceptive", "recency"]),
11
+ urgency: z.number().min(0).max(1),
12
+ })
13
+ ),
14
+ demotions: z.array(
15
+ z.object({
16
+ memoryId: z.string(),
17
+ targetTier: z.literal("L2"),
18
+ reason: z.string(),
19
+ })
20
+ ),
21
+ pins: z.array(
22
+ z.object({
23
+ memoryId: z.string(),
24
+ targetTier: z.literal("L0"),
25
+ reason: z.string(),
26
+ })
27
+ ),
28
+ detectedTensions: z.array(
29
+ z.object({
30
+ claimA: z.string().describe("First conflicting claim or statement"),
31
+ claimB: z.string().describe("Contradicting statement from code or user"),
32
+ impact: z.enum(["low", "medium", "critical"]),
33
+ actionableQuestion: z.string().describe("Specific clarifying question the agent should resolve"),
34
+ })
35
+ ),
36
+ trajectoryPrediction: z
37
+ .object({
38
+ predictedDomains: z.array(z.string()),
39
+ predictedFiles: z.array(z.string()),
40
+ prefetchMemoryIds: z.array(z.string()),
41
+ confidence: z.number().min(0).max(1),
42
+ })
43
+ .optional(),
44
+ selfModelUpdate: z
45
+ .object({
46
+ domain: z.string(),
47
+ success: z.boolean().optional(),
48
+ failurePatternObserved: z.string().optional(),
49
+ })
50
+ .optional(),
51
+ });
52
+
53
+ export interface CreateModelArbiterOptions {
54
+ /** Fast decision model (e.g., Gemini 1.5/2.5 Flash, Claude 3.5 Haiku, or GPT-4o-mini) */
55
+ model: LanguageModel;
56
+ /** Custom system prompt override if desired */
57
+ systemPrompt?: string;
58
+ }
59
+
60
+ /**
61
+ * Creates an Arbiter function powered by an AI SDK model with structured output.
62
+ */
63
+ export const createModelArbiter = (options: CreateModelArbiterOptions): ArbiterFn => {
64
+ return async ({ turnText, assistantReply, l0Prompt, l1Summaries, candidates }): Promise<ArbiterEvaluationResult> => {
65
+ try {
66
+ const prompt = `You are the Cache Arbiter for an AI coding agent.
67
+ Evaluate the latest turn and candidate memories to decide what should be HOT (L1), COLD (L2), or PINNED (L0).
68
+
69
+ CURRENT TURN:
70
+ User: "${turnText}"
71
+ Assistant: "${assistantReply}"
72
+
73
+ L0 CORE STATE:
74
+ ${l0Prompt || "(none)"}
75
+
76
+ CURRENT HOT L1 SUMMARIES:
77
+ ${JSON.stringify(l1Summaries, null, 2)}
78
+
79
+ WARM L2 CANDIDATE MEMORIES:
80
+ ${JSON.stringify(candidates, null, 2)}
81
+
82
+ Analyze:
83
+ 1. What will the agent likely need in the NEXT 1-2 turns? (Anticipatory -> promote candidate to L1)
84
+ 2. Are any active L1 items no longer relevant? (Demote L1 to L2)
85
+ 3. Did the conversation reveal a contradiction between user claims and known facts? (Flag as detectedTension)
86
+ 4. Did the agent succeed or struggle in a specific domain? (Update self-model)`;
87
+
88
+ const result = await generateObject({
89
+ model: options.model,
90
+ schema: ArbiterSchema,
91
+ prompt,
92
+ });
93
+
94
+ return result.object;
95
+ } catch {
96
+ // Return safe empty fallback on model failure so the cache remains stable
97
+ return {
98
+ promotions: [],
99
+ demotions: [],
100
+ pins: [],
101
+ detectedTensions: [],
102
+ };
103
+ }
104
+ };
105
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Fast Heuristic Gate
3
+ *
4
+ * Synchronous, sub-5ms gate running on the critical path before model invocation.
5
+ * Detects explicit user corrections/contradictions and significant domain pivots
6
+ * without triggering an LLM call.
7
+ */
8
+
9
+ export interface FastGateResult {
10
+ action: "proceed" | "inject_caution";
11
+ cautionNote?: string;
12
+ detectedDomains?: string[];
13
+ }
14
+
15
+ const CONTRADICTION_PATTERNS = [
16
+ /actually,?\s+(?:we|i)\s+(?:switched|changed|moved|migrated|replaced|use|want|have)/i,
17
+ /that(?:'s|\s+is)\s+(?:wrong|incorrect|outdated|false|not\s+right|no\s+longer)/i,
18
+ /no,?\s+(?:it(?:'s|\s+is)|we\s+use|we\s+switched|it\s+should\s+be|don(?:'t|\s+not)\s+use)/i,
19
+ /stop\s+using\s+/i,
20
+ /instead\s+of\s+/i,
21
+ /disregard\s+(?:prior|previous|what\s+i\s+said)/i,
22
+ ];
23
+
24
+ const DOMAIN_KEYWORDS: Record<string, RegExp[]> = {
25
+ auth: [/auth/i, /login/i, /session/i, /jwt/i, /oauth/i, /token/i, /password/i, /permission/i],
26
+ database: [/postgres/i, /sqlite/i, /sql/i, /schema/i, /migration/i, /prisma/i, /drizzle/i, /mongo/i, /redis/i],
27
+ frontend: [/css/i, /tailwind/i, /component/i, /jsx/i, /tsx/i, /layout/i, /ui/i, /render/i, /html/i],
28
+ build: [/vite/i, /webpack/i, /turbo/i, /tsconfig/i, /pnpm/i, /package\.json/i, /bundle/i],
29
+ api: [/rest/i, /graphql/i, /endpoint/i, /route/i, /fetch/i, /request/i, /response/i],
30
+ git: [/branch/i, /commit/i, /merge/i, /rebase/i, /worktree/i, /conflict/i],
31
+ };
32
+
33
+ export const runFastGate = (userMessage: string): FastGateResult => {
34
+ if (!userMessage || userMessage.trim().length === 0) {
35
+ return { action: "proceed" };
36
+ }
37
+
38
+ // 1. Check for explicit contradiction signals
39
+ for (const pattern of CONTRADICTION_PATTERNS) {
40
+ if (pattern.test(userMessage)) {
41
+ return {
42
+ action: "inject_caution",
43
+ cautionNote: "User appears to be explicitly correcting a prior premise or configuration. Verify current facts before proceeding with assumptions.",
44
+ detectedDomains: extractDomains(userMessage),
45
+ };
46
+ }
47
+ }
48
+
49
+ return {
50
+ action: "proceed",
51
+ detectedDomains: extractDomains(userMessage),
52
+ };
53
+ };
54
+
55
+ export const extractDomains = (text: string): string[] => {
56
+ const matched: string[] = [];
57
+ for (const [domain, patterns] of Object.entries(DOMAIN_KEYWORDS)) {
58
+ if (patterns.some((p) => p.test(text))) {
59
+ matched.push(domain);
60
+ }
61
+ }
62
+ return matched;
63
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The cognitive layer.
3
+ *
4
+ * Everything that decides *what* to remember, *what* to put in front of a model,
5
+ * and *whether* a new statement says something new — with no database, no
6
+ * network, and no model in the retrieval path.
7
+ *
8
+ * That last part is the design constraint everything else follows from. A memory
9
+ * layer whose recall quality moves with provider availability is not a storage
10
+ * layer, it is a demo, so ranking, merge safety and index budgeting are all
11
+ * deterministic functions of the text in front of them.
12
+ *
13
+ * `CognitiveMemory` is the in-process implementation: four Maps, snapshots to
14
+ * and from JSON, no I/O beyond what you give it. The service is the same
15
+ * decisions with the tiers in a database, which is why the primitives below are
16
+ * shared rather than reimplemented.
17
+ */
18
+
19
+ export { CognitiveMemory } from "./memory.js"
20
+
21
+ export { runFastGate, extractDomains, type FastGateResult } from "./fast-gate.js"
22
+
23
+ export {
24
+ extractDeterministic,
25
+ type DeterministicMemory
26
+ } from "./rules.js"
27
+
28
+ export {
29
+ CANDIDATE_FLOOR,
30
+ COLLAPSE_THRESHOLD,
31
+ MAX_CANDIDATES,
32
+ PROMOTE_THRESHOLD,
33
+ distinctiveTokens,
34
+ estimateTokens,
35
+ extractIdentifiers,
36
+ gistOf,
37
+ isInteractionScoped,
38
+ isLossyRewrite,
39
+ normalise,
40
+ overlapScore,
41
+ relevanceTokens,
42
+ similarity
43
+ } from "./relevance.js"
44
+
45
+ export type {
46
+ MemoryTier,
47
+ TensionStatus,
48
+ TensionImpact,
49
+ KnowledgeTension,
50
+ DomainCapability,
51
+ ProprioceptiveSelfModel,
52
+ MemoryMetadata,
53
+ MemoryItem,
54
+ TrajectoryPrediction,
55
+ MemoryInclusionReason,
56
+ MemoryInclusionReason as InclusionReason,
57
+ MemoryInjectionEntry,
58
+ MemoryInjectionReport,
59
+ MemoryReconciliation,
60
+ ArbiterEvaluationResult,
61
+ ArbiterFn,
62
+ CognitiveMemoryOptions,
63
+ CognitiveMemoryStateSnapshot
64
+ } from "./types.js"