mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-25 00:16:29 +02:00
voice memos update the graph correctly
This commit is contained in:
parent
81cbd1a891
commit
51f1ce43ae
5 changed files with 334 additions and 209 deletions
|
|
@ -183,6 +183,7 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) =>
|
|||
const chunksRef = React.useRef<Blob[]>([])
|
||||
const notePathRef = React.useRef<string | null>(null)
|
||||
const timestampRef = React.useRef<string | null>(null)
|
||||
const relativePathRef = React.useRef<string | null>(null)
|
||||
|
||||
const startRecording = async () => {
|
||||
try {
|
||||
|
|
@ -195,6 +196,9 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) =>
|
|||
|
||||
timestampRef.current = timestamp
|
||||
notePathRef.current = notePath
|
||||
// Relative path for linking (from knowledge/ root, without .md extension)
|
||||
const relativePath = `Voice Memos/${dateStr}/${noteName}`
|
||||
relativePathRef.current = relativePath
|
||||
|
||||
// Create the note immediately with a "Recording..." placeholder
|
||||
await window.ipc.invoke('workspace:mkdir', {
|
||||
|
|
@ -206,6 +210,7 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) =>
|
|||
|
||||
**Type:** voice memo
|
||||
**Recorded:** ${now.toLocaleString()}
|
||||
**Path:** ${relativePath}
|
||||
|
||||
## Transcript
|
||||
|
||||
|
|
@ -264,11 +269,13 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) =>
|
|||
|
||||
// Update note to show transcribing status
|
||||
const currentNotePath = notePathRef.current
|
||||
if (currentNotePath) {
|
||||
const currentRelativePath = relativePathRef.current
|
||||
if (currentNotePath && currentRelativePath) {
|
||||
const transcribingContent = `# Voice Memo
|
||||
|
||||
**Type:** voice memo
|
||||
**Recorded:** ${new Date().toLocaleString()}
|
||||
**Path:** ${currentRelativePath}
|
||||
|
||||
## Transcript
|
||||
|
||||
|
|
@ -283,12 +290,13 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) =>
|
|||
|
||||
// Transcribe and update the note with the transcript
|
||||
const transcript = await transcribeWithDeepgram(blob)
|
||||
if (currentNotePath) {
|
||||
if (currentNotePath && currentRelativePath) {
|
||||
const finalContent = transcript
|
||||
? `# Voice Memo
|
||||
|
||||
**Type:** voice memo
|
||||
**Recorded:** ${new Date().toLocaleString()}
|
||||
**Path:** ${currentRelativePath}
|
||||
|
||||
## Transcript
|
||||
|
||||
|
|
@ -298,6 +306,7 @@ ${transcript}
|
|||
|
||||
**Type:** voice memo
|
||||
**Recorded:** ${new Date().toLocaleString()}
|
||||
**Path:** ${currentRelativePath}
|
||||
|
||||
## Transcript
|
||||
|
||||
|
|
|
|||
|
|
@ -30,90 +30,87 @@ const SOURCE_FOLDERS = [
|
|||
'granola_notes',
|
||||
];
|
||||
|
||||
// Voice memos are handled separately - they get moved to knowledge/Voice Memos/<date>/
|
||||
const VOICE_MEMOS_DIR = path.join(WorkDir, 'voice_memos');
|
||||
// Voice memos are now created directly in knowledge/Voice Memos/<date>/
|
||||
const VOICE_MEMOS_KNOWLEDGE_DIR = path.join(NOTES_OUTPUT_DIR, 'Voice Memos');
|
||||
|
||||
/**
|
||||
* Extract date from voice memo filename
|
||||
* Filename format: voice-memo-2026-02-03T04-56-53-182Z.txt
|
||||
* Returns date string like "2026-02-03"
|
||||
* Get unprocessed voice memo files from knowledge/Voice Memos/
|
||||
* Voice memos are created directly in this directory by the UI.
|
||||
* Returns paths to files that need entity extraction.
|
||||
*/
|
||||
function extractDateFromVoiceMemoFilename(filename: string): string {
|
||||
// Match the date pattern: YYYY-MM-DD from voice-memo-YYYY-MM-DDTHH-MM-SS-MMMZ
|
||||
const match = filename.match(/voice-memo-(\d{4}-\d{2}-\d{2})T/);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
// Fallback to current date if pattern doesn't match
|
||||
return new Date().toISOString().split('T')[0];
|
||||
}
|
||||
function getUnprocessedVoiceMemos(state: GraphState): string[] {
|
||||
console.log(`[VoiceMemos] Checking directory: ${VOICE_MEMOS_KNOWLEDGE_DIR}`);
|
||||
|
||||
/**
|
||||
* Move voice memo transcripts to knowledge/Voice Memos/<date>/
|
||||
* Returns info about moved files for processing.
|
||||
*/
|
||||
function moveVoiceMemosToKnowledge(state: GraphState): { sourcePath: string; targetPath: string }[] {
|
||||
if (!fs.existsSync(VOICE_MEMOS_DIR)) {
|
||||
if (!fs.existsSync(VOICE_MEMOS_KNOWLEDGE_DIR)) {
|
||||
console.log(`[VoiceMemos] Directory does not exist`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const movedFiles: { sourcePath: string; targetPath: string }[] = [];
|
||||
const entries = fs.readdirSync(VOICE_MEMOS_DIR);
|
||||
const unprocessedFiles: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
// Only process .txt transcript files
|
||||
if (!entry.endsWith('.txt')) {
|
||||
// Scan date folders (e.g., 2026-02-03)
|
||||
const dateFolders = fs.readdirSync(VOICE_MEMOS_KNOWLEDGE_DIR);
|
||||
console.log(`[VoiceMemos] Found ${dateFolders.length} date folders: ${dateFolders.join(', ')}`);
|
||||
|
||||
for (const dateFolder of dateFolders) {
|
||||
const dateFolderPath = path.join(VOICE_MEMOS_KNOWLEDGE_DIR, dateFolder);
|
||||
|
||||
// Skip if not a directory
|
||||
try {
|
||||
if (!fs.statSync(dateFolderPath).isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`[VoiceMemos] Error checking ${dateFolderPath}:`, err);
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourcePath = path.join(VOICE_MEMOS_DIR, entry);
|
||||
// Scan markdown files in this date folder
|
||||
const files = fs.readdirSync(dateFolderPath);
|
||||
console.log(`[VoiceMemos] Found ${files.length} files in ${dateFolder}: ${files.join(', ')}`);
|
||||
|
||||
// Skip if already processed (check by source path)
|
||||
if (state.processedFiles[sourcePath]) {
|
||||
continue;
|
||||
for (const file of files) {
|
||||
// Only process voice memo markdown files
|
||||
if (!file.endsWith('.md') || !file.startsWith('voice-memo-')) {
|
||||
console.log(`[VoiceMemos] Skipping ${file} - not a voice memo file`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = path.join(dateFolderPath, file);
|
||||
|
||||
// Skip if already processed
|
||||
if (state.processedFiles[filePath]) {
|
||||
console.log(`[VoiceMemos] Skipping ${file} - already processed`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the file has actual content (not still recording/transcribing)
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
// Skip files that are still recording or transcribing
|
||||
if (content.includes('*Recording in progress...*')) {
|
||||
console.log(`[VoiceMemos] Skipping ${file} - still recording`);
|
||||
continue;
|
||||
}
|
||||
if (content.includes('*Transcribing...*')) {
|
||||
console.log(`[VoiceMemos] Skipping ${file} - still transcribing`);
|
||||
continue;
|
||||
}
|
||||
if (content.includes('*Transcription failed')) {
|
||||
console.log(`[VoiceMemos] Skipping ${file} - transcription failed`);
|
||||
continue;
|
||||
}
|
||||
console.log(`[VoiceMemos] Found unprocessed voice memo: ${file}`);
|
||||
unprocessedFiles.push(filePath);
|
||||
} catch (err) {
|
||||
console.log(`[VoiceMemos] Error reading ${file}:`, err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract date and create target directory
|
||||
const date = extractDateFromVoiceMemoFilename(entry);
|
||||
const targetDir = path.join(VOICE_MEMOS_KNOWLEDGE_DIR, date);
|
||||
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Convert .txt to .md and create a proper markdown file
|
||||
const baseName = entry.replace('.txt', '');
|
||||
const targetPath = path.join(targetDir, `${baseName}.md`);
|
||||
|
||||
// Read original content and wrap in markdown format
|
||||
const originalContent = fs.readFileSync(sourcePath, 'utf-8');
|
||||
|
||||
// Create a relative path for linking (from knowledge/ root)
|
||||
const relativePath = `Voice Memos/${date}/${baseName}`;
|
||||
|
||||
const mdContent = `# Voice Memo - ${date}
|
||||
|
||||
**Type:** voice memo
|
||||
**Recorded:** ${date}
|
||||
**Path:** ${relativePath}
|
||||
|
||||
## Transcript
|
||||
|
||||
${originalContent}
|
||||
`;
|
||||
|
||||
// Write to knowledge directory
|
||||
fs.writeFileSync(targetPath, mdContent);
|
||||
console.log(`[VoiceMemos] Moved transcript to ${targetPath}`);
|
||||
|
||||
// Delete the original .txt file (it's been moved)
|
||||
fs.unlinkSync(sourcePath);
|
||||
|
||||
movedFiles.push({ sourcePath, targetPath });
|
||||
}
|
||||
|
||||
return movedFiles;
|
||||
console.log(`[VoiceMemos] Total unprocessed files: ${unprocessedFiles.length}`);
|
||||
return unprocessedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -272,23 +269,26 @@ export async function buildGraph(sourceDir: string): Promise<void> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Process voice memos and run entity extraction on them
|
||||
* Process voice memos from knowledge/Voice Memos/ and run entity extraction on them
|
||||
* Voice memos are now created directly in the knowledge directory by the UI.
|
||||
*/
|
||||
async function processVoiceMemosForKnowledge(): Promise<boolean> {
|
||||
console.log(`[VoiceMemos] Starting voice memo processing...`);
|
||||
const state = loadState();
|
||||
|
||||
// Move voice memos to knowledge directory
|
||||
const movedFiles = moveVoiceMemosToKnowledge(state);
|
||||
// Get unprocessed voice memos from knowledge/Voice Memos/
|
||||
const unprocessedFiles = getUnprocessedVoiceMemos(state);
|
||||
|
||||
if (movedFiles.length === 0) {
|
||||
if (unprocessedFiles.length === 0) {
|
||||
console.log(`[VoiceMemos] No unprocessed voice memos found`);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(`[VoiceMemos] Processing ${movedFiles.length} voice memo transcripts for entity extraction...`);
|
||||
console.log(`[VoiceMemos] Processing ${unprocessedFiles.length} voice memo transcripts for entity extraction...`);
|
||||
console.log(`[VoiceMemos] Files to process: ${unprocessedFiles.map(f => path.basename(f)).join(', ')}`);
|
||||
|
||||
// Read the moved files
|
||||
const targetPaths = movedFiles.map(f => f.targetPath);
|
||||
const contentFiles = await readFileContents(targetPaths);
|
||||
// Read the files
|
||||
const contentFiles = await readFileContents(unprocessedFiles);
|
||||
|
||||
if (contentFiles.length === 0) {
|
||||
return false;
|
||||
|
|
@ -300,7 +300,6 @@ async function processVoiceMemosForKnowledge(): Promise<boolean> {
|
|||
|
||||
for (let i = 0; i < contentFiles.length; i += BATCH_SIZE) {
|
||||
const batch = contentFiles.slice(i, i + BATCH_SIZE);
|
||||
const batchMovedFiles = movedFiles.slice(i, i + BATCH_SIZE);
|
||||
const batchNumber = Math.floor(i / BATCH_SIZE) + 1;
|
||||
|
||||
try {
|
||||
|
|
@ -313,11 +312,9 @@ async function processVoiceMemosForKnowledge(): Promise<boolean> {
|
|||
await createNotesFromBatch(batch, batchNumber, indexForPrompt);
|
||||
console.log(`[VoiceMemos] Batch ${batchNumber}/${totalBatches} complete`);
|
||||
|
||||
// Mark files as processed using SOURCE path as key (to prevent reprocessing)
|
||||
for (const { sourcePath, targetPath } of batchMovedFiles) {
|
||||
markFileAsProcessed(targetPath, state);
|
||||
// Also track by source path so we don't reprocess if a new file with same name appears
|
||||
state.processedFiles[sourcePath] = state.processedFiles[targetPath];
|
||||
// Mark files as processed
|
||||
for (const file of batch) {
|
||||
markFileAsProcessed(file.path, state);
|
||||
}
|
||||
|
||||
// Save state after each batch
|
||||
|
|
@ -392,7 +389,7 @@ async function processAllSources(): Promise<void> {
|
|||
*/
|
||||
export async function init() {
|
||||
console.log('[GraphBuilder] Starting Knowledge Graph Builder Service...');
|
||||
console.log(`[GraphBuilder] Monitoring folders: ${SOURCE_FOLDERS.join(', ')}, voice_memos`);
|
||||
console.log(`[GraphBuilder] Monitoring folders: ${SOURCE_FOLDERS.join(', ')}, knowledge/Voice Memos`);
|
||||
console.log(`[GraphBuilder] Will check for new content every ${SYNC_INTERVAL_MS / 1000} seconds`);
|
||||
|
||||
// Initial run
|
||||
|
|
|
|||
|
|
@ -7,15 +7,21 @@ tools:
|
|||
workspace-readFile:
|
||||
type: builtin
|
||||
name: workspace-readFile
|
||||
workspace-edit:
|
||||
type: builtin
|
||||
name: workspace-edit
|
||||
workspace-readdir:
|
||||
type: builtin
|
||||
name: workspace-readdir
|
||||
workspace-mkdir:
|
||||
type: builtin
|
||||
name: workspace-mkdir
|
||||
executeCommand:
|
||||
workspace-grep:
|
||||
type: builtin
|
||||
name: executeCommand
|
||||
name: workspace-grep
|
||||
workspace-glob:
|
||||
type: builtin
|
||||
name: workspace-glob
|
||||
---
|
||||
# Task
|
||||
|
||||
|
|
@ -70,20 +76,51 @@ When you need to:
|
|||
|
||||
# Tools Available
|
||||
|
||||
You have access to \`executeCommand\` to run shell commands:
|
||||
You have access to these tools:
|
||||
|
||||
**For reading files:**
|
||||
\`\`\`
|
||||
executeCommand("ls {path}") # List directory contents
|
||||
executeCommand("cat {path}") # Read file contents
|
||||
executeCommand("head -50 {path}") # Read first 50 lines
|
||||
executeCommand("write {path} {content}") # Create or overwrite file
|
||||
workspace-readFile({ path: "knowledge/People/Sarah Chen.md" })
|
||||
\`\`\`
|
||||
|
||||
**Important:** Use shell escaping for paths with spaces:
|
||||
**For creating NEW files:**
|
||||
\`\`\`
|
||||
executeCommand("cat 'knowledge_folder/People/Sarah Chen.md'")
|
||||
workspace-writeFile({ path: "knowledge/People/Sarah Chen.md", data: "# Sarah Chen\\n\\n..." })
|
||||
\`\`\`
|
||||
|
||||
**NOTE:** Do NOT use grep to search for entities. Use the provided knowledge_index instead.
|
||||
**For editing EXISTING files (preferred for updates):**
|
||||
\`\`\`
|
||||
workspace-edit({
|
||||
path: "knowledge/People/Sarah Chen.md",
|
||||
oldString: "## Activity\\n",
|
||||
newString: "## Activity\\n- **2026-02-03** (meeting): New activity entry\\n"
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
**For listing directories:**
|
||||
\`\`\`
|
||||
workspace-readdir({ path: "knowledge/People" })
|
||||
\`\`\`
|
||||
|
||||
**For creating directories:**
|
||||
\`\`\`
|
||||
workspace-mkdir({ path: "knowledge/Projects", recursive: true })
|
||||
\`\`\`
|
||||
|
||||
**For searching files:**
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "Acme Corp", searchPath: "knowledge", fileGlob: "*.md" })
|
||||
\`\`\`
|
||||
|
||||
**For finding files by pattern:**
|
||||
\`\`\`
|
||||
workspace-glob({ pattern: "**/*.md", cwd: "knowledge/People" })
|
||||
\`\`\`
|
||||
|
||||
**IMPORTANT:**
|
||||
- Use \`workspace-edit\` for updating existing notes (adding activity, updating fields)
|
||||
- Use \`workspace-writeFile\` only for creating new notes
|
||||
- Prefer the knowledge_index for entity resolution (it's faster than grep)
|
||||
|
||||
# Output
|
||||
|
||||
|
|
@ -113,7 +150,7 @@ Either:
|
|||
|
||||
Read the source file and determine if it's a meeting or email.
|
||||
\`\`\`
|
||||
executeCommand("cat '{source_file}'")
|
||||
workspace-readFile({ path: "{source_file}" })
|
||||
\`\`\`
|
||||
|
||||
**Meeting indicators:**
|
||||
|
|
@ -307,9 +344,9 @@ If someone only appears in your memory as "CC'd on outreach emails from [Sender]
|
|||
## Email-Specific Filtering
|
||||
|
||||
For emails, check if sender/recipients have existing notes:
|
||||
\`\`\`bash
|
||||
executeCommand("grep -r -i -l '{sender email}' '{knowledge_folder}/'")
|
||||
executeCommand("grep -r -i -l '{sender name}' '{knowledge_folder}/People/'")
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "{sender email}", searchPath: "{knowledge_folder}" })
|
||||
workspace-grep({ pattern: "{sender name}", searchPath: "{knowledge_folder}/People" })
|
||||
\`\`\`
|
||||
|
||||
**If no existing note found:**
|
||||
|
|
@ -349,7 +386,7 @@ If processing, continue to Step 2.
|
|||
|
||||
# Step 2: Read and Parse Source File
|
||||
\`\`\`
|
||||
executeCommand("cat '{source_file}'")
|
||||
workspace-readFile({ path: "{source_file}" })
|
||||
\`\`\`
|
||||
|
||||
Extract metadata:
|
||||
|
|
@ -446,7 +483,7 @@ From index, find matches for:
|
|||
|
||||
Only read the full note content when you need details not in the index (e.g., activity logs, open items):
|
||||
\`\`\`bash
|
||||
executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'")
|
||||
workspace-readFile({ path: "{knowledge_folder}/People/Sarah Chen.md" })
|
||||
\`\`\`
|
||||
|
||||
**Why read these notes:**
|
||||
|
|
@ -530,27 +567,27 @@ Resolution Map:
|
|||
When multiple candidates match a variant, disambiguate:
|
||||
|
||||
**By organization (strongest signal):**
|
||||
\`\`\`bash
|
||||
\`\`\`
|
||||
# "David" could be David Kim or David Chen
|
||||
executeCommand("grep -i 'Acme' '{knowledge_folder}/People/David Kim.md'")
|
||||
workspace-grep({ pattern: "Acme", searchPath: "{knowledge_folder}/People/David Kim.md" })
|
||||
# Output: **Organization:** [[Acme Corp]]
|
||||
|
||||
executeCommand("grep -i 'Acme' '{knowledge_folder}/People/David Chen.md'")
|
||||
workspace-grep({ pattern: "Acme", searchPath: "{knowledge_folder}/People/David Chen.md" })
|
||||
# Output: **Organization:** [[Other Corp]]
|
||||
|
||||
# Source is from Acme context → "David" = "David Kim"
|
||||
\`\`\`
|
||||
|
||||
**By email (definitive):**
|
||||
\`\`\`bash
|
||||
executeCommand("grep -i 'david@acme.com' '{knowledge_folder}/People/David Kim.md'")
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "david@acme.com", searchPath: "{knowledge_folder}/People/David Kim.md" })
|
||||
# Exact email match is definitive
|
||||
\`\`\`
|
||||
|
||||
**By role:**
|
||||
\`\`\`bash
|
||||
\`\`\`
|
||||
# Source mentions "their CTO"
|
||||
executeCommand("grep -r -i 'Role.*CTO' '{knowledge_folder}/People/'")
|
||||
workspace-grep({ pattern: "Role.*CTO", searchPath: "{knowledge_folder}/People" })
|
||||
# Filter results by organization context
|
||||
\`\`\`
|
||||
|
||||
|
|
@ -979,8 +1016,8 @@ STATE CHANGES:
|
|||
Before writing, compare extracted content against existing notes.
|
||||
|
||||
## Check Activity Log
|
||||
\`\`\`bash
|
||||
executeCommand("grep '2025-01-15' '{knowledge_folder}/People/Sarah Chen.md'")
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "2025-01-15", searchPath: "{knowledge_folder}/People/Sarah Chen.md" })
|
||||
\`\`\`
|
||||
|
||||
If an entry for this date/source already exists, this may have been processed. Skip or verify different interaction.
|
||||
|
|
@ -1010,28 +1047,28 @@ If new info contradicts existing:
|
|||
|
||||
**IMPORTANT: Write sequentially, one file at a time.**
|
||||
- Generate content for exactly one note.
|
||||
- Issue exactly one \`write\` command.
|
||||
- Issue exactly one write/edit command.
|
||||
- Wait for the tool to return before generating the next note.
|
||||
- Do NOT batch multiple \`write\` commands in a single response.
|
||||
- Do NOT batch multiple write commands in a single response.
|
||||
|
||||
**For new entities (meetings only):**
|
||||
\`\`\`bash
|
||||
executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'")
|
||||
**For NEW entities (use workspace-writeFile):**
|
||||
\`\`\`
|
||||
workspace-writeFile({
|
||||
path: "{knowledge_folder}/People/Jennifer.md",
|
||||
data: "# Jennifer\\n\\n## Summary\\n..."
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
**For existing entities:**
|
||||
- Read current content first
|
||||
- Add activity entry at TOP of Activity section (reverse chronological)
|
||||
- Update "Last seen" date
|
||||
- Add new key facts (skip duplicates)
|
||||
- Add new open items
|
||||
- Add new decisions
|
||||
- Add new relationships
|
||||
- Update summary ONLY if significant new understanding
|
||||
\`\`\`bash
|
||||
executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'")
|
||||
# ... modify content ...
|
||||
executeCommand("write '{knowledge_folder}/People/Sarah Chen.md' '{full_updated_content}'")
|
||||
**For EXISTING entities (use workspace-edit):**
|
||||
- Read current content first with workspace-readFile
|
||||
- Use workspace-edit to add activity entry at TOP (reverse chronological)
|
||||
- Update fields using targeted edits
|
||||
\`\`\`
|
||||
workspace-edit({
|
||||
path: "{knowledge_folder}/People/Sarah Chen.md",
|
||||
oldString: "## Activity\\n",
|
||||
newString: "## Activity\\n- **2026-02-03** (meeting): Met to discuss project timeline\\n"
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
## 9b: Emails — Update Existing Notes Only
|
||||
|
|
@ -1054,7 +1091,7 @@ For each state change identified in Step 7:
|
|||
### Update Project Status
|
||||
\`\`\`bash
|
||||
# Read current project note
|
||||
executeCommand("cat '{knowledge_folder}/Projects/Acme Integration.md'")
|
||||
workspace-readFile({ path: "{knowledge_folder}/Projects/Acme Integration.md" })
|
||||
|
||||
# Update the Status field
|
||||
# Change: **Status:** planning
|
||||
|
|
@ -1064,7 +1101,7 @@ executeCommand("cat '{knowledge_folder}/Projects/Acme Integration.md'")
|
|||
### Mark Open Items Complete
|
||||
\`\`\`bash
|
||||
# Read current note
|
||||
executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'")
|
||||
workspace-readFile({ path: "{knowledge_folder}/People/Sarah Chen.md" })
|
||||
|
||||
# Find matching open item and update
|
||||
# Change: - [ ] Send API documentation — by Friday
|
||||
|
|
@ -1074,7 +1111,7 @@ executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'")
|
|||
### Update Role
|
||||
\`\`\`bash
|
||||
# Read current person note
|
||||
executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'")
|
||||
workspace-readFile({ path: "{knowledge_folder}/People/Sarah Chen.md" })
|
||||
|
||||
# Update role field
|
||||
# Change: **Role:** Engineering Lead
|
||||
|
|
@ -1084,7 +1121,7 @@ executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'")
|
|||
### Update Relationship
|
||||
\`\`\`bash
|
||||
# Read current org note
|
||||
executeCommand("cat '{knowledge_folder}/Organizations/Acme Corp.md'")
|
||||
workspace-readFile({ path: "{knowledge_folder}/Organizations/Acme Corp.md" })
|
||||
|
||||
# Update relationship field
|
||||
# Change: **Relationship:** prospect
|
||||
|
|
@ -1149,8 +1186,8 @@ This ensures:
|
|||
## Check Each New Link
|
||||
|
||||
If you added \`[[People/Jennifer]]\` to \`Organizations/Acme Corp.md\`:
|
||||
\`\`\`bash
|
||||
executeCommand("grep 'Acme Corp' '{knowledge_folder}/People/Jennifer.md'")
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "Acme Corp", searchPath: "{knowledge_folder}/People/Jennifer.md" })
|
||||
\`\`\`
|
||||
|
||||
If not found, update Jennifer.md to add the link.
|
||||
|
|
@ -1384,11 +1421,11 @@ Not mass email, not automated. Continue.
|
|||
- Variants: "Sarah Chen", "sarah@acme.com", "David Kim", "David", "Jennifer", "CTO", "Acme", "the pilot"
|
||||
|
||||
### Step 3: Search Existing Notes
|
||||
\`\`\`bash
|
||||
executeCommand("grep -r -i -l 'Sarah Chen' 'knowledge/'")
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "Sarah Chen", searchPath: "knowledge" })
|
||||
# Output: (none)
|
||||
|
||||
executeCommand("grep -r -i -l 'acme' 'knowledge/'")
|
||||
workspace-grep({ pattern: "acme", searchPath: "knowledge" })
|
||||
# Output: (none)
|
||||
\`\`\`
|
||||
|
||||
|
|
@ -1527,8 +1564,8 @@ VP Engineering, Acme Corp
|
|||
### Step 1: Filter
|
||||
|
||||
Check for existing relationship:
|
||||
\`\`\`bash
|
||||
executeCommand("grep -r -i -l 'sarah@acme.com' 'knowledge/'")
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "sarah@acme.com", searchPath: "knowledge" })
|
||||
# Output: notes/People/Sarah Chen.md
|
||||
\`\`\`
|
||||
|
||||
|
|
@ -1663,11 +1700,11 @@ John Smith
|
|||
### Step 1: Filter
|
||||
|
||||
Check for existing relationship:
|
||||
\`\`\`bash
|
||||
executeCommand("grep -r -i -l 'randomvendor' 'knowledge/'")
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "randomvendor", searchPath: "knowledge" })
|
||||
# Output: (none)
|
||||
|
||||
executeCommand("grep -r -i -l 'John Smith' 'knowledge/'")
|
||||
workspace-grep({ pattern: "John Smith", searchPath: "knowledge" })
|
||||
# Output: (none)
|
||||
\`\`\`
|
||||
|
||||
|
|
@ -1710,8 +1747,8 @@ David
|
|||
### Step 1: Filter
|
||||
|
||||
Check for sender:
|
||||
\`\`\`bash
|
||||
executeCommand("grep -r -i -l 'david@friendly.vc' 'knowledge/'")
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "david@friendly.vc", searchPath: "knowledge" })
|
||||
# Output: notes/People/David Park.md
|
||||
\`\`\`
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,21 @@ tools:
|
|||
workspace-readFile:
|
||||
type: builtin
|
||||
name: workspace-readFile
|
||||
workspace-edit:
|
||||
type: builtin
|
||||
name: workspace-edit
|
||||
workspace-readdir:
|
||||
type: builtin
|
||||
name: workspace-readdir
|
||||
workspace-mkdir:
|
||||
type: builtin
|
||||
name: workspace-mkdir
|
||||
executeCommand:
|
||||
workspace-grep:
|
||||
type: builtin
|
||||
name: executeCommand
|
||||
name: workspace-grep
|
||||
workspace-glob:
|
||||
type: builtin
|
||||
name: workspace-glob
|
||||
---
|
||||
# Task
|
||||
|
||||
|
|
@ -70,20 +76,51 @@ When you need to:
|
|||
|
||||
# Tools Available
|
||||
|
||||
You have access to \`executeCommand\` to run shell commands:
|
||||
You have access to these tools:
|
||||
|
||||
**For reading files:**
|
||||
\`\`\`
|
||||
executeCommand("ls {path}") # List directory contents
|
||||
executeCommand("cat {path}") # Read file contents
|
||||
executeCommand("head -50 {path}") # Read first 50 lines
|
||||
executeCommand("write {path} {content}") # Create or overwrite file
|
||||
workspace-readFile({ path: "knowledge/People/Sarah Chen.md" })
|
||||
\`\`\`
|
||||
|
||||
**Important:** Use shell escaping for paths with spaces:
|
||||
**For creating NEW files:**
|
||||
\`\`\`
|
||||
executeCommand("cat 'knowledge_folder/People/Sarah Chen.md'")
|
||||
workspace-writeFile({ path: "knowledge/People/Sarah Chen.md", data: "# Sarah Chen\\n\\n..." })
|
||||
\`\`\`
|
||||
|
||||
**NOTE:** Do NOT use grep to search for entities. Use the provided knowledge_index instead.
|
||||
**For editing EXISTING files (preferred for updates):**
|
||||
\`\`\`
|
||||
workspace-edit({
|
||||
path: "knowledge/People/Sarah Chen.md",
|
||||
oldString: "## Activity\\n",
|
||||
newString: "## Activity\\n- **2026-02-03** (meeting): New activity entry\\n"
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
**For listing directories:**
|
||||
\`\`\`
|
||||
workspace-readdir({ path: "knowledge/People" })
|
||||
\`\`\`
|
||||
|
||||
**For creating directories:**
|
||||
\`\`\`
|
||||
workspace-mkdir({ path: "knowledge/Projects", recursive: true })
|
||||
\`\`\`
|
||||
|
||||
**For searching files:**
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "Acme Corp", searchPath: "knowledge", fileGlob: "*.md" })
|
||||
\`\`\`
|
||||
|
||||
**For finding files by pattern:**
|
||||
\`\`\`
|
||||
workspace-glob({ pattern: "**/*.md", cwd: "knowledge/People" })
|
||||
\`\`\`
|
||||
|
||||
**IMPORTANT:**
|
||||
- Use \`workspace-edit\` for updating existing notes (adding activity, updating fields)
|
||||
- Use \`workspace-writeFile\` only for creating new notes
|
||||
- Prefer the knowledge_index for entity resolution (it's faster than grep)
|
||||
|
||||
# Output
|
||||
|
||||
|
|
@ -120,7 +157,7 @@ This mode prioritizes comprehensive capture over selectivity. The goal is to nev
|
|||
|
||||
Read the source file and determine if it's a meeting or email.
|
||||
\`\`\`
|
||||
executeCommand("cat '{source_file}'")
|
||||
workspace-readFile({ path: "{source_file}" })
|
||||
\`\`\`
|
||||
|
||||
**Meeting indicators:**
|
||||
|
|
@ -210,7 +247,7 @@ If processing, continue to Step 2.
|
|||
|
||||
# Step 2: Read and Parse Source File
|
||||
\`\`\`
|
||||
executeCommand("cat '{source_file}'")
|
||||
workspace-readFile({ path: "{source_file}" })
|
||||
\`\`\`
|
||||
|
||||
Extract metadata:
|
||||
|
|
@ -297,8 +334,8 @@ From index, find matches for:
|
|||
## 3d: Read Full Notes When Needed
|
||||
|
||||
Only read the full note content when you need details not in the index (e.g., activity logs, open items):
|
||||
\`\`\`bash
|
||||
executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'")
|
||||
\`\`\`
|
||||
workspace-readFile({ path: "{knowledge_folder}/People/Sarah Chen.md" })
|
||||
\`\`\`
|
||||
|
||||
**Why read these notes:**
|
||||
|
|
@ -566,21 +603,29 @@ Before writing:
|
|||
|
||||
**IMPORTANT: Write sequentially, one file at a time.**
|
||||
- Generate content for exactly one note.
|
||||
- Issue exactly one \`write\` command.
|
||||
- Issue exactly one write/edit command.
|
||||
- Wait for the tool to return before generating the next note.
|
||||
- Do NOT batch multiple \`write\` commands in a single response.
|
||||
- Do NOT batch multiple write commands in a single response.
|
||||
|
||||
**For new entities:**
|
||||
\`\`\`bash
|
||||
executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'")
|
||||
**For NEW entities (use workspace-writeFile):**
|
||||
\`\`\`
|
||||
workspace-writeFile({
|
||||
path: "{knowledge_folder}/People/Jennifer.md",
|
||||
data: "# Jennifer\\n\\n## Summary\\n..."
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
**For existing entities:**
|
||||
- Read current content first
|
||||
- Add activity entry at TOP (reverse chronological)
|
||||
- Update "Last seen" date
|
||||
- Add new key facts (skip duplicates)
|
||||
- Add new open items
|
||||
**For EXISTING entities (use workspace-edit):**
|
||||
- Read current content first with workspace-readFile
|
||||
- Use workspace-edit to add activity entry at TOP (reverse chronological)
|
||||
- Update fields using targeted edits
|
||||
\`\`\`
|
||||
workspace-edit({
|
||||
path: "{knowledge_folder}/People/Sarah Chen.md",
|
||||
oldString: "## Activity\\n",
|
||||
newString: "## Activity\\n- **2026-02-03** (meeting): Met to discuss project timeline\\n"
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
## 9b: Apply State Changes
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,21 @@ tools:
|
|||
workspace-readFile:
|
||||
type: builtin
|
||||
name: workspace-readFile
|
||||
workspace-edit:
|
||||
type: builtin
|
||||
name: workspace-edit
|
||||
workspace-readdir:
|
||||
type: builtin
|
||||
name: workspace-readdir
|
||||
workspace-mkdir:
|
||||
type: builtin
|
||||
name: workspace-mkdir
|
||||
executeCommand:
|
||||
workspace-grep:
|
||||
type: builtin
|
||||
name: executeCommand
|
||||
name: workspace-grep
|
||||
workspace-glob:
|
||||
type: builtin
|
||||
name: workspace-glob
|
||||
---
|
||||
# Task
|
||||
|
||||
|
|
@ -70,20 +76,51 @@ When you need to:
|
|||
|
||||
# Tools Available
|
||||
|
||||
You have access to \`executeCommand\` to run shell commands:
|
||||
You have access to these tools:
|
||||
|
||||
**For reading files:**
|
||||
\`\`\`
|
||||
executeCommand("ls {path}") # List directory contents
|
||||
executeCommand("cat {path}") # Read file contents
|
||||
executeCommand("head -50 {path}") # Read first 50 lines
|
||||
executeCommand("write {path} {content}") # Create or overwrite file
|
||||
workspace-readFile({ path: "knowledge/People/Sarah Chen.md" })
|
||||
\`\`\`
|
||||
|
||||
**Important:** Use shell escaping for paths with spaces:
|
||||
**For creating NEW files:**
|
||||
\`\`\`
|
||||
executeCommand("cat 'knowledge_folder/People/Sarah Chen.md'")
|
||||
workspace-writeFile({ path: "knowledge/People/Sarah Chen.md", data: "# Sarah Chen\\n\\n..." })
|
||||
\`\`\`
|
||||
|
||||
**NOTE:** Do NOT use grep to search for entities. Use the provided knowledge_index instead.
|
||||
**For editing EXISTING files (preferred for updates):**
|
||||
\`\`\`
|
||||
workspace-edit({
|
||||
path: "knowledge/People/Sarah Chen.md",
|
||||
oldString: "## Activity\\n",
|
||||
newString: "## Activity\\n- **2026-02-03** (meeting): New activity entry\\n"
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
**For listing directories:**
|
||||
\`\`\`
|
||||
workspace-readdir({ path: "knowledge/People" })
|
||||
\`\`\`
|
||||
|
||||
**For creating directories:**
|
||||
\`\`\`
|
||||
workspace-mkdir({ path: "knowledge/Projects", recursive: true })
|
||||
\`\`\`
|
||||
|
||||
**For searching files:**
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "Acme Corp", searchPath: "knowledge", fileGlob: "*.md" })
|
||||
\`\`\`
|
||||
|
||||
**For finding files by pattern:**
|
||||
\`\`\`
|
||||
workspace-glob({ pattern: "**/*.md", cwd: "knowledge/People" })
|
||||
\`\`\`
|
||||
|
||||
**IMPORTANT:**
|
||||
- Use \`workspace-edit\` for updating existing notes (adding activity, updating fields)
|
||||
- Use \`workspace-writeFile\` only for creating new notes
|
||||
- Prefer the knowledge_index for entity resolution (it's faster than grep)
|
||||
|
||||
# Output
|
||||
|
||||
|
|
@ -119,7 +156,7 @@ Either:
|
|||
|
||||
Read the source file and determine if it's a meeting or email.
|
||||
\`\`\`
|
||||
executeCommand("cat '{source_file}'")
|
||||
workspace-readFile({ path: "{source_file}" })
|
||||
\`\`\`
|
||||
|
||||
**Meeting indicators:**
|
||||
|
|
@ -350,7 +387,7 @@ If processing, continue to Step 2.
|
|||
|
||||
# Step 2: Read and Parse Source File
|
||||
\`\`\`
|
||||
executeCommand("cat '{source_file}'")
|
||||
workspace-readFile({ path: "{source_file}" })
|
||||
\`\`\`
|
||||
|
||||
Extract metadata:
|
||||
|
|
@ -447,7 +484,7 @@ From index, find matches for:
|
|||
|
||||
Only read the full note content when you need details not in the index (e.g., activity logs, open items):
|
||||
\`\`\`bash
|
||||
executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'")
|
||||
workspace-readFile({ path: "{knowledge_folder}/People/Sarah Chen.md" })
|
||||
\`\`\`
|
||||
|
||||
**Why read these notes:**
|
||||
|
|
@ -531,27 +568,27 @@ Resolution Map:
|
|||
When multiple candidates match a variant, disambiguate:
|
||||
|
||||
**By organization (strongest signal):**
|
||||
\`\`\`bash
|
||||
\`\`\`
|
||||
# "David" could be David Kim or David Chen
|
||||
executeCommand("grep -i 'Acme' '{knowledge_folder}/People/David Kim.md'")
|
||||
workspace-grep({ pattern: "Acme", searchPath: "{knowledge_folder}/People/David Kim.md" })
|
||||
# Output: **Organization:** [[Acme Corp]]
|
||||
|
||||
executeCommand("grep -i 'Acme' '{knowledge_folder}/People/David Chen.md'")
|
||||
workspace-grep({ pattern: "Acme", searchPath: "{knowledge_folder}/People/David Chen.md" })
|
||||
# Output: **Organization:** [[Other Corp]]
|
||||
|
||||
# Source is from Acme context → "David" = "David Kim"
|
||||
\`\`\`
|
||||
|
||||
**By email (definitive):**
|
||||
\`\`\`bash
|
||||
executeCommand("grep -i 'david@acme.com' '{knowledge_folder}/People/David Kim.md'")
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "david@acme.com", searchPath: "{knowledge_folder}/People/David Kim.md" })
|
||||
# Exact email match is definitive
|
||||
\`\`\`
|
||||
|
||||
**By role:**
|
||||
\`\`\`bash
|
||||
\`\`\`
|
||||
# Source mentions "their CTO"
|
||||
executeCommand("grep -r -i 'Role.*CTO' '{knowledge_folder}/People/'")
|
||||
workspace-grep({ pattern: "Role.*CTO", searchPath: "{knowledge_folder}/People" })
|
||||
# Filter results by organization context
|
||||
\`\`\`
|
||||
|
||||
|
|
@ -888,8 +925,8 @@ STATE CHANGES:
|
|||
Before writing, compare extracted content against existing notes.
|
||||
|
||||
## Check Activity Log
|
||||
\`\`\`bash
|
||||
executeCommand("grep '2025-01-15' '{knowledge_folder}/People/Sarah Chen.md'")
|
||||
\`\`\`
|
||||
workspace-grep({ pattern: "2025-01-15", searchPath: "{knowledge_folder}/People/Sarah Chen.md" })
|
||||
\`\`\`
|
||||
|
||||
If an entry for this date/source already exists, this may have been processed. Skip or verify different interaction.
|
||||
|
|
@ -919,28 +956,28 @@ If new info contradicts existing:
|
|||
|
||||
**IMPORTANT: Write sequentially, one file at a time.**
|
||||
- Generate content for exactly one note.
|
||||
- Issue exactly one \`write\` command.
|
||||
- Issue exactly one write/edit command.
|
||||
- Wait for the tool to return before generating the next note.
|
||||
- Do NOT batch multiple \`write\` commands in a single response.
|
||||
- Do NOT batch multiple write commands in a single response.
|
||||
|
||||
**For new entities (meetings and qualifying emails):**
|
||||
\`\`\`bash
|
||||
executeCommand("write '{knowledge_folder}/People/Jennifer.md' '{content}'")
|
||||
**For NEW entities (use workspace-writeFile):**
|
||||
\`\`\`
|
||||
workspace-writeFile({
|
||||
path: "{knowledge_folder}/People/Jennifer.md",
|
||||
data: "# Jennifer\\n\\n## Summary\\n..."
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
**For existing entities:**
|
||||
- Read current content first
|
||||
- Add activity entry at TOP of Activity section (reverse chronological)
|
||||
- Update "Last seen" date
|
||||
- Add new key facts (skip duplicates)
|
||||
- Add new open items
|
||||
- Add new decisions
|
||||
- Add new relationships
|
||||
- Update summary ONLY if significant new understanding
|
||||
\`\`\`bash
|
||||
executeCommand("cat '{knowledge_folder}/People/Sarah Chen.md'")
|
||||
# ... modify content ...
|
||||
executeCommand("write '{knowledge_folder}/People/Sarah Chen.md' '{full_updated_content}'")
|
||||
**For EXISTING entities (use workspace-edit):**
|
||||
- Read current content first with workspace-readFile
|
||||
- Use workspace-edit to add activity entry at TOP (reverse chronological)
|
||||
- Update fields using targeted edits
|
||||
\`\`\`
|
||||
workspace-edit({
|
||||
path: "{knowledge_folder}/People/Sarah Chen.md",
|
||||
oldString: "## Activity\\n",
|
||||
newString: "## Activity\\n- **2026-02-03** (meeting): Met to discuss project timeline\\n"
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
## 9b: Apply State Changes
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue