Skip to content

Commit eab9fef

Browse files
committed
v1.9.4: visible knowledge hooks, L0-only SessionStart, identity onboarding
Hooks now visible in the terminal: - first-prompt-inject emits `knowledge: injected N hits (...)` systemMessage - session-start always appends `| identity: ...` status suffix SessionStart auto-wakeup drops L1 (first-prompt-inject covers it better with query-targeted search). AGENT_KNOWLEDGE_WAKEUP_BUDGET default 800 → 200. ~600-750 fewer tokens pre-loaded per session, no loss of context quality. Fix: session-start clears the first-prompt-inject marker on every SessionStart event so /exit+resume re-arms injection (was silently skipping on resume because the prior run's marker stayed on disk). Add: identity onboarding. When identity.md is missing and no .identity-declined opt-out exists, session-start injects an instruction telling the agent to ask the user 3 short questions and save via the host's Write tool. No manual file creation. 563/563 tests passing; prettier/typecheck/lint clean.
1 parent 576609b commit eab9fef

9 files changed

Lines changed: 336 additions & 43 deletions

File tree

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# Changelog
22

3+
## 1.9.4 (2026-04-19) — visible knowledge-hook feedback + L0-only SessionStart
4+
5+
### Changed
6+
7+
- **`scripts/hooks/first-prompt-inject.mjs` now emits a user-visible `systemMessage`** alongside the silent `additionalContext`. When the session's first real prompt triggers injection, the host renders a one-line summary like `knowledge: injected 4 hits (decisions/database-choice.md, sessions/odoo-sh/...md, +2 more)` in the transcript, so the user can see which entries the model received without having to diff context.
8+
- **`scripts/hooks/session-start.js` now auto-loads L0 identity only**, not the full L0+L1 wakeup pack. L1 top-weighted facts are covered by `first-prompt-inject.mjs`, which runs query-targeted search against the KB on the session's first real prompt — a better-matched slice than any query-agnostic pre-load. Effect: ~600 fewer tokens pre-loaded per session on average, no loss of context quality because `first-prompt-inject.mjs` fills the same budget one turn later with relevance-ranked content.
9+
- **`AGENT_KNOWLEDGE_WAKEUP_BUDGET` default changed from 800 → 200 tokens.** L0 identity is small; the legacy 800-token ceiling existed to accommodate the now-removed L1 pack. Users who want the full legacy L0+L1 injection can still call `knowledge(action="wakeup")` manually — the MCP tool is unchanged.
10+
- **SessionStart `systemMessage`** now always appends a status suffix so the user can tell the hook ran: `identity: 312 chars` when an `identity.md` is loaded, `identity: placeholder (no identity.md)` when only the default placeholder was emitted, or `autowake off · <source>` when disabled. Previously the suffix was suppressed on the "no identity.md" path, which was visually indistinguishable from the pre-v1.9.4 URL-only line.
11+
12+
### Fixed
13+
14+
- **`session-start.js` clears the `first-prompt-inject.mjs` marker on every SessionStart event.** Resumed sessions reuse the same `session_id`, so the marker from the prior run stayed on disk and the inject hook silently skipped the next user prompt. Now every `/exit`+resume (or `/clear`) re-arms query-targeted injection. When the marker is cleared, the SessionStart `systemMessage` appends `· inject rearmed`.
15+
16+
### Added
17+
18+
- **Identity onboarding.** When no `identity.md` (or `IDENTITY.md`) exists in the memory dir AND no `.identity-declined` opt-out marker is present, `session-start.js` injects a short instruction into `additionalContext` telling the agent to ask the user three questions (name/role, stack, projects) and save the answers via the host's `Write` tool directly into `~/agent-knowledge/identity.md`. No manual file creation; the agent drives the flow. If the user says "skip" / "not now", the agent writes `~/agent-knowledge/.identity-declined` and the nag stops. Delete that file to re-enable. The SessionStart `systemMessage` reports `identity: onboarding pending` or `identity: declined` accordingly.
19+
20+
Neither change alters the model-facing MCP API. Fail-open behaviour unchanged: errors still produce a valid empty JSON response.
21+
22+
### Tests
23+
24+
563/563 passing. New assertions cover: `first-prompt-inject.mjs` `systemMessage` shape, `session-start.js` dashboard-URL line, identity-onboarding instruction when neither `identity.md` nor `.identity-declined` exist, opt-out path via `.identity-declined`, happy-path with an existing `identity.md`, and first-prompt-inject marker clear on resume. Existing hook tests only asserted `hookSpecificOutput`, which stays the same shape; the new `systemMessage` field is additive.
25+
326
## 1.9.3 (2026-04-19) — README showcase rewrite
427

528
### Changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
44
[![Node >= 20](https://img.shields.io/badge/Node-%3E%3D%2020-brightgreen.svg)](https://nodejs.org)
5-
[![Tests: 558 passing](https://img.shields.io/badge/Tests-558%20passing-brightgreen.svg)]()
5+
[![Tests: 563 passing](https://img.shields.io/badge/Tests-563%20passing-brightgreen.svg)]()
66
[![MCP Tools: 6](https://img.shields.io/badge/MCP%20Tools-6-blueviolet.svg)]()
77
[![LongMemEval R@5: 98.8%](https://img.shields.io/badge/LongMemEval%20R%405-98.8%25-brightgreen.svg)]()
88

@@ -383,7 +383,7 @@ This enables HTTP-based writes from other services without an MCP connection.
383383
## Testing
384384

385385
```bash
386-
npm test # 558 tests across 35 files
386+
npm test # 563 tests across 35 files
387387
npm run test:watch # Watch mode
388388
npm run lint # ESLint on src/ and tests/
389389
npm run typecheck # tsc --noEmit

agent-desk-plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"id": "agent-knowledge",
33
"name": "Knowledge",
44
"icon": "psychology",
5-
"version": "1.9.3",
5+
"version": "1.9.4",
66
"description": "Knowledge base — entries, sessions, search, graph relationships",
77
"ui": "./dist/ui/app.js",
88
"css": "./dist/ui/styles.css",

docs/HOOKS.md

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,61 @@ returns an empty JSON object so the user is never blocked.
1818

1919
## session-start.js
2020

21-
Two jobs:
21+
Four jobs:
2222

2323
1. Prints the dashboard URL (`http://localhost:3423` by default, override via
2424
`AGENT_KNOWLEDGE_PORT`) and injects it into SessionStart `additionalContext`.
25-
2. Auto-loads a token-budgeted **wakeup** payload (L0 identity + L1 top-weighted
26-
entries) into the same `additionalContext`, so the agent starts every session
27-
with a small world-model already in the prompt — no manual
28-
`knowledge(action="wakeup")` call required.
25+
2. Auto-loads the **L0 identity** section (who the user is — query-agnostic
26+
facts from `~/agent-knowledge/identity.md`) into the same
27+
`additionalContext`, so every session starts with that context without a
28+
manual `knowledge(action="wakeup")` call.
29+
3. Clears the `first-prompt-inject.mjs` marker file for this `session_id` on
30+
every SessionStart event (startup / resume / clear). Resumed sessions
31+
reuse the same `session_id`, so without this the inject hook silently
32+
skips on the next prompt because the prior run's marker is still on disk.
33+
4. **Identity onboarding**: if neither `identity.md` nor `IDENTITY.md` exists
34+
in the memory dir AND there is no `.identity-declined` opt-out marker,
35+
appends a short instruction to `additionalContext` telling the agent to
36+
ask the user three questions (name/role, stack, projects) and save the
37+
answers via the host's `Write` tool. The agent drives the conversation;
38+
the user never has to manually create the file. If the user says "skip"
39+
or "not now", the agent creates `~/agent-knowledge/.identity-declined` and
40+
the onboarding prompt stops firing. Delete that file to re-enable.
41+
42+
L1 top-weighted facts are **not** auto-loaded here. The companion
43+
`first-prompt-inject.mjs` runs a query-targeted KB search on the session's
44+
first real prompt and injects a better-matched slice than any
45+
query-agnostic pre-load would. Users who want the full legacy L0+L1 pack can
46+
still call `knowledge(action="wakeup")` manually, or flip
47+
`AGENT_KNOWLEDGE_WAKEUP_BUDGET` up to re-enable larger identity sections.
48+
49+
Also emits a user-visible `systemMessage` on every session start with a
50+
status suffix so the user can always tell the hook ran:
51+
52+
- `agent-knowledge: http://localhost:3423 | identity: 312 chars` — when a
53+
user-authored `identity.md` was loaded.
54+
- `agent-knowledge: http://localhost:3423 | identity: onboarding pending`
55+
when no identity file exists and onboarding is queued (agent will ask
56+
the user on the next prompt).
57+
- `agent-knowledge: http://localhost:3423 | identity: declined (delete ~/agent-knowledge/.identity-declined to redo)`
58+
when the user previously opted out.
59+
- `agent-knowledge: http://localhost:3423 | identity: placeholder (no identity.md) · inject rearmed`
60+
fallback for edge cases (e.g. stat errors), with `· inject rearmed` when
61+
the first-prompt-inject marker was cleared.
62+
- `agent-knowledge: http://localhost:3423 | autowake off · resume` — when
63+
`AGENT_KNOWLEDGE_AUTOWAKE=0` disables auto-injection.
64+
65+
The model-facing context and the user-facing line are separate channels —
66+
the `systemMessage` is purely a UX signal so the user can see what got
67+
pre-loaded and whether the next prompt will trigger query-targeted injection.
2968

3069
### Environment variables
3170

32-
| Variable | Default | Description |
33-
| ------------------------------- | ------- | --------------------------------------------------- |
34-
| `AGENT_KNOWLEDGE_PORT` | `3423` | Dashboard port announced in the context line |
35-
| `AGENT_KNOWLEDGE_AUTOWAKE` | `1` | Set `0` / `false` to skip the wakeup auto-injection |
36-
| `AGENT_KNOWLEDGE_WAKEUP_BUDGET` | `800` | Max tokens (chars/4 estimate) for the L0+L1 blob |
71+
| Variable | Default | Description |
72+
| ------------------------------- | ------- | ------------------------------------------------------- |
73+
| `AGENT_KNOWLEDGE_PORT` | `3423` | Dashboard port announced in the context line |
74+
| `AGENT_KNOWLEDGE_AUTOWAKE` | `1` | Set `0` / `false` to skip the identity auto-injection |
75+
| `AGENT_KNOWLEDGE_WAKEUP_BUDGET` | `200` | Max tokens (chars/4 estimate) for the L0 identity slice |
3776

3877
Requires `npm run build` to have succeeded — if `dist/wakeup.js` is missing,
3978
only the dashboard-URL line is emitted (fail-open).
@@ -67,6 +106,11 @@ The marker file (`{dataDir}/.first-prompt-seen/<session_id>`) is only written
67106
AFTER a real search has run — skip-gates like `/clear` do NOT burn the shot,
68107
so the user's next real question still gets injection.
69108

109+
When injection fires, the hook also emits a user-visible `systemMessage`:
110+
`knowledge: injected 4 hits (decisions/database-choice.md, sessions/foo.md, +2 more)`.
111+
This is separate from the model-facing `additionalContext` — the user sees a
112+
one-line summary in the transcript, the model sees the full rendered hit list.
113+
70114
### Environment variables
71115

72116
| Variable | Default | Description |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "agent-knowledge",
3-
"version": "1.9.3",
3+
"version": "1.9.4",
44
"description": "Cross-session memory and recall for AI agents — git-synced knowledge base, knowledge graph, confidence scoring, hybrid semantic+TF-IDF search, auto-distillation with secrets scrubbing",
55
"type": "module",
66
"main": "dist/index.js",

scripts/hooks/first-prompt-inject.mjs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { homedir } from 'os';
3838
const __filename = fileURLToPath(import.meta.url);
3939
const __dirname = dirname(__filename);
4040

41-
function emit(additionalContext) {
41+
function emit(additionalContext, systemMessage) {
4242
const payload = additionalContext
4343
? {
4444
hookSpecificOutput: {
@@ -47,10 +47,22 @@ function emit(additionalContext) {
4747
},
4848
}
4949
: {};
50+
if (systemMessage) payload.systemMessage = systemMessage;
5051
process.stdout.write(JSON.stringify(payload) + '\n');
5152
process.exit(0);
5253
}
5354

55+
function summarizeHits(hits) {
56+
if (!hits || hits.length === 0) return null;
57+
const paths = hits
58+
.map((h) => h.entry?.path)
59+
.filter(Boolean)
60+
.slice(0, 2);
61+
const suffix = hits.length > paths.length ? `, +${hits.length - paths.length} more` : '';
62+
const list = paths.length ? ` (${paths.join(', ')}${suffix})` : '';
63+
return `knowledge: injected ${hits.length} hit${hits.length === 1 ? '' : 's'}${list}`;
64+
}
65+
5466
function warn(msg) {
5567
process.stderr.write(`[knowledge firstprompt] ${msg}\n`);
5668
}
@@ -221,7 +233,7 @@ async function main() {
221233
const budgetChars = tokenBudget() * 4;
222234
const rendered = renderHits(hits, budgetChars);
223235
if (!rendered) return emit(null);
224-
emit(rendered);
236+
emit(rendered, summarizeHits(hits));
225237
}
226238

227239
main().catch((err) => {

0 commit comments

Comments
 (0)