Skip to content

memory-ingest: allow keeping the rendered transcript markdown on a local-stdio brain (the input gbrain dream --phase synthesize already wants) #2352

Description

@chris-conte

Environment

  • gstack 1.60.1.0
  • gbrain 0.42.66.0, Postgres engine, local stdio MCP (mcpServers.gbrain = {"type":"stdio","command":"gbrain","args":["serve"]})
  • Linux

What happens

memory-ingest already renders every session to markdown and stages it on disk, then behaves differently based only on the MCP transport:

// bin/gstack-memory-ingest.ts:1567-1571
const stagingDir = resuming
  ? resumeDir!
  : remoteHttpMode
    ? makePersistentTranscriptDir()   // ~/.gstack/transcripts/run-<pid>-<ts>/  — kept, import skipped
    : makeStagingDir();               // ~/.gstack/.staging-ingest-<pid>-<ts>/  — imported, then deleted

with remoteHttpMode = isRemoteHttpMcpMode() (:1256), and cleanup at :1832 (if (!remoteHttpMode && !preserveStaging) cleanupStagingDir(stagingDir)).

--no-write (:1472) is not an escape hatch — it returns before the staging dir is created (:1567), so it skips writing the files, not the import.

Net effect on a local brain: the rendered markdown exists for milliseconds and is deleted, and the raw dialogue is imported as searchable pages instead.

What I expected

A supported way — flag or env var — to say "render to a stable directory and don't import", independent of transport.

Minimal repro

# ~/.claude.json has mcpServers.gbrain = {"type":"stdio", ...}
gstack-memory-ingest.ts            # → pages imported; ~/.gstack/transcripts/ never created
gstack-memory-ingest.ts --no-write # → nothing written anywhere
ls ~/.gstack/transcripts           # → does not exist

Why it matters

gbrain's own dream synthesizer consumes a directory of .txt/.md files:

// gbrain src/core/cycle/transcript-discovery.ts:190
} else if (st.isFile() && (name.endsWith('.txt') || name.endsWith('.md'))) {

fed by dream.synthesize.session_corpus_dir. So gstack already produces exactly the artifact gbrain's synthesizer is built to consume — but only users on a remote-http brain can reach it.

Local users get the opposite of what gbrain's own schema guidance recommends: raw dialogue as searchable pages, competing with (and outranking) curated notes at retrieval time. On one brain this accumulated ~900 transcript pages that agents then retrieved as current fact, including descriptions of components that had since been deleted; agents repeatedly discarded the hits as "pilot-era transcripts" in their own logs.

The persistent-dir code path already exists and is exercised in remote-http mode — this is a switch, not a feature build.

Suggested fix

Make the mode selectable rather than transport-derived — two lines:

const forcePersist = process.env.GSTACK_TRANSCRIPTS_PERSIST === "1" || args.persistTranscripts;
const remoteHttpMode = isRemoteHttpMcpMode() || forcePersist;

Everything downstream (skip import at :1668, skip cleanup at :1832, skip _activeStagingDir registration at :1577) already keys off remoteHttpMode.

Related nicety: gbrain import accepts --source-id, but the memory stage never passes it (:1419), so imported pages land in default regardless of how many sources the brain has.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions