Skip to content

feat(harness): add session_store — durable session history and run ledger - #90

Merged
senamakel merged 5 commits into
mainfrom
agent-sessions-to-tinyagents
Aug 8, 2026
Merged

feat(harness): add session_store — durable session history and run ledger#90
senamakel merged 5 commits into
mainfrom
agent-sessions-to-tinyagents

Conversation

@senamakel

@senamakel senamakel commented Aug 8, 2026

Copy link
Copy Markdown
Member

What

Adds harness::session_store — the SQLite-backed (WAL + FTS5) history layer for agent
sessions: sessions, messages, tool calls, cost metadata, parent/child lineage, plus a
run_ledger for background agent/workflow execution state.

Ported from OpenHuman's agent/session_db, which was generic runtime machinery sitting in
a host. Its only coupling to that host was reading workspace_dir off a Config, so entry
points now take &Path and the crate derives {workspace}/session_db/sessions.db itself.
No host type crosses the boundary.

Why this belongs here, and what deliberately does not

This is history, not durability — nothing resumes from it. Resume stays with
graph::checkpoint; live runtime data stays with harness::store. This answers
"what happened", supports cross-session search, and lets a host recover orchestration
state after a restart.

A companion audit found two neighbouring bodies of code that look like they belong here
and do not. Recording them so this is not re-litigated:

  • OpenHuman's session_import reads legacy OpenHuman formats (session_raw/,
    DDMMYYYY folders, legacy Markdown) and writes them into this crate's stores. It is a
    host-side adapter that already consumes harness::store; moving it would put a host's
    legacy directory layout inside the generic runtime.
  • OpenHuman's transcript.rs is a durable on-disk format with .md rendering. Upstreaming
    it would make a live user-data format public API of this crate. That is a crate-roadmap
    decision, not a cleanup, and it is not proposed here.

Port notes

  • anyhow is not a dependency here, so error handling funnels through a new
    TinyAgentsError::Storage variant. session_store/context.rs provides a
    StorageContext trait deliberately mirroring anyhow::Context's shape — including the
    Option impl for "row expected but absent" — which kept the conversion mechanical and
    reviewable.
  • From<rusqlite::Error> is added under the sqlite feature so driver calls can use ?.
  • chrono gains serde: ledger records carry DateTime<Utc> across the serde boundary.
  • FTS5 needs no cargo feature — bundled already compiles it in, and there is no
    fts5 feature at rusqlite 0.40 (adding one does not resolve). Noted in Cargo.toml so
    it is not "fixed" later.
  • Gated behind the existing sqlite feature, alongside the graph checkpointer.

Semver note for reviewers

TinyAgentsError is a public non-#[non_exhaustive] enum, so the new Storage variant is
a minor-breaking change for any downstream matching it exhaustively. Flagging it explicitly
rather than letting it ride in silently.

Verification

  • cargo test --features sqlite --lib1415 passed, 0 failed (34 are the moved suite,
    which came across intact, plus 4 new for the context shim).
  • cargo clippy --all-targets --all-features -- -D warnings — clean.
  • cargo fmt --check — clean.

The four wide record_* signatures carry a scoped #[allow(clippy::too_many_arguments)]
with a reason. Grouping them into a record struct is worth doing and is an API change
rather than part of a move, so it is left as follow-up.


Merge order — this PR goes FIRST

Head of a three-repo chain. Nothing blocks it; it depends on no other PR.

  1. this PR
  2. tinyhumansai/openhuman#5447 (rewrites 29 call sites onto this crate, then repoints
    vendor/tinyagents at this PR's merge SHA) →
  3. tinyhumansai/workflow-openhuman#6 (advances the openhuman/ gitlink)

After this merges, do not delete the agent-sessions-to-tinyagents branch until #5447
has repointed its submodule. That gitlink currently references a commit on this branch, and
deleting it first would leave #5447 pointing at an unreachable object.

Summary by CodeRabbit

  • New Features

    • Added durable SQLite-backed session history with message and tool-call recording.
    • Added full-text session search, filtering, pagination, child-session queries, and interruption handling.
    • Added a run ledger for agent and workflow runs, events, telemetry, checkpoints, and status tracking.
    • Added team coordination capabilities, including task dependencies, claims, completion evidence, and member lifecycle management.
    • Added structured storage errors and public session/run-ledger APIs.
  • Documentation

    • Added documentation covering session storage, search, run tracking, and operational behavior.
  • Tests

    • Added comprehensive coverage for session, search, persistence, coordination, telemetry, and run lifecycle behavior.

…dger

Adds `harness::session_store`, the SQLite-backed (WAL + FTS5) history layer
for sessions, messages, tool calls, cost metadata, and parent/child lineage,
plus `run_ledger` for background agent/workflow execution state.

Ported from OpenHuman's `agent/session_db`, which was generic runtime
machinery sitting in a host: its only coupling to that host was reading
`workspace_dir` off a `Config`. Entry points now take `&Path` directly, so the
crate derives `{workspace}/session_db/sessions.db` itself and no host type
crosses the boundary.

Port notes:

- `anyhow` is not a dependency here, so error handling funnels through a new
  `TinyAgentsError::Storage` variant. `context.rs` provides a `StorageContext`
  trait mirroring `anyhow::Context`'s shape (including the `Option` impl for
  "row expected but absent"), which kept the conversion mechanical.
- `From<rusqlite::Error>` is added under the `sqlite` feature so driver calls
  can use `?` directly; it is the only new blanket conversion.
- `chrono` gains the `serde` feature — ledger records carry `DateTime<Utc>`
  across the serde boundary. FTS5 needs no cargo feature; `bundled` already
  compiles it in.
- Gated behind `sqlite`, alongside the existing graph checkpointer.

This module is history, not durability: nothing resumes from it. Resume stays
with `graph::checkpoint`, and live runtime data stays with `harness::store`.

34 tests move across intact and pass.

Co-authored-by: Medulla <medulla@tinyhumans.ai>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 482bc61b-f3be-44a9-b3b2-118b013bc4bd

📥 Commits

Reviewing files that changed from the base of the PR and between 8ace86e and 54b188b.

📒 Files selected for processing (3)
  • src/session/ops.rs
  • src/session/run_ledger/ops.rs
  • src/session/test.rs
📝 Walkthrough

Walkthrough

Added a SQLite-gated session module and durable run ledger. The change includes schemas, typed APIs, recording and search operations, run and team coordination workflows, contextual storage errors, documentation, and extensive tests.

Changes

SQLite persistence

Layer / File(s) Summary
Public contracts and module wiring
Cargo.toml, src/error.rs, src/lib.rs, src/session/mod.rs, src/session/types.rs, src/session/run_ledger/mod.rs, src/session/run_ledger/types.rs
Added SQLite-gated session and run-ledger APIs, serializable data types, status and outcome enums, storage errors, and dependency configuration.
Session database and operations
src/session/store.rs, src/session/context.rs, src/session/ops.rs, src/session/test.rs, src/session/README.md
Added SQLite schema setup, transactions, session recording, retrieval, FTS5 search, truncation, interruption handling, contextual errors, documentation, and tests.
Run ledger persistence and recovery
src/session/run_ledger/store.rs, src/session/run_ledger/ops.rs, src/session/run_ledger/test.rs
Added agent and workflow run persistence, event sequencing, telemetry updates, run queries, status transitions, orphan recovery, and tests.
Team coordination and task lifecycle
src/session/run_ledger/store.rs, src/session/run_ledger/ops.rs, src/session/run_ledger/test.rs
Added team, member, and task persistence with dependency-aware claims, evidence-gated completion, member state transitions, task release, and tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Session caller
  participant Ops as session::ops
  participant Store as session::store
  participant SQLite as SQLite and FTS5
  Caller->>Ops: record or search session data
  Ops->>Store: open connection or transaction
  Store->>SQLite: persist records and indexes
  SQLite-->>Ops: return rows and search matches
  Ops-->>Caller: return typed session results
Loading
sequenceDiagram
  participant Member as Team member
  participant Ledger as run_ledger::ops
  participant SQLite as SQLite transaction
  participant Task as Agent team task
  Member->>Ledger: claim task
  Ledger->>SQLite: validate dependencies and guarded claim
  SQLite-->>Ledger: return claim outcome
  Member->>Ledger: submit completion evidence
  Ledger->>SQLite: validate ownership and quality gates
  SQLite->>Task: persist completion or failure state
  Ledger-->>Member: return completion outcome
Loading

Poem

I’m a rabbit with records tucked neat,
SQLite keeps each session complete.
Runs hop in line,
Claims guard the sign,
And tests make the ledger fleet.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the addition of durable session history and the run ledger.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b4478dd441

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/session/run_ledger/ops.rs
Comment thread src/session/ops.rs Outdated
Comment thread src/session/run_ledger/ops.rs Outdated
Comment thread src/harness/session_store/run_ledger/ops.rs Outdated
Comment thread src/harness/mod.rs Outdated
`harness::session_store` becomes `session`. Session history is a persistence
domain in its own right, not part of the agent loop: nothing in `harness` reads
from it, and a host can use it without running a harness at all — indexing
sessions produced elsewhere, or recovering orchestration state at boot before
any agent exists. Filing it under `harness::` implied a dependency that does not
exist in either direction.

Sits alongside the crate's other top-level domains (`graph`, `registry`, `repl`,
`rlm`) and keeps its `sqlite` feature gate, which simply moves from
`harness/mod.rs` to `lib.rs`.

Pure rename: no behaviour, no schema, no on-disk change. Public path is now
`tinyagents::session::*`.

1415 tests pass, clippy clean under -D warnings, fmt clean.

Co-authored-by: Medulla <medulla@tinyhumans.ai>

@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.

Your trial has ended. Reactivate Greptile to resume code reviews.

senamakel added a commit to senamakel/openhuman that referenced this pull request Aug 8, 2026
… module

Path-only update across 27 files: tinyagents::harness::session_store::* becomes
tinyagents::session::*. No behaviour change; the DB path and the session_db /
run_ledger RPC namespaces are untouched.

Tracks tinyhumansai/tinyagents#90.

Co-authored-by: Medulla <medulla@tinyhumans.ai>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2233c02b9b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib.rs
Comment thread src/session/ops.rs Outdated
Comment thread src/session/ops.rs
Comment thread src/session/run_ledger/ops.rs
Comment thread src/session/run_ledger/ops.rs Outdated
…ports

All four code findings are pre-existing defects carried over from OpenHuman,
not introduced by the move. Each fix is pinned by a regression test, and the
first two were mutation-checked (revert the fix, the test fails; restore it,
it passes).

P1 — partial telemetry upsert failed outright. The counters are `Option` so a
caller can update one field alone, but the columns are `NOT NULL DEFAULT` and
SQLite does not apply a column default to an explicitly supplied NULL. So
recording only `model` or only `error` hit a NOT NULL constraint on a run's
first write. The insert side now coalesces to the column default; the update
side re-reads the SAME parameter and coalesces to the stored value, keeping
per-field optionality. `excluded.*` cannot serve the update side — it observes
the already-coalesced insert row, so a `None` would read as 0 and clobber the
stored counter.

P1 — run-event sequences raced. `SELECT MAX(sequence) + 1` followed by a
separate INSERT is read-modify-write: two connections appending for the same
run read the same value and the loser fails the `(run_id, sequence)` primary
key, silently dropping a real event. Allocation now happens inside the INSERT
via a sub-select with RETURNING, so SQLite's write lock serializes it.

P2 (severity understated — it is a panic on ordinary user data) — the FTS
snippet sliced at a raw byte offset. `&content[..2000]` panics whenever byte
2000 lands inside a multi-byte character, which any long non-ASCII message can
do. Worse than a crash: the message INSERT has already autocommitted, so the
row survives with no FTS entry and is permanently unsearchable. Now walks back
to a character boundary, as the tool-output path already did.

P2 — team task claim/completion had no isolation. `with_connection` hands out
an autocommit connection, so the documented compare-and-swap contract did not
hold across statements: a dependency could flip out of `done` between the check
and the claim. Added `session::store::with_transaction` (BEGIN IMMEDIATE,
commit on Ok, best-effort rollback on Err) and routed both coordination
operations through it. Immediate rather than deferred so racing claims
serialize at BEGIN instead of failing at COMMIT after one has already decided
it won.

P1 — public surface was reachable only through the module path. AGENTS.md
requires exports centralized in `lib.rs`; added feature-gated root re-exports
for the record/query entry points and the ledger's coordination types.

One test caught itself: the UTF-8 regression first used a 2-byte character,
where byte 2000 is a boundary, so it passed vacuously. Switched to a 3-byte
character so the offset genuinely lands mid-character.

1420 tests pass, clippy clean under -D warnings, fmt clean.

Co-authored-by: Medulla <medulla@tinyhumans.ai>

@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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 081e0e18f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/session/ops.rs Outdated
Comment thread src/session/run_ledger/ops.rs Outdated
P2 — plain-text search hit the FTS5 parser. `SessionSearchParams::query` is
documented as plain text, but was bound straight to `MATCH`, so ordinary input
(`C++`, `foo-bar`, `file.rs`, a stray quote) returned a syntax or `no such
column` error instead of results. Each whitespace-separated term is now emitted
as a quoted FTS5 string literal (quotes escaped by doubling) joined with AND,
so punctuation is data and multi-term queries stay conjunctive.

P2 — an upsert could strand a claimed task. A claim only means anything while
the task is `in_progress`, but editing a claimed task through the upsert left
`claimed_by_member_id`/`claim_token` set on a `todo` row: a new claim saw
AlreadyClaimed, completion saw NotClaimed, and release/shutdown skipped it
because they only match `in_progress`. The claim is now cleared whenever the
new status is not `in_progress`, and preserved when it is, so an unrelated edit
cannot steal a live claim.

P2 — a failed completion gate discarded submitted evidence. Evidence
accumulates across attempts, so dropping it punished the caller for an
unrelated gate failure: after fixing a dependency, a retry that did not resend
the same links failed `require_evidence` on evidence already submitted. The
failed-gate update now persists the merged evidence alongside the verdict.

P1 — test layout. AGENTS.md requires module-local tests in a dedicated
`test.rs` per module directory; this module had `ops_tests.rs` plus inline
`mod tests` blocks in four files, which would have established a conflicting
convention in a large new feature. Consolidated into `session/test.rs` and
`session/run_ledger/test.rs`, with the helpers they reach widened to
`pub(super)`. All 62 session tests survive the move.

P1 — missing module README. AGENTS.md requires one for complex modules.
Added `src/session/README.md` covering the schema, the FTS behaviour, the
coordination guarantees, and the operational constraints — each of the
non-obvious rules there is pinned by a named test.

1424 tests pass, clippy clean under -D warnings, fmt clean.

Co-authored-by: Medulla <medulla@tinyhumans.ai>

@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.

Your trial has ended. Reactivate Greptile to resume code reviews.

…tdown

P1 — `record_tool_call` returned the wrong id. `index_fts_tool` inserts into
the `sessions_fts` virtual table, which moves `last_insert_rowid()`, and the
id was read after that call — so callers got an FTS rowid addressing no tool
call at all. Any real session already has its session row in `sessions_fts`,
so the two counters diverge immediately. `record_message` already ordered these
correctly; this path did not. The id is now captured directly after the insert.

P2 — `shutdown_agent_team_member` documented one transaction but ran on the
autocommit helper, so its read and two updates had no isolation: a task
completed between the released-ID query and the release update was still
reported as released, and a failure on the member update could leave tasks
released while the member stayed active, even as the call returned an error.
Routed through `with_transaction`, which the claim and completion paths already
use.

The regression test asserts its own premise first — that the FTS insert really
does move `last_insert_rowid()` — so it cannot pass vacuously if the ordering
is ever reintroduced.

1425 tests pass, clippy clean under -D warnings, fmt clean.

Co-authored-by: Medulla <medulla@tinyhumans.ai>

@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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54b188b8e2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1035 to +1037
// 2. Only the current claimant may complete, and only while in progress.
let is_claimant = task.claimed_by_member_id.as_deref() == Some(member_id);
let in_progress = task.status == AgentTeamTaskStatus::InProgress;

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 Badge Validate the claim token before completing

When the same member releases and reclaims a task with a new token, a late completion from the previous worker still passes this check because completion validates only member_id. It can therefore mark the new attempt done and persist stale evidence. Accept and compare the stored claim_token so only the worker holding the current claim can complete the task.

Useful? React with 👍 / 👎.

Comment on lines +968 to +970
"UPDATE agent_team_tasks
SET claimed_by_member_id = ?1, claim_token = ?2, status = 'in_progress', updated_at = ?3
WHERE id = ?4 AND team_id = ?5 AND claimed_by_member_id IS NULL",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict claims to claimable task statuses

When an unclaimed task is already done—for example after inserting or upserting it with that status—this update still matches and changes it back to in_progress, so an ordinary claim call can undo durable completion. Add a status predicate that permits only claimable states, at minimum excluding terminal done tasks.

Useful? React with 👍 / 👎.

Comment thread src/session/ops.rs
Comment on lines +575 to +578
raw.split_whitespace()
.map(|term| format!("\"{}\"", term.replace('"', "\"\"")))
.collect::<Vec<_>>()
.join(" AND ")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match query terms across all rows in a session

When search terms occur in different messages of the same session, joining them with FTS AND returns no result because each message is indexed as a separate sessions_fts row and MATCH requires all terms in one row. For example, a session with alpha in one message and beta in another is omitted from an alpha beta search; intersect matching session_id sets per term or index one aggregate FTS document per session.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 14

🧹 Nitpick comments (15)
src/session/run_ledger/types.rs (3)

464-464: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

gate_status is stringly typed while every sibling status is an enum.

AgentTeamTask uses AgentTeamTaskStatus for status but a bare String for gate_status. src/session/README.md line 101 documents gate_status = "failed" as a defined value, so the set is closed. An untyped field lets a typo persist to storage and forces every consumer to compare raw strings.

Introduce an AgentTeamGateStatus enum with the same as_str/parse pair the other status enums use.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/run_ledger/types.rs` at line 464, Replace
AgentTeamTask.gate_status’s String type with a new AgentTeamGateStatus enum,
following the existing status-enum patterns and implementing the same as_str and
parse methods. Preserve the documented closed set of gate-status values,
including "failed", and update any serialization or parsing usage in the
surrounding types to use the enum.

514-515: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

ClaimOutcome and CompletionOutcome derive Serialize but not Deserialize.

Both enums are re-exported at the crate root (src/lib.rs lines 93-98). A host can send an outcome over a wire protocol but cannot read one back. Every other public record type in this file derives both traits. Add Deserialize unless the one-way shape is deliberate.

♻️ Proposed fix
-#[derive(Debug, Clone, PartialEq, Serialize)]
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase", tag = "kind")]
 pub enum ClaimOutcome {
-#[derive(Debug, Clone, PartialEq, Serialize)]
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase", tag = "kind")]
 pub enum CompletionOutcome {

Also applies to: 534-535

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/run_ledger/types.rs` around lines 514 - 515, Update the public
ClaimOutcome and CompletionOutcome enum definitions to derive Deserialize
alongside Serialize, preserving their existing serde rename and tag
configuration and matching the other public record types in the module.

253-256: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Use one integer width for shared pagination fields.

AgentRunListRequest and RunEventListRequest use Option<u32>, while WorkflowRunListRequest and AgentTeamListRequest use Option<u64>. This requires casts when callers share pagination values. Remove the unsupported TypeSchema::U64 rationale and document the crate-local pagination limits and conversions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/run_ledger/types.rs` around lines 253 - 256, Standardize the
pagination fields in AgentRunListRequest, RunEventListRequest,
WorkflowRunListRequest, and AgentTeamListRequest on Option<u32> so callers can
share values without casts. Remove the TypeSchema::U64 rationale and document
the crate-local pagination limits and any required conversions at the shared
pagination definitions.
src/session/ops.rs (3)

183-195: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the char-boundary truncation into one helper.

record_tool_call and index_fts_content implement the same backward scan to a UTF-8 boundary with different byte caps. src/session/test.rs lines 234-244 copies the logic a third time to predict the expected value, so the test cannot catch a divergence in the production code. One helper removes all three copies.

♻️ Proposed helper
+/// Truncates `s` to at most `max_bytes`, cutting on a UTF-8 character
+/// boundary. Slicing at a raw byte offset panics on multi-byte input.
+pub(super) fn truncate_on_char_boundary(s: &str, max_bytes: usize) -> &str {
+    if s.len() <= max_bytes {
+        return s;
+    }
+    let mut cutoff = max_bytes;
+    while cutoff > 0 && !s.is_char_boundary(cutoff) {
+        cutoff -= 1;
+    }
+    &s[..cutoff]
+}
     let bounded_output = tool_output.map(|o| {
-        if o.len() <= MAX_TOOL_OUTPUT_BYTES {
-            o.to_string()
-        } else {
-            let mut cutoff = MAX_TOOL_OUTPUT_BYTES;
-            while cutoff > 0 && !o.is_char_boundary(cutoff) {
-                cutoff -= 1;
-            }
-            let mut truncated = o[..cutoff].to_string();
-            truncated.push_str("\n...[truncated]");
-            truncated
-        }
+        let bounded = truncate_on_char_boundary(o, MAX_TOOL_OUTPUT_BYTES);
+        if bounded.len() == o.len() {
+            o.to_string()
+        } else {
+            format!("{bounded}\n...[truncated]")
+        }
     });
-    let snippet = if content.len() > MAX_FTS_SNIPPET_BYTES {
-        let mut cutoff = MAX_FTS_SNIPPET_BYTES;
-        while cutoff > 0 && !content.is_char_boundary(cutoff) {
-            cutoff -= 1;
-        }
-        &content[..cutoff]
-    } else {
-        content
-    };
+    let snippet = truncate_on_char_boundary(content, MAX_FTS_SNIPPET_BYTES);

Also applies to: 584-592

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/ops.rs` around lines 183 - 195, Extract the shared UTF-8-safe
truncation logic into a helper, such as near the existing session utilities, and
have record_tool_call, index_fts_content, and the corresponding
src/session/test.rs expectation use it with their respective byte caps. Preserve
the current behavior for untruncated values, boundary-safe slicing, and the
"\n...[truncated]" suffix.

390-396: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

The COUNT and the page SELECT run outside one transaction.

search_sessions_inner and list_sessions (lines 280-286) issue a COUNT(*) and then a separate SELECT. with_connection is autocommit, so a concurrent writer can commit between the two statements. The returned total then disagrees with the returned page.

If the callers accept an approximate total, state that in the doc comment. If they do not, route both statements through with_transaction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/ops.rs` around lines 390 - 396, The COUNT and page SELECT in
search_sessions_inner and list_sessions must share one transaction to keep total
and returned rows consistent under concurrent writes. Route both statements
through with_transaction using the existing connection and preserve the current
query results and parameter handling; otherwise explicitly document that total
is approximate if transactional consistency is not intended.

222-241: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

get_session reports a missing session as a storage error.

A session that does not exist is an ordinary query result, not a database failure. TinyAgentsError::Storage is documented in src/error.rs lines 225-233 as an operation failure of the backing database. A caller must now match on the message text to tell "no such session" from "database is locked".

get_workflow_run in src/session/run_ledger/ops.rs returns Result<Option<_>> and lets the caller decide (line 149). Align get_session with that shape, or add a distinct not-found signal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/ops.rs` around lines 222 - 241, Update get_session to distinguish
an absent session from database failures by returning an optional session
result, matching get_workflow_run’s Result<Option<_>> contract, and return None
when rows.next() yields no record. Preserve query and row-mapping errors as
errors, and update affected callers to handle the optional result without
matching Storage message text.
src/session/context.rs (1)

20-29: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider a lazy context argument.

storage_context takes &str. Call sites that need interpolation must build the string before the call, so format! runs on the success path too. Example: src/session/store.rs lines 33-36 and line 40 allocate on every with_connection call. A second method that takes a closure keeps the common &str form and removes the allocation from the hot path.

♻️ Proposed addition
 pub(crate) trait StorageContext<T> {
     /// Wraps the failure as a storage error prefixed with `context`.
     fn storage_context(self, context: &str) -> Result<T>;
+
+    /// Wraps the failure as a storage error prefixed with the lazily built
+    /// `context`, so the message is only formatted on the error path.
+    fn storage_context_with<C: Display>(self, context: impl FnOnce() -> C) -> Result<T>;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/context.rs` around lines 20 - 29, Update the StorageContext trait
and its Result implementation to add a lazy context variant accepting a closure
that produces the context string, while retaining storage_context(&str) for
static messages. Use the lazy method at interpolated with_connection call sites
in session store code so formatting occurs only when the Result is an error.
src/session/run_ledger/mod.rs (1)

12-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the run-ledger submodules private, as src/session/mod.rs does.

ops, store, and types are declared pub and their items are also re-exported below. Downstream users then have two paths to every item, for example run_ledger::upsert_agent_run and run_ledger::ops::upsert_agent_run. The sibling module keeps ops and store private (src/session/mod.rs lines 67-71) and exposes one path.

♻️ Proposed fix
-pub mod ops;
-pub mod store;
-pub mod types;
+mod ops;
+mod store;
+pub mod types;

Note: src/session/run_ledger/ops.rs line 113 calls crate::session::store::with_connection, and src/session/mod.rs re-exports with_connection, so verify the visibility change compiles for internal callers of run_ledger::store.

As per coding guidelines: "Keep public API exports centralized in src/lib.rs so downstream users have a predictable surface."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/run_ledger/mod.rs` around lines 12 - 14, Make the ops, store, and
types module declarations in run_ledger private, matching the visibility pattern
in session/mod.rs, while preserving the existing item re-exports as the sole
public access path. Update internal references such as ops’s with_connection
call to use the appropriate crate-visible or session-level re-export so
compilation remains intact, and keep public API exposure centralized through the
existing exports.

Source: Coding guidelines

src/session/types.rs (1)

14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Take self by value in as_str.

SessionStatus is Copy. Every status enum in src/session/run_ledger/types.rs declares pub fn as_str(self). Match that signature here for consistency.

♻️ Proposed fix
-    pub fn as_str(&self) -> &'static str {
+    pub fn as_str(self) -> &'static str {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/types.rs` at line 14, Update SessionStatus::as_str to take self
by value instead of borrowing &self, matching the existing as_str signatures for
Copy status enums in src/session/run_ledger/types.rs while preserving its
current return behavior.
src/session/mod.rs (1)

77-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Re-export rusqlite or restrict the helpers to crate visibility.

The sqlite feature gates these public helpers, but their callback types still expose rusqlite::Connection without a crate re-export. Callers must add a compatible direct rusqlite dependency to name the type, which couples this API to rusqlite version changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/mod.rs` at line 77, Update the public helper exports around
db_path, with_connection, and with_transaction so their rusqlite::Connection
callback types are usable without requiring callers to depend directly on
rusqlite: either publicly re-export the compatible rusqlite API from the session
module or reduce these helpers and their signatures to crate visibility. Keep
the sqlite feature gating intact and apply the chosen visibility/API change
consistently.
src/session/run_ledger/ops.rs (3)

1463-1488: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

map_agent_run_row runs one extra telemetry query per row.

Line 1483 calls get_optional_run_telemetry, which prepares and executes a statement for every mapped row. list_agent_runs (Line 410) maps up to 500 rows per page, so one list call can issue up to 501 queries. Fetch the telemetry columns with a LEFT JOIN run_telemetry in the two agent-run queries and map them from the same row. Keep the per-row helper only for the single-row get_agent_run_inner path if the join complicates it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/run_ledger/ops.rs` around lines 1463 - 1488, Eliminate the
per-row telemetry query from list operations by adding a LEFT JOIN to
run_telemetry in both agent-run list queries and selecting the telemetry columns
in their result sets. Update map_agent_run_row to read telemetry directly from
the joined row, while retaining get_optional_run_telemetry only for the
single-row get_agent_run_inner path if needed. Ensure the list mapping still
handles missing telemetry as optional.

99-101: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Read the run back inside the same connection.

Line 101 opens a second connection through get_agent_run. The write closure already has a connection and the schema is initialized. Reading inside the closure removes one file open per upsert and closes the window in which another writer changes the row between the write and the read. The same pattern applies to upsert_workflow_run (Line 149), upsert_agent_team (Line 594), upsert_agent_team_member (Line 734), and upsert_agent_team_task (Line 859).

♻️ Proposed change for `upsert_agent_run`
-        .storage_context("upsert agent run")?;
-        Ok(())
-    })?;
-
-    get_agent_run(workspace_dir, &upsert.id)?.storage_context("agent run missing after upsert")
+        .storage_context("upsert agent run")?;
+        get_agent_run_inner(conn, &upsert.id)?.storage_context("agent run missing after upsert")
+    })
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/run_ledger/ops.rs` around lines 99 - 101, Update upsert_agent_run
and the corresponding upsert_workflow_run, upsert_agent_team,
upsert_agent_team_member, and upsert_agent_team_task functions to read the
persisted row within their existing write-connection closures. Replace the
post-closure get_agent_run-style lookup with the closure’s connection query,
preserving the existing returned model and missing-row error behavior.

184-190: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Return the original payload instead of re-parsing the serialized string.

Line 188 parses payload_json back into a Value and falls back to {} on failure. event.payload is still owned here. Move it into the result. This removes one parse per append and removes a silent fallback that can return a payload that differs from the stored row.

♻️ Proposed change
         Ok(RunEvent {
             run_id: event.run_id,
             sequence: next_sequence as u64,
             event_type: event.event_type,
-            payload: serde_json::from_str(&payload_json).unwrap_or_else(|_| json!({})),
+            payload: event.payload,
             timestamp: now,
         })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/run_ledger/ops.rs` around lines 184 - 190, Update the RunEvent
construction in the append operation to assign the original owned event.payload
directly to payload instead of parsing payload_json with serde_json::from_str
and falling back to an empty object. Preserve the remaining fields unchanged.
src/session/run_ledger/test.rs (2)

174-207: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a concurrent append case for the sequence allocator.

This test appends events from one thread, so it proves density and per-run independence. It does not exercise the race that the implementation comment in src/session/run_ledger/ops.rs (Lines 159-165) describes. Spawn several threads that append to the same run_id in the same workspace, then assert that the returned sequences are unique and form a contiguous range. That case fails on a read-then-write allocator and passes on the current single-statement allocator.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/run_ledger/test.rs` around lines 174 - 207, Add a concurrent
append scenario to run_event_sequences_are_allocated_by_the_insert: clone or
otherwise share the workspace and spawn several threads appending events with
the same run_id, collect their returned sequence values, then assert the values
are unique and equal to the contiguous range from 1 through the number of
appends. Retain the existing sequential density and per-run independence
assertions.

507-547: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for completion, shutdown, and claiming a done task.

This file exercises claim, release, and member transitions, but three operations in the layer have no test.

  • complete_agent_team_task: no case covers Completed, GateFailed (unmet dependency, owner mismatch, missing evidence under require_evidence), or NotClaimed. This function holds the gate logic and two compare-and-swap guards.
  • shutdown_agent_team_member: no case covers the bulk release of in_progress tasks or the returned released ids.
  • Claiming a done task: this test marks task-a done at Lines 525-543, which clears the claim on that row. A follow-up claim on task-a demonstrates the missing status guard raised on src/session/run_ledger/ops.rs Lines 964-979. Add that case together with the guard.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/run_ledger/test.rs` around lines 507 - 547, Expand run-ledger
tests to cover complete_agent_team_task outcomes: Completed, GateFailed for
unmet dependencies, owner mismatch, and missing evidence when require_evidence
is enabled, plus NotClaimed and both compare-and-swap guards. Add
shutdown_agent_team_member coverage asserting in-progress tasks are released and
returned released IDs are correct. Extend claim_blocked_until_dependency_done to
attempt claiming task-a after it is marked done, and add the status guard in
claim_agent_team_task rejecting done tasks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Cargo.toml`:
- Around line 48-51: Update the three comments in Cargo.toml that reference
harness::session_store to use the promoted top-level session module path,
session::session_store, while preserving the existing bundled/FTS5 rationale and
wording otherwise.

In `@src/session/mod.rs`:
- Around line 64-65: Update the module-level documentation link near the session
schema and FTS coordination text so it resolves in rendered rustdoc: either
point it to the repository-hosted README file or inline the README via the
module’s documentation attribute using include_str!("README.md").

In `@src/session/ops.rs`:
- Around line 42-66: Use with_transaction instead of with_connection in
record_session_start, record_message, and record_tool_call so each base-row
insert and its corresponding index_fts_session, index_fts_content, or
index_fts_tool call commit atomically; preserve the existing SQL, parameters,
error context, and callback logic.

In `@src/session/README.md`:
- Around line 93-97: Update the README paragraph describing coordination
operations and BEGIN IMMEDIATE to reflect the current SQLITE_BUSY behavior when
no busy timeout is configured, or revise it after the store’s busy-timeout
handling is added; do not claim racing claims wait and serialize unless the
implementation guarantees that behavior.
- Around line 36-47: Update the session README statements to match the actual
API and schema: clarify that only the functions re-exported by src/lib.rs are
available at the crate root, while list_messages, list_tool_calls,
list_children, mark_interrupted, with_connection, and with_transaction remain
under session::. Correct the table-count description to state that there are ten
non-FTS tables plus one FTS5 virtual table, matching the schema table.

In `@src/session/run_ledger/ops.rs`:
- Around line 391-392: Update the offset conversion in the surrounding
request-handling function to use i64::try_from(request.offset.unwrap_or(0))
instead of an as i64 cast, and propagate the conversion failure with the same
storage context and error-handling pattern used by list_workflow_runs and
list_agent_teams. Keep the existing default offset behavior unchanged.
- Around line 793-795: Update the upsert handling around gate_status and the
conflict clause to avoid pairing a reset status with a stale gate_reason. When
upsert.gate_status is None, preserve the existing stored gate_status (or
alternatively clear gate_reason whenever the status becomes pending), while
retaining the current behavior for explicitly supplied statuses.
- Around line 1183-1235: Update shutdown_agent_team_member to use
with_transaction instead of with_connection, keeping the existence check,
released-ID query, task release, member update, and final member read within one
transaction. Preserve the existing transaction error propagation and returned
(member, released) result.
- Around line 964-979: Update the compare-and-swap in claim_agent_team_task to
require the task status is eligible for claiming, preventing done tasks from
being changed back to in_progress. Before executing the update, distinguish a
done task and return the appropriate non-claim outcome instead of reporting
AlreadyClaimed; preserve the existing AlreadyClaimed behavior for tasks blocked
by an active claimant.

In `@src/session/store.rs`:
- Around line 27-44: Update with_connection so schema initialization is
performed once per database path instead of on every operation. Add a
synchronized per-path initialization guard, such as
OnceLock<Mutex<HashSet<PathBuf>>> or an equivalent user_version check, and call
init_schema only when the current database has not been initialized; preserve
connection creation and callback behavior.
- Around line 55-65: Configure a nonzero busy timeout immediately after opening
each SQLite connection in with_connection, using the established timeout value
so concurrent BEGIN IMMEDIATE calls wait for the write lock. Update
src/session/README.md lines 93-97 to retain the serialization claim only with
this configured timeout and explicitly name its value; the store.rs
documentation should likewise reflect the timeout-backed behavior.

In `@src/session/test.rs`:
- Around line 228-263: Update the tests tool_output_truncation,
mark_interrupted_updates_running, and session_end_updates_cost_fields to
exercise the public session APIs instead of duplicating their SQL or
transformation logic. Use a TempDir workspace root and invoke record_tool_call,
mark_interrupted, and record_session_end respectively, following the setup
pattern in run_ledger tests; retain assertions on the resulting persisted values
and statuses.
- Around line 508-517: Update wal_mode_is_set to use a temporary file-backed
database instead of with_memory_connection, then initialize it through
init_schema before querying PRAGMA journal_mode. Assert that the reported mode
is "wal" and retain the test’s existing error propagation and cleanup behavior.

In `@src/session/types.rs`:
- Around line 33-34: Apply #[serde(rename_all = "camelCase")] consistently to
the public session types SessionRecord, SessionMessage, SessionToolCall, and
SessionSearchResult, matching SessionSearchParams and the run-ledger types.
Ensure their serialized field names use camelCase without changing the Rust
field names or other behavior.

---

Nitpick comments:
In `@src/session/context.rs`:
- Around line 20-29: Update the StorageContext trait and its Result
implementation to add a lazy context variant accepting a closure that produces
the context string, while retaining storage_context(&str) for static messages.
Use the lazy method at interpolated with_connection call sites in session store
code so formatting occurs only when the Result is an error.

In `@src/session/mod.rs`:
- Line 77: Update the public helper exports around db_path, with_connection, and
with_transaction so their rusqlite::Connection callback types are usable without
requiring callers to depend directly on rusqlite: either publicly re-export the
compatible rusqlite API from the session module or reduce these helpers and
their signatures to crate visibility. Keep the sqlite feature gating intact and
apply the chosen visibility/API change consistently.

In `@src/session/ops.rs`:
- Around line 183-195: Extract the shared UTF-8-safe truncation logic into a
helper, such as near the existing session utilities, and have record_tool_call,
index_fts_content, and the corresponding src/session/test.rs expectation use it
with their respective byte caps. Preserve the current behavior for untruncated
values, boundary-safe slicing, and the "\n...[truncated]" suffix.
- Around line 390-396: The COUNT and page SELECT in search_sessions_inner and
list_sessions must share one transaction to keep total and returned rows
consistent under concurrent writes. Route both statements through
with_transaction using the existing connection and preserve the current query
results and parameter handling; otherwise explicitly document that total is
approximate if transactional consistency is not intended.
- Around line 222-241: Update get_session to distinguish an absent session from
database failures by returning an optional session result, matching
get_workflow_run’s Result<Option<_>> contract, and return None when rows.next()
yields no record. Preserve query and row-mapping errors as errors, and update
affected callers to handle the optional result without matching Storage message
text.

In `@src/session/run_ledger/mod.rs`:
- Around line 12-14: Make the ops, store, and types module declarations in
run_ledger private, matching the visibility pattern in session/mod.rs, while
preserving the existing item re-exports as the sole public access path. Update
internal references such as ops’s with_connection call to use the appropriate
crate-visible or session-level re-export so compilation remains intact, and keep
public API exposure centralized through the existing exports.

In `@src/session/run_ledger/ops.rs`:
- Around line 1463-1488: Eliminate the per-row telemetry query from list
operations by adding a LEFT JOIN to run_telemetry in both agent-run list queries
and selecting the telemetry columns in their result sets. Update
map_agent_run_row to read telemetry directly from the joined row, while
retaining get_optional_run_telemetry only for the single-row get_agent_run_inner
path if needed. Ensure the list mapping still handles missing telemetry as
optional.
- Around line 99-101: Update upsert_agent_run and the corresponding
upsert_workflow_run, upsert_agent_team, upsert_agent_team_member, and
upsert_agent_team_task functions to read the persisted row within their existing
write-connection closures. Replace the post-closure get_agent_run-style lookup
with the closure’s connection query, preserving the existing returned model and
missing-row error behavior.
- Around line 184-190: Update the RunEvent construction in the append operation
to assign the original owned event.payload directly to payload instead of
parsing payload_json with serde_json::from_str and falling back to an empty
object. Preserve the remaining fields unchanged.

In `@src/session/run_ledger/test.rs`:
- Around line 174-207: Add a concurrent append scenario to
run_event_sequences_are_allocated_by_the_insert: clone or otherwise share the
workspace and spawn several threads appending events with the same run_id,
collect their returned sequence values, then assert the values are unique and
equal to the contiguous range from 1 through the number of appends. Retain the
existing sequential density and per-run independence assertions.
- Around line 507-547: Expand run-ledger tests to cover complete_agent_team_task
outcomes: Completed, GateFailed for unmet dependencies, owner mismatch, and
missing evidence when require_evidence is enabled, plus NotClaimed and both
compare-and-swap guards. Add shutdown_agent_team_member coverage asserting
in-progress tasks are released and returned released IDs are correct. Extend
claim_blocked_until_dependency_done to attempt claiming task-a after it is
marked done, and add the status guard in claim_agent_team_task rejecting done
tasks.

In `@src/session/run_ledger/types.rs`:
- Line 464: Replace AgentTeamTask.gate_status’s String type with a new
AgentTeamGateStatus enum, following the existing status-enum patterns and
implementing the same as_str and parse methods. Preserve the documented closed
set of gate-status values, including "failed", and update any serialization or
parsing usage in the surrounding types to use the enum.
- Around line 514-515: Update the public ClaimOutcome and CompletionOutcome enum
definitions to derive Deserialize alongside Serialize, preserving their existing
serde rename and tag configuration and matching the other public record types in
the module.
- Around line 253-256: Standardize the pagination fields in AgentRunListRequest,
RunEventListRequest, WorkflowRunListRequest, and AgentTeamListRequest on
Option<u32> so callers can share values without casts. Remove the
TypeSchema::U64 rationale and document the crate-local pagination limits and any
required conversions at the shared pagination definitions.

In `@src/session/types.rs`:
- Line 14: Update SessionStatus::as_str to take self by value instead of
borrowing &self, matching the existing as_str signatures for Copy status enums
in src/session/run_ledger/types.rs while preserving its current return behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2d5fd136-02b5-4124-a5c2-2e23e4d26674

📥 Commits

Reviewing files that changed from the base of the PR and between 3e1dbea and 8ace86e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • Cargo.toml
  • src/error.rs
  • src/lib.rs
  • src/session/README.md
  • src/session/context.rs
  • src/session/mod.rs
  • src/session/ops.rs
  • src/session/run_ledger/mod.rs
  • src/session/run_ledger/ops.rs
  • src/session/run_ledger/store.rs
  • src/session/run_ledger/test.rs
  • src/session/run_ledger/types.rs
  • src/session/store.rs
  • src/session/test.rs
  • src/session/types.rs

Comment thread Cargo.toml
Comment on lines +48 to +51
# `bundled` compiles SQLite with FTS5 already enabled, which
# `harness::session_store` relies on for its `sessions_fts` cross-session
# search table. There is no separate `fts5` cargo feature at this version — do
# not add one, it does not resolve.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the stale module path in these comments.

The three new comments name harness::session_store. The PR promotes that API to the top-level session module (src/lib.rs line 85, src/session/mod.rs). Rename the references so the manifest rationale matches the shipped module path.

📝 Proposed comment updates
 # `bundled` compiles SQLite with FTS5 already enabled, which
-# `harness::session_store` relies on for its `sessions_fts` cross-session
+# `session` relies on for its `sessions_fts` cross-session
 # search table. There is no separate `fts5` cargo feature at this version — do
 # not add one, it does not resolve.
-# `serde` is required by `harness::session_store`, whose records carry
+# `serde` is required by `session`, whose records carry
 # `DateTime<Utc>` timestamps across the serde boundary.
-# Throwaway workspace roots for `harness::session_store` tests, which exercise
+# Throwaway workspace roots for `session` tests, which exercise
 # the real on-disk SQLite path rather than an in-memory database.

Also applies to: 61-62, 84-85

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` around lines 48 - 51, Update the three comments in Cargo.toml
that reference harness::session_store to use the promoted top-level session
module path, session::session_store, while preserving the existing bundled/FTS5
rationale and wording otherwise.

Comment thread src/session/mod.rs
Comment on lines +64 to +65
//! See [`README.md`](./README.md) for the schema, the FTS behaviour, and the
//! coordination guarantees.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The relative README link does not resolve in rendered docs.

rustdoc does not copy src/session/README.md into the generated output. On docs.rs this link returns 404. Point the link at the repository file, or inline the relevant content with #![doc = include_str!("README.md")].

📝 Proposed fix
-//! See [`README.md`](./README.md) for the schema, the FTS behaviour, and the
-//! coordination guarantees.
+//! See `src/session/README.md` in the repository for the schema, the FTS
+//! behaviour, and the coordination guarantees.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//! See [`README.md`](./README.md) for the schema, the FTS behaviour, and the
//! coordination guarantees.
//! See `src/session/README.md` in the repository for the schema, the FTS
//! behaviour, and the coordination guarantees.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/mod.rs` around lines 64 - 65, Update the module-level
documentation link near the session schema and FTS coordination text so it
resolves in rendered rustdoc: either point it to the repository-hosted README
file or inline the README via the module’s documentation attribute using
include_str!("README.md").

Comment thread src/session/ops.rs
Comment on lines +42 to +66
with_connection(workspace_dir, |conn| {
conn.execute(
"INSERT INTO sessions (
id, agent_definition_id, agent_definition_name, session_key,
parent_session_id, thread_id, source_channel, status, model,
transcript_path, started_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, 'running', ?8, ?9, ?10)",
params![
id,
agent_definition_id,
agent_definition_name,
session_key,
parent_session_id,
thread_id,
source_channel,
model,
transcript_path,
now.to_rfc3339(),
],
)
.storage_context("failed to insert session")?;

index_fts_session(conn, id, agent_definition_name)?;
Ok(())
})?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The row insert and its FTS index write are not atomic.

All three recording functions use with_connection, which is autocommit. Each conn.execute commits on its own. If index_fts_session, index_fts_content, or index_fts_tool fails, the sessions, session_messages, or session_tool_calls row is already committed and stays permanently absent from sessions_fts. The comment at lines 578-583 states this exact failure mode for the panic case, but the same gap remains for any FTS insert error, for example SQLITE_BUSY or a disk error.

with_transaction exists for this (src/session/store.rs line 59). Use it so the row and its index entry commit together.

🐛 Proposed fix for `record_message`; apply the same change to `record_session_start` and `record_tool_call`
-    with_connection(workspace_dir, |conn| {
+    with_transaction(workspace_dir, |conn| {
         conn.execute(
             "INSERT INTO session_messages (

Also applies to: 137-161, 197-219

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/ops.rs` around lines 42 - 66, Use with_transaction instead of
with_connection in record_session_start, record_message, and record_tool_call so
each base-row insert and its corresponding index_fts_session, index_fts_content,
or index_fts_tool call commit atomically; preserve the existing SQL, parameters,
error context, and callback logic.

Comment thread src/session/README.md
Comment on lines +36 to +47
Re-exported from the crate root (see `src/lib.rs`); the full surface stays
reachable under `session::` and `session::run_ledger::`.

- **Recording** — `record_session_start`, `record_message`, `record_tool_call`,
`record_session_end`
- **Querying** — `get_session`, `list_sessions`, `search_sessions`,
`list_messages`, `list_tool_calls`, `list_children`
- **Recovery** — `mark_interrupted`
- **Run ledger** — agent runs, workflow runs, teams, members, tasks, run events,
and telemetry, with the claim/completion coordination primitives
- **Connections** — `with_connection` (autocommit) and `with_transaction`
(`BEGIN IMMEDIATE`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Two statements in this section do not match the code.

  1. Line 36 says the listed surface is "Re-exported from the crate root". src/lib.rs lines 100-104 re-export only get_session, list_sessions, record_message, record_session_end, record_session_start, record_tool_call, and search_sessions. list_messages, list_tool_calls, list_children, mark_interrupted, with_connection, and with_transaction are reachable only under session::.
  2. Line 51 says "Six tables plus one FTS5 virtual table". The table below lists ten non-FTS tables: sessions, session_messages, session_tool_calls, agent_runs, workflow_runs, run_events, run_telemetry, agent_teams, agent_team_members, and agent_team_tasks.
📝 Proposed fixes
-Re-exported from the crate root (see `src/lib.rs`); the full surface stays
-reachable under `session::` and `session::run_ledger::`.
+The recording and querying entry points and the run-ledger types are re-exported
+from the crate root (see `src/lib.rs`). The rest of the surface, marked below
+with †, is reachable only under `session::` and `session::run_ledger::`.
-Six tables plus one FTS5 virtual table, created on demand and idempotently:
+Ten tables plus one FTS5 virtual table, created on demand and idempotently:

Also applies to: 51-61

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/README.md` around lines 36 - 47, Update the session README
statements to match the actual API and schema: clarify that only the functions
re-exported by src/lib.rs are available at the crate root, while list_messages,
list_tool_calls, list_children, mark_interrupted, with_connection, and
with_transaction remain under session::. Correct the table-count description to
state that there are ten non-FTS tables plus one FTS5 virtual table, matching
the schema table.

Comment thread src/session/README.md
Comment on lines +93 to +97
**Coordination operations need `with_transaction`, not `with_connection`.**
`with_connection` is autocommit, which gives ordering but no isolation. Claim
and completion read state and then act on it, so they take the write lock up
front with `BEGIN IMMEDIATE` — racing claims serialize at `BEGIN` rather than
failing at `COMMIT` after one has already decided it won.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

This guarantee does not hold without a busy timeout.

The text states that racing claims "serialize at BEGIN rather than failing at COMMIT". src/session/store.rs sets no busy_timeout, so the second BEGIN IMMEDIATE returns SQLITE_BUSY immediately instead of waiting. See the comment on src/session/store.rs lines 55-65. Update this paragraph after that fix lands, or state the current behaviour.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/README.md` around lines 93 - 97, Update the README paragraph
describing coordination operations and BEGIN IMMEDIATE to reflect the current
SQLITE_BUSY behavior when no busy timeout is configured, or revise it after the
store’s busy-timeout handling is added; do not claim racing claims wait and
serialize unless the implementation guarantees that behavior.

Comment thread src/session/store.rs
Comment on lines +27 to +44
pub fn with_connection<T>(
workspace_dir: &Path,
f: impl FnOnce(&Connection) -> Result<T>,
) -> Result<T> {
let db_path = db_path(workspace_dir);
if let Some(parent) = db_path.parent() {
std::fs::create_dir_all(parent).storage_context(&format!(
"failed to create session_db directory: {}",
parent.display()
))?;
}

let conn = Connection::open(&db_path)
.storage_context(&format!("failed to open session DB: {}", db_path.display()))?;

init_schema(&conn)?;
f(&conn)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

init_schema runs on every operation.

with_connection opens a connection, calls create_dir_all, and then executes the full DDL batch for each call. record_message (src/session/ops.rs line 137) uses with_connection, so a run that records one message per turn re-parses and re-executes roughly twenty DDL statements plus two PRAGMA statements each time. src/session/run_ledger/ops.rs line 114 then calls init_run_ledger_schema on top of that.

The DDL is idempotent, so this is a cost concern, not a correctness one. Consider running the schema once per database path, for example with a std::sync::OnceLock<Mutex<HashSet<PathBuf>>> of initialized paths, or by checking PRAGMA user_version and skipping the batch when the version already matches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/store.rs` around lines 27 - 44, Update with_connection so schema
initialization is performed once per database path instead of on every
operation. Add a synchronized per-path initialization guard, such as
OnceLock<Mutex<HashSet<PathBuf>>> or an equivalent user_version check, and call
init_schema only when the current database has not been initialized; preserve
connection creation and callback behavior.

Comment thread src/session/store.rs
Comment on lines +55 to +65
/// `BEGIN IMMEDIATE` rather than the default deferred begin: it takes the
/// write lock up front, so two racing claims serialize at `BEGIN` instead of
/// discovering the conflict at COMMIT time and failing with `SQLITE_BUSY`
/// after one of them has already decided it won.
pub fn with_transaction<T>(
workspace_dir: &Path,
f: impl FnOnce(&Connection) -> Result<T>,
) -> Result<T> {
with_connection(workspace_dir, |conn| {
conn.execute_batch("BEGIN IMMEDIATE")
.storage_context("begin session DB transaction")?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

No busy timeout is configured, so BEGIN IMMEDIATE does not serialize concurrent writers. SQLite's default busy timeout is 0 and with_connection opens a new connection per call. A second BEGIN IMMEDIATE therefore returns SQLITE_BUSY immediately instead of waiting for the write lock. Both the code comment and the README state the opposite guarantee.

  • src/session/store.rs#L55-L65: call conn.busy_timeout(...) in with_connection right after Connection::open, so a racing writer waits for the lock instead of failing.
  • src/session/README.md#L93-L97: keep the serialization claim only after the busy timeout is set, and name the configured timeout value.
📍 Affects 2 files
  • src/session/store.rs#L55-L65 (this comment)
  • src/session/README.md#L93-L97
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/store.rs` around lines 55 - 65, Configure a nonzero busy timeout
immediately after opening each SQLite connection in with_connection, using the
established timeout value so concurrent BEGIN IMMEDIATE calls wait for the write
lock. Update src/session/README.md lines 93-97 to retain the serialization claim
only with this configured timeout and explicitly name its value; the store.rs
documentation should likewise reflect the timeout-backed behavior.

Comment thread src/session/test.rs
Comment on lines +228 to +263
fn tool_output_truncation() {
with_memory_connection(|conn| {
let session_id = "trunc-sess";
insert_test_session(conn, session_id, "agent", "key");

let large_output = "x".repeat(MAX_TOOL_OUTPUT_BYTES + 1000);
let bounded = if large_output.len() <= MAX_TOOL_OUTPUT_BYTES {
large_output.clone()
} else {
let mut cutoff = MAX_TOOL_OUTPUT_BYTES;
while cutoff > 0 && !large_output.is_char_boundary(cutoff) {
cutoff -= 1;
}
let mut truncated = large_output[..cutoff].to_string();
truncated.push_str("\n...[truncated]");
truncated
};

conn.execute(
"INSERT INTO session_tool_calls (session_id, tool_name, tool_output, status, created_at)
VALUES (?1, 'test', ?2, 'ok', ?3)",
params![session_id, bounded, Utc::now().to_rfc3339()],
)?;

let stored: String = conn.query_row(
"SELECT tool_output FROM session_tool_calls WHERE session_id = ?1",
params![session_id],
|r| r.get(0),
)?;
assert!(stored.len() <= MAX_TOOL_OUTPUT_BYTES + 20);
assert!(stored.ends_with("[truncated]"));

Ok(())
})
.unwrap();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

These three tests do not call the functions they claim to cover.

  • tool_output_truncation copies the truncation logic from src/session/ops.rs lines 183-195, inserts the result, and asserts on its own computation. If record_tool_call changes its cap or drops the marker, this test still passes.
  • mark_interrupted_updates_running runs the UPDATE statement directly instead of calling mark_interrupted.
  • session_end_updates_cost_fields runs the UPDATE statement directly instead of calling record_session_end.

tempfile is already a dev-dependency (Cargo.toml line 86). Use a TempDir workspace root and call the public functions, as src/session/run_ledger/test.rs does.

💚 Sketch for `mark_interrupted_updates_running`
 #[test]
 fn mark_interrupted_updates_running() {
-    with_memory_connection(|conn| {
-        insert_test_session(conn, "run1", "agent", "key1");
-        ...
-        let changed = conn.execute(
-            "UPDATE sessions SET status = 'interrupted', ended_at = ?1
-             WHERE status = 'running'",
-            params![now.to_rfc3339()],
-        )?;
-        assert_eq!(changed, 1);
+    let dir = tempfile::TempDir::new().unwrap();
+    let workspace = dir.path();
+
+    record_session_start(
+        workspace, "run1", "agent", "agent", "key1", None, None, None, None, None,
+    )
+    .unwrap();
+    record_session_start(
+        workspace, "run2", "agent", "agent", "key2", None, None, None, None, None,
+    )
+    .unwrap();
+    record_session_end(
+        workspace, "run2", SessionStatus::Completed, 1, 0, 0, 0, 0.0,
+    )
+    .unwrap();
+
+    assert_eq!(mark_interrupted(workspace).unwrap(), 1);
+    assert_eq!(
+        get_session(workspace, "run1").unwrap().status,
+        SessionStatus::Interrupted
+    );
+    assert_eq!(
+        get_session(workspace, "run2").unwrap().status,
+        SessionStatus::Completed
+    );
 }

Also applies to: 266-298, 301-335

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/test.rs` around lines 228 - 263, Update the tests
tool_output_truncation, mark_interrupted_updates_running, and
session_end_updates_cost_fields to exercise the public session APIs instead of
duplicating their SQL or transformation logic. Use a TempDir workspace root and
invoke record_tool_call, mark_interrupted, and record_session_end respectively,
following the setup pattern in run_ledger tests; retain assertions on the
resulting persisted values and statuses.

Comment thread src/session/test.rs
Comment on lines +508 to +517
#[test]
fn wal_mode_is_set() {
with_memory_connection(|conn| {
let mode: String = conn.query_row("PRAGMA journal_mode", [], |r| r.get(0))?;
// In-memory DBs may report "memory" instead of "wal"
assert!(mode == "wal" || mode == "memory");
Ok(())
})
.unwrap();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

wal_mode_is_set never checks WAL.

with_memory_connection opens :memory:. SQLite always reports memory for that database, so the mode == "wal" branch is unreachable and the assertion always passes on the wrong value. Open a TempDir-backed database to test the pragma that init_schema sets.

💚 Proposed fix
 #[test]
 fn wal_mode_is_set() {
-    with_memory_connection(|conn| {
-        let mode: String = conn.query_row("PRAGMA journal_mode", [], |r| r.get(0))?;
-        // In-memory DBs may report "memory" instead of "wal"
-        assert!(mode == "wal" || mode == "memory");
-        Ok(())
-    })
-    .unwrap();
+    let dir = tempfile::TempDir::new().unwrap();
+    super::store::with_connection(dir.path(), |conn| {
+        let mode: String = conn.query_row("PRAGMA journal_mode", [], |r| r.get(0))?;
+        assert_eq!(mode, "wal");
+        Ok(())
+    })
+    .unwrap();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[test]
fn wal_mode_is_set() {
with_memory_connection(|conn| {
let mode: String = conn.query_row("PRAGMA journal_mode", [], |r| r.get(0))?;
// In-memory DBs may report "memory" instead of "wal"
assert!(mode == "wal" || mode == "memory");
Ok(())
})
.unwrap();
}
#[test]
fn wal_mode_is_set() {
let dir = tempfile::TempDir::new().unwrap();
super::store::with_connection(dir.path(), |conn| {
let mode: String = conn.query_row("PRAGMA journal_mode", [], |r| r.get(0))?;
assert_eq!(mode, "wal");
Ok(())
})
.unwrap();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/test.rs` around lines 508 - 517, Update wal_mode_is_set to use a
temporary file-backed database instead of with_memory_connection, then
initialize it through init_schema before querying PRAGMA journal_mode. Assert
that the reported mode is "wal" and retain the test’s existing error propagation
and cleanup behavior.

Comment thread src/session/types.rs
Comment on lines +33 to +34
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionRecord {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Serde casing is inconsistent across the public session types.

SessionSearchParams uses #[serde(rename_all = "camelCase")]. SessionRecord, SessionMessage, SessionToolCall, and SessionSearchResult use the default snake_case. Every run-ledger record type in src/session/run_ledger/types.rs uses camelCase. A JSON client therefore sends camelCase parameters and receives snake_case records.

Field names are a public serialization contract. Fix the convention now; changing it after release breaks consumers.

♻️ Proposed alignment on camelCase
 #[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
 pub struct SessionRecord {
 #[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
 pub struct SessionMessage {
 #[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
 pub struct SessionToolCall {

Also applies to: 80-82, 103-104

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session/types.rs` around lines 33 - 34, Apply #[serde(rename_all =
"camelCase")] consistently to the public session types SessionRecord,
SessionMessage, SessionToolCall, and SessionSearchResult, matching
SessionSearchParams and the run-ledger types. Ensure their serialized field
names use camelCase without changing the Rust field names or other behavior.

@senamakel
senamakel merged commit 107a515 into main Aug 8, 2026
3 checks passed
senamakel added a commit to senamakel/openhuman that referenced this pull request Aug 8, 2026
…mmit

tinyhumansai/tinyagents#90 merged as 107a515. The gitlink referenced a branch
commit (2233c02) that predated the review fixes; it now points at merged main.

Co-authored-by: Medulla <medulla@tinyhumans.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant