From 068f29634d75300a71e6bb2568b8e3565e53d0b8 Mon Sep 17 00:00:00 2001 From: Mahmoud Mabrouk Date: Fri, 24 Jul 2026 23:27:52 +0200 Subject: [PATCH 1/3] fix(hosting): wire the session history flags into compose and the env examples The runner service deliberately has no env_file, so AGENTA_SESSIONS_RECONSTRUCT and AGENTA_RECORDS_DURABLE were unreachable in every edition and could not be turned on outside a unit test. List them alongside the other runner variables and document all four flags in the env examples. Claude-Session: https://claude.ai/code/session_01KM69J7uHafgciiN5zfG7qR --- hosting/docker-compose/ee/docker-compose.dev.yml | 6 ++++++ hosting/docker-compose/ee/docker-compose.gh.yml | 6 ++++++ hosting/docker-compose/ee/env.ee.dev.example | 12 ++++++++++++ hosting/docker-compose/ee/env.ee.gh.example | 12 ++++++++++++ hosting/docker-compose/oss/docker-compose.dev.yml | 6 ++++++ hosting/docker-compose/oss/docker-compose.gh.yml | 6 ++++++ hosting/docker-compose/oss/env.oss.dev.example | 12 ++++++++++++ hosting/docker-compose/oss/env.oss.gh.example | 12 ++++++++++++ 8 files changed, 72 insertions(+) diff --git a/hosting/docker-compose/ee/docker-compose.dev.yml b/hosting/docker-compose/ee/docker-compose.dev.yml index b26c27e0cf..29ed893d43 100644 --- a/hosting/docker-compose/ee/docker-compose.dev.yml +++ b/hosting/docker-compose/ee/docker-compose.dev.yml @@ -424,6 +424,12 @@ services: AGENTA_RUNNER_DAYTONA_SESSION_IDLE_TTL_MS: ${AGENTA_RUNNER_DAYTONA_SESSION_IDLE_TTL_MS:-} AGENTA_RUNNER_DAYTONA_SESSION_MAX_WARM: ${AGENTA_RUNNER_DAYTONA_SESSION_MAX_WARM:-} AGENTA_RUNNER_DAYTONA_AUTODELETE_MINUTES: ${AGENTA_RUNNER_DAYTONA_AUTODELETE_MINUTES:-} + # This service has no env_file (see above), so the session flags must be listed + # here or they can never be set. AGENTA_SESSIONS_RECONSTRUCT must be enabled + # together with the web NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY, or a turn that + # carries only its last message loses the conversation. + AGENTA_SESSIONS_RECONSTRUCT: ${AGENTA_SESSIONS_RECONSTRUCT:-} + AGENTA_RECORDS_DURABLE: ${AGENTA_RECORDS_DURABLE:-} # === STORAGE ============================================== # volumes: - ../../../services/runner/src:/app/src diff --git a/hosting/docker-compose/ee/docker-compose.gh.yml b/hosting/docker-compose/ee/docker-compose.gh.yml index 2e71f84034..a27df46a02 100644 --- a/hosting/docker-compose/ee/docker-compose.gh.yml +++ b/hosting/docker-compose/ee/docker-compose.gh.yml @@ -295,6 +295,12 @@ services: AGENTA_RUNNER_DAYTONA_SESSION_IDLE_TTL_MS: ${AGENTA_RUNNER_DAYTONA_SESSION_IDLE_TTL_MS:-} AGENTA_RUNNER_DAYTONA_SESSION_MAX_WARM: ${AGENTA_RUNNER_DAYTONA_SESSION_MAX_WARM:-} AGENTA_RUNNER_DAYTONA_AUTODELETE_MINUTES: ${AGENTA_RUNNER_DAYTONA_AUTODELETE_MINUTES:-} + # This service has no env_file (see above), so the session flags must be listed + # here or they can never be set. AGENTA_SESSIONS_RECONSTRUCT must be enabled + # together with the web NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY, or a turn that + # carries only its last message loses the conversation. + AGENTA_SESSIONS_RECONSTRUCT: ${AGENTA_SESSIONS_RECONSTRUCT:-} + AGENTA_RECORDS_DURABLE: ${AGENTA_RECORDS_DURABLE:-} # === SUBSCRIPTION MOUNTS (opt-in) ========================= # # Use your own harness subscription for LOCAL runs instead of a managed API # key. Mount the login READ-WRITE: the harness runs directly out of the mount and diff --git a/hosting/docker-compose/ee/env.ee.dev.example b/hosting/docker-compose/ee/env.ee.dev.example index 0782fe2c74..94bea8b7b0 100644 --- a/hosting/docker-compose/ee/env.ee.dev.example +++ b/hosting/docker-compose/ee/env.ee.dev.example @@ -111,6 +111,18 @@ AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER=local # 5 min per tool call. # AGENTA_RUNNER_TOOL_CALL_TIMEOUT_MS=300000 +# --- Session history: last-message-only turns (all four default off) --- +# The client sends only the newest user message and the runner rebuilds the earlier turns +# from the durable session record log, which keeps requests and traces small on a long +# conversation. The first two must be flipped together: with the web flag on and the runner +# flag off, a turn arrives with no history at all. +# AGENTA_SESSIONS_RECONSTRUCT=true +# NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY=true +# Records become the only copy of the conversation, so make writing them durable and keep +# the structure of a record whose body exceeds the API size cap. +# AGENTA_RECORDS_DURABLE=true +# AGENTA_RECORDS_SMART_TRUNCATION=true + # ================================================================== # # Agenta - API # ================================================================== # diff --git a/hosting/docker-compose/ee/env.ee.gh.example b/hosting/docker-compose/ee/env.ee.gh.example index a8de6f8618..3cef8caad3 100644 --- a/hosting/docker-compose/ee/env.ee.gh.example +++ b/hosting/docker-compose/ee/env.ee.gh.example @@ -113,6 +113,18 @@ AGENTA_RUNNER_TOKEN=replace-me # 5 min per tool call. # AGENTA_RUNNER_TOOL_CALL_TIMEOUT_MS=300000 +# --- Session history: last-message-only turns (all four default off) --- +# The client sends only the newest user message and the runner rebuilds the earlier turns +# from the durable session record log, which keeps requests and traces small on a long +# conversation. The first two must be flipped together: with the web flag on and the runner +# flag off, a turn arrives with no history at all. +# AGENTA_SESSIONS_RECONSTRUCT=true +# NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY=true +# Records become the only copy of the conversation, so make writing them durable and keep +# the structure of a record whose body exceeds the API size cap. +# AGENTA_RECORDS_DURABLE=true +# AGENTA_RECORDS_SMART_TRUNCATION=true + # ================================================================== # # Agenta - API # ================================================================== # diff --git a/hosting/docker-compose/oss/docker-compose.dev.yml b/hosting/docker-compose/oss/docker-compose.dev.yml index 604d62bfa1..b178dfe07d 100644 --- a/hosting/docker-compose/oss/docker-compose.dev.yml +++ b/hosting/docker-compose/oss/docker-compose.dev.yml @@ -410,6 +410,12 @@ services: AGENTA_RUNNER_DAYTONA_SESSION_IDLE_TTL_MS: ${AGENTA_RUNNER_DAYTONA_SESSION_IDLE_TTL_MS:-} AGENTA_RUNNER_DAYTONA_SESSION_MAX_WARM: ${AGENTA_RUNNER_DAYTONA_SESSION_MAX_WARM:-} AGENTA_RUNNER_DAYTONA_AUTODELETE_MINUTES: ${AGENTA_RUNNER_DAYTONA_AUTODELETE_MINUTES:-} + # This service has no env_file (see above), so the session flags must be listed + # here or they can never be set. AGENTA_SESSIONS_RECONSTRUCT must be enabled + # together with the web NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY, or a turn that + # carries only its last message loses the conversation. + AGENTA_SESSIONS_RECONSTRUCT: ${AGENTA_SESSIONS_RECONSTRUCT:-} + AGENTA_RECORDS_DURABLE: ${AGENTA_RECORDS_DURABLE:-} # === STORAGE ============================================== # volumes: - ../../../services/runner/src:/app/src diff --git a/hosting/docker-compose/oss/docker-compose.gh.yml b/hosting/docker-compose/oss/docker-compose.gh.yml index a9777adfa9..09f9f0e802 100644 --- a/hosting/docker-compose/oss/docker-compose.gh.yml +++ b/hosting/docker-compose/oss/docker-compose.gh.yml @@ -312,6 +312,12 @@ services: AGENTA_RUNNER_DAYTONA_SESSION_IDLE_TTL_MS: ${AGENTA_RUNNER_DAYTONA_SESSION_IDLE_TTL_MS:-} AGENTA_RUNNER_DAYTONA_SESSION_MAX_WARM: ${AGENTA_RUNNER_DAYTONA_SESSION_MAX_WARM:-} AGENTA_RUNNER_DAYTONA_AUTODELETE_MINUTES: ${AGENTA_RUNNER_DAYTONA_AUTODELETE_MINUTES:-} + # This service has no env_file (see above), so the session flags must be listed + # here or they can never be set. AGENTA_SESSIONS_RECONSTRUCT must be enabled + # together with the web NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY, or a turn that + # carries only its last message loses the conversation. + AGENTA_SESSIONS_RECONSTRUCT: ${AGENTA_SESSIONS_RECONSTRUCT:-} + AGENTA_RECORDS_DURABLE: ${AGENTA_RECORDS_DURABLE:-} # === SUBSCRIPTION MOUNTS (opt-in) ========================= # # Use your own harness subscription for LOCAL runs instead of a managed API # key. Mount the login READ-WRITE: the harness runs directly out of the mount and diff --git a/hosting/docker-compose/oss/env.oss.dev.example b/hosting/docker-compose/oss/env.oss.dev.example index b75eb975e1..37e70a43c1 100644 --- a/hosting/docker-compose/oss/env.oss.dev.example +++ b/hosting/docker-compose/oss/env.oss.dev.example @@ -111,6 +111,18 @@ AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER=local # 5 min per tool call. # AGENTA_RUNNER_TOOL_CALL_TIMEOUT_MS=300000 +# --- Session history: last-message-only turns (all four default off) --- +# The client sends only the newest user message and the runner rebuilds the earlier turns +# from the durable session record log, which keeps requests and traces small on a long +# conversation. The first two must be flipped together: with the web flag on and the runner +# flag off, a turn arrives with no history at all. +# AGENTA_SESSIONS_RECONSTRUCT=true +# NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY=true +# Records become the only copy of the conversation, so make writing them durable and keep +# the structure of a record whose body exceeds the API size cap. +# AGENTA_RECORDS_DURABLE=true +# AGENTA_RECORDS_SMART_TRUNCATION=true + # ================================================================== # # Agenta - API # ================================================================== # diff --git a/hosting/docker-compose/oss/env.oss.gh.example b/hosting/docker-compose/oss/env.oss.gh.example index 7cf53d9c10..dfa65636ea 100644 --- a/hosting/docker-compose/oss/env.oss.gh.example +++ b/hosting/docker-compose/oss/env.oss.gh.example @@ -113,6 +113,18 @@ AGENTA_RUNNER_TOKEN=replace-me # 5 min per tool call. # AGENTA_RUNNER_TOOL_CALL_TIMEOUT_MS=300000 +# --- Session history: last-message-only turns (all four default off) --- +# The client sends only the newest user message and the runner rebuilds the earlier turns +# from the durable session record log, which keeps requests and traces small on a long +# conversation. The first two must be flipped together: with the web flag on and the runner +# flag off, a turn arrives with no history at all. +# AGENTA_SESSIONS_RECONSTRUCT=true +# NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY=true +# Records become the only copy of the conversation, so make writing them durable and keep +# the structure of a record whose body exceeds the API size cap. +# AGENTA_RECORDS_DURABLE=true +# AGENTA_RECORDS_SMART_TRUNCATION=true + # ================================================================== # # Agenta - API # ================================================================== # From 8688ed4ca72a84f665451dc9c9f278b19f961ae5 Mon Sep 17 00:00:00 2001 From: Kaosiso Ezealigo Date: Mon, 27 Jul 2026 14:30:39 +0200 Subject: [PATCH 2/3] style(agent-chat): fix prettier formatting in transcriptToMessages.test.ts Three unformatted lines in this test (added upstream in fix/transcript-hygiene) fail both the format and lint CI jobs, and that red is inherited by every PR stacked on top, including this one. Co-Authored-By: Claude Sonnet 5 --- .../AgentChatSlice/assets/transcriptToMessages.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/oss/src/components/AgentChatSlice/assets/transcriptToMessages.test.ts b/web/oss/src/components/AgentChatSlice/assets/transcriptToMessages.test.ts index 2429a036a0..8b09a0d99a 100644 --- a/web/oss/src/components/AgentChatSlice/assets/transcriptToMessages.test.ts +++ b/web/oss/src/components/AgentChatSlice/assets/transcriptToMessages.test.ts @@ -279,9 +279,9 @@ describe("transcriptToMessages approval hydration", () => { // "View full trace" on the merged turn links to the RESUME trace (where the tool ran), // not the paused turn's trace. - expect( - (assistant as unknown as {metadata?: {traceId?: string}}).metadata?.traceId, - ).toBe("trace-resume") + expect((assistant as unknown as {metadata?: {traceId?: string}}).metadata?.traceId).toBe( + "trace-resume", + ) }) }) From 4bc2dece3896f0d9e3600c051dc7fb19f0fe82e4 Mon Sep 17 00:00:00 2001 From: Kaosiso Ezealigo Date: Mon, 27 Jul 2026 17:16:46 +0200 Subject: [PATCH 3/3] fix(web): read sandbox_id from the turns ledger, fix SessionStream id nullability Two build-breaking type errors left over from the storage rework's schema change (fetchState now hits the stream endpoint, not the old session_state one): - StatesTab read sandbox_id/data off the stream row. Both moved: sandbox_id now lives on the latest session_turns row (session_states no longer exists), so fetch it from there instead. - The local sessionStreamSchema required a non-null id, but the backend's Identifier mixin has always declared id as Optional[UUID]. That made the Fern-generated type unassignable where StreamsTab's deriveStreamNest call expected the local schema's type. Loosened to match backend reality. Co-Authored-By: Claude Sonnet 5 --- .../src/components/SessionInspector/api.ts | 9 +++++++++ .../SessionInspector/tabs/StatesTab.tsx | 19 ++++++++++++++++--- .../src/session/core/schema.ts | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/web/oss/src/components/SessionInspector/api.ts b/web/oss/src/components/SessionInspector/api.ts index 5b53e1304b..14a83c081a 100644 --- a/web/oss/src/components/SessionInspector/api.ts +++ b/web/oss/src/components/SessionInspector/api.ts @@ -43,6 +43,15 @@ export async function fetchState(sessionId: string, projectId?: string | null) { return res.stream ?? null } +// sandbox_id lives on the latest session_turns row, not on the stream (storage rework). +export async function fetchLatestTurn(sessionId: string, projectId?: string | null) { + const res = await getSessionsClient().queryTurns( + {query: {session_id: sessionId}, windowing: {order: "descending", limit: 1}}, + scope(projectId), + ) + return res.turns?.[0] ?? null +} + export async function fetchMounts(sessionId: string, projectId?: string | null) { return getSessionsClient().querySessionMounts({session_id: sessionId}, scope(projectId)) } diff --git a/web/oss/src/components/SessionInspector/tabs/StatesTab.tsx b/web/oss/src/components/SessionInspector/tabs/StatesTab.tsx index b5366c9064..68a77187e2 100644 --- a/web/oss/src/components/SessionInspector/tabs/StatesTab.tsx +++ b/web/oss/src/components/SessionInspector/tabs/StatesTab.tsx @@ -7,7 +7,7 @@ import {useAtomValue} from "jotai" import {sessionStatusAtomFamily} from "@/oss/components/AgentChatSlice/state/sessions" import {projectIdAtom} from "@/oss/state/project" -import {fetchState} from "../api" +import {fetchLatestTurn, fetchState} from "../api" const {Text} = Typography @@ -28,8 +28,21 @@ const StatesTab = ({sessionId}: {sessionId: string}) => { refetchInterval: runStatus === "running" ? 2500 : false, }) + // sandbox_id moved off the stream row onto the latest turn (storage rework) — fetched + // separately since it's a different resource. + const turnQueryKey = ["session-inspector", "latest-turn", projectId, sessionId] + const {data: latestTurn} = useQuery({ + queryKey: turnQueryKey, + queryFn: () => fetchLatestTurn(sessionId, projectId), + enabled: Boolean(sessionId), + refetchOnWindowFocus: false, + refetchOnMount: "always", + refetchInterval: runStatus === "running" ? 2500 : false, + }) + useEffect(() => { void queryClient.invalidateQueries({queryKey}) + void queryClient.invalidateQueries({queryKey: turnQueryKey}) }, [runStatus, sessionId, projectId]) if (isLoading) return @@ -46,7 +59,7 @@ const StatesTab = ({sessionId}: {sessionId: string}) => {
sandbox_id
- {data.sandbox_id ?? "—"} + {latestTurn?.sandbox_id ?? "—"}
updated_at
@@ -56,7 +69,7 @@ const StatesTab = ({sessionId}: {sessionId: string}) => {
data
-                    {JSON.stringify(data.data ?? {}, null, 2)}
+                    {JSON.stringify(latestTurn ?? {}, null, 2)}
                 
diff --git a/web/packages/agenta-entities/src/session/core/schema.ts b/web/packages/agenta-entities/src/session/core/schema.ts index 2283da115f..928f9fbb83 100644 --- a/web/packages/agenta-entities/src/session/core/schema.ts +++ b/web/packages/agenta-entities/src/session/core/schema.ts @@ -88,7 +88,7 @@ export type SessionInteractionKind = "user_approval" | "user_input" | "client_to * client-side. */ export const sessionStreamSchema = z.object({ - id: z.string(), + id: z.string().nullish(), project_id: z.string(), session_id: z.string(), turn_id: z.string().nullish(),