Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .config/mise/tasks/github-actions/lint-adr-refs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ def adr_number_for(path: Path) -> str | None:


# Directories that never contain committed docs: dependencies, git internals,
# and gitignored agent scratch notes (`.trogonai/**/*.internal.trogonai.md`).
IGNORED_DIRS = {"node_modules", ".git", ".trogonai"}
# gitignored agent scratch notes (`.trogonai/**/*.internal.trogonai.md`), and
# Conductor workspace scratch (`.context`, excluded via .git/info/exclude).
IGNORED_DIRS = {"node_modules", ".git", ".trogonai", ".context"}


def iter_markdown() -> list[Path]:
Expand Down
415 changes: 268 additions & 147 deletions docs/adr/0031-agent-implementation-and-session-plan.md

Large diffs are not rendered by default.

298 changes: 240 additions & 58 deletions docs/adr/0035-session-store-decider-aggregate.md

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions docs/glossary/checkpoint.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ order: 11

# Checkpoint

The last stream sequence a projection or consumer has processed, so it can resume
without reprocessing. See [Decider Platform](../architecture/decider.md#read-side-primitives-projector-and-processor).
The last physical stream sequence a projection or consumer has processed, so it
can resume without reprocessing. This read-side position is not an aggregate
[Snapshot](./snapshot) and is not the `trogonai.session.sessions.v1alpha1.Checkpoint`
protobuf. [ADR#0031](../adr/0031-agent-implementation-and-session-plan.md) and
[ADR#0035](../adr/0035-session-store-decider-aggregate.md) call that opaque
platform harness state a harness recovery checkpoint to keep the failure modes
distinct.
See [Decider Platform](../architecture/decider.md#read-side-primitives-projector-and-processor).
2 changes: 1 addition & 1 deletion docs/research/acp/products/gemini-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ ACP mode is invoked with `gemini --acp` (add `--debug` for verbose ACP tracing)

### Integration wiring

Process model: the ACP client (an editor/IDE) spawns `gemini --acp` as a stdio subprocess; gemini-cli never listens on a socket in this mode. Zed's own gemini-cli agent page describes this explicitly as spawning the same CLI binary as a subprocess speaking ACP (https://zed.dev/acp/agent/gemini-cli). Filesystem access is proxied: gemini-cli does not touch the workspace filesystem itself in ACP mode, it issues fs/terminal RPCs back to the client, which enforces what paths are visible (https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/acp-mode.md). MCP passthrough happens during `initialize`: the client advertises its own MCP server, gemini-cli connects to it and folds the discovered tools into the same tool-calling loop it uses for its native MCP client support. Session lifecycle (new/load/resume) rides on ACP's `newSession`/`loadSession`, but the underlying durable transcript is gemini-cli's own local append-only JSONL log under `~/.gemini/tmp/<projectShortId>/chats/`, single-writer, no multi-host coordination; see the [session store research](../../session-store/products/gemini-cli.md) for detail.
Process model: the ACP client (an editor/IDE) spawns `gemini --acp` as a stdio subprocess; gemini-cli never listens on a socket in this mode. Zed's own gemini-cli agent page describes this explicitly as spawning the same CLI binary as a subprocess speaking ACP (https://zed.dev/acp/agent/gemini-cli). Filesystem access is proxied: gemini-cli does not touch the workspace filesystem itself in ACP mode, it issues fs/terminal RPCs back to the client, which enforces what paths are visible (https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/acp-mode.md). MCP passthrough happens during `initialize`: the client advertises its own MCP server, gemini-cli connects to it and folds the discovered tools into the same tool-calling loop it uses for its native MCP client support. Session lifecycle (new/load/resume) rides on ACP's `newSession`/`loadSession`, but the underlying durable transcript is gemini-cli's own local append-only JSONL log under `~/.gemini/tmp/<projectShortId>/chats/`, single-writer, no multi-host coordination; see the [session store research](../../session-store/products/gemini-cli/index.md) for detail.

### Channel mapping

Expand Down
2 changes: 1 addition & 1 deletion docs/research/acp/products/opencode.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Copy: exposing ACP as a thin, additive surface over an agent's existing tool/ses
- https://github.com/kortix-ai/opencode-channels
- https://github.com/ominiverdi/opencode-chat-bridge
- https://agentclientprotocol.com/overview/clients
- [session store research](../../session-store/products/opencode.md) (anomalyco/opencode fork, commit 62e4641235d7847dadc60da37cca8a023dd54fc1)
- [session store research](../../session-store/products/opencode/index.md) (anomalyco/opencode fork, commit 62e4641235d7847dadc60da37cca8a023dd54fc1)

## Adversarial verification

Expand Down
43 changes: 36 additions & 7 deletions docs/research/session-store/RESEARCH_PROMPT.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Research Prompt: how {PRODUCT} stores and resumes sessions

Reusable prompt for the session-store study. Run once per product. Output
goes into `docs/research/session-store/products/{slug}.md`, following the
goes into `docs/research/session-store/products/{slug}/index.md`, following the
section skeleton below. Add the dossier to `index.md` when done.

## Task
Expand Down Expand Up @@ -102,6 +102,18 @@ to the durable session.
wrapper (timestamps, method/kind tags, ids), the payload shape, how message
types are distinguished, and how entries link into a chain or thread
(parent/uuid references, ordering fields). Quote the type definitions.
- **An envelope is not the payload.** If the entry type inherits its content
field from a base class, or carries the message in a generic `params`, `data`,
or `content` field, open that type too and quote it. Quoting only the
wrapper's own declared fields satisfies "quote the type definitions" while
leaving the actual message undocumented, and two dossiers here did exactly
that: Google ADK's records every field `Event` declares but never opens its
superclass, where the payload lives (`content: Optional[types.Content]`,
`src/google/adk/models/llm_response.py:62`), and Grok Build's names
`SessionUpdateEnvelope{timestamp, method, params}` as the source-of-truth log
line without ever opening `params` or the `ConversationItem` in the cache it
derives. In both cases the envelope is the easy half and the payload is the
half a reader needs.
- Is the entry opaque to the store (persisted and returned verbatim) or does
the store parse and interpret it? What field, if any, does the store rely on
for identity/dedup?
Expand Down Expand Up @@ -166,21 +178,38 @@ to the durable session.
schemas, on-disk layouts, official repos. Secondary sources only to
triangulate. When the source is a repository, pin the exact commit and cite
`path:line` for each claim.
2. Capture each exact quote as a checked-in source excerpt. Record its direct
2. **Cite repo-root-relative paths, not bare filenames.** Write
`crates/agent/src/db.rs:671`, not `db.rs:671`. Large trees hold many files
with the same basename (Zed has three `db.rs` and three `migrations.rs`;
Pi has ten `types.ts`), and a bare basename cannot be mechanically resolved
by a later auditor, which is exactly when the citation matters most. A
short form is acceptable only for repeat references within the same section
*after* the full path has appeared there, and only when that basename is
unique in the tree. Prefer the `:123` bare-line form for repeats within a
section that has already named one file.
3. Capture each exact quote as a checked-in source excerpt. Record its direct
URL or repo `path:line`, document section or repository symbol, source
version or commit when available, and retrieval date. Also record an
archive or snapshot URL when one exists and a content digest when the source
publishes or permits one. These evidence records must remain auditable if
the live URL changes.
3. Record the retrieval date with `date +%F`; never guess it.
4. Stay on the operational storage model. Ignore pricing, marketing claims,
4. **Quotation marks mean transcribed, never summarized.** If text sits inside
quotes, it must match the source character for character, including its
punctuation. Tightening a comment while leaving the quotes on it produces a
claim that survives every mechanical check and is still false: a Mastra
default-throw message was rendered as "this is likely a bug -- all adapters
should implement this" when the source reads "This is likely a bug - all
Mastra storage adapters should implement resource support." Paraphrase
freely, but then drop the quotes and cite the line.
5. Record the retrieval date with `date +%F`; never guess it.
6. Stay on the operational storage model. Ignore pricing, marketing claims,
and features unrelated to how sessions are persisted, resumed, listed, and
retired.
5. Fill the product skeleton sections in the order the product's model makes
7. Fill the product skeleton sections in the order the product's model makes
natural; omit a section only when the product genuinely has no such concept,
and say so. Put anything the sources leave unanswered under **Open
questions**.
6. Where a conclusion here would differ from an accepted record in the
8. Where a conclusion here would differ from an accepted record in the
[ADR index](../../adr/index.md), the ADR is authoritative; note the
difference rather than overriding it.

Expand All @@ -190,7 +219,7 @@ to the durable session.
# {PRODUCT}: how session transcripts are stored and resumed

Part of Session Store Research.
Produced by running [RESEARCH_PROMPT](../RESEARCH_PROMPT.md).
Produced by running [RESEARCH_PROMPT](../../RESEARCH_PROMPT.md).
Evidence snapshot retrieved YYYY-MM-DD. Version-sensitive claims were checked
against these authoritative anchors:

Expand Down
197 changes: 197 additions & 0 deletions docs/research/session-store/RESEARCH_PROMPT_COMPARISON.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Research Prompt: how {PRODUCT} compares to our Session Store

Reusable prompt for stage two of the session-store study. Stage one is
[RESEARCH_PROMPT](./RESEARCH_PROMPT.md), which produces a standalone dossier
describing how a product stores sessions. This prompt consumes that dossier
and produces a comparison against our own design, plus a ranked set of
changes we should consider making.

Run once per product. Output goes to
`docs/research/session-store/products/{slug}/vs-session-events.md` and is
linked from `index.md` under its stage-one dossier.

[fx compared to our session event catalog](./products/fx/vs-session-events.md)
is the worked reference implementation of this prompt. Match its shape.

## Preconditions

Do not run this prompt until the stage-one dossier for {PRODUCT} exists and
its claims are pinned to a commit or a dated document snapshot. A comparison
built on an unverified dossier inherits its errors and launders them into a
recommendation.

## Inputs

Read all of these before writing anything:

- The stage-one dossier: `./products/{slug}/index.md`.
- Our decision record:
[ADR#0035: Session Store as a Decider Aggregate on NATS JetStream](../../adr/0035-session-store-decider-aggregate.md).
This is authoritative. Where the dossier's conclusion conflicts with an
accepted ADR, note the difference; do not override it.
- Our event catalog: `proto/trogonai/session/sessions/v1alpha1/`. Read the
actual `.proto` files. Do not compare against a remembered version of the
catalog.
- The cross-product [synthesis](./synthesis.md), so a recommendation already
argued there is cited rather than re-derived.

## Break-change latitude

The catalog is `v1alpha1` and [ADR#0035](../../adr/0035-session-store-decider-aggregate.md) is a draft. **Breaking changes are on
the table.** A recommendation must not be watered down into an additive
half-measure merely to preserve wire compatibility.

What this does *not* mean is that breaking changes are free. Every
recommendation must state its blast radius explicitly:

- **Additive** -- new field, new event type, new optional value. No migration.
- **Breaking, cheap** -- rename, retype, or field removal with no persisted
data to carry forward, or a mechanical regeneration.
- **Breaking, expensive** -- changes the meaning of already-persisted events,
requires a replay/rewrite, or splits or merges an existing event type.
- **Breaking the decision, not the schema** -- contradicts a numbered decision
in [ADR#0035](../../adr/0035-session-store-decider-aggregate.md). Name the decision by number. These are the most valuable
findings and the most expensive to act on.

Prefer the honest expensive recommendation over the dishonest cheap one.
State the cost; let the ADR owner make the trade.

## Maturity weighting

Weight evidence by how proven the **store** is, not by how popular the
product is. A 48k-star product whose sessions are an unversioned markdown
log is weak evidence. A 2k-star vendor CLI with eight SQL migrations is
strong evidence, because its schema demonstrably survived contact with
shipped users.

Score each axis 0-3 and record the evidence inline. Do not report a bare
number without the artifact that justifies it.

| Axis | What earns a high score | Evidence to cite |
| --- | --- | --- |
| **Evolution scars** | The store format changed under load and carried its data forward | Migration files, schema-version fields, legacy-format sniffing, back-compat read paths, format-version constants |
| **Operational age** | The store has been in the field long enough to hit real failure modes | First commit touching the store, not repo creation date; issues reporting corruption, growth, or lock contention, and their fixes |
| **Exposure** | Real users depend on resume working across crashes, upgrades, and hosts | Vendor-shipped distribution, paid product, or adoption scale; multi-host or network-filesystem handling in the code |
| **Design independence** | The store is an original design, not inherited from an upstream fork | Whether the store code diverges from the fork parent, with paths |

Sum to a 0-12 **store maturity score**. Record it in the comparison's front
matter. It is not a ranking of products; it is the weight the reader should
place on this product's answer when it disagrees with another product's.

Two rules follow from the score:

1. When products disagree, the higher-scoring store's answer is the default,
and the comparison must say why the lower-scoring one diverged (deliberate
trade-off, immaturity, or different problem).
2. A recommendation supported only by stores scoring under 6 must be labelled
**thin evidence** and must not be presented as an industry norm.

## Research questions

### 14. Comparison against our catalog

- **The one structural difference everything else follows from.** Most
comparisons reduce to a single divergence (commit granularity, identity
model, mutability, ownership of derived state) that explains the rest of
the diffs as consequences. Find it and lead with it. If there genuinely
isn't one, say so rather than manufacturing one.
- **Fact-by-fact mapping.** For every durable field or entry type in the
product's store, name our equivalent event or field, or record that we have
none. Use a table. Include the reverse direction: what we record that they
do not, and whether their omission looks deliberate.
- **Semantic mismatches.** Where both sides have a nominal equivalent that
means something different (a "session id" that is a path in one and an
opaque id in the other; a "checkpoint" that is a marker in one and a
snapshot in the other), call it out. These are more dangerous than gaps
because they survive a naive mapping.
- **Where our design is already ahead.** Required, not optional. A comparison
that only finds gaps is a comparison that was not read critically.

### 15. What we should consider changing

Each recommendation is a numbered subsection, ordered most-consequential
first, and must carry all of:

- **The change**, stated concretely against a named `.proto` file, event
type, or [ADR#0035](../../adr/0035-session-store-decider-aggregate.md) decision number.
- **The evidence anchor**: the product, its store maturity score, and the
`path:line` or quote that supports it. No anchor, no recommendation.
- **Blast radius**, using the four categories above.
- **Why it is a good idea, or why it is not.** A recommendation may conclude
*do not do this*. Recording a rejected change with its reasoning is as
valuable as recording an accepted one, and stops it being re-proposed.
- **What it costs us** beyond the migration: added write-path work, larger
events, a new projection to maintain, a new failure mode.

Then three closing buckets, all required:

- **Trade-offs, not gaps.** Differences that are defensible on both sides.
Say what each side bought and paid.
- **What not to copy.** Patterns present in the product that we should
explicitly reject, with the reason. This section prevents a future reader
mistaking the dossier's description for endorsement.
- **Open questions for the ADR.** Anything the comparison surfaced that the
ADR does not currently answer, phrased as a question the ADR owner can
decide.

### 16. Feed the two gaps the industry has not closed

The synthesis concludes that the industry has approximated the event-sourced
session store everywhere, and that the two things nobody in the corpus has
closed are **subagent cascade semantics** and **retention on an unbounded
log**.

These are gaps in *the industry*, not in our design. [ADR#0035](../../adr/0035-session-store-decider-aggregate.md) decisions 6 and
7 already take detailed positions on both, so the job here is to **test those
decisions against this product's evidence**, not to describe them as missing.
Name the decision, say whether the evidence validates, refines, or challenges
it, and where the product's answer is worse, say that too. Writing this
section as though we have no position on cascade or retention is the single
most likely way to get this comparison wrong. Every comparison must answer
both explicitly, even if the answer is "this product has no position on it":

- What does this product do when a parent session is deleted, rewound, or
crashes while a child session is live? Quote the code path, not the docs.
- What stops this product's durable record from growing without bound? If
nothing does, find the issue reports where that became a user-visible
problem.

## Method

1. Every claim about the product traces to the stage-one dossier or to a
pinned `path:line`. Every claim about our design traces to a `.proto` file
or an [ADR#0035](../../adr/0035-session-store-decider-aggregate.md) decision number. Assertions with neither do not ship.
2. Record the retrieval date with `date +%F`; never guess it.
3. Quote our own proto fields exactly. The catalog moves; a paraphrase from
memory will silently go stale.
4. Mark inference as inference. The dossiers keep description and conclusion
separate on purpose; keep that discipline here.
5. Put anything unresolved under **Open questions** rather than resolving it
with a guess.

## Output skeleton (per product file)

```markdown
# {PRODUCT} compared to our session event catalog

Part of Session Store Research.
Produced by running [RESEARCH_PROMPT_COMPARISON](../../RESEARCH_PROMPT_COMPARISON.md).
Stage-one dossier: [{PRODUCT}](./index.md).
Compared against `proto/trogonai/session/sessions/v1alpha1/` and [ADR#0035](../../adr/0035-session-store-decider-aggregate.md) on YYYY-MM-DD.

**Store maturity: N/12** -- evolution scars N/3 ({evidence}), operational age
N/3 ({evidence}), exposure N/3 ({evidence}), design independence N/3 ({evidence}).

## The one structural difference everything else follows from
## Mapping
## What we should consider changing
### 1. {change}
### 2. {change}
## What our design already does better
## Trade-offs, not gaps
## What not to copy
## The two gaps the industry has not closed
### Subagent cascade
### Retention on an unbounded log
## Open questions for the ADR
```
Loading
Loading