Throughline: memory across channels
An agent works in several places: a Telegram chat, a nightly cron job, a background loop. Each one starts with no idea what happened in the others. Ask the agent in chat whether last night’s invoice run worked, and it can’t say; it wasn’t there.
Throughline closes that gap. At the end of each session or job, your runner hands the transcript to meaning-memory capture-session. It stores a short record of what happened, which the agent can find from any other channel a few minutes later. Capture uses no language model: the same transcript always produces the same record.
What gets captured
Section titled “What gets captured”For each session, capture stores up to five working memories:
- One summary: the channel, the session, how many messages, and the time span. It contains no quoted text.
- Up to four key lines: lines the agent wrote that read as a commitment (“I’ll resend the invoices tomorrow”), a decision (“Decided to hold the November invoice”) or a note to remember (“For future reference: …”).
Privacy is built into what can be stored:
- Lines typed by people are never stored word for word. Only agent-written lines can become key lines, and the summary has no place for quoted text.
- A key line is stored as the agent wrote it. If the agent wrote a name, an email address or an account number into a commitment, that is in the memory.
- A line that looks like a credential (common API key and token formats, private keys, passwords in URLs) is dropped whole, not redacted. This is a best-effort filter, not a security boundary: a secret in an unusual format, split across lines or encoded can pass it. The guarantee is the first rule above.
- Working memories are private to the agent that captured them, and are left out of normal search. You ask for them explicitly.
Capture a session
Section titled “Capture a session”Write the session as JSON Lines, one message per line, with role and content (and optionally ts):
{"role": "user", "content": "Can you check whether Zephyr's invoices went out?", "ts": "2026-10-03T16:02:00Z"}{"role": "assistant", "content": "Zephyr's October invoices went out at 9:00; two bounced because their billing address changed.", "ts": "2026-10-03T16:02:30Z"}{"role": "assistant", "content": "I'll resend the two bounced invoices tomorrow once Dana confirms the new address.", "ts": "2026-10-03T16:03:00Z"}{"role": "assistant", "content": "Decided to hold Zephyr's November invoice until the address is fixed.", "ts": "2026-10-03T16:03:30Z"}{"role": "user", "content": "Thanks, Dana will reply tomorrow.", "ts": "2026-10-03T16:04:00Z"}Then capture it when the session ends:
meaning-memory capture-session --agent support-bot --session-id chat-7f3a \ --channel telegram:dm --source-kind chat --transcript chat.jsonl \ --config-dir /srv/support-bot-mmcapture-session: 3 artifact(s) queued for support-bot (session chat-7f3a, 0 already queued, 0 malformed lines skipped)That is a summary and two key lines. The first assistant line is a plain statement, so it isn’t one.
For output with no roles, such as a cron job’s log, use --plain. It stores the summary only:
meaning-memory capture-session --agent support-bot --session-id nightly-invoices-2026-10-03 \ --channel cron:nightly-invoices --source-kind cron --plain cron.log \ --config-dir /srv/support-bot-mmWhere to call it:
| Runner | When | --source-kind |
|---|---|---|
| A chat or session host | When the session closes or times out | chat |
| A cron or scheduled job | After the job’s own output, including on failure | cron |
| A background loop | At the end of each cycle that did something | heartbeat |
Capture again whenever you like: the same transcript is recognised and stored once. Give each logical session a stable --session-id (for a cron job, the job name and the scheduled date).
Make captures visible within minutes
Section titled “Make captures visible within minutes”Captures are queued, like other memories, and become searchable at the next consolidation drain. For memory to follow the agent across channels within minutes, drain often, for example every five minutes:
MM_BACKEND=file meaning-memory consolidate --agent-id support-bot --drain-only \ --workspace /srv/support-bot-mm --config-dir /srv/support-bot-mmOn Postgres, set MM_BACKEND=postgres instead.
Recall it from another channel
Section titled “Recall it from another channel”Normal search leaves working memories out, so a plain search doesn’t find the chat:
mm_search({"query": "Zephyr"})No memories found (agent: support-bot, budget: 50, filters: query=Zephyr, category=None, min_sig=0.0)Ask for them with memory_stage:
mm_search({"query": "Zephyr", "memory_stage": "working"})**Meaning Memory** — support-bot | 1 entries | …
| # | mem_id | Sig | Eligible | Category | Date | Text ||---|--------|-----|----------|----------|------|------|| 1 | `6bac731efa99cdc3` | 0.65 | - | decision | 2026-10-03 | Decided to hold Zephyr's November invoice until the address is fixed. |…
---⚠️ **This search covered the WORKING buffer only** (recent session captures). Long-term memory was NOT searched by it. Checked for you: no long-term match either for this query.The last lines are a quick check of long-term memory for the same query, so the agent knows whether to search there too. Use "memory_stage": "any" to search working and long-term memory together.
To read back everything from one session:
mm_session_recall({"session_id": "chat-7f3a"})# Session recall `chat-7f3a` — 3 row(s), newest first- `c9bfa3670f5a10fa` [summary] 2026-10-03 · Session record [chat] on telegram:dm: 5 messages (3 agent-authored), session chat-7f3a. Span 2026-10-03T16:02:00Z → 2026-10-03T16:04:00Z. Transcript content is not represented in this memory row.- `aa10231d5f0ccbf9` [keyline-m2-l0] 2026-10-03 · I'll resend the two bounced invoices tomorrow once Dana confirms the new address.- `6bac731efa99cdc3` [keyline-m3-l0] 2026-10-03 · Decided to hold Zephyr's November invoice until the address is fixed.Tell your agent to check working memory when it’s asked about something that may have happened elsewhere (“did the invoice run work?”, “what did we agree in chat?”).
An agent can also write a working memory itself, mid-session, with mm_remember and "memory_stage": "working", adding session_id and channel.
What happens to working memories
Section titled “What happens to working memories”Working memories stay in the working buffer until you turn on the next steps. All of these are off by default, and with all of them off, working memories never expire: the buffer keeps growing until you turn on MM_WORKING_PROMOTE.
| Setting | What it does |
|---|---|
MM_WORKING_COMPILE=1 |
Adds a short Recent (48h) section to the agent’s compiled memory file whenever that file is rebuilt, so recent activity is in context without a search. |
MM_WORKING_AUTO_REFRESH=1 |
Lets a drain of captured sessions rebuild the compiled file. Without it, working memories never trigger a rebuild, so the Recent section only changes when something else does (see When it is rebuilt). With it, a drained working memory with significance 0.60 or more, or a completed tool action, triggers a rebuild (at most one a minute). |
MM_WORKING_PROMOTE=1 |
When meaning-memory consolidate --promote-only runs (schedule it nightly), working memories that matter (significance 0.60 or more, or a commitment or deadline) are copied into long-term memory, with a link back to the original. All working memories older than MM_WORKING_TTL_HOURS (72 by default) then expire. Copying into long-term memory needs Postgres; expiry works on both backends. For expiry without copying, also set MM_WORKING_COPY_PROMOTE=0. |
Set them in the environment of the consolidation command. To get recent activity into the compiled file, turn on MM_WORKING_COMPILE and MM_WORKING_AUTO_REFRESH together:
MM_WORKING_COMPILE=1 MM_WORKING_AUTO_REFRESH=1 MM_BACKEND=file meaning-memory consolidate \ --agent-id support-bot --drain-only --workspace /srv/support-bot-mm --config-dir /srv/support-bot-mmSessions that were drained before you turned them on show up at the next rebuild, as long as they are still within 48 hours. On Postgres you can also rebuild on demand with the mm_compile_memory tool. A sensible order is: capture only and check recall, then the two compile settings for one agent, then MM_WORKING_PROMOTE.
On Postgres, meaning-memory doctor --category throughline checks promotion, lineage and the capture index.
Limits in this release
Section titled “Limits in this release”- Capture writes through the engine’s queue in the agent’s workspace, so run it where that workspace is.
- The REST API can read working memories (
?memory_stage=working) but cannot write them; usecapture-sessionormm_remember.