cyberine
UsageRun recipes

Long-term memory — episodic records, retrieval + injection (waves 1-4)

Every run that reaches a terminal state (done / error / cancelled) automatically writes an episodic memory record — no setup needed:

~/.cyberine/memory/
  projects.json                      # slug -> { cwd } map (resolves same-basename collisions)
  <project-slug>/                    # slug = lowercased basename of the project cwd (readable)
    records/<runId>.json             # one record per run, append-only, never rewritten
    issues.jsonl                     # issue ledger, append-only opened/resolved transitions
    index.json                       # compact derived index (newest first)

The slug is the lowercased project basename so ~/.cyberine/memory/ stays human-readable. Two DIFFERENT project paths with the SAME basename (e.g. /a/web and /b/web) would otherwise share one dir; projects.json records which cwd owns each slug, the first owner keeps the clean basename, and a later different-cwd project is disambiguated to <base>-<hash6(cwd)>. The map is migrated lazily on first use — pre-existing dirs are backfilled from the cwd carried in their records, so the incumbent owner is never re-slugged. Override the whole root with CYBERINE_MEMORY_DIR.

Each record carries the full task, final state, per-worker states, a one-line judge verdict, an extractive outcome summary, and a lineage block that makes runs traceable:

  • previousRunId — auto-linked to the latest prior run of the same project (the chain).
  • parentRunId / chatSessionId — set when a run is dispatched by another run or a chat session (threaded via CYBERINE_PARENT_RUN_ID / CYBERINE_CHAT_SESSION_ID env, populated by launchBackgroundRun(task, cwd, { parentRunId, chatSessionId })).
  • rootRunId — first run of the dispatch chain.

The same lineage block is stamped on run.json and the global registry record, so the fleet TUI and cyberine agents data carry it too. Writes are best-effort and atomic — a memory failure can never affect the run itself.

Structured outcomes (wave 2): the implement-task workflow asks every implement worker to end its final answer with an ACCOMPLISHED: / ISSUES: / NEXT: trailer (one - bullet per item, - none when empty). At run end the winning worker's answer text is parsed (parseOutcomeSections in src/store/MemoryStore.ts) into the record's outcome.accomplished / outcome.issues / outcome.next fields. Each parsed issue opens an event in issues.jsonl with a stable slug id (provenance: byRunId); an id that is already open is not re-opened, and current state is the fold of events (last one wins — listOpenIssues). Any custom workflow def gets the same extraction by emitting the trailer in its workers' final answers.

Retrieval + injection (wave 3): every dispatch through launchBackgroundRun (cyberine run --bg, chat /run, the TUI n new-task) automatically prepends a MEMORY (prior runs in this project ...) preamble — the top-K prior runs scored by keyword overlap + recency decay + lineage affinity, plus the open issues — ahead of the task prompt, and a fresh cyberine chat session gets the same block as a second system message (resumed sessions already carry theirs). The preamble is bounded by CYBERINE_MEMORY_TOPK / CYBERINE_MEMORY_MAX_TOKENS. Before the run's own record is written the injected preamble is removed (the implement-task workflow embeds it INSIDE the wrapped TASK: block, so a leading-only strip is not enough — the whole MEMORY ... END MEMORY block is removed wherever it sits) and the human task is lifted out of the wrapper, so records stay readable and memory never re-ingests itself (no compounding growth). Inspect from the CLI:

cyberine memory list             # all records newest-first
cyberine memory show <runId>     # full record (id or unique prefix; follows supersededBy)
cyberine memory search <query>   # multi-axis scored search, top 10
cyberine memory issues [--all]   # open issues (--all: full event ledger)
cyberine memory chain <runId>    # walk the previousRunId chain both directions
cyberine memory rebuild          # regenerate index.json from records/