Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
7f17f18
Test isolated native Mastra memory replay
strickvl Sep 23, 2026
b04612b
Add versioned Mastra memory snapshots
strickvl Sep 23, 2026
fa0bba8
Reject mutations during initial memory capture
strickvl Sep 23, 2026
2517ace
Restore historical Mastra memory in isolated stores
strickvl Sep 23, 2026
e2b93bb
Capture effective Mastra provider requests
strickvl Sep 23, 2026
bdfd2b9
Pin file and skill content for Mastra replay
strickvl Sep 23, 2026
b7bc1d2
Link memory changes to provider attempts
strickvl Sep 23, 2026
68cc513
Enable isolated Mastra memory stream replay
strickvl Sep 23, 2026
c8c0e12
Simplify memory source and tool hook setup
strickvl Sep 23, 2026
96d7b1f
Identify extra context in memory request evidence
strickvl Sep 23, 2026
e56e22c
Accept SDK job objects in MCP activity pages
strickvl Sep 23, 2026
1f6e19e
Prove authenticated headless Mastra memory replay
strickvl Sep 23, 2026
a17ec54
Document and package isolated memory replay
strickvl Sep 23, 2026
b1b86d5
Stop replay after native memory storage failure
strickvl Sep 23, 2026
2a61a29
Format replay documentation examples
strickvl Sep 23, 2026
898bd52
Guard memory replay context and cleanup
strickvl Sep 23, 2026
23038f1
Isolate the optional Mastra memory entrypoint
strickvl Sep 23, 2026
e15a11d
Harden Mastra memory replay
strickvl Sep 24, 2026
5acefb3
Fix devtools import lint on Linux
strickvl Sep 24, 2026
81a614b
Guard shared Mastra resource replay
strickvl Sep 24, 2026
0f9527e
Read ISO-string memory dates at capture
strickvl Sep 24, 2026
f512fdf
Match source store semantics in memory replay
strickvl Sep 24, 2026
59f6039
Test Mastra memory replay on `@mastra/pg`
strickvl Sep 24, 2026
b6051ac
Keep tape models out of production OM records
strickvl Sep 24, 2026
1be648f
Scope Mastra lease poison to the turns it affects
strickvl Sep 24, 2026
4a25031
Honor context selectors and release before uploads
strickvl Sep 24, 2026
978d26d
Tolerate OM call drift in Mastra memory replay
strickvl Sep 24, 2026
e1e0e59
Put Mastra evidence on the replay input budget
strickvl Sep 24, 2026
5bf90d5
Redact URL credentials in Mastra memory replay
strickvl Sep 24, 2026
05d7937
Keep Kitaru latency out of Mastra baseline turns
strickvl Sep 24, 2026
9b7f248
Keep Mastra replay requests identical to baseline
strickvl Sep 24, 2026
0bda0bb
Let pending Mastra sessions close as ineligible
strickvl Sep 24, 2026
07412ad
Report refused Mastra baselines per experiment replay
strickvl Sep 24, 2026
7c8263e
Keep completed runs when finalization is rejected
strickvl Sep 24, 2026
5628daf
Let `RunRecorder` complete incomplete recordings
strickvl Sep 24, 2026
afc7092
Name why Mastra memory turns are not replayable
strickvl Sep 24, 2026
2c8a745
Document Mastra memory replay server and limits
strickvl Sep 24, 2026
5a69ff7
Share repeated Mastra replay helpers
strickvl Sep 24, 2026
5b0e663
Stop Mastra baselines waiting for OM buffering
strickvl Sep 24, 2026
acbcc37
Close Mastra turns ended by processor tripwires
strickvl Sep 24, 2026
5c0731a
Release the Mastra lease before attempt uploads
strickvl Sep 24, 2026
cee803a
Redact escaped and re-encoded URL credentials
strickvl Sep 24, 2026
d4b80ca
Refuse Mastra history with undeclared file URLs
strickvl Sep 24, 2026
96eb262
Refuse outdated Mastra v3 replay inputs up front
strickvl Sep 24, 2026
a1b6207
Keep Mastra OM replay results in their window
strickvl Sep 24, 2026
4f26411
Record Mastra memory tool results on replay budget
strickvl Sep 24, 2026
e3b98eb
Reuse recorded attachment token counts in replay
strickvl Sep 24, 2026
8f21552
Fetch undeclared `resolveFile` URLs in baselines
strickvl Sep 25, 2026
1f12518
Capture Mastra history attachment URLs implicitly
strickvl Sep 25, 2026
c6bd763
Capture Mastra history files on `resolveFile`
strickvl Sep 25, 2026
fcb9e7f
Store Mastra replay files as Kitaru blobs
strickvl Sep 25, 2026
011290e
Keep a finalizing Mastra turn eligible on quick reply
strickvl Sep 25, 2026
c8aa73e
Let Mastra replay answer missing OM results live
strickvl Sep 25, 2026
68798a3
Report the result session error on agent failure
strickvl Sep 25, 2026
b22a52e
Tag replay OM settle timeout `om_settle_timeout`
strickvl Sep 25, 2026
648b1ca
Align Mastra memory replay docs with fixes
strickvl Sep 25, 2026
870df8e
Fence late Mastra writes from released turns
strickvl Sep 25, 2026
3489280
Keep Mastra OM replay from downloading files
strickvl Sep 25, 2026
364d7f4
Refuse Mastra replays whose file blobs are gone
strickvl Sep 25, 2026
b5cc7d2
Record live Mastra OM results without re-encoding
strickvl Sep 25, 2026
0744f47
Skip Mastra buffered OM calls with no recording
strickvl Sep 25, 2026
1fbd222
Reuse Mastra token counts for inline attachments
strickvl Sep 25, 2026
a498c51
Name captured Mastra files in request evidence
strickvl Sep 25, 2026
f62f8d1
Name the reason in failed Mastra baseline errors
strickvl Sep 25, 2026
3aa2fa7
Note new Mastra replay reasons in the changelog
strickvl Sep 25, 2026
6581dc3
Reference captured files in Mastra inline history
strickvl Sep 25, 2026
1d71ad2
Name mismatched Mastra OM calls in replay spans
strickvl Sep 25, 2026
12400ad
Ignore unsent Mastra attachments in `file_url_sent_to_model`
strickvl Sep 25, 2026
c0c273e
Let Mastra quick replies follow a finalizing reply
strickvl Sep 25, 2026
9b5e643
Close diverged Mastra replays with the call named
strickvl Sep 25, 2026
68337ff
Reference Mastra inline files before snapshot copy
strickvl Sep 25, 2026
b01894a
Let Mastra reply writes follow their own lease
strickvl Sep 25, 2026
173f213
Capture Mastra input files on `resolveFile`
strickvl Sep 25, 2026
652874f
Refuse Mastra input URLs `resolveFile` missed
strickvl Sep 25, 2026
29ed985
Refuse Mastra history URLs `resolveFile` missed
strickvl Sep 25, 2026
49d8dcb
Wait for the finalizing mark in quick-reply test
strickvl Sep 25, 2026
cbf9003
Say captured Mastra inputs record a file reference
strickvl Sep 25, 2026
fe18e23
Use neutral record fixtures in Mastra replay tests
strickvl Sep 25, 2026
164f50e
Merge remote-tracking branch 'origin/develop' into feat/issue-1176-ad…
strickvl Sep 25, 2026
aac4183
Refuse Mastra writes outside the captured selector
strickvl Sep 25, 2026
ca98ec3
Refuse compound credential keys like `access_token`
strickvl Sep 25, 2026
753cb6e
Detect in-place Mastra request context edits
strickvl Sep 25, 2026
da3d5fc
Refuse malformed Mastra `keyOrder` on the server
strickvl Sep 25, 2026
f12e976
Send recorded media type in live OM file parts
strickvl Sep 25, 2026
f12a6ba
Drop provider messages from Mastra error text
strickvl Sep 25, 2026
9d75c9c
Grant recorded-file blobs only to replay tasks
strickvl Sep 25, 2026
4a297d2
Check inline Mastra file size before decoding
strickvl Sep 25, 2026
94cadf8
Document extras the Mastra replay check needs
strickvl Sep 25, 2026
db8d294
Check replay model before `recordedInputProjector`
strickvl Sep 25, 2026
092762d
Check blob file references at Mastra finalization
strickvl Sep 25, 2026
af86765
Refuse standalone replays at the final check
strickvl Sep 25, 2026
407c45e
Let lease answers win over a stalled bound
strickvl Sep 25, 2026
eccc720
Make the shared file lease test helper synchronous
strickvl Sep 25, 2026
9ba04e6
Drop the substring credential scan of request context
strickvl Sep 25, 2026
46538b9
Require the exact `toISOString` form for turn starts
strickvl Sep 25, 2026
9b17316
Check each `omTape` entry before replay starts
strickvl Sep 25, 2026
93fea7a
Match credential keys with a format suffix
strickvl Sep 25, 2026
b1a78c4
Refuse ID-only writes to rows outside the turn
strickvl Sep 25, 2026
865e145
Add `nonSecretKeys` and `isSecretKey` to memory replay
strickvl Sep 28, 2026
53c9c88
Name the credential in refused request evidence
strickvl Sep 28, 2026
ba1e7c7
Record memory replay data faithfully by default
strickvl Sep 28, 2026
c5408c1
Document faithful recording for memory replay
strickvl Sep 28, 2026
9a642d2
Hide provider errors and transport keys everywhere
strickvl Sep 28, 2026
c035091
Scope credential docs to the memory replay agent
strickvl Sep 28, 2026
db1af76
Merge remote-tracking branch 'origin/develop' into feat/issue-1176-ad…
strickvl Sep 28, 2026
1422a50
Build not-scorable Mastra results without `notScorable`
strickvl Sep 28, 2026
9a34203
Test the Mastra adapter against `@mastra/core` 1.71
strickvl Sep 28, 2026
f8a2597
Accept every tested Mastra release in memory replay
strickvl Sep 29, 2026
01dcc37
Stop Dependabot moving the pinned Mastra versions
strickvl Sep 29, 2026
a066b03
Trim the Mastra compat packages and guard the doc tables
strickvl Sep 29, 2026
e51e20d
Add changelog fragment for #1229
strickvl Sep 29, 2026
386c106
Replay buffered OM results at production's step
strickvl Sep 29, 2026
2a932df
Store OM tape entries through one helper
strickvl Sep 29, 2026
43b7c2d
Name the changelog fragment for #1230
strickvl Sep 29, 2026
db550f5
Merge develop into Mastra compatibility checks
strickvl Oct 1, 2026
fdaf469
Merge branch 'develop' into ci/mastra-core-1.71-compat
strickvl Oct 2, 2026
7604830
Merge branch 'develop' into ci/mastra-core-1.71-compat
strickvl Oct 2, 2026
b4a6b2b
Fix Mastra compatibility CI integration
strickvl Oct 2, 2026
465f4f8
Merge updated Mastra compatibility CI into range tests
strickvl Oct 2, 2026
32fbc70
Merge updated Mastra range tests into buffer replay
strickvl Oct 2, 2026
b111c49
Refresh MCP index for Mastra version documentation
strickvl Oct 2, 2026
330ad8d
Refresh MCP index for buffered memory replay
strickvl Oct 2, 2026
a655284
Include the final Mastra range parent update
strickvl Oct 2, 2026
57a2847
Merge remote-tracking branch 'origin/develop' into HEAD
strickvl Oct 5, 2026
d74f05a
Merge commit '57a2847f9' into HEAD
strickvl Oct 5, 2026
8e604e9
Merge remote-tracking branch 'origin/develop' into HEAD
strickvl Oct 5, 2026
46c87a8
Merge commit '8e604e922' into HEAD
strickvl Oct 5, 2026
d947a13
Merge remote-tracking branch 'origin/develop' into HEAD
strickvl Oct 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1230.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- A Mastra memory replay (`createMemoryReplayAgent()`) no longer observes at a step where production did not. With a slow production observer, the instant replay used to start extra buffering rounds while the recorded round would still have been running. Each extra round split later steps into more stored messages and raised OM's pending token count, so a turn that ended just under `messageTokens` crossed it only in replay and the actor's last prompt differed. Baselines now record how many actor steps had started when each OM output arrived and how long the call took, and replay returns a matching buffered output at that step, or after that duration at the latest. Baselines recorded earlier replay as before.
2 changes: 1 addition & 1 deletion docs/book/adapters/mastra.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ The factory creates a fresh native agent for each invocation. A baseline uses yo

Replay never calls `sourceMemory()` and never writes to your source storage. It calls an observer or reflector model only when you opt in with `missingObservationalMemoryResults: "live"`, described below.

Each replay OM call takes the unused recorded output with the same phase (observer or reflector), model method, and input. The input comparison ignores message times, dates, generated ids, and how an attachment is held: a declared URL, its captured reference, its downloaded bytes, or its content inline as base64 text or a data URL. Replayed OM models accept captured references and network URLs, so Mastra never downloads a file for them. Mastra also counts an attachment's tokens from its URL, sometimes by asking the provider, and those counts decide when OM observes. A baseline therefore records the tokens OM counted for each attachment, declared, from thread history, or held inline as bytes by a processor, and replay reuses them instead of counting the captured reference or calling the provider. An attachment a replay counts without a recorded count, such as new inline content, takes Mastra's local estimate, so replay never asks the provider to count tokens. Mastra's number of OM calls depends on timing: a slow production observer merges buffering rounds that an instant replay makes separately, and it covers messages the actor produced while it ran. A buffered call (async observation or reflection) whose input matches no unused output therefore gets no result, because another window's output could describe messages the replay has not produced yet. Its messages stay in the actor's context, as they did in production while the observer ran, and a later buffered call usually matches the recorded window. A blocking call whose input matches no unused output takes the next unused output of its phase, and replay records an `om_input_mismatch` span. Its `calls` attribute lists each such call with its phase, model method, `recorded_ordinal` (the tape position of the output it took), and `replay_call` (its position among the replay's OM calls). A blocking call after its phase's recorded outputs are used up fails replay with `KITARU_REPLAY_DIVERGED:mastra_om_call_order`, because an empty observation would drop the observed messages from the actor's context. So does a blocking call whose phase has no recorded output at all; a buffered call of such a phase gets no result and counts as a surplus call. The failed replay records an `om_unanswered_call` span that names the call: its phase, model method, `replay_call`, and `cause` (`no_recorded_result` when production made no call of that phase and method, or `recorded_results_used_up`). By default, no OM call reaches a provider. To let such a replay finish instead, set `missingObservationalMemoryResults: "live"` on `createMemoryReplayAgent`. A blocking call with no recorded output then calls the observer or reflector model that `resolveModel` returns for the recorded identity, with captured files sent as their recorded bytes, and recorded outputs still answer every other call. Buffered calls never go live. Each live call is recorded as an `llm_call` node named `om_observer_live_call` or `om_reflector_live_call`, and the replay session reports how many ran in `metadata.mastra_om_live_calls`, because part of its memory no longer comes from what production observed. Replay reports the other departures in an `om_call_divergence` span and in the session's `metadata.mastra_om_divergence` counts: `input_mismatches` (blocking calls that took another input's output), `surplus_calls` (buffered calls after their phase's outputs were used up, or of a phase with none), `unused_results`, and `live_calls` (blocking calls the live model answered). A baseline also records failed OM attempts, so a turn whose observer succeeded after Mastra retried it stays eligible, and its replay serves the successful output directly. When a failed blocking observation or an input processor's `abort()` ends the Mastra stream with a tripwire, the session still closes and the lease is released: a baseline becomes `ineligible` and a replay fails. A replay closes its session before its stream ends, so the replay process can exit as soon as it has read the stream. Reusing recorded outputs lets you compare actor instruction/model changes, but does not measure how a fresh observer or reflector would respond to the changed conversation.
Each replay OM call takes the unused recorded output with the same phase (observer or reflector), model method, and input. The input comparison ignores message times, dates, generated ids, and how an attachment is held: a declared URL, its captured reference, its downloaded bytes, or its content inline as base64 text or a data URL. Replayed OM models accept captured references and network URLs, so Mastra never downloads a file for them. Mastra also counts an attachment's tokens from its URL, sometimes by asking the provider, and those counts decide when OM observes. A baseline therefore records the tokens OM counted for each attachment, declared, from thread history, or held inline as bytes by a processor, and replay reuses them instead of counting the captured reference or calling the provider. An attachment a replay counts without a recorded count, such as new inline content, takes Mastra's local estimate, so replay never asks the provider to count tokens. Mastra's number of OM calls depends on timing: a slow production observer merges buffering rounds that an instant replay makes separately, and it covers messages the actor produced while it ran. A buffered call (async observation or reflection) whose input matches no unused output therefore gets no result, because another window's output could describe messages the replay has not produced yet. Its messages stay in the actor's context, as they did in production while the observer ran, and a later buffered call usually matches the recorded window. A buffered call whose input matches returns its output only once the actor has started as many steps as it had when production's output arrived, or once production's call duration has passed. Mastra starts no new buffering round while one runs, and each round seals the messages it covers, which raises the pending token count that decides when OM observes; an instant output would let replay start rounds production never ran, so a turn that ended just under the observation threshold would observe only in replay. A baseline recorded before this timing was kept returns matching buffered outputs at once. A blocking call whose input matches no unused output takes the next unused output of its phase, and replay records an `om_input_mismatch` span. Its `calls` attribute lists each such call with its phase, model method, `recorded_ordinal` (the tape position of the output it took), and `replay_call` (its position among the replay's OM calls). A blocking call after its phase's recorded outputs are used up fails replay with `KITARU_REPLAY_DIVERGED:mastra_om_call_order`, because an empty observation would drop the observed messages from the actor's context. So does a blocking call whose phase has no recorded output at all; a buffered call of such a phase gets no result and counts as a surplus call. The failed replay records an `om_unanswered_call` span that names the call: its phase, model method, `replay_call`, and `cause` (`no_recorded_result` when production made no call of that phase and method, or `recorded_results_used_up`). By default, no OM call reaches a provider. To let such a replay finish instead, set `missingObservationalMemoryResults: "live"` on `createMemoryReplayAgent`. A blocking call with no recorded output then calls the observer or reflector model that `resolveModel` returns for the recorded identity, with captured files sent as their recorded bytes, and recorded outputs still answer every other call. Buffered calls never go live. Each live call is recorded as an `llm_call` node named `om_observer_live_call` or `om_reflector_live_call`, and the replay session reports how many ran in `metadata.mastra_om_live_calls`, because part of its memory no longer comes from what production observed. Replay reports the other departures in an `om_call_divergence` span and in the session's `metadata.mastra_om_divergence` counts: `input_mismatches` (blocking calls that took another input's output), `surplus_calls` (buffered calls after their phase's outputs were used up, or of a phase with none), `unused_results`, and `live_calls` (blocking calls the live model answered). A baseline also records failed OM attempts, so a turn whose observer succeeded after Mastra retried it stays eligible, and its replay serves the successful output directly. When a failed blocking observation or an input processor's `abort()` ends the Mastra stream with a tripwire, the session still closes and the lease is released: a baseline becomes `ineligible` and a replay fails. A replay closes its session before its stream ends, so the replay process can exit as soon as it has read the stream. Reusing recorded outputs lets you compare actor instruction/model changes, but does not measure how a fresh observer or reflector would respond to the changed conversation.

The following binding uses a process-local store. Supply your existing public memory storage domain and its complete configuration for a persistent application:

Expand Down
2 changes: 1 addition & 1 deletion packages/mastra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ The factory creates a fresh native agent for each invocation. A baseline uses yo

Replay never calls `sourceMemory()` and never writes to your source storage. It calls an observer or reflector model only when you opt in with `missingObservationalMemoryResults: "live"`, described below.

Each replay OM call takes the unused recorded output with the same phase (observer or reflector), model method, and input. The input comparison ignores message times, dates, generated ids, and how an attachment is held: a declared URL, its captured reference, its downloaded bytes, or its content inline as base64 text or a data URL. Replayed OM models accept captured references and network URLs, so Mastra never downloads a file for them. Mastra also counts an attachment's tokens from its URL, sometimes by asking the provider, and those counts decide when OM observes. A baseline therefore records the tokens OM counted for each attachment, declared, from thread history, or held inline as bytes by a processor, and replay reuses them instead of counting the captured reference or calling the provider. An attachment a replay counts without a recorded count, such as new inline content, takes Mastra's local estimate, so replay never asks the provider to count tokens. Mastra's number of OM calls depends on timing: a slow production observer merges buffering rounds that an instant replay makes separately, and it covers messages the actor produced while it ran. A buffered call (async observation or reflection) whose input matches no unused output therefore gets no result, because another window's output could describe messages the replay has not produced yet. Its messages stay in the actor's context, as they did in production while the observer ran, and a later buffered call usually matches the recorded window. A blocking call whose input matches no unused output takes the next unused output of its phase, and replay records an `om_input_mismatch` span. Its `calls` attribute lists each such call with its phase, model method, `recorded_ordinal` (the tape position of the output it took), and `replay_call` (its position among the replay's OM calls). A blocking call after its phase's recorded outputs are used up fails replay with `KITARU_REPLAY_DIVERGED:mastra_om_call_order`, because an empty observation would drop the observed messages from the actor's context. So does a blocking call whose phase has no recorded output at all; a buffered call of such a phase gets no result and counts as a surplus call. The failed replay records an `om_unanswered_call` span that names the call: its phase, model method, `replay_call`, and `cause` (`no_recorded_result` when production made no call of that phase and method, or `recorded_results_used_up`). By default, no OM call reaches a provider. To let such a replay finish instead, set `missingObservationalMemoryResults: "live"` on `createMemoryReplayAgent`. A blocking call with no recorded output then calls the observer or reflector model that `resolveModel` returns for the recorded identity, with captured files sent as their recorded bytes, and recorded outputs still answer every other call. Buffered calls never go live. Each live call is recorded as an `llm_call` node named `om_observer_live_call` or `om_reflector_live_call`, and the replay session reports how many ran in `metadata.mastra_om_live_calls`, because part of its memory no longer comes from what production observed. Replay reports the other departures in an `om_call_divergence` span and in the session's `metadata.mastra_om_divergence` counts: `input_mismatches` (blocking calls that took another input's output), `surplus_calls` (buffered calls after their phase's outputs were used up, or of a phase with none), `unused_results`, and `live_calls` (blocking calls the live model answered). A baseline also records failed OM attempts, so a turn whose observer succeeded after Mastra retried it stays eligible, and its replay serves the successful output directly. When a failed blocking observation or an input processor's `abort()` ends the Mastra stream with a tripwire, the session still closes and the lease is released: a baseline becomes `ineligible` and a replay fails. A replay closes its session before its stream ends, so the replay process can exit as soon as it has read the stream. Reusing recorded outputs lets you compare actor instruction/model changes, but does not measure how a fresh observer or reflector would respond to the changed conversation.
Each replay OM call takes the unused recorded output with the same phase (observer or reflector), model method, and input. The input comparison ignores message times, dates, generated ids, and how an attachment is held: a declared URL, its captured reference, its downloaded bytes, or its content inline as base64 text or a data URL. Replayed OM models accept captured references and network URLs, so Mastra never downloads a file for them. Mastra also counts an attachment's tokens from its URL, sometimes by asking the provider, and those counts decide when OM observes. A baseline therefore records the tokens OM counted for each attachment, declared, from thread history, or held inline as bytes by a processor, and replay reuses them instead of counting the captured reference or calling the provider. An attachment a replay counts without a recorded count, such as new inline content, takes Mastra's local estimate, so replay never asks the provider to count tokens. Mastra's number of OM calls depends on timing: a slow production observer merges buffering rounds that an instant replay makes separately, and it covers messages the actor produced while it ran. A buffered call (async observation or reflection) whose input matches no unused output therefore gets no result, because another window's output could describe messages the replay has not produced yet. Its messages stay in the actor's context, as they did in production while the observer ran, and a later buffered call usually matches the recorded window. A buffered call whose input matches returns its output only once the actor has started as many steps as it had when production's output arrived, or once production's call duration has passed. Mastra starts no new buffering round while one runs, and each round seals the messages it covers, which raises the pending token count that decides when OM observes; an instant output would let replay start rounds production never ran, so a turn that ended just under the observation threshold would observe only in replay. A baseline recorded before this timing was kept returns matching buffered outputs at once. A blocking call whose input matches no unused output takes the next unused output of its phase, and replay records an `om_input_mismatch` span. Its `calls` attribute lists each such call with its phase, model method, `recorded_ordinal` (the tape position of the output it took), and `replay_call` (its position among the replay's OM calls). A blocking call after its phase's recorded outputs are used up fails replay with `KITARU_REPLAY_DIVERGED:mastra_om_call_order`, because an empty observation would drop the observed messages from the actor's context. So does a blocking call whose phase has no recorded output at all; a buffered call of such a phase gets no result and counts as a surplus call. The failed replay records an `om_unanswered_call` span that names the call: its phase, model method, `replay_call`, and `cause` (`no_recorded_result` when production made no call of that phase and method, or `recorded_results_used_up`). By default, no OM call reaches a provider. To let such a replay finish instead, set `missingObservationalMemoryResults: "live"` on `createMemoryReplayAgent`. A blocking call with no recorded output then calls the observer or reflector model that `resolveModel` returns for the recorded identity, with captured files sent as their recorded bytes, and recorded outputs still answer every other call. Buffered calls never go live. Each live call is recorded as an `llm_call` node named `om_observer_live_call` or `om_reflector_live_call`, and the replay session reports how many ran in `metadata.mastra_om_live_calls`, because part of its memory no longer comes from what production observed. Replay reports the other departures in an `om_call_divergence` span and in the session's `metadata.mastra_om_divergence` counts: `input_mismatches` (blocking calls that took another input's output), `surplus_calls` (buffered calls after their phase's outputs were used up, or of a phase with none), `unused_results`, and `live_calls` (blocking calls the live model answered). A baseline also records failed OM attempts, so a turn whose observer succeeded after Mastra retried it stays eligible, and its replay serves the successful output directly. When a failed blocking observation or an input processor's `abort()` ends the Mastra stream with a tripwire, the session still closes and the lease is released: a baseline becomes `ineligible` and a replay fails. A replay closes its session before its stream ends, so the replay process can exit as soon as it has read the stream. Reusing recorded outputs lets you compare actor instruction/model changes, but does not measure how a fresh observer or reflector would respond to the changed conversation.

The following binding uses a process-local store. Supply your existing public memory storage domain and its complete configuration for a persistent application:

Expand Down
2 changes: 1 addition & 1 deletion packages/mastra/src/attachment-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function getAttachmentData(part: unknown): unknown {
return data instanceof URL ? data.href : data;
}

function isCount(value: unknown): value is number {
export function isCount(value: unknown): value is number {
return typeof value === "number" && Number.isFinite(value) && value >= 0;
}

Expand Down
Loading
Loading