Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,13 @@ const tf = new ThinkFleetMemory({
| `listPendingReview(params?)` | `GET /projects/:id/admin/memory/review` |
| `stats()` | `GET /projects/:id/admin/memory/stats` |
| `create(body)` | `POST /projects/:id/admin/memory` |
| `createProcedure(body)` | `POST /projects/:id/admin/memory` (type=procedure)|
| `update(memId, body)` | `PATCH /projects/:id/admin/memory/:memId` |
| `confirm(memId, body)` | `POST /projects/:id/admin/memory/:memId/confirm`|
| `promote(memId, body)` | `POST /projects/:id/admin/memory/:memId/promote`|
| `search(body)` | `POST /projects/:id/admin/memory/search` |
| `getPrecedence()` | `GET /projects/:id/admin/memory/precedence` |
| `setPrecedence(policy)` | `PUT /projects/:id/admin/memory/precedence` |
| `delete(memId)` | `DELETE /projects/:id/admin/memory/:memId` |
| `listFeedback(memId)` | `GET /projects/:id/admin/memory/:memId/feedback`|

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@thinkfleet/memory-sdk",
"version": "0.6.0",
"version": "0.8.0",
"description": "TypeScript SDK for app.memmesh.ai — admin + project memory CRUD, semantic search, feedback, and Lattice behavioral patterns",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
13 changes: 13 additions & 0 deletions src/core/http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ export class HttpClient {
)
}

async put<T>(path: string, body?: unknown, requestOptions?: RequestOptions): Promise<T> {
const url = this.buildUrl(path, undefined, requestOptions)
return this.request<T>(
url,
{
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: body != null ? JSON.stringify(body) : undefined,
},
requestOptions,
)
}

async delete<T>(path: string, requestOptions?: RequestOptions): Promise<T> {
const url = this.buildUrl(path, undefined, requestOptions)
return this.request<T>(url, { method: 'DELETE' }, requestOptions)
Expand Down
43 changes: 43 additions & 0 deletions src/core/procedural.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { ProceduralMemoryMetadata } from '../types/memory.js'

/**
* Render a procedure into the injectable `content` string. Kept identical to
* the engine-side renderer so author-time content matches what the server
* would produce. Pure and deterministic.
*/
export function renderProcedureContent(meta: ProceduralMemoryMetadata): string {
const lines: string[] = [`Goal: ${meta.goal.trim()}`]

if (meta.whenToUse && meta.whenToUse.trim()) {
lines.push(`When: ${meta.whenToUse.trim()}`)
}

lines.push('Steps:')
meta.steps.forEach((step, i) => {
const pitfall =
step.pitfall && step.pitfall.trim()
? ` (watch out: ${step.pitfall.trim()})`
: ''
lines.push(`${i + 1}. ${step.text.trim()}${pitfall}`)
})

const failures = (meta.failureModes ?? []).filter((f) => f.trim())
if (failures.length > 0) {
lines.push('Avoid:')
failures.forEach((f) => lines.push(`- ${f.trim()}`))
}

return lines.join('\n')
}

/** The out-of-the-box precedence ladder: human-verified > local > licensed-brain > base. */
export const DEFAULT_PRECEDENCE_POLICY = {
defaultOrder: [
'human_verified',
'local',
'licensed_brain',
'base',
] as const,
scopeNearestWins: true,
overrides: [] as const,
}
14 changes: 14 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ export type {
ReflectResult,
Insight,
PrefetchRelatedRequest,
ReviewQueueItem,
ProcedureStep,
ProceduralMemoryMetadata,
CreateProcedureRequest,
MemoryPrecedenceOverride,
MemoryPrecedencePolicy,
} from './types/memory.js'

export {
Expand All @@ -199,8 +205,16 @@ export {
MemoryStatus,
MemoryImpact,
MemoryFeedbackRating,
MemoryReviewReason,
MemoryProvenanceTier,
} from './types/memory.js'

// Procedural memory helpers
export {
renderProcedureContent,
DEFAULT_PRECEDENCE_POLICY,
} from './core/procedural.js'

// Types — lattice
export type {
BehaviorPatternKind,
Expand Down
67 changes: 63 additions & 4 deletions src/resources/memory.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { ConsentResource } from './consent.js'
import type { HttpClient } from '../core/http-client.js'
import { renderProcedureContent } from '../core/procedural.js'
import type { RequestOptions } from '../core/types.js'
import {
MemoryItemType,
MemoryScope,
type ConfirmMemoryRequest,
type CreateProcedureRequest,
type MemoryPrecedencePolicy,
type ReviewQueueItem,
type CreateMemoryRequest,
type ListMemoryParams,
type MemoryFeedback,
Expand Down Expand Up @@ -456,14 +460,16 @@ export class AdminMemoryResource {
}

/**
* List memories pending review — either freshly extracted (status=pending)
* or auto-flagged by negative feedback (negativeRatingCount >= 3).
* List the adjudication queue — everything the system is unsure about. Each
* row carries a `reviewReason`: `pending` (awaiting confirmation), `flagged`
* (>= 3 negative), `low_confidence` (weak auto-extraction), or `stale`
* (old and long-unused).
*/
async listPendingReview(
params?: { limit?: number; offset?: number },
options?: RequestOptions,
): Promise<MemoryItem[]> {
return this.http.get<MemoryItem[]>(
): Promise<ReviewQueueItem[]> {
return this.http.get<ReviewQueueItem[]>(
'/admin/memory/review',
params as Record<string, string | number | boolean | undefined>,
options,
Expand All @@ -486,6 +492,59 @@ export class AdminMemoryResource {
return this.http.post<MemoryItem>('/admin/memory', body, options)
}

/**
* Author a procedure — "how this job is done here" (goal + steps + failure
* modes). Stored as a PROCEDURE memory: the structured shape goes on
* `metadata` and the rendered how-to text on `content`, so retrieval injects
* it as an explicit exemplar. A cheaper model reasons better on-domain when
* handed the procedure instead of a flattened fact.
*/
async createProcedure(
body: CreateProcedureRequest,
options?: RequestOptions,
): Promise<MemoryItem> {
const { category, scope, importance, ...metadata } = body
return this.create(
{
type: MemoryItemType.PROCEDURE,
content: renderProcedureContent(metadata),
category,
scope: scope ?? MemoryScope.PROJECT,
importance: importance ?? 7,
metadata: metadata as unknown as Record<string, unknown>,
},
options,
)
}

/**
* Get the project's memory precedence policy — which memory wins when two
* disagree. Falls back to the default ladder (human-verified > local >
* licensed-brain > base) when unset.
*/
async getPrecedence(options?: RequestOptions): Promise<MemoryPrecedencePolicy> {
return this.http.get<MemoryPrecedencePolicy>(
'/admin/memory/precedence',
undefined,
options,
)
}

/**
* Save the project's memory precedence policy. Requires the Memory Steward
* role (MANAGE_MEMORY_POLICY).
*/
async setPrecedence(
policy: MemoryPrecedencePolicy,
options?: RequestOptions,
): Promise<MemoryPrecedencePolicy> {
return this.http.put<MemoryPrecedencePolicy>(
'/admin/memory/precedence',
policy,
options,
)
}

/**
* Update any memory item.
*/
Expand Down
87 changes: 87 additions & 0 deletions src/types/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ export enum MemoryItemType {
RULE = 'rule',
CORRECTION = 'correction',
SUMMARY = 'summary',
/**
* A reusable procedure — how a job/task is done here (goal + steps +
* failure modes). Injected as a how-to exemplar, not a fact, so a cheaper
* model reasons better on-domain. Structured shape on `metadata`
* (ProceduralMemoryMetadata); `content` holds the rendered, injectable text
* (see renderProcedureContent). Author with `admin.createProcedure()`.
*/
PROCEDURE = 'procedure',
/** Behavioral pattern emitted by Lattice mining. */
BEHAVIOR_PATTERN = 'behavior_pattern',
/** Subject-level consent / opt-out record. See ConsentResource. */
Expand Down Expand Up @@ -337,3 +345,82 @@ export interface PrefetchRelatedRequest {
/** Max related memories to return. Default 10, clamped [1, 100]. */
limit?: number
}

// ── Adjudication queue ───────────────────────────────────────────────

/** Why a memory is in the review queue. The queue reports the highest-priority reason. */
export enum MemoryReviewReason {
/** Awaiting first confirmation (status = pending). */
PENDING = 'pending',
/** Flagged by repeated negative feedback (>= 3). */
FLAGGED = 'flagged',
/** Auto-extracted, never human-confirmed, with low confidence. */
LOW_CONFIDENCE = 'low_confidence',
/** Old and long-unrecalled — a decay/forget candidate. */
STALE = 'stale',
}

/** A review-queue row: a memory plus why it needs a steward's attention. */
export interface ReviewQueueItem extends MemoryItem {
reviewReason: MemoryReviewReason
}

// ── Procedural memory ────────────────────────────────────────────────

/** One step in a procedure. `pitfall` is an optional inline warning. */
export interface ProcedureStep {
text: string
pitfall?: string
}

/** Structured shape stored on a PROCEDURE memory's `metadata`. */
export interface ProceduralMemoryMetadata {
/** What the procedure accomplishes. */
goal: string
/** When to reach for it — the trigger condition, in words. */
whenToUse?: string
/** Ordered steps. */
steps: ProcedureStep[]
/** Known failure modes / anti-patterns — what NOT to do and why. */
failureModes?: string[]
}

/** Input for `admin.createProcedure()`. */
export interface CreateProcedureRequest extends ProceduralMemoryMetadata {
/** Optional category tag — used as the heading when injected. */
category?: string
/** Override the default PROJECT scope. */
scope?: MemoryScope
/** 0-10 importance, defaults to 7. */
importance?: number
}

// ── Memory precedence policy ─────────────────────────────────────────

/** Origin tier of a memory, used to resolve conflicts. Default order strongest→weakest. */
export enum MemoryProvenanceTier {
/** Confirmed by a human steward — most trusted. */
HUMAN_VERIFIED = 'human_verified',
/** Learned/extracted inside this organization. */
LOCAL = 'local',
/** Ingested from a purchased/licensed brain. */
LICENSED_BRAIN = 'licensed_brain',
/** Seed / base-model brain baseline. */
BASE = 'base',
}

/** Category-level override: "for this category, this tier wins." */
export interface MemoryPrecedenceOverride {
category: string
winningTier: MemoryProvenanceTier
}

/** Which memory wins when two disagree. See `admin.getPrecedence()` / `setPrecedence()`. */
export interface MemoryPrecedencePolicy {
/** Tier ranking, strongest first. Tiers omitted rank last. */
defaultOrder: MemoryProvenanceTier[]
/** When true, a nearer (more specific) scope breaks ties within a tier. */
scopeNearestWins: boolean
/** Category-level exceptions applied before the default order. */
overrides: MemoryPrecedenceOverride[]
}
Loading