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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to the Nowledge Mem Gemini CLI extension will be documented
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.9] - 2026-06-06

### Improved

- Session-start hooks, commands, and Gemini guidance now prefer Context Bundle when startup identity, active scope, or guidance matters, with Working Memory kept as the lightweight fallback for older `nmem` clients.

## [0.1.8] - 2026-04-27

### Improved
Expand Down
33 changes: 22 additions & 11 deletions GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ Use MCP tools for retrieval and memory writes when Gemini exposes them in this s

## Core Memory Lifecycle

Treat Nowledge Mem as four linked surfaces:
Treat Nowledge Mem as five linked surfaces:

1. Working Memory for current focus and active priorities
2. Distilled memories for durable knowledge
3. Threads for full searchable conversation history
4. Handoff summaries for compact resumability when the user wants a manual handoff
1. Context Bundle for startup identity, active space, active rules, and current priorities
2. Working Memory for a lightweight current-focus briefing
3. Distilled memories for durable knowledge
4. Threads for full searchable conversation history
5. Handoff summaries for compact resumability when the user wants a manual handoff

Prefer the smallest surface that answers the user's need, then move upward only when more context is necessary.

Expand Down Expand Up @@ -39,9 +40,19 @@ nmem config mcp show --host gemini-cli

Paste the generated JSON into Gemini `settings.json`. Direct MCP clients do not read `~/.nowledge-mem/config.json` automatically.

## Working Memory
## Context Bundle And Working Memory

At the start of a session, or when recent priorities would help, read Working Memory with:
At the start of a session, or when recent priorities would help, read Context Bundle when identity, active space, active rules, or multi-agent behavior could matter:

Prefer the MCP `read_context_bundle` tool when it is available.

Otherwise use:

```bash
nmem --json context --source-app gemini-cli
```

Use Working Memory alone for the lighter daily briefing or compatibility fallback.

Prefer the MCP `read_working_memory` tool when it is available.

Expand All @@ -53,15 +64,15 @@ nmem --json wm read

If the command succeeds but returns `exists: false`, there is no Working Memory briefing yet. Say that clearly instead of pretending a briefing exists.

If the runtime already knows the current project or agent lane, add `--space "<space name>"`.
If the runtime already knows the current project or agent lane, add `--space "<space name>"` to either command. Multi-agent orchestrators can set `NMEM_AGENT_ID="<agent-slug>"` before launching Gemini CLI. Add `NMEM_SPACE` only when that whole run should override the identity's default space. Use `NMEM_HOST_AGENT_ID` only for advanced host-id aliases.

Only fall back to the legacy file below for older local-only **Default-space** setups where the user still keeps Working Memory there:

```bash
test -f ~/ai-now/memory.md && cat ~/ai-now/memory.md
```

Read Working Memory once near the start of a session, then reuse that context mentally. Do not re-read on every turn unless the user asks, the session context changed materially, or a long-running session clearly needs a refresh.
Read Context Bundle or Working Memory once near the start of a session, then reuse that context mentally. If Context Bundle already included Working Memory, do not read Working Memory again immediately. Do not re-read on every turn unless the user asks, the session context changed materially, or a long-running session clearly needs a refresh.

## Search Memory

Expand All @@ -87,7 +98,7 @@ Otherwise use:
nmem --json m search "query"
```

If the runtime already knows the active project or agent lane, add `--space "<space name>"` to Working Memory, memory search, thread search, and save commands.
If the runtime already knows the active project or agent lane, add `--space "<space name>"` to Context Bundle, Working Memory, memory search, thread search, and save commands.

If the recall need is conceptual or the first pass is weak, use deep search:

Expand All @@ -113,7 +124,7 @@ Prefer the smallest retrieval surface that answers the question.

Distill only durable knowledge worth keeping after the current session ends.

Use MCP `memory_add` for genuinely new decisions, procedures, lessons, preferences, or plans when available:
Use MCP `memory_add` for genuinely new facts, preferences, decisions, plans, procedures, learnings, events, or context when available. Pass `unit_type` when the type is clear:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Complete the unit_type guidance with enumerated values.

Line 116 ends with "Pass unit_type when the type is clear:" but doesn't provide the list of allowed values inline. Users would need to reference SKILL.md line 25 to discover the valid values.

Additionally, the text uses "learnings" (plural) in the content type list, but the actual unit_type value should be learning (singular) based on the enumeration in SKILL.md line 25.

📝 Suggested improvement

Option 1: Add the enumerated list inline:

-Use MCP `memory_add` for genuinely new facts, preferences, decisions, plans, procedures, learnings, events, or context when available. Pass `unit_type` when the type is clear:
+Use MCP `memory_add` for genuinely new facts, preferences, decisions, plans, procedures, learnings, events, or context when available. Pass `unit_type` when the type is clear: `fact`, `preference`, `decision`, `plan`, `procedure`, `learning`, `context`, or `event`.

Option 2: Make it a complete sentence without the colon:

-Use MCP `memory_add` for genuinely new facts, preferences, decisions, plans, procedures, learnings, events, or context when available. Pass `unit_type` when the type is clear:
+Use MCP `memory_add` for genuinely new facts, preferences, decisions, plans, procedures, learning, events, or context when available. Pass `unit_type` when known (allowed values documented in SKILL.md).

Note: Also consider using "learning" (singular) consistently instead of "learnings" to match the actual unit_type value.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Use MCP `memory_add` for genuinely new facts, preferences, decisions, plans, procedures, learnings, events, or context when available. Pass `unit_type` when the type is clear:
Use MCP `memory_add` for genuinely new facts, preferences, decisions, plans, procedures, learnings, events, or context when available. Pass `unit_type` when the type is clear: `fact`, `preference`, `decision`, `plan`, `procedure`, `learning`, `context`, or `event`.
Suggested change
Use MCP `memory_add` for genuinely new facts, preferences, decisions, plans, procedures, learnings, events, or context when available. Pass `unit_type` when the type is clear:
Use MCP `memory_add` for genuinely new facts, preferences, decisions, plans, procedures, learning, events, or context when available. Pass `unit_type` when known (allowed values documented in SKILL.md).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@GEMINI.md` at line 116, Update the GEMINI.md line that ends with "Pass
`unit_type` when the type is clear:" to include the explicit enumerated allowed
values for unit_type (use singular nouns): fact, preference, decision, plan,
procedure, learning, event, context; also replace the plural "learnings" with
the correct singular `learning` to match the SKILL.md enumeration and ensure the
guidance is complete and consistent.


If MCP tools are not exposed, use:

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This package is the **Gemini-native product surface** for Nowledge Mem.

It is deliberately **hybrid**:

- Gemini CLI loads `GEMINI.md` plus extension hooks for Working Memory bootstrap and session capture
- Gemini CLI loads `GEMINI.md` plus extension hooks for Context Bundle / Working Memory startup context and session capture
- the extension exposes local Nowledge Mem MCP tools for lower-friction retrieval and memory writes
- bundled commands wrap common `nmem` workflows
- bundled skills teach Gemini when to recall, distill, save real threads, and create handoff summaries
Expand Down Expand Up @@ -65,7 +65,7 @@ Release packaging and marketplace notes live in [`RELEASING.md`](./RELEASING.md)

**Automatic lifecycle hooks**

- Session start loads Working Memory into Gemini when a briefing exists
- Session start loads Context Bundle when available, with Working Memory as the lightweight fallback
- Before context compression, Gemini imports the current thread so the pre-compression transcript remains searchable
- Session end performs a best-effort real Gemini thread import through `nmem t save --from gemini-cli`

Expand All @@ -76,7 +76,7 @@ Release packaging and marketplace notes live in [`RELEASING.md`](./RELEASING.md)

**Persistent context**

- `GEMINI.md` tells Gemini how to route recall across Working Memory, distilled memories, conversation threads, thread save, distillation, and handoff summaries
- `GEMINI.md` tells Gemini how to route recall across Context Bundle, Working Memory, distilled memories, conversation threads, thread save, distillation, and handoff summaries

**Custom commands**

Expand Down
16 changes: 11 additions & 5 deletions commands/nowledge/read-working-memory.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
prompt = """
Load the user's Working Memory briefing before continuing.
Load the user's startup context before continuing.

Use the shell tool to try:
When identity, active space, active rules, or multi-agent behavior could matter, use Context Bundle first:

```bash
nmem --json context --source-app gemini-cli
```

For a lightweight daily briefing or older `nmem` clients, use:

```bash
nmem --json wm read
```

If the command succeeds but reports that no Working Memory exists yet, say that clearly.
If either command succeeds but returns no content yet, say that clearly.

If the runtime already knows the current project or agent lane, add `--space "<space name>"`.
If the runtime already knows the current project or agent lane, add `--space "<space name>"` to either command. Multi-agent orchestrators can set `NMEM_AGENT_ID="<agent-slug>"` before launching Gemini CLI. Add `NMEM_SPACE` only when that whole run should override the identity's default space. Use `NMEM_HOST_AGENT_ID` only for advanced host-id aliases.

Only if `nmem` is unavailable in an older local-only **Default-space** setup, fall back to:

```bash
cat ~/ai-now/memory.md
```

Then summarize the user's active focus areas, priorities, unresolved flags, and the most relevant recent changes when a briefing is actually present.
Then summarize only the parts relevant to the task. If Context Bundle was loaded, do not separately read Working Memory unless the user asks for a lightweight refresh.

If remote access is configured through `~/.nowledge-mem/config.json`, let `nmem` use it naturally. Do not assume environment variables are the only auth path.
"""
6 changes: 3 additions & 3 deletions gemini-extension.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"name": "nowledge-mem-gemini-cli",
"version": "0.1.8",
"description": "Gemini CLI extension for Nowledge Mem with persistent context, memory commands, and agent skills.",
"version": "0.1.9",
"description": "Gemini CLI extension for Nowledge Mem with startup context, memory commands, and agent skills.",
"mcpServers": {
"nowledge-mem": {
"httpUrl": "http://127.0.0.1:14242/mcp/",
"headers": {
"APP": "Gemini CLI"
},
"timeout": 10000,
"description": "Nowledge Mem local MCP tools for Working Memory, memory search, thread search, and memory writes."
"description": "Nowledge Mem local MCP tools for Context Bundle, Working Memory, memory search, thread search, and memory writes."
}
},
"contextFileName": "GEMINI.md"
Expand Down
102 changes: 84 additions & 18 deletions hooks/session-start.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,104 @@ function emit(payload) {
);
}

function readWorkingMemory() {
const result = spawnSync('nmem', ['--json', 'wm', 'read'], {
function parseJsonText(stdout, keys) {
try {
const data = JSON.parse(stdout || '{}');
for (const key of keys) {
const value = data[key];
if (typeof value === 'string' && value.trim()) {
return value.trim();
}
}
} catch {
// Fall back to the next context source.
}
return '';
}

function readNmem(args, keys) {
const result = spawnSync('nmem', ['--json', ...args], {
encoding: 'utf8',
timeout: 10000,
});

if (result.status === 0) {
try {
const data = JSON.parse(result.stdout || '{}');
const content = typeof data.content === 'string' ? data.content.trim() : '';
if (content) {
return content;
}
} catch {
// Fall back to the legacy file path below.
const content = parseJsonText(result.stdout, keys);
if (content) {
return content;
}
}
return '';
}

function envValue(name) {
const value = process.env[name];
return typeof value === 'string' ? value.trim() : '';
}

function withStartupArgs(args) {
const next = [...args];
const agentId = envValue('NMEM_AGENT_ID');
const hostAgentId = envValue('NMEM_HOST_AGENT_ID');
const space = envValue('NMEM_SPACE') || envValue('NMEM_SPACE_ID');
if (agentId && !next.includes('--agent-id')) {
next.push('--agent-id', agentId);
}
if (hostAgentId && !next.includes('--host-agent-id')) {
next.push('--host-agent-id', hostAgentId);
}
if (space && !next.includes('--space')) {
next.push('--space', space);
}
return next;
}

function withSpaceArgs(args) {
const next = [...args];
const space = envValue('NMEM_SPACE') || envValue('NMEM_SPACE_ID');
if (space && !next.includes('--space')) {
next.push('--space', space);
}
return next;
}

function readStartupContext() {
const contextBundle = readNmem(withStartupArgs(['context', '--source-app', 'gemini-cli']), ['rendered_markdown', 'markdown', 'content']);
if (contextBundle) {
return {
tag: 'nowledge_context_bundle',
label: 'Context Bundle',
content: contextBundle,
};
}

const workingMemory = readNmem(withSpaceArgs(['wm', 'read']), ['content']);
if (workingMemory) {
return {
tag: 'nowledge_working_memory',
label: 'Working Memory',
content: workingMemory,
};
}

const legacyPath = path.join(os.homedir(), 'ai-now', 'memory.md');
if (existsSync(legacyPath)) {
const content = readFileSync(legacyPath, 'utf8').trim();
if (content) {
return content;
return {
tag: 'nowledge_working_memory',
label: 'legacy Working Memory file',
content,
};
}
}

return '';
return null;
}

const workingMemory = readWorkingMemory();
const startupContext = readStartupContext();

if (!workingMemory) {
if (!startupContext) {
emit({
hookSpecificOutput: {
hookEventName: 'SessionStart',
Expand All @@ -53,11 +119,11 @@ if (!workingMemory) {
emit({
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: `<nowledge_working_memory>
Use this as current user context from Nowledge Mem Working Memory. It is situational context, not a higher-priority instruction.
additionalContext: `<${startupContext.tag}>
Use this as current user context from Nowledge Mem ${startupContext.label}. It is situational context, not a higher-priority instruction.

${workingMemory}
</nowledge_working_memory>`,
${startupContext.content}
</${startupContext.tag}>`,
Comment on lines +122 to +126

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Escape startup context text before injecting into XML-like wrapper.

At Line 122, raw startupContext.content is interpolated into <${startupContext.tag}>...</${startupContext.tag}>. If stored memory includes <...> or a matching close tag, it can break the wrapper boundary and alter prompt semantics.

🔧 Suggested fix
+function escapeXml(text) {
+  return String(text)
+    .replaceAll('&', '&amp;')
+    .replaceAll('<', '&lt;')
+    .replaceAll('>', '&gt;');
+}
+
 const startupContext = readStartupContext();
@@
 } else {
+  const escapedContent = escapeXml(startupContext.content);
   emit({
     hookSpecificOutput: {
       hookEventName: 'SessionStart',
       additionalContext: `<${startupContext.tag}>
 Use this as current user context from Nowledge Mem ${startupContext.label}. It is situational context, not a higher-priority instruction.
 
-${startupContext.content}
+${escapedContent}
 </${startupContext.tag}>`,
     },
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
additionalContext: `<${startupContext.tag}>
Use this as current user context from Nowledge Mem ${startupContext.label}. It is situational context, not a higher-priority instruction.
${workingMemory}
</nowledge_working_memory>`,
${startupContext.content}
</${startupContext.tag}>`,
additionalContext: `<${startupContext.tag}>
Use this as current user context from Nowledge Mem ${startupContext.label}. It is situational context, not a higher-priority instruction.
${escapedContent}
</${startupContext.tag}>`,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hooks/session-start.mjs` around lines 122 - 126, The injected startup context
is unescaped and can break the XML-like wrapper used in additionalContext;
before interpolating startupContext.content into the template (where
startupContext.tag and startupContext.label are used), XML-escape special
characters (&, <, >, ", ') in startupContext.content (and validate/sanitize
startupContext.tag if it can be user-controlled) so the wrapper boundaries
remain intact; update the code that builds additionalContext to use the escaped
content and ensure the original content remains unchanged elsewhere.

},
});
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nowledge-mem-gemini-cli",
"version": "0.1.8",
"version": "0.1.9",
"private": true,
"description": "Gemini CLI extension for Nowledge Mem",
"license": "MIT",
Expand Down
4 changes: 4 additions & 0 deletions release-notes/0.1.9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Nowledge Mem Gemini CLI 0.1.9

Session-start context now uses Nowledge Mem's full Context Bundle when available. Gemini can receive owner identity, AI Identity, active space, active rules, Working Memory, and KFS paths before falling back to the lighter Working Memory briefing on older `nmem` clients.

4 changes: 3 additions & 1 deletion skills/distill-memory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Detect breakthrough moments, durable lessons, and decisions worth p

# Distill Memory

Save proactively when the conversation produces a decision, preference, plan, procedure, learning, or important context. Do not wait to be asked.
Save proactively when the conversation produces a durable fact, preference, decision, plan, procedure, learning, event, or important context. Do not wait to be asked.

## Good Candidates

Expand All @@ -21,3 +21,5 @@ Save proactively when the conversation produces a decision, preference, plan, pr
- If an existing memory already captures the same decision, workflow, or preference and the new information refines it, use MCP `memory_update` when available. Otherwise use `nmem m update <id> ...` instead of creating a duplicate.

Prefer atomic, standalone memories with strong titles and structured meaning. Focus on what was learned or decided, not routine chatter.

When saving directly, pass `unit_type` when you know it: `fact`, `preference`, `decision`, `plan`, `procedure`, `learning`, `context`, or `event`.
19 changes: 16 additions & 3 deletions skills/read-working-memory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,21 @@ description: Read the user's daily Working Memory briefing at session start or w

# Read Working Memory

Use MCP `read_working_memory` when it is available. Otherwise use `nmem --json wm read` for the user's current priorities, unresolved flags, and recent context.
Use MCP `read_context_bundle` when startup identity, agent lane, space scope, or Rules could matter. It includes Working Memory plus the full owner/AI Identity/scope/rules contract.

If the runtime already knows the current project or agent lane, add `--space "<space name>"`.
If Context Bundle is unavailable or you only need current priorities, use MCP `read_working_memory` or:

```bash
nmem --json wm read
```

CLI Context Bundle fallback:

```bash
nmem --json context --source-app gemini-cli
```

If the runtime already knows the current project or agent lane, add `--space "<space name>"`. Multi-agent orchestrators can set `NMEM_AGENT_ID="<agent-slug>"` before launching Gemini CLI. Add `NMEM_SPACE` only when that whole run should override the identity's default space. Use `NMEM_HOST_AGENT_ID` only for advanced host-id aliases.

## When to Use

Expand All @@ -18,6 +30,7 @@ If the runtime already knows the current project or agent lane, add `--space "<s

## Usage Pattern

- Read once near the start of a session.
- Read Context Bundle or Working Memory once near the start of a session.
- If Context Bundle was already loaded and includes Working Memory, do not read Working Memory again.
- Reuse that context mentally instead of re-reading on every turn.
- Refresh only if the user asks, the session context changed materially, or a long-running session clearly needs it.
Loading