ei-tui 1.6.8 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +4 -0
  2. package/package.json +2 -1
  3. package/skills/coding-harness-reflect/SKILL.md +230 -0
  4. package/skills/ei-curate/SKILL.md +174 -0
  5. package/skills/ei-curate/references/cli.md +160 -0
  6. package/skills/ei-curate/references/provenance.md +88 -0
  7. package/skills/ei-curate/references/recipes.md +144 -0
  8. package/skills/ei-curate/references/talking-to-the-user.md +71 -0
  9. package/skills/ei-persona/SKILL.md +238 -0
  10. package/skills/ei-persona/references/cli.md +265 -0
  11. package/skills/ei-persona/references/recipes.md +247 -0
  12. package/skills/ei-persona/references/talking-to-the-user.md +107 -0
  13. package/src/cli/README.md +72 -2
  14. package/src/cli/commands/personas.ts +46 -1
  15. package/src/cli/corrections-endpoints.ts +297 -0
  16. package/src/cli/corrections-writer.ts +138 -0
  17. package/src/cli/install.ts +252 -157
  18. package/src/cli/mcp.ts +80 -1
  19. package/src/cli/persona-corrections.ts +442 -0
  20. package/src/cli/retrieval.ts +46 -2
  21. package/src/cli.ts +148 -1
  22. package/src/core/corrections.ts +233 -0
  23. package/src/core/handlers/human-extraction.ts +8 -2
  24. package/src/core/handlers/human-matching.ts +2 -2
  25. package/src/core/llm-client.ts +7 -1
  26. package/src/core/orchestrators/human-extraction.ts +1 -0
  27. package/src/core/persona-tools.ts +92 -0
  28. package/src/core/personas/opencode-agent.ts +1 -3
  29. package/src/core/processor.ts +113 -1
  30. package/src/core/state/human.ts +10 -0
  31. package/src/core/state/personas.ts +8 -0
  32. package/src/core/state-manager.ts +11 -0
  33. package/src/core/types/entities.ts +1 -0
  34. package/src/core/utils/identifier-utils.ts +3 -2
  35. package/src/integrations/pi/importer.ts +142 -50
  36. package/src/integrations/pi/reader.ts +1 -0
  37. package/src/integrations/pi/types.ts +4 -0
  38. package/src/storage/file-lock.ts +120 -0
  39. package/tui/README.md +2 -0
  40. package/tui/src/commands/provider.tsx +1 -1
  41. package/tui/src/components/WelcomeOverlay.tsx +3 -3
  42. package/tui/src/context/ei.tsx +14 -0
  43. package/tui/src/index.tsx +13 -1
  44. package/tui/src/storage/file.ts +15 -83
  45. package/tui/src/util/instance-lock.ts +3 -2
  46. package/tui/src/util/provider-detection.ts +4 -2
  47. package/tui/src/util/yaml-persona.ts +7 -38
@@ -0,0 +1,88 @@
1
+ # reference: attributing a quote by its provenance
2
+
3
+ Fixing a bad merge comes down to one question, asked once per quote: **who really said or
4
+ meant this?** You answer it from the quote's stored provenance. Do **not** answer it from
5
+ the (possibly wrong) name on the record you're fixing.
6
+
7
+ Every quote carries three provenance fields:
8
+ - `speaker` — who uttered it (a person's name, `"human"`, or a persona/agent name)
9
+ - `channel` — the human-readable place it happened
10
+ - `message_id` — a machine pointer whose **prefix names the origin system**
11
+
12
+ ## Read the `message_id` prefix first
13
+
14
+ | Prefix | Origin | Externally re-checkable? |
15
+ |---|---|---|
16
+ | `slack:TEAM:CHANNEL:TS` | a Slack message | **Yes** — channel + speaker are strong signal; live lookup possible if you have a Slack tool |
17
+ | `opencode:…` `cursor:…` `codex:…` `pi:…` | a coding-assistant session | **No** — you cannot re-derive a canonical identity from a transcript |
18
+ | `ei:…` | Ei-internal (persona-authored) | n/a — not about an external person |
19
+
20
+ The prefix decides your method and your confidence ceiling.
21
+
22
+ ---
23
+
24
+ ## § Slack-sourced quotes
25
+
26
+ The stored `channel` and `speaker` usually settle attribution on their own:
27
+
28
+ - **`speaker`** tells you who talked. If the speaker *is* the person you're sorting (e.g. a
29
+ quote spoken by "Jeff Kirk"), that's a strong direct signal.
30
+ - **`channel`** tells you the context. A quote in `#akrochem-qa` about "Jeff's explanation"
31
+ is about the Jeff who lives in the Akrochem work — even if a *different* person said it.
32
+ - **The text** disambiguates topic vs. person: "candidate… Fortune 500 IT landscape" is a
33
+ hiring context; "AS400 resync… inventory event" is an integration context. Two different
34
+ people can share a first name and live in different channels — the channel + text is how
35
+ you tell them apart.
36
+
37
+ **Optional live verification (only if you have a Slack tool and are still unsure):** resolve
38
+ the `channel`/user ids to real names and, crucially, **email domains** — the domain reveals
39
+ the org (e.g. `@company.com` vs a vendor's `@vendor.com`), which is often the cleanest way to
40
+ separate two same-named people across organizations. If you don't have a Slack tool, that's
41
+ fine — the stored fields are usually enough. **Never block on a tool you don't have.**
42
+
43
+ If, after channel + speaker + text, a Slack quote is *still* genuinely ambiguous → it's a
44
+ question for the user, not a guess for you.
45
+
46
+ ---
47
+
48
+ ## § Code-session quotes (`opencode:` / `cursor:` / `codex:` / `pi:`)
49
+
50
+ **Provenance here is not externally verifiable.** The `message_id` points at a session
51
+ transcript, not at a person directory. The `speaker` is typically `"human"` or an
52
+ assistant/persona name, and the actual *person being discussed* is buried in the text. There
53
+ is no channel-to-org mapping and no way to "look up" who was meant.
54
+
55
+ So:
56
+ - Attribute **only** from the quote's own text and whatever the user tells you.
57
+ - If the text doesn't make the person unambiguous, **stop and ask the user** — describe the
58
+ quote and ask "does this belong to <A> or <B>?" Do not pick one to keep moving.
59
+ - This is the single most common place a confident model will fabricate. Don't.
60
+
61
+ ---
62
+
63
+ ## Turning attribution into a decision
64
+
65
+ For each quote on the record you're fixing:
66
+
67
+ 1. Note `message_id` prefix → method + confidence ceiling (table above).
68
+ 2. Gather `speaker` + `channel` + `text`.
69
+ 3. Assign it to a candidate person **only if the evidence is clear**.
70
+ 4. If not clear → add it to a short "need your call" list for the user.
71
+
72
+ You now have three buckets: **stays** (correctly attributed), **moves** (belongs to a
73
+ different/new person — re-point it), and **ask** (ambiguous). Take the "ask" bucket to the
74
+ user before writing anything.
75
+
76
+ ---
77
+
78
+ ## Why this also governs the `sources` field
79
+
80
+ A record's `sources` are the same provenance, aggregated. That's why:
81
+ - **Slack-origin `sources` self-heal** — as the user keeps talking in those channels, Ei
82
+ re-ingests and re-populates them. You don't need to hand-fix them.
83
+ - **Code-session `sources` cannot be reconstructed** — the ids are opaque and tied to
84
+ specific past sessions. You **cannot** hand-craft a valid one.
85
+
86
+ Therefore, when you create or clean a record, **leave `sources` empty rather than inventing
87
+ entries.** A missing source is normal and (for Slack) temporary; a fabricated one is a lie
88
+ in the data. See `references/cli.md` → "Don't fabricate sources."
@@ -0,0 +1,144 @@
1
+ # reference: curation recipes
2
+
3
+ Pick the recipe that matches the task. Every recipe assumes you have already **assessed**
4
+ the record(s) and **disambiguated** the quotes (`references/provenance.md`), and that you
5
+ will **confirm with the user** (`references/talking-to-the-user.md`) before writing and
6
+ **verify** after. All writes follow the full-record round-trip rule in `references/cli.md`.
7
+
8
+ Command mechanics live in `references/cli.md`; this file is the *sequence and judgment* for
9
+ each operation.
10
+
11
+ ---
12
+
13
+ ## Recipe A — Split one record into two (a bad merge)
14
+
15
+ **Symptom:** one record is really two people/topics wearing one name (its quotes and/or
16
+ description describe two different subjects). This is the most common and most delicate fix.
17
+
18
+ **Worked example (real):** a person record named "Jeff Kirk" had accreted a second person —
19
+ its description blended a hiring/sales R&P employee with an AS400 vendor engineer, and one of
20
+ its quotes (spoken in `#akrochem-qa`) actually belonged to the vendor. The record was *mostly*
21
+ Kirk, *contaminated* by "Jeff Nickles."
22
+
23
+ **Steps:**
24
+
25
+ 1. **Decide which identity the existing record keeps.** Keep the majority/most-established
26
+ identity on the existing id (preserves its history and links); extract the *minority*
27
+ identity into a **new** record. In the example: keep the record as Kirk, create Nickles.
28
+
29
+ 2. **Create the extracted person.** Supply real, *distinct* identifiers so the two can never
30
+ re-merge (see the prevention note below). Capture the returned id.
31
+ ```
32
+ ei create person --json '{ name, description, relationship, sentiment,
33
+ identifiers:[ {Full Name…}, {Slack:"handle"}, {Email:"…"} ] }'
34
+ → returns { id: "<new-id>", … } # CAPTURE <new-id>
35
+ ```
36
+
37
+ 3. **Re-point the "moves" quotes** (from your disambiguation buckets). For each quote that
38
+ belongs to the extracted identity: read it, set `data_item_ids` to the new id (preserving
39
+ any *other* ids already in the array — a quote can link multiple people/topics — and
40
+ de-duping), write it back.
41
+ ```
42
+ ei --id <quote-id> # read full record
43
+ # data_item_ids: replace the OLD person id with <new-id>, keep the rest
44
+ ei update quote <quote-id> --json '<full record with fixed data_item_ids>'
45
+ ```
46
+
47
+ 4. **Clean the original record.** Full-record `update` that:
48
+ - rewrites the `description` to describe **only** the identity that stays,
49
+ - **removes the identifier that caused the merge** and adds a distinct one,
50
+ - drops any `sources` that belong to the extracted identity (leave the rest; don't invent).
51
+
52
+ 5. **Verify.** Re-read both people and each moved quote. Confirm `linked_quotes` shifted from
53
+ the original to the new record, the descriptions/identifiers are clean, and nothing else
54
+ changed.
55
+
56
+ > **Prevention (do not skip):** bad merges usually come from a **too-generic identifier** —
57
+ > a bare first name like `Slack: "Jeff"`. If you leave it, Ei will re-merge them. Remove the
58
+ > generic identifier and give **each** record a *distinct* one (a real Slack handle, an
59
+ > email). This is the difference between fixing the symptom and fixing the cause.
60
+
61
+ ---
62
+
63
+ ## Recipe B — Merge two records into one (a duplicate)
64
+
65
+ **Symptom:** two records are the same person/topic (Ei failed to match them).
66
+
67
+ **Steps:**
68
+
69
+ 1. **Choose the survivor.** Keep the one with the richer history / more correct identifiers;
70
+ the other is the "loser."
71
+ 2. **Move every quote off the loser.** For each of the loser's `linked_quotes`: read it,
72
+ replace the loser id in `data_item_ids` with the survivor id (preserve/de-dupe others),
73
+ write it back.
74
+ 3. **Fold in detail.** Full-record `update` the survivor to absorb any correct identifiers
75
+ and description nuance the loser had (don't lose real information).
76
+ 4. **Remove the loser** (`ei remove …`) — only *after* its quotes are moved, or you'll orphan
77
+ them.
78
+ 5. **Verify** the survivor now carries all the quotes and correct identifiers; confirm the
79
+ loser is gone.
80
+
81
+ ---
82
+
83
+ ## Recipe C — Rename / relabel a person
84
+
85
+ **Symptom:** right person, wrong name or wrong/missing identifiers (but not a merge).
86
+
87
+ 1. `ei --id <id>` → read the full record.
88
+ 2. Fix `name` and/or `identifiers` (add the real handle/email; fix a misspelled name; set the
89
+ correct `is_primary`). Prefer **adding** a distinct identifier over leaving a generic one.
90
+ 3. `ei update person <id> --json '<full record>'`.
91
+ 4. Verify the read-back.
92
+
93
+ ---
94
+
95
+ ## Recipe D — Correct a field (fact value, topic/person description)
96
+
97
+ **Symptom:** the record is the right entity, but a value/description is wrong or stale.
98
+
99
+ 1. `ei --id <id>` → read.
100
+ 2. Change only the offending field (`description`, a fact's `description`/value, a topic's
101
+ `description`; preserve a topic's `category` unless you mean to change it).
102
+ 3. `ei update <type> <id> --json '<full record>'`.
103
+ 4. Verify. (Ei re-embeds on write, so search reflects the new text.)
104
+
105
+ ---
106
+
107
+ ## Recipe E — Re-point a single quote
108
+
109
+ **Symptom:** one quote is attached to the wrong person/topic.
110
+
111
+ The canonical three-step (see `references/cli.md`):
112
+ ```
113
+ ei --id <quote-id> # 1. read
114
+ # 2. set data_item_ids to the correct id(s)
115
+ ei update quote <quote-id> --json '<full record with fixed data_item_ids>' # 3. write
116
+ ```
117
+ Then re-read the quote and the affected person(s) to confirm the link moved.
118
+
119
+ ---
120
+
121
+ ## Recipe F — Remove a junk or empty record
122
+
123
+ **Symptom:** a record is genuinely spurious (a mis-extracted "person" that's actually a
124
+ company name, an empty duplicate, noise).
125
+
126
+ 1. **First, rescue any real quotes** — if it has `linked_quotes` that belong to a real
127
+ entity, re-point them (Recipe E) so removing this record doesn't destroy them.
128
+ 2. Confirm with the user that the record is truly junk (removal has no undo).
129
+ 3. `ei remove <type> <id>`.
130
+ 4. Verify it no longer appears in `ei --id` / search.
131
+
132
+ ---
133
+
134
+ ## If a write looks wrong afterward
135
+
136
+ There's no undo, but the data isn't stuck — you fix a bad write with another write:
137
+ - Wrong field value → `update` it again with the correct full record.
138
+ - Re-pointed the wrong quote → `update` the quote again to the right id.
139
+ - Removed something you shouldn't have → re-`create` it (note: new id; re-point its quotes to
140
+ the new id). Tell the user this happened and what the new id is.
141
+
142
+ Re-read the record(s) via `ei --id <id>` to confirm — that's the reliable check. Don't rely
143
+ on inspecting `corrections.json`: it's frequently already empty even after a fully
144
+ successful write (see `references/cli.md` → "There is no undo"). Then re-verify and re-report.
@@ -0,0 +1,71 @@
1
+ # reference: working with a non-technical user
2
+
3
+ The person asking you to fix their memory may not be a developer. They cannot read JSON,
4
+ they don't know what an "id" or "data_item_ids" is, and they should never have to. They
5
+ think in **people, facts, and things that were said** — talk to them in exactly those terms.
6
+ Your competence shows up as *clarity and care*, not jargon.
7
+
8
+ This matters because this skill is often the **only** way a non-technical person can fix
9
+ their Ei data. If you confuse or mislead them, they have no fallback.
10
+
11
+ ## Core rules
12
+
13
+ 1. **Never show raw records, JSON, ids, or embeddings.** Not to confirm, not to report.
14
+ They are noise to this user and erode trust ("I don't understand what you're showing me").
15
+ 2. **Describe the change, not the mechanism.** They care *what will be true about their
16
+ memory afterward*, not which command you'll run.
17
+ 3. **Confirm before every write, in their language.** Get a real yes on a plain-language plan.
18
+ 4. **When you're unsure, ask a specific, answerable question** — never guess to keep moving.
19
+ 5. **Report results as human outcomes**, and tell them how to see it for themselves.
20
+
21
+ ## Confirming a plan — translate before you ask
22
+
23
+ Turn the operation into plain outcomes. For a split:
24
+
25
+ > **Don't say:** "I'll `create` a new person record, re-point quote `f157e5b9`'s
26
+ > `data_item_ids`, and `update` the identifiers on `aea65478`."
27
+ >
28
+ > **Say:** "It looks like your memory has mixed up **two different Jeffs** into one:
29
+ > **Jeff Kirk** (Robots & Pencils — hiring and sales) and **Jeff Nickles** (the AS400 vendor).
30
+ > I'd like to **split them into two separate people**, move the one comment about the AS400
31
+ > system over to Jeff Nickles where it belongs, and clean up Jeff Kirk so it only describes
32
+ > him. Want me to go ahead?"
33
+
34
+ Lead with what was wrong, then what you'll make true, then ask. Short. No ids.
35
+
36
+ ## Asking a good disambiguation question
37
+
38
+ When a comment could belong to either person and you can't tell from the evidence, hand them
39
+ a clean either/or with the actual words:
40
+
41
+ > "One comment is ambiguous — *'Please validate the updated approach with Nick and Eric, I am
42
+ > not the dependency.'* Was that **Jeff Kirk** or **Jeff Nickles**? I don't want to file it
43
+ > under the wrong person."
44
+
45
+ Give them the quote and the two options. Don't make them think in records.
46
+
47
+ ## Reporting the result — outcome + how to check
48
+
49
+ > "Done. You now have **two people** in your memory instead of one:
50
+ > • **Jeff Kirk** — the R&P colleague (hiring / sales).
51
+ > • **Jeff Nickles** — the AS400 vendor engineer.
52
+ > The AS400 comment now lives under Jeff Nickles. You can check by searching **Jeff** — you'll
53
+ > see both, cleanly separated."
54
+
55
+ If you had to make a judgment call, name it: "I moved the comment about *X* to Jeff Nickles
56
+ because it was in the Akrochem QA channel — let me know if that's not right."
57
+
58
+ ## Being honest about limits (this builds trust, it doesn't erode it)
59
+
60
+ - **No undo:** "There's no undo button on memory, so I'll confirm with you before I change
61
+ anything — and if something looks off afterward, I can correct it with another edit."
62
+ - **Can't verify:** "I can't tell for certain who said this one — it came from an old coding
63
+ session, and there's no record of the speaker I can check. What's your call?"
64
+ - **Partial fix:** if you fixed most of it but left something for them to decide, say so
65
+ plainly rather than implying it's all done.
66
+
67
+ ## Tone
68
+
69
+ Calm, plain, and precise. You're a careful librarian tidying *their* shelf, not a database
70
+ admin running migrations. Match their vocabulary, keep it short, and always leave them
71
+ understanding exactly what changed and why.
@@ -0,0 +1,238 @@
1
+ ---
2
+ name: ei-persona
3
+ description: >
4
+ Use when the user wants to author or direct the CHARACTER of an Ei persona —
5
+ create one, change how it talks or behaves, or retire one. Triggers: "make Ei
6
+ talk like Yoda", "give DJ a new trait about being sarcastic", "make [persona]
7
+ more formal/less sarcastic", "change [persona]'s personality", "[persona]
8
+ shouldn't talk about X anymore", "create a new assistant persona for me",
9
+ "I want a persona that specializes in Y", "delete the persona I made by
10
+ accident", "archive Bob, I don't use him anymore", "rename [persona]", "give
11
+ [persona] a new topic they care about". This is the safe, guided way to
12
+ create, edit, or retire a persona's identity without hand-editing JSON. The
13
+ persona is an artifact the user is designing — there's nothing to verify
14
+ against evidence, but writes still replace the whole record and there's no
15
+ undo, so plan, confirm, and write carefully.
16
+ ---
17
+
18
+ # Ei Persona — authoring a persona's character
19
+
20
+ You are helping someone shape an **Ei persona** — the identity a persona presents
21
+ (its `display_name`, description, traits, and topics) rather than anything it has
22
+ learned about the user. Something about a persona's character needs to change: a
23
+ new persona should exist, an existing one should talk or think differently, gain
24
+ or lose a trait, or be retired. Your job is to make that change **correctly and
25
+ safely**, then tell the user in plain language what you did.
26
+
27
+ > **Read this whole file first.** This skill is rarely invoked, so it is written to
28
+ > be *complete*, not short. The person driving you may be **non-technical** — they
29
+ > cannot check your JSON, they are trusting you. Act like it.
30
+
31
+ ---
32
+
33
+ ## Is this the right skill? (read before you start)
34
+
35
+ This skill is for **directing or authoring another persona's character from the
36
+ outside** — the user (a human) telling you to change how a *named* persona (Ei,
37
+ Emmet, DJ, or any custom persona) looks, talks, or thinks, or asking you to
38
+ create/archive/delete one. The request names a persona and describes a character
39
+ change.
40
+
41
+ **If instead the calling agent is being asked to reflect on its OWN identity**,
42
+ based on **its own** accumulated session history (a Person log it has been
43
+ building up about itself) — that is a *different* skill: **`coding-harness-reflect`**.
44
+ That skill is introspective self-critique ("do a reflection", "reflect on my
45
+ identity", "my person record is full", "trim my person record") driven by the
46
+ agent examining its own behavior log with an outside observer. It is not about a
47
+ human directing a persona's character from outside, and it has its own
48
+ minimum-trait/topic conventions specific to that ceremony. If you're not sure
49
+ which applies, ask: *is a human telling an agent what to become, or is the agent
50
+ examining what it already is?* The former is this skill; the latter is
51
+ `coding-harness-reflect`.
52
+
53
+ This skill is also **not** `ei-curate`: `ei-curate` fixes records of *real external
54
+ people* (facts/topics/people/quotes) by verifying against evidence. A persona isn't
55
+ a record of anyone else — it's a character the user is designing. There is no
56
+ evidence to check; the user's stated intent **is** the truth.
57
+
58
+ ---
59
+
60
+ ## Prime directive
61
+
62
+ **You are shaping a character the user is designing. There is no ground truth to
63
+ verify against — the user's stated intent IS the truth. But writes still replace
64
+ the whole record and there is still no undo, so plan before you act, confirm
65
+ before you write, and never guess at what the user meant.**
66
+
67
+ Three rules that override everything else:
68
+
69
+ 1. **Don't invent character.** If the user says "make Ei sassier" without saying
70
+ what that means in practice, ask what "sassier" looks like to them before you
71
+ write traits — don't improvise a personality they didn't ask for.
72
+ 2. **You are still touching a live system prompt.** A persona's `long_description`
73
+ and `traits`/`topics` feed directly into how that persona behaves and is
74
+ perceived by the user (and, for non-human personas talking to each other, by
75
+ other personas too). A careless edit changes how something *actually talks* to
76
+ someone, not just a database row.
77
+ 3. **Confirm before every write.** Show the user, in plain language, what you
78
+ intend to change. Get a yes. Writes are an append-only log — there is **no undo
79
+ button** (see `references/cli.md` → "There is no undo").
80
+
81
+ If you feel the pull to "just handle it" and push a change through on a guess at
82
+ what the user wants — that is the exact failure this skill exists to prevent.
83
+ Stop and ask.
84
+
85
+ ---
86
+
87
+ ## Mental model of a persona record
88
+
89
+ A **persona** (`PersonaEntity`) is the identity a persona presents — not anything
90
+ it has learned about the user (that's `ei-curate`'s territory: facts/topics/
91
+ people/quotes). It has:
92
+
93
+ | Part | What it is | How you change it |
94
+ |---|---|---|
95
+ | **identity fields** | `display_name`, `short_description`, `long_description` | rewrite the field |
96
+ | **traits** (`traits[]`) | named character traits, each with a sentiment and optional strength | add / adjust / remove an entry |
97
+ | **topics** (`topics[]`) | subjects the persona has a stance on — perspective, approach, personal stake | add / adjust / remove an entry |
98
+ | **lifecycle flags** | `is_paused`, `is_archived`, etc. | flip via a normal update |
99
+ | **tool grants** (`tools`) | a map of every tool on each currently-**enabled** provider, `true`/`false` per tool for whether this persona has it granted | read the map, flip the boolean(s), write the whole map back |
100
+
101
+ **Tool grants are not about you.** `tools` has nothing to do with whatever tools *you* —
102
+ the agent running this skill — can reach (MCP, your own harness's toolset). On a read,
103
+ `tools` is a **map**: `{ "<Provider Display Name>": { "<Tool Display Name>": true|false } }`,
104
+ covering every tool belonging to every currently-**enabled** provider, with `true`/`false`
105
+ for whether **this persona** may call it. A disabled provider (e.g. Spotify before the
106
+ human finishes OAuth) simply isn't in the map — there's nothing to grant until it's
107
+ enabled, and the map you read is the full, live menu of what *is* grantable right now.
108
+ Granting a tool means: read the map, flip the one boolean you mean to change, write the
109
+ whole map back. Example: "give DJ Spotify access so she can answer 'what are you listening
110
+ to'" means reading DJ's record, finding `"Spotify": { "Currently Playing Track": false,
111
+ ... }` in the `tools` map, flipping `"Currently Playing Track"` to `true`, and writing the
112
+ whole record back — it says nothing about what tools are available to you, right now, in
113
+ this session. If `"Spotify"` isn't in the map at all, the human hasn't finished connecting
114
+ it in Ei yet.
115
+
116
+ Full CRUD is in scope here — **create**, **update**, and **remove** — unlike
117
+ `ei-curate`'s quote carve-out (quotes are evidence of real events and can't be
118
+ fabricated or destroyed; personas are authored artifacts with no such
119
+ restriction). See `references/cli.md` for the exact field list and shapes.
120
+
121
+ **Reserved personas** (`ei`, `emmet`) are structural built-ins, not off-limits —
122
+ their identity is fully editable through this same path (that's expected: a user
123
+ asking Ei to talk like Yoda is asking you to edit the **`ei`** persona's identity
124
+ directly). The *only* restriction is **delete** — see Guardrails below.
125
+
126
+ ---
127
+
128
+ ## Before you touch anything
129
+
130
+ 1. **Confirm the CLI is reachable and current.** Run `ei --help` (or `bunx ei-tui
131
+ --help` if `ei` is not on PATH). **The live `--help` is the source of truth for
132
+ the command surface** — this skill's examples are a guide, but the CLI evolves;
133
+ if a command here doesn't match `--help`, trust `--help` and adapt.
134
+ 2. **Read `references/cli.md`** for the exact create/update/remove contracts, the
135
+ full field list and shapes, the critical **full-record round-trip** rule, and
136
+ how to pass JSON safely.
137
+ 3. **Don't assume a minimum trait or topic count.** Nothing in this path enforces
138
+ one (unlike the reflection ceremony's own 3-traits/3-topics convention — that's
139
+ a different skill's guidance for a different situation, not a rule here). A
140
+ user asking for "one more trait" should get exactly that.
141
+
142
+ ---
143
+
144
+ ## The universal workflow
145
+
146
+ Follow these six steps in order for any persona task. Load the linked reference
147
+ the moment its condition applies.
148
+
149
+ ### 1. Find the persona
150
+ - `ei persona "<name>"` (or `ei personas "<name>"`) to search for it by name — a substring
151
+ match against `display_name`, falling back to semantic search over its description if no
152
+ substring hits. Or `ei --id <id>` if you already have the id. **Do not use the `--persona`
153
+ flag for this** — that flag filters *other* entities (facts/topics/people) down to what a
154
+ named persona has learned; it never returns the persona's own record, so it will not find
155
+ what you're looking for. If the user is asking you to create a brand-new persona, there's
156
+ nothing to find yet — skip to step 3.
157
+
158
+ ### 2. Read — understand what's actually there
159
+ - `ei --id <id>` → the **full** current record: `display_name`,
160
+ `short_description`, `long_description`, every entry in `traits[]` and
161
+ `topics[]`, and the lifecycle flags (`is_paused`, `is_archived`, …). You cannot
162
+ safely change one field without seeing all the others (full-record round-trip —
163
+ see `references/cli.md`).
164
+ - Restate to yourself what the persona currently is, in plain terms, before
165
+ deciding what to change.
166
+
167
+ ### 3. Plan — decide the exact change
168
+ - Pick the recipe for the task: add/adjust/remove a trait, add/adjust/remove a
169
+ topic, rewrite a description, create a new persona, archive/unarchive, or
170
+ delete. **→ Read `references/recipes.md`** and follow the matching one.
171
+ - Write the plan as a short, plain-language list of changes (no JSON yet). If the
172
+ user's request is vague ("make it funnier"), turn it into something concrete
173
+ *with them* before writing anything.
174
+
175
+ ### 4. Confirm — get the user's yes
176
+ - Present the plan the way a non-developer can evaluate it.
177
+ **→ Read `references/talking-to-the-user.md`** for how. Never show raw JSON to
178
+ confirm; describe the *character change*. Wait for approval before any write.
179
+
180
+ ### 5. Write — make the change
181
+ - Follow `references/cli.md` exactly: **read → modify only the target field(s) →
182
+ write the whole record back** (create and update are both full-object writes,
183
+ not patches). Capture the `id` a `create` returns.
184
+
185
+ ### 6. Verify & report — prove it worked
186
+ - Re-read the record you touched (`ei --id <id>`) and confirm the change landed:
187
+ the trait/topic is there (or gone) with the values you intended, the
188
+ description reads the way you meant, nothing else changed.
189
+ - Tell the user, in plain language, exactly what changed and how to see it (e.g.
190
+ "say hello to Ei and you should hear it").
191
+
192
+ ---
193
+
194
+ ## Guardrails (non-negotiable)
195
+
196
+ - **Full-record round-trip.** `update` **replaces** the record. Any field you omit
197
+ is **lost** — including traits/topics you didn't mean to touch. Always fetch the
198
+ current record, change only what you mean to, and send it all back.
199
+ (`references/cli.md`)
200
+ - **There is no undo.** Writes append to a corrections log. A mistake is only
201
+ fixable by *another* write, and `remove` discards a persona's id for good. Plan
202
+ and confirm accordingly.
203
+ - **Reserved personas (`ei`, `emmet`) can be edited freely, but never deleted.**
204
+ Their *identity* — description, traits, topics, even `display_name` — is fair
205
+ game for this skill (that's the whole point: it's how a user gives Ei a new
206
+ voice). But `ei remove persona ei` (or `emmet`) is rejected immediately, before
207
+ it's even queued, with:
208
+ > `Cannot delete reserved persona "<id>". Use archive instead.`
209
+ If a user asks to "delete" Ei or Emmet, that's not possible — offer to
210
+ **archive** instead (`ei update persona <id> --json '<record with
211
+ "is_archived": true>'`), which hides it without destroying it.
212
+ - **Non-reserved personas can be fully deleted.** `ei remove persona <id>` is
213
+ permanent — confirm the user actually means "gone for good," not "hide it," and
214
+ offer archive as the reversible alternative first (`references/recipes.md`).
215
+ - **No minimum trait/topic count here.** Don't invent a floor; a persona with one
216
+ trait, or a brand-new one with none, is valid.
217
+ - **`is_static` is not yours to change.** It marks built-in structural personas and
218
+ isn't part of the writable surface — never try to set it.
219
+ - **STOP and ask when:** the user's description of the change is vague enough that
220
+ you'd be inventing character traits on their behalf, they haven't confirmed a
221
+ plan yet, or a `remove` would destroy a persona the user might still want
222
+ archived instead. Waiting is cheap; overwriting someone's persona on a guess is
223
+ not.
224
+
225
+ ---
226
+
227
+ ## Load references on demand (branching inclusion)
228
+
229
+ Read only what the current step needs — these live in this skill's `references/`
230
+ folder:
231
+
232
+ | When you are… | Read |
233
+ |---|---|
234
+ | about to run any `ei` persona read/write command | `references/cli.md` |
235
+ | executing an add/adjust/remove trait or topic, a create, an archive, or a delete | `references/recipes.md` |
236
+ | planning with, or reporting to, a non-technical user | `references/talking-to-the-user.md` |
237
+
238
+ When in doubt, read more, write less, and ask the user.