-
Notifications
You must be signed in to change notification settings - Fork 4
CLI Companion
ed companion talks to Edith's local memory backend. The companion stores
Markdown notes as append-only episodes in Postgres, keeps the original note in
its vault, and deduplicates sources by their SHA-256 content hash.
Run the backend with docker compose from apps/companion. The CLI reaches it
directly on this Mac or through an ed machines port forward. Pass
--endpoint for one invocation, set EDITH_COMPANION_URL for a shell, or use
the default http://127.0.0.1:4820.
| Command | What it does |
|---|---|
ed companion |
Runs ed companion status, the default subcommand. |
ed companion status |
Counts stored records and episodes waiting to be indexed. |
ed companion doctor |
Checks Postgres, migrations, pgvector, Redis and the vault. |
ed companion search <query> |
Searches indexed memory with hybrid retrieval. |
ed companion index |
Embeds episodes that are waiting to be indexed. |
ed companion ingest <path> |
Sends one Markdown file or a recursive folder scan to the backend. |
ed companion episodes |
Lists recent episodes, newest first. |
Reports how much the companion currently stores and when an episode was most recently ingested.
Usage:
ed companion status [--json] [--endpoint <url>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
{
"chunks": 126,
"claims": 18,
"episodes": 42,
"latestIngestedAt": "2026-08-09T08:14:22.301Z",
"observations": 64,
"pendingEpisodes": 2,
"sources": 39
}sources counts unique note bodies, episodes counts appended memory events,
and claims and observations count derived records. chunks counts embedded
search chunks, and pendingEpisodes counts episodes that have no chunks yet.
latestIngestedAt is the most recent ingest time as an ISO 8601 string, or
null when no episode exists.
Examples:
$ ed companion status
RESOURCE COUNT
sources 39
episodes 42
claims 18
observations 64
chunks 126
pending episodes 2
latest 2026-08-09T08:14:22.301Z
$ ed companion status --endpoint http://127.0.0.1:4821 --json
{
"chunks": 126,
"claims": 18,
"episodes": 42,
"latestIngestedAt": "2026-08-09T08:14:22.301Z",
"observations": 64,
"pendingEpisodes": 2,
"sources": 39
}
Behaviour: this is a read-only GET /v1/status. A bare ed companion runs the
same command. If no API answers at the resolved endpoint, stdout stays empty,
the diagnostic names that endpoint on stderr, and the command exits 4.
Asks the backend to check each service it depends on.
Usage:
ed companion doctor [--json] [--endpoint <url>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
{
"checks": [
{ "detail": "connected", "name": "postgres", "ok": true },
{ "detail": "3 of 3 migrations applied", "name": "migrations", "ok": true },
{ "detail": "installed", "name": "pgvector", "ok": true },
{ "detail": "connected", "name": "redis", "ok": true },
{ "detail": "writable", "name": "vault", "ok": true }
],
"ok": true
}ok is true only when every check passed. Each item in checks has the
dependency name, its own Boolean ok, and a human-readable detail from the
backend.
Examples:
$ ed companion doctor
postgres ok connected
migrations ok 3 of 3 migrations applied
pgvector ok installed
redis ok connected
vault ok writable
$ ed companion doctor --json
{
"checks": [
{ "detail": "connected", "name": "postgres", "ok": true }
],
"ok": true
}
Behaviour: doctor decodes the health report even when the API returns HTTP
503. A reachable but unhealthy backend still exits 0 because health lives in
the payload, where scripts can inspect ok. Failure to reach the API exits 4.
Searches embedded memory chunks with hybrid vector and full-text retrieval.
Usage:
ed companion search <query> [--limit <n>] [--json] [--endpoint <url>]
Arguments:
| Name | Type / values | Default | What it does |
|---|---|---|---|
<query> |
text | required | Supplies the memory search text. |
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--limit |
integer from 1 to 50
|
8 |
Asks for this many ranked hits. |
--json |
flag | off | Emits one JSON array on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
[
{
"chunkId": "ad5085e1-6c90-471f-a58d-16e028423d10",
"episodeId": "5d4c0ebf-1086-46fb-ab93-dd325ed197f3",
"kind": "md",
"occurredAt": "2026-08-09T06:30:00Z",
"ord": 0,
"score": 0.032787,
"snippet": "The launch plan calls for a staged rollout after the warden review.",
"title": "Planning notes"
}
]Each hit identifies its chunk and parent episode with chunkId and
episodeId. ord is the chunk position in the episode. title, occurredAt
and kind describe the source episode, snippet contains matching text, and
score is the fused retrieval score.
Examples:
$ ed companion search "launch plan" --limit 3
# SCORE TITLE OCCURRED
1 0.032787 Planning notes 2026-08-09T06:30:00Z
1 The launch plan calls for a staged rollout after the warden review.
$ ed companion search "nothing like this" --json
[]
Behaviour: this is a read-only GET /v1/search. The query is URL encoded and
--limit must be from 1 through 50. No hits print no matches in human output
or [] with --json. If the embedding service returns HTTP 502, the command
names the Ollama embedding service, leaves stdout empty, and exits 4.
Embeds pending episodes and stores their searchable chunks.
Usage:
ed companion index [--json] [--endpoint <url>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
{
"chunksCreated": 9,
"episodesIndexed": 3
}episodesIndexed counts episodes completed by this request, and
chunksCreated counts the searchable chunks created for them.
Examples:
$ ed companion index
indexed 3 episodes into 9 chunks
$ ed companion index --json
{
"chunksCreated": 9,
"episodesIndexed": 3
}
Behaviour: this mutating command sends POST /v1/index with an empty body.
Only episodes without chunks are indexed. If the embedding service returns
HTTP 502, the command names the Ollama embedding service, leaves stdout empty,
and exits 4.
Scans Markdown, audio recordings and PDFs and posts them to the companion.
Usage:
ed companion ingest <path> [--json] [--endpoint <url>]
Arguments:
| Name | Type / values | Default | What it does |
|---|---|---|---|
<path> |
.md, audio or .pdf file, or a directory |
required | Reads one file or recursively finds Markdown, audio (.wav, .m4a, .mp3, .ogg, .flac, .aiff) and PDFs below a folder. |
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
{
"duplicates": 1,
"ingested": 1,
"results": [
{
"episodeId": "5d4c0ebf-1086-46fb-ab93-dd325ed197f3",
"name": "daily/2026-08-09.md",
"occurredAt": "2026-08-09T06:30:00.000Z",
"status": "ingested"
},
{
"episodeId": "b938dfb5-cc52-477c-8be2-3997c59931aa",
"name": "projects/edith.md",
"occurredAt": "2026-08-08T18:10:00.000Z",
"status": "duplicate"
}
],
"skipped": 1
}ingested, duplicates and skipped are counts for the whole scan. Every
posted file has one item in results: name is the filename or its path
relative to the scanned folder, status is ingested or duplicate,
episodeId identifies the stored episode, and occurredAt is its ISO 8601
event time. Oversized files are counted in skipped but have no result item.
Examples:
$ ed companion ingest ./notes
ingested daily/2026-08-09.md
duplicate projects/edith.md
1 ingested, 1 duplicates, 0 skipped
$ ed companion ingest ./notes --json
{
"duplicates": 0,
"ingested": 0,
"results": [],
"skipped": 0
}
Behaviour: a directory walk is recursive, skips hidden files, and sorts names
before upload. The file modification time is sent as a fallback event time.
Markdown larger than 2MB and audio larger than 48MB are skipped with a note on
stderr. No matching file is a usage error. Markdown is posted in batches of at
most 200; audio uploads one file at a time and waits while the companion
transcribes it with whisper.cpp, so a long recording takes a while. The
transcript becomes the episode body with kind voice, the detected language,
the duration, and per-segment timings kept in the episode metadata. PDFs upload
one at a time and land as kind pdf with their extracted text as the body;
scanned PDFs without a text layer are rejected with a clear error.
Lists the most recent episodes ordered by occurrence time.
Usage:
ed companion episodes [--limit <n>] [--json] [--endpoint <url>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--limit |
positive integer | 20 |
Asks for this many recent episodes. The API caps it at 200. |
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
[
{
"id": "5d4c0ebf-1086-46fb-ab93-dd325ed197f3",
"kind": "md",
"occurredAt": "2026-08-09T06:30:00.000Z",
"sha256": "a46b0c249b09d97a9a2eeb66e995fe56d6513fbb80a4f711f846d6b92e98a1e3",
"title": "Planning notes"
}
]Each item has its episode id, ISO 8601 occurredAt, source kind, display
title, and the source content hash in sha256.
Examples:
$ ed companion episodes --limit 3
# TITLE KIND OCCURRED
1 Planning notes md 2026-08-09T06:30:00.000Z
2 Edith launch md 2026-08-08T18:10:00.000Z
$ ed companion episodes --limit 50 --json
[
{
"id": "5d4c0ebf-1086-46fb-ab93-dd325ed197f3",
"kind": "md",
"occurredAt": "2026-08-09T06:30:00.000Z",
"sha256": "a46b0c249b09d97a9a2eeb66e995fe56d6513fbb80a4f711f846d6b92e98a1e3",
"title": "Planning notes"
}
]
Behaviour: this is a read-only GET /v1/episodes. --limit must be greater
than zero. The backend returns at most 200 items, and an empty backend returns
an empty JSON array or a table header with no rows.
Pulls a connector's recent activity into the observations table.
Usage:
ed companion sync <connector> [--json] [--endpoint <url>]
Arguments:
| Name | Type / values | Default | What it does |
|---|---|---|---|
<connector> |
github |
required | Which connector to sync; only github exists so far. |
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
{
"eventsFetched": 87,
"observationsInserted": 42
}eventsFetched counts the GitHub events read this run; observationsInserted counts the observations that were new. Re-running immediately inserts nothing because every observation carries a dedupe key.
Examples:
$ ed companion sync github
fetched 87 events, 42 new observations
Behaviour: the companion reads up to three pages of the authenticated user's GitHub events with the token in its GITHUB_TOKEN environment variable. Push events become one commit observation per commit; pull request, issue and review events each become one observation. Without a configured token the companion answers 412 and the command fails with exit 4.
Lists the behavioural record the connectors have gathered.
Usage:
ed companion observations [--json] [--endpoint <url>] [--limit <n>] [--kind <kind>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--limit |
1 to 200 | 20 | How many observations to list. |
--kind |
commit, pull_request, issue, review
|
all | Only this observation kind. |
--json shape:
[
{
"id": "0d5c53a4-52f0-4be3-a121-c7f3ac53f7de",
"kind": "commit",
"observedAt": "2026-08-09T09:12:44Z",
"source": "github",
"summary": "pulkitxm/edith 72d2aeb Route audio files through ed companion ingest"
}
]Each item is one observed action: source names the connector, kind the action type, summary a one-line rendering, and observedAt when it happened. Newest first.
Examples:
$ ed companion observations --kind commit --limit 3
# KIND SUMMARY OBSERVED
1 commit pulkitxm/edith 72d2aeb Route audio files ... 2026-08-09T09:12:44Z
Behaviour: read-only; an empty record prints a quiet line. Observations are what corroboration will check your claims against, so they never come from anything you wrote for the companion.
Asks the companion's reasoning provider to distill durable beliefs from recent episodes.
Usage:
ed companion reflect [--json] [--endpoint <url>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
{
"beliefsFormed": 2,
"episodesConsidered": 7,
"model": "anthropic, model claude-sonnet-5"
}episodesConsidered counts the episodes read this run, beliefsFormed the new beliefs that survived validation, and model names the provider that did the thinking.
Examples:
$ ed companion reflect
considered 7 episodes, formed 2 beliefs (anthropic, model claude-sonnet-5)
Behaviour: the companion reads its most recent episodes and asks the configured reasoner for two to five higher-order beliefs, each citing the episode ids it rests on. Candidates that cite unknown episodes, cite nothing, or restate an existing active belief are dropped. The provider is Anthropic when ANTHROPIC_API_KEY is set on the companion, or any OpenAI-compatible endpoint via REASON_PROVIDER=openai and REASON_URL; with neither configured the command fails with exit 4.
Lists the beliefs the reflection pass has formed, newest first.
Usage:
ed companion beliefs [--json] [--endpoint <url>] [--limit <n>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--limit |
1 to 200 | 20 | How many to list. |
--json shape:
[
{
"confidence": 0.7,
"evidenceEpisodeIds": ["ade45706-c7e0-480c-9125-11503509bef2"],
"firstFormed": "2026-08-09T16:20:11Z",
"id": "3f7f7a68-0f4b-4f0f-9dd6-6cf2b1f7f0aa",
"kind": "pattern",
"statement": "Ships work in small, frequently pushed increments.",
"status": "active"
}
]Each belief carries its statement, a kind of pattern, preference or state, the extractor's confidence, when it was firstFormed, the evidenceEpisodeIds it cites, and its status. Beliefs are never deleted, only superseded or retired.
Examples:
$ ed companion beliefs --limit 1
1. [pattern, 70%] Ships work in small, frequently pushed increments.
evidence: 3 episodes, since 2026-08-09T16:20:11Z
Behaviour: read-only. An empty list suggests running ed companion reflect first.
Answers a question from your own memory, citing the episodes the answer rests on.
Usage:
ed companion ask <question> [--json] [--endpoint <url>]
Arguments:
| Name | Type / values | Default | What it does |
|---|---|---|---|
<question> |
text | required | The question to answer. |
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
{
"answer": "The auth refactor shipped in March, and it felt slower than it should have.",
"chunksConsidered": 8,
"citations": [
{
"episodeId": "ade45706-c7e0-480c-9125-11503509bef2",
"occurredAt": "2026-03-14T00:00:00Z",
"quote": "Shipped the auth refactor this week. Felt slower than it should have been.",
"support": "verbatim",
"title": "Warden retro"
}
],
"model": "anthropic, model claude-sonnet-5"
}answer is the grounded reply, citations the episodes it rests on, chunksConsidered how many memory chunks were retrieved, and model the reasoner that answered. support types each citation: verbatim is checked structurally, the quote must actually appear in the cited text or the label demotes to paraphrase; inference marks the reasoner reading between the lines and renders that way.
Examples:
$ ed companion ask "how did the auth refactor go"
The auth refactor shipped in March, and it felt slower than it should have.
[1] Warden retro (2026-03-14T00:00:00Z) [verbatim]
"Shipped the auth refactor this week. Felt slower than it should have been."
Behaviour: the companion retrieves the eight nearest chunks by embedding, hands them to the reasoner tagged by episode id, and drops any citation that names an episode the reasoner was not shown, so an answer can only cite what it actually read. When the memory holds nothing relevant the answer says so. Needs a reasoning provider like ed companion reflect; exit 4 without one.
Pulls the claims you made out of episodes that have none yet.
Usage:
ed companion extract [--json] [--endpoint <url>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
{
"claimsExtracted": 4,
"episodesConsidered": 3
}Examples:
$ ed companion extract
considered 3 episodes, extracted 4 claims
Behaviour: the reasoner reads up to ten episodes without claims and records each assertion with a type (fact, intention, commitment, progress, self_assessment, prediction, preference, feeling) and whether independent records could test it. Needs a reasoning provider; exit 4 without one.
Lists the extracted claims, newest first, with the latest verdict where one exists.
Usage:
ed companion claims [--json] [--endpoint <url>] [--limit <n>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--limit |
1 to 200 | 20 | How many to list. |
--json shape:
[
{
"assertedAt": "2026-03-14T00:00:00Z",
"claimType": "progress",
"id": "77b7a0e2-4a37-4b3f-8b0f-0a9d1c2c8e21",
"statement": "Shipped the auth refactor this week.",
"testable": true,
"verdict": "corroborated",
"verdictNote": "Commits to the auth paths land in the same week."
}
]verdict and verdictNote are null until a corroboration pass has judged the claim.
Examples:
$ ed companion claims --limit 1
1. [progress] -> corroborated Shipped the auth refactor this week.
Commits to the auth paths land in the same week.
Behaviour: read-only.
Judges unchecked testable claims against the observations recorded around when they were made.
Usage:
ed companion corroborate [--json] [--endpoint <url>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--json shape:
{
"claimsChecked": 2,
"contradicted": 0,
"corroborated": 1,
"unclear": 1
}Examples:
$ ed companion corroborate
checked 2 claims: 1 corroborated, 0 contradicted, 1 unclear
Behaviour: up to ten unchecked progress, commitment and fact claims are compared against the observations within four days of their assertion. The verdict is corroborated, contradicted or unclear with a one-line note; missing records always read as unclear, never as contradiction. Needs a reasoning provider; exit 4 without one.
Lists the background learning runs the scheduler has recorded.
Usage:
ed companion runs [--json] [--endpoint <url>] [--limit <n>]
Options:
| Name | Type / values | Default | What it does |
|---|---|---|---|
--json |
flag | off | Emits one JSON document on stdout. |
--endpoint |
URL | environment or local default | Uses this Companion API base URL. |
--limit |
1 to 200 | 10 | How many to list. |
--json shape:
[
{
"finishedAt": "2026-08-10T02:03:11Z",
"id": "b0a3e2c1-9a70-40dd-b1b4-2e0a1f5c7d90",
"ok": true,
"startedAt": "2026-08-10T02:00:00Z",
"steps": [
{ "name": "sync_github", "ok": true },
{ "name": "index", "ok": true },
{ "name": "extract_claims", "ok": true },
{ "name": "corroborate", "ok": true },
{ "name": "reflect", "ok": true }
]
}
]Examples:
$ ed companion runs --limit 1
1. 2026-08-10T02:00:00Z ok sync_github, index, extract_claims, corroborate, reflect
Behaviour: read-only. The companion runs the pipeline once a night at COMPANION_REFLECT_AT (02:00 by default) in the backend's local time, or continuously every COMPANION_SCHEDULE_EVERY_SECONDS when that testing override is set. Steps that lack their prerequisite, like a missing GitHub token or reasoning provider, record themselves as skipped rather than failing the run. POST /v1/nightly/run triggers one manually.
| Code | Meaning |
|---|---|
0 |
Success, including a reachable doctor report whose ok is false. |
1 |
General failure. |
2 |
Bad usage, including a missing Markdown path or a non-positive limit. |
4 |
The companion backend is unreachable or returned an unusable response. |
--endpoint wins over EDITH_COMPANION_URL, which wins over
http://127.0.0.1:4820. A saved machine forward can put a remote companion on
that local address: run ed machines forwards on tuf 2, then
ed companion status.
Each Markdown file must be no larger than 2MB. Larger notes are skipped before
any request, and uploads are split into batches of 200 files. The backend
deduplicates by SHA-256 of the note text, so ingesting the same content again is
safe and returns duplicate with the original episode id.
Search and indexing need the Ollama service to be reachable and the configured embedding model to be pulled before the first request.
Use ed machines to create and open a port forward. Read
conventions and contracts for JSON, stdout, stderr and exit
code guarantees shared by every command.
Auto-generated from docs/, edit the docs in the repo, not the wiki.
CLI reference
Companion
- Deploy
- Concepts
- Concepts Memory
- Concepts Ingestion
- Concepts Search
- Concepts Chat
- Concepts Learning
- Concepts Brain
- Concepts Friend
- Hosts
- Stack
- Status
- Doctor
- Search
- Index
- Ingest
- Episodes
- Sync
- Observations
- Reflect
- Beliefs
- Ask
- Extract
- Claims
- Corroborate
- Runs
- Chat
- Conversations
- Forget
- Export
- Import
- Erase
- Wipe
- Episode
- Nightly
- Reason
- Personas
- Council
- Lenses
- Core
- Why
- Hypotheses
- Predictions
- Commitments
- Discrepancies
- Calibration
- Inquire
- Entities
- Eval
- Standup
- Machines
- Baselines
- Connectors
- Facts
- Correct
- Weekly
- Db
Guides