Skip to content

feat(observability): expose immutable runtime snapshots - #105

Merged
divo12 merged 1 commit into
mainfrom
feat/session-snapshot-trace
Aug 16, 2026
Merged

divo12 merged 1 commit into
mainfrom
feat/session-snapshot-trace

Conversation

@divo12

@divo12 divo12 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

User description

Summary

  • expose a typed RunTrace aggregate over the existing JSONL event store
  • make SessionSnapshot transcript, tool-call, tool-input, and metadata collections immutable
  • preserve the existing provider-neutral save/resume codec and engine boundaries

Tests

  • pytest tests/test_runtime_contracts.py tests/test_services/test_session_store.py tests/test_public_api.py (34 passed)
  • mypy src/dream
  • Ruff on changed files
  • full suite: 2,645 passed, 5 skipped; 3 existing research-claw examples require a python executable unavailable in this macOS environment

CodeAnt-AI Description

Expose immutable session snapshots and trace history

What Changed

  • Session snapshots now provide read-only transcripts, tool calls, metadata, and nested JSON values that cannot change after capture
  • Added RunTrace.read() for typed access to a session’s existing trace events, while ignoring truncated final log entries and rejecting events from another session
  • Saved snapshots remain JSON-compatible, and resumed sessions receive mutable copies of metadata
  • Invalid non-object trace attributes are safely read as empty metadata

Impact

✅ Fewer accidental mutations of captured session state
✅ Reliable session-specific trace inspection
✅ Safe resume with independent mutable session metadata

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai

codeant-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed ef29c3d Aug 16, 2026 · 16:10 16:10
✅ Reviewed your PR 86281c9 Aug 09, 2026 · 11:10 11:13

@codeant-ai

codeant-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 9, 2026
Comment thread src/dream/observability/_run_trace.py Outdated
@classmethod
def read(cls, *, session_id: str, path: Path) -> RunTrace:
"""Read a session's trace without introducing another trace store."""
events = tuple(read_events(path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The aggregate is only shallowly immutable: each TraceEvent exposes its original mutable attributes mapping, so callers can mutate trace.events[0].attributes despite the frozen RunTrace contract. Copy and freeze the attribute mappings when constructing the aggregate, or make TraceEvent deeply immutable. [possible bug]

Severity Level: Major ⚠️
- ⚠️ Public `RunTrace` snapshots can change after construction.
- ⚠️ Query and serialization results can observe caller mutations.
- ⚠️ Event data no longer represents a stable point-in-time view.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/dream/observability/_run_trace.py
**Line:** 27:27
**Comment:**
	*Possible Bug: The aggregate is only shallowly immutable: each `TraceEvent` exposes its original mutable `attributes` mapping, so callers can mutate `trace.events[0].attributes` despite the frozen `RunTrace` contract. Copy and freeze the attribute mappings when constructing the aggregate, or make `TraceEvent` deeply immutable.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9df8677. RunTrace now rebuilds each event with recursively frozen JSON value objects; the regression test covers nested objects and arrays.

@greptile-apps

greptile-apps Bot commented Aug 9, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a public RunTrace aggregate over JSONL trace events and changes durable session snapshots to tuple-backed collections. The outer structures are immutable, but nested metadata, tool-input, and trace-attribute values remain mutable.

  • Exposes RunTrace through the observability and top-level APIs.
  • Converts snapshot messages, content blocks, tool calls, tool inputs, and metadata to tuples.
  • Preserves the existing JSON serialization and session restore paths through mapping conversion helpers.

Confidence Score: 3/5

The PR should not merge until snapshots and run traces enforce the immutable point-in-time behavior promised by their public contracts.

Both new read models freeze only their outer containers, allowing nested metadata, tool-input, and trace-attribute collections to be modified after capture.

Files Needing Attention: src/dream/session.py, src/dream/services/session_store.py, src/dream/observability/_run_trace.py

Important Files Changed

Filename Overview
src/dream/services/session_store.py Converts durable record collections to tuples and preserves codec behavior, but shallow conversion leaves nested JSON collections mutable.
src/dream/session.py Builds tuple-backed snapshots while retaining references to nested mutable metadata values.
src/dream/observability/_run_trace.py Adds session-validated trace aggregation, but returned events still expose mutable attribute dictionaries.
src/dream/harness.py Converts tuple-backed snapshot metadata into a fresh dictionary before restoring session options.
tests/test_runtime_contracts.py Covers outer tuple and frozen-dataclass behavior but not nested collection immutability.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Live session metadata and tool inputs] --> B[Session.snapshot]
    B --> C[Tuple-backed SessionSnapshot]
    C --> D[File-store serialization]
    E[JSONL trace file] --> F[read_events]
    F --> G[Tuple-backed RunTrace]
    C -. nested mutable values retained .-> A
    G -. mutable event attributes exposed .-> H[Control-plane consumer]
Loading
Prompt To Fix All With AI
### Issue 1
src/dream/session.py:284
**Nested snapshot values remain mutable**

When metadata or tool input contains a nested list or dictionary, snapshot construction retains that object by reference inside the new tuples, causing a captured snapshot to change after creation and potentially serialize state from after the capture point.

### Issue 2
src/dream/observability/_run_trace.py:27
**Trace attributes remain mutable**

When a consumer modifies `trace.events[n].attributes` or one of its nested collections, `RunTrace.read()` has exposed the parser's mutable dictionary unchanged, causing the contents of the advertised immutable aggregate to change after it was read.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(observability): expose immutable ru..." | Re-trigger Greptile

Comment thread src/dream/session.py Outdated
max_turns=self._effective_max_turns(),
working_dir=self._working_dir(),
metadata=metadata,
metadata=tuple(metadata.items()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Nested snapshot values remain mutable

When metadata or tool input contains a nested list or dictionary, snapshot construction retains that object by reference inside the new tuples, causing a captured snapshot to change after creation and potentially serialize state from after the capture point.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/dream/session.py
Line: 284

Comment:
**Nested snapshot values remain mutable**

When metadata or tool input contains a nested list or dictionary, snapshot construction retains that object by reference inside the new tuples, causing a captured snapshot to change after creation and potentially serialize state from after the capture point.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9df8677. Session metadata and tool inputs are recursively captured as immutable JSON value objects, including persistence round trips.

Comment thread src/dream/observability/_run_trace.py Outdated
@classmethod
def read(cls, *, session_id: str, path: Path) -> RunTrace:
"""Read a session's trace without introducing another trace store."""
events = tuple(read_events(path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Trace attributes remain mutable

When a consumer modifies trace.events[n].attributes or one of its nested collections, RunTrace.read() has exposed the parser's mutable dictionary unchanged, causing the contents of the advertised immutable aggregate to change after it was read.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/dream/observability/_run_trace.py
Line: 27

Comment:
**Trace attributes remain mutable**

When a consumer modifies `trace.events[n].attributes` or one of its nested collections, `RunTrace.read()` has exposed the parser's mutable dictionary unchanged, causing the contents of the advertised immutable aggregate to change after it was read.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9df8677. Trace attributes are recursively frozen on read and covered by a nested-attribute regression test.

Comment thread src/dream/services/session_store.py Outdated
tool_use_id=_require_str(data, "tool_use_id"),
tool_name=_require_str(data, "tool_name"),
input=json_dict_from_mapping(input_raw),
input=tuple(json_dict_from_mapping(input_raw).items()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Schema version 2 snapshots that contain tool-use records written before this change do not have an input field. Because the schema version remains accepted as 2 but decoding now requires input, those previously resumable snapshots are reported as corrupt instead of being migrated or decoded with an empty input. Bump the schema version and provide an explicit migration for existing version-2 files, or preserve backward-compatible decoding. [api mismatch]

Severity Level: Major ⚠️
- ❌ Existing saved sessions containing tool calls cannot resume after deployment.
- ⚠️ `Harness.resume_session()` reports compatible files as corrupt.
- ⚠️ Users may lose access to persisted transcript state.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/dream/services/session_store.py
**Line:** 536:536
**Comment:**
	*Api Mismatch: Schema version 2 snapshots that contain tool-use records written before this change do not have an `input` field. Because the schema version remains accepted as 2 but decoding now requires `input`, those previously resumable snapshots are reported as corrupt instead of being migrated or decoded with an empty input. Bump the schema version and provide an explicit migration for existing version-2 files, or preserve backward-compatible decoding.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@divo12 divo12 Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a schema compatibility regression. The pre-PR schema-v2 writer already serialized the input field for every tool-use and tool-call record, and the pre-PR decoder already required that field through _require_mapping. The PR changes only the in-memory immutable representation; the JSON shape remains unchanged.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

divo12 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Seal session and trace JSON into public typed immutable values so control-plane readers cannot mutate captured execution state.

Co-authored-by: Cursor <cursoragent@cursor.com>
@divo12
divo12 force-pushed the feat/session-snapshot-trace branch from 9df8677 to ef29c3d Compare August 16, 2026 16:10
@codeant-ai

codeant-ai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

divo12 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@codeant-ai codeant-ai Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Aug 16, 2026
@divo12
divo12 merged commit ec59336 into main Aug 16, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant