Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
158 commits
Select commit Hold shift + click to select a range
63766dc
feat(web): support durable approval responses
mmabrouk Sep 4, 2026
2eac810
feat(runner): admit durable continuations once
mmabrouk Sep 4, 2026
fb514c5
feat(api): persist durable approval continuations
mmabrouk Sep 4, 2026
b0ada37
fix(sessions): fence continuation recovery
mmabrouk Sep 4, 2026
7fc342f
fix(web): preflight durable continuation retries
mmabrouk Sep 4, 2026
de5c812
fix(sessions): handle parallel durable approvals
mmabrouk Sep 4, 2026
e942d73
test(sessions): keep unknown command coverage generic
mmabrouk Sep 4, 2026
63d51f0
fix(sessions): gate continuation reconciliation consistently
mmabrouk Sep 4, 2026
ed74085
fix(sessions): keep heartbeat guard failures local
mmabrouk Sep 5, 2026
e3663e4
fix(sessions): commit watchdog state before Redis cleanup
mmabrouk Sep 5, 2026
f4797ab
test(sessions): keep sweeping after guard timeout
mmabrouk Sep 5, 2026
0d21ef8
test(sessions): clear expired-turn affinity after sweep
mmabrouk Sep 4, 2026
7f5390a
fix(sessions): keep parked continuations steerable
mmabrouk Sep 4, 2026
f79228a
fix(sessions): validate batch anchor before feature routing
mmabrouk Sep 4, 2026
51355cf
fix(sessions): replay the current continuation target
mmabrouk Sep 4, 2026
10a976b
fix(workflows): scope strict start records to commands
mmabrouk Sep 4, 2026
916454b
fix(sessions): replay matching partial answers
mmabrouk Sep 4, 2026
b6e6ca6
fix(sessions): make continuation preflight additive
mmabrouk Sep 4, 2026
b5f6e0d
fix(chat): route approvals by server capability
mmabrouk Sep 4, 2026
fea531c
fix(mobile): reject cross-execution approval batches
mmabrouk Sep 4, 2026
a03d5a1
test(sessions): model conditional watchdog updates
mmabrouk Sep 4, 2026
23038a2
fix(sessions): disable ORM sync for guarded collapse
mmabrouk Sep 4, 2026
95195f9
test(chat): preserve recoverable approval state
mmabrouk Sep 4, 2026
24362fb
test(sessions): enforce commit before Redis release
mmabrouk Sep 4, 2026
d58265b
fix(sessions): collapse turns settled lost during sweep
mmabrouk Sep 4, 2026
abe27dd
fix(web): use generated session capabilities type
mmabrouk Sep 4, 2026
3291c10
fix(web): cache durable approval capability
mmabrouk Sep 4, 2026
3bc5b97
fix(sessions): return heartbeat after guard lease loss
mmabrouk Sep 4, 2026
d78ad56
fix(sessions): resolve a resume's references from the session
mmabrouk Sep 4, 2026
e29364f
fix(web): keep the approval outcome the card reads
mmabrouk Sep 4, 2026
b04d816
fix(workflows): read the service wire on a detached start
mmabrouk Sep 4, 2026
a8e60e0
fix(sessions): keep a running continuation out of the recoverable state
mmabrouk Sep 4, 2026
9d90b92
fix(web): sync approval answers across readers
mmabrouk Sep 4, 2026
70b01db
fix(web): keep held messages visible during approval recovery
mmabrouk Sep 4, 2026
7771a62
fix(web): retire desktop approval dock from terminal records
mmabrouk Sep 4, 2026
ec0d222
fix(web): scope recovery state to its approval
mmabrouk Sep 4, 2026
0f6a322
fix(web): refresh interaction rows during transcript hydration
mmabrouk Sep 4, 2026
89b1229
fix(api): share bounded interaction reference fallback
mmabrouk Sep 4, 2026
930de8b
fix(web): drain held queue after continuation terminal
mmabrouk Sep 4, 2026
cf0bc4a
fix(web): hold queued messages through continuation
mmabrouk Sep 4, 2026
3fc86c5
fix(web): retire observer approvals on continuation
mmabrouk Sep 4, 2026
0b6b3b4
fix(web): retire approval dock on continuation end
mmabrouk Sep 4, 2026
73235ae
test(web): isolate recoverable approval state
mmabrouk Sep 4, 2026
7de9592
fix(web): hold a queued message until its continuation ends
mmabrouk Sep 4, 2026
ee19614
fix(api): refuse a Send that would supersede an executing continuation
mmabrouk Sep 4, 2026
1864946
test(web): replay the round-8 record logs for the hold and the dock
mmabrouk Sep 4, 2026
c972929
chore: drop STATUS-round8.md from the branch
mmabrouk Sep 4, 2026
47f3cbf
fix(sessions): push interaction resolutions to readers
mmabrouk Sep 4, 2026
868e46b
fix(chat): preserve ownership for released sends
mmabrouk Sep 4, 2026
8d75484
fix(chat): own durable approval continuations
mmabrouk Sep 4, 2026
2ebd848
fix(api): preserve cancellation settlement ordering
mmabrouk Sep 4, 2026
f3b98bb
fix(api): serialize terminal interaction retries
mmabrouk Sep 4, 2026
ebb5c0f
fix(mobile): distinguish local continuation ownership
mmabrouk Sep 4, 2026
c5659f2
style(sessions): reformat the completion-failure filter
mmabrouk Sep 5, 2026
1978bf1
fix(runner): register the Stop handle only after admission
mmabrouk Sep 5, 2026
ac4c48e
fix(chat): settle a cancelled durable continuation
mmabrouk Sep 5, 2026
6ddc2bf
test(web): mock the durable approval seams the session hook now reads
mmabrouk Sep 5, 2026
6954262
style(chat): format the durable approval test files
mmabrouk Sep 5, 2026
892e2ba
feat(api): persist queued session input
mmabrouk Sep 4, 2026
539e0ba
feat(api): promote pending session input
mmabrouk Sep 4, 2026
a9a7e35
feat(api): steer pending session input
mmabrouk Sep 4, 2026
033c703
feat(web): use durable session input queue
mmabrouk Sep 4, 2026
876d9e0
test(sessions): cover pending input transactions
mmabrouk Sep 4, 2026
f4cca69
fix(sessions): fence queued input ownership
mmabrouk Sep 5, 2026
ab75d64
fix(sessions): exclude failed runner terminals
mmabrouk Sep 5, 2026
f978137
fix(sessions): deduplicate idle input retries
mmabrouk Sep 5, 2026
cdc831d
fix(sessions): serialize input admission with settlement
mmabrouk Sep 5, 2026
8dd9452
fix(chat): route queue sends through durable admission
mmabrouk Sep 5, 2026
4c47b6a
fix(sessions): bind steer to collapsed stop
mmabrouk Sep 5, 2026
72611c8
fix(sessions): expose recoverable promoted inputs
mmabrouk Sep 5, 2026
8238f7d
fix(sessions): guard missing input delivery receipts
mmabrouk Sep 5, 2026
6b9d4a3
refactor(sessions): defer replay snapshot projection
mmabrouk Sep 5, 2026
3e2c82b
fix(sessions): queue behind promoted continuations
mmabrouk Sep 5, 2026
b5b30e8
fix(sessions): align steer settlement locks
mmabrouk Sep 5, 2026
db08307
fix(sessions): preserve running queue successors
mmabrouk Sep 5, 2026
744007f
fix(sessions): advertise durable queue capabilities
mmabrouk Sep 5, 2026
afd0b1d
fix(chat): keep stop available for durable runs
mmabrouk Sep 5, 2026
ea12e61
fix(chat): require durable queue admission
mmabrouk Sep 5, 2026
1b39397
fix(sessions): release completed turn before queue delivery
mmabrouk Sep 5, 2026
8356fc6
fix(chat): hide remote-run banner for idle queued input
mmabrouk Sep 5, 2026
737b343
fix(chat): deliver steer during active streams
mmabrouk Sep 5, 2026
973ed10
test(chat): cover running-elsewhere admission
mmabrouk Sep 5, 2026
d25b83d
test(chat): match the milestone 2 running-elsewhere copy
mmabrouk Sep 5, 2026
b8eaeb3
fix(web): bind the remote-run gate and the steer payload to milestone 2
mmabrouk Sep 5, 2026
b92f46e
style(web): format the merged liveness module
mmabrouk Sep 5, 2026
e7a3232
fix(sessions): unify the session snapshot contract
mmabrouk Sep 5, 2026
30613c7
Merge pull request #6530 from Agenta-AI/feat/session-durable-approvals
mmabrouk Sep 5, 2026
dcf4753
fix(sessions): keep the queue snapshot reachable without a stream
mmabrouk Sep 5, 2026
ffa7765
Merge pull request #6555 from Agenta-AI/feat/session-queue-steer
mmabrouk Sep 5, 2026
1422435
merge: milestone 2 fixes (#6574) into the milestone 3 branch
mmabrouk Sep 5, 2026
fa993ff
fix(sessions): fail open during capability negotiation
mmabrouk Sep 5, 2026
0be8e16
fix(chat): keep remote stop behind queue capability
mmabrouk Sep 5, 2026
34cca19
fix(chat): avoid flag-off queue snapshot loads
mmabrouk Sep 5, 2026
bf79588
Merge pull request #6578 from Agenta-AI/fix/session-approvals-queue-f…
mmabrouk Sep 5, 2026
2c0ff8b
merge: milestone 2 index fix (#6576) into the milestone 3 branch
mmabrouk Sep 5, 2026
808851c
fix(api): propagate resumed continuation execution
mmabrouk Sep 5, 2026
2838cc6
fix(api): skip invalid durable event fields
mmabrouk Sep 5, 2026
791dc66
fix(api): reject lost steer command binds
mmabrouk Sep 5, 2026
6e00514
fix(api): preserve heartbeat after lease release failure
mmabrouk Sep 5, 2026
05cebbb
fix(api): keep heartbeat fence during lost settlement
mmabrouk Sep 5, 2026
2a05301
test(web): complete session live test inputs
mmabrouk Sep 5, 2026
730e9f8
fix(frontend): back off approval gate polling
mmabrouk Sep 5, 2026
3ec93cf
fix(chat): accept mid-execution live preview attach
mmabrouk Sep 5, 2026
04dba03
Merge pull request #6580 from Agenta-AI/fix/session-approvals-queue-c…
mmabrouk Sep 5, 2026
377774b
fix(mobile): release held messages without queue capabilities
mmabrouk Sep 5, 2026
a8fe022
Merge pull request #6582 from Agenta-AI/fix/session-approvals-queue-m…
mmabrouk Sep 5, 2026
d3bb689
v0.115.2
mmabrouk Sep 5, 2026
1f24465
Merge release/v0.115.2 into session approvals queue
mmabrouk Sep 5, 2026
78e5810
Merge pull request #6575 from Agenta-AI/feat/session-approvals-queue
mmabrouk Sep 5, 2026
832a344
fix(sessions): preserve live transcript through durable handoff
mmabrouk Sep 5, 2026
fab59e4
style(tests): format session preview regression cases
mmabrouk Sep 5, 2026
a6f2795
fix(chat): reset remote stop state for each turn
mmabrouk Sep 5, 2026
20cd767
fix(runner): finish teardown before promoting steered input
mmabrouk Sep 5, 2026
186b96d
fix(sdk): retain builder configuration for approval resumes
mmabrouk Sep 6, 2026
e3cecf0
fix(api): persist final heartbeat state after execution settlement
mmabrouk Sep 6, 2026
d1c4490
fix(tests): avoid stale app responses during navigation
mmabrouk Sep 6, 2026
bda6a6e
fix(frontend): refresh configuration after live agent commits
mmabrouk Sep 6, 2026
e30b0f8
fix(frontend): retire tool previews when durable approvals take over
mmabrouk Sep 6, 2026
094ee60
fix(api): keep queued messages behind pending approvals
mmabrouk Sep 6, 2026
9623488
test: retry named connection propagation in playground fixtures
mmabrouk Sep 6, 2026
8295ade
fix(frontend): negotiate shared responses for durable chat inputs
mmabrouk Sep 6, 2026
19b0077
fix(api): keep input behind starting approval continuations
mmabrouk Sep 6, 2026
68941b3
Merge pull request #6591 from Agenta-AI/fix/release-1152-session-reader
mmabrouk Sep 6, 2026
9176071
Merge pull request #6592 from Agenta-AI/fix/release-1152-steer-settle…
mmabrouk Sep 6, 2026
83314c9
Merge pull request #6593 from Agenta-AI/fix/release-1152-builder-appr…
mmabrouk Sep 6, 2026
5581ba1
Merge pull request #6594 from Agenta-AI/fix/release-1152-heartbeat-mi…
mmabrouk Sep 6, 2026
27e97c7
Merge pull request #6595 from Agenta-AI/fix/release-1152-chat-fixture…
mmabrouk Sep 6, 2026
99b8c02
Merge pull request #6596 from Agenta-AI/fix/release-1152-live-commit-…
mmabrouk Sep 6, 2026
4c50996
Merge pull request #6597 from Agenta-AI/fix/release-1152-terminal-too…
mmabrouk Sep 6, 2026
9241b7a
Merge pull request #6599 from Agenta-AI/fix/release-1152-shared-input…
mmabrouk Sep 6, 2026
a7f7252
Merge pull request #6598 from Agenta-AI/fix/release-1152-queue-paused…
mmabrouk Sep 6, 2026
9f090e8
Merge pull request #6600 from Agenta-AI/fix/release-1152-resume-queue
mmabrouk Sep 6, 2026
c9e54b3
fix(agents): resume queued turns after questionnaire answers
mmabrouk Sep 6, 2026
0cd481f
fix(agents): keep rejected connection answers retryable
mmabrouk Sep 6, 2026
8a6b6ef
style(tests): format questionnaire regressions
mmabrouk Sep 6, 2026
a2e2d81
fix(agents): retry saved connection answers without reconnecting
mmabrouk Sep 6, 2026
73d7227
Merge pull request #6603 from Agenta-AI/fix/release-1152-questionnair…
mmabrouk Sep 6, 2026
3a0f4f3
feat(sessions): send a selected queued input next atomically
mmabrouk Sep 6, 2026
68ccffa
feat(client): generate selected queued input Send Now endpoint
mmabrouk Sep 6, 2026
9367cc2
feat(frontend): send queued messages from their existing row
mmabrouk Sep 6, 2026
5e58370
style: format Send Now regression tests
mmabrouk Sep 6, 2026
868000f
fix(sessions): preserve inputs reserved by Send Now
mmabrouk Sep 6, 2026
9a6ddda
fix(frontend): remove queue pause footer copy
mmabrouk Sep 6, 2026
c30669d
fix(chat): resolve session capabilities before admitting input
mmabrouk Sep 6, 2026
5f57fdb
fix(frontend): connect desktop queued Send Now action
mmabrouk Sep 6, 2026
17860ea
fix(chat): retain admission ownership through manual retries
mmabrouk Sep 6, 2026
a12ce06
Merge pull request #6605 from Agenta-AI/fix/release-1152-queue-send-now
mmabrouk Sep 6, 2026
f57f68c
Merge branch 'release/v0.115.2' of github.com:Agenta-AI/agenta into f…
mmabrouk Sep 6, 2026
2f08d5d
fix(chat): preserve failed admission and expose queued Send Now
mmabrouk Sep 6, 2026
bc1c1cd
fix(chat): restore editing of queued messages on web and mobile
mmabrouk Sep 6, 2026
8bc8629
style(chat): shorten queued action comment
mmabrouk Sep 6, 2026
eb00804
Merge pull request #6606 from Agenta-AI/fix/release-1152-session-capa…
mmabrouk Sep 6, 2026
ada8a1c
Merge remote-tracking branch 'origin/release/v0.115.2' into fix/relea…
mmabrouk Sep 6, 2026
1d96e28
fix(chat): preserve queued edit ownership during admission
mmabrouk Sep 6, 2026
1445ef5
fix(agent): preserve queued input attachments
mmabrouk Sep 6, 2026
d68162e
fix(api): reject blank queued attachment ids
mmabrouk Sep 6, 2026
95a9e22
Merge pull request #6607 from Agenta-AI/fix/release-1152-queued-input…
mmabrouk Sep 6, 2026
377227f
fix(sessions): enable release features by default
mmabrouk Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions STATUS-round7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Round 7 status

Branch: `feat/session-durable-approvals`

Implemented as seven buildable commits after `5700408966`:

1. Cross-reader interaction events bypass the general refetch throttle, then refetch and reconcile
settled rows into mounted transcripts.
2. Held messages remain visible during gates; recoverable Sends retry the durable continuation.
3. A terminal server transcript clears the desktop approval dock even if the row cache is stale.
4. Approval response and recovery state is scoped to the interaction that produced it.
5. Initial and background transcript hydration replay against fresh interaction rows.
6. Dispatcher and inline router fallback share bounded reference resolution (latest turn, then stream).
7. Replayed terminal records release the held queue after a completed continuation.

## Browser re-check

- Open one pending approval in two desktop tabs. Answer in tab B. Tab A must leave the actionable
"Needs your approval" state within one second without a second click, then clear after the
continuation's terminal record.
- Repeat with mobile answering and desktop observing. The desktop result must match the two-desktop
case.
- While a gate is open, send a message. A visible `1 queued message · waits for your answer` card
must appear immediately on desktop and mobile.
- Force a recoverable approval response, then Send. The Send must redeliver the saved continuation,
keep the typed message visible in the held queue, and must not start a competing fresh turn or
create a `continuation_resumed` failure bubble.
- After that continuation writes its terminal record, the approval dock must close and the held
message must leave the queue and run exactly once as the next turn.
- After any recoverable interaction, start a new approval whose continuation succeeds. The new card
must show ordinary pending/answered copy, never inherited "retry needed" copy.
- Reload a session whose interaction row is `responded` or `resolved`. No actionable approval card
may reappear.
- Exercise a legacy/reference-less gate through the inline fallback composition. The continuation
must resolve the newest turn's workflow reference (or the stream fallback) and invoke normally.

## Automated verification

- `@agenta/chat`: 648 passed.
- `@agenta/oss`: 429 passed, 1 skipped.
- `@agenta/entities`: 1,480 unit tests passed; 31 integration tests skipped because the required
API/auth environment was not configured.
- `@agenta/mobile`: 147 passed.
- `@agenta/sessions`: 71 passed.
- Chat, OSS, entities, and mobile typechecks passed.
- Monorepo frontend lint passed (four pre-existing mobile hook warnings remain).
- API sessions: 706 passed.
- Ruff 0.15.12 format check: 1,491 files formatted; Ruff check passed.
45 changes: 43 additions & 2 deletions api/entrypoints/routers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from contextlib import asynccontextmanager
import asyncio
import time
from uuid import UUID

import agenta as ag
from fastapi import FastAPI
Expand Down Expand Up @@ -95,6 +96,7 @@
from oss.src.core.folders.service import FoldersService
from oss.src.core.workflows.service import WorkflowsService
from oss.src.core.workflows.service import SimpleWorkflowsService
from oss.src.core.workflows.dtos import WorkflowServiceRequest
from oss.src.core.workflows.static_catalog import StaticWorkflowCatalog
from oss.src.core.evaluators.service import EvaluatorsService
from oss.src.core.evaluators.service import SimpleEvaluatorsService
Expand Down Expand Up @@ -185,6 +187,9 @@
from oss.src.dbs.postgres.sessions.commands.dbes import SessionCommandDBE # noqa: F401
from oss.src.dbs.postgres.sessions.commands.dao import SessionCommandsDAO
from oss.src.dbs.postgres.sessions.executions.dao import SessionExecutionsDAO
from oss.src.dbs.postgres.sessions.inputs.dbes import SessionInputDBE # noqa: F401
from oss.src.dbs.postgres.sessions.inputs.dao import SessionInputsDAO
from oss.src.core.sessions.inputs.service import SessionInputsService
from oss.src.core.sessions.commands.service import SessionCommandsService
from oss.src.dbs.http.sessions.control_delivery_direct import DirectControlDelivery
from oss.src.tasks.asyncio.sessions.orphan_sweep import orphan_sweep_loop
Expand Down Expand Up @@ -602,6 +607,7 @@ async def lifespan(*args, **kwargs):
session_turns_dao = SessionTurnsDAO(engine=_transactions_engine)
session_commands_dao = SessionCommandsDAO(engine=_transactions_engine)
session_executions_dao = SessionExecutionsDAO(engine=_transactions_engine)
session_inputs_dao = SessionInputsDAO(engine=_transactions_engine)

connections_dao = ConnectionsDAO(engine=_transactions_engine)
mounts_dao = MountsDAO(engine=_transactions_engine)
Expand Down Expand Up @@ -868,11 +874,12 @@ async def lifespan(*args, **kwargs):

# Detached workflow start: hand the run to the runner and return on the started handshake
# (no awaiting the run). Shared by both detached consumers (triggers + interactions respond).
async def _dispatch_detached_run(*, project_id, user_id, request) -> str:
async def _dispatch_detached_run(*, project_id, user_id, request, run_id=None) -> str:
result = await workflows_service.invoke_workflow_detached(
project_id=project_id,
user_id=user_id,
request=request,
run_id=run_id,
)
return result.run_id

Expand All @@ -891,6 +898,10 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str:
workflows_service=workflows_service,
interactions_service=interactions_service,
records_service=records_service,
# Read-only: the resume's reference fallback, for a gate row whose own `data.references` is
# empty. Without it the invoke has nothing to resolve a service URL from.
turns_service=session_turns_service,
streams_service=session_streams_service,
dispatch_fn=_dispatch_detached_run,
)

Expand Down Expand Up @@ -1148,8 +1159,37 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str:
streams_service=session_streams_service,
interactions_service=interactions_service,
lock_engine=_lock_engine,
delivery=DirectControlDelivery(),
delivery=DirectControlDelivery(
continue_interaction=lambda command: _interactions_dispatcher.respond_many(
project_id=command.project_id,
user_id=command.created_by_id,
interaction_answers=[
(UUID(item["interaction_id"]), item["answer"])
for item in command.data["answers"]
],
control_command_id=command.id,
continuation_execution_id=command.target_turn_id,
),
continue_input=lambda command: workflows_service.invoke_workflow_detached(
project_id=command.project_id,
user_id=command.created_by_id,
request=WorkflowServiceRequest.model_validate(command.data["request"]),
run_id=command.target_turn_id,
control_command_id=command.id,
),
),
executions_dao=session_executions_dao,
inputs_dao=session_inputs_dao,
)
session_inputs_service = SessionInputsService(
inputs_dao=session_inputs_dao,
interactions_dao=interactions_dao,
streams_service=session_streams_service,
executions_dao=session_executions_dao,
continuation_resumer=session_commands_service.resume_recoverable_continuation,
)
workflows_service.set_session_continuation_resumer(
session_commands_service.resume_recoverable_continuation
)

sessions = SessionsRouter(
Expand All @@ -1163,6 +1203,7 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str:
turns_service=session_turns_service,
sessions_service=sessions_service,
commands_service=session_commands_service,
inputs_service=session_inputs_service,
respond_task=_interactions_worker.respond_interaction,
interactions_dispatcher=_interactions_dispatcher,
)
Expand Down
6 changes: 5 additions & 1 deletion api/entrypoints/worker_streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from oss.src.dbs.postgres.events.dao import EventsDAO
from oss.src.dbs.postgres.secrets.dao import SecretsDAO
from oss.src.dbs.postgres.sessions.interactions.dao import SessionInteractionsDAO
from oss.src.dbs.postgres.sessions.executions.dao import SessionExecutionsDAO
from oss.src.dbs.postgres.sessions.records.dao import RecordsDAO
from oss.src.dbs.postgres.tracing.dao import TracingDAO
from oss.src.dbs.postgres.webhooks.dao import WebhooksDAO
Expand Down Expand Up @@ -88,7 +89,10 @@ async def _build_spans_worker(redis_client: Redis) -> StreamConsumer:
async def _build_records_worker(redis_client: Redis) -> StreamConsumer:
watch_publisher = SessionsWatchPublisher(redis_client=redis_client)
return RecordsWorker(
service=RecordsService(records_dao=RecordsDAO()),
service=RecordsService(
records_dao=RecordsDAO(),
executions_dao=SessionExecutionsDAO(),
),
redis_client=redis_client,
stream_name=RECORD_STREAM_NAME,
consumer_group="worker-records",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""add durable interaction continuation executions

Revision ID: oss000000027
Revises: oss000000026
Create Date: 2026-09-04 12:00:00.000000
"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql


revision: str = "oss000000027"
down_revision: Union[str, None] = "oss000000026"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.drop_constraint("ck_session_commands_kind", "session_commands", type_="check")
op.create_check_constraint(
"ck_session_commands_kind",
"session_commands",
"kind IN ('cancel', 'continue_interaction')",
)

op.alter_column("session_executions", "terminal_outcome", nullable=True)
op.alter_column("session_executions", "settled_by", nullable=True)
op.alter_column("session_executions", "settled_at", nullable=True)
op.add_column(
"session_executions",
sa.Column("state", sa.String(), server_default="terminal", nullable=False),
)
op.add_column(
"session_executions",
sa.Column("parent_execution_id", sa.String(), nullable=True),
)
op.add_column(
"session_executions",
sa.Column("source_interaction_id", sa.UUID(as_uuid=True), nullable=True),
)
op.add_column(
"session_executions",
sa.Column("error", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
)
op.alter_column(
"session_executions", "state", server_default="active", nullable=False
)
op.create_index(
"uq_session_executions_source_interaction",
"session_executions",
["project_id", "source_interaction_id"],
unique=True,
postgresql_where=sa.text("source_interaction_id IS NOT NULL"),
)


def downgrade() -> None:
op.drop_index(
"uq_session_executions_source_interaction", table_name="session_executions"
)
op.drop_column("session_executions", "error")
op.drop_column("session_executions", "source_interaction_id")
op.drop_column("session_executions", "parent_execution_id")
op.drop_column("session_executions", "state")
op.execute("DELETE FROM session_executions WHERE terminal_outcome IS NULL")
op.alter_column("session_executions", "settled_at", nullable=False)
op.alter_column("session_executions", "settled_by", nullable=False)
op.alter_column("session_executions", "terminal_outcome", nullable=False)

op.drop_constraint("ck_session_commands_kind", "session_commands", type_="check")
op.create_check_constraint(
"ck_session_commands_kind", "session_commands", "kind IN ('cancel')"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""add durable session pending inputs

Revision ID: oss000000028
Revises: oss000000027
Create Date: 2026-09-04 15:00:00.000000
"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql


revision: str = "oss000000028"
down_revision: Union[str, None] = "oss000000027"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.drop_constraint("ck_session_commands_kind", "session_commands", type_="check")
op.create_check_constraint(
"ck_session_commands_kind",
"session_commands",
"kind IN ('cancel', 'continue_interaction', 'continue_input')",
)
op.create_table(
"session_inputs",
sa.Column("project_id", sa.UUID(as_uuid=True), nullable=False),
sa.Column("id", sa.UUID(as_uuid=True), nullable=False),
sa.Column("session_id", sa.String(), nullable=False),
sa.Column("content", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
sa.Column("position", sa.BigInteger(), nullable=False),
sa.Column("state", sa.String(), server_default="pending", nullable=False),
sa.Column("policy", sa.String(), nullable=False),
sa.Column("idempotency_key", sa.String(), nullable=False),
sa.Column("request_fingerprint", sa.String(length=64), nullable=False),
sa.Column("promoted_execution_id", sa.String(), nullable=True),
sa.Column(
"created_at",
sa.TIMESTAMP(timezone=True),
server_default=sa.func.current_timestamp(),
nullable=True,
),
sa.Column("updated_at", sa.TIMESTAMP(timezone=True), nullable=True),
sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
sa.Column("created_by_id", sa.UUID(as_uuid=True), nullable=True),
sa.Column("updated_by_id", sa.UUID(as_uuid=True), nullable=True),
sa.Column("deleted_by_id", sa.UUID(as_uuid=True), nullable=True),
sa.CheckConstraint(
"state IN ('pending', 'promoted', 'removed')",
name="ck_session_inputs_state",
),
sa.CheckConstraint(
"policy IN ('queue', 'steer')", name="ck_session_inputs_policy"
),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("project_id", "id"),
)
op.create_index("uq_session_inputs_id", "session_inputs", ["id"], unique=True)
op.create_index(
"uq_session_inputs_idempotency",
"session_inputs",
["project_id", "session_id", "idempotency_key"],
unique=True,
)
op.create_index(
"uq_session_inputs_position",
"session_inputs",
["project_id", "session_id", "position"],
unique=True,
)
op.create_index(
"ix_session_inputs_pending",
"session_inputs",
["project_id", "session_id", "position"],
postgresql_where=sa.text("state = 'pending'"),
)


def downgrade() -> None:
op.drop_index("ix_session_inputs_pending", table_name="session_inputs")
op.drop_index("uq_session_inputs_position", table_name="session_inputs")
op.drop_index("uq_session_inputs_idempotency", table_name="session_inputs")
op.drop_index("uq_session_inputs_id", table_name="session_inputs")
op.drop_table("session_inputs")
op.drop_constraint("ck_session_commands_kind", "session_commands", type_="check")
op.create_check_constraint(
"ck_session_commands_kind",
"session_commands",
"kind IN ('cancel', 'continue_interaction')",
)
Loading
Loading