Give your AI agent a memory it keeps

Archivum keeps what your agent learns as plain files on your machine: short notes, session transcripts and a small wiki. A background “dream” reviews them, corrects stale facts and proposes better memory. You read the diff and approve it.

npx archivum-cli init

Free and open source. No account. Dreaming runs on your own LLM key; nothing leaves your machine until you dream.

Vector search float field(vec2 p, float t) { float v = 0.0; vec4 C[4]; C[0] = vec4(540.0, 118.0, 52.0, 0.62); C[1] = vec4(585.0, 330.0, 64.0, 0.85); C[2] = vec4(350.0, 398.0, 40.0, 0.5); C[3] = vec4(664.0, 214.0, 32.0, 0.42); for (int i = 0; i < 4; i++) { float ph = TAU * t / T + float(i) * 1.7; v += blob(p, C[i].xy + vec2(7.0 * cos(ph), 6.0 * sin(ph)), C[i].z * (1.0 + 0.08 * sin(2.0 * ph)), C[i].w); } float heat = max(1.0 - sm(0.0, 0.6, t), sm(2.8, 3.3, t)); v += blob(p, vec2(318.0, 222.0), 60.0 * (1.0 + 0.06 * heat), 0.8 + 0.45 * heat); vec2 S = vec2(62.0, 318.0); vec2 D = vec2(212.0, 222.0); float u = clamp((t - 0.8) / 1.8, 0.0, 1.0); vec2 q = t < 0.6 ? D : mix(S, D, ease(u)); float k = t < 0.6 ? 1.05 * (1.0 - sm(0.0, 0.6, t)) : 1.05 * sm(0.6, 0.9, t); float speed = (u > 0.0 && u < 1.0) ? sin(3.14159 * u) : 0.0; vec2 dir = normalize(D - S); v += blob(p, q, 24.0, k); for (int s = 1; s <= 4; s++) v += blob(p, q - dir * 16.0 * float(s), 24.0 - 4.0 * float(s), k * speed * (0.55 - 0.12 * float(s))); if (t > 2.6 && t < 3.95) { float R = 30.0 + 320.0 * sm(2.6, 3.9, t); v += 0.42 * (1.0 - sm(2.6, 3.95, t)) * exp(-pow(length(p - D) - R, 2.0) / 260.0) * (v > 0.05 ? 1.6 : 1.0); } if (hash(floor(p / vec2(7.0, 9.4)) * vec2(7.0, 3.0)) > 0.992) v = max(v, 0.3); return v; }
terminalexample
archivum recall "how do I run the tests?" \
  --limit 3
  1. 8.41make test, no env varwiki
  2. 3.97CI runs make testnote
  3. 2.12flaky snapshot testswiki

From a conversation to memory that gets better

Your agent writes notes and its sessions are saved as transcripts. A dream looks back over them with hindsight and proposes updates to a small wiki. The next session starts from the approved version.

  1. Data pipeline float field(vec2 p, float t) { if (p.x < 30.0 || p.x > 690.0) return 0.0; float ph = TAU * t / T; // how raw the stream still is: 1 before the first sieve, less after each float raw = 1.0 - 0.34 * step(240.0, p.x) - 0.33 * step(380.0, p.x) - 0.3 * step(520.0, p.x); float soft = 1.0 - 0.34 * sm(220.0, 260.0, p.x) - 0.33 * sm(360.0, 400.0, p.x) - 0.3 * sm(500.0, 540.0, p.x); // turbulence that flows right, periodic in the cycle so the loop closes float wob = sin(p.x * 0.045 - ph * 3.0 + sin(p.x * 0.013 + ph)) * 52.0 + sin(p.x * 0.11 - ph * 5.0) * 18.0; float y = 232.0 + wob * soft; float spread = 14.0 + 40.0 * soft; // grit flows with the stream, 72 columns a cycle, and its pattern repeats over those, so the loop closes float grit = soft > 0.2 ? step(0.55 - 0.3 * (1.0 - soft), hash(vec2(mod(floor((p.x - 504.0 * t / T) / 7.0), 72.0), floor(p.y / 9.4)))) : 1.0; float pulse = 0.55 + 0.45 * pow(0.5 + 0.5 * sin(p.x * 0.06 - ph * 6.0), 3.0); float v = exp(-pow(p.y - y, 2.0) / (spread * spread)) * pulse * (0.55 + 0.45 * grit) * (0.7 + 0.3 * (1.0 - soft)); return v; }
    01

    Your agent works

    Claude Code hooks or the MCP server save short notes and session transcripts to ~/.archivum on your machine. Known secret patterns are redacted before anything is written.

  2. 02

    Notes are searchable

    Notes and wiki pages are ranked with BM25 keyword search. The agent can also follow links between wiki pages from the index. No embeddings, no vector database.

  3. 03

    A dream reviews and corrects

    When you run archivum dream, a model checks the wiki against your transcripts, fixes stale or wrong facts, merges duplicates and links related pages. It writes a proposal, and nothing changes until you apply it.

  4. Streaming tokens float field(vec2 p, float t) { float ph = TAU * t / T; float v = blob(p, vec2(88.0, 230.0), 30.0, 1.05); // the ribbon: a travelling wave from the model to the response, pulses running along it (six a cycle) if (p.x > 116.0 && p.x < 356.0) { float env = sin(3.14159 * (p.x - 116.0) / 240.0); float y = 230.0 + 30.0 * env * sin(p.x * 0.035 - ph * 2.0); float pulse = pow(0.5 + 0.5 * sin(p.x * 0.08 - ph * 6.0), 4.0); v += exp(-pow(p.y - y, 2.0) / 64.0) * (0.3 + 0.75 * pulse); } // the response: four lines; the last is written by the cursor, then it all scrolls up a line if (p.x > 376.0 && p.x < 654.0 && p.y > 130.0 && p.y < 322.0) { float yy = p.y + 46.0 * sm(3.2, 3.8, t); float idx = floor((yy - 160.0 + 23.0) / 46.0); float ly = 160.0 + 46.0 * idx; float on = step(0.32, hash(vec2(floor(p.x / 7.0), 1.0))); float front = t < 0.3 ? 380.0 : t < 3.0 ? mix(380.0, 650.0, (t - 0.3) / 2.7) : 650.0; float filled = idx < 3.0 ? 1.0 : idx < 4.0 ? (t > 3.0 ? 1.0 : 1.0 - sm(front - 6.0, front + 4.0, p.x)) : 0.0; float band = exp(-pow(yy - ly, 2.0) / 49.0); v += band * (on * (0.55 + 0.35 * hash(vec2(floor(p.x / 7.0), 2.0))) * filled + 0.13 * (1.0 - filled) * on); float cx = t < 3.0 ? front : 380.0; float cyy = t < 3.0 ? 298.0 : 298.0 - 46.0 * sm(3.2, 3.8, t) + 46.0 * step(3.8, t); v += blob(p, vec2(cx + 3.0, cyy), 8.0, 1.2 * (t < 3.0 || t > 3.8 ? 1.0 : 0.0)); } return v; }
    04

    The next session starts informed

    A short index of the wiki is loaded at session start, so your agent does not have to rediscover what earlier sessions already learned.

Install in one command

Run archivum init and it sets up the hooks and MCP server for the clients it finds. You can also wire things by hand, as shown here.

  • Hooks verified against the Claude Code docs
  • Standard MCP over stdio, 13 tools
  • One space per project, kept as plain folders
  • Not yet tested in every MCP client
Read the quickstart
# set up hooks and the MCP server for the clients found
npx archivum-cli init

# later: review what your sessions taught the agent
archivum dream
archivum apply
What happens~/.archivum
dream  proposes wiki changes with your own LLM key
apply  writes them to the live wiki, as versions

Memory that gets reviewed, not just stored

Notes are searchable right away. Dreams turn notes and transcripts into a small wiki and propose fixes. You decide what is applied, and everything stays in files you own.

A wiki your agent can navigate

Plain markdown files in ~/.archivum, linked to each other, with an _index.md that lists them. Open them in any editor, or let your agent follow the links and search.

RAG answer A question lands among a field of document pages; the three closest pages light up and are pulled in around it, and an answer page beside them writes itself line by line from those three, each part ending in a citation mark. // A page centred at c: 5 columns by 6 cells. Faint paper that flickers, a title, ragged lines of text; lit brightens it // and hot (a read) flashes its text. float page(vec2 p, vec2 c, float s, float lit, float hot, float ph) { vec2 q = p - c + vec2(17.5, 28.2); if (q.x < 0.0 || q.x >= 35.0 || q.y < 0.0 || q.y >= 56.4) return 0.0; float lc = floor(q.x / 7.0); float lk = floor(q.y / 9.4); float hs = hash(vec2(lc + s * 5.0, lk + s)); float v = mix(0.13, 0.28, lit) + 0.05 * sin(ph + hs * 40.0); if (lk < 0.5) { if (lc < 2.0 + floor(2.0 * hash(vec2(s, 1.0)))) v = mix(0.45, 1.1, lit); } else if (lk > 1.5) { if (lc < 2.0 + floor(3.6 * hash(vec2(s, lk)))) v = mix(0.3, 0.72, lit) + 0.4 * hot; } return v; } // The question: a speech bubble 9 columns by 4 cells, two lines of words inside its rim, a tail under its left end. float bubble(vec2 p, vec2 c, float k) { vec2 q = p - c + vec2(31.5, 18.8); if (q.x < 0.0 || q.x >= 63.0 || q.y < 0.0 || q.y >= 47.0) return 0.0; float lc = floor(q.x / 7.0); float lk = floor(q.y / 9.4); float side = (lc < 0.5 || lc > 7.5) ? 1.0 : 0.0; float v = 0.0; if (lk < 3.5) { if (lk < 0.5 || lk > 2.5) v = side > 0.5 ? 0.0 : 0.45; else if (side > 0.5) v = 0.45; else if (lk < 1.5 || lc < 5.5) v = 1.15; } else if (lc > 0.5 && lc < 2.5) v = 0.45; return v * k; } float field(vec2 p, float t) { if (p.x < 24.0 || p.x > 696.0 || p.y < 24.0 || p.y > 436.0) return 0.0; float ph = TAU * t / T; float v = 0.0; // the rest of the field, idle v = max(v, page(p, vec2(70.0, 78.0), 4.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(150.0, 58.0), 5.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(250.0, 60.0), 6.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(390.0, 64.0), 7.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(452.0, 108.0), 8.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(90.0, 162.0), 9.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(404.0, 182.0), 10.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(62.0, 310.0), 11.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(134.0, 360.0), 12.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(204.0, 404.0), 13.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(392.0, 330.0), 14.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(352.0, 404.0), 15.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(450.0, 380.0), 16.0, 0.0, 0.0, ph)); v = max(v, page(p, vec2(66.0, 400.0), 17.0, 0.0, 0.0, ph)); // the three nearest: let go as the next question comes in, lit as the ring reaches them, pulled in, read and sent { float g = t < 1.5 ? 1.0 - ease((t - 0.3) / 0.6) : ease((t - 1.850) / 0.65); float lit = t < 1.0 ? 1.0 - sm(0.3, 0.75, t) : sm(1.379, 1.499, t); float hot = exp(-pow(t - 2.590, 2.0) / 0.006); v = max(v, page(p, mix(vec2(165.0, 175.0), vec2(220.0, 182.0), g), 1.0, lit, hot, ph)); } { float g = t < 1.5 ? 1.0 - ease((t - 0.3) / 0.6) : ease((t - 1.910) / 0.65); float lit = t < 1.0 ? 1.0 - sm(0.3, 0.75, t) : sm(1.459, 1.579, t); float hot = exp(-pow(t - 2.840, 2.0) / 0.006); v = max(v, page(p, mix(vec2(300.0, 350.0), vec2(262.0, 302.0), g), 2.0, lit, hot, ph)); } { float g = t < 1.5 ? 1.0 - ease((t - 0.3) / 0.6) : ease((t - 1.970) / 0.65); float lit = t < 1.0 ? 1.0 - sm(0.3, 0.75, t) : sm(1.587, 1.707, t); float hot = exp(-pow(t - 3.090, 2.0) / 0.006); v = max(v, page(p, mix(vec2(335.0, 125.0), vec2(282.0, 180.0), g), 3.0, lit, hot, ph)); } // the question: the last one fades as the next comes in along the lane and lands v = max(v, bubble(p, vec2(250.0, 238.0), 1.0 - sm(0.3, 0.5, t))); v = max(v, bubble(p, mix(vec2(40.0, 238.0), vec2(250.0, 238.0), ease((t - 0.3) / 0.8)), sm(0.3, 0.45, t))); { float u = clamp((t - 0.3) / 0.8, 0.0, 1.0); float sp = (u > 0.0 && u < 1.0) ? sin(3.14159 * u) : 0.0; vec2 b = mix(vec2(40.0, 238.0), vec2(250.0, 238.0), ease(u)); for (int s = 1; s <= 3; s++) v += blob(p, b - vec2(32.0 + 13.0 * float(s), 0.0), 9.0 - 1.5 * float(s), sp * (0.5 - 0.12 * float(s))); } // the search ring if (t > 1.100 && t < 1.750) { float u = (t - 1.100) / 0.650; float R = 14.0 + 136.0 * (1.0 - (1.0 - u) * (1.0 - u)); v += 0.5 * (1.0 - u) * exp(-pow(length((p - vec2(250.0, 238.0)) * vec2(1.0, 1.0)) - R, 2.0) / 90.0); } // the line to the answer: dotted, on one row of cells; each part runs down it with a short tail if (abs(p.y - 238.500) < 1.0 && p.x > 290.0 && p.x < 535.0 && mod(floor((p.x - 4.0) / 7.0 + 0.5), 2.0) < 0.5) v = max(v, 0.3); { float u = clamp((t - 2.650) / 0.4, 0.0, 1.0); if (u > 0.0 && u < 1.0) { float x = mix(304.0, 520.0, ease(u)); float k = sm(0.0, 0.1, u) * (1.0 - sm(0.85, 1.0, u)); v += blob(p, vec2(x, 238.500), 9.0, 1.2 * k) + blob(p, vec2(x - 14.0, 238.500), 6.0, 0.45 * k); } } { float u = clamp((t - 2.900) / 0.4, 0.0, 1.0); if (u > 0.0 && u < 1.0) { float x = mix(304.0, 520.0, ease(u)); float k = sm(0.0, 0.1, u) * (1.0 - sm(0.85, 1.0, u)); v += blob(p, vec2(x, 238.500), 9.0, 1.2 * k) + blob(p, vec2(x - 14.0, 238.500), 6.0, 0.45 * k); } } { float u = clamp((t - 3.150) / 0.4, 0.0, 1.0); if (u > 0.0 && u < 1.0) { float x = mix(304.0, 520.0, ease(u)); float k = sm(0.0, 0.1, u) * (1.0 - sm(0.85, 1.0, u)); v += blob(p, vec2(x, 238.500), 9.0, 1.2 * k) + blob(p, vec2(x - 14.0, 238.500), 6.0, 0.45 * k); } } // the answer page: faint paper; six lines, each written left to right by a hot head; a citation mark after each part { vec2 q = p - vec2(556.0, 177.0); if (q.x >= 0.0 && q.x < 105.0 && q.y >= 0.0 && q.y < 122.2) { float lc = floor(q.x / 7.0); float lk = floor(q.y / 9.4); float a = 0.15 + 0.04 * sin(ph + hash(vec2(lc, lk)) * 40.0); float clear = 1.0 - sm(0.3, 0.6, t); for (int j = 0; j < 6; j++) { float fj = float(j); float w = fj < 0.5 ? 3.050 : fj < 1.5 ? 3.210 : fj < 2.5 ? 3.370 : fj < 3.5 ? 3.530 : fj < 4.5 ? 3.690 : 3.850; float len = fj < 0.5 ? 13.0 : fj < 1.5 ? 11.0 : fj < 2.5 ? 12.0 : fj < 3.5 ? 9.0 : fj < 4.5 ? 12.0 : 7.0; if (abs(lk - 1.0 - 2.0 * fj) < 0.5) { float hc = min(len, 1.0 + floor(len * clamp((t - w) / 0.160, 0.0, 1.0))); float on = t < 1.0 ? clear : step(w, t) * step(lc, hc); if (lc >= 1.0 && lc < 1.0 + len) a = max(a, 0.72 * on); if (t > w && t < w + 0.160 && abs(lc - hc) < 0.5) a = max(a, 1.2); // the citation mark, two cells after a part's last line if (mod(fj, 2.0) > 0.5 && abs(lc - len - 2.0) < 0.5) a = max(a, 1.15 * (t < 1.0 ? clear : sm(w + 0.160, w + 0.220, t))); } } v = max(v, a); } } return v; }

Every change is a version

Each write to the live wiki, from a dream you applied or from you, is recorded. See the history of a page and restore an earlier version.

Cache layer vec3 drop(float t) { vec2 A = vec2(108.0, 250.0), P = vec2(262.0, 250.0), C = vec2(300.0, 250.0), O = vec2(344.0, 252.0), R = vec2(470.0, 262.0), B = vec2(560.0, 316.0); if (t < 0.15) return vec3(A, 0.0); if (t < 0.5) return vec3(mix(A, P, ease((t - 0.15) / 0.35)), 1.2 * sm(0.15, 0.22, t)); if (t < 0.95) return vec3(mix(P, A, ease((t - 0.55) / 0.4)), 1.2 * (1.0 - sm(0.85, 0.95, t)) * step(0.55, t) + 1.2 * step(t, 0.55)); if (t < 1.3) return vec3(A, 0.0); if (t < 1.65) return vec3(mix(A, P, ease((t - 1.3) / 0.35)), 1.2 * sm(1.3, 1.37, t)); if (t < 1.8) return vec3(mix(P, O, (t - 1.65) / 0.15), 1.2); if (t < 2.4) return vec3(mix(O, R, ease((t - 1.8) / 0.6)), 1.2); if (t < 2.9) return vec3(mix(R, B, 1.0 - pow(1.0 - (t - 2.4) / 0.5, 2.0)), 1.2); if (t < 3.35) return vec3(mix(B, C, ease((t - 2.95) / 0.4)), 1.2 * step(2.95, t)); if (t < 3.85) return vec3(mix(C, A, ease((t - 3.4) / 0.45)), 1.2 * (1.0 - sm(3.75, 3.85, t))); return vec3(A, 0.0); } float ring(vec2 p, vec2 c, float t, float t0, float dur, float rmax, float k) { if (t < t0 || t > t0 + dur) return 0.0; float u = (t - t0) / dur; return k * (1.0 - u) * exp(-pow(length((p - c) * vec2(1.0, 1.6)) - 8.0 - rmax * u, 2.0) / 30.0); } float field(vec2 p, float t) { vec2 C = vec2(300.0, 250.0); float v = blob(p, vec2(86.0, 250.0), 22.0, 0.95); // the pool: small and hot, its surface rippling where things land float kept = sm(3.3, 3.45, t) * (1.0 - sm(3.6, 4.1, t)); v += blob(p, C, 44.0, 0.78 + 0.45 * kept); v += ring(p, C, t, 0.5, 0.6, 52.0, 0.55) + ring(p, C, t, 1.65, 0.45, 40.0, 0.35) + ring(p, C, t, 3.35, 0.55, 52.0, 0.5); // the reservoir: wide and deep, denser the deeper it goes, stirring when something sinks in vec2 d = (p - vec2(565.0, 272.0)) / vec2(108.0, 118.0); float body = exp(-pow(max(0.0, length(d) - 0.82), 2.0) / 0.004) * step(length(d), 1.0); float stir = sm(2.4, 2.6, t) * (1.0 - sm(2.9, 3.4, t)); v += body * (0.3 + 0.45 * clamp((p.y - 154.0) / 236.0, 0.0, 1.0) + 0.3 * stir * noise(p * 0.05 + t)); v += ring(p, vec2(470.0, 262.0), t, 2.4, 0.7, 70.0, 0.4); vec3 q = drop(t); return v + blob(p, q.xy, 11.0, q.z); }

0dream changes applied without you running apply

Dreams use your own key

Anthropic by default, or any OpenAI-compatible endpoint, including a local router. Each run is estimated first, asks to proceed, and stops at the token budget you set.

Rate limiting float rate(float x) { float P = 420.0; float b = 0.0; for (int j = -1; j <= 3; j++) b += exp(-pow(x - 420.0 - float(j) * P, 2.0) / 2116.0); return 70.0 + 36.0 * sin(TAU * 3.0 * x / P + 0.3) + 24.0 * sin(TAU * 7.0 * x / P + 1.0) + 190.0 * b; } float field(vec2 p, float t) { float X0 = 60.0, X1 = 655.0, FLOOR = 400.0, LIM = 220.0; if (p.x < X0 || p.x > X1 || p.y > FLOOR) return 0.0; float xx = p.x + 420.0 * t / T; float top = FLOOR - rate(xx); if (p.y < top) return 0.0; if (p.y >= LIM) { float flare = top < LIM ? 0.45 * exp(-pow(p.y - LIM, 2.0) / 50.0) : 0.0; return 0.42 + 0.5 * (p.y - max(top, LIM)) / (FLOOR - max(top, LIM)) + flare; } // above the limit: sheared off, breaking into spray that rises and fades. It moves 60 columns and 30 cells a cycle, // and its pattern repeats over exactly those, so the loop closes. float rise = (LIM - p.y) / (LIM - top + 1.0); float spray = step(0.6, hash(vec2(mod(floor(xx / 7.0), 60.0), mod(floor((p.y + 9.4 * 30.0 * t / T) / 9.4), 30.0)))); return spray * (0.55 - 0.42 * rise); }

Secrets are scrubbed before storage

Known patterns such as API keys and tokens are replaced in transcripts, notes and anything a dream writes, before they reach disk.

sk-ant-…AKIA…Bearer …

Replaced with [REDACTED] markers. This catches known patterns, not everything.

Review, diff, apply or reject

A dream only writes a proposal. Read the diff against your live wiki, then apply or reject it. A page changed since the dream started is skipped, not overwritten.

Failover Requests flow through a router to one of two regions of server racks. The primary region loses power, the next request is turned back at its edge, and the router marks it down and sends that request and every one after it to the standby, which takes over; a strip of served requests shows no empty slot. vec2 atA(float u) { float x = mix(237.0, 420.0, u); return vec2(x, 219.7000 + -106.9000 * smoothstep(251.0, 370.0, x)); } vec2 atB(float u) { float x = mix(237.0, 391.5000, u); return vec2(x, 219.7000 + 109.3000 * smoothstep(251.0, 341.5000, x)); } // a rack of 6 columns by 9 cells: rails, caps, four units with a status light each. act is 0 dark, 0.5 standing by, // 1 serving; hit flashes its lights. float rack(float col, float cell, float c0, float k0, float act, float hit, float ph, float s) { float lc = col - c0; float lr = cell - k0; if (lc < 0.0 || lc > 5.0 || lr < 0.0 || lr > 8.0) return 0.0; float frame = 0.2 + 0.26 * min(act * 2.0, 1.0); if (lc < 0.5 || lc > 4.5 || lr < 0.5 || lr > 7.5) return frame; if (mod(lr, 2.0) < 0.5) return 0.0; if (lc > 3.5) return mix(act, 1.1 + 0.4 * hit, sm(0.6, 0.9, act)); return act * (0.68 + 0.08 * sin(2.0 * ph + hash(vec2(lr + s, lc)) * 30.0)); } // a flash of width w round time s, on the cycle: the nearer of s and its copy a cycle away float at(float t, float s, float w) { float d = t - s; d -= T * floor(d / T + 0.5); return exp(-d * d / w); } // a line's band, at least a column wide and a cell tall float band(float d, float k) { return k * (1.0 - sm(3.6, 4.8, abs(d))); } float field(vec2 p, float t) { if (p.x < 24.0 || p.x > 696.0 || p.y < 24.0 || p.y > 436.0) return 0.0; float ph = TAU * t / T; float col = floor((p.x - 4.0) / 7.0 + 0.5); float cell = floor(p.y / 9.4); float v = 0.0; // the regions' states: the primary boots, stands by, serves, loses power; the standby serves, stands down, takes over float pa = t < 1.0 ? sm(0.3, 0.8, t) : 1.0 - sm(2.8500, 3.1000, t); float actA = t < 1.0 ? 0.5 * sm(0.3, 0.8, t) : (0.5 + 0.5 * sm(1.6, 1.8, t)) * (1.0 - sm(2.8500, 3.1000, t)); float actB = t < 2.6 ? 1.0 - 0.5 * sm(1.95, 2.2, t) : 0.5 + 0.5 * sm(3.3100, 3.5300, t); float flash = exp(-pow(t - 2.8800, 2.0) / 0.0012); float toA = sm(1.6, 1.7, t) * (1.0 - sm(3.3100, 3.4100, t)); float hitA = 0.0; float hitB = 0.0; hitB += at(t, 0.5300, 0.006); hitB += at(t, 1.0925, 0.006); hitB += at(t, 1.6550, 0.006); hitA += at(t, 2.2175, 0.006); hitA += at(t, 2.7800, 0.006); hitB += at(t, 3.7600, 0.006); hitB += at(t, 3.9050, 0.006); hitB += at(t, 4.4675, 0.006); // the floors: faint ground; the primary's goes out with its power float hs = hash(floor(p / vec2(7.0, 9.4)) + 5.0); if (col >= 61.0 && col <= 89.0) { if (cell >= 5.0 && cell <= 18.0) v = max(v, step(0.62, hs) * (0.16 + 0.04 * sin(ph + hs * 40.0)) * pa + 0.6 * flash * step(0.62, hs)); if (cell >= 28.0 && cell <= 41.0) v = max(v, step(0.62, hs) * (0.16 + 0.04 * sin(ph + hs * 40.0))); } v = max(v, rack(col, cell, 64.0, 8.0, actA, hitA, ph, 0.0) + 0.7 * flash * step(0.1, rack(col, cell, 64.0, 8.0, 1.0, 0.0, ph, 0.0))); v = max(v, rack(col, cell, 64.0, 31.0, actB, hitB, ph, 3.0)); v = max(v, rack(col, cell, 73.0, 8.0, actA, 0.0, ph, 1.0) + 0.7 * flash * step(0.1, rack(col, cell, 73.0, 8.0, 1.0, 0.0, ph, 0.0))); v = max(v, rack(col, cell, 73.0, 31.0, actB, 0.0, ph, 4.0)); v = max(v, rack(col, cell, 82.0, 8.0, actA, 0.0, ph, 2.0) + 0.7 * flash * step(0.1, rack(col, cell, 82.0, 8.0, 1.0, 0.0, ph, 0.0))); v = max(v, rack(col, cell, 82.0, 31.0, actB, 0.0, ph, 5.0)); // the replication line: dotted, from the primary's floor to the standby's box; copies run from the serving region if (abs(p.x - 501.0) < 1.0 && p.y > 184.6000 && p.y < 241.2000) v = max(v, mod(cell, 2.0) < 0.5 ? 0.32 : 0.0); { float u = clamp((t - 0.8000) / 0.25, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, vec2(501.0, mix(229.2000, 190.6000, ease(u))), 7.0, 1.0 * sm(0.0, 0.15, u) * (1.0 - sm(0.85, 1.0, u))); } { float u = clamp((t - 1.0500) / 0.25, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, vec2(501.0, mix(229.2000, 190.6000, ease(u))), 7.0, 1.0 * sm(0.0, 0.15, u) * (1.0 - sm(0.85, 1.0, u))); } { float u = clamp((t - 1.3000) / 0.25, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, vec2(501.0, mix(229.2000, 190.6000, ease(u))), 7.0, 1.0 * sm(0.0, 0.15, u) * (1.0 - sm(0.85, 1.0, u))); } { float u = clamp((t - 2.0) / 0.25, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, vec2(501.0, mix(190.6000, 229.2000, ease(u))), 7.0, 1.0 * sm(0.0, 0.15, u) * (1.0 - sm(0.85, 1.0, u))); } { float u = clamp((t - 2.4000) / 0.25, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, vec2(501.0, mix(190.6000, 229.2000, ease(u))), 7.0, 1.0 * sm(0.0, 0.15, u) * (1.0 - sm(0.85, 1.0, u))); } // the users: a crowd of faint points that flicker, and the dotted way in to the router { vec2 d = (p - vec2(64.0, 219.7000)) / vec2(38.0, 100.0); if (length(d) < 1.0) v = max(v, step(0.6, hs) * (0.46 + 0.12 * sin(ph + hs * 50.0)) * (1.0 - 0.35 * length(d))); } if (abs(p.y - 219.7000) < 1.0 && p.x > 108.0 && p.x < 201.0) v = max(v, mod(col, 2.0) < 0.5 ? 0.3 : 0.0); // the router: a dense core in a ring, and its two branches; the one it sends down is lit v += blob(p, vec2(221.0, 219.7000), 13.0, 1.1 + 0.4 * exp(-pow(t - 3.3100, 2.0) / 0.003)); v += 0.5 * exp(-pow(length(p - vec2(221.0, 219.7000)) - 22.0, 2.0) / 16.0); if (p.x > 237.0 && p.x < 420.0) v = max(v, band(p.y - atA(clamp((p.x - 237.0) / 183.0, 0.0, 1.0)).y, 0.26 + 0.32 * toA)); if (p.x > 237.0 && p.x < 391.5000) v = max(v, band(p.y - atB(clamp((p.x - 237.0) / 154.5000, 0.0, 1.0)).y, 0.26 + 0.32 * (1.0 - toA))); // the requests: in from the crowd (one crosses the seam), then down a branch; the one sent to the dead primary is // turned back at its edge, and resent to the standby for (int j = 0; j < 8; j++) for (int w = 0; w <= 1; w++) { float a = 0.0800 + 0.5625 * float(j) + float(w) * T; float u = clamp((t - a + 0.35) / 0.35, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, vec2(mix(108.0, 207.0, u), 219.7000), 8.0, 1.15 * sm(0.0, 0.1, u)); } { float u = clamp((t - 0.0800) / 0.4000, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, atB(ease(u)), 8.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); } { float u = clamp((t - 0.6425) / 0.4000, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, atB(ease(u)), 8.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); } { float u = clamp((t - 1.2050) / 0.4000, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, atB(ease(u)), 8.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); } { float u = clamp((t - 1.7675) / 0.4000, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, atA(ease(u)), 8.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); } { float u = clamp((t - 2.3300) / 0.4000, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, atA(ease(u)), 8.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); } { float u = clamp((t - 2.8925) / 0.4000, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, atA(ease(u)), 8.0, 1.15); float b = clamp((t - 3.2925) / 0.18, 0.0, 1.0); if (b > 0.0 && b < 1.0) v += blob(p, atA(1.0) - vec2(22.0 * (1.0 - pow(1.0 - b, 3.0)), 0.0), 8.0, 1.15 * (1.0 - b)); float r = clamp((t - 3.3100) / 0.4000, 0.0, 1.0); if (r > 0.0 && r < 1.0) v += blob(p, atB(ease(r)), 8.0, 1.15 * (1.0 - sm(0.9, 1.0, r))); } { float u = clamp((t - 3.4550) / 0.4000, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, atB(ease(u)), 8.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); } { float u = clamp((t - 4.0175) / 0.4000, 0.0, 1.0); if (u > 0.0 && u < 1.0) v += blob(p, atB(ease(u)), 8.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); } // the strip: a slot opens at the right as each request reaches the router, and fills, in its region's row, once // that request is served float n = 0.0; n += ease((t - 0.0800) / 0.3); n += ease((t - 0.6425) / 0.3); n += ease((t - 1.2050) / 0.3); n += ease((t - 1.7675) / 0.3); n += ease((t - 2.3300) / 0.3); n += ease((t - 2.8925) / 0.3); n += ease((t - 3.4550) / 0.3); n += ease((t - 4.0175) / 0.3); float k = cell - 5.0; if (p.x > 100.0 && p.x < 350.0 && k > -0.5 && k < 2.5 && mod(k, 2.0) < 0.5) { float q = floor(n - 1.0 - (330.0 - p.x) / 14.0 + 0.5); float cx = 330.0 - 14.0 * (n - 1.0 - q); if (abs(p.x - cx) < 3.5 && cx > 106.0 && cx < 344.0) { float j = mod(q, 8.0); float inA = (abs(j - 3.0) < 0.5 || abs(j - 4.0) < 0.5) ? 1.0 : 0.0; float s = j < 0.5 ? 0.4800 : j < 1.5 ? 1.0425 : j < 2.5 ? 1.6050 : j < 3.5 ? 2.1675 : j < 4.5 ? 2.7300 : j < 5.5 ? 3.7100 : j < 6.5 ? 3.8550 : 4.4175; float done = q < -0.5 ? 1.0 : sm(s, s + 0.06, t); float wt = clamp((cx - 106.0) / 14.0, 0.0, 1.0) * clamp((344.0 - cx) / 14.0, 0.0, 1.0); float mine = k < 0.5 ? inA : 1.0 - inA; v = max(v, wt * (0.18 + (mine * 0.92) * done)); } } return v; }
  • 0

    Accounts, servers or databases to run

  • 4

    Memory layers: transcripts, notes, wiki, context

  • 7

    MCP tools, plus a context resource

  • 16

    CLI commands, from init to restore

Free, and your data stays yours

Archivum is open source software that runs on your machine. There is nothing to sign up for. The only thing that costs money is the model you choose to dream with, and you pay that provider directly.

Who pays for what

The CLI and MCP server MIT licence, runs on your machine
Free
Dreaming your key, your budget
Your provider
Accounts, servers, telemetry there are none
None
Usage billing Usage drops fall into a graduated basin and raise its level a step at a time as the month's days pass; at the month's end the basin drains through a narrow outlet into a meter that prints the invoice slip line by line, and the total shows once it is out. float xl(float y) { return 90.0 + 45.0 * (y - 80.0) / 215.0; } float xr(float y) { return 310.0 - 45.0 * (y - 80.0) / 215.0; } // the slip's print at its own row r (0 at the top) and column j (0 to 17) float slip(float r, float j) { // pale paper, so the print reads: a header, four line items a row apart, a rule, the total, a torn foot float P = 0.12; float v = P; if (j < 0.5 || j > 16.5 || r < 0.5) v = 0.6; // its edges else if (r < 1.5) v = j > 1.5 && j < 9.5 ? 1.15 : (j > 11.5 && j < 15.5 ? 0.6 : P); // header, date else if (abs(r - 4.0) < 0.5 || abs(r - 16.0) < 0.5) v = mod(j, 2.0) < 0.5 ? 0.6 : P; // rules else if (r > 5.5 && r < 14.5 && mod(r - 6.0, 3.0) < 0.5) { // four line items: what was used, and its amount float len = floor(hash(vec2(r, 3.0)) * 4.0); v = j > 1.5 && j < 5.5 + len ? 0.95 : (j > 11.5 && j < 15.5 ? 1.05 : P); } else if (abs(r - 19.0) < 0.5) v = j > 1.5 && j < 6.5 ? 1.15 : (j > 9.5 && j < 15.5 ? 1.2 : P); // the total else if (abs(r - 23.0) < 0.5) v = mod(j, 2.0) < 0.5 ? 0.6 : 0.0; // torn foot return v; } float field(vec2 p, float t) { if (p.x < 18.0 || p.x > 702.0 || p.y < 18.0 || p.y > 442.0) return 0.0; float ph = TAU * t / T; float k = floor(p.y / 9.4); float c = floor((p.x - 4.0) / 7.0 + 0.5); float v = 0.0; // the level, in graduations: up one per landed drop, down as it drains float lvl = ease((t - 0.47) / 0.15) + ease((t - 0.77) / 0.15) + ease((t - 1.07) / 0.15) + ease((t - 1.37) / 0.15) + ease((t - 1.67) / 0.15) + ease((t - 1.97) / 0.15); float dr = ease((t - 2.3) / 1.1); lvl -= 6.0 * dr; float surf = 31.0 - 3.0 * lvl; // the surface row float draining = step(2.3, t) * step(t, 3.4); float open = sm(2.25, 2.32, t) * (1.0 - sm(3.5, 3.6, t)); // the feed rail: a churning manifold over a dense bar, six nozzles under it if (p.x > 126.0 && p.x < 262.0) { if (k == 3.0) v += 0.3 + 0.3 * noise(vec2(p.x * 0.09 + 2.0 * cos(ph), 2.0 * sin(ph))); if (k == 4.0) v += 1.05; if (k == 5.0 && mod(c - 19.0, 3.0) < 0.5) v += 0.6; } // the basin: slanted walls with a lip, a floor with the outlet in it float L = xl(p.y), R = xr(p.y); if (k >= 8.0 && k <= 30.0) { if ((p.x >= L - 8.0 && p.x < L) || (p.x > R && p.x <= R + 8.0)) v += 1.05; else if ((p.x >= L - 16.0 && p.x < L) || (p.x > R && p.x <= R + 16.0)) v += 0.7; if (k == 8.0 && ((p.x >= L - 30.0 && p.x < L - 16.0) || (p.x > R + 16.0 && p.x <= R + 30.0))) v += 1.0; } if ((k == 31.0 || k == 32.0) && p.x >= L - 16.0 && p.x <= R + 16.0) { if (c == 28.0) v += 1.05 * (1.0 - open) + 0.95 * open; else v += k == 31.0 ? 1.0 : 0.7; } // graduations: ticks out from the left wall, long every other one, and a dotted level line inside at each for (int i = 0; i <= 6; i++) { float gk = 31.0 - 3.0 * float(i); if (k == gk && i > 0) { bool major = mod(float(i), 2.0) < 0.5; if (p.x < L - 16.0 && p.x >= L - 16.0 - (major ? 21.0 : 10.0)) v += major ? 0.7 : 0.6; if (p.x > L + 3.0 && p.x < R - 3.0 && mod(c, 2.0) < 0.5) v += 0.18; } if (k == gk && i == 0 && p.x < L - 16.0 && p.x >= L - 37.0) v += 0.7; } // the contents: a dense surface, denser with depth, streaming toward the outlet while it drains if (k < 31.0 && p.x >= L && p.x <= R && k >= surf - 0.5) { float depth = clamp((k - surf) / 18.0, 0.0, 1.0); v += k < surf + 0.5 ? 1.05 : 0.42 + 0.3 * depth; float d = length((p - vec2(200.0, 294.9)) / vec2(1.0, 1.6)); v += draining * 0.35 * step(0.62, fract(d / 26.0 + 5.0 * t)) * sm(2.3, 2.4, t); } { // drop 1: forms at its nozzle, falls gathering speed, lands on the surface, ripples out float u = (t - 0.15) / 0.32; float g = (t - 0.03) / 0.12; if (g > 0.0 && u < 0.0) v += blob(p, vec2(179.0, 58.0), 8.0, 1.1 * g); if (u >= 0.0 && u < 1.0) v += blob(p, vec2(179.0, mix(58.0, 285.4, u * u)), 9.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); float w = (t - 0.47) / 0.4; if (w > 0.0 && w < 1.0) { float sk = 27.0; if (k == sk && p.x > L && p.x < R) v += 0.7 * (1.0 - w) * exp(-pow(abs(p.x - 179.0) - 120.0 * w, 2.0) / 120.0); if (k == sk - 1.0 && w < 0.4 && abs(abs(p.x - 179.0) - 9.0) < 4.0) v += 0.7; } } { // drop 2: forms at its nozzle, falls gathering speed, lands on the surface, ripples out float u = (t - 0.45) / 0.32; float g = (t - 0.33) / 0.12; if (g > 0.0 && u < 0.0) v += blob(p, vec2(221.0, 58.0), 8.0, 1.1 * g); if (u >= 0.0 && u < 1.0) v += blob(p, vec2(221.0, mix(58.0, 257.2, u * u)), 9.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); float w = (t - 0.77) / 0.4; if (w > 0.0 && w < 1.0) { float sk = 24.0; if (k == sk && p.x > L && p.x < R) v += 0.7 * (1.0 - w) * exp(-pow(abs(p.x - 221.0) - 120.0 * w, 2.0) / 120.0); if (k == sk - 1.0 && w < 0.4 && abs(abs(p.x - 221.0) - 9.0) < 4.0) v += 0.7; } } { // drop 3: forms at its nozzle, falls gathering speed, lands on the surface, ripples out float u = (t - 0.75) / 0.32; float g = (t - 0.63) / 0.12; if (g > 0.0 && u < 0.0) v += blob(p, vec2(137.0, 58.0), 8.0, 1.1 * g); if (u >= 0.0 && u < 1.0) v += blob(p, vec2(137.0, mix(58.0, 229.0, u * u)), 9.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); float w = (t - 1.07) / 0.4; if (w > 0.0 && w < 1.0) { float sk = 21.0; if (k == sk && p.x > L && p.x < R) v += 0.7 * (1.0 - w) * exp(-pow(abs(p.x - 137.0) - 120.0 * w, 2.0) / 120.0); if (k == sk - 1.0 && w < 0.4 && abs(abs(p.x - 137.0) - 9.0) < 4.0) v += 0.7; } } { // drop 4: forms at its nozzle, falls gathering speed, lands on the surface, ripples out float u = (t - 1.05) / 0.32; float g = (t - 0.93) / 0.12; if (g > 0.0 && u < 0.0) v += blob(p, vec2(200.0, 58.0), 8.0, 1.1 * g); if (u >= 0.0 && u < 1.0) v += blob(p, vec2(200.0, mix(58.0, 200.8, u * u)), 9.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); float w = (t - 1.37) / 0.4; if (w > 0.0 && w < 1.0) { float sk = 18.0; if (k == sk && p.x > L && p.x < R) v += 0.7 * (1.0 - w) * exp(-pow(abs(p.x - 200.0) - 120.0 * w, 2.0) / 120.0); if (k == sk - 1.0 && w < 0.4 && abs(abs(p.x - 200.0) - 9.0) < 4.0) v += 0.7; } } { // drop 5: forms at its nozzle, falls gathering speed, lands on the surface, ripples out float u = (t - 1.35) / 0.32; float g = (t - 1.23) / 0.12; if (g > 0.0 && u < 0.0) v += blob(p, vec2(242.0, 58.0), 8.0, 1.1 * g); if (u >= 0.0 && u < 1.0) v += blob(p, vec2(242.0, mix(58.0, 172.6, u * u)), 9.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); float w = (t - 1.67) / 0.4; if (w > 0.0 && w < 1.0) { float sk = 15.0; if (k == sk && p.x > L && p.x < R) v += 0.7 * (1.0 - w) * exp(-pow(abs(p.x - 242.0) - 120.0 * w, 2.0) / 120.0); if (k == sk - 1.0 && w < 0.4 && abs(abs(p.x - 242.0) - 9.0) < 4.0) v += 0.7; } } { // drop 6: forms at its nozzle, falls gathering speed, lands on the surface, ripples out float u = (t - 1.65) / 0.32; float g = (t - 1.53) / 0.12; if (g > 0.0 && u < 0.0) v += blob(p, vec2(158.0, 58.0), 8.0, 1.1 * g); if (u >= 0.0 && u < 1.0) v += blob(p, vec2(158.0, mix(58.0, 144.4, u * u)), 9.0, 1.15 * (1.0 - sm(0.9, 1.0, u))); float w = (t - 1.97) / 0.4; if (w > 0.0 && w < 1.0) { float sk = 12.0; if (k == sk && p.x > L && p.x < R) v += 0.7 * (1.0 - w) * exp(-pow(abs(p.x - 158.0) - 120.0 * w, 2.0) / 120.0); if (k == sk - 1.0 && w < 0.4 && abs(abs(p.x - 158.0) - 9.0) < 4.0) v += 0.7; } } // the outlet and the pipe: narrow walls, and the flow along them once the outlet is open float wl = 0.42; if (c == 27.0 && k > 32.0 && k <= 41.0) v += wl; if (c == 29.0 && k > 32.0 && k <= 39.0) v += wl; if (k == 39.0 && c > 29.0 && c < 62.0) v += wl; if (k == 41.0 && c >= 27.0 && c < 62.0) v += wl; float sd = -1.0; // how far along the flow's path this cell is if (c == 28.0 && k > 32.0 && k <= 40.0) sd = (k - 32.0) * 9.4; if (k == 40.0 && c > 28.0 && c < 62.0) sd = 75.2 + (c - 28.0) * 7.0; if (sd >= 0.0) { float at = sd / 1566.0; float on = step(2.3 + at, t) * step(t, 3.4 + at); v += on * (fract((sd - 900.0 * t) / 28.0) < 0.55 ? 1.05 : 0.55); } // the meter: a slot on top, a body with vents, its intake and a light that is on while it prints float printing = step(2.5, t) * step(t, 3.6); if (c >= 62.0 && c <= 88.0 && k >= 38.0 && k <= 42.0) { if (k == 38.0) v += c >= 66.0 && c <= 85.0 ? 0.2 : 1.0; else if (k == 39.0 || k == 42.0) v += 0.7; else if (c >= 86.0 && c < 88.0) v += 0.42 + 0.68 * printing; else if (c == 62.0) v += 0.42 + 0.63 * step(2.5, t) * step(t, 3.6); else v += mod(c, 2.0) < 0.5 ? 0.7 : 0.3; } // the slip: last month's lifts off and fades at the month's end; the new one comes out of the slot a row at a time if (c >= 67.0 && c <= 84.0 && k < 38.0) { float top = 13.0, a = 1.0, rows = 25.0; if (t >= 2.12 && t < 2.5) { float u = clamp((t - 2.12) / 0.28, 0.0, 1.0); top -= floor(2.0 * ease(u) + 0.5); a = 1.0 - ease(u); } else if (t >= 2.5) { rows = floor(25.0 * ease((t - 2.5) / 1.1) + 0.001); top = 38.0 - rows; } float r = k - top; if (r >= 0.0 && r < rows) v += a * slip(r, c - 67.0); } // the month: 30 days, each lit as it passes; day 30 the month's end; cleared left to right for the new month if (k == 45.0 && c >= 14.0 && c < 44.0) { float d = c - 13.0; float lit = step(0.15 + 0.065 * d, t) * (1.0 - step(3.62 + 0.006 * d, t)); v += 0.2 + lit * (d > 29.5 ? 0.9 : 0.55); } return v; }
  • Local Notes, transcripts, wiki and history are files under ~/.archivum
  • Dreaming Sends transcripts, notes and wiki pages to the LLM you configure, and nowhere else
  • Delete Remove the folder, or run archivum retention to drop old dreamed transcripts

Questions

Not answered here? Read the docs or write to us.

What is a dream?

A pass that reads your pending transcripts and notes alongside the current wiki, one analyzer per input and then one organizer. It checks what the wiki claims against what happened, corrects or marks stale facts, merges duplicates and links related pages. The result is a proposal you can diff, then apply or reject.

Who pays for dreaming?

You do, through your own LLM key: Anthropic by default, or any OpenAI-compatible endpoint with tool calling, including a local router. archivum dream prints a rough estimate, asks before it proceeds and refuses runs estimated above your token budget. Without a key, dreaming does not run and nothing is sent anywhere.

Can a dream make memory worse?

Yes. It is an AI model working from your transcripts, so quality depends on the model you use and it can get things wrong. That is why changes are reviewed before they are applied, every applied write is a version you can restore, and transcripts are treated as untrusted data: dream agents only get document tools, with no shell and no network.

Where is my data stored?

In ~/.archivum (or $ARCHIVUM_HOME), as markdown and JSON files, with one folder per space. Nothing leaves your machine except the calls to the LLM you configure when you dream. Dreamed transcripts are removed after 30 days by default.

How does search work?

BM25 keyword ranking over notes and wiki pages, plus the wiki’s index and links that the agent can follow. There are no embeddings and no vector database, so a query that shares no words with a memory will not find it.

Which clients work?

The Claude Code hooks are built against its documented hook format. The MCP server speaks standard MCP over stdio, but it has not been tested in every MCP client. Agents without hooks can send transcripts with archivum log.

What does the simulation do?

The simulation plays out memory and dreaming in your browser. Recorded mode uses a scripted example and runs no model. Live mode sends your chat text through this site’s server to a model, under usage caps paid by the site owner, and may be switched off.

How good is the evidence that dreaming helps?

Limited. The eval/ folder has a small synthetic check, not a benchmark, and it does not prove dreaming improves memory for your agent. Read the diffs before you apply them.