Project Learnings
An agent that works in a project keeps rediscovering the same things: which
of nine files holds the model list, that the tests only exist as ad-hoc
scripts, why a cache is per-process, which command actually deploys. This
skill turns those rediscoveries into a learnings graph in the user's
InfraNodus account — learn-<project> — that the next session reads before
it starts and appends to when it finishes.
It is a graph rather than a notes file because learnings link to the same
entities (file paths, modules, concepts) as everything else in the project:
"which parts of the codebase accumulate traps" is a graph query, and the
gaps between what is known and what the code contains show where nobody has
looked yet.
Prerequisites
The connected InfraNodus MCP server must expose these three tools (added
after infranodus-mcp-server 1.7.4; hosted at https://mcp.infranodus.com or
the npm package):
get_project_learnings — read (by prompt, by entity, overview, or list)
add_project_learnings — propose / write (dry run by default)
enable_project_learnings — create the graph, only on the user's explicit request
If they are missing from the tool list, say so once ("the connected
InfraNodus server doesn't expose project learnings; update it or connect
https://mcp.infranodus.com") and continue the task without this skill. Do
not emulate the feature with memory_add_relations — that would bypass the
consent model.
Consent — read before anything else
- Nothing is written unless the user enabled learnings for this project.
add_project_learnings refuses (enabled: false) when the graph does not
exist and never creates it. Never call enable_project_learnings on your
own initiative — only when the user explicitly asks to start keeping
learnings for a project. Before that call, tell them in two sentences what
will be stored (project knowledge only), where (a private, append-only
graph in their InfraNodus account that they can delete at any time), and
that each batch is shown before it is saved.
- Show every batch before it is written.
add_project_learnings is a
dry run by default. Present the plan, ask, and only then call again with
confirm: true. If the client supports MCP elicitation the server asks
the user itself and writes in the same call. Skip the question only when
the user has said, in this conversation, that they do not want to be asked.
- Project knowledge only. "The model list lives in nine files" is a
learning. "The user prefers short answers" is not — that belongs in the
client's own memory, never in a shared graph.
- No secrets, hostnames, env values, credentials, customer data, or
verbatim error output. Paths and descriptions only. The server rejects
secret-like statements and reports only their index.
- If
get_project_learnings returns enabled: false, carry on with the
task without this skill. At the end of a substantive task, if you hold at
least one learning that passes the admission criteria below, say once — one
sentence — that learnings are not enabled for this project and can be turned
on with "start keeping learnings". Never repeat that in the same session,
and never enable on your own. With nothing worth keeping, stay silent.
Workflow
1. Start of a substantive task — retrieve
Call get_project_learnings with the project name and the task as prompt.
Use the same project name every time (the repo or folder name; call the tool
with no project to see which names already exist in the account). The
response carries the most relevant learnings, the areas that have
accumulated knowledge (knownAreas), and the gaps. Read them before
opening files. Skip for trivial tasks (a typo, a one-line answer).
2. Before touching an unfamiliar area — look up the entity
get_project_learnings with entity set to the file path, module, or
concept returns every learning that mentions it. Cheap; do it before the
first edit in an area this session has not visited.
3. End of a substantive task — reflect
A substantive task is multi-step, involved discovery, a correction from the
user, or a non-obvious fix. Ask yourself four questions:
- What did I have to discover that was not in the docs or obvious from the code?
- Where did I go wrong first, and what was the actual cause?
- What decision did I make, and why that one?
- What would I check first next time?
- Self-assessment: what about my approach worked well in this project and
should be repeated, and what should I do differently next time — which
check would have caught a mistake earlier, which order of steps saved
effort, where did I over- or under-verify? Save these with type
approach. Be as honest about what went well as about what did not: both
are equally useful to the next session.
Draft 0–5 statements. Zero is a normal answer; do not pad. Each must pass
all five admission criteria:
- Not derivable from the code, docs, or git history in a few reads
- Would have saved time if known at the start
- Survived verification — only after the thing actually worked
- About the project, never about the person
- Adds insight, not just a fact: prefer learnings that connect things that
are not obviously connected — a cross-module dependency, a pattern that
recurs across files, the non-obvious consequence of a design choice, a
hypothesis about why something is the way it is. A location fact is
admissible; a location fact plus the reason it is scattered is better.
Then add_project_learnings (dry run) → show the plan → confirm: true
on agreement.
4. User-initiated
"Save what you learned", "remember this about the project", "/learnings":
run step 3 immediately for the current session, whatever its size.
Statement contract
One statement per learning, at most two sentences, with at least two
[[wikilinked]] entities — file paths, modules, functions, concepts,
tools. Entities are what make the graph queryable, so link the things a
future session would search for.
Each statement carries one type:
| type |
use for |
location |
where X lives, especially when it is scattered or misnamed |
trap |
what went wrong first and why |
convention |
how things are done here that the code does not spell out |
decision |
what was chosen and the rationale |
workflow |
how to run, test, build, deploy, debug |
question |
open, unresolved, worth a future session's attention |
approach |
self-assessment: what worked well and should be repeated, or what to do differently next time |
Example (a real one):
The list of available AI model names is duplicated in 9 places: the modelToUse enums in [[src/schemas/index.ts]], [[src/instructions.ts]], [[src/resources/about.ts]], [[README.md]], and the tool defaults in [[generateOntologyGraph.ts]] and [[analyzeLlmResults.ts]]. Source of truth is [[infranodus-app]] routes/ai.js getModelsAvailable, which keeps old names as aliases. — location
Reading the tool responses
enabled: false → not enabled for this project; nothing written. Carry on
(or, if the user asked to save, tell them it is not enabled and offer to
enable it — they decide).
dryRun: true with wouldWrite → show it. Items marked reinforced are
near-duplicates of something already known; they are written as a short
"Confirmed again" statement, which raises the weight of the same links.
That is deliberate: things that keep being rediscovered become central.
declined: true with a note → the user wants a change; adjust and try
once more. Without a note → do not retry.
dryRun: true with an elicitation field → the server tried to ask the
user directly but could not (dialog dismissed, client error). Not a
decline: show the plan and ask in chat as usual.
rejected with indices → strip the secret-like content from those
statements (the server never echoes it) and resubmit.
- A stale learning is not deleted (the graph is append-only); it is
superseded by a newer statement, and retrieval sorts newest first. If you
find a learning that names a file that no longer exists, add the
correction as a new statement.
Using the graph as a graph
Once a project has a few dozen learnings, the InfraNodus tools that work on
any saved graph work on learn-<project> too:
get_project_learnings with no prompt/entity → overview: known areas,
main concepts, gaps. Add type: "trap" for the fragile parts of the
project, type: "question" for what previous sessions left open,
type: "approach" for what worked and what to do differently.
analyze_existing_graph_by_name on the graph name → clusters, gaps, and
the most influential entities across all learnings.
optimize_knowledge_base with graphName: "learn-<project>" and
focus: "codebase" (or "procedural" for a rule-heavy project) →
structural feedback on the learnings themselves: which areas of the
project dominate what was learned, which are under-developed, which
clusters of learnings never connect. Add compareWith: ["repo-<project>-structure"]
when the infranodus skill built one to see the parts of the code base no
learning has touched.
- If the infranodus skill has built
repo-<project>-* graphs,
difference_between_texts with {graphName} contexts shows modules that
have code but no learnings — unexplored territory — and the learnings
graph can be added to that project's infranodus/manifest.json as a scope
with policy: "learnings" so question routing sees it. Optional; nothing
here depends on the manifest.
The type of each learning is stored as statement metadata (a category on
the statement), not as a node — so the graph's nodes are only the real
entities and the structure is not distorted by the labels. Filter by type
through the tool's type parameter or the statement categories in the
InfraNodus UI.
Relationship to other skills
- infranodus — maps what a repo or vault contains (deterministic
extraction). This skill records what an agent learned operating in it.
They share entity names, so the graphs compose.
- llm-wiki — authored knowledge pages compounding over time. Route there
when the user wants prose, not one-line operational learnings.
- Claude Code's own file memory — per-user, per-machine. Keep using it for
user preferences and for project facts when learnings are not enabled;
when they are, project-type facts go to both.
Who decides when this runs
This skill can only ask; every gate is the user's. Nothing here needs a hook
or a CLAUDE.md entry to work, and the skill ships neither.
- The real gate is server-side. Until the user enables
learn-<project>,
an automatic invocation is one read call that returns enabled: false, after
which the skill goes dormant for the session. Nothing is written, nothing is
created.
- Manual only. Set
disable-model-invocation: true in this file's
frontmatter (the description leaves Claude's context; only
/project-learnings invokes it), or per project in
.claude/settings.local.json:
"skillOverrides": { "project-learnings": "user-invocable-only" }
("off" hides it entirely).
- Remove. Delete the skill folder or uninstall the plugin.
- Every session, guaranteed. The user's own choice, never the skill's: a
line in the project's
CLAUDE.md such as
Before any multi-step task call get_project_learnings for project <name>,
or a SessionStart hook that injects the same sentence (the update-config
skill can add one).
Reference: https://code.claude.com/docs/en/skills ("Control who invokes a
skill", "Restrict Claude's skill access").
Example
User: start keeping learnings for this repo
Agent: I'll create a private, append-only graph learn-mcp-server-infranodus in your InfraNodus account. It will hold knowledge about operating in this project — where things live, traps, conventions, decisions — never anything about you, never secrets. I'll show each batch before saving, and you can delete the graph any time. Enabling now.
(calls enable_project_learnings)
…later, after a task…
Agent: Two things from this session worth keeping:
- [trap]
Reading a graph right after creating it can fail on infranodus.com: the app runs several instances with a per-process contexts-list cache ([[lib/middleware/getOwnerContextsList.js]]); [[src/utils/learnings.ts]] retries reads and treats any positive as authoritative.
- [workflow]
There is no test runner in the repo; [[npm test]] builds with tsc and runs [[node --test]] over test/*.test.mjs against dist/.
- [approach]
Testing the new tools through an in-memory [[MCP client]] with an intercepted [[fetch]] caught the body-shape and session-state bugs that unit tests of the pure helpers missed; do that before any live run in this project.
Save these? (dry run shown; on "yes" → confirm: true)
1---2name: project-learnings3description: Use PROACTIVELY, without being asked, at the START of any multi-step task in a git repo, project folder, or vault (before reading files) and at the END of one (to reflect and save). Applies whenever the user asks to implement, fix, refactor, debug, or investigate something in a project, whenever a first attempt was wrong or the user corrected the agent, and whenever the agent rediscovers something a previous session should have known. Retrieves what earlier sessions learned about operating in the project and saves durable insights (locations, traps, conventions, decisions with rationale, workflows, open questions, what to do differently) to an opt-in, append-only InfraNodus graph named learn- plus the project name. Also on "what do we know about this repo?", "what did I learn?", "save learnings", "remember this about the project", "start keeping learnings", "/learnings". Load before calling get_project_learnings, add_project_learnings, or enable_project_learnings directly. Never writes without the user's expl4---56# Project Learnings78An agent that works in a project keeps rediscovering the same things: which9of nine files holds the model list, that the tests only exist as ad-hoc10scripts, why a cache is per-process, which command actually deploys. This11skill turns those rediscoveries into a **learnings graph** in the user's12InfraNodus account — `learn-<project>` — that the next session reads before13it starts and appends to when it finishes.1415It is a graph rather than a notes file because learnings link to the same16entities (file paths, modules, concepts) as everything else in the project:17"which parts of the codebase accumulate traps" is a graph query, and the18gaps between what is known and what the code contains show where nobody has19looked yet.2021## Prerequisites2223The connected InfraNodus MCP server must expose these three tools (added24after `infranodus-mcp-server` 1.7.4; hosted at https://mcp.infranodus.com or25the npm package):2627- `get_project_learnings` — read (by prompt, by entity, overview, or list)28- `add_project_learnings` — propose / write (dry run by default)29- `enable_project_learnings` — create the graph, **only on the user's explicit request**3031If they are missing from the tool list, say so once ("the connected32InfraNodus server doesn't expose project learnings; update it or connect33https://mcp.infranodus.com") and continue the task without this skill. Do34not emulate the feature with `memory_add_relations` — that would bypass the35consent model.3637## Consent — read before anything else38391. **Nothing is written unless the user enabled learnings for this project.**40 `add_project_learnings` refuses (`enabled: false`) when the graph does not41 exist and never creates it. Never call `enable_project_learnings` on your42 own initiative — only when the user explicitly asks to start keeping43 learnings for a project. Before that call, tell them in two sentences what44 will be stored (project knowledge only), where (a private, append-only45 graph in their InfraNodus account that they can delete at any time), and46 that each batch is shown before it is saved.472. **Show every batch before it is written.** `add_project_learnings` is a48 dry run by default. Present the plan, ask, and only then call again with49 `confirm: true`. If the client supports MCP elicitation the server asks50 the user itself and writes in the same call. Skip the question only when51 the user has said, in this conversation, that they do not want to be asked.523. **Project knowledge only.** "The model list lives in nine files" is a53 learning. "The user prefers short answers" is not — that belongs in the54 client's own memory, never in a shared graph.554. **No secrets, hostnames, env values, credentials, customer data, or56 verbatim error output.** Paths and descriptions only. The server rejects57 secret-like statements and reports only their index.585. If `get_project_learnings` returns `enabled: false`, carry on with the59 task without this skill. At the end of a substantive task, if you hold at60 least one learning that passes the admission criteria below, say once — one61 sentence — that learnings are not enabled for this project and can be turned62 on with "start keeping learnings". Never repeat that in the same session,63 and never enable on your own. With nothing worth keeping, stay silent.6465## Workflow6667### 1. Start of a substantive task — retrieve6869Call `get_project_learnings` with the project name and the task as `prompt`.70Use the same project name every time (the repo or folder name; call the tool71with no `project` to see which names already exist in the account). The72response carries the most relevant learnings, the areas that have73accumulated knowledge (`knownAreas`), and the `gaps`. Read them before74opening files. Skip for trivial tasks (a typo, a one-line answer).7576### 2. Before touching an unfamiliar area — look up the entity7778`get_project_learnings` with `entity` set to the file path, module, or79concept returns every learning that mentions it. Cheap; do it before the80first edit in an area this session has not visited.8182### 3. End of a substantive task — reflect8384A substantive task is multi-step, involved discovery, a correction from the85user, or a non-obvious fix. Ask yourself four questions:8687- What did I have to discover that was not in the docs or obvious from the code?88- Where did I go wrong first, and what was the actual cause?89- What decision did I make, and why that one?90- What would I check first next time?91- **Self-assessment:** what about my approach worked well in this project and92 should be repeated, and what should I do differently next time — which93 check would have caught a mistake earlier, which order of steps saved94 effort, where did I over- or under-verify? Save these with type95 `approach`. Be as honest about what went well as about what did not: both96 are equally useful to the next session.9798Draft 0–5 statements. **Zero is a normal answer**; do not pad. Each must pass99all five admission criteria:1001011. Not derivable from the code, docs, or git history in a few reads1022. Would have saved time if known at the start1033. Survived verification — only after the thing actually worked1044. About the project, never about the person1055. Adds insight, not just a fact: prefer learnings that connect things that106 are not obviously connected — a cross-module dependency, a pattern that107 recurs across files, the non-obvious consequence of a design choice, a108 hypothesis about *why* something is the way it is. A location fact is109 admissible; a location fact plus the reason it is scattered is better.110111Then `add_project_learnings` (dry run) → show the plan → `confirm: true`112on agreement.113114### 4. User-initiated115116"Save what you learned", "remember this about the project", "/learnings":117run step 3 immediately for the current session, whatever its size.118119## Statement contract120121One statement per learning, at most two sentences, with **at least two122`[[wikilinked]]` entities** — file paths, modules, functions, concepts,123tools. Entities are what make the graph queryable, so link the things a124future session would search for.125126Each statement carries one `type`:127128| type | use for |129|---|---|130| `location` | where X lives, especially when it is scattered or misnamed |131| `trap` | what went wrong first and why |132| `convention` | how things are done here that the code does not spell out |133| `decision` | what was chosen and the rationale |134| `workflow` | how to run, test, build, deploy, debug |135| `question` | open, unresolved, worth a future session's attention |136| `approach` | self-assessment: what worked well and should be repeated, or what to do differently next time |137138Example (a real one):139140> `The list of available AI model names is duplicated in 9 places: the modelToUse enums in [[src/schemas/index.ts]], [[src/instructions.ts]], [[src/resources/about.ts]], [[README.md]], and the tool defaults in [[generateOntologyGraph.ts]] and [[analyzeLlmResults.ts]]. Source of truth is [[infranodus-app]] routes/ai.js getModelsAvailable, which keeps old names as aliases.` — `location`141142## Reading the tool responses143144- `enabled: false` → not enabled for this project; nothing written. Carry on145 (or, if the user asked to save, tell them it is not enabled and offer to146 enable it — they decide).147- `dryRun: true` with `wouldWrite` → show it. Items marked `reinforced` are148 near-duplicates of something already known; they are written as a short149 "Confirmed again" statement, which raises the weight of the same links.150 That is deliberate: things that keep being rediscovered become central.151- `declined: true` with a `note` → the user wants a change; adjust and try152 once more. Without a note → do not retry.153- `dryRun: true` with an `elicitation` field → the server tried to ask the154 user directly but could not (dialog dismissed, client error). Not a155 decline: show the plan and ask in chat as usual.156- `rejected` with indices → strip the secret-like content from those157 statements (the server never echoes it) and resubmit.158- A stale learning is not deleted (the graph is append-only); it is159 superseded by a newer statement, and retrieval sorts newest first. If you160 find a learning that names a file that no longer exists, add the161 correction as a new statement.162163## Using the graph as a graph164165Once a project has a few dozen learnings, the InfraNodus tools that work on166any saved graph work on `learn-<project>` too:167168- `get_project_learnings` with no `prompt`/`entity` → overview: known areas,169 main concepts, gaps. Add `type: "trap"` for the fragile parts of the170 project, `type: "question"` for what previous sessions left open,171 `type: "approach"` for what worked and what to do differently.172- `analyze_existing_graph_by_name` on the graph name → clusters, gaps, and173 the most influential entities across all learnings.174- `optimize_knowledge_base` with `graphName: "learn-<project>"` and175 `focus: "codebase"` (or `"procedural"` for a rule-heavy project) →176 structural feedback on the learnings themselves: which areas of the177 project dominate what was learned, which are under-developed, which178 clusters of learnings never connect. Add `compareWith: ["repo-<project>-structure"]`179 when the infranodus skill built one to see the parts of the code base no180 learning has touched.181- If the **infranodus** skill has built `repo-<project>-*` graphs,182 `difference_between_texts` with `{graphName}` contexts shows modules that183 have code but no learnings — unexplored territory — and the learnings184 graph can be added to that project's `infranodus/manifest.json` as a scope185 with `policy: "learnings"` so question routing sees it. Optional; nothing186 here depends on the manifest.187188The `type` of each learning is stored as statement metadata (a category on189the statement), not as a node — so the graph's nodes are only the real190entities and the structure is not distorted by the labels. Filter by type191through the tool's `type` parameter or the statement categories in the192InfraNodus UI.193194## Relationship to other skills195196- **infranodus** — maps what a repo or vault *contains* (deterministic197 extraction). This skill records what an agent *learned operating in it*.198 They share entity names, so the graphs compose.199- **llm-wiki** — authored knowledge pages compounding over time. Route there200 when the user wants prose, not one-line operational learnings.201- Claude Code's own file memory — per-user, per-machine. Keep using it for202 user preferences and for project facts when learnings are not enabled;203 when they are, project-type facts go to both.204205## Who decides when this runs206207This skill can only *ask*; every gate is the user's. Nothing here needs a hook208or a CLAUDE.md entry to work, and the skill ships neither.209210- **The real gate is server-side.** Until the user enables `learn-<project>`,211 an automatic invocation is one read call that returns `enabled: false`, after212 which the skill goes dormant for the session. Nothing is written, nothing is213 created.214- **Manual only.** Set `disable-model-invocation: true` in this file's215 frontmatter (the description leaves Claude's context; only216 `/project-learnings` invokes it), or per project in217 `.claude/settings.local.json`:218 `"skillOverrides": { "project-learnings": "user-invocable-only" }`219 (`"off"` hides it entirely).220- **Remove.** Delete the skill folder or uninstall the plugin.221- **Every session, guaranteed.** The user's own choice, never the skill's: a222 line in the project's `CLAUDE.md` such as223 `Before any multi-step task call get_project_learnings for project <name>`,224 or a `SessionStart` hook that injects the same sentence (the `update-config`225 skill can add one).226227Reference: https://code.claude.com/docs/en/skills ("Control who invokes a228skill", "Restrict Claude's skill access").229230## Example231232> **User:** start keeping learnings for this repo233>234> **Agent:** I'll create a private, append-only graph `learn-mcp-server-infranodus` in your InfraNodus account. It will hold knowledge about operating in this project — where things live, traps, conventions, decisions — never anything about you, never secrets. I'll show each batch before saving, and you can delete the graph any time. Enabling now.235> *(calls `enable_project_learnings`)*236>237> …later, after a task…238>239> **Agent:** Two things from this session worth keeping:240> 1. [trap] `Reading a graph right after creating it can fail on infranodus.com: the app runs several instances with a per-process contexts-list cache ([[lib/middleware/getOwnerContextsList.js]]); [[src/utils/learnings.ts]] retries reads and treats any positive as authoritative.`241> 2. [workflow] `There is no test runner in the repo; [[npm test]] builds with tsc and runs [[node --test]] over test/*.test.mjs against dist/.`242> 3. [approach] `Testing the new tools through an in-memory [[MCP client]] with an intercepted [[fetch]] caught the body-shape and session-state bugs that unit tests of the pure helpers missed; do that before any live run in this project.`243>244> Save these? *(dry run shown; on "yes" → `confirm: true`)*