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/README.md +139 -0
- package/dist/arbiter.cjs +22 -0
- package/dist/arbiter.cjs.map +1 -0
- package/dist/arbiter.d.ts +72 -0
- package/dist/arbiter.js +85 -0
- package/dist/arbiter.js.map +1 -0
- package/dist/cognitive-memory.cjs +15 -0
- package/dist/cognitive-memory.cjs.map +1 -0
- package/dist/cognitive-memory.js +1068 -0
- package/dist/cognitive-memory.js.map +1 -0
- package/dist/index.d.ts +1086 -0
- package/package.json +83 -0
- package/src/arbiter.ts +14 -0
- package/src/client.ts +105 -0
- package/src/cogmem.ts +74 -0
- package/src/cognitive/arbiter.ts +105 -0
- package/src/cognitive/fast-gate.ts +63 -0
- package/src/cognitive/index.ts +64 -0
- package/src/cognitive/memory.ts +878 -0
- package/src/cognitive/relevance.ts +199 -0
- package/src/cognitive/rules.ts +127 -0
- package/src/cognitive/types.ts +232 -0
- package/src/errors.ts +85 -0
- package/src/helpers/turn.ts +172 -0
- package/src/index.ts +134 -0
- package/src/resources/context.ts +17 -0
- package/src/resources/memories.ts +60 -0
- package/src/resources/recall.ts +17 -0
- package/src/resources/self-model.ts +21 -0
- package/src/resources/stats.ts +27 -0
- package/src/resources/tensions.ts +36 -0
- package/src/resources/turns.ts +16 -0
- package/src/types.ts +246 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { Cogmem } from "../cogmem"
|
|
2
|
+
import type { ContextReport, LearnResult, Memory } from "../types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Workflows.
|
|
6
|
+
*
|
|
7
|
+
* Resource methods are one call each. These compose them into the sequences
|
|
8
|
+
* people actually get wrong when they write the loop themselves — and the
|
|
9
|
+
* mistakes are mundane: forgetting to learn from a turn, learning from a question,
|
|
10
|
+
* letting a memory failure take down the turn it was meant to help, or never
|
|
11
|
+
* telling the service how a task went so the guardrails never fire.
|
|
12
|
+
*
|
|
13
|
+
* Each helper takes the client explicitly rather than closing over one, so it
|
|
14
|
+
* works with a second client without a module-level singleton.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface TurnOptions {
|
|
18
|
+
/** Groups everything learned in one conversation. */
|
|
19
|
+
sessionId?: string
|
|
20
|
+
/**
|
|
21
|
+
* What the turn turned out to be about, e.g. `"database"`.
|
|
22
|
+
*
|
|
23
|
+
* Recorded against the self-model, which is what eventually produces a
|
|
24
|
+
* guardrail for a domain the agent keeps failing in. Cheap to send and the
|
|
25
|
+
* difference between a self-model that learns and one that sits at its priors.
|
|
26
|
+
*/
|
|
27
|
+
domain?: string
|
|
28
|
+
/** What went wrong, when the turn was a failure. */
|
|
29
|
+
failurePattern?: string
|
|
30
|
+
/** What worked, when the turn was a success. */
|
|
31
|
+
strategy?: string
|
|
32
|
+
/** Set false to skip learning — a retrieval-only turn, say. */
|
|
33
|
+
learn?: boolean
|
|
34
|
+
onLearn?: (result: LearnResult) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RunTurnResult {
|
|
38
|
+
context: ContextReport
|
|
39
|
+
learning: LearnResult | null
|
|
40
|
+
/**
|
|
41
|
+
* Why learning did not happen, if it did not.
|
|
42
|
+
*
|
|
43
|
+
* Present because a silent skip looks identical to a working one until the
|
|
44
|
+
* thing you taught it never comes back.
|
|
45
|
+
*/
|
|
46
|
+
learningSkipped?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* One turn of an agent loop, with memory handled correctly.
|
|
51
|
+
*
|
|
52
|
+
* The order is the point:
|
|
53
|
+
*
|
|
54
|
+
* 1. build context *before* the model runs, keyed on the message being answered;
|
|
55
|
+
* 2. run the model;
|
|
56
|
+
* 3. learn from the finished exchange;
|
|
57
|
+
* 4. record how the domain went, so the self-model moves.
|
|
58
|
+
*
|
|
59
|
+
* Steps 3 and 4 never fail the turn. Memory is an enhancement; an outage in it
|
|
60
|
+
* must not take down the agent that was working fine without it, and the errors
|
|
61
|
+
* are surfaced through `onLearn` and the returned skip reason rather than
|
|
62
|
+
* swallowed.
|
|
63
|
+
*/
|
|
64
|
+
export async function runTurn(
|
|
65
|
+
memory: Cogmem,
|
|
66
|
+
input: {
|
|
67
|
+
userMessage: string
|
|
68
|
+
run: (context: string) => Promise<string>
|
|
69
|
+
},
|
|
70
|
+
options: TurnOptions = {}
|
|
71
|
+
): Promise<RunTurnResult> {
|
|
72
|
+
const context = await memory.context.build({
|
|
73
|
+
userMessage: input.userMessage,
|
|
74
|
+
...(options.sessionId === undefined ? {} : { sessionId: options.sessionId })
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const assistantResponse = await input.run(context.text)
|
|
78
|
+
|
|
79
|
+
if (options.learn === false) {
|
|
80
|
+
return { context, learning: null, learningSkipped: "learning disabled for this turn" }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let learning: LearnResult | null = null
|
|
84
|
+
try {
|
|
85
|
+
learning = await memory.turns.learn({
|
|
86
|
+
userMessage: input.userMessage,
|
|
87
|
+
assistantResponse,
|
|
88
|
+
...(options.sessionId === undefined ? {} : { sessionId: options.sessionId })
|
|
89
|
+
})
|
|
90
|
+
options.onLearn?.(learning)
|
|
91
|
+
} catch (error) {
|
|
92
|
+
// Deliberately not rethrown: the turn already succeeded, and the assistant's
|
|
93
|
+
// answer does not become wrong because memory could not be written.
|
|
94
|
+
console.warn("[cognitive-memory] could not learn from this turn", error)
|
|
95
|
+
return { context, learning: null, learningSkipped: String(error) }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options.domain !== undefined) {
|
|
99
|
+
try {
|
|
100
|
+
await memory.selfModel.record({
|
|
101
|
+
domain: options.domain,
|
|
102
|
+
success: options.failurePattern === undefined,
|
|
103
|
+
...(options.failurePattern === undefined ? {} : { failurePattern: options.failurePattern }),
|
|
104
|
+
...(options.strategy === undefined ? {} : { strategy: options.strategy })
|
|
105
|
+
})
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.warn("[cognitive-memory] could not record the domain outcome", error)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { context, learning }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface RecallOrExplainOptions {
|
|
115
|
+
limit?: number
|
|
116
|
+
/** Overrides the default "you were not told this" wording. */
|
|
117
|
+
emptyMessage?: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Recall, phrased for injection into a prompt.
|
|
122
|
+
*
|
|
123
|
+
* Returns text rather than results because the caller's next step is almost
|
|
124
|
+
* always "put this in the prompt", and doing the formatting here means every
|
|
125
|
+
* integration handles the empty case the same way: by saying so, rather than
|
|
126
|
+
* letting a model fill the gap with a guess.
|
|
127
|
+
*/
|
|
128
|
+
export async function recallOrExplain(
|
|
129
|
+
memory: Cogmem,
|
|
130
|
+
query: string,
|
|
131
|
+
options: RecallOrExplainOptions = {}
|
|
132
|
+
): Promise<string> {
|
|
133
|
+
const response = await memory.recall.search({
|
|
134
|
+
query,
|
|
135
|
+
...(options.limit === undefined ? {} : { limit: options.limit })
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
if (response.empty) {
|
|
139
|
+
return options.emptyMessage ?? `Nothing in memory matches "${query}". If you were not told, say so rather than guessing.`
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const lines = response.results.map((hit) => {
|
|
143
|
+
const domains = hit.memory.domains.length > 0 ? ` (${hit.memory.domains.join(", ")})` : ""
|
|
144
|
+
return `- ${hit.memory.content}${domains} [${hit.memory.tier}, relevance ${hit.score.toFixed(2)}]`
|
|
145
|
+
})
|
|
146
|
+
return `Remembered (${response.results.length} match${response.results.length === 1 ? "" : "es"}):\n${lines.join("\n")}`
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface SeedOptions {
|
|
150
|
+
/** Statements worth keeping regardless of how they are phrased. */
|
|
151
|
+
facts: string[]
|
|
152
|
+
sessionId?: string
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Store a set of statements once, tolerating the ones already held.
|
|
157
|
+
*
|
|
158
|
+
* For a migration or a first run, where you have a list of facts and no way to
|
|
159
|
+
* know which the service already knows. Restatements come back under
|
|
160
|
+
* `mergedInto` rather than as errors, so re-running it is safe.
|
|
161
|
+
*/
|
|
162
|
+
export async function seedMemories(memory: Cogmem, options: SeedOptions): Promise<{
|
|
163
|
+
stored: Memory[]
|
|
164
|
+
merged: Memory[]
|
|
165
|
+
rejected: LearnResult["rejected"]
|
|
166
|
+
}> {
|
|
167
|
+
const result = await memory.memories.create({
|
|
168
|
+
items: options.facts.map((content) => ({ content })),
|
|
169
|
+
...(options.sessionId === undefined ? {} : { sessionId: options.sessionId })
|
|
170
|
+
})
|
|
171
|
+
return { stored: result.stored, merged: result.mergedInto, rejected: result.rejected }
|
|
172
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @astracollab/cogmem — Cognitive Memory for agents.
|
|
3
|
+
*
|
|
4
|
+
* Two things in one package, deliberately:
|
|
5
|
+
*
|
|
6
|
+
* 1. **A client** for the Cognitive Memory service, for agents that call memory
|
|
7
|
+
* over HTTP.
|
|
8
|
+
* 2. **The cognitive layer itself** — the deterministic decisions about what to
|
|
9
|
+
* store, what to inject, and whether a new statement is new. No database, no
|
|
10
|
+
* network, no model in the retrieval path.
|
|
11
|
+
*
|
|
12
|
+
* Shipping both means a consumer can start with the service and later run the
|
|
13
|
+
* same logic in-process, or run it only in-process, without the behaviour
|
|
14
|
+
* changing underneath them. The primitives are the reason the two cannot drift.
|
|
15
|
+
*
|
|
16
|
+
* The exports are arranged for tree-shaking: importing `createClient` alone does
|
|
17
|
+
* not pull in the in-process engine, and the model-backed arbiter — which needs
|
|
18
|
+
* `ai` and `zod` — is a separate entry point so those stay optional peers.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// The service client
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
export { Cogmem, createClient } from "./cogmem"
|
|
25
|
+
export { createHttpClient, type HttpClient } from "./client"
|
|
26
|
+
export { CognitiveMemoryError } from "./errors"
|
|
27
|
+
|
|
28
|
+
// Helpers — the workflows that encode best practice
|
|
29
|
+
export {
|
|
30
|
+
runTurn,
|
|
31
|
+
recallOrExplain,
|
|
32
|
+
seedMemories,
|
|
33
|
+
type TurnOptions,
|
|
34
|
+
type RunTurnResult,
|
|
35
|
+
type RecallOrExplainOptions,
|
|
36
|
+
type SeedOptions
|
|
37
|
+
} from "./helpers/turn"
|
|
38
|
+
|
|
39
|
+
// Resources, for advanced use and for testing
|
|
40
|
+
export { MemoriesResource } from "./resources/memories"
|
|
41
|
+
export { ContextResource } from "./resources/context"
|
|
42
|
+
export { RecallResource } from "./resources/recall"
|
|
43
|
+
export { TurnsResource } from "./resources/turns"
|
|
44
|
+
export { TensionsResource } from "./resources/tensions"
|
|
45
|
+
export { SelfModelResource } from "./resources/self-model"
|
|
46
|
+
export { StatsResource, fetchHealth } from "./resources/stats"
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// The cognitive layer — runnable in-process
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
export { CognitiveMemory, runFastGate, extractDomains } from "./cognitive"
|
|
52
|
+
export {
|
|
53
|
+
extractDeterministic,
|
|
54
|
+
relevanceTokens,
|
|
55
|
+
overlapScore,
|
|
56
|
+
distinctiveTokens,
|
|
57
|
+
isLossyRewrite,
|
|
58
|
+
extractIdentifiers,
|
|
59
|
+
isInteractionScoped,
|
|
60
|
+
similarity,
|
|
61
|
+
normalise,
|
|
62
|
+
estimateTokens,
|
|
63
|
+
gistOf,
|
|
64
|
+
CANDIDATE_FLOOR,
|
|
65
|
+
COLLAPSE_THRESHOLD,
|
|
66
|
+
MAX_CANDIDATES,
|
|
67
|
+
PROMOTE_THRESHOLD
|
|
68
|
+
} from "./cognitive"
|
|
69
|
+
export type { FastGateResult, DeterministicMemory } from "./cognitive"
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Types
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
// The client's wire types.
|
|
76
|
+
export type {
|
|
77
|
+
Memory,
|
|
78
|
+
MemoryTier,
|
|
79
|
+
MemorySource,
|
|
80
|
+
Impact,
|
|
81
|
+
TensionStatus,
|
|
82
|
+
InclusionReason,
|
|
83
|
+
Scope,
|
|
84
|
+
RecallHit,
|
|
85
|
+
ContextEntry,
|
|
86
|
+
ContextReport,
|
|
87
|
+
LearnResult,
|
|
88
|
+
RejectedStatement,
|
|
89
|
+
Claim,
|
|
90
|
+
Tension,
|
|
91
|
+
DomainCapability,
|
|
92
|
+
SelfModel,
|
|
93
|
+
Health,
|
|
94
|
+
Stats,
|
|
95
|
+
RememberRequest,
|
|
96
|
+
ListMemoriesParams,
|
|
97
|
+
RecallRequest,
|
|
98
|
+
RecallResponse,
|
|
99
|
+
TurnRequest,
|
|
100
|
+
ContextRequest,
|
|
101
|
+
AddTensionRequest,
|
|
102
|
+
ResolveTensionRequest,
|
|
103
|
+
DomainOutcomeRequest,
|
|
104
|
+
CognitiveMemoryConfig,
|
|
105
|
+
CognitiveMemoryErrorBody
|
|
106
|
+
} from "./types"
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* The cognitive layer's own vocabulary, under its own names.
|
|
110
|
+
*
|
|
111
|
+
* No prefixing was necessary: the client's types are `Memory`, `Impact`,
|
|
112
|
+
* `Tension` and `SelfModel`, and the layer's are `MemoryItem`, `TensionImpact`,
|
|
113
|
+
* `KnowledgeTension` and `ProprioceptiveSelfModel`. The only genuine overlap is
|
|
114
|
+
* `MemoryTier`, `TensionStatus` and `DomainCapability`, which are identical in
|
|
115
|
+
* both — the same field names and the same string values — so the client's
|
|
116
|
+
* copies above are the canonical ones and are not re-exported here.
|
|
117
|
+
*/
|
|
118
|
+
export type {
|
|
119
|
+
MemoryItem,
|
|
120
|
+
MemoryMetadata,
|
|
121
|
+
MemoryInclusionReason,
|
|
122
|
+
MemoryInjectionEntry,
|
|
123
|
+
MemoryInjectionReport,
|
|
124
|
+
MemoryReconciliation,
|
|
125
|
+
MemoryTier as CognitiveMemoryTier,
|
|
126
|
+
KnowledgeTension,
|
|
127
|
+
ProprioceptiveSelfModel,
|
|
128
|
+
TrajectoryPrediction,
|
|
129
|
+
TensionImpact,
|
|
130
|
+
ArbiterEvaluationResult,
|
|
131
|
+
ArbiterFn,
|
|
132
|
+
CognitiveMemoryOptions,
|
|
133
|
+
CognitiveMemoryStateSnapshot
|
|
134
|
+
} from "./cognitive"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HttpClient } from "../client"
|
|
2
|
+
import type { ContextReport, ContextRequest } from "../types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The prompt block.
|
|
6
|
+
*
|
|
7
|
+
* One call per turn, and the reason the service exists: a one-line index of
|
|
8
|
+
* everything remembered, plus full bodies only where a named identifier, an
|
|
9
|
+
* unresolved contradiction, or a weak domain earned them.
|
|
10
|
+
*/
|
|
11
|
+
export class ContextResource {
|
|
12
|
+
constructor(private readonly client: HttpClient) {}
|
|
13
|
+
|
|
14
|
+
build(request: ContextRequest = {}): Promise<ContextReport> {
|
|
15
|
+
return this.client<ContextReport>("/context", { method: "POST", body: request })
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { HttpClient } from "../client"
|
|
2
|
+
import type { LearnResult, ListMemoriesParams, Memory, MemoryTier, RememberRequest } from "../types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reading and writing what is stored.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately thin: one method, one endpoint, no cleverness. The class is a
|
|
8
|
+
* namespace, so `memory.memories.` offers the whole surface in an editor, but it
|
|
9
|
+
* is not a place for business logic. The workflow that composes these calls lives
|
|
10
|
+
* in `../helpers`.
|
|
11
|
+
*/
|
|
12
|
+
export class MemoriesResource {
|
|
13
|
+
constructor(private readonly client: HttpClient) {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The envelope is unwrapped here rather than passed on.
|
|
17
|
+
*
|
|
18
|
+
* The service replies with `{ memories, stats }` so a dashboard can get both in
|
|
19
|
+
* one call, but an SDK caller who asked for a list wants a list. Deciding that
|
|
20
|
+
* in one place here is better than making every integration know the shape.
|
|
21
|
+
*/
|
|
22
|
+
async list(params: ListMemoriesParams = {}): Promise<Memory[]> {
|
|
23
|
+
const response = await this.client<{ memories: Memory[] }>("/memories", {
|
|
24
|
+
method: "GET",
|
|
25
|
+
query: params
|
|
26
|
+
})
|
|
27
|
+
return response.memories
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async get(id: string): Promise<Memory> {
|
|
31
|
+
const response = await this.client<{ memory: Memory }>(`/memories/${encodeURIComponent(id)}`, {
|
|
32
|
+
method: "GET"
|
|
33
|
+
})
|
|
34
|
+
return response.memory
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* State facts outright.
|
|
39
|
+
*
|
|
40
|
+
* Restatements of something already held are folded in rather than stored
|
|
41
|
+
* twice, and the result says which ones were folded and into what — a write
|
|
42
|
+
* that silently did nothing is indistinguishable from a bug.
|
|
43
|
+
*/
|
|
44
|
+
create(request: RememberRequest): Promise<LearnResult> {
|
|
45
|
+
return this.client<LearnResult>("/memories", { method: "POST", body: request })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Move a memory between tiers. L1 is pre-staged into every context build. */
|
|
49
|
+
async promote(id: string, tier: MemoryTier): Promise<Memory> {
|
|
50
|
+
const response = await this.client<{ memory: Memory }>(`/memories/${encodeURIComponent(id)}`, {
|
|
51
|
+
method: "PATCH",
|
|
52
|
+
body: { tier }
|
|
53
|
+
})
|
|
54
|
+
return response.memory
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
remove(id: string): Promise<{ deleted: boolean; id: string }> {
|
|
58
|
+
return this.client(`/memories/${encodeURIComponent(id)}`, { method: "DELETE" })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HttpClient } from "../client"
|
|
2
|
+
import type { RecallRequest, RecallResponse } from "../types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Deterministic ranked lookup.
|
|
6
|
+
*
|
|
7
|
+
* No model is involved, so recall quality does not change when a provider is
|
|
8
|
+
* down or a different model is configured. `empty: true` is the signal to admit
|
|
9
|
+
* ignorance rather than guess.
|
|
10
|
+
*/
|
|
11
|
+
export class RecallResource {
|
|
12
|
+
constructor(private readonly client: HttpClient) {}
|
|
13
|
+
|
|
14
|
+
search(request: RecallRequest): Promise<RecallResponse> {
|
|
15
|
+
return this.client<RecallResponse>("/recall", { method: "POST", body: request })
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HttpClient } from "../client"
|
|
2
|
+
import type { DomainOutcomeRequest, SelfModel } from "../types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The proprioceptive self-model: how reliably this agent has done in each domain.
|
|
6
|
+
*
|
|
7
|
+
* Nothing here is inferred. Without outcomes recorded through `record`, the
|
|
8
|
+
* model stays at its priors and no guardrail ever fires — which is the most
|
|
9
|
+
* common reason a self-model looks like it is not working.
|
|
10
|
+
*/
|
|
11
|
+
export class SelfModelResource {
|
|
12
|
+
constructor(private readonly client: HttpClient) {}
|
|
13
|
+
|
|
14
|
+
get(): Promise<SelfModel> {
|
|
15
|
+
return this.client<SelfModel>("/self-model", { method: "GET" })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
record(request: DomainOutcomeRequest): Promise<SelfModel> {
|
|
19
|
+
return this.client<SelfModel>("/self-model/outcome", { method: "POST", body: request })
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HttpClient } from "../client"
|
|
2
|
+
import type { Health, Stats } from "../types"
|
|
3
|
+
|
|
4
|
+
/** What the memory currently holds, and what the service can do. */
|
|
5
|
+
export class StatsResource {
|
|
6
|
+
constructor(private readonly client: HttpClient) {}
|
|
7
|
+
|
|
8
|
+
/** Needs only `stats:read`, so monitoring can watch without reading memories. */
|
|
9
|
+
get(): Promise<Stats> {
|
|
10
|
+
return this.client<Stats>("/stats", { method: "GET" })
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Liveness, and this deployment's limits and extractor mode.
|
|
16
|
+
*
|
|
17
|
+
* Takes an explicit base URL because it is the one call that must work with no
|
|
18
|
+
* key at all — including from a health check that has no credentials.
|
|
19
|
+
*/
|
|
20
|
+
export const fetchHealth = async (baseUrl: string): Promise<Health> => {
|
|
21
|
+
const origin = baseUrl.replace(/\/$/, "")
|
|
22
|
+
const response = await fetch(`${origin}/api/v1/health`, { headers: { accept: "application/json" } })
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`cognitive-memory: health check failed with ${response.status}`)
|
|
25
|
+
}
|
|
26
|
+
return (await response.json()) as Health
|
|
27
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HttpClient } from "../client"
|
|
2
|
+
import type { AddTensionRequest, ResolveTensionRequest, Tension, TensionStatus } from "../types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Knowledge tensions: two claims that cannot both be true.
|
|
6
|
+
*
|
|
7
|
+
* A tension is worth more than either claim alone, so it is pinned into every
|
|
8
|
+
* context build with an actionable question until it is resolved.
|
|
9
|
+
*/
|
|
10
|
+
export class TensionsResource {
|
|
11
|
+
constructor(private readonly client: HttpClient) {}
|
|
12
|
+
|
|
13
|
+
async list(status?: TensionStatus): Promise<Tension[]> {
|
|
14
|
+
const response = await this.client<{ tensions: Tension[] }>("/tensions", {
|
|
15
|
+
method: "GET",
|
|
16
|
+
query: status === undefined ? {} : { status }
|
|
17
|
+
})
|
|
18
|
+
return response.tensions
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async create(request: AddTensionRequest): Promise<Tension> {
|
|
22
|
+
const response = await this.client<{ tension: Tension }>("/tensions", {
|
|
23
|
+
method: "POST",
|
|
24
|
+
body: request
|
|
25
|
+
})
|
|
26
|
+
return response.tension
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async resolve(id: string, request: ResolveTensionRequest): Promise<Tension> {
|
|
30
|
+
const response = await this.client<{ tension: Tension }>(`/tensions/${encodeURIComponent(id)}`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
body: request
|
|
33
|
+
})
|
|
34
|
+
return response.tension
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HttpClient } from "../client"
|
|
2
|
+
import type { LearnResult, TurnRequest } from "../types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Learning from a completed turn.
|
|
6
|
+
*
|
|
7
|
+
* A turn whose user message contains a question is treated as a lookup rather
|
|
8
|
+
* than a lesson, so this is called after work is done, not before.
|
|
9
|
+
*/
|
|
10
|
+
export class TurnsResource {
|
|
11
|
+
constructor(private readonly client: HttpClient) {}
|
|
12
|
+
|
|
13
|
+
learn(request: TurnRequest): Promise<LearnResult> {
|
|
14
|
+
return this.client<LearnResult>("/turns", { method: "POST", body: request })
|
|
15
|
+
}
|
|
16
|
+
}
|