Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the functionality of timestamps #367

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
19 changes: 15 additions & 4 deletions src/memory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,25 @@ Observations are discrete pieces of information about an entity. They are:
- Attached to specific entities
- Can be added or removed independently
- Should be atomic (one fact per observation)
- Must include a Unix timestamp

Example:
```json
{
"entityName": "John_Smith",
"observations": [
"Speaks fluent Spanish",
"Graduated in 2019",
"Prefers morning meetings"
{
"content": "Speaks fluent Spanish",
"timestamp": 1734940800
},
{
"content": "Graduated in 2019",
"timestamp": 1734940800
},
{
"content": "Prefers morning meetings",
"timestamp": 1734940800
}
]
}
```
Expand Down Expand Up @@ -171,7 +181,8 @@ Follow these steps for each interaction:
- If any new information was gathered during the interaction, update your memory as follows:
a) Create entities for recurring organizations, people, and significant events
b) Connect them to the current entities using relations
b) Store facts about them as observations
c) Store facts about them as observations
d) IMPORTANT: Always include the Unix timestamp with each observation (e.g., for December 17, 2024, use timestamp: 1734940800)
```

## License
Expand Down
95 changes: 69 additions & 26 deletions src/memory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,37 @@ import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';


// Define the path to the JSONL file, you can change this to your desired local path
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const MEMORY_FILE_PATH = path.join(__dirname, 'memory.json');

// Define the TimeManager interface
interface TimeManager {
getCurrentTimestamp(): number;
}

// Implementation that standardizes all timestamps
class UnixTimeManager implements TimeManager {
private lastTimestamp: number = 0;

// Reducing latency
getCurrentTimestamp(): number {
const now = Math.floor(Date.now() / 1000);
if (now !== this.lastTimestamp) {
this.lastTimestamp = now;
}
return this.lastTimestamp;
}
}

// Create a single instance to use throughout the app
const timeManager = new UnixTimeManager();

// We are storing our memory using entities, relations, and observations in a graph structure
interface Entity {
name: string;
entityType: string;
observations: string[];
observations: { content: string; timestamp: number }[]; // Updated to include timestamp
}

interface Relation {
Expand All @@ -35,6 +56,12 @@ interface KnowledgeGraph {

// The KnowledgeGraphManager class contains all operations to interact with the knowledge graph
class KnowledgeGraphManager {
private timeManager: TimeManager;

constructor(timeManager: TimeManager) {
this.timeManager = timeManager;
}

private async loadGraph(): Promise<KnowledgeGraph> {
try {
const data = await fs.readFile(MEMORY_FILE_PATH, "utf-8");
Expand Down Expand Up @@ -81,20 +108,24 @@ class KnowledgeGraphManager {
return newRelations;
}

async addObservations(observations: { entityName: string; contents: string[] }[]): Promise<{ entityName: string; addedObservations: string[] }[]> {
const graph = await this.loadGraph();
const results = observations.map(o => {
const entity = graph.entities.find(e => e.name === o.entityName);
if (!entity) {
throw new Error(`Entity with name ${o.entityName} not found`);
}
const newObservations = o.contents.filter(content => !entity.observations.includes(content));
entity.observations.push(...newObservations);
return { entityName: o.entityName, addedObservations: newObservations };
});
await this.saveGraph(graph);
return results;
}
async addObservations(observations: { entityName: string; contents: string[] }[]): Promise<{ entityName: string; addedObservations: { content: string; timestamp: number }[] }[]> {
const graph = await this.loadGraph();
const timestamp = this.timeManager.getCurrentTimestamp();
const results = observations.map(o => {
const entity = graph.entities.find(e => e.name === o.entityName);
if (!entity) {
throw new Error(`Entity with name ${o.entityName} not found`);
}
const newObservations = o.contents.map(content => ({
content,
timestamp
}));
entity.observations.push(...newObservations);
return { entityName: o.entityName, addedObservations: newObservations };
});
await this.saveGraph(graph);
return results;
}

async deleteEntities(entityNames: string[]): Promise<void> {
const graph = await this.loadGraph();
Expand All @@ -108,7 +139,7 @@ class KnowledgeGraphManager {
deletions.forEach(d => {
const entity = graph.entities.find(e => e.name === d.entityName);
if (entity) {
entity.observations = entity.observations.filter(o => !d.observations.includes(o));
entity.observations = entity.observations.filter(o => !d.observations.includes(o.content));
}
});
await this.saveGraph(graph);
Expand Down Expand Up @@ -136,7 +167,7 @@ class KnowledgeGraphManager {
const filteredEntities = graph.entities.filter(e =>
e.name.toLowerCase().includes(query.toLowerCase()) ||
e.entityType.toLowerCase().includes(query.toLowerCase()) ||
e.observations.some(o => o.toLowerCase().includes(query.toLowerCase()))
e.observations.some(o => o.content.toLowerCase().includes(query.toLowerCase()))
);

// Create a Set of filtered entity names for quick lookup
Expand Down Expand Up @@ -178,9 +209,7 @@ class KnowledgeGraphManager {
}
}

const knowledgeGraphManager = new KnowledgeGraphManager();


const knowledgeGraphManager = new KnowledgeGraphManager(timeManager);
// The server instance and tools exposed to Claude
const server = new Server({
name: "memory-server",
Expand All @@ -189,7 +218,7 @@ const server = new Server({
capabilities: {
tools: {},
},
},);
});

server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
Expand All @@ -209,7 +238,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
entityType: { type: "string", description: "The type of the entity" },
observations: {
type: "array",
items: { type: "string" },
items: {
type: "object",
properties: {
content: { type: "string", description: "An observation content" },
timestamp: { type: "number", description: "The timestamp of the observation" }
},
required: ["timestamp"],
},
description: "An array of observation contents associated with the entity"
},
},
Expand Down Expand Up @@ -256,11 +292,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
entityName: { type: "string", description: "The name of the entity to add the observations to" },
contents: {
type: "array",
items: { type: "string" },
description: "An array of observation contents to add"
items: {
type: "object",
properties: {
content: { type: "string", description: "An observation content" },
timestamp: { type: "number", description: "The timestamp of the observation" }
},
required: ["content", "timestamp"]
},
description: "An array of observations with content and timestamp"
},
},
required: ["entityName", "contents"],
required: ["entityName"],
},
},
},
Expand Down