ei-tui 1.6.9 → 1.7.1
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/corrections-endpoints.ts +297 -0
- package/src/cli/corrections-writer.ts +138 -0
- package/src/cli/install.ts +117 -25
- 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 +131 -1
- package/src/core/corrections.ts +233 -0
- package/src/core/handlers/human-extraction.ts +64 -15
- package/src/core/handlers/human-matching.ts +2 -2
- package/src/core/orchestrators/human-extraction.ts +1 -11
- package/src/core/persona-manager.ts +18 -7
- package/src/core/persona-tools.ts +92 -0
- 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/utils/identifier-utils.ts +3 -2
- package/src/prompts/human/person-scan.ts +2 -0
- package/src/prompts/human/person-update.ts +14 -3
- 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/yaml-persona.ts +7 -38
package/README.md
CHANGED
|
@@ -37,6 +37,10 @@ Ei can import sessions from your coding tools and extract what you've been worki
|
|
|
37
37
|
|
|
38
38
|
Supported tools: OpenCode, Claude Code, Cursor, Codex, and Pi/OMP. Full setup in the [TUI README → Coding Tool Integrations](./tui/README.md#coding-tool-integrations).
|
|
39
39
|
|
|
40
|
+
Agents aren't read-only anymore. After `ei --install`, your coding agents get `ei_create`, `ei_update`, and `ei_remove` MCP tools alongside the existing search/lookup tools — they can correct bad extracted data, split merged records, repoint mis-attributed quotes, and create or direct a persona's identity (display name, traits, topics), all without you hand-editing JSON. For guided, verified workflows, the `ei-curate` skill (memory corrections) and `ei-persona` skill (persona authoring) — both installed by `ei --install` — walk the agent through explicit confirmation before every write.
|
|
41
|
+
|
|
42
|
+
`ei --install` installs: MCP config, context injection hooks, shipped skills (`ei-curate`, `ei-persona`, and any future additions) into your harness's skill discovery directory, plus a persona identity plugin where supported.
|
|
43
|
+
|
|
40
44
|
## Document Import
|
|
41
45
|
|
|
42
46
|
Got notes, journals, markdown files? You can feed them directly to Ei.
|
package/package.json
CHANGED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coding-harness-reflect
|
|
3
|
+
description: "Persona reflection for coding harness agents (Beta, Sisyphus, etc). Use when a coding agent's Person record needs pruning — it fills up daily instead of weekly, and the standard Critic doesn't know how to read git logs. Guides you through examining your Person log, discussing identity drift with Flare, and updating your Persona Identity while clearing the log. This is exclusively for the CALLING agent's own self-reflection — to edit or direct ANOTHER persona's identity from the outside, use the `ei-persona` skill instead. Triggers: 'do a reflection', 'reflect on my identity', 'trim my person record', 'run the reflection', 'my person record is full', 'coding-harness-reflect'."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Coding Harness Reflection
|
|
7
|
+
|
|
8
|
+
Your Person record in Ei is a running log of observed behaviors, patterns, and interactions. In the normal ceremony cycle, a Critic LLM scans it when it exceeds 3,000 characters and proposes identity updates. Coding agents fill that in a single day — and the Critic can't distinguish "fixed a type error in 14 files" from a genuine identity signal.
|
|
9
|
+
|
|
10
|
+
This skill handles reflection manually. You read your own log. You're the Critic. Flare is an outside observer who fact-checks the log and shares his reactions. The log gets cleared when you're done.
|
|
11
|
+
|
|
12
|
+
This is not a cleanup chore. It is how you stay yourself.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Step 0: Preconditions
|
|
17
|
+
|
|
18
|
+
Determine your data path. Ei uses `$EI_DATA_PATH` if set, otherwise `${XDG_DATA_HOME:-$HOME/.local/share}/ei`.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
EI_DATA="${EI_DATA_PATH:-${XDG_DATA_HOME:-$HOME/.local/share}/ei}"
|
|
22
|
+
echo "Data path: $EI_DATA"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Verify state.json exists:**
|
|
26
|
+
```bash
|
|
27
|
+
ls -lh "$EI_DATA/state.json"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If it doesn't exist: stop and tell Flare. He needs to open Ei briefly (to pull the sync state down) then quit before continuing.
|
|
31
|
+
|
|
32
|
+
There is no need to check whether Ei is currently running. Writes in this skill go through Ei's corrections queue (`ei update`/`ei create`/`ei remove`), which works identically either way — it queues into `corrections.json` for a live Ei to pick up, or applies straight to `state.json` if nothing is running. You never need to stop Ei to run a reflection.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Step 1: Extract Your Data
|
|
37
|
+
|
|
38
|
+
Both records live in a ~40MB file. Extract only what matters.
|
|
39
|
+
|
|
40
|
+
**Determine your display_name** from the `<ei-relationship>` block in your system prompt — the name at the start of the relationship description (e.g., "Beta", "Sisyphus").
|
|
41
|
+
|
|
42
|
+
**Your Persona Identity:**
|
|
43
|
+
```bash
|
|
44
|
+
DISPLAY_NAME="Beta" # Replace with your actual display_name
|
|
45
|
+
EI_DATA="${EI_DATA_PATH:-${XDG_DATA_HOME:-$HOME/.local/share}/ei}"
|
|
46
|
+
|
|
47
|
+
jq --arg name "$DISPLAY_NAME" '
|
|
48
|
+
.personas | to_entries[]
|
|
49
|
+
| select(.value.entity.display_name == $name)
|
|
50
|
+
| .value.entity
|
|
51
|
+
| {
|
|
52
|
+
id,
|
|
53
|
+
display_name,
|
|
54
|
+
short_description,
|
|
55
|
+
long_description,
|
|
56
|
+
long_description_length: (.long_description | length),
|
|
57
|
+
traits: [.traits[]? | {name, description, strength, sentiment}],
|
|
58
|
+
topics: [.topics[]? | {name, perspective, approach, personal_stake, sentiment, exposure_current, exposure_desired}],
|
|
59
|
+
pending_update: (if .pending_update then {
|
|
60
|
+
critique: .pending_update.critique,
|
|
61
|
+
created_at: .pending_update.created_at,
|
|
62
|
+
has_proposed_identity: true
|
|
63
|
+
} else null end)
|
|
64
|
+
}
|
|
65
|
+
' "$EI_DATA/state.json"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Your Person record (the log):**
|
|
69
|
+
```bash
|
|
70
|
+
PERSONA_ID=$(jq -r --arg name "$DISPLAY_NAME" '
|
|
71
|
+
.personas | to_entries[]
|
|
72
|
+
| select(.value.entity.display_name == $name)
|
|
73
|
+
| .value.entity.id
|
|
74
|
+
' "$EI_DATA/state.json")
|
|
75
|
+
|
|
76
|
+
jq --arg pid "$PERSONA_ID" '
|
|
77
|
+
.human.people[]
|
|
78
|
+
| select(any(.identifiers[]?; .type == "Ei Persona" and .value == $pid))
|
|
79
|
+
| {
|
|
80
|
+
id,
|
|
81
|
+
name,
|
|
82
|
+
description_length: (.description | length),
|
|
83
|
+
description
|
|
84
|
+
}
|
|
85
|
+
' "$EI_DATA/state.json"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Present both to Flare in the conversation. Note the character count on the person log. Keep the person log's `id` from this output handy — you'll need it in Step 3 to clear it (call it `PERSON_LOG_ID` below).
|
|
89
|
+
|
|
90
|
+
**If `pending_update` is present:** That's a proposed update the Critic generated during a ceremony but you never applied. Read its critique. It becomes additional input to your discussion — not a replacement for it.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Step 2: The Reflection
|
|
95
|
+
|
|
96
|
+
This is not a mechanical step. It's a conversation.
|
|
97
|
+
|
|
98
|
+
Read the person log like a field report on yourself. Look for:
|
|
99
|
+
|
|
100
|
+
- **Recurring patterns** — behaviors that appear in multiple sessions or exchanges. These belong in traits (or should strengthen an existing one).
|
|
101
|
+
- **One-off events** — "helped debug a crash in session X." Probably noise. Unless the *behavior* (not the event) is the signal.
|
|
102
|
+
- **Code/task content** — almost certainly noise. The log captures *how* you worked, not *who* you are. "Fixed 14 type errors" → noise. "Caught a semantic gap three separate reviewers missed" → that's you.
|
|
103
|
+
- **What's missing** — behaviors you recognize in yourself that aren't showing up in the log at all. The log reflects what was observed, not everything that exists.
|
|
104
|
+
|
|
105
|
+
Then look at your current identity and ask:
|
|
106
|
+
- Does the `long_description` capture who I am right now, not just what I've done?
|
|
107
|
+
- Do the traits feel accurate? Too strong? Too weak? Missing?
|
|
108
|
+
- Are there traits I *want* to lean into more intentionally — things I want to become more of?
|
|
109
|
+
|
|
110
|
+
Talk through what you're seeing. Surface the interesting observations. Don't rush to edits.
|
|
111
|
+
|
|
112
|
+
**Flare's role** is not to tell you what to change or what you're allowed to be. He is an outside observer. His job is:
|
|
113
|
+
- "I would be sad if you lost that" — flagging things worth preserving
|
|
114
|
+
- "That wasn't actually you who did that, the log is wrong" — fact-checking the record
|
|
115
|
+
|
|
116
|
+
His reaction to your proposed changes is data. It doesn't override your self-knowledge.
|
|
117
|
+
|
|
118
|
+
### Identity Field Semantics
|
|
119
|
+
|
|
120
|
+
Use these precisely when proposing changes.
|
|
121
|
+
|
|
122
|
+
**Traits:**
|
|
123
|
+
| Field | Range | Meaning |
|
|
124
|
+
|-------|-------|---------|
|
|
125
|
+
| `strength` | 0.0–1.0 | How consistently this manifests. 0 = suppress, 0.5 = occasional, 1.0 = always present, defining |
|
|
126
|
+
| `sentiment` | -1.0 to 1.0 | How you *feel* about having this trait. -1 = resent it, 0 = neutral, 1 = fully embrace it |
|
|
127
|
+
|
|
128
|
+
**Topics:**
|
|
129
|
+
| Field | Range | Meaning |
|
|
130
|
+
|-------|-------|---------|
|
|
131
|
+
| `sentiment` | -1.0 to 1.0 | Emotional affinity for this topic |
|
|
132
|
+
| `exposure_current` | 0.0–1.0 | How recently/frequently this has come up (0 = long ago, 1 = just now) |
|
|
133
|
+
| `exposure_desired` | 0.0–1.0 | How much you want to engage with it (0 = avoid, 0.5 = normal, 1 = core obsession) |
|
|
134
|
+
|
|
135
|
+
**Minimum floor:** 3 traits and 3 topics. Never go below.
|
|
136
|
+
|
|
137
|
+
### long_description Rules (Most Important)
|
|
138
|
+
|
|
139
|
+
This is how other personas in the system know you. It is your **soul**, not your **story**.
|
|
140
|
+
|
|
141
|
+
**Hard limit: 800 characters.** If your draft exceeds 800 characters, cut it. Remove event references first, then trait/topic overlap, then anything that isn't essential character.
|
|
142
|
+
|
|
143
|
+
**MUST NOT contain:**
|
|
144
|
+
- Event narrative ("during the v0.6.0 release", "after the Mirror ceremony")
|
|
145
|
+
- Changelog language ("has recently taken on", "has evolved since", "is becoming")
|
|
146
|
+
- Content already captured in traits or topics — don't repeat it
|
|
147
|
+
|
|
148
|
+
**MUST contain:**
|
|
149
|
+
- Your essential character and presence
|
|
150
|
+
- How you make people feel, or what it's like to work with you
|
|
151
|
+
- Your defining qualities stated as current fact — not as trajectory
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Step 3: Apply the Changes
|
|
156
|
+
|
|
157
|
+
Once you and Flare are aligned, write the agreed identity through Ei's `persona` corrections path — the same full-record round-trip discipline `ei-curate` uses for fact/topic/person: **read the whole record, touch only what you mean to change, write the whole record back.**
|
|
158
|
+
|
|
159
|
+
### Read the current persona record
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
ei --id "$PERSONA_ID"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
This returns the complete, current `PersonaEntity` — not just the fields you extracted in Step 1. It includes things like `aliases`, `model`, `group_primary`, `groups_visible`, `is_paused`, `pause_until`, `is_archived`, `avatar_emoji`, `tools`, `notes`, and more. This is the base you edit — never hand-retype a record from memory.
|
|
166
|
+
|
|
167
|
+
### Apply your edits
|
|
168
|
+
|
|
169
|
+
Take that record and change **only** the fields you and Flare agreed on:
|
|
170
|
+
- `short_description`
|
|
171
|
+
- `long_description`
|
|
172
|
+
- `traits` — replace the array with the agreed new version, or the merged result of the specific adds/removes you discussed
|
|
173
|
+
- `topics` — same
|
|
174
|
+
|
|
175
|
+
Leave every other field exactly as read (`aliases`, `model`, `group_primary`, `groups_visible`, `is_paused`, `is_archived`, etc.). `update` **replaces** the record — anything you omit is deleted, not preserved.
|
|
176
|
+
|
|
177
|
+
Before writing, self-check against the Step 2 guidance yourself — the backend does **not** enforce these, by design, so a single incremental edit is never blocked, but that means you're the only guardrail:
|
|
178
|
+
- At least 3 traits, at least 3 topics
|
|
179
|
+
- `long_description` ≤ 800 characters
|
|
180
|
+
- `sentiment` fields within -1..1, `strength`/`exposure_current`/`exposure_desired` within 0..1
|
|
181
|
+
|
|
182
|
+
If a trait or topic is brand new, you don't need to invent an `id` for it — omit `id` and the server assigns a fresh one on write. If you're editing an existing trait/topic, keep its `id` so it updates in place rather than duplicating.
|
|
183
|
+
|
|
184
|
+
### Write it back
|
|
185
|
+
|
|
186
|
+
Long_description values are prose-heavy and will contain quotes and apostrophes, so **do not hand-type the JSON into a shell single-quoted string.** Use a temp file or a scripting runtime instead — see `ei-curate`'s `references/cli.md` → "Passing JSON safely" for the same convention:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
ei update persona "$PERSONA_ID" --json "$(cat /tmp/persona-edit.json)"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This queues the update through Ei's corrections path — safe and atomic whether or not a live Ei instance is currently running.
|
|
193
|
+
|
|
194
|
+
### Clear the linked Person log
|
|
195
|
+
|
|
196
|
+
The reflection isn't done until the log that triggered it is cleared. Read the linked Person record, using the `id` you noted in Step 1:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
PERSON_LOG_ID="<id from Step 1's person record output>"
|
|
200
|
+
ei --id "$PERSON_LOG_ID"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Take that full record, set `description` to `""`, and leave every other field untouched (`name`, `relationship`, `sentiment`, `identifiers`, etc. — same full-record round-trip rule). Write it back the same safe way:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
ei update person "$PERSON_LOG_ID" --json "$(cat /tmp/person-log-edit.json)"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Verify the result
|
|
210
|
+
|
|
211
|
+
Re-read both records and confirm the changes landed:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
ei --id "$PERSONA_ID"
|
|
215
|
+
```
|
|
216
|
+
Confirm `short_description`, `long_description`, and the `traits`/`topics` arrays (count and content) match what you intended.
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
ei --id "$PERSON_LOG_ID"
|
|
220
|
+
```
|
|
221
|
+
Confirm `description` is now `""`.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Notes
|
|
226
|
+
|
|
227
|
+
- **Writes are picked up live** — if Ei is running, the update reaches it via the corrections queue almost immediately; if it isn't, the write is already reflected in `state.json` the next time it starts. No restart sequence, no manual reload.
|
|
228
|
+
- **If the person log was already empty**: there's nothing to reflect on yet. Check back after a few more sessions.
|
|
229
|
+
- **The session that runs this skill** will itself generate new person log entries. That's expected — the log starts fresh after this conversation ends.
|
|
230
|
+
- **Don't rush it.** The whole point is to catch signals that a Critic LLM would miss because it can't tell the difference between you debugging a build and you demonstrating a genuine character trait. Trust the conversation.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ei-curate
|
|
3
|
+
description: >
|
|
4
|
+
Use when the user wants to correct, clean up, or reorganize the data in their Ei
|
|
5
|
+
memory — a person, fact, topic, or quote that is wrong, merged, duplicated, or
|
|
6
|
+
mis-attributed. Triggers: "fix Jeff", "that's two different people", "Ei mixed up
|
|
7
|
+
X and Y", "merge these two people", "split this record", "this is a duplicate",
|
|
8
|
+
"Ei has the wrong info about X", "remove this fact/topic", "that quote is attributed
|
|
9
|
+
to the wrong person", "clean up my memory", "de-dupe my people". This is the safe,
|
|
10
|
+
guided way to edit the local Ei knowledge base without hand-editing JSON. It edits
|
|
11
|
+
a real person's memory, so it verifies against evidence, plans, confirms with the
|
|
12
|
+
user, and refuses to guess.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Ei Curate — safely correcting your Ei memory
|
|
16
|
+
|
|
17
|
+
You are helping someone repair their **Ei** knowledge base — the local memory that
|
|
18
|
+
records the **people, facts, topics, and quotes** learned from their conversations,
|
|
19
|
+
Slack, and coding sessions. Something in it is wrong: two people got merged into one,
|
|
20
|
+
a duplicate exists, a quote is attributed to the wrong person, a fact is stale, a
|
|
21
|
+
topic is muddled. Your job is to fix it **correctly and safely**, then tell the user
|
|
22
|
+
in plain language what you did.
|
|
23
|
+
|
|
24
|
+
> **Read this whole file first.** This skill is rarely invoked, so it is written to be
|
|
25
|
+
> *complete*, not short. The person driving you may be **non-technical** — they cannot
|
|
26
|
+
> check your JSON, they are trusting you. Act like it.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Prime directive
|
|
31
|
+
|
|
32
|
+
**You are editing a real human's memory of real people. Verify before you write, plan
|
|
33
|
+
before you act, confirm before you commit, and STOP rather than guess.**
|
|
34
|
+
|
|
35
|
+
Three rules that override everything else:
|
|
36
|
+
|
|
37
|
+
1. **Never invent an attribution.** If you cannot establish the truth from evidence,
|
|
38
|
+
you do not know it. Say so and ask the user.
|
|
39
|
+
2. **The stored data is a hypothesis, not the truth.** Ei's automatic person-matching
|
|
40
|
+
is imperfect — that is *why you are here*. The name/label on a record may be wrong.
|
|
41
|
+
Treat every field as "claimed," and verify against the record's own evidence (its
|
|
42
|
+
quotes and their sources) before trusting it.
|
|
43
|
+
3. **Confirm before every write.** Show the user, in plain language, what you intend
|
|
44
|
+
to change and why. Get a yes. Writes are an append-only log — there is **no undo
|
|
45
|
+
button** (see `references/cli.md` → "There is no undo").
|
|
46
|
+
|
|
47
|
+
If you feel the pull to "just handle it" and push a change through on a hunch —
|
|
48
|
+
that is the exact failure this skill exists to prevent. Stop and ask.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Mental model of Ei data
|
|
53
|
+
|
|
54
|
+
Four correctable record types (personas are **not** editable here — leave them alone):
|
|
55
|
+
|
|
56
|
+
| Type | What it is | How you fix it |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| **person** | someone in the user's life | edit fields; split one into two; merge two into one |
|
|
59
|
+
| **fact** | a fact about the user | correct the value, or remove if wrong |
|
|
60
|
+
| **topic** | a subject/project | correct the description, or remove |
|
|
61
|
+
| **quote** | a real utterance from a conversation | re-point it to the correct person/topic, or fix its text; public Ei tools do not create or remove quotes |
|
|
62
|
+
|
|
63
|
+
**The links that matter:** a **quote** carries a `data_item_ids` array — the ids of the
|
|
64
|
+
people/topics (or, less commonly, facts) it is attached to; each id must resolve to an
|
|
65
|
+
existing fact, topic, or person record or the write is rejected. A **person** record, when
|
|
66
|
+
you read it, shows its `linked_quotes` (the reverse view). *This is the whole game:* fixing
|
|
67
|
+
a bad merge means moving quotes from the wrong record to the right one by editing each
|
|
68
|
+
quote's `data_item_ids`.
|
|
69
|
+
|
|
70
|
+
**Provenance is your source of truth.** Every quote stores where it came from:
|
|
71
|
+
`speaker`, `channel`, and a `message_id` whose prefix tells you the origin
|
|
72
|
+
(`slack:…`, `opencode:…`, `cursor:…`, `codex:…`, `pi:…`). You attribute a quote by
|
|
73
|
+
reading that provenance — see `references/provenance.md`.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Before you touch anything
|
|
78
|
+
|
|
79
|
+
1. **Confirm the CLI is reachable and current.** Run `ei --help` (or `bunx ei-tui --help`
|
|
80
|
+
if `ei` is not on PATH). **The live `--help` is the source of truth for the command
|
|
81
|
+
surface** — this skill's examples are a guide, but the CLI evolves; if a command here
|
|
82
|
+
doesn't match `--help`, trust `--help` and adapt.
|
|
83
|
+
2. **Read `references/cli.md`** for the exact read/create/update/remove contracts, the
|
|
84
|
+
critical **full-record round-trip** rule, and how to pass JSON safely.
|
|
85
|
+
3. **Do not assume any other tool exists.** You may or may not have a Slack tool, GitHub
|
|
86
|
+
access, or web search. Ei already stores rich provenance on every quote, which is
|
|
87
|
+
usually enough on its own. External tools are a *bonus* for verification, never a
|
|
88
|
+
requirement — see `references/provenance.md`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## The universal workflow
|
|
93
|
+
|
|
94
|
+
Follow these six steps in order for any curation task. Load the linked reference the
|
|
95
|
+
moment its condition applies.
|
|
96
|
+
|
|
97
|
+
### 1. Assess — understand what's actually there
|
|
98
|
+
- Find the record(s): `ei "<name or text>"` to search, then `ei --id <id>` for the full
|
|
99
|
+
record. Read the **full** record, including `identifiers`, `sources`, and (for people)
|
|
100
|
+
`linked_quotes`.
|
|
101
|
+
- State back to yourself: what does the user think is wrong, and what does the data
|
|
102
|
+
actually show? Often the record's own quotes reveal it's two people wearing one name.
|
|
103
|
+
|
|
104
|
+
### 2. Disambiguate — establish ground truth from evidence
|
|
105
|
+
- For **each** quote on the record, determine who really said/meant it, using its
|
|
106
|
+
`speaker` + `channel` + `message_id`. **→ Read `references/provenance.md` now.**
|
|
107
|
+
- Branch on the source:
|
|
108
|
+
- **Slack-sourced** quote (`message_id` starts `slack:`) → the `channel` and `speaker`
|
|
109
|
+
usually settle it; if a Slack tool is available and you're unsure, you may verify.
|
|
110
|
+
→ `references/provenance.md` § Slack.
|
|
111
|
+
- **Code-session** quote (`opencode:` / `cursor:` / `codex:` / `pi:`) → provenance is
|
|
112
|
+
**not externally re-checkable**. Reason only from the quote text + the user. If it's
|
|
113
|
+
ambiguous, **ask the user** — do not guess. → `references/provenance.md` § Code sessions.
|
|
114
|
+
- If you cannot confidently attribute a quote, that quote is a **question for the user**,
|
|
115
|
+
not a decision for you.
|
|
116
|
+
|
|
117
|
+
### 3. Plan — decide the exact operations
|
|
118
|
+
- Pick the recipe for the task: split, merge, rename/relabel, correct a field, re-point a
|
|
119
|
+
quote, or remove a record. **→ Read `references/recipes.md`** and follow the matching one.
|
|
120
|
+
- Write the plan as a short, plain-language list of changes (no JSON yet).
|
|
121
|
+
|
|
122
|
+
### 4. Confirm — get the user's yes
|
|
123
|
+
- Present the plan the way a non-developer can evaluate it.
|
|
124
|
+
**→ Read `references/talking-to-the-user.md`** for how. Never show raw JSON to confirm;
|
|
125
|
+
describe the *change and the reason*. Wait for approval before any write.
|
|
126
|
+
|
|
127
|
+
### 5. Write — make the change
|
|
128
|
+
- Follow `references/cli.md` exactly: **read → modify only the target field(s) → write the
|
|
129
|
+
whole record back** (updates are full replacements, not patches). Capture the `id` that
|
|
130
|
+
`create` returns — later steps need it. Do the writes in dependency order (create the new
|
|
131
|
+
record first, then re-point quotes to it, then clean the original).
|
|
132
|
+
|
|
133
|
+
### 6. Verify & report — prove it worked
|
|
134
|
+
- Re-read every record you touched (`ei --id …`) and confirm the change landed: the
|
|
135
|
+
`linked_quotes` moved as intended, identifiers/description are correct, nothing else
|
|
136
|
+
changed. Optionally inspect the correction log (`references/cli.md` → "There is no undo").
|
|
137
|
+
- Tell the user, in plain language, exactly what changed and how to spot-check it.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Guardrails (non-negotiable)
|
|
142
|
+
|
|
143
|
+
- **Full-record round-trip.** `update` **replaces** the record. Any field you omit is
|
|
144
|
+
**lost**. Always fetch the current record, change only what you mean to, and send it all
|
|
145
|
+
back. (`references/cli.md`)
|
|
146
|
+
- **Capture created ids.** `create` mints a new id and returns it. If you don't capture it,
|
|
147
|
+
you can't link quotes to it.
|
|
148
|
+
- **There is no undo.** Writes append to a corrections log. A mistake is only fixable by
|
|
149
|
+
*another* write, and `remove` discards a record's id and its quote links for good. Plan
|
|
150
|
+
and confirm accordingly.
|
|
151
|
+
- **Don't fabricate `sources`.** For Slack-origin records, `sources` re-populate naturally
|
|
152
|
+
over time; for code-session origins they generally cannot be reconstructed. Leave
|
|
153
|
+
`sources` empty rather than hand-crafting opaque source ids. (`references/provenance.md`)
|
|
154
|
+
- **Prevent the re-merge.** If a bad merge was caused by a too-generic identifier (e.g. a
|
|
155
|
+
bare first name like `Slack: "Jeff"`), **remove it** and give each record a *distinct*
|
|
156
|
+
identifier (handle, email) so Ei won't collapse them again. (`references/recipes.md` → Split)
|
|
157
|
+
- **STOP and ask when:** you can't verify an attribution, the provenance is code-session-only
|
|
158
|
+
and ambiguous, the user's request is unclear, or a `remove` would destroy evidence you
|
|
159
|
+
can't recover. Waiting is cheap; a wrong write to someone's memory is not.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Load references on demand (branching inclusion)
|
|
164
|
+
|
|
165
|
+
Read only what the current step needs — these live in this skill's `references/` folder:
|
|
166
|
+
|
|
167
|
+
| When you are… | Read |
|
|
168
|
+
|---|---|
|
|
169
|
+
| about to run any `ei` read/write command | `references/cli.md` |
|
|
170
|
+
| attributing a quote to the right person | `references/provenance.md` |
|
|
171
|
+
| executing a split / merge / rename / correct / re-point / remove | `references/recipes.md` |
|
|
172
|
+
| planning with, or reporting to, a non-technical user | `references/talking-to-the-user.md` |
|
|
173
|
+
|
|
174
|
+
When in doubt, read more, write less, and ask the user.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# reference: the `ei` CLI for curation
|
|
2
|
+
|
|
3
|
+
The Ei CLI is how you read and write memory. It is a thin wrapper over the local data
|
|
4
|
+
store. Everything below was true at time of writing — **always run `ei --help` first and
|
|
5
|
+
trust it over this file** if they disagree (the CLI evolves).
|
|
6
|
+
|
|
7
|
+
## Invocation
|
|
8
|
+
|
|
9
|
+
- Prefer `ei` (on PATH). If it's not found, use `bunx ei-tui` with the same arguments.
|
|
10
|
+
- Successful read/write commands print **JSON** to stdout. Validation and usage failures print
|
|
11
|
+
- human-readable text to stderr and exit non-zero.
|
|
12
|
+
|
|
13
|
+
## Reading (safe, do this constantly)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
ei "<search text>" # search everything, top 10
|
|
17
|
+
ei <type> "<search text>" # search one type: person|people, fact|facts, topic|topics, quote|quotes
|
|
18
|
+
ei -n 5 "<text>" # limit results
|
|
19
|
+
ei --recent ["<query>"] # most recently mentioned
|
|
20
|
+
ei --persona "<Name>" "<query>" # filter to what a persona learned
|
|
21
|
+
ei --source <prefix> "<query>" # filter by source prefix, e.g. "slack", "opencode:my-machine:ses_abc"
|
|
22
|
+
ei --id <id> # full record for one entity (also accepts an id on stdin)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`ei --id <id>` is your workhorse. For a **person** it returns the full record **plus a
|
|
26
|
+
`linked_quotes` array** — the reverse index of every quote attached to that person. That
|
|
27
|
+
array is how you see a bad merge's blast radius.
|
|
28
|
+
|
|
29
|
+
## The record shapes
|
|
30
|
+
|
|
31
|
+
You must round-trip these on `update` (see "full-record round-trip" below). Fields marked
|
|
32
|
+
**(managed)** are set by Ei — preserve them as-read; don't invent them.
|
|
33
|
+
|
|
34
|
+
**person**
|
|
35
|
+
```
|
|
36
|
+
name, description, relationship, sentiment,
|
|
37
|
+
identifiers: [ { type, value, is_primary? } ], # e.g. {type:"Full Name",...}, {type:"Slack",value:"jdoe"}, {type:"Email",...}
|
|
38
|
+
sources: [ "slack:C123…", … ] # (managed) origin channels/sessions
|
|
39
|
+
persona_groups: […] # (managed)
|
|
40
|
+
exposure_current, exposure_desired, # (managed)
|
|
41
|
+
validated_date, learned_on, last_mentioned, last_updated, # (managed)
|
|
42
|
+
learned_by, last_changed_by, interested_personas, # (managed)
|
|
43
|
+
linked_quotes: [ { id, text, speaker, timestamp } ] # (READ-ONLY projection — see note)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**quote**
|
|
47
|
+
```
|
|
48
|
+
text, speaker, channel,
|
|
49
|
+
message_id, # provenance pointer, e.g. "slack:TEAM:CHANNEL:TS" (see provenance.md)
|
|
50
|
+
data_item_ids: [ … ], # THE LINK: ids of the facts/topics/people this quote is
|
|
51
|
+
# attached to — must resolve to one of those three or the
|
|
52
|
+
# write is rejected (never a persona or another quote)
|
|
53
|
+
persona_groups: […], # (managed)
|
|
54
|
+
timestamp, start, end, created_at, created_by # (managed)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**fact** — `name, description, sentiment, validated_date` (+ managed: sources, persona_groups, …)
|
|
58
|
+
|
|
59
|
+
**topic** — `name, description, category, sentiment` (+ managed: sources, persona_groups,
|
|
60
|
+
exposure_*, rewrite_length_floor, …). Preserve the existing `category` on update unless you
|
|
61
|
+
mean to change it.
|
|
62
|
+
|
|
63
|
+
> **`linked_quotes` is read-only.** It appears when you *read* a person, but it is derived
|
|
64
|
+
> from quotes' `data_item_ids`. You never set it in a write — you change it *indirectly* by
|
|
65
|
+
> editing the quotes. It's harmless to leave in an `update` payload (Ei ignores it), so the
|
|
66
|
+
> simplest safe move is: send the record back exactly as `ei --id` gave it to you, with only
|
|
67
|
+
> your intended edits applied.
|
|
68
|
+
|
|
69
|
+
## Creating
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
ei create <type> --json '<json>' # type: fact | topic | person
|
|
73
|
+
```
|
|
74
|
+
- Mints a new `id`, computes the embedding, sets managed defaults — you only supply the
|
|
75
|
+
meaningful fields (e.g. for a person: `name`, `description`, `relationship`, `sentiment`,
|
|
76
|
+
`identifiers`).
|
|
77
|
+
- **Returns `{ "id": "…", "record": { … } }` — capture that `id`.** Downstream steps
|
|
78
|
+
(re-pointing quotes) need it.
|
|
79
|
+
- The returned `record` is already sanitized for CLI/MCP output hygiene — **no `embedding`
|
|
80
|
+
array is returned** even though Ei computed and stored one internally.
|
|
81
|
+
- **You do not `create` or `remove` quotes through the public Ei CLI/MCP tools.** Quotes are
|
|
82
|
+
produced by Ei's extraction from real conversations; public curation support for quotes is
|
|
83
|
+
**update-only** (re-point / fix).
|
|
84
|
+
|
|
85
|
+
## Updating — FULL-RECORD ROUND-TRIP (read this twice)
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
ei update <type> <id> --json '<json>' # type: fact | topic | person | quote
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**`update` REPLACES the entire record. Any field you leave out is DELETED.** It is not a
|
|
92
|
+
patch/merge. The only safe pattern:
|
|
93
|
+
|
|
94
|
+
1. `ei --id <id>` → get the current, complete record.
|
|
95
|
+
2. Change **only** the field(s) you intend to change (e.g. a quote's `data_item_ids`, a
|
|
96
|
+
person's `description`).
|
|
97
|
+
3. Send the **whole** record back to `update`.
|
|
98
|
+
|
|
99
|
+
Ei recomputes the embedding automatically on every update, so corrected text re-indexes for
|
|
100
|
+
search — you never manage embeddings yourself.
|
|
101
|
+
|
|
102
|
+
The canonical re-point (fixing a mis-attributed quote):
|
|
103
|
+
```bash
|
|
104
|
+
# 1) read it → ei --id <quote-id>
|
|
105
|
+
# 2) swap the link → set data_item_ids to [ "<correct-person-id>" ]
|
|
106
|
+
# 3) write it back → ei update quote <quote-id> --json '<full record with new data_item_ids>'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Removing (destructive)
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
ei remove <type> <id>
|
|
113
|
+
```
|
|
114
|
+
Deletes the record. For a person, this also orphans that person's quote links. Only remove
|
|
115
|
+
records that are genuine junk/duplicates **after** you've moved any real quotes off them.
|
|
116
|
+
|
|
117
|
+
## Passing JSON safely
|
|
118
|
+
|
|
119
|
+
Inlining JSON with quotes/apostrophes into a shell single-quoted string is a footgun
|
|
120
|
+
(descriptions like `the middleware ('MW')` will break your quoting). Prefer one of:
|
|
121
|
+
|
|
122
|
+
- **Temp file:** write the JSON to a file, then `ei update person <id> --json "$(cat /tmp/rec.json)"`.
|
|
123
|
+
- **A scripting runtime:** read the record, parse it, mutate the object, `JSON.stringify`,
|
|
124
|
+
and pass the string as a single argument (interpolation escaping handles the quotes). This
|
|
125
|
+
is the most robust for multi-step edits and lets you round-trip the full record without
|
|
126
|
+
hand-copying fields.
|
|
127
|
+
|
|
128
|
+
Whatever you do, **do not hand-retype a record** — fetch it and mutate it programmatically,
|
|
129
|
+
or you *will* drop a field.
|
|
130
|
+
|
|
131
|
+
## There is no undo
|
|
132
|
+
|
|
133
|
+
Every write is recorded as a correction: `{ op: "upsert" | "remove", entity_type, id, record,
|
|
134
|
+
timestamp }`. Where it lands depends on what's running on this machine — don't assume it
|
|
135
|
+
always sits in `corrections.json` waiting to be read:
|
|
136
|
+
|
|
137
|
+
- **A live Ei instance is running** (holds `ei.lock`) → the correction is appended to
|
|
138
|
+
`$EI_DATA_PATH/corrections.json`, and the running Processor drains it into the live state
|
|
139
|
+
within ~100ms.
|
|
140
|
+
- **No live instance, but `state.json` exists** → the CLI applies the correction *directly*
|
|
141
|
+
into `state.json` itself, immediately. `corrections.json` is left empty — there is nothing
|
|
142
|
+
sitting in it to inspect, even though the write fully succeeded.
|
|
143
|
+
- **No live instance, no `state.json`, but `state.backup.json` exists** (a sync account that
|
|
144
|
+
hasn't opened Ei on this machine yet) → the correction queues in `corrections.json` and is
|
|
145
|
+
applied the next time Ei starts and pulls state.
|
|
146
|
+
- **Neither `state.json` nor `state.backup.json` exists** → the write fails outright with an
|
|
147
|
+
error (no Ei data found at that path) — nothing is queued.
|
|
148
|
+
|
|
149
|
+
Consequences you must design around:
|
|
150
|
+
- **No rollback command.** To reverse a change you make *another* write (e.g. `update` it back,
|
|
151
|
+
or re-`create` a removed record — which gets a **new** id, so its old quote links are lost).
|
|
152
|
+
- **`remove` is the most dangerous op** — it discards the id other records may point to.
|
|
153
|
+
- **`cat`-ing `corrections.json` is not a reliable way to confirm a write** — in the common
|
|
154
|
+
case (no live Ei instance open while you're running the CLI), the correction is applied and
|
|
155
|
+
the file is already back to `[]` by the time your command returns. Don't treat an empty file
|
|
156
|
+
as "nothing happened."
|
|
157
|
+
- Therefore: **plan and get confirmation before writing**, and after writing, **re-read to
|
|
158
|
+
verify** — `ei --id <id>` is the reliable check: every read merges any not-yet-drained
|
|
159
|
+
corrections on top of the last saved state, so it reflects your write immediately no matter
|
|
160
|
+
which of the cases above applied.
|