KG Entity Timeline
localnest_kg_timelineRetrieve a chronological timeline of all triples for an entity, including invalidated facts, ordered by valid_from date.
Instructions
Get a chronological timeline of all triples for an entity, including invalidated facts. Ordered by valid_from date ascending.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| entity_id | Yes | ||
| response_format | No | json |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | ||
| meta | Yes |
Implementation Reference
- Core handler: queries all triples involving the entity (as subject or object), ordered by valid_from/recorded_at, and returns them as a timeline.
export async function getEntityTimeline( adapter: Adapter, entityId: string ): Promise<{ entity_id: string; count: number; triples: KgTripleWithNames[] }> { const id = cleanString(entityId, 400); if (!id) throw new Error('entityId is required'); const triples = await adapter.all<KgTripleWithNames>( `SELECT t.*, s.name AS subject_name, o.name AS object_name FROM kg_triples t JOIN kg_entities s ON s.id = t.subject_id JOIN kg_entities o ON o.id = t.object_id WHERE t.subject_id = ? OR t.object_id = ? ORDER BY t.valid_from ASC, t.recorded_at ASC`, [id, id] ); return { entity_id: id, count: triples.length, triples }; } - src/mcp/tools/graph-tools.ts:216-229 (registration)Registration: defines the 'localnest_kg_timeline' MCP tool with input schema, read-only annotation, and ties to memory.getEntityTimeline.
registerJsonTool( ['localnest_kg_timeline'], { title: 'KG Entity Timeline', description: 'Get a chronological timeline of all triples for an entity, including invalidated facts. Ordered by valid_from date ascending.', inputSchema: { entity_id: z.string().min(1).max(400) }, annotations: READ_ONLY_ANNOTATIONS, outputSchema: schemas.OUTPUT_BUNDLE_RESULT_SCHEMA }, async ({ entity_id }: Record<string, unknown>) => memory.getEntityTimeline(entity_id as string) ); - Memory service layer: delegates getEntityTimeline to the store layer.
async getEntityTimeline(entityId: string) { this.assertEnabled(); return this.store.getEntityTimeline(entityId); } - src/services/memory/store.ts:338-341 (helper)Store layer: initializes and calls the getEntityTimeline implementation from kg.ts
async getEntityTimeline(entityId: string) { await this.init(); return getEntityTimelineFn(this.adapter!, entityId); } - src/mcp/tools/graph-tools.ts:23-210 (schema)TypeScript interface definition for MemoryService including getEntityTimeline method.
interface MemoryService { addEntity(opts: Record<string, unknown>): Promise<unknown>; addTriple(opts: Record<string, unknown>): Promise<unknown>; addEntityBatch(opts: Record<string, unknown>): Promise<unknown>; addTripleBatch(opts: Record<string, unknown>): Promise<unknown>; queryEntityRelationships(entityId: string, opts: Record<string, unknown>): Promise<unknown>; invalidateTriple(tripleId: string, validTo?: string | null): Promise<unknown>; queryTriplesAsOf(entityId: string, asOfDate: string, mode?: 'event' | 'transaction'): Promise<unknown>; getEntityTimeline(entityId: string): Promise<unknown>; getKgStats(): Promise<unknown>; listNests(): Promise<unknown>; listBranches(nest: string): Promise<unknown>; getTaxonomyTree(): Promise<unknown>; traverseGraph(opts: Record<string, unknown>): Promise<unknown>; discoverBridges(opts: Record<string, unknown>): Promise<unknown>; writeDiaryEntry(opts: Record<string, unknown>): Promise<unknown>; readDiaryEntries(opts: Record<string, unknown>): Promise<unknown>; ingestMarkdown(opts: Record<string, unknown>): Promise<unknown>; ingestJson(opts: Record<string, unknown>): Promise<unknown>; checkDuplicate(content: string, opts: Record<string, unknown>): Promise<unknown>; backfillMemoryKgLinks(opts: Record<string, unknown>): Promise<unknown>; store: { hooks: MemoryHooks; }; } export interface RegisterGraphToolsOptions { registerJsonTool: RegisterJsonToolFn; memory: MemoryService; schemas: SharedSchemas; } export function registerGraphTools({ registerJsonTool, memory, schemas }: RegisterGraphToolsOptions): void { // --- KG Tools (localnest_kg_*) --- registerJsonTool( ['localnest_kg_add_entity'], { title: 'KG Add Entity', description: 'Create or update an entity in the knowledge graph. Entity IDs are auto-generated as normalized slugs (lowercase, underscored).', inputSchema: { name: z.string().min(1).max(400), type: z.string().max(100).default('concept'), properties: z.record(z.string(), z.any()).default({}), memory_id: z.string().optional(), terse: z.enum(['minimal', 'verbose']).default('verbose') }, annotations: IDEMPOTENT_WRITE_ANNOTATIONS, outputSchema: schemas.OUTPUT_ACK_RESULT_SCHEMA }, async ({ name, type, properties, memory_id, terse }: Record<string, unknown>) => toMinimalWriteResponse(await memory.addEntity({ name, type, properties, memoryId: memory_id }), terse as string) ); registerJsonTool( ['localnest_kg_add_triple'], { title: 'KG Add Triple', description: 'Add a subject-predicate-object triple to the knowledge graph. Entities are auto-created on first reference. Detects contradictions (same subject+predicate with different valid object) and warns without blocking.', inputSchema: { subject_name: z.string().min(1).max(400), predicate: z.string().min(1).max(400), object_name: z.string().min(1).max(400), subject_id: z.string().max(400).optional(), object_id: z.string().max(400).optional(), valid_from: z.string().nullable().optional(), valid_to: z.string().nullable().optional(), confidence: z.number().min(0).max(1).default(1.0), source_memory_id: z.string().nullable().optional(), source_type: z.string().max(100).default('manual'), terse: z.enum(['minimal', 'verbose']).default('verbose') }, annotations: WRITE_ANNOTATIONS, outputSchema: schemas.OUTPUT_TRIPLE_RESULT_SCHEMA }, async ({ subject_name, predicate, object_name, subject_id, object_id, valid_from, valid_to, confidence, source_memory_id, source_type, terse }: Record<string, unknown>) => toMinimalWriteResponse(await memory.addTriple({ subjectName: subject_name, subjectId: subject_id, predicate, objectName: object_name, objectId: object_id, validFrom: valid_from, validTo: valid_to, confidence, sourceMemoryId: source_memory_id, sourceType: source_type }), terse as string) ); registerJsonTool( ['localnest_kg_add_entities_batch'], { title: 'KG Add Entities Batch', description: 'Create up to 500 entities in a single transactional batch. Returns created/duplicate counts and per-row errors. Use response_format "verbose" to get back an ids[] array.', inputSchema: { entities: z.array(z.object({ name: z.string().min(1).max(400), type: z.string().max(100).default('concept'), properties: z.record(z.string(), z.any()).default({}), memory_id: z.string().optional() })).min(1).max(500), response_format: z.enum(['minimal', 'verbose']).default('minimal') }, annotations: IDEMPOTENT_WRITE_ANNOTATIONS, outputSchema: schemas.OUTPUT_BATCH_RESULT_SCHEMA }, async ({ entities, response_format }: Record<string, unknown>) => memory.addEntityBatch({ entities: (entities as Array<Record<string, unknown>>).map(e => ({ name: e.name, type: e.type, properties: e.properties, memoryId: e.memory_id })), response_format }) ); registerJsonTool( ['localnest_kg_add_triples_batch'], { title: 'KG Add Triples Batch', description: 'Add up to 500 triples in a single transactional batch. Entities auto-created on first reference. Deduplicates against active triples.', inputSchema: { triples: z.array(z.object({ subject_name: z.string().min(1).max(400), predicate: z.string().min(1).max(400), object_name: z.string().min(1).max(400), subject_id: z.string().max(400).optional(), object_id: z.string().max(400).optional(), valid_from: z.string().nullable().optional(), valid_to: z.string().nullable().optional(), confidence: z.number().min(0).max(1).default(1.0), source_memory_id: z.string().nullable().optional(), source_type: z.string().max(100).default('manual') })).min(1).max(500), response_format: z.enum(['minimal', 'verbose']).default('minimal') }, annotations: IDEMPOTENT_WRITE_ANNOTATIONS, outputSchema: schemas.OUTPUT_BATCH_RESULT_SCHEMA }, async ({ triples, response_format }: Record<string, unknown>) => memory.addTripleBatch({ triples: (triples as Array<Record<string, unknown>>).map(t => ({ subjectName: t.subject_name, subjectId: t.subject_id, predicate: t.predicate, objectName: t.object_name, objectId: t.object_id, validFrom: t.valid_from, validTo: t.valid_to, confidence: t.confidence, sourceMemoryId: t.source_memory_id, sourceType: t.source_type })), response_format }) ); registerJsonTool( ['localnest_kg_query'], { title: 'KG Query Entity', description: 'Query all relationships for an entity in the knowledge graph with optional direction filtering (outgoing, incoming, or both).', inputSchema: { entity_id: z.string().min(1).max(400), direction: z.enum(['outgoing', 'incoming', 'both']).default('both'), include_invalid: z.boolean().default(false) }, annotations: READ_ONLY_ANNOTATIONS, outputSchema: schemas.OUTPUT_BUNDLE_RESULT_SCHEMA }, async ({ entity_id, direction, include_invalid }: Record<string, unknown>) => memory.queryEntityRelationships(entity_id as string, { direction, includeInvalid: include_invalid }) ); registerJsonTool( ['localnest_kg_invalidate'], { title: 'KG Invalidate Triple', description: 'Set valid_to on a triple to mark it as no longer current. The triple remains in history but is excluded from current-state queries.', inputSchema: { triple_id: z.string().min(1), valid_to: z.string().nullable().optional(), terse: z.enum(['minimal', 'verbose']).default('verbose') }, annotations: IDEMPOTENT_WRITE_ANNOTATIONS, outputSchema: schemas.OUTPUT_ACK_RESULT_SCHEMA }, async ({ triple_id, valid_to, terse }: Record<string, unknown>) => toMinimalWriteResponse(await memory.invalidateTriple(triple_id as string, valid_to as string | null | undefined), terse as string) ); registerJsonTool( ['localnest_kg_as_of'], { title: 'KG As-Of Query', description: 'Query triples for an entity at a specific point in time. mode="event" (default) returns facts whose valid_from/valid_to bracket the date (event-time axis). mode="transaction" returns every triple LocalNest knew at that time via recorded_at, regardless of valid_to (transaction-time axis).', inputSchema: { entity_id: z.string().min(1).max(400), as_of_date: z.string().min(1), mode: z.enum(['event', 'transaction']).default('event') }, annotations: READ_ONLY_ANNOTATIONS, outputSchema: schemas.OUTPUT_BUNDLE_RESULT_SCHEMA