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
4 changes: 3 additions & 1 deletion proto/coverage.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_comment": "Tracks how each engine RPC (proto/memory.proto) is surfaced in the SDK. check-proto-drift.mjs fails CI if the proto gains an RPC that is in neither list, so the SDK can't silently fall behind the engine. Vendored from thinkfleet-memory-engine main @ bc5c9ec.",
"_comment": "Tracks how each engine RPC (proto/memory.proto) is surfaced in the SDK. check-proto-drift.mjs fails CI if the proto gains an RPC that is in neither list, so the SDK can't silently fall behind the engine. Vendored from thinkfleet-memory-engine main @ 7b8b80f.",
"covered": {
"Save": "memory.admin.create / memory.observe",
"Observe": "memory.observe",
Expand All @@ -15,6 +15,8 @@
"GetCalibration": "lattice.getCalibration",
"GetProfile": "lattice.getProfile",
"BuildContext": "context.build",
"BatchBuildContext": "context.batchBuild",
"QueryGraph": "context.queryGraph",
"EmitEvent": "events.emit",
"PollEvents": "events.poll",
"CreateAlertRule": "alerts.create",
Expand Down
57 changes: 57 additions & 0 deletions proto/memory.proto
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ service Memory {
// most likely needed next before it's asked for. Deterministic; read-only.
rpc PrefetchRelated(PrefetchRelatedRequest) returns (SearchResponse);

// Point-in-time knowledge-graph query. Returns the edges valid AT `as_of`
// (or, if omitted, the current ones) matching the subject/predicate filter
// — "what did we believe about X on date Y". Backed by the bi-temporal
// valid_from/valid_to on each edge. Read-only.
rpc QueryGraph(QueryGraphRequest) returns (QueryGraphResult);

// Generate embeddings for items that don't have one yet (the backfill
// work-list). generate-on-save only covers new writes; this catches up the
// existing corpus. No-op if the engine's embedding provider is disabled.
Expand Down Expand Up @@ -156,6 +162,12 @@ service Memory {
// carries source ids so any inference can be traced.
rpc BuildContext(BuildContextRequest) returns (ContextBundle);

// Batch context assembly — build bundles for many subjects in ONE call.
// Replaces the N-round-trip / direct-DB bulk load (e.g. EngageIt loading
// memory for hundreds of customers at once, which is what killed the old
// path). Bundles are returned in request order; capped at 500 subjects.
rpc BatchBuildContext(BatchBuildContextRequest) returns (BatchContextBundle);

// ─── Events (the proactive event log) ───────────────────────────

// Append an event to the durable event log. Idempotent —
Expand Down Expand Up @@ -842,6 +854,34 @@ message PrefetchRelatedRequest {
optional uint32 limit = 2;
}

message QueryGraphRequest {
optional string platform_id = 10;
optional string project_id = 11;
// Narrow to a subject entity and/or predicate (both optional).
optional string subject_id = 1;
optional string predicate = 2;
// RFC3339 instant for a point-in-time view. Omit for the current graph.
optional string as_of = 3;
// Max edges. Default 100, clamped [1, 1000].
optional uint32 limit = 4;
}

message GraphEdge {
string id = 1;
string subject_id = 2;
string predicate = 3;
// Exactly one of object_id (entity) or object_literal is set.
optional string object_id = 4;
optional string object_literal = 5;
double weight = 6;
string valid_from = 7; // RFC3339
optional string valid_to = 8; // RFC3339; absent = still current
}

message QueryGraphResult {
repeated GraphEdge edges = 1;
}

message BackfillRequest {
// Max items to embed this call. Default 500, clamped [1, 10000]. Call
// repeatedly until embedded == 0 to drain a large corpus.
Expand Down Expand Up @@ -1032,6 +1072,23 @@ message BuildContextRequest {
optional string project_id = 11;
}

message BatchBuildContextRequest {
// Subjects to build context for (<= 500). The options below apply to all.
repeated Subject subjects = 1;
repeated string include = 2;
optional uint32 max_tokens = 3;
optional uint32 memory_limit = 4;
optional uint32 prediction_limit = 5;
repeated string exclude_categories = 6;
optional string platform_id = 10;
optional string project_id = 11;
}

message BatchContextBundle {
// One bundle per requested subject, in request order.
repeated ContextBundle bundles = 1;
}

message ContextProfileSummary {
optional string rfm_segment = 1;
optional uint32 recency_score = 2;
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export type {
ContextPrediction,
ContextMemory,
ContextObservation,
BatchContextBuildRequest,
QueryGraphRequest,
GraphEdge,
} from './resources/context.js'

export { EventsResource } from './resources/events.js'
Expand Down
63 changes: 63 additions & 0 deletions src/resources/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,40 @@ export interface ContextObservation {
* const prompt = `Subject context:\n${JSON.stringify(ctx, null, 2)}\n\nUser question: ...`
* ```
*/
/** One edge of the temporal knowledge graph. */
export interface GraphEdge {
id: string
subjectId: string
predicate: string
/** Exactly one of objectId (entity) or objectLiteral is set. */
objectId?: string
objectLiteral?: string
weight: number
validFrom: string
/** Absent = still current. */
validTo?: string
}

export interface QueryGraphRequest {
/** Narrow to a subject entity and/or predicate. */
subjectId?: string
predicate?: string
/** RFC3339 instant for a point-in-time view. Omit for the current graph. */
asOf?: string
/** Max edges. Default 100, clamped [1, 1000]. */
limit?: number
}

export interface BatchContextBuildRequest {
/** Subjects to build context for (<= 500). Options below apply to all. */
subjects: Array<{ kind: string; externalId: string }>
include?: ContextSection[]
maxTokens?: number
memoryLimit?: number
predictionLimit?: number
excludeCategories?: string[]
}

export class ContextResource {
constructor(private readonly http: HttpClient) {}

Expand All @@ -141,4 +175,33 @@ export class ContextResource {
): Promise<ContextBundle> {
return this.http.post<ContextBundle>('/lattice/context', body, options)
}

/**
* Build context bundles for many subjects in ONE call (up to 500). Replaces
* the N-round-trip bulk load — return order matches `subjects`.
*/
async batchBuild(
body: BatchContextBuildRequest,
options?: RequestOptions,
): Promise<ContextBundle[]> {
const r = await this.http.post<{ bundles: ContextBundle[] }>(
'/lattice/context/batch',
body,
options,
)
return r.bundles
}

/**
* Point-in-time knowledge-graph query: edges valid AT `asOf` (or current if
* omitted), filtered by subject/predicate. "What did we believe about X on
* date Y."
*/
async queryGraph(
body: QueryGraphRequest = {},
options?: RequestOptions,
): Promise<GraphEdge[]> {
const r = await this.http.post<{ edges: GraphEdge[] }>('/lattice/graph/query', body, options)
return r.edges
}
}
Loading