Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
20db9b1
memory: M0 – atlas-retrieval builds usearch without numkong on Windows
ahammadnafiz Oct 7, 2026
0298bec
memory: M0 – AtlasMemBench agent-switch probes over the tool server
ahammadnafiz Oct 7, 2026
f6dbcf4
memory: M0 – memory_changes pages through every change instead of ski…
ahammadnafiz Oct 7, 2026
218e372
memory: M0 – forgets reach other sessions as tombstones in memory_cha…
ahammadnafiz Oct 7, 2026
995d8cd
memory: M0 – marker-captured lines are candidates, kept out of the br…
ahammadnafiz Oct 7, 2026
7c48333
memory: M0 – redact transcripts before extraction and handoff summari…
ahammadnafiz Oct 7, 2026
b3e1833
memory: M0 – Policy edits resolve the path and accept only agent memo…
ahammadnafiz Oct 7, 2026
f79539f
memory: M0 – capture in emit order, stop when switched off, and extra…
ahammadnafiz Oct 7, 2026
767b708
memory: M0 – clearing shared memory asks for confirmation
ahammadnafiz Oct 7, 2026
a722f6d
memory: M0 – keep the engine's own memories pipeline off
ahammadnafiz Oct 7, 2026
9eea0d8
memory: M0 – demote global facts their repositories forgot; recall by…
ahammadnafiz Oct 7, 2026
35c72e4
memory: M0 – the write contract says what not to save, and memory is …
ahammadnafiz Oct 7, 2026
2bc5948
memory: M0 – extraction over a session that read outside content stor…
ahammadnafiz Oct 7, 2026
e6122fa
memory: M0 – the capture corpus reader finds sessions under the canon…
ahammadnafiz Oct 7, 2026
c11539e
memory: M0 – AtlasMemBench gate at M0 (7 of 17 probes)
ahammadnafiz Oct 7, 2026
d41f726
memory: M1 – record schema v5: immutable revisions in a hash chain, C…
ahammadnafiz Oct 7, 2026
5c4ee78
memory: M1 – corpus index in SQLite with BM25 and a vector file that …
ahammadnafiz Oct 7, 2026
fde9203
memory: M1 – agents see revisions, history, sources, preferences and …
ahammadnafiz Oct 7, 2026
4b87b9b
memory: M1 – ADR-0017 revisions are canonical; everything else is a p…
ahammadnafiz Oct 7, 2026
4e45548
memory: M2 – reconciler checks and repairs, daily snapshot, quarantin…
ahammadnafiz Oct 7, 2026
b101812
memory: M2 – a health pass per project on open, every 30 minutes and …
ahammadnafiz Oct 7, 2026
3d39edb
memory: M2 – AtlasMemBench gate at M2 (the fault-injection suite is M…
ahammadnafiz Oct 7, 2026
a0d1d1d
memory: M3 – memories carry evidence; every read checks it; stale mem…
ahammadnafiz Oct 7, 2026
184b8c3
memory: M4 – feedback, a review queue, and idle consolidation that on…
ahammadnafiz Oct 7, 2026
80971db
memory: M4 – every session leaves a handoff note; the next agent's br…
ahammadnafiz Oct 7, 2026
58454e1
memory: M4 – a Memory updated card per session; writes from a taken-b…
ahammadnafiz Oct 7, 2026
986989d
memory: M4 – memory_why names the recorded sessions behind a file or …
ahammadnafiz Oct 7, 2026
c6e32dc
memory: M4 – a daily dream pass proposes typed memory changes for review
ahammadnafiz Oct 7, 2026
3c71143
memory: M4 – export chosen memories into a managed AGENTS.md block
ahammadnafiz Oct 7, 2026
0a5a3b5
memory: M4 – mirror memory as an Agent Memory Repo and import one as …
ahammadnafiz Oct 7, 2026
a9c7c0b
memory: M4 – Atlas is the only memory in Atlas sessions (setting, def…
ahammadnafiz Oct 7, 2026
ae50501
memory: M4 – agents without HTTP MCP reach memory through a stdio bri…
ahammadnafiz Oct 7, 2026
86ab165
memory: M4 – AtlasMemBench gate at M4 (17 probes)
ahammadnafiz Oct 7, 2026
14c22f4
docs(memory): M0–M4 in CONTEXT.md, architecture.md and the crate READ…
ahammadnafiz Oct 7, 2026
06cce4a
memory: fix review findings before the first build
ahammadnafiz Oct 7, 2026
f18ab7b
memory: fix CI – blake3 finalize under sha2::Digest, preference is an…
ahammadnafiz Oct 7, 2026
b11a986
memory: fix CI – deref the shared store before citing evidence
ahammadnafiz Oct 7, 2026
15cd155
memory: fix CI – redundant clone in the per-line import test
ahammadnafiz Oct 7, 2026
e57d73a
memory: fix review – handoff notes carry only live memory; purge, lin…
ahammadnafiz Oct 7, 2026
0909ce8
memory: fix review – a dream accept is claimed once, guarded by revis…
ahammadnafiz Oct 7, 2026
e10ba9d
memory: fix review – the stdio bridge outlives idle sessions, relays …
ahammadnafiz Oct 7, 2026
5b2d52b
memory: fix review – capture, rewound checks, commit evidence and mem…
ahammadnafiz Oct 7, 2026
b8b4006
memory: fix review – store commands, AGENTS.md export, repo mirror an…
ahammadnafiz Oct 7, 2026
57ec545
memory: fix review – Review errors surface, Memory updated card follo…
ahammadnafiz Oct 7, 2026
f734851
memory: fix review – an imported line keeps its own metadata as untru…
ahammadnafiz Oct 7, 2026
4137d6b
memory: fix review – commit evidence from a worktree nested in the ch…
ahammadnafiz Oct 7, 2026
7aecaa3
memory: fix review – a Memory updated row opens its entry in Shared m…
ahammadnafiz Oct 7, 2026
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
13 changes: 10 additions & 3 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,31 @@ Glossary of domain terms as this project uses them. Decisions with lasting conse

## Shared memory domain

**Shared memory** is the one record every agent on a project reads and writes, native and ACP alike, so that a second agent inherits what the first learned. It is Atlas-owned; no agent's private store is shared memory. It holds exactly six kinds of entry (decided 2026-09-17):
**Shared memory** is the one record every agent on a project reads and writes, native and ACP alike, so that a second agent inherits what the first learned. It is Atlas-owned; no agent's private store is shared memory. It holds exactly seven kinds of entry (six decided 2026-09-17; preference added 2026-10-06):

- **Active plan** — the agent's own structured plan list. One per project; a newer plan replaces the older one, and a plan that is done or abandoned clears it.
- **Decision** — a choice made and why. Keyed; a newer decision with the same key replaces the older one. Shown capped; never evicted.
- **File changed** — one entry per path with a summary of what was done. A repeat edit to the same path replaces the earlier entry. Shown capped; never evicted.
- **Fact** — a durable project fact or convention. Shown capped; never evicted.
- **Failure** — a dead end or anti-pattern, kept so a second agent does not repeat it. Shown capped; never evicted.
- **Architecture** — a structural note about how the system fits together. Shown capped; never evicted.
- **Preference** — how the user wants things done, or a correction with the rule to follow next time. Briefed first, newest first; never ranked by age.

The six kinds have two lifetimes. **Working memory** is Active plan and File changed: it describes the current stretch of work, replaces by key, is always shown to an agent fresh, and never ages or travels beyond the project. **Durable memory** is Decision, Fact, Failure and Architecture: it accumulates, stays true across sessions, is what agents search, and is what can be promoted beyond one project. Both are shared memory.
The six kinds have two lifetimes. **Working memory** is Active plan and File changed: it describes the current stretch of work, replaces by key, is always shown to an agent fresh, and never ages or travels beyond the project. **Durable memory** is Decision, Fact, Failure, Architecture and Preference: it accumulates, stays true across sessions, is what agents search, and is what can be promoted beyond one project. Both are shared memory.

**Scope** of shared memory is the repository: every worktree and every subdirectory launch of one repository shares one memory. Outside a repository the scope is the directory the agent was started in.

Every write to an entry is a **revision**: immutable, sealed into a hash chain, and the canonical record; the entry is the current view of its revisions (ADR-0017). An entry's **state** is **active** (briefed and searched), **candidate** (captured or imported, not confirmed: never briefed until an agent restates it or the user approves it) or **archived** (dismissed, wrong, superseded or expired: kept in history, out of briefings and default search). A keyed replace of another writer's entry needs the **expected revision** it read.

An entry may carry **evidence**: **citations** of code lines, hashed by the server and checked at every read, giving its **validity** — valid, moved, stale or unverifiable (ADR-0018). An uncited decision, fact or architecture note may instead get **commit evidence** from the session recorder: the commits that carried its writing turn's files, stale only when every file lost that work. A stale memory leaves the briefing.

Each finished session leaves a **handoff note**: what it planned and left open, decided, found failing and changed, plus — when the session recorder captured it — what it actually did (files written, failed tool calls, commits, an interrupted last turn). The next agent's briefing carries the newest one from another session. The **review queue** (Memory ▸ Shared ▸ Review) holds candidates, **merge proposals** (near-duplicates), **contradictions** (pairs linked `contradicts`) and the **dream** pass's proposals; nothing in it changes memory until the user acts.

Shared memory also records **session lifecycle** (session start and end, todo added and done, and which agent owns each session). Lifecycle is bookkeeping for the record, never something an agent is shown.

Terms that are *not* shared memory: **capture** (raw transcripts, see Timeline), **knowledge notes** (user-written pages in the Knowledge panel), the **codebase index** (derived from source), and any agent's own memory files (`CLAUDE.md`, `AGENTS.md`, Claude's auto-memory directory). These may be *sources* shared memory cites or imports, but an entry in them is not an entry in shared memory.

Shared memory reaches an agent one way only: it **pulls** it through the **memory tool server**, the in-process MCP server Atlas hands every session that can take it (ADR-0010). Nothing is prepended to a user's message; the text goes to the agent exactly as typed. The server's **instructions** are the protocol — read first, write as you learn — and its tools are, in that order: **`memory_briefing`** (the first look of a session: working memory, the **durable index**, the **curated pack** and the **recent-session handoff**), **`memory_changes`** (what *other* sessions recorded since this session's **last look**), **`memory_search`** (the record plus the codebase index's documents), **`memory_get`**, **`memory_list`**, **`memory_remember`** and **`memory_forget`**.
Shared memory reaches an agent one way only: it **pulls** it through the **memory tool server**, the in-process MCP server Atlas hands every session that can take it (ADR-0010). Nothing is prepended to a user's message; the text goes to the agent exactly as typed. The server's **instructions** are the protocol — read first, write as you learn — and its tools are, in that order: **`memory_briefing`** (the first look of a session: preferences, working memory, the **durable index**, the **curated pack**, the **recent-session handoff** and the **handoff note**), **`memory_changes`** (what *other* sessions recorded since this session's **last look**), **`memory_search`** (the record plus the codebase index's documents), **`memory_get`**, **`memory_list`**, **`memory_history`** (every revision of one entry), **`memory_why`** (the recorded sessions behind a file or a commit), **`memory_remember`**, **`memory_feedback`** (useful, wrong or stale) and **`memory_forget`**. An agent without HTTP MCP reaches the same server through the **stdio bridge**, the Atlas binary run as `atlas mcp-bridge` (ADR-0019).

- The **durable index** is one capped line per durable entry, each kind's best up to its display cap, ranked by recency (two-week half-life), use count and confidence; `memory_get` expands a line. It lists shared-memory entries; it is not the codebase index, and not the retrieval index `memory_search` also searches.
- The **curated pack** is the high-signal part of the project's foreign memory files (Claude's memory directory, `CLAUDE.md`, `AGENTS.md`), recency-ranked and budget-bounded. The **recent-session handoff** is the tail of the most recent other session in the scope, whichever agent ran it, read from capture (or, where capture was never enabled, from Atlas's own session transcripts) — never from an agent's own files.
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions crates/atlas-agent-servers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ pub use debug_log::{
pub use handlers::ClientContext;
pub use host_env::sanitize_host_env;
pub use server::{
env_quirks, env_quirks_from, load_proxy_env, AgentServer, AgentServerDelegate, ConnectOptions,
CustomAgentServer, ExternalAgentServer,
atlas_only_memory, env_quirks, env_quirks_from, load_proxy_env, memory_quirks,
set_atlas_only_memory, AgentServer, AgentServerDelegate, ConnectOptions, CustomAgentServer,
ExternalAgentServer,
};
pub use session::{
AcpSession, CancelProbe, CancelSignal, CancelWaiter, ConfigOptions, SessionDirectories,
Expand Down
84 changes: 83 additions & 1 deletion crates/atlas-agent-servers/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,14 @@ impl AgentServer for CustomAgentServer {
Box::pin(async move {
let mut extra_env = load_proxy_env();
extra_env.extend(env_quirks(&agent_id));
let (memory_env, memory_args) = memory_quirks(&agent_id, atlas_only_memory());
extra_env.extend(memory_env);

let server = delegate
.server
.clone()
.ok_or_else(|| anyhow::anyhow!("no command resolver for agent `{agent_id}`"))?;
let command = server.get_command(Vec::new(), extra_env).await?;
let command = server.get_command(memory_args, extra_env).await?;

let connection = AcpConnection::stdio(
agent_id,
Expand Down Expand Up @@ -249,6 +251,52 @@ pub fn load_proxy_env() -> HashMap<String, String> {
env
}

/// Whether Atlas's shared memory is the only memory in Atlas sessions (the
/// `atlasOnlyMemory` setting, default on). Read when an agent starts, so a
/// change applies to the next session.
static ATLAS_ONLY_MEMORY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);

/// Set from the app's settings at startup and whenever they change.
pub fn set_atlas_only_memory(on: bool) {
ATLAS_ONLY_MEMORY.store(on, std::sync::atomic::Ordering::Relaxed);
}

/// The current `atlasOnlyMemory` setting.
pub fn atlas_only_memory() -> bool {
ATLAS_ONLY_MEMORY.load(std::sync::atomic::Ordering::Relaxed)
}

/// What turns an agent's own memory off when Atlas's is the only one:
/// Claude Code's auto memory by `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1`
/// (code.claude.com/docs/en/memory), Codex's memories by the
/// `memories.use_memories` / `memories.generate_memories` config keys passed
/// as `-c` overrides. Nothing when `atlas_only` is off, or for another agent.
pub fn memory_quirks(
agent_id: &AgentId,
atlas_only: bool,
) -> (HashMap<String, String>, Vec<String>) {
let mut env = HashMap::new();
let mut args = Vec::new();
if atlas_only {
match agent_id.as_str() {
CLAUDE_AGENT_ID => {
env.insert("CLAUDE_CODE_DISABLE_AUTO_MEMORY".to_owned(), "1".to_owned());
}
CODEX_AGENT_ID => {
for key in [
"memories.use_memories=false",
"memories.generate_memories=false",
] {
args.push("-c".to_owned());
args.push(key.to_owned());
}
}
_ => {}
}
}
(env, args)
}

// Agent ids that need an environment workaround. These are NOT a list of agents
// Atlas ships, offers, or knows how to install — an agent reaches this function
// only because the user already installed it and asked to run it. Nothing here
Expand Down Expand Up @@ -301,3 +349,37 @@ pub fn env_quirks_from(

env
}

#[cfg(test)]
mod memory_quirk_tests {
use super::*;

#[test]
fn atlas_only_memory_turns_each_agents_own_memory_off() {
let (env, args) = memory_quirks(&AgentId::new(CLAUDE_AGENT_ID), true);
assert_eq!(
env.get("CLAUDE_CODE_DISABLE_AUTO_MEMORY")
.map(String::as_str),
Some("1")
);
assert!(args.is_empty());
let (env, args) = memory_quirks(&AgentId::new(CODEX_AGENT_ID), true);
assert!(env.is_empty());
assert_eq!(
args,
[
"-c",
"memories.use_memories=false",
"-c",
"memories.generate_memories=false"
]
);
for agent in [CLAUDE_AGENT_ID, CODEX_AGENT_ID, GEMINI_AGENT_ID] {
let (env, args) = memory_quirks(&AgentId::new(agent), false);
assert!(
env.is_empty() && args.is_empty(),
"{agent}: off is today's launch"
);
}
}
}
6 changes: 3 additions & 3 deletions crates/atlas-checkpoint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ pub use import::{
import_all, preview as import_preview, ImportOutcome, ImportPreview, TranscriptSource,
};
pub use model::{
AgentEdit, Binding, Checkpoint, FileTouch, LinkState, Message, Mode, ProjectDetection,
ProjectMode, Role, Session, Source, SyncState, TokenTotals, ToolCall, ToolStatus, TurnMessages,
TurnState, UsageDeltaRow,
AgentEdit, Binding, Checkpoint, FailedCall, FileTouch, LinkState, Message, Mode,
ProjectDetection, ProjectMode, Role, Session, Source, SyncState, TokenTotals, ToolCall,
ToolStatus, TurnMessages, TurnSpan, TurnState, UsageDeltaRow,
};
pub use schema::{REQUIRED_INDEXES, SCHEMA_VERSION};
pub use store::{CheckpointInput, MessageInput, Store};
Expand Down
21 changes: 21 additions & 0 deletions crates/atlas-checkpoint/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,27 @@ impl TurnState {
}
}

/// One turn's lifecycle as recorded: when it started and, once it ended
/// (completed, aborted or rewound), when.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TurnSpan {
pub turn_seq: i64,
pub state: TurnState,
pub started_at: DateTime<Utc>,
pub ended_at: Option<DateTime<Utc>>,
}

/// One failed tool call, as a handoff note reads it: inline payloads only,
/// cut short.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FailedCall {
pub tool_name: crate::tools::ToolName,
pub title: Option<String>,
pub arguments: Option<String>,
pub result: Option<String>,
pub turn_seq: i64,
}

/// Token accounting for a Session.
///
/// Agent-dependent by nature: only the native agent reports a real input/output
Expand Down
132 changes: 132 additions & 0 deletions crates/atlas-checkpoint/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,138 @@ impl Store {
Ok(rows)
}

/// Every turn of this Session with its state and times, oldest first.
pub fn turn_spans(&self, session_id: &str) -> Result<Vec<TurnSpan>> {
let mut stmt = self.conn.prepare(
"SELECT turn_seq, state, started_at, ended_at FROM turn
WHERE session_id = ?1 ORDER BY turn_seq",
)?;
let rows = stmt
.query_map([session_id], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, Option<String>>(3)?,
))
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
Ok(rows
.into_iter()
.map(|(turn_seq, state, started, ended)| TurnSpan {
turn_seq,
// A state we do not know is one this build cannot have
// finished: read it as still open.
state: TurnState::parse(&state).unwrap_or(TurnState::Open),
started_at: parse_time(started),
ended_at: ended.map(parse_time),
})
.collect())
}

/// A Session's failed tool calls, newest first, at most `limit`. Reads
/// only inline text payloads, cut to 400 characters: a spilled payload
/// stays on disk and a binary result is left out. Walks
/// `idx_tool_call_session_seq`.
pub fn failed_tool_calls(&self, session_id: &str, limit: i64) -> Result<Vec<FailedCall>> {
let mut stmt = self.conn.prepare(
"SELECT tool_name, title, substr(arguments, 1, 400),
CASE WHEN result_binary = 0 THEN substr(result, 1, 400) END, turn_seq
FROM tool_call
WHERE session_id = ?1 AND status = 'failed'
ORDER BY seq DESC LIMIT ?2",
)?;
let rows = stmt
.query_map(rusqlite::params![session_id, limit], |row| {
let name: String = row.get(0)?;
Ok(FailedCall {
tool_name: ToolName::parse(&name).unwrap_or(ToolName::Other),
title: row.get(1)?,
arguments: row.get(2)?,
result: row.get(3)?,
turn_seq: row.get(4)?,
})
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
Ok(rows)
}

/// The in-repository paths a Session wrote, newest write first, each
/// once, with whether its last write deleted it. At most `limit`.
pub fn written_paths(&self, session_id: &str, limit: i64) -> Result<Vec<(String, bool)>> {
let mut stmt = self.conn.prepare(
"SELECT f.path, f.deleted FROM file_touch f
WHERE f.session_id = ?1 AND f.out_of_repo = 0
AND f.seq = (SELECT MAX(g.seq) FROM file_touch g
WHERE g.session_id = f.session_id AND g.path = f.path)
ORDER BY f.seq DESC LIMIT ?2",
)?;
let rows = stmt
.query_map(rusqlite::params![session_id, limit], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)? != 0))
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
Ok(rows)
}

/// The Sessions that wrote `path` (project-relative, as stored), with
/// their last write to it, newest first. Uses `idx_file_touch_by_path`.
pub fn sessions_touching_path(
&self,
path: &str,
limit: i64,
) -> Result<Vec<(String, DateTime<Utc>)>> {
let mut stmt = self.conn.prepare(
"SELECT session_id, MAX(created_at) AS last FROM file_touch
WHERE path = ?1 GROUP BY session_id
ORDER BY last DESC, session_id LIMIT ?2",
)?;
let rows = stmt
.query_map(rusqlite::params![path, limit], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
Ok(rows
.into_iter()
.map(|(session, last)| (session, parse_time(last)))
.collect())
}

/// The Checkpoints whose commit carried `path`, newest first. A renamed
/// file appears here under its committed name, which no touch has. No
/// index covers `files_touched`, so this reads every Checkpoint row: one
/// per (Session, commit), small next to the message tables.
pub fn checkpoints_touching_path(&self, path: &str, limit: i64) -> Result<Vec<Checkpoint>> {
let mut stmt = self.conn.prepare(&format!(
"SELECT {CHECKPOINT_COLUMNS} FROM checkpoint c
WHERE EXISTS (SELECT 1 FROM json_each(c.files_touched) WHERE value = ?1)
ORDER BY created_at DESC, id LIMIT ?2"
))?;
let rows = stmt
.query_map(rusqlite::params![path, limit], row_to_checkpoint)?
.collect::<rusqlite::Result<Vec<_>>>()?;
Ok(rows)
}

/// The Checkpoints of every commit whose sha starts with `prefix` (hex,
/// at least 7). A range over `idx_checkpoint_commit`, for a short sha git
/// can no longer resolve.
pub fn checkpoints_for_commit_prefix(&self, prefix: &str) -> Result<Vec<Checkpoint>> {
if prefix.len() < 7 || !prefix.bytes().all(|b| b.is_ascii_hexdigit()) {
return Ok(Vec::new());
}
let low = prefix.to_ascii_lowercase();
let high = format!("{low}g");
let mut stmt = self.conn.prepare(&format!(
"SELECT {CHECKPOINT_COLUMNS} FROM checkpoint
WHERE commit_sha >= ?1 AND commit_sha < ?2 ORDER BY created_at DESC, id"
))?;
let rows = stmt
.query_map(rusqlite::params![low, high], row_to_checkpoint)?
.collect::<rusqlite::Result<Vec<_>>>()?;
Ok(rows)
}

/// The highest turn number this Session has ever used.
///
/// Seeds the in-memory counter after a restart, so a resumed conversation
Expand Down
Loading
Loading