Skip to content

Commit 386c63e

Browse files
committed
feat(sessions): rebase session turns/streams backend onto v0.105.5
Rebases the backend two-thirds of JP's feat/sessions-extensions (PR #5363) onto current main (v0.105.5). Covers api, SDK, Fern python client, and the web generated client: the append-only session_turns domain replacing session_states, the stream command discriminator rename (prompt -> inputs/data), the root /sessions router and SessionsService, /kill teardown wiring, span identity columns plus ag.agent.id, and mounts agent_id. Ported from feat/sessions-extensions@1532ec7fe5 (JP). Reconciliations with the release main: - Kept main's pi-openai model_ref threading (PiHarness/AgentaHarness) and the EndpointResolutionError export alongside JP's HarnessType -> HarnessKind rename. - Kept main's 0.105.5 version in api/pyproject.toml while re-adding JP's greenlet dependency; api/uv.lock updated for greenlet only. - Kept main's sdks/python and services uv.lock (JP's were stale transitive bumps). Claude-Session: https://claude.ai/code/session_01DnWRxU3dCJ11hgDidm26vW
1 parent 3b0c0cb commit 386c63e

221 files changed

Lines changed: 8415 additions & 2088 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/entrypoints/routers.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,6 @@
137137
from oss.src.core.ai_services.service import AIServicesService
138138
from oss.src.apis.fastapi.ai_services.router import AIServicesRouter
139139

140-
from oss.src.dbs.postgres.sessions.states.dbes import SessionStateDBE # noqa: F401
141-
from oss.src.dbs.postgres.sessions.states.dao import SessionStatesDAO
142-
from oss.src.core.sessions.states.service import SessionStatesService
143-
144140
from oss.src.core.accounts.service import PlatformAdminAccountsService
145141
from oss.src.apis.fastapi.accounts.router import PlatformAdminAccountsRouter
146142
from oss.src.dbs.postgres.gateway.connections.dao import ConnectionsDAO
@@ -178,10 +174,15 @@
178174
from oss.src.tasks.asyncio.sessions.orphan_sweep import orphan_sweep_loop
179175
from oss.src.dbs.redis.shared.engine import get_lock_engine
180176

177+
from oss.src.dbs.postgres.sessions.turns.dbes import SessionTurnDBE # noqa: F401
178+
from oss.src.dbs.postgres.sessions.turns.dao import SessionTurnsDAO
179+
from oss.src.core.sessions.turns.service import SessionTurnsService
180+
181181
# Interactions
182182
from oss.src.dbs.postgres.sessions.interactions.dbes import SessionInteractionDBE # noqa: F401
183183
from oss.src.dbs.postgres.sessions.interactions.dao import SessionInteractionsDAO
184184
from oss.src.core.sessions.interactions.service import SessionInteractionsService
185+
from oss.src.core.sessions.service import SessionsService
185186
from oss.src.tasks.asyncio.sessions.interactions_dispatcher import (
186187
InteractionsDispatcher,
187188
)
@@ -549,6 +550,7 @@ async def lifespan(*args, **kwargs):
549550
evaluations_dao = EvaluationsDAO(engine=_transactions_engine)
550551
folders_dao = FoldersDAO(engine=_transactions_engine)
551552
session_streams_dao = SessionStreamsDAO(engine=_transactions_engine)
553+
session_turns_dao = SessionTurnsDAO(engine=_transactions_engine)
552554

553555
connections_dao = ConnectionsDAO(engine=_transactions_engine)
554556
mounts_dao = MountsDAO(engine=_transactions_engine)
@@ -616,6 +618,10 @@ async def lifespan(*args, **kwargs):
616618
lock_engine=_lock_engine,
617619
)
618620

621+
session_turns_service = SessionTurnsService(
622+
turns_dao=session_turns_dao,
623+
)
624+
619625
workflows_service = WorkflowsService(
620626
workflows_dao=workflows_dao,
621627
static_catalog=StaticWorkflowCatalog(),
@@ -1026,22 +1032,26 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str:
10261032
ai_services_service=ai_services_service,
10271033
)
10281034

1029-
# SESSION STATES ---------------------------------------------------------------
1035+
# SESSIONS ---------------------------------------------------------------------
1036+
# Session header rename (name/description) lives on the streams router
1037+
# (PUT /sessions/streams/header) via streams_service.set_header.
10301038

1031-
session_states_dao = SessionStatesDAO(engine=_transactions_engine)
1032-
1033-
session_states_service = SessionStatesService(
1034-
session_states_dao=session_states_dao,
1039+
sessions_service = SessionsService(
1040+
streams_service=session_streams_service,
1041+
turns_service=session_turns_service,
1042+
interactions_service=interactions_service,
1043+
mounts_service=mounts_service,
10351044
)
10361045

10371046
sessions = SessionsRouter(
10381047
streams_service=session_streams_service,
1039-
states_service=session_states_service,
10401048
records_service=records_service,
10411049
interactions_service=interactions_service,
10421050
workflows_service=workflows_service,
10431051
session_mounts_service=session_mounts_service,
10441052
mounts_service=mounts_service,
1053+
turns_service=session_turns_service,
1054+
sessions_service=sessions_service,
10451055
respond_task=_interactions_worker.respond_interaction,
10461056
)
10471057

@@ -1479,15 +1489,20 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str:
14791489
tags=["Sessions"],
14801490
)
14811491

1492+
app.include_router(
1493+
router=sessions.turns.router,
1494+
prefix="/sessions/turns",
1495+
tags=["Sessions"],
1496+
)
1497+
14821498
app.include_router(
14831499
router=platform_admin_accounts.router,
14841500
prefix="/admin",
14851501
tags=["Admin"],
14861502
)
14871503

14881504
app.include_router(
1489-
router=sessions.states.router,
1490-
prefix="/sessions",
1505+
router=sessions.root.router,
14911506
tags=["Sessions"],
14921507
)
14931508

api/oss/databases/postgres/migrations/core/env.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import oss.src.dbs.postgres.users.dbes # noqa: F401
2323
import oss.src.dbs.postgres.workflows.dbes # noqa: F401
2424
import oss.src.dbs.postgres.webhooks.dbes # noqa: F401
25-
import oss.src.dbs.postgres.sessions.states.dbes # noqa: F401
2625

2726

2827
# this is the Alembic Config object, which provides
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""add session_turns table
2+
3+
Revision ID: oss000000014
4+
Revises: oss000000013
5+
Create Date: 2026-07-17 00:00:00.000000
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
import sqlalchemy as sa
13+
from sqlalchemy.dialects import postgresql
14+
15+
revision: str = "oss000000014"
16+
down_revision: Union[str, None] = "oss000000013"
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
op.create_table(
23+
"session_turns",
24+
sa.Column("id", sa.UUID(as_uuid=True), nullable=False),
25+
sa.Column("project_id", sa.UUID(as_uuid=True), nullable=False),
26+
sa.Column("session_id", sa.String(), nullable=False),
27+
sa.Column("stream_id", sa.UUID(as_uuid=True), nullable=False),
28+
sa.Column("turn_index", sa.Integer(), nullable=False),
29+
sa.Column("harness_kind", sa.String(), nullable=False),
30+
sa.Column("agent_session_id", sa.String(), nullable=True),
31+
sa.Column("sandbox_id", sa.String(), nullable=True),
32+
sa.Column(
33+
"references",
34+
postgresql.JSONB(none_as_null=True),
35+
nullable=True,
36+
),
37+
sa.Column("trace_id", sa.UUID(as_uuid=True), nullable=True),
38+
sa.Column("span_id", sa.UUID(as_uuid=True), nullable=True),
39+
sa.Column("start_time", sa.TIMESTAMP(timezone=True), nullable=True),
40+
sa.Column("end_time", sa.TIMESTAMP(timezone=True), nullable=True),
41+
sa.Column(
42+
"created_at",
43+
sa.TIMESTAMP(timezone=True),
44+
server_default=sa.func.current_timestamp(),
45+
nullable=True,
46+
),
47+
sa.Column("updated_at", sa.TIMESTAMP(timezone=True), nullable=True),
48+
sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
49+
sa.Column("created_by_id", sa.UUID(as_uuid=True), nullable=True),
50+
sa.Column("updated_by_id", sa.UUID(as_uuid=True), nullable=True),
51+
sa.Column("deleted_by_id", sa.UUID(as_uuid=True), nullable=True),
52+
sa.ForeignKeyConstraint(
53+
["project_id"],
54+
["projects.id"],
55+
ondelete="CASCADE",
56+
),
57+
sa.ForeignKeyConstraint(
58+
["project_id", "stream_id"],
59+
["session_streams.project_id", "session_streams.id"],
60+
ondelete="NO ACTION",
61+
),
62+
sa.PrimaryKeyConstraint("project_id", "id"),
63+
)
64+
op.create_index(
65+
"ix_session_turns_project_id_session_id",
66+
"session_turns",
67+
["project_id", "session_id"],
68+
)
69+
op.create_index(
70+
"ix_session_turns_project_id_session_id_turn_index",
71+
"session_turns",
72+
["project_id", "session_id", "turn_index"],
73+
)
74+
op.create_index(
75+
"ix_session_turns_references",
76+
"session_turns",
77+
["references"],
78+
postgresql_using="gin",
79+
postgresql_ops={"references": "jsonb_path_ops"},
80+
)
81+
82+
83+
def downgrade() -> None:
84+
op.drop_index(
85+
"ix_session_turns_references",
86+
table_name="session_turns",
87+
)
88+
op.drop_index(
89+
"ix_session_turns_project_id_session_id_turn_index",
90+
table_name="session_turns",
91+
)
92+
op.drop_index(
93+
"ix_session_turns_project_id_session_id",
94+
table_name="session_turns",
95+
)
96+
op.drop_table("session_turns")
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""add name/description header to session_streams
2+
3+
Revision ID: oss000000015
4+
Revises: oss000000014
5+
Create Date: 2026-07-17 00:00:00.000000
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
import sqlalchemy as sa
13+
14+
revision: str = "oss000000015"
15+
down_revision: Union[str, None] = "oss000000014"
16+
branch_labels: Union[str, Sequence[str], None] = None
17+
depends_on: Union[str, Sequence[str], None] = None
18+
19+
20+
def upgrade() -> None:
21+
op.add_column(
22+
"session_streams",
23+
sa.Column("name", sa.String(), nullable=True),
24+
)
25+
op.add_column(
26+
"session_streams",
27+
sa.Column("description", sa.String(), nullable=True),
28+
)
29+
30+
31+
def downgrade() -> None:
32+
op.drop_column("session_streams", "description")
33+
op.drop_column("session_streams", "name")
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""add mounts agent_id column, index, and backfill
2+
3+
Mirrors session_id: a bare, nullable varchar populated only for agent mounts
4+
(session mounts stay agent_id-null), with a partial index for querying "mounts
5+
for agent X" the same way session_id already supports "mounts for session X".
6+
7+
Backfill: mounts are core DB, so a data migration is allowed here (unlike the
8+
tracing DB). Agent-mount slugs are minted as
9+
`__ag__agent__<canonical_artifact_id>__<name>` (`mint_agent_slug`,
10+
`core/mounts/service.py`) — the artifact id is the raw canonical (lowercase)
11+
UUID, not a hash, so it is deterministically recoverable straight out of the
12+
slug. Session-mount slugs (`__ag__session__<uuid5(session_id)>__<name>`) hash
13+
the session id, so no session_id backfill is possible or attempted here.
14+
15+
Revision ID: oss000000016
16+
Revises: oss000000015
17+
Create Date: 2026-07-17 00:00:00.000000
18+
19+
"""
20+
21+
from typing import Sequence, Union
22+
23+
from alembic import op
24+
import sqlalchemy as sa
25+
26+
revision: str = "oss000000016"
27+
down_revision: Union[str, None] = "oss000000015"
28+
branch_labels: Union[str, Sequence[str], None] = None
29+
depends_on: Union[str, Sequence[str], None] = None
30+
31+
_AGENT_SLUG_PREFIX = "__ag__agent__"
32+
33+
34+
def upgrade() -> None:
35+
op.add_column("mounts", sa.Column("agent_id", sa.String(), nullable=True))
36+
37+
op.create_index(
38+
"ix_mounts_project_id_agent_id",
39+
"mounts",
40+
["project_id", "agent_id"],
41+
unique=False,
42+
postgresql_where=sa.text("agent_id IS NOT NULL"),
43+
)
44+
45+
conn = op.get_bind()
46+
conn.execute(
47+
sa.text(
48+
f"""
49+
UPDATE mounts
50+
SET agent_id = split_part(substr(slug, {len(_AGENT_SLUG_PREFIX) + 1}), '__', 1)
51+
WHERE left(slug, {len(_AGENT_SLUG_PREFIX)}) = '{_AGENT_SLUG_PREFIX}'
52+
"""
53+
)
54+
)
55+
56+
57+
def downgrade() -> None:
58+
op.drop_index("ix_mounts_project_id_agent_id", table_name="mounts")
59+
op.drop_column("mounts", "agent_id")
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""drop session_states table
2+
3+
Superseded by the session_streams header (name/description) — the /sessions/states/
4+
router now reads/writes the merged stream row via streams_service. The standalone
5+
table, its DAO/service, and DBE are orphaned; drop the physical table.
6+
7+
Revision ID: oss000000017
8+
Revises: oss000000016
9+
"""
10+
11+
from typing import Sequence, Union
12+
13+
from alembic import op
14+
import sqlalchemy as sa
15+
from sqlalchemy.dialects import postgresql
16+
17+
18+
revision: str = "oss000000017"
19+
down_revision: Union[str, None] = "oss000000016"
20+
branch_labels: Union[str, Sequence[str], None] = None
21+
depends_on: Union[str, Sequence[str], None] = None
22+
23+
24+
def upgrade() -> None:
25+
op.drop_table("session_states")
26+
27+
28+
def downgrade() -> None:
29+
op.create_table(
30+
"session_states",
31+
sa.Column("project_id", sa.UUID(), nullable=False),
32+
sa.Column("id", sa.UUID(), nullable=False),
33+
sa.Column("session_id", sa.String(), nullable=False),
34+
sa.Column("sandbox_id", sa.String(), nullable=True),
35+
sa.Column(
36+
"data",
37+
postgresql.JSON(astext_type=sa.Text()),
38+
nullable=True,
39+
),
40+
sa.Column("flags", postgresql.JSONB(none_as_null=True), nullable=True),
41+
sa.Column("tags", postgresql.JSONB(none_as_null=True), nullable=True),
42+
sa.Column("meta", postgresql.JSON(none_as_null=True), nullable=True),
43+
sa.Column(
44+
"created_at",
45+
sa.TIMESTAMP(timezone=True),
46+
server_default=sa.text("CURRENT_TIMESTAMP"),
47+
nullable=True,
48+
),
49+
sa.Column("updated_at", sa.TIMESTAMP(timezone=True), nullable=True),
50+
sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
51+
sa.Column("created_by_id", sa.UUID(), nullable=True),
52+
sa.Column("updated_by_id", sa.UUID(), nullable=True),
53+
sa.Column("deleted_by_id", sa.UUID(), nullable=True),
54+
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="CASCADE"),
55+
sa.PrimaryKeyConstraint("project_id", "id"),
56+
sa.UniqueConstraint(
57+
"project_id",
58+
"session_id",
59+
name="uq_session_states_project_session_id",
60+
),
61+
)

0 commit comments

Comments
 (0)