asobi 0.6.1

A persistent, project-local knowledge graph CLI for AI agents.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
---
name: asobi
description: Use Asobi CLI to store and retrieve long-term project memory (Entities, Observations, Relations) via a persistent SQLite Knowledge Graph.
---

# Asobi Memory Skill

Asobi maintains a persistent Knowledge Graph of project facts, user preferences, and technical decisions. Data is stored locally in `.asobi/data/asobi.db` (project-local when `asobi.toml` or `.asobi/` exists in the working directory).

## When to use

- **New fact learned**: stable architecture decision, tool preference, team convention.
- **Session start**: load prior context for the current project.
- **Session end**: persist status, completed work, and next steps.
- **Context handoff**: another agent or session needs to resume where you left off.

---

## Concepts

One graph, a few node parts — knowing which to write is the skill:

- **Entity** — a named node with a `type`.
- **Observation** — append-only log line, capped (oldest evicted past the limit, default 200). The _trail_.
- **Truth** — a `key→value` fact that upserts. The _current state_ (`status`, `version`).
- **Relation** — directed edge `(from, to, type)`.
- **Skill** — an installed instruction: Markdown body + `description`/`source`/`version` truths.

`graph`/`search` return a lean entity index with truths, `observationCount`, and relations only (cheap); `show` also returns observations and the skill body. Use `export` or `backup` when a complete archival payload is required.

---

## Command Reference

Stream contract (matters for scripted callers): **mutating** commands print a one-line confirmation (`Entity 'X' created.`, `Observation added.`, etc.) to **stderr** and leave **stdout empty** on success — check the exit code, not stdout. **Read** commands (`graph`, `search`, `show`, `stats`, `export`) write their command-specific JSON result to **stdout**. Use `asobi schema --command NAME` to discover the payload contract.

Pass the global `--json` flag to any mutating command to also print the affected entity/entities (and the relations among them) as JSON to **stdout** — e.g. `asobi new A task --json`. This removes the follow-up `show` round-trip; `rm --json` returns `{ "deleted": [...] }`.

### Create

```
asobi new <NAME> <ENTITY_TYPE> [<NAME> <ENTITY_TYPE> ...] [--obs <OBSERVATION> ...]
```

Creates one or more entities in a single call — pass repeated `NAME TYPE` pairs (`new A task B concept` creates two). The argument count must be a multiple of 2. Silently no-ops on names that already exist (`INSERT OR IGNORE`). Prefer one batched call over many invocations.

Supports seeding observations at creation via repeatable `--obs <OBSERVATION>` options (e.g. `asobi new my-node task --obs "initial observation"`). If multiple entities are created in a single call, the specified observations are seeded to all of them.

```
asobi obs <NAME> <CONTENT> [<CONTENT> ...]
```

Appends one or more observation strings to an existing entity. The entity must already exist. Observations are subject to a rolling history cap (defaults to 200, oldest evicted per entity, customizable via `ASOBI_OBSERVATION_LIMIT` or `asobi.toml`'s `observation_limit`).

```
asobi link <FROM> <TO> <RELATION_TYPE> [<FROM> <TO> <RELATION_TYPE> ...]
```

Creates one or more directed relations in a single call — pass repeated `FROM TO TYPE` triples (`link A B uses C D blocks`). The argument count must be a multiple of 3. Upserts on the composite key `(from, to, relation_type)`.

### Read

```
asobi graph
```

Returns the full lean graph as `{ "entities": [...], "relations": [...] }`; entities include truths and observation counts, while observation bodies remain lazy. Use `show` for selected observations and `asobi schema --command graph` for the payload contract.

```
asobi search <QUERY> [--limit <N>] [--where KEY=VALUE ...]
```

Returns a subgraph (same payload shape) of entities matching `QUERY`. Use `asobi schema --command search` for the payload contract. Uses two search paths, merged in order:

1. **FTS5 on observations** — porter stemming + BM25 ranking. `"run"` matches `"running"`, `"tokio async"` ranks entities that contain both words higher. Supports FTS5 operators: `AND`, `OR`, `NOT`, prefix with `*` (e.g. `auth*`).
2. **LIKE on entity name / type** — substring fallback, always runs. Catches exact-name lookups (`UserPreferences`) and entities with no observations.

Supports filtering the search results by matching entity truths via repeatable `--where KEY=VALUE` filters (e.g. `asobi search --where status=READY`). If multiple `--where` filters are specified, they are treated as an intersection (AND condition).

Relations between matched entities are included. Results are ordered by BM25 relevance (FTS matches first, then name/type matches). The default limit is 100 matched nodes; use `--limit` for larger ranked exports. Use `graph` when the caller needs the full graph; do not use a broad `search` query as an implicit export.

```
asobi show <NAME> [<NAME> ...] [--expand <RELATION_TYPE> ...] [--with-ids]
```

Returns a subgraph for the named entities plus relations between them. Takes one or more names as positional args.

- `--expand <RELATION_TYPE>`: repeatably expand relations of a given type. Useful for loading subtrees (e.g. `--expand part_of` to eagerly load related epic tasks).
- `--with-ids`: include `observationsDetailed` list showing exact unique integer IDs (`id`) for each observation.

Do not use `graph` or broad `search` as a document export. Fetch heavy content only for the specific entities needed with `show`.

### Truths

```
asobi truth <NAME> <KEY> <VALUE>
```

Add or update a truth key-value pair for the named entity. Overwriting a truth records the superseded value in an append-only history with its valid-time window, so the current state stays a single value while the change trail is preserved.

```
asobi rm-truth <NAME> <KEY>
```

Delete a specific truth key from the named entity.

```
asobi history <NAME> [KEY]
```

Show an entity's truth change history — each superseded value with the `validFrom`/`validUntil` interval it was current for, newest first. Pass a `KEY` to narrow to a single truth. The value that is current now lives on the entity (`show`), not in history. History is recorded automatically on every overwrite and never appears in `search`/`graph`/`show`, so the default reads stay unchanged. It is local physical state and is **not** carried by JSON `export`/`import`.

### Skills Subsystem

```
asobi skills
```

List all installed skills, grouped by source.

```
asobi skills install <SOURCE> [--all | --select <NAME>...]
```

Install skills from a local path or git repository. Pick with `--all`, `--select <NAME>...`, or neither — which prompts an interactive numbered picker (TTY required; otherwise it errors asking for a flag). Git sources are shallow-cloned to a reused cache under `.asobi/caches/<slug>`. Frontmatter gives the metadata (name falls back to the file/dir name); the body is stored as graph-backed skill data. `--all` is a full **sync**: skills previously installed from the source but no longer present upstream (deleted or renamed) are pruned. `--select` and the interactive picker stay purely additive.

```
asobi skills update [SOURCE]
```

Refreshes installed skills (or one source) from the cache via `git fetch` + `reset --hard`, re-cloning if that fails. Like `install --all`, it syncs — skills dropped upstream are pruned. Needs `git` on `PATH`; unreachable remotes fail with a clear error.

```
asobi skills remove <NAME | SOURCE>
```

Remove a specific skill by its name or all skills from a source URL/slug.

```
asobi skills show <NAME>
```

Show the raw body of an installed skill without JSON escaping. Useful for humans to read. <NAME> can be fully-qualified (e.g. `skill:slug:name`) or just the short name.

### Delete

```
asobi rm <NAME> [<NAME> ...]
```

Deletes one or more entities and all their observations and relations (cascades).

```
asobi update-obs <NAME> <OLD_CONTENT> <NEW_CONTENT>
```

Atomically replaces an existing observation `<OLD_CONTENT>` under the entity with `<NEW_CONTENT>`.

```
asobi rm-obs <NAME> <CONTENT> [--prefix]
```

Removes matching observations from the named entity.

- `--prefix`: deletes all observations under the entity matching the content string as a prefix, rather than requiring an exact match.

```
asobi unlink <FROM> <TO> <RELATION_TYPE>
```

Removes a single relation by its three-part key.

### Workspace init

```
asobi init           # XDG (default) — user-level dirs under $HOME
asobi init --local   # project-local — ./.asobi/ + ./asobi.toml
```

Idempotent in both modes. Run `asobi init` once on a new machine; run `asobi init --local` inside a project root when you want an isolated, project-scoped graph.

### Maintenance

```
asobi compact [--older-than <DAYS>]
```

Three-step maintenance sweep:

1. Prunes session Markdown files in `.asobi/topics/sessions/` older than `DAYS` (default: 90).
2. Syncs **durable knowledge** entities (`project`, `concept`, `reference`, `preference`, `standard`) back to a Markdown file in `.asobi/topics/` — including their truths. Volatile state (`session`, `task`/epic) and self-indexing `skill` entities are skipped: they stay graph-only (read them with `search` / `show`), and `export` / `backup` cover full archival.

```bash
asobi purge [--dry-run]
asobi purge --type task --status DONE --older-than 90 --apply
```

`purge` is dry-run by default and accepts only `session` plus terminal `task` statuses (`DONE`, `CLOSED`, or `ABANDONED`). It never accepts durable knowledge or skills. Review the candidate report before adding `--apply`; it does not run implicitly during `graph`, `search`, `compact`, or startup.

### Shell completion

```bash
asobi completions bash|elvish|fish|powershell|zsh
```

Generate completion scripts from the installed binary so command and flag names stay aligned with the running Asobi version.

### Physical backup and restore

```bash
asobi backup                          # timestamped snapshot; keep newest 3
asobi backup --keep 5                 # retention for managed snapshots
asobi backup -o /secure/asobi.db      # explicit path; never overwrites
asobi restore /secure/asobi.db        # validate, save current DB, then prompt
asobi restore /secure/asobi.db --force
```

- **Includes:** graph state and skill bodies.
- **Safety:** integrity check, owner-only snapshot, `pre-restore-*.db`, closed handles, atomic replacement, stale sidecar cleanup.
- **Scope:** the local SQLite database. Use JSON `export`/`import` for teammate or machine handoff.
- **Retention:** `--keep` applies only to managed snapshots under `backups/`, not an explicit `-o` path.

### Performance verification

```bash
ASOBI_BENCH_SIZES=1000,10000 make bench-graph
ASOBI_BENCH_SIZE=10000 make bench-criterion
ASOBI_BENCH_SIZE=10000 make bench-alloc
make bench-sql-plans
make bench-tasks
make bench-storage
```

Compare Criterion medians and confidence intervals under `target/criterion/`. Open `dhat-heap.json` with DHAT's viewer to find allocation-heavy call stacks. SQL plans should seek `idx_asobi_truths_lookup` for truth filters and use both relation indexes for neighborhood lookup. Use the same dataset size, commit profile, and machine for before/after comparisons.

---

## Entity Type Conventions

Use consistent types so `search` and `show` filters are predictable:

| Type         | Use for                                          |
| ------------ | ------------------------------------------------ |
| `project`    | Per-project stable facts, architecture decisions |
| `session`    | Volatile task state — reset each session end     |
| `preference` | Cross-project user or tool preferences           |
| `standard`   | Global conventions that apply everywhere         |
| `concept`    | Technical concepts, definitions                  |
| `task`       | In-progress task lists, status tracking          |
| `reference`  | Pointers to external resources, URLs             |

---

## Session Protocol

### Session Start

```bash
asobi search --where status=IN_PROGRESS      # find active session entities
asobi show "<project>:session"              # load specific session state
```

Or load everything and filter client-side:

```bash
asobi graph
```

### Session End

```bash
# Update session state
asobi truth "<project>:session" "status" "DONE"
asobi truth "<project>:session" "last-updated" "YYYY-MM-DD"
asobi obs "<project>:session" "next: <one sentence handoff>"

# Session state already lives in the graph; compact only refreshes the
# Markdown topic projection (it skips session/task entities). Use export/backup
# for full archival.
asobi compact
```

### Full Session Reset (next agent starts clean)

```bash
asobi rm "<project>:session"
# recreate at next session start
asobi new "<project>:session" "session"
asobi truth "<project>:session" "status" "IN_PROGRESS"
```

---

## Naming Conventions

- Session entities: `<project-name>:session` (e.g. `asobi:session`)
- Epics / tasks: `<project>:<epic>` and `<project>:<epic>:task-<n>` (e.g. `asobi:skills-truths:task-1`), linked `part_of` the epic
- Skills: `skill:<source-slug>:<name>` (e.g. `skill:jasonswett-llm-skills:tdd`)
- Cross-project preferences: `UserPreferences`, `CodingStyle`, `ToolPreferences`
- Relations use verb phrases: `uses`, `depends-on`, `validates`, `extends`, `blocks`

---

## Output Format Reference

**Graph commands** return their documented JSON payload directly. Use `asobi schema --command graph` to discover the exact shape.

`graph` and `search` use a **lazy-read contract** (they do not populate observation content or skill bodies, returning only `observationCount` and `truths`):

```json
{
  "entities": [
    {
      "name": "string",
      "entityType": "string",
      "truths": {
        "key": "value"
      },
      "observationCount": 12
    }
  ],
  "relations": [
    {
      "from": "string",
      "to": "string",
      "relationType": "string"
    }
  ]
}
```

`show` eagerly returns all `observations` and the skill `body` (if it's a skill entity):

```json
{
  "entities": [
    {
      "name": "string",
      "entityType": "string",
      "observations": ["string", ...],
      "truths": {
        "key": "value"
      },
      "observationCount": 12,
      "body": "string",
      "observationsDetailed": [
        {
          "id": 123,
          "content": "string"
        }
      ]
    }
  ],
  "relations": [
    {
      "from": "string",
      "to": "string",
      "relationType": "string"
    }
  ]
}
```

**Mutating commands** print a plain-text confirmation line to **stderr** (no JSON, stdout empty) unless global `--json` is passed. With `--json`, read the affected payload directly.

---

## Storage Layout

```
.asobi/
  data/
    asobi.db        # SQLite: entities, observations, truths, relations, skills, and FTS5 index
  caches/              # Persistent shallow clones of git skill sources (skills install/update)
    <slug>/
  topics/              # Markdown snapshots synced by `compact`
    <slug>.md
    sessions/          # Session files pruned by compact --older-than
```

The user-level (XDG) workspace mirrors this exact tree under a single root, `$XDG_DATA_HOME/asobi/` (default `~/.local/share/asobi/`), honoring `XDG_DATA_HOME` on every platform — macOS included.

Controlled by `asobi.toml` in the project root (takes precedence over XDG paths). Generated by `asobi init`:

```toml
data_dir   = ".asobi/data"
config_dir = ".asobi/config"
topics_dir = ".asobi/topics"
```

---

## Quick Examples

```bash
# Store a project decision
asobi new "my-project" "project"
asobi obs "my-project" "Uses SQLite WAL for local multi-agent concurrency"

# Store user preference
asobi new "UserPreferences" "preference"
asobi obs "UserPreferences" "Prefer make over cargo commands directly"

# Link them
asobi link "my-project" "UserPreferences" "follows"

# Resume context
asobi show "my-project" "UserPreferences"

# Correct a stale observation
asobi update-obs "my-project" "Uses SQLite WAL for local multi-agent concurrency" "Uses SQLite WAL with bounded busy timeouts for local multi-agent concurrency"

# Search by keyword
asobi search "SQLite"

# Deliberately request a larger ranked result set
asobi search "auth" --limit 500
```