@getlore/cli 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -106,7 +106,7 @@ export function registerDocsCommand(program, defaultDataDir) {
106
106
  .option('-d, --data-dir <dir>', 'Data directory', defaultDataDir)
107
107
  .option('--no-push', 'Skip git push')
108
108
  .action(async (content, options) => {
109
- const { handleRetain } = await import('../../mcp/handlers/retain.js');
109
+ const { handleIngest } = await import('../../mcp/handlers/ingest.js');
110
110
  const dataDir = options.dataDir;
111
111
  const dbPath = path.join(dataDir, 'lore.lance');
112
112
  const validTypes = ['insight', 'decision', 'requirement', 'note'];
@@ -114,21 +114,28 @@ export function registerDocsCommand(program, defaultDataDir) {
114
114
  console.error(`Invalid type: ${options.type}. Must be one of: ${validTypes.join(', ')}`);
115
115
  process.exit(1);
116
116
  }
117
- const result = await handleRetain(dbPath, dataDir, {
117
+ // Map CLI type to source_type
118
+ const sourceTypeMap = {
119
+ decision: 'notes',
120
+ requirement: 'notes',
121
+ insight: 'notes',
122
+ note: 'notes',
123
+ };
124
+ const result = await handleIngest(dbPath, dataDir, {
118
125
  content,
119
126
  project: options.project,
120
- type: options.type,
121
- source_context: options.context,
127
+ title: `${options.type.charAt(0).toUpperCase() + options.type.slice(1)}: ${content.slice(0, 50)}${content.length > 50 ? '...' : ''}`,
128
+ source_type: sourceTypeMap[options.type] || 'notes',
122
129
  tags: options.tags?.split(',').map((t) => t.trim()),
123
- }, { autoPush: options.push !== false });
130
+ }, { autoPush: options.push !== false, hookContext: { mode: 'cli' } });
124
131
  if (result.success) {
125
- console.log(`\n✓ ${result.message}`);
132
+ console.log(`\n✓ Created ${options.type} for project "${options.project}"`);
126
133
  console.log(` ID: ${result.id}`);
127
134
  console.log(` Indexed: ${result.indexed ? 'yes' : 'no'}`);
128
135
  console.log(` Synced: ${result.synced ? 'yes' : 'no'}`);
129
136
  }
130
137
  else {
131
- console.error(`\nFailed to create: ${result.message}`);
138
+ console.error(`\nFailed to create ${options.type}`);
132
139
  process.exit(1);
133
140
  }
134
141
  });
@@ -19,7 +19,6 @@ import path from 'path';
19
19
  export async function initDataRepo(dirPath) {
20
20
  await mkdir(dirPath, { recursive: true });
21
21
  await mkdir(path.join(dirPath, 'sources'), { recursive: true });
22
- await mkdir(path.join(dirPath, 'retained'), { recursive: true });
23
22
  // Create .gitignore if missing
24
23
  const gitignorePath = path.join(dirPath, '.gitignore');
25
24
  if (!existsSync(gitignorePath)) {
@@ -34,8 +33,7 @@ Your personal knowledge repository for Lore.
34
33
 
35
34
  ## Structure
36
35
 
37
- - \`sources/\` - Ingested documents
38
- - \`retained/\` - Explicitly saved insights
36
+ - \`sources/\` - Ingested content
39
37
 
40
38
  Vector embeddings are stored in Supabase (cloud) for multi-machine access.
41
39
  `);
@@ -8,7 +8,7 @@
8
8
  */
9
9
  export type SourceType = string;
10
10
  export type SearchMode = 'semantic' | 'keyword' | 'hybrid' | 'regex';
11
- export type ContentType = 'interview' | 'meeting' | 'conversation' | 'document' | 'note' | 'analysis' | 'survey' | 'research' | 'decision' | 'insight' | 'requirement';
11
+ export type ContentType = 'interview' | 'meeting' | 'conversation' | 'document' | 'note' | 'analysis' | 'survey' | 'research';
12
12
  export interface SourceDocument {
13
13
  id: string;
14
14
  source_type: SourceType;
@@ -154,13 +154,6 @@ export interface SearchArgs {
154
154
  limit?: number;
155
155
  mode?: SearchMode;
156
156
  }
157
- export interface RetainArgs {
158
- content: string;
159
- project: string;
160
- type: 'insight' | 'decision' | 'requirement' | 'note';
161
- source_context?: string;
162
- tags?: string[];
163
- }
164
157
  export interface ResearchArgs {
165
158
  task: string;
166
159
  project?: string;
@@ -111,9 +111,7 @@ export async function addSource(_dbPath, source, vector, extras) {
111
111
  if (extras?.source_name) {
112
112
  record.source_name = extras.source_name;
113
113
  }
114
- const { error } = await client.from('sources').upsert(record, {
115
- ignoreDuplicates: true,
116
- });
114
+ const { error } = await client.from('sources').upsert(record);
117
115
  if (error) {
118
116
  // Duplicate content_hash for this user — document already exists, skip silently
119
117
  if (error.code === '23505') {
@@ -2,13 +2,12 @@
2
2
  * Proposal-based write system for extensions
3
3
  */
4
4
  export interface ProposedChange {
5
- type: 'create_source' | 'update_source' | 'delete_source' | 'retain_insight' | 'add_tags';
5
+ type: 'create_source' | 'update_source' | 'delete_source' | 'add_tags';
6
6
  title?: string;
7
7
  content?: string;
8
8
  project?: string;
9
9
  sourceId?: string;
10
10
  changes?: Record<string, unknown>;
11
- insight?: string;
12
11
  tags?: string[];
13
12
  reason: string;
14
13
  }
@@ -7,7 +7,6 @@ import { mkdir, readFile, readdir, writeFile } from 'fs/promises';
7
7
  import os from 'os';
8
8
  import path from 'path';
9
9
  import { handleIngest } from '../mcp/handlers/ingest.js';
10
- import { handleRetain } from '../mcp/handlers/retain.js';
11
10
  import { getDatabase, getSourceById } from '../core/vector-store.js';
12
11
  export function getPendingDir() {
13
12
  return path.join(os.homedir(), '.config', 'lore', 'pending');
@@ -84,18 +83,6 @@ async function applyProposalChange(proposal, dbPath, dataDir) {
84
83
  }, { hookContext: { mode: 'cli' } });
85
84
  return;
86
85
  }
87
- case 'retain_insight': {
88
- if (!change.insight) {
89
- throw new Error('retain_insight requires insight');
90
- }
91
- const project = change.project || proposal.extensionName;
92
- await handleRetain(dbPath, dataDir, {
93
- content: change.insight,
94
- project,
95
- type: 'insight',
96
- }, {});
97
- return;
98
- }
99
86
  case 'update_source': {
100
87
  if (!change.sourceId || !change.changes) {
101
88
  throw new Error('update_source requires sourceId and changes');
@@ -83,7 +83,7 @@ export function createProposeFunction(extensionName, permissions) {
83
83
  return async (change) => {
84
84
  // Enforce permissions
85
85
  const perms = permissions || {};
86
- if (change.type === 'create_source' || change.type === 'retain_insight') {
86
+ if (change.type === 'create_source') {
87
87
  if (!perms.proposeCreate) {
88
88
  throw new Error(`Extension "${extensionName}" does not have permission to propose creating documents. Add permissions.proposeCreate = true to the extension.`);
89
89
  }
@@ -7,7 +7,7 @@
7
7
  */
8
8
  interface IngestArgs {
9
9
  content: string;
10
- title: string;
10
+ title?: string;
11
11
  project: string;
12
12
  source_type?: string;
13
13
  date?: string;
@@ -123,9 +123,11 @@ function mapContentType(sourceType) {
123
123
  }
124
124
  }
125
125
  export async function handleIngest(dbPath, dataDir, args, options = {}) {
126
- const { content, title, project, source_type: raw_source_type, date, participants = [], tags = [], source_url, source_name, } = args;
126
+ const { content, project, source_type: raw_source_type, date, participants = [], tags = [], source_url, source_name, } = args;
127
127
  const { autoPush = true, hookContext } = options;
128
128
  const source_type = normalizeSourceType(raw_source_type);
129
+ // Auto-generate title if not provided
130
+ const title = args.title || `${source_type.charAt(0).toUpperCase() + source_type.slice(1)}: ${content.slice(0, 50)}${content.length > 50 ? '...' : ''}`;
129
131
  // Content hash deduplication — skip everything if already ingested
130
132
  const contentHash = createHash('sha256').update(content).digest('hex');
131
133
  try {
@@ -170,12 +172,17 @@ export async function handleIngest(dbPath, dataDir, args, options = {}) {
170
172
  await writeFile(path.join(sourceDir, 'metadata.json'), JSON.stringify(metadata, null, 2));
171
173
  // Save content.md
172
174
  await writeFile(path.join(sourceDir, 'content.md'), content);
173
- // Extract insights using LLM
175
+ // Extract insights using LLM (skip for short content)
174
176
  let summary = content.slice(0, 200) + (content.length > 200 ? '...' : '');
175
177
  let themes = [];
176
178
  let quotes = [];
177
- try {
178
- if (content.trim().length > 100) {
179
+ const isShortContent = content.trim().length <= 500;
180
+ if (isShortContent) {
181
+ // Short content fast path — use content as its own summary, skip LLM extraction
182
+ summary = content;
183
+ }
184
+ else {
185
+ try {
179
186
  const insights = await extractInsights(content, title, id, { contentType });
180
187
  summary = insights.summary;
181
188
  themes = insights.themes.map((t) => ({ name: t.name, quotes: [] }));
@@ -183,10 +190,10 @@ export async function handleIngest(dbPath, dataDir, args, options = {}) {
183
190
  // Save insights.json
184
191
  await writeFile(path.join(sourceDir, 'insights.json'), JSON.stringify({ summary, themes, quotes }, null, 2));
185
192
  }
186
- }
187
- catch (error) {
188
- console.error('Failed to extract insights:', error);
189
- // Continue with basic summary
193
+ catch (error) {
194
+ console.error('Failed to extract insights:', error);
195
+ // Continue with basic summary
196
+ }
190
197
  }
191
198
  // Add to vector store immediately
192
199
  try {
@@ -27,9 +27,9 @@ function createLoreToolsServer(dbPath, dataDir, archivedProjects) {
27
27
  tool('search', 'Semantic search across all sources in the knowledge repository. Returns summaries with relevant quotes. Use this to find information related to a topic.', {
28
28
  query: z.string().describe('Semantic search query - describe what you\'re looking for'),
29
29
  source_type: z
30
- .enum(['granola', 'claude-code', 'claude-desktop', 'chatgpt', 'markdown', 'document'])
30
+ .string()
31
31
  .optional()
32
- .describe('Filter by source type (e.g., "granola" for meeting transcripts)'),
32
+ .describe('Filter by source type (e.g., "meeting", "slack", "document")'),
33
33
  content_type: z
34
34
  .enum(['interview', 'meeting', 'conversation', 'document', 'note', 'analysis'])
35
35
  .optional()
@@ -91,6 +91,8 @@ ${quotes}`;
91
91
  .slice(0, 10)
92
92
  .map((q) => `- [${q.speaker || 'unknown'}] "${q.text}"`)
93
93
  .join('\n');
94
+ const sourceUrlLine = source.source_url ? `\n**Source URL:** ${source.source_url}` : '';
95
+ const sourceNameLine = source.source_name ? `\n**Source:** ${source.source_name}` : '';
94
96
  return {
95
97
  content: [
96
98
  {
@@ -99,7 +101,7 @@ ${quotes}`;
99
101
 
100
102
  **Type:** ${source.source_type} / ${source.content_type}
101
103
  **Created:** ${source.created_at}
102
- **Projects:** ${source.projects.join(', ') || 'none'}
104
+ **Projects:** ${source.projects.join(', ') || 'none'}${sourceUrlLine}${sourceNameLine}
103
105
 
104
106
  ## Summary
105
107
  ${source.summary}
@@ -122,9 +124,9 @@ ${quotes || 'No quotes extracted'}`,
122
124
  // List sources - browse available sources
123
125
  tool('list_sources', 'List all sources in the repository. Use this to understand what knowledge is available before searching.', {
124
126
  source_type: z
125
- .enum(['granola', 'claude-code', 'claude-desktop', 'chatgpt', 'markdown', 'document'])
127
+ .string()
126
128
  .optional()
127
- .describe('Filter by source type'),
129
+ .describe('Filter by source type (e.g., "meeting", "slack", "document")'),
128
130
  project: z.string().optional().describe('Filter to specific project'),
129
131
  limit: z.number().optional().describe('Max results (default 20)'),
130
132
  }, async (args) => {
@@ -19,7 +19,6 @@ import { toolDefinitions } from './tools.js';
19
19
  import { handleSearch } from './handlers/search.js';
20
20
  import { handleGetSource } from './handlers/get-source.js';
21
21
  import { handleListSources } from './handlers/list-sources.js';
22
- import { handleRetain } from './handlers/retain.js';
23
22
  import { handleIngest } from './handlers/ingest.js';
24
23
  import { startResearchJob, getResearchJobStatus } from './handlers/research.js';
25
24
  import { handleListProjects } from './handlers/list-projects.js';
@@ -136,7 +135,7 @@ async function main() {
136
135
  }
137
136
  const server = new Server({
138
137
  name: 'lore',
139
- version: '0.7.0',
138
+ version: '0.8.0',
140
139
  }, {
141
140
  capabilities: {
142
141
  tools: {},
@@ -219,12 +218,6 @@ async function main() {
219
218
  case 'list_projects':
220
219
  result = await handleListProjects(DB_PATH);
221
220
  break;
222
- // Push-based retention
223
- case 'retain':
224
- result = await handleRetain(DB_PATH, LORE_DATA_DIR, args, {
225
- autoPush: AUTO_GIT_PUSH,
226
- });
227
- break;
228
221
  // Direct document ingestion
229
222
  case 'ingest':
230
223
  result = await handleIngest(DB_PATH, LORE_DATA_DIR, args, {
package/dist/mcp/tools.js CHANGED
@@ -86,18 +86,6 @@ const ListSourcesSchema = z.object({
86
86
  .describe('Filter by source type (matches the source_type passed during ingest, e.g. "meeting", "slack", "github-issue")'),
87
87
  limit: z.number().optional().describe('Max results (default 20)'),
88
88
  });
89
- const RetainSchema = z.object({
90
- content: z.string().describe('The insight, decision, or note to retain'),
91
- project: z.string().describe('Project this belongs to'),
92
- type: z
93
- .enum(['insight', 'decision', 'requirement', 'note'])
94
- .describe('Type of knowledge being retained'),
95
- source_context: z
96
- .string()
97
- .optional()
98
- .describe('Where this came from (e.g., "user interview with Sarah")'),
99
- tags: z.array(z.string()).optional().describe('Optional tags for categorization'),
100
- });
101
89
  // ============================================================================
102
90
  // Agentic Research Tool
103
91
  // ============================================================================
@@ -116,7 +104,7 @@ const ResearchSchema = z.object({
116
104
  // ============================================================================
117
105
  const IngestSchema = z.object({
118
106
  content: z.string().describe('The document content to ingest'),
119
- title: z.string().describe('Title for the document'),
107
+ title: z.string().optional().describe('Title for the document. Auto-generated from content if not provided.'),
120
108
  project: z.string().describe('Project this document belongs to'),
121
109
  source_type: z
122
110
  .string()
@@ -224,18 +212,6 @@ Use this to browse what exists in a project, understand the scope of available k
224
212
  properties: {},
225
213
  },
226
214
  },
227
- {
228
- name: 'retain',
229
- description: `Save a discrete insight, decision, requirement, or note to the knowledge base. These are short, synthesized pieces of knowledge — NOT full documents.
230
-
231
- Examples of what to retain:
232
- - A decision: "We chose JWT over session cookies because of mobile app requirements"
233
- - An insight: "3 out of 5 users mentioned export speed as their top frustration"
234
- - A requirement: "Must support SSO for enterprise customers"
235
-
236
- USE 'ingest' INSTEAD for full documents, meeting notes, transcripts, or any content longer than a few paragraphs.`,
237
- inputSchema: zodToJsonSchema(RetainSchema),
238
- },
239
215
  // Agentic tool
240
216
  {
241
217
  name: 'research',
@@ -276,7 +252,7 @@ IDEMPOTENT: Content is deduplicated by SHA256 hash. Calling ingest with identica
276
252
  WHAT HAPPENS:
277
253
  1. Content hash checked for deduplication
278
254
  2. Document saved to disk
279
- 3. LLM extracts summary, themes, and key quotes
255
+ 3. LLM extracts summary, themes, and key quotes (skipped for short content ≤500 chars)
280
256
  4. Embedding generated for semantic search
281
257
  5. Indexed in Supabase for instant retrieval
282
258
 
@@ -284,7 +260,7 @@ BEST PRACTICES:
284
260
  - Always pass source_url when available (enables citation linking back to the original)
285
261
  - Use source_name for human-readable origin context (e.g., "Slack #product-team")
286
262
  - source_type is a free-form hint — use whatever describes the content (slack, email, notion, github-issue, etc.)
287
- - Use 'retain' instead for short discrete insights/decisions (not full documents)`,
263
+ - For short insights, decisions, or notes just pass the content. Title and source_type are optional.`,
288
264
  inputSchema: zodToJsonSchema(IngestSchema),
289
265
  },
290
266
  // Sync tool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getlore/cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Research knowledge repository with semantic search, citations, and project lineage tracking",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,8 +36,7 @@ After setup, Lore works autonomously.
36
36
  | `get_source` | Low | Full document retrieval by ID |
37
37
  | `list_sources` | Low | Browse what exists in a project |
38
38
  | `list_projects` | Low | Discover available knowledge domains |
39
- | `retain` | Low | Save discrete insights/decisions |
40
- | `ingest` | Medium | Push full documents into the knowledge base |
39
+ | `ingest` | Low-Medium | Push content — documents, insights, or decisions |
41
40
  | `research` | High | Cross-reference multiple sources, synthesize findings |
42
41
  | `sync` | Variable | Refresh from configured source directories |
43
42
 
@@ -50,6 +49,11 @@ Use `ingest` to push content into Lore when:
50
49
 
51
50
  Always pass `source_url` (original URL for linking) and `source_name` (human-readable label like "GitHub PR #123") when available. Ingestion is idempotent — safe to call repeatedly with the same content.
52
51
 
52
+ For short insights, decisions, or notes — title and source_type are optional:
53
+ ```
54
+ ingest(content: "We chose JWT for auth", project: "auth-system")
55
+ ```
56
+
53
57
  ## When to Search
54
58
 
55
59
  Before making recommendations or answering questions about past work:
@@ -57,13 +61,6 @@ Before making recommendations or answering questions about past work:
57
61
  2. Only use `research` if the question genuinely needs cross-referencing multiple sources
58
62
  3. Use `get_source(id, include_content: true)` when you need the full text
59
63
 
60
- ## When to Retain
61
-
62
- Use `retain` for short synthesized knowledge (not full documents):
63
- - Decisions made during a session
64
- - Key insights distilled from analysis
65
- - Requirements extracted from conversations
66
-
67
64
  ## Example: Grounding a Decision
68
65
 
69
66
  ```
@@ -73,11 +70,9 @@ search("database migration approach", project: "backend-rewrite")
73
70
  # 2. If results are relevant, get full context
74
71
  get_source("abc-123", include_content: true)
75
72
 
76
- # 3. After making a decision, retain it
77
- retain(
73
+ # 3. After making a decision, save it
74
+ ingest(
78
75
  content: "Chose pgvector over Pinecone for embeddings — lower latency, simpler ops, sufficient scale",
79
- project: "backend-rewrite",
80
- type: "decision",
81
- source_context: "Architecture review session"
76
+ project: "backend-rewrite"
82
77
  )
83
78
  ```
@@ -35,8 +35,7 @@ After setup, Lore works autonomously.
35
35
  | `get_source` | Low | Full document retrieval by ID |
36
36
  | `list_sources` | Low | Browse what exists in a project |
37
37
  | `list_projects` | Low | Discover available knowledge domains |
38
- | `retain` | Low | Save discrete insights/decisions |
39
- | `ingest` | Medium | Push full documents into the knowledge base |
38
+ | `ingest` | Low-Medium | Push content — documents, insights, or decisions |
40
39
  | `research` | High | Cross-reference multiple sources, synthesize findings |
41
40
  | `sync` | Variable | Refresh from configured source directories |
42
41
 
@@ -51,6 +50,4 @@ Before making recommendations or answering questions about past work:
51
50
  2. Only use `research` for multi-source synthesis (10x more expensive)
52
51
  3. Use `get_source(id, include_content: true)` for full text
53
52
 
54
- ## When to Retain
55
-
56
- Use `retain` for short synthesized knowledge (decisions, insights, requirements) — not full documents.
53
+ For short insights or decisions, just pass the content — title and source_type are optional and auto-generated from content.
@@ -30,8 +30,7 @@ After setup, Lore works autonomously.
30
30
  | `get_source` | Low | Full document retrieval by ID |
31
31
  | `list_sources` | Low | Browse what exists in a project |
32
32
  | `list_projects` | Low | Discover available knowledge domains |
33
- | `retain` | Low | Save discrete insights/decisions |
34
- | `ingest` | Medium | Push full documents into the knowledge base |
33
+ | `ingest` | Low-Medium | Push content — documents, insights, or decisions |
35
34
  | `research` | High | Cross-reference multiple sources, synthesize findings |
36
35
  | `sync` | Variable | Refresh from configured source directories |
37
36
 
@@ -46,6 +45,4 @@ Before making recommendations or answering questions about past work:
46
45
  2. Only use `research` for multi-source synthesis (10x more expensive)
47
46
  3. Use `get_source(id, include_content: true)` for full text
48
47
 
49
- ## When to Retain
50
-
51
- Use `retain` for short synthesized knowledge (decisions, insights, requirements) — not full documents.
48
+ For short insights or decisions, just pass the content — title and source_type are optional and auto-generated from content.
@@ -26,7 +26,6 @@ After setup, Lore works autonomously.
26
26
 
27
27
  - **Sources**: Full documents (meeting notes, interviews, Slack threads, specs, etc.)
28
28
  - **Projects**: Organizational grouping for sources
29
- - **Insights**: Short retained knowledge (decisions, requirements, observations)
30
29
  - **Citations**: Every piece of knowledge links back to its original source
31
30
 
32
31
  ## Tools Reference
@@ -47,10 +46,19 @@ The primary way to add content. Accepts any document with metadata.
47
46
  }
48
47
  ```
49
48
 
49
+ For short insights, decisions, or notes — title and source_type are optional:
50
+ ```json
51
+ {
52
+ "content": "We chose JWT over session cookies because of mobile app requirements",
53
+ "project": "auth-system"
54
+ }
55
+ ```
56
+
50
57
  - **Idempotent**: Duplicate content returns `{deduplicated: true}` with no processing cost.
51
58
  - **source_type**: Free-form string. Common values: `meeting`, `interview`, `document`, `notes`, `analysis`, `conversation`, `slack`, `email`, `github-issue`, `notion`.
52
59
  - **source_url**: Always pass when available — enables citation linking.
53
60
  - **source_name**: Human-readable origin label.
61
+ - Short content (≤500 chars) skips LLM extraction for speed.
54
62
 
55
63
  ### `search` — Find relevant sources
56
64
  Fast lookup. Returns summaries with relevance scores.
@@ -79,18 +87,6 @@ List sources filtered by project or type. Sorted by date (newest first).
79
87
  ### `list_projects` — Discover projects
80
88
  Lists all projects with source counts and activity dates.
81
89
 
82
- ### `retain` — Save discrete knowledge
83
- For short insights, decisions, or requirements — not full documents.
84
-
85
- ```json
86
- {
87
- "content": "Users consistently report export takes >30s for large datasets",
88
- "project": "my-project",
89
- "type": "insight",
90
- "source_context": "User interview synthesis — Jan batch"
91
- }
92
- ```
93
-
94
90
  ### `research` — Deep research with citations
95
91
  Runs an internal agent that iteratively searches, reads, and synthesizes findings.
96
92
 
@@ -101,7 +97,7 @@ Runs an internal agent that iteratively searches, reads, and synthesizes finding
101
97
  }
102
98
  ```
103
99
 
104
- **Cost warning**: Makes 3-8 internal LLM calls. Use `search` for simple lookups.
100
+ **Async**: Returns a `job_id` immediately. Poll `research_status` for results (typically 2-8 minutes). Makes 10-30 internal LLM calls. Use `search` for simple lookups.
105
101
 
106
102
  ### `sync` — Refresh from source directories
107
103
  Scans configured directories for new files. Use `ingest` for agent-pushed content instead.
@@ -114,6 +110,6 @@ Excludes from default search. Only use when explicitly requested.
114
110
  1. **Search before you answer**: If a question might have documented context, search Lore first.
115
111
  2. **Ingest what matters**: After meaningful conversations or when processing external content, ingest it.
116
112
  3. **Always pass source_url**: Enables citation linking back to the original.
117
- 4. **Use retain for synthesis**: After analyzing multiple sources, retain the key insight.
113
+ 4. **Ingest handles both long and short content**: For short insights, decisions, or notes — just pass the content. Title and source_type are optional.
118
114
  5. **Prefer search over research**: `search` is 10x cheaper. Only use `research` for multi-source synthesis.
119
115
  6. **Cite your sources**: When presenting Lore results, reference the source title and date.
@@ -37,12 +37,12 @@ Before answering questions about past decisions, user feedback, project history,
37
37
 
38
38
  3. **Use `get_source`** with `include_content=true` when you need the full original text of a specific document.
39
39
 
40
- ## When to Retain Insights
40
+ ## Short Content
41
41
 
42
- Use `retain` (not `ingest`) for short, discrete pieces of knowledge:
43
- - Key decisions: "We chose X because Y"
44
- - Synthesized insights: "3/5 users mentioned Z as their top issue"
45
- - Requirements: "Must support SSO for enterprise"
42
+ For short insights, decisions, or notes title and source_type are optional:
43
+ ```
44
+ ingest(content: "We chose X because Y", project: "my-project")
45
+ ```
46
46
 
47
47
  ## Citation Best Practices
48
48