Alphakdeps.io is experimental and evolving — the registry and packages may change or reset.
Templatekdeps asset

kdeps-templates-knowledge

v2.2.1

kdeps template: knowledge

set templates / shelf workflow

PurposeProject scaffolds for kdeps new, and the knowledge vault's files (routing table, schema, graph page).

LLM context: none - kdeps uses it without sending it to the model

Where it runs

Part of the templates set, used in workflow and agent mode. Project scaffolds for kdeps new, and the knowledge vault's files (routing table, schema, graph page).

  1. kdeps new, or the knowledge vault's first use
  2. the template is renderedtemplates/knowledge
  3. the project or vault files are written

Loaded by: kdeps (agent loop), kdeps new, kdeps run, kdeps knowledge build

In the code: kdeps newtemplates.(*Generator).GenerateProject

Install

A kdeps asset: installed with the first-run profile that includes its set, or the first time a command needs it, and updated without a new release.

kdeps install templates/knowledge

Pin a version with kdeps install templates/knowledge@<version>.

Contents

KDEPS.md

# Vault routing

This vault is the long-term memory. Read this table first, then the
project card, then only the notes the knowledge recall names. Never read the
whole vault.

## Where everything lives

Every place kdeps runs is a project with one folder, created and filled in by
kdeps. Nothing here needs to be set up by hand.

| What | Home | Written by |
|---|---|---|
| Project status and next step | `projects/<name>/<name>.md` (the card) | kdeps on first run; `knowledge_card_update` |
| Session history (focus, built, mistakes, decisions, wins) | `projects/<name>/sessions/` | kdeps, every turn |
| Saved facts (`[MEMORY: key] value`) | `projects/<name>/memory/` | kdeps, one note per key |
| Decisions and their rationale | `projects/<name>/decisions/` | kdeps from session summaries; `knowledge_note_save` with type decision |
| Ideas, gotchas, concepts for one project | `projects/<name>/notes/` | `knowledge_note_save`, or by hand |
| Knowledge across projects | `shared/` | `knowledge_note_save` with scope shared, or by hand |
| Unsorted notes | `inbox/` | by hand |
| Finished projects | `archive/<name>/` | kdeps moves the folder when the card says `status: archived` |
| Connections between notes | `~/.kdeps/knowledge/<vault-id>/` (outside this vault) | kdeps |
| How to write cards and notes | `schema.md` | |

## Rules

- One home per fact. Status lives in the card, the story in notes, the
  connections in the graph. A fact written in two places goes stale in one.
- Cards stay short: frontmatter and a links table. They point; they do not
  tell the story.
- Update a card with `knowledge_card_update`, never by copying status into a
  note.
- `kdeps knowledge check` lists structural duplicates (the same summary, or the
  same `canonical` or `fact_id`), dangling links and notes without a summary.

## Status overview

kdeps lists every active project's status and next step on each turn, read
from the cards themselves. Do not copy status into this file.

report/graph.html

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Knowledge graph</title>
<style>
  :root { --bg: #0d1117; --fg: #c9d1d9; --dim: #8b949e; --line: rgba(255,255,255,0.08); --accent: #00e5ff; }
  html, body { margin: 0; height: 100%; background: var(--bg); color: var(--fg); font: 13px system-ui, sans-serif; }
  #bar { position: fixed; top: 12px; left: 12px; right: 12px; display: flex; gap: 12px; align-items: center; z-index: 2; }
  #bar input { flex: 0 1 320px; background: #161b22; color: var(--fg); border: 1px solid var(--line); border-radius: 2px; padding: 6px 8px; }
  #bar input:focus { outline: 1px solid var(--accent); }
  #stats { color: var(--dim); }
  #tip { position: fixed; pointer-events: none; background: #161b22; border: 1px solid var(--line); border-radius: 2px;
         padding: 6px 8px; max-width: 360px; display: none; z-index: 3; }
  #tip b { color: var(--fg); } #tip div { color: var(--dim); margin-top: 2px; }
  canvas { display: block; width: 100%; height: 100%; cursor: grab; }
  #legend { position: fixed; top: 56px; right: 12px; width: 280px; max-height: calc(100% - 80px); overflow-y: auto;
            background: rgba(22,27,34,0.92); border: 1px solid var(--line); border-radius: 2px; padding: 8px; z-index: 2; }
  #legend h2 { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); margin: 0 0 6px; }
  .com { display: flex; gap: 8px; align-items: flex-start; width: 100%; text-align: left; background: none; border: 1px solid transparent;
         color: var(--fg); font: inherit; padding: 4px; border-radius: 2px; cursor: pointer; transition: border-color .15s ease; }
  .com:hover, .com:focus-visible { border-color: var(--line); outline: none; }
  .com.on { border-color: var(--accent); }
  .sw { flex: 0 0 10px; height: 10px; border-radius: 50%; margin-top: 3px; }
  .com small { display: block; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 240px; }
</style>
</head>
<body>
<div id="bar"><input id="q" placeholder="Find a note..." aria-label="Find a note"><span id="stats"></span></div>
<div id="tip"></div>
<div id="legend" aria-label="Communities"><h2>Communities</h2></div>
<canvas id="c"></canvas>
<script>
const G = "__GRAPH_DATA__";
const MAX = 1500;
const deg = {};
for (const e of G.edges) { deg[e.from] = (deg[e.from] || 0) + 1; deg[e.to] = (deg[e.to] || 0) + 1; }
let nodes = G.nodes.slice().sort((a, b) => (deg[b.id] || 0) - (deg[a.id] || 0)).slice(0, MAX);
const byId = {};
nodes.forEach((n, i) => { n.x = Math.cos(i) * (50 + i); n.y = Math.sin(i) * (50 + i); n.vx = 0; n.vy = 0; byId[n.id] = n; });
const edges = G.edges.filter(e => byId[e.from] && byId[e.to]);
document.getElementById('stats').textContent =
  `${G.nodes.length} nodes, ${G.edges.length} edges, ${G.communities} communities` + (G.nodes.length > MAX ? ` (showing the ${MAX} most linked)` : '');
const color = c => `hsl(${(c * 137.508) % 360}, 65%, 60%)`;
// short is a node's display name: the last two parts of a package path.
const short = n => n.kind === 'code' ? n.label.split('/').slice(-2).join('/') : n.label;
// Communities: members by degree, and an anchor on a ring, largest first.
const coms = {};
for (const n of nodes) (coms[n.community] = coms[n.community] || []).push(n);
const comIds = Object.keys(coms).map(Number).sort((a, b) => coms[b].length - coms[a].length);
comIds.forEach(c => coms[c].sort((a, b) => (deg[b.id] || 0) - (deg[a.id] || 0)));
const anchor = {};
const ring = 120 + 25 * Math.sqrt(nodes.length);
comIds.forEach((c, i) => {
  const r = i === 0 ? 0 : ring * (1 + Math.floor((i - 1) / 8) * 0.6), t = (i - 1) * Math.PI / 4 + (i > 8 ? 0.4 : 0);
  anchor[c] = [Math.cos(t) * r, Math.sin(t) * r];
});
nodes.forEach((n, i) => { const [ax, ay] = anchor[n.community]; n.x = ax + Math.cos(i) * 30; n.y = ay + Math.sin(i) * 30; });
let focus = null;
const legend = document.getElementById('legend');
for (const c of comIds) {
  const btn = document.createElement('button');
  btn.className = 'com';
  btn.setAttribute('aria-pressed', 'false');
  const sw = document.createElement('span'); sw.className = 'sw'; sw.style.background = color(c);
  const txt = document.createElement('span');
  const head = document.createElement('span'); head.textContent = `#${c}  ${coms[c].length} nodes`;
  const top = document.createElement('small'); top.textContent = coms[c].slice(0, 3).map(short).join(', ');
  top.title = coms[c].slice(0, 8).map(n => n.label).join('\n');
  txt.append(head, top); btn.append(sw, txt);
  btn.addEventListener('click', () => {
    focus = focus === c ? null : c;
    for (const b of legend.querySelectorAll('.com')) { b.classList.remove('on'); b.setAttribute('aria-pressed', 'false'); }
    if (focus !== null) { btn.classList.add('on'); btn.setAttribute('aria-pressed', 'true'); }
  });
  legend.appendChild(btn);
}
const cv = document.getElementById('c'), ctx = cv.getContext('2d');
let W, H, scale = 1, ox = 0, oy = 0, query = '', hover = null;
function resize() { W = cv.width = innerWidth * devicePixelRatio; H = cv.height = innerHeight * devicePixelRatio; }
addEventListener('resize', resize); resize();
scale = Math.min(1, (innerHeight - 80) / (2.4 * ring + 200));
function step() {
  for (let i = 0; i < nodes.length; i++) {
    const a = nodes[i];
    for (let j = i + 1; j < nodes.length; j++) {
      const b = nodes[j], dx = a.x - b.x, dy = a.y - b.y, d2 = dx * dx + dy * dy + 0.01;
      if (d2 > 90000) continue;
      const f = 80 / d2; a.vx += dx * f; a.vy += dy * f; b.vx -= dx * f; b.vy -= dy * f;
    }
  }
  for (const e of edges) {
    const a = byId[e.from], b = byId[e.to], dx = b.x - a.x, dy = b.y - a.y;
    const k = (a.community === b.community ? 0.02 : 0.0015) * (e.provenance === 'extracted' ? 1 : 0.5);
    a.vx += dx * k; a.vy += dy * k; b.vx -= dx * k; b.vy -= dy * k;
  }
  for (const n of nodes) {
    const [ax, ay] = anchor[n.community];
    n.vx += (ax - n.x) * 0.01; n.vy += (ay - n.y) * 0.01; // each community gathers at its anchor
    n.vx *= 0.85; n.vy *= 0.85; n.x += n.vx; n.y += n.vy;
  }
}
function radius(n) { return 2 + Math.sqrt(deg[n.id] || 0) * 1.5; }
function toScreen(n) { return [W / 2 + (n.x + ox) * scale * devicePixelRatio, H / 2 + (n.y + oy) * scale * devicePixelRatio]; }
function dimmed(n) { return (query && !n.label.toLowerCase().includes(query)) || (focus !== null && n.community !== focus); }
function draw() {
  ctx.clearRect(0, 0, W, H);
  ctx.lineWidth = devicePixelRatio * 0.6;
  for (const e of edges) {
    const a = byId[e.from], b = byId[e.to];
    if (focus !== null && a.community !== focus && b.community !== focus) continue;
    const [x1, y1] = toScreen(a), [x2, y2] = toScreen(b);
    ctx.strokeStyle = e.provenance === 'extracted' ? 'rgba(255,255,255,0.10)' : 'rgba(255,214,10,0.25)';
    ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
  }
  for (const n of nodes) {
    const [x, y] = toScreen(n), match = query && n.label.toLowerCase().includes(query);
    ctx.fillStyle = dimmed(n) ? 'rgba(139,148,158,0.18)' : color(n.community);
    ctx.beginPath(); ctx.arc(x, y, radius(n) * devicePixelRatio * (match ? 1.8 : 1), 0, 7); ctx.fill();
    const lead = coms[n.community][0] === n;
    if (match || n === hover || (!dimmed(n) && (lead || (focus !== null && (deg[n.id] || 0) > 3)))) {
      ctx.fillStyle = '#c9d1d9'; ctx.font = `${11 * devicePixelRatio}px system-ui`;
      ctx.fillText(short(n), x + 6 * devicePixelRatio, y - 6 * devicePixelRatio);
    }
  }
}
let ticks = 0;
for (; ticks < 250; ticks++) step(); // settle before the first frame
(function loop() { if (ticks++ < 500) step(); draw(); requestAnimationFrame(loop); })();
const tip = document.getElementById('tip');
let drag = null;
cv.addEventListener('mousedown', e => { drag = [e.clientX, e.clientY, ox, oy]; });
addEventListener('mouseup', () => { drag = null; });
cv.addEventListener('mousemove', e => {
  if (drag) { ox = drag[2] + (e.clientX - drag[0]) / scale; oy = drag[3] + (e.clientY - drag[1]) / scale; return; }
  hover = null;
  for (const n of nodes) {
    const [x, y] = toScreen(n), dx = x / devicePixelRatio - e.clientX, dy = y / devicePixelRatio - e.clientY;
    if (dx * dx + dy * dy < 64) { hover = n; break; }
  }
  if (!hover) { tip.style.display = 'none'; return; }
  tip.style.display = 'block'; tip.style.left = (e.clientX + 12) + 'px'; tip.style.top = (e.clientY + 12) + 'px';
  tip.innerHTML = '';
  const b = document.createElement('b'); b.textContent = hover.label; tip.appendChild(b);
  const d = document.createElement('div');
  d.textContent = [hover.kind, 'community ' + hover.community, hover.note || '', hover.summary || ''].filter(Boolean).join(' - ');
  tip.appendChild(d);
});
cv.addEventListener('wheel', e => { e.preventDefault(); scale *= e.deltaY < 0 ? 1.1 : 0.9; }, { passive: false });
document.getElementById('q').addEventListener('input', e => { query = e.target.value.trim().toLowerCase(); });
</script>
</body>
</html>

schema.md

# Schema: cards and notes

## Card (`projects/<name>/<name>.md`)

kdeps writes this the first time it runs in a place: in a git repository the
project is named after the repository's root folder, else after the folder;
a workflow's project is named after the workflow (`metadata.name`) and shared by every place it runs from.

```markdown
---
type: project          # project, client, area, research, bot - any word
status: active         # active | paused | done | archived (archived moves the folder to archive/)
next: the one next step
updated: 2026-10-15
---
| Source | Path |
|---|---|
| repo | ~/code/my-project |
| folder | ~/Documents/my-project-notes |
| workflow | ~/agents/invoices |
```

A card has no prose. A `repo`, `folder` or `workflow` row ties a place on
disk to the project: kdeps runs in that place (or below it) use this project.
Add rows to make several places one project. kdeps adds an `index` row for a place's search index.

## Note

```markdown
---
type: note             # session | decision | gotcha | concept | tool | note
status: draft          # the note's own state: draft | evergreen | outdated
tags: [kdeps, graph]
created: 2026-10-15
summary: one line saying what this note holds
canonical: ship-monthly    # optional stable id; fact_id is the same field
links: ["[[Other note]]"]
---
```

- One idea per note: `projects/<name>/notes/` for one project, `shared/` for
  knowledge across projects. Two notes with the same `canonical` or `fact_id`
  are one fact in two homes; `kdeps knowledge check` reports that.
- `summary` is what the knowledge recall shows before anyone opens the note.
  Write it so the reader can decide whether to open it.
- Link with `[[Note title]]`, or the full path `[[projects/app/notes/Title]]`.
  Links written in a note are facts; the graph marks them as extracted.
  kdeps writes every link with its full vault path: sessions and decisions
  link their card as `[[projects/<name>/<name>]]`.

## Written by kdeps

- Session note (`sessions/`): `## Focus`, `## What was built`, `## Mistakes`,
  `## Decisions`, `## Wins`. Updated after every turn; rewritten from the
  model's summary at compaction and session end.
- Decision note (`decisions/`): one per item of a summary's Key Decisions,
  titled by its bold lead.
- Memory note (`memory/`): one per `[MEMORY: key]`; the frontmatter keeps the
  key, type and dates.
- Workflow run note (`sessions/<date> <name> runs.md`): one line per chat
  call, when `knowledge.workflow_notes: true`.

Versions

VersionPublishedStatus
2.2.110/27/2026active
2.2.010/16/2026active
2.1.010/16/2026active
2.0.010/15/2026active
1.0.010/15/2026active

Details

Author
kdeps
License
Apache-2.0
Latest Version
2.2.1
Published
10/15/2026

Tags

kdepstemplate