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.
- package/README.md +4 -0
- package/package.json +2 -1
- package/skills/coding-harness-reflect/SKILL.md +230 -0
- package/skills/ei-curate/SKILL.md +174 -0
- package/skills/ei-curate/references/cli.md +160 -0
- package/skills/ei-curate/references/provenance.md +88 -0
- package/skills/ei-curate/references/recipes.md +144 -0
- package/skills/ei-curate/references/talking-to-the-user.md +71 -0
- package/skills/ei-persona/SKILL.md +238 -0
- package/skills/ei-persona/references/cli.md +265 -0
- package/skills/ei-persona/references/recipes.md +247 -0
- package/skills/ei-persona/references/talking-to-the-user.md +107 -0
- package/src/cli/README.md +72 -2
- package/src/cli/commands/personas.ts +46 -1
- package/src/cli/corrections-endpoints.ts +297 -0
- package/src/cli/corrections-writer.ts +138 -0
- package/src/cli/install.ts +252 -157
- package/src/cli/mcp.ts +80 -1
- package/src/cli/persona-corrections.ts +442 -0
- package/src/cli/retrieval.ts +46 -2
- package/src/cli.ts +148 -1
- package/src/core/corrections.ts +233 -0
- package/src/core/handlers/human-extraction.ts +8 -2
- package/src/core/handlers/human-matching.ts +2 -2
- package/src/core/llm-client.ts +7 -1
- package/src/core/orchestrators/human-extraction.ts +1 -0
- package/src/core/persona-tools.ts +92 -0
- package/src/core/personas/opencode-agent.ts +1 -3
- package/src/core/processor.ts +113 -1
- package/src/core/state/human.ts +10 -0
- package/src/core/state/personas.ts +8 -0
- package/src/core/state-manager.ts +11 -0
- package/src/core/types/entities.ts +1 -0
- package/src/core/utils/identifier-utils.ts +3 -2
- package/src/integrations/pi/importer.ts +142 -50
- package/src/integrations/pi/reader.ts +1 -0
- package/src/integrations/pi/types.ts +4 -0
- package/src/storage/file-lock.ts +120 -0
- package/tui/README.md +2 -0
- package/tui/src/commands/provider.tsx +1 -1
- package/tui/src/components/WelcomeOverlay.tsx +3 -3
- package/tui/src/context/ei.tsx +14 -0
- package/tui/src/index.tsx +13 -1
- package/tui/src/storage/file.ts +15 -83
- package/tui/src/util/instance-lock.ts +3 -2
- package/tui/src/util/provider-detection.ts +4 -2
- 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.
|