cyberine
UsageRun recipes

Knowledge ledger — cyberine knowledge (k6tbw)

An append-only ledger of lessons, facts, pointers and prefs that lives outside every repo, under $CYBERINEHOME/knowledge/ (default ~/.cyberine/knowledge/). Nothing is written inside the repo...

An append-only ledger of lessons, facts, pointers and prefs that lives outside every repo, under $CYBERINE_HOME/knowledge/ (default ~/.cyberine/knowledge/). Nothing is written inside the repo working tree, so git status stays clean. It is separate from the run-record store cyberine memory reads (~/.cyberine/memory/) and from the project groups above (<CYBERINE_HOME>/groups.json).

Run recipe:

cyberine knowledge groups                     # this repo's identity, detected stacks and matched groups (--json)
cyberine knowledge add --kind lesson --title "Jitter retries" --body "Retries need jitter" \
  --when "before changing the retry loop" --evidence src/retry.ts --scope repo     # prints the new id (m + 8 hex)
cyberine knowledge add --kind fact --title "..." --body "..." --scope group:acme --epistemic told --json
cyberine knowledge search retry jitter --limit 5      # id, kind, title per line; entries in scope for this repo only
cyberine knowledge show m1a2b3c4d                     # full entry; text capped at 4000 chars, --json is uncapped
cyberine knowledge list [--all] [--json]              # in-scope entries, newest first; --all adds retired and merged ones
cyberine knowledge edit m1a2b3c4d --title "Jitter and backoff" --body "Retries need jitter and a cap" --reason "clarified"   # same id; prints an undo: line
cyberine knowledge show m1a2b3c4d --history           # earlier values with dates
cyberine knowledge retire m1a2b3c4d --reason "superseded by the new retry module"
cyberine knowledge ack m1a2b3c4d [<id>...]            # unreviewed -> active; or: cyberine knowledge ack --all
cyberine knowledge import --from ~/.claude/memory/kisstour --dry-run --json   # preview an amemoria folder; drop --dry-run to import
  • --kind is one of lesson, fact, pointer, pref; --scope is repo (default), global or group:<name>; --epistemic is observed (default), told or inferred; --evidence repeats. A new entry starts unreviewed.
  • edit <id> [--title t] [--body b] [--when w] [--kind k] [--scope s] --reason <r> [--json] appends a revise event, so the entry keeps its id and show, search and list present the new values; at least one field is required, and a value (or reason) the shared secret redactor would change is refused with a non-zero exit and nothing written. It prints an undo: line holding the knowledge edit command that restores the old values. Because it is an ordinary ledger event it travels through pull and push like any other and never duplicates the entry. Known limitation: --evidence and --epistemic cannot be edited, and a group push refuses an entry whose current values carry a secret.
  • Repo identity comes from remote.origin.url (the first remote when there is no origin): git@github.com:acme/widget.git and https://github.com/acme/widget are both github.com/acme/widget, so two clones of one repo share their entries. A linked worktree resolves to its main worktree; a submodule is its own repo. A repo with no remote is path:<realpath of its toplevel>.
  • In scope for a repo: entries scoped to its identity, to any group it matches, and global ones. show and retire take any id, in scope or not.
  • import --from <dir> [--dry-run] [--json] reads amemoria-format files (Markdown with name, description and metadata.type|project|cwd frontmatter) from the top level of <dir> and never modifies it. metadata.type maps feedback to lesson, project to fact, reference to pointer, user to pref. The title is the description, the body is the text after the frontmatter, the entry is told and starts active (already curated). Scope is the repo identity of metadata.cwd; project: generic, a missing cwd, or a cwd that no longer exists (unless a sibling directory named after project is a git repo) gives global, counted in the summary as an unresolvable cwd. MEMORY.md, MEMORY_*.md, HANDOFF_*.md and subfolders such as archive/ are skipped. Each file has a stable import key (realpath plus name) on its ledger event, so a second run appends nothing and an edited file appends a revise event, not a second entry.
  • Ledger files are ledger/<YYYY-MM>.<host>.jsonl (host = the short host token), one JSON event per line, never rewritten, so two machines can rsync the folder without a conflict.

Extend recipe, groups: write $CYBERINE_HOME/knowledge/groups.json. A repo joins a group when any rule matches: its remote id sits under a remoteOrg, its toplevel sits under a pathPrefix (~ expands; case-insensitive on macOS), or a detected stack matches (wrangler.toml/wrangler.json/wrangler.jsonc is cloudflare, next in package.json dependencies is nextjs, wp-config.php or wp-content/ is wordpress, pyproject.toml is python, go.mod is go).

{ "schema": "knowledge-groups/1",
  "groups": { "acme": { "remoteOrg": ["github.com/acme"] },
              "edge": { "stack": ["cloudflare"] },
              "work": { "pathPrefix": ["~/WORK/ACME"] } } }

Per-repo exceptions go in that repo's gitignored .cyberine/config.local.json, which the ledger only reads: { "knowledge": { "groups": { "add": ["acme"], "remove": ["edge"] } } }.

No new environment variables: the store follows CYBERINE_HOME.

Share through a git source (ksrat)

A group in $CYBERINE_HOME/knowledge/groups.json may name a git repo of knowledge as its source, so teammates share entries without touching any product repo:

{ "schema": "knowledge-groups/1", "groups": { "acme": { "remoteOrg": ["github.com/acme"], "source": { "git": "git@github.com:acme/knowledge.git" } } } }
cyberine knowledge pull --group acme            # free: fetch the source into the local ledger
cyberine knowledge push --group acme            # lists what would go out, changes nothing, exits 1
cyberine knowledge push --group acme --yes      # publish this machine's group entries (never a force-push)

The source repo holds write-once batch files events/<timestamp>.<host>.<hash>.jsonl, so two machines never conflict; pull appends only events the ledger does not already hold (idempotent) and only for entries whose add event is scoped group:acme. The fetch cache lives in $CYBERINE_HOME/knowledge/sources/<group>/ and can be deleted. push refuses outright, naming the entry id, when any event it would send matches the repo's secret patterns (src/core/redact.ts); revise or retire the entry first. A source URL with an embedded password is refused and remote URLs are printed with userinfo redacted. Acceptance check: node .cyberine/accept/ksrat.mjs.

Known Limitations and Deferred Work: there is no hosted store (a hosted team store belongs to the planned paid team tier, see .claude/rules/pricing.md); the source is a plain repo you provide and authenticate to yourself. Only group: entries sync (repo and global entries never leave the machine); ack events stay local, so every machine reviews pulled entries itself; a push that loses a race is retried once after a rebase; a secret already pushed by an older build is not scrubbed from the source history.

Capture at session end (kx5xv)

The long handoff writer (cyberine handoff write --mode long, cyberine tasks end --handoff long, /cyberine:end) also proposes knowledge entries in the same model turn; there is no second turn. Its prompt asks for one fenced block tagged knowledge-proposals holding a JSON array of {"kind", "title", "body", "when"?} (durable lessons only: no secrets, no session chatter, scoped to this repo unless an item sets "scope": "global"), placed under ## Knowledge in the long handoff and repeated at the end of the writer's final reply. The detached watcher that already waits for the writer ingests it, never the live session.

printf 'Where: ...\nNext: ...\n' | cyberine tasks end --handoff long     # wrap, then the writer proposes entries in the background
cyberine knowledge list                                                 # the proposals, status unreviewed
cyberine tasks end                                                       # the next wrap lists up to 5 unreviewed entries, then "+N more: cyberine knowledge list"
cyberine knowledge ack m1a2b3c4d [<id>...]                              # or: cyberine knowledge ack --all
  • Source: the claude writer's final result text when it carries the block, else the long handoff file; only the first source with a block is read, so the repeated block is never ingested twice. The codex backend has no parsed reply, so it uses the file. A writer that failed after emitting the block in its reply still gets its entries captured, even though the watcher deletes its partial handoff file.
  • Each entry is epistemic: inferred, status: unreviewed, by: cyberine-long/<backend>/<model>, session: <session id>, scoped to the repo identity of the handoff's repo. At most 10 per run; title, body and when are clipped (200, 2000, 300 chars) and passed through the secret redactor. A proposal whose title matches a live entry in the same scope is skipped.
  • A missing block, invalid JSON or a malformed item captures nothing for that item and never fails the watcher; the writer's .log gets one knowledge: ... line saying what was captured.
  • cyberine tasks end prints the knowledge: block for this repo's identity on every wrap (with or without --handoff); /cyberine:end reports those entries and acks exactly the ids it listed. Entries proposed by the writer it just started land after the wrap printed, so they show at the next wrap.
  • Plain language: "cyberine nho cai nay" / "cyberine remember this" routes through /cyberine:go to cyberine knowledge add --epistemic told.

Known Limitations and Deferred Work (capture): the proposals are only as good as the writer model's judgement and nothing verifies them against the repo; title matching is the only duplicate check (no semantic dedupe or consolidation); a writer killed before its final reply and its file captures nothing; the short (--mode short) breadcrumb never captures.

Promote an entry into the repo (kv8dn)

The one deliberate path that makes git dirty: promote copies a ledger entry and the reason into the tracked file .cyberine/knowledge/shared.md of the repo you run it in, so a teammate's fresh clone shows it in cyberine notice with an empty CYBERINE_HOME.

cyberine knowledge promote m1a2b3c4d --to repo --reason "every contributor hits this on first build"
git commit -m "docs: share the retry lesson"      # promote only stages; the commit is yours
  • --to repo and --reason are both required; without either the command exits 1 and writes nothing.
  • The file is Markdown, one section per entry: a header line ## <id> | <kind> | <title>, then - promoted:, - reason:, - epistemic:, optional - when: and - evidence: lines, then the body indented by four spaces. Promoting the same id again replaces its section.
  • The file is staged with git add and never committed. The ledger gets a promote event carrying the reason; the entry's status does not change.
  • An entry whose title, body, when, evidence or the reason carries a secret (the src/core/redact.ts patterns, as push checks) is refused by id and nothing is written.
  • In the notice, shared entries rank as repo-scoped entries under the same cap; an id that also sits in the local ledger is shown once, from the ledger.

Known Limitations and Deferred Work (promote): the file is staged, not committed or pushed. Editing shared.md by hand is not synced back to the ledger, so a later revise or retire of the local entry does not touch the shared section; re-promote to refresh it, delete the section to withdraw it. Only --to repo exists; there is no un-promote verb. The shared entries have no status, so a stale one stays until its section is deleted. Acceptance check: node .cyberine/accept/kv8dn.mjs.

Consolidation (k9nhg)

cyberine knowledge consolidate keeps the store correct as it grows. It makes no model call and writes nothing in a repo working tree; every action is an event in the ledger (by: cyberine-consolidate, session: consolidate-<time>), so the ledger only grows.

cyberine knowledge consolidate --no-model --dry-run --json   # plan only: {"actions":[{"op":"merge|stale|lift","ids":[...],"group"?}]}
cyberine knowledge consolidate --no-model --apply            # perform the plan; prints the session id
cyberine knowledge revert consolidate-2026-11-10T09:00:00.000Z   # undo a whole run; or pass one event ts to undo one action
  • merge: two live entries of one scope with the same kind, title and body (case and whitespace ignored). The oldest is kept; the other gets a merge event (id = the duplicate, refs[0] = the keeper) and shows mergedInto in list --all.
  • stale: a live entry whose evidence paths all no longer exist. A relative path resolves against the toplevel of the repo you run the command in, and only for entries scoped to that repo; absolute and ~ paths are checked for any entry; URLs are ignored. An entry with mixed evidence stays live while any path exists.
  • lift: a live lesson with the same title in 3 or more repos under one group's remoteOrg gets a new group-scoped entry (status active, inferred) plus a lift event whose refs are the source entries. The sources stay in place. A group that already has an entry with that title is never lifted again.
  • Revert is a later event: a merge is undone by a merge event with no refs, a stale by an ack (which now also moves a stale entry back to active), a lift by retire of the lifted entry. A reverted merge or stale is not proposed again; a retired lifted entry still blocks re-lifting.
  • Without --apply the command is a dry run. A second --apply appends nothing.
  • After a long handoff writer finishes, the same detached watcher that captures proposals runs the same deterministic apply for the writer's repo, best-effort; a failure is swallowed.

Known Limitations and Deferred Work (consolidate): without --no-model the command refuses, because the model round that merges near-duplicates is not built; only exact duplicates merge. Lift counts only groups defined by remoteOrg (path and stack groups cannot be resolved from a repo id alone) and matches titles exactly. Stale only judges evidence that can be resolved from the current repo, so run it from each repo to sweep that repo's entries. The watcher consolidates from the handoff repo's toplevel only.

Known Limitations and Deferred Work: no session-start notice or pointer file reads the ledger yet, so a session only sees an entry when it runs search, list or show; capture at session end rides only the long handoff writer (above); consolidation is deterministic only (see Consolidation), and the revise and confirm events are understood by the store but have no CLI verb; search is plain word matching, not semantic; a repo whose remote is a local path gets that path (without .git) as its id; import covers only amemoria files (top level of one folder, no recursion); a file renamed to a different name or moved to another path imports as a new entry, and an entry removed from the source is not retired.

Memory tool for the Anthropic API worker (k7e3x)

AnthropicAgentAdapter can expose Claude's client-side memory tool (memory_20250818) backed by this store, so an API worker reads the repo's knowledge without a CLI.

new AnthropicAgentAdapter({ id: 'claude-native', provider: 'anthropic:claude-sonnet-5-5', model: 'claude-sonnet-5-5', apiKey, knowledgeMemory: true });
  • With knowledgeMemory: true the first request declares { "type": "memory_20250818", "name": "memory" } and the adapter answers the tool calls itself. Without it the request bodies are unchanged. There is no environment variable or config key; the option is set where the adapter is constructed.
  • view /memories lists the entries in scope for the run's cwd repo (entriesFor: that repo, its groups and global; retired and merged entries left out), one line each: id, kind, title. view /memories/<id> shows one in-scope entry, capped at 4000 characters like cyberine knowledge show. An entry of another repo is not reachable by id.
  • create, str_replace, insert, delete and rename return a normal (non-error) tool result saying the directory is read-only and that knowledge is written with cyberine knowledge add.

Known Limitations and Deferred Work: read-only by design, because a write from an API worker would skip the review queue (unreviewed entries); only AnthropicAgentAdapter has the option, not the OpenAI-wire loop adapters or the CLI workers; nothing turns it on by default and no fleet config key sets it yet; the view command ignores view_range. Acceptance check: node .cyberine/accept/k7e3x.mjs.