Skip to content
Merged
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
72 changes: 71 additions & 1 deletion docs/features/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,77 @@ For memory to activate on a call, all three conditions must be met:

1. `memory.enabled` is `true` in the config
2. `options.context.userId` is provided in the generate/stream call
3. The response has non-empty content (for storage)
3. The response has non-empty content (for write)

### Per-Call Memory Control

When memory is globally enabled, it is active for every `generate()` and `stream()` call by default. You can override this behavior on a **per-call basis** using the `memory` option without changing the global config.

**Available flags:**

| Flag | Type | Default | Description |
| --------- | ------- | ------- | ------------------------------------------------------------------ |
| `enabled` | boolean | `true` | Master toggle — when `false`, both read and write are skipped |
| `read` | boolean | `true` | Whether to read past memory and prepend it to the prompt |
| `write` | boolean | `true` | Whether to write this conversation turn into memory after the call |

> **Note:** These flags only take effect when the global memory SDK is enabled. If global memory is disabled, per-call flags have no effect.

**Precedence:**

1. **Global config** — Is memory enabled globally? If not, per-call flags are ignored.
2. **`enabled`** — Master per-call toggle. If `false`, both read and write are skipped regardless of individual flags.
3. **`read` / `write`** — Fine-grained control over individual operations.

#### Read memory but don't write

Use when you want past context but don't want this call stored — e.g., code review where you'll store a curated summary later.

```typescript
const result = await neurolink.generate({
input: { text: "Review this pull request for security issues" },
memory: { read: true, write: false },
context: { userId: "user-123" },
});
```

#### Write memory but don't read

Use for onboarding or seeding memory without injecting past context into the prompt.

```typescript
const result = await neurolink.generate({
input: {
text: "My name is Alice. I work on the payments team and use Python.",
},
memory: { read: false, write: true },
context: { userId: "user-123" },
});
```

#### Skip memory entirely

Use for operational or utility calls where memory adds noise.

```typescript
const result = await neurolink.generate({
input: { text: "Fetch the latest PR comments from GitHub" },
memory: { enabled: false },
context: { userId: "user-123" },
});
```

#### Per-call control with stream()

The same `memory` option works identically in `stream()`.

```typescript
const stream = await neurolink.stream({
input: { text: "Summarize today's standup notes" },
memory: { read: true, write: false },
context: { userId: "user-123" },
});
```

## Environment Variables

Expand Down
69 changes: 62 additions & 7 deletions src/lib/neurolink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,56 @@ ${memoryContext}
Current user's request: ${currentInput}`;
}

/**
* Determine whether memory should be read (retrieved) for this call.
* Respects both the global memory SDK config and per-call overrides.
*/
private shouldReadMemory(
perCallMemory: { enabled?: boolean; read?: boolean } | undefined,
userId: unknown,
): boolean {
if (
!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled ||
!userId
) {
Comment on lines +1299 to +1306

Copilot AI Mar 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

memory.read is now part of the public per-call API, but generate() never performs condensed-memory retrieval (there’s no call path that uses shouldReadMemory()/retrieveMemory() for generate). As a result, GenerateOptions.memory.read has no effect, and the docs/API contract are misleading. Consider adding the same pre-call retrieval step to generate() (after auth/requestContext merging so context.userId is available) or remove/rename the read option for generate if it’s intentionally stream-only.

Copilot uses AI. Check for mistakes.
return false;
}
if (perCallMemory?.enabled === false) {
return false;
}
if (perCallMemory?.read === false) {
return false;
}
return true;
}

/**
* Determine whether memory should be written (stored) for this call.
* Respects both the global memory SDK config and per-call overrides.
*/
private shouldWriteMemory(
perCallMemory: { enabled?: boolean; write?: boolean } | undefined,
userId: unknown,
content: string | undefined | null,
): boolean {
if (
!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled ||
!userId
) {
return false;
}
if (!content?.trim()) {
return false;
}
if (perCallMemory?.enabled === false) {
return false;
}
if (perCallMemory?.write === false) {
return false;
}
return true;
}

/**
* Retrieve condensed memory for a user.
* Returns the input text enhanced with memory context, or unchanged if no memory.
Expand Down Expand Up @@ -3546,9 +3596,12 @@ Current user's request: ${currentInput}`;
): void {
// Memory storage
if (
this.conversationMemoryConfig?.conversationMemory?.memory?.enabled &&
options.context?.userId &&
generateResult.content?.trim()
this.shouldWriteMemory(
options.memory,
options.context?.userId,
generateResult.content,
) &&
options.context?.userId
) {
this.storeMemoryInBackground(
originalPrompt ?? "",
Expand Down Expand Up @@ -6110,7 +6163,7 @@ Current user's request: ${currentInput}`;

// Memory retrieval
if (
this.conversationMemoryConfig?.conversationMemory?.memory?.enabled &&
this.shouldReadMemory(options.memory, options.context?.userId) &&
options.context?.userId
) {
Comment on lines 6165 to 6168

Copilot AI Mar 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per-call memory overrides (memory.enabled / memory.read / memory.write) introduce new branching behavior for both stream retrieval and background storage, but there doesn’t appear to be automated coverage asserting the precedence rules (global disabled vs per-call overrides, enabled=false overriding read/write, etc.). Adding coverage in the existing continuous test suite for memory would help prevent regressions and ensure the new flags behave as documented.

Copilot uses AI. Check for mistakes.
try {
Expand Down Expand Up @@ -6575,9 +6628,11 @@ Current user's request: ${currentInput}`;
}

if (
this.conversationMemoryConfig?.conversationMemory?.memory?.enabled &&
enhancedOptions.context?.userId &&
accumulatedContent?.trim()
this.shouldWriteMemory(
enhancedOptions.memory,
enhancedOptions.context?.userId,
accumulatedContent,
)
) {
this.storeMemoryInBackground(
originalPrompt ?? "",
Expand Down
17 changes: 17 additions & 0 deletions src/lib/types/generateTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,23 @@ export type GenerateOptions = {

/** Raw auth token — validated by configured auth provider */
auth?: { token: string };

/**
* Per-call memory control.
*
* Override the global memory SDK behavior for this specific call.
* All flags default to `true` when the global memory SDK is enabled.
* If the global memory SDK is disabled, these flags have no effect.
*
*/
memory?: {
/** Master toggle for this call. When false, both read and write are skipped. Defaults to true. */
enabled?: boolean;
/** Whether to read condensed memory and prepend to prompt. Defaults to true. */
read?: boolean;
/** Whether to write (add/condense) the conversation into memory after completion. Defaults to true. */
write?: boolean;
};
};

/**
Expand Down
16 changes: 16 additions & 0 deletions src/lib/types/streamTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,22 @@ export type StreamOptions = {

/** Raw auth token — validated by configured auth provider */
auth?: { token: string };

/**
* Per-call memory control.
*
* Override the global memory SDK behavior for this specific call.
* All flags default to `true` when the global memory SDK is enabled.
* If the global memory SDK is disabled, these flags have no effect.
*/
memory?: {
/** Master toggle for this call. When false, both read and write are skipped. Defaults to true. */
enabled?: boolean;
/** Whether to read condensed memory and prepend to prompt. Defaults to true. */
read?: boolean;
/** Whether to write (add/condense) the conversation into memory after completion. Defaults to true. */
write?: boolean;
};
};

/**
Expand Down
Loading