Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 16 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ on:
- main
workflow_dispatch:

# The companions job relinks every graphql-orm-ai test binary once per provider
# feature combination. With full debug info that exhausted the runner disk and
# the linker died with SIGBUS mid-link. `line-tables-only` keeps file and line
# numbers in failure backtraces, which is what CI diagnosis actually needs,
# at a fraction of the size.
env:
CARGO_PROFILE_DEV_DEBUG: line-tables-only
CARGO_PROFILE_TEST_DEBUG: line-tables-only
CARGO_INCREMENTAL: "0"

jobs:
workspace-integrity:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -158,11 +168,12 @@ jobs:
manifest-path: crates/graphql-orm/Cargo.toml
baseline-root: ../graphql-orm-baseline/crates/graphql-orm
feature-group: default-features
- uses: obi1kenobi/cargo-semver-checks-action@v2
with:
manifest-path: crates/graphql-orm-macros/Cargo.toml
baseline-root: ../graphql-orm-baseline/crates/graphql-orm-macros
feature-group: default-features
# graphql-orm-macros is deliberately absent. It is a proc-macro crate, so
# it exposes no library API surface for cargo-semver-checks to analyze;
# newer versions of the tool now fail the job outright rather than
# silently checking nothing. scripts/check-semver.sh has excluded it for
# the same reason. Macro compatibility is covered by the aligned
# package-version gate and the full compile/trybuild fixture matrix.
- if: steps.companion-baseline.outputs.exists == 'true'
uses: obi1kenobi/cargo-semver-checks-action@v2
with:
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

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

63 changes: 63 additions & 0 deletions crates/graphql-orm-ai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,69 @@ checkpoint facts. For the current workspace baseline and active gates, use the
[implementation status](docs/implementation-status.md) and the central
[AI production-readiness plan](../../docs/plans/active/ai-production-readiness/README.md).

## [0.82.0] - 2026-08-20

Persistent schema module: **0.61.0**.

### Added

- `retryAiRun` and `acknowledgeAiRunFailure` dispose of a failed or
recovery-required run. Retry authors a *new* run over the same durable user
message under current policy and is idempotent under a client request id;
acknowledge durably dismisses the failure. Neither mutates or deletes the
source run, so its state, immutable attempt outcomes, and session and inbox
events survive. At most one disposition wins per run.
- `run_failed` and `run_recovery_required` events carry a bounded failure
record with a stable code, a retryable flag, and the admission reason.
Retry admission is computed from committed rows only: `RecoveryRequired` is
never retryable, an absent or unrecognized failure code is never retryable,
and a run that already produced a durable assistant message is refused.
- `provider_session_reset` and `provider_session_rebound` events disclose that
a retained provider thread stopped being usable, carrying only the
server-owned reason class. Previously the durable transcript read as
continuous while the model had silently lost all prior context.
- `AiSessionEventEnvelope.closed` carries a typed
[`AiSessionStreamClose`] on the final envelope of any server-ended session
stream, so a client can distinguish "stream over, resubscribe" from network
silence.
- `AiRunInterruptSettlement` reports what an interrupt proved about the turn it
stopped. It fails closed: only proven settlement retains a thread, and no
adapter currently reports it.
- `OrmAiSessionService::session_stream_head` performs one bounded authorized
head-sequence read, used as a durable fallback delivery path.

### Changed

- **Conversation bootstrap no longer fails while an assistant is streaming.**
The snapshot retry predicate compared `row_version` and `stream_head`, which
every coalesced live delta advances at roughly the coalescer rate, so the
bounded snapshot returned `Conflict` for exactly the sessions a user is most
likely to open. The predicate now covers only fields the bootstrap returns.
The watermark is a resume floor: nothing at or below it is missing, run and
tool-call rows may already reflect a later event, and the message window
never leads it.
- Session-event subscriptions survive a brief authorization-service restart
within a bounded, per-session jittered grace window instead of failing on the
first `resolve` error. An authoritative denial, or any class this crate does
not recognize, still denies immediately.
- Session-event subscriptions run a periodic bounded durable head check, so
single-replica delivery no longer depends solely on the process-local wakeup
channel and a missed hint is no longer unrecoverable.
- A message accepted while provider-session cleanup is pending now converges
without an operator. Exhausting the bounded retry allowance closes the run as
a clean visible failure instead of leaving it to expire into
`RecoveryRequired`, which was misclassified because nothing had executed.
- `AiRunCompletion::outcome_code` is now readable.

### Breaking

- `AiAgentProviderTurnExecutor::interrupt_run` returns
`AiRunInterruptSettlement` instead of `()`.
- `AiSessionEventEnvelope` gained a `closed` field. Construct it through
`AiSessionEventEnvelope::delivered` or `AiSessionEventEnvelope::ended`.
- The persistent schema module advances to 0.61.0 for the new run-failure
disposition entity.

## [0.81.0] - 2026-08-16

### Added
Expand Down
3 changes: 2 additions & 1 deletion crates/graphql-orm-ai/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graphql-orm-ai"
version = "0.81.0"
version = "0.82.0"
edition = "2024"
authors = ["Toby Martin <toby@dastari.net>"]
description = "Project-agnostic AI agent runtime for graphql-orm applications"
Expand Down Expand Up @@ -62,4 +62,5 @@ url = "2"
uuid = { version = "1", features = ["serde", "v4"] }

[dev-dependencies]
tempfile = "3"
tokio = { version = "1", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "sync", "time"] }
65 changes: 65 additions & 0 deletions crates/graphql-orm-ai/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,71 @@ they describe. For the current workspace baseline and active delivery gates,
use [implementation status](docs/implementation-status.md) and the central
[AI production-readiness plan](../../docs/plans/active/ai-production-readiness/README.md).

## 0.81.0 to 0.82.0: session reliability and failure disposition

Adopt `graphql-orm-ai` 0.82.0 at one reviewed full monorepo revision.

### Schema module

The AI schema module advances **0.60.0 to 0.61.0** and adds one entity,
`graphql_orm_ai_run_failure_dispositions`, with a unique index on
`source_run_id` and a session/decision index. Apply and verify the module
before serving traffic. There is no backfill, no column change to an existing
table, and no protected-payload migration. Existing rows and events remain
readable.

### Source-breaking changes

`AiAgentProviderTurnExecutor::interrupt_run` now returns
`AiRunInterruptSettlement` instead of `()`. Existing implementations that
interrupt without proving settlement should return
`AiRunInterruptSettlement::RequestedUnsettled`, and one that finds no live
resource should return `NotActive`. Do not return `Settled` unless the adapter
can prove the interrupted turn left the provider's retained thread consistent
with the durable transcript; `retains_thread()` is the only thing that keeps a
binding, and it fails closed.

`AiSessionEventEnvelope` gained a nullable `closed` field. Build envelopes with
`AiSessionEventEnvelope::delivered` or `AiSessionEventEnvelope::ended` instead
of struct literals.

### Behavioural changes with no API change

`conversation_bootstrap` no longer returns `Conflict` while an assistant is
streaming. Its `watermark` is now documented as a **resume floor** rather than
an equality point. Subscribe with `after_sequence = watermark`; no event at or
below it is missing, and the message window never leads it, but run and
tool-call rows may already reflect an event after it. Apply replayed events by
identifier so re-applying one the snapshot already reflects is idempotent. A
client that assumed every replayed event was unseen must be updated.

Session-event streams now tolerate a briefly unavailable authorization
dependency within a bounded per-session jittered grace window, and emit a typed
close envelope before ending. Authoritative denials are unchanged: the stream
still fails immediately, and the existing `AiError` still follows the close
envelope, so a client reading only errors keeps working.

A run whose provider-session cleanup stays pending past its retry allowance now
closes as `Failed` with `provider_session_cleanup_unavailable` instead of
expiring into `RecoveryRequired`. That code is retryable, because nothing
executed.

### New GraphQL surface

`retryAiRun` and `acknowledgeAiRunFailure` are additive; regenerate typed and
PascalCase clients. Install `Arc<dyn AiRunDispositionService>` in schema data
or both mutations return a configuration error. `OrmAiRunDispositionService`
is the generated-ORM implementation.

Four event types are additive on the existing session stream:
`run_retry_queued`, `run_failure_acknowledged`, `provider_session_reset`, and
`provider_session_rebound`. Clients that reject unknown event types must be
updated to ignore them.

The `run_failed` and `run_recovery_required` payloads advance from the tagged
`...-v1` shape to `...-v2` and carry a `failure` record. Readers accept both
shapes; a v1 payload written before this release stays readable.

## 0.80.0 to 0.81.0: capability discovery and durable provider loops

Adopt `graphql-orm-ai` 0.81.0 and
Expand Down
27 changes: 26 additions & 1 deletion crates/graphql-orm-ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ for AI, ORM, storage, backup, and tool-profile packages:

```toml
[dependencies]
graphql-orm-ai = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.81.0", default-features = false, features = ["sqlite"] }
graphql-orm-ai = { git = "https://github.com/Dastari/graphql-orm.git", rev = "<reviewed-full-40-character-commit-sha>", version = "0.82.0", default-features = false, features = ["sqlite"] }
```

Exactly one persistence backend is required: `sqlite` (default), `postgres`,
Expand Down Expand Up @@ -118,6 +118,31 @@ authenticated registered replay source and the existing run queue; it
rehydrates current authority at open, event and adoption boundaries. See
[durable bounded subscription waits](docs/durable-subscription-waits.md).

## Session reliability

One bounded `aiConversationBootstrap` snapshot plus durable event replay is the
supported way to open a conversation. Its watermark is a **resume floor**:
nothing at or below it is missing from the snapshot, the message window never
leads it, and run and tool-call rows may already reflect a later event, so
replayed events are applied by identifier.

Session-event streams end with a typed close envelope rather than silence,
tolerate a briefly unavailable authorization dependency inside a bounded
jittered grace window while denying authoritative revocation immediately, and
run a periodic bounded durable head check so single-replica delivery does not
depend solely on the process-local wakeup channel.

Terminal `run_failed` and `run_recovery_required` events carry a bounded,
content-free failure record with a stable code and a retryable flag computed
from committed rows. `retryAiRun` authors a new run over the same durable user
message under current policy where re-execution is provably safe;
`acknowledgeAiRunFailure` dismisses a failure without deleting audit history.
Invalidating a retained provider thread emits `provider_session_reset` or
`provider_session_rebound` so a host can tell the user the model's context was
reset even though the durable transcript reads as continuous.

See the [session reliability adoption contract](docs/session-reliability-adoption.md).

## Features and capability boundary

| Feature | Default | Meaning |
Expand Down
1 change: 1 addition & 0 deletions crates/graphql-orm-ai/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ tool-free, and network-free; production integration comes later.

- [Attachments](attachments.md), [provider files](provider-files.md), [live streaming](live-streaming.md), [context compaction](context-compaction.md), [skills and UI intents](skills-and-ui-intents.md), and [remote GraphQL execution](remote-graphql-execution.md).
- [Capability index, compact planning, durable broker and conversation bootstrap](capability-discovery-and-execution.md).
- [Session reliability adoption contract](session-reliability-adoption.md) — bootstrap watermark semantics, stream close envelopes, run failure records, retry/acknowledge, and retained-thread disclosure.
- [Migration guide](../MIGRATION.md) and [changelog](../CHANGELOG.md).

## Concepts and operations
Expand Down
27 changes: 25 additions & 2 deletions crates/graphql-orm-ai/docs/capability-discovery-and-execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,31 @@ returns the session shell, newest messages plus backward cursor, durable
watermark, active runs, recent terminal codes, related tool calls, safe
provider activity and reset-required state. It never returns prompts, tool
results, provider payloads, credentials or authorization details. The ORM
implementation uses a bounded optimistic snapshot and retries if the session
watermark changes during assembly.
implementation uses a bounded optimistic snapshot and retries only when a
field it actually returns changed during assembly.

### The watermark is a resume floor

The returned watermark is captured before the snapshot is assembled and is a
lower bound, not an equality point:

- every durable effect at or before the watermark is reflected in the
snapshot, so subscribing with `after_sequence = watermark` cannot miss an
event;
- the message window never leads the watermark, because a new message changes
the message head and forces the snapshot to be reassembled;
- run and tool-call rows may already reflect an effect after the watermark.
Both are identified state keyed by row ID, so re-applying the replayed event
that produced them is idempotent. A client must apply replayed events by ID
rather than assuming every replayed event is unseen.

Deliberately excluded from the retry predicate are the session stream head,
last-activity timestamp and CAS row version. A coalesced live delta advances
all three at roughly the streaming coalescer rate while an assistant is
answering, but appends only a session event and cannot change anything the
bootstrap returns. Including that churn made the bounded snapshot fail with
`Conflict` for exactly the sessions a user is most likely to open, which a
client cannot distinguish from a disconnection.

A client renders the snapshot, begins durable event replay strictly after the
returned watermark, drains to the captured/current head, then attaches live
Expand Down
Loading
Loading