A comprehensive memory orchestration SDK for Line AI applications, built on top of @lineai/cognee-api.
@lineai/memory provides a higher-level abstraction over the Cognee API with:
- ✅ Automatic session management - Handle authentication and configuration seamlessly
- ✅ Organization-based multi-tenancy - Strict data isolation between organizations
- ✅ Smart dataset naming strategies - User, project, and organization-scoped datasets
- ✅ Type-safe error handling - Discriminated unions for predictable error handling
- ✅ Functional programming principles - Pure functions, immutability, composition
- ✅ Built-in retry logic - Exponential backoff and circuit breaker patterns
npm install @lineai/memory
# or
yarn add @lineai/memoryimport { createSession, remember, process, search } from '@lineai/memory';
// Create authenticated session
const sessionResult = await createSession({
cogneeUrl: 'http://localhost:8000',
credentials: { username: 'alice', password: 'secret' },
organizationId: 'org-acme-corp',
datasetStrategy: {
scope: 'user',
organizationId: 'org-acme-corp',
userId: 'alice-123',
},
});
if (!sessionResult.success) {
console.error('Failed to create session:', sessionResult.error);
return;
}
const session = sessionResult.value;
// Remember something
const memoryResult = await remember(session, {
type: 'text',
text: 'TypeScript is a typed superset of JavaScript',
});
if (memoryResult.success) {
console.log('Remembered:', memoryResult.value);
}
// Process into knowledge graph
const processResult = await process(session);
// Search later
const searchResult = await search(session, {
text: 'What is TypeScript?',
});
if (searchResult.success && searchResult.value.found) {
console.log('Found:', searchResult.value.results);
}Sessions represent an authenticated connection to Cognee with dataset strategy configuration:
type Session = {
readonly cogneeUrl: string;
readonly organizationId: string;
readonly userId: string;
readonly userName: string;
readonly datasetStrategy: DatasetStrategy;
readonly config: CogneeConfig;
};All datasets are organization-scoped for multi-tenant isolation:
// User-scoped: personal memories
const userStrategy: DatasetStrategy = {
scope: 'user',
organizationId: 'org-123',
userId: 'user-456',
};
// Dataset name: "organization_org-123_user_user-456_memories"
// Project-scoped: shared team knowledge
const projectStrategy: DatasetStrategy = {
scope: 'project',
organizationId: 'org-123',
projectId: 'proj-789',
};
// Dataset name: "organization_org-123_project_proj-789_knowledge"
// Organization-scoped: company-wide data
const orgStrategy: DatasetStrategy = {
scope: 'organization',
organizationId: 'org-123',
};
// Dataset name: "organization_org-123_shared"
// Custom: define your own naming
const customStrategy: DatasetStrategy = {
scope: 'custom',
namingFn: (context) =>
`organization_${context.organizationId}_custom_${context.customField}`,
};All functions return Outcome<T> for type-safe error handling:
const result = await remember(session, content);
if (result.success) {
const memory = result.value;
console.log('Success:', memory.id);
} else {
const error = result.error;
switch (error.error) {
case 'authentication_failed':
console.error('Auth failed:', error.message);
break;
case 'permission_denied':
console.error('No access to dataset:', error.datasetId);
break;
case 'network_error':
console.error('Network error:', error.statusCode);
break;
}
}@lineai/memory integrates with Cognee's multi-tenant authentication system. Line AI organizations map 1:1 to Cognee tenants, and Line AI users map to Cognee users.
- Cognee Tenant ↔ Line AI Organization (1:1 mapping)
- Cognee User ↔ Line AI User (1:1 mapping)
- Cognee Role ↔ Line AI user groups/roles
- Dataset Permissions → Managed through Cognee's ACL system
When a Line AI organization is created, you should:
- Create admin user (creates admin account without tenant)
- Provision organization (creates tenant owned by admin)
- Provision additional users (when Line AI users join the organization)
- Create roles (optional, for grouping users)
import { provisionAdminUser } from '@lineai/memory';
// Step 1: Create admin user account
const adminResult = await provisionAdminUser({
cogneeUrl: 'http://localhost:8000',
superuserCreds: {
username: 'admin@cognee.local',
password: process.env.COGNEE_ADMIN_PASSWORD,
},
adminEmail: 'alice@acme.com',
// adminPassword is optional - will auto-generate if not provided
});
if (adminResult.success) {
// IMPORTANT: Store credentials BEFORE creating organization
await db.users.create({
email: adminResult.value.email,
cogneeUserId: adminResult.value.userId,
cogneePassword: await encrypt(adminResult.value.password), // IMPORTANT: Encrypt!
role: 'admin',
});
// Securely send password to admin
await sendEmail(adminResult.value.email, adminResult.value.password);
}import { provisionOrganization } from '@lineai/memory';
// Step 2: Create organization tenant using admin credentials
const orgResult = await provisionOrganization({
cogneeUrl: 'http://localhost:8000',
adminEmail: adminResult.value.email,
adminPassword: adminResult.value.password,
organizationName: 'acme-corp', // Organization slug
});
if (orgResult.success) {
// Store tenant ID
await db.organizations.create({
cogneeTenantId: orgResult.value.tenantId,
});
// Link admin user to organization
await db.users.update(adminUserId, {
organizationId: orgId,
});
}Why two steps? Separating admin user creation from organization creation prevents an irrecoverable state where the user exists but credentials were never returned. Always store admin credentials before attempting to create the organization.
import { provisionUser } from '@lineai/memory';
// Get admin credentials (stored during org provisioning)
const org = await db.organizations.findById(orgId);
const admin = await db.users.findOne({ organizationId: orgId, role: 'admin' });
const adminPassword = await decrypt(admin.cogneePassword);
const result = await provisionUser({
cogneeUrl: 'http://localhost:8000',
adminEmail: admin.email,
adminPassword: adminPassword,
userEmail: 'bob@acme.com',
// password is optional - will auto-generate if not provided
});
if (result.success) {
// IMPORTANT: Store userId and ENCRYPTED password
await db.users.create({
email: result.value.email,
cogneeUserId: result.value.userId,
cogneePassword: await encrypt(result.value.password),
organizationId: orgId,
});
// Securely send password to user
await sendEmail(result.value.email, result.value.password);
}import { createTenantRole } from '@lineai/memory';
// Get admin credentials
const admin = await db.users.findOne({ organizationId: orgId, role: 'admin' });
const adminPassword = await decrypt(admin.cogneePassword);
// Create role using admin credentials
const roleResult = await createTenantRole({
cogneeUrl: 'http://localhost:8000',
ownerEmail: admin.email,
ownerPassword: adminPassword,
roleName: 'data-scientists',
});
if (roleResult.success) {
// Store role ID for later use
await db.roles.create({
name: 'data-scientists',
cogneeRoleId: roleResult.value.roleId, // ✅ Real UUID now!
tenantId: org.cogneeTenantId,
});
}Once users are provisioned, create sessions using their Cognee credentials:
import { createSession } from '@lineai/memory';
const sessionResult = await createSession({
cogneeUrl: 'http://localhost:8000',
credentials: {
username: user.email, // Cognee username
password: await decrypt(user.cogneePassword), // Decrypted password
},
organizationId: user.organizationId,
datasetStrategy: {
scope: 'user',
organizationId: user.organizationId,
userId: user.id,
},
});- Never store passwords in plain text - Always encrypt Cognee passwords before storing
- Use environment variables - Store superuser credentials in
process.env - Limit superuser access - Only use superuser credentials for provisioning operations
- Rotate passwords - Implement password rotation for long-lived users
- Use HTTPS - Always use
https://URLs in production
For more details, see AUTHENTICATION.md.
Create a new authenticated session.
const result = await createSession({
cogneeUrl: 'http://localhost:8000',
credentials: { username: 'alice', password: 'secret' },
organizationId: 'org-acme-corp',
datasetStrategy: {
scope: 'user',
organizationId: 'org-acme-corp',
userId: 'alice-123',
},
});End a session (logout).
await endSession(session);Add content to memory (single item).
// Text content
await remember(session, {
type: 'text',
text: 'Important information',
});
// File content
await remember(session, {
type: 'file',
file: myFile,
});
// URL content
await remember(session, {
type: 'url',
url: 'https://example.com/document.pdf',
});
// With tags and custom dataset
await remember(
session,
{ type: 'text', text: 'Data' },
{
tags: ['important', 'finance'],
datasetName: 'organization_org-123_custom_dataset',
}
);Add multiple items to memory (batch operation).
await rememberMany(session, [
{ type: 'text', text: 'First item' },
{ type: 'text', text: 'Second item' },
{ type: 'text', text: 'Third item' },
]);Remove content from memory.
// Soft delete (default)
await forget(session, memory, 'soft');
// Hard delete
await forget(session, memory, 'hard');Process memories into knowledge graph.
// Process with default dataset
const ref = await process(session);
// Process specific datasets in background
const ref = await process(session, {
datasetIds: ['dataset-1', 'dataset-2'],
background: true,
});Check processing status.
const status = await getProcessingStatus(session, ref);
if (status.success) {
if (status.value.complete) {
console.log('Processing complete!');
} else if (status.value.error) {
console.log('Processing failed:', status.value.message);
} else {
console.log('Progress:', status.value.progress, '%');
}
}Search across memories (GRAPH_COMPLETION).
const result = await search(session, {
text: 'What did I learn about TypeScript?',
topK: 5,
datasetIds: ['dataset-1'], // Optional
tags: ['programming'], // Optional
});
if (result.success && result.value.found) {
result.value.results.forEach((item) => {
console.log('Dataset:', item.datasetName);
console.log('Content:', item.content);
});
}searchGraph(session, query)- INSIGHTS searchsearchChunks(session, query)- CHUNKS searchsearchInsights(session, query)- INSIGHTS searchsearchSummaries(session, query)- SUMMARIES searchsearchCode(session, query)- CODE search
Get search history.
const history = await getSearchHistory(session, {
since: '2024-01-01',
});List datasets accessible to user (automatically filtered by organization).
const datasets = await listDatasets(session);Create a new dataset explicitly.
await createDataset(session, 'organization_org-123_custom_dataset');Get dataset graph for visualization.
const graph = await getDatasetGraph(session, 'dataset-id-123');Delete a dataset.
await deleteDataset(session, 'dataset-id-123');Share dataset with another user.
await shareDataset(session, 'dataset-id-123', 'user-456', [
'read',
'write',
]);import { withRetry, defaultRetryStrategy } from '@lineai/memory';
const result = await withRetry(
() => remember(session, content),
{
...defaultRetryStrategy,
maxAttempts: 5,
initialDelayMs: 500,
}
);import { CircuitBreaker } from '@lineai/memory';
const breaker = new CircuitBreaker(5, 60000); // threshold, timeout
const result = await breaker.execute(() => remember(session, content));import {
validateDatasetName,
validateOrganizationId,
validateQuery,
validateUrl,
} from '@lineai/memory';
const nameResult = validateDatasetName('my-dataset');
if (!nameResult.success) {
console.error(nameResult.error.message);
}import {
createSession,
remember,
process,
search,
endSession,
} from '@lineai/memory';
const session = await createSession({
cogneeUrl: 'http://localhost:8000',
credentials: { username: 'alice', password: 'secret' },
organizationId: 'org-acme-corp',
datasetStrategy: {
scope: 'user',
organizationId: 'org-acme-corp',
userId: 'alice-123',
},
});
if (!session.success) {
throw new Error('Failed to create session');
}
const s = session.value;
// Remember conversation
await remember(s, {
type: 'text',
text: 'User: What is the capital of France?\nBot: Paris',
});
// Process into knowledge graph
await process(s);
// Search later
const outcome = await search(s, {
text: 'What did I ask about France?',
});
if (outcome.success && outcome.value.found) {
console.log(outcome.value.results[0].content);
}
await endSession(s);// Alice creates project dataset
const aliceSession = await createSession({
cogneeUrl: 'http://localhost:8000',
credentials: { username: 'alice', password: 'secret' },
organizationId: 'org-acme-corp',
datasetStrategy: {
scope: 'project',
organizationId: 'org-acme-corp',
projectId: 'proj-456',
},
});
// Add project documents
const memory = await remember(aliceSession.value, {
type: 'file',
file: projectDoc,
});
// Share with Bob
if (memory.success) {
await shareDataset(aliceSession.value, memory.value.datasetId, 'bob-789', [
'read',
]);
}
// Bob searches
const bobSession = await createSession({
cogneeUrl: 'http://localhost:8000',
credentials: { username: 'bob', password: 'secret' },
organizationId: 'org-acme-corp',
datasetStrategy: {
scope: 'project',
organizationId: 'org-acme-corp',
projectId: 'proj-456',
},
});
const outcome = await search(bobSession.value, {
text: 'project requirements',
});import { createSession, remember, process, searchChunks } from '@lineai/memory';
const ragPipeline = async (documents: File[], userQuery: string) => {
const session = await createSession({
/* ... */
});
if (!session.success) {
throw new Error('Failed to create session');
}
const s = session.value;
// Ingest documents
for (const doc of documents) {
await remember(s, { type: 'file', file: doc });
}
// Process
await process(s, { background: false });
// Search for relevant chunks
const outcome = await searchChunks(s, {
text: userQuery,
topK: 5,
});
if (!outcome.success || !outcome.value.found) {
return 'No relevant information found';
}
// Use chunks as context for LLM
const context = outcome.value.results.map((r) => r.content).join('\n\n');
return generateResponse(context, userQuery);
};- Functional Purity - All functions are pure with explicit dependencies
- Deterministic Patterns - Discriminated unions instead of status fields
- Minimal Abstraction - Direct mapping to domain concepts
- Explicit Configuration - All configuration visible at call site
- Composition Over Configuration - Compose behaviors instead of flags
import { login, addData, cognify, search, CogneeConfig } from '@lineai/cognee-api';
const config: CogneeConfig = { baseUrl: 'http://localhost:8000' };
await login(config, { username: 'alice', password: 'secret' });
const file = new File(['content'], 'doc.txt');
await addData(config, [file], { datasetName: 'my-dataset' });
await cognify(config, { datasets: ['my-dataset'] });
const results = await search(config, { query: 'test', dataset_name: 'my-dataset' });import { createSession, remember, process, search } from '@lineai/memory';
const session = await createSession({
cogneeUrl: 'http://localhost:8000',
credentials: { username: 'alice', password: 'secret' },
organizationId: 'org-acme-corp',
datasetStrategy: { scope: 'user', organizationId: 'org-acme-corp', userId: 'alice' },
});
await remember(session.value, { type: 'file', file: new File(['content'], 'doc.txt') });
await process(session.value);
const outcome = await search(session.value, { text: 'test' });- ✅ Automatic dataset naming with organization isolation
- ✅ Type-safe error handling with discriminated unions
- ✅ Built-in session management
- ✅ Permission enforcement
- ✅ Retry logic and circuit breaker patterns
MIT
Issues and pull requests welcome!