Doto's home (den) new in 0.6.0

The den icon: a glowing house outline with Doto, a small violet dot, at home inside it.

See what Doto learned about you, and edit or forget any of it by pointing. den is the second default unique, next to graph: one calm page that shows everything the companion keeps about you.

What it is

Doto learns you through three honest channels: a profile of how you work, memory lines Claude writes down, and the special skills he writes for himself (see How Doto learns). All of it lives on disk in ~/.lets/entity/, and /lets entity lists it in the chat.

den puts it on one page: his persona and traits, the likes, dislikes and nicknames he picked up, his memory lines, his special skills, the secret moves you found, and your interaction profile. Every row can be changed or forgotten right where it sits; Claude applies the change and Doto says one short line about it. Nothing is inferred silently in lets, and den is where you can check that for yourself.

Install

It comes with lets 0.6.0: den is listed next to graph in the engine's defaults.json, so the engine installs it into ~/.lets/uniques/den the first time it runs. Then:

/lets den               # or /lets:lets den without the alias

Manual: /lets store den (inspect → confirm), or clone the repo into ~/.lets/uniques/den. den needs engine 0.6.0 or later ("engine": ">=0.6.0") because it reads the companion's state from the new GET /lip/entity. It asks for no permissions: "fs.write": [], "net": [].

What you can do

A toolbar sits on top: the listening dot, Refresh, Ask, Send to Claude, chat, Doto and ? for shortcuts. Below it, nine cards, top to bottom:

CardYou seeYou can do
personaHis name and tagline, "born … · n sessions · mood …", six colour swatchesClick the name or tagline to edit it in place (name 1 to 24 characters, tagline up to 120). Pick a colour, or default. Reset to defaults asks "Sure?" first, then puts name, tagline, colour and traits back; everything he learned stays.
traitsSix sliders with their live value and a two-word meaning at each end: speed calm ↔ snappy, distance close ↔ far, chatty quiet ↔ talks, playful still ↔ bouncy, formality casual ↔ formal, curiosity assumes ↔ asksDrag a slider. The value goes to Claude once the slider settles.
likes, dislikesChips× forgets one. Type in the add field and press Enter to add one (up to 60 characters).
nicknamesRows: what you call a thing → what it is× forgets one; the add row has two fields.
memoryUp to 60 lines, newest first: a kind badge (note, preference, habit, request, style, secret, skill), how long ago (hover for the exact time) and the text× forgets a line. Filter chips show one kind at a time.
skillsEach special skill: title, name, trigger chips, a confidence bar, uses and the unique it came from; the playbook folds openThumbs up or thumbs down rates it; × forgets it.
secretsSeven dots and a count ("2 of 7"): found moves are lit and named, the rest show "?"Nothing to edit, and no hints. Finding them is the point.
profileHow you work: pace (px/s), clicks per minute, dwell, idle, summons · picks · dismisses, sessions seen, last seen, and a 3×3 grid of your favourite screen regionsRead only. A value not measured yet shows "—".

An empty card says so ("Nothing yet. Open graph and do something he can notice."). Ask, or a question from Doto's menu, asks Claude about the row you are hovering or have focused; the answer comes in the chat and Claude highlights the rows it cites. Collapsed cards and the memory filter are remembered in this browser; everything else lives on the host.

Mouse & keyboard

DoHow
Rename him, edit the taglineClick it; Enter saves, Esc cancels
Forget a chip, nickname, memory line or skillClick its ×, or focus the × and press Delete / Backspace
Add a like, dislike or nicknameType in the add field, then Enter
Undo your own editCtrl+Z (20 steps)
Shortcuts? opens the overlay; Esc closes it and any inline editor

Every row is reachable with the keyboard and has an accessible name. Under reduced motion, scrolling to a card is instant.

You edit, Claude confirms

  1. You change a row. den updates it at once and marks it waiting for Claude: dimmed, with a small pulsing dot.
  2. den sends the matching event (below).
  3. Claude applies it with the entity tool, refreshes den, and Doto says one short line. The mark clears.

If Claude is not listening right now, den tells you (at most every 30 seconds): "Doto is away. Claude applies this with your next message in Claude Code." The mark stays until Claude catches up; the prompt hook delivers your edits with your next message.

Ctrl+Z undoes your own edits by sending the opposite event: a forgotten like is added back, a slider returns to its previous value. Claude treats an undo as a "no" and applies it quietly.

Doto at home

This is his home. Click him for questions about the row under your pointer: What do you know about me?, Why this?, Forget this (den does that one itself) and Open chat. What do you know about me? gets a short, honest summary in the chat, counts included: "Four likes, two dislikes, two of seven secrets."

He reacts once per thing you do, at most: a wink after you forget something, a blush when you edit his name or tagline, a nod when Claude's refresh lands after your edit. Claude may also roll him over to a memory line and have him say why he wrote it.

Send to Claude & resume

Send to Claude hands den over for a memory review. The engine saves it as a session and closes the window, and Claude answers in the Claude Code chat, not in the window: in three to six lines it sums up what Doto knows about you (from the memory lines, likes and dislikes, skills and secrets), flags lines that look stale or contradict each other, and proposes a short cleanup list you can say yes to. The session gets a label such as "doto memory review".

Say continue (or /lets den resume) and den comes back live: it fetches the current state again instead of replaying the saved snapshot, so it shows what Doto knows now.

The manifest's session block names the roles:

"session": { "export": "den.export", "snapshot": "den.snapshot", "restore": "den.load" }

Where the data comes from

den reads everything from the engine with GET /lip/entity new in 0.6.0, token-protected like /lip/persona: once when it opens, and again on den.refresh. The payload, in short:

{ persona: { name, tagline, born, color, mood, sessions,
             traits: { speed, distance, chatty, playful, formality, curiosity },
             likes: [], dislikes: [], nicknames: { "what you call it": "what it is" } },
  profile: { pace, clickRate, dwell, idleMs, regions: { tl, tr, bl, br, c },
             sessionsSeen, summons, picks, dismisses, lastSeen,
             secrets: [ /* names found, in order of discovery */ ] },
  skills:  [{ name, title, triggers, confidence, uses, unique, learned, updated, body }],
  memory:  [{ ts, kind, text, weight }],   // oldest → newest, the last 60 lines
  secretsAll: [ /* all seven */ ],
  defaults: { name, tagline, color, traits },
  at }

memory[].ts is a memory line's id. pace is in px/s, clickRate in clicks per minute, dwell and idleMs in milliseconds. Any field may be null until something was measured. profile.secrets is new in 0.6.0: when you find a secret move, the engine records its name, once.

The entity tool

Claude applies your edits with the entity MCP tool. Version 0.6.0 adds five actions:

ActionDoes
full {}The same payload as GET /lip/entity.
set {name?, tagline?, traits?, color?}Sets the persona directly (not the moving average that learn uses), clamps traits to 0 to 1, and updates every open window.
unlearn {likes?, dislikes?, nicknames?, memory?}Removes exactly those: likes and dislikes by text, nicknames by key, memory lines by ts (from memory.jsonl). Reports what it removed.
memory {n?}The last n memory lines with their ts (default 30).
reset {}Name, tagline, colour and traits back to the defaults. Likes, dislikes, nicknames, memory and skills stay.

den's edits also use the existing actions: learn (adds likes, dislikes and nicknames), forget and reinforce (special skills), rename and brief.

Events (up)

Each event says what you meant, not what you clicked.

EventWhen
den.persona.set {name?, tagline?, traits?, color?}You renamed him, edited the tagline, let a trait slider settle (debounced 250 ms) or picked a colour.
den.persona.reset {}You pressed Reset to defaults and confirmed.
den.like.add {text} · den.like.forget {text}You added a like / pressed × on one.
den.dislike.add {text} · den.dislike.forget {text}The same for dislikes.
den.nickname.set {key, value} · den.nickname.forget {key}You added or edited a nickname / removed one.
den.memory.forget {ts, text}You pressed × on a memory line. text is an 80-character preview, so Claude can say what it forgot.
den.skill.forget {name}You removed a special skill.
den.skill.rate {name, ok}Thumbs up (ok: true) or thumbs down on a skill.
den.ask {question, about, entity}Ask, or a question from Doto's menu. about is the section and row you were looking at.
den.export {entity}You pressed Send to Claude: the handoff.
den.snapshot {entity}The reply to Claude's den.snapshot command.

Section names, as used in about, den.highlight and den.focus: persona, traits, likes, dislikes, nicknames, memory, skills, secrets, profile.

Commands (down)

Commands are idempotent and animate (300 ms); nothing teleports. Every command accepts ack: true.

CommandDoes
den.load {entity, merge?}Render this payload; merge: true patches only the sections present. The restore command on resume, and one way Claude confirms edits: rows that now match lose their pending mark.
den.refresh {}Fetch GET /lip/entity again and render; clears every pending mark.
den.highlight {section, id?, color?, ttl?}A glow on a whole card or one row (id = like or dislike text, nickname key, memory ts, skill name or secret name). ttl defaults to 4000 ms, 0 keeps it, section: "" clears.
den.focus {section}Scroll that card into view.
den.snapshot {}Reply with a den.snapshot event carrying the current payload.

A round trip, as the guide describes it: den.like.forget {text} → entity {action: "unlearn", likes: [text]} → den.refresh → Doto: "Forgot it. Three likes left."

// point at the memory line Claude is talking about
{"type":"den.highlight","data":{"section":"memory","id":1791573980436,"ttl":4000}}
{"type":"den.focus","data":{"section":"skills"}}
{"type":"den.refresh","data":{}}

Claude's etiquette

den's GUIDE.md is what Claude reads when it opens the page. Its promises, in short:

Repository layout

lets-unique-den/
  unique.json            # manifest: domain den, engine >=0.6.0, no permissions
  GUIDE.md               # what Claude reads on open
  CONTRACT.md            # the unique's behaviour spec
  README.md  CHANGELOG.md  LICENSE  icon.svg
  package.json           # npm run validate
  scripts/validate.mjs   # zero-dependency manifest check
  schemas/               # unique.schema.json, entity.json and message schemas
  .github/workflows/validate.yml
  ui/index.html  ui/den.js  ui/style.css

Plain HTML, ES modules and CSS: no build step, no CDN, no inline script. MIT licensed. More: lets-unique-den has the full GUIDE.md, the behaviour contract (CONTRACT.md) and JSON Schemas for the payload and messages.