Skip to content

Commit 882eebc

Browse files
committed
feat(web): send only the trailing user message per agent turn when enabled
Behind NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY (default off; MUST pair with the backend's AGENTA_SESSIONS_RECONSTRUCT), buildAgentRequest sends just the newest user message and lets the runner rebuild prior turns from the durable record log — shrinking request and trace payloads (the AGE-3970 trace-drawer OOM driver). Only a fresh user turn is trimmed: a HITL resume, whose trailing turn carries the settled answer, keeps the full history so the answer still binds to its tool call, as does any run without a session id. New isSessionsLastMessageOnlyEnabled helper.
1 parent 5a2f595 commit 882eebc

4 files changed

Lines changed: 69 additions & 2 deletions

File tree

web/packages/agenta-playground/src/state/execution/agentRequest.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
workflowMolecule,
3131
type AgentTemplate,
3232
} from "@agenta/entities/workflow"
33+
import {isSessionsLastMessageOnlyEnabled} from "@agenta/shared/api"
3334
import {projectIdAtom} from "@agenta/shared/state"
3435
import {getDefaultStore} from "jotai"
3536

@@ -400,13 +401,24 @@ export async function buildAgentRequest(
400401
// Strip answer-less assistant turns so a "no response" turn can't poison the next request.
401402
const history = messages.filter(hasAnswer)
402403

404+
// Last-message-only (flag-gated; MUST pair with the backend's AGENTA_SESSIONS_RECONSTRUCT).
405+
// On a fresh user turn, send just the trailing user message and let the runner rebuild prior
406+
// turns from the durable record log — smaller request + trace payloads. A HITL resume, whose
407+
// trailing turn carries the settled answer (not a user turn), keeps the full history so the
408+
// answer still binds to its tool call.
409+
const lastMessage = history[history.length - 1] as {role?: unknown} | undefined
410+
const outboundMessages =
411+
isSessionsLastMessageOnlyEnabled() && opts.sessionId && lastMessage?.role === "user"
412+
? [lastMessage]
413+
: history
414+
403415
return {
404416
invocationUrl: url,
405417
headers,
406418
requestBody: {
407419
session_id: opts.sessionId,
408420
references,
409-
data: {inputs: {messages: history}, parameters},
421+
data: {inputs: {messages: outboundMessages}, parameters},
410422
},
411423
}
412424
}

web/packages/agenta-playground/tests/unit/agentRequest.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* headers + project come from the real `executionHeadersAtom` / `projectIdAtom`.
1414
*/
1515
import {createStore, type PrimitiveAtom} from "jotai"
16-
import {describe, expect, it, beforeEach, vi} from "vitest"
16+
import {describe, expect, it, beforeEach, afterEach, vi} from "vitest"
1717

1818
vi.mock("@agenta/entities/workflow", async (importOriginal) => {
1919
const actual = (await importOriginal()) as any
@@ -177,6 +177,48 @@ describe("buildAgentRequest", () => {
177177
expect(await buildAgentRequest("e", [], {sessionId: "s1", store})).toBeNull()
178178
})
179179

180+
describe("last-message-only (NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY)", () => {
181+
const u1 = {role: "user", parts: [{type: "text", text: "q1"}]}
182+
const a1 = {role: "assistant", parts: [{type: "text", text: "a1"}]}
183+
const u2 = {role: "user", parts: [{type: "text", text: "q2"}]}
184+
185+
// Runtime override path (getEnv checks globalThis.__env before the build-time snapshot).
186+
const enableFlag = () => {
187+
;(globalThis as any).__env = {NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY: "true"}
188+
}
189+
afterEach(() => {
190+
delete (globalThis as any).__env
191+
})
192+
193+
const outMessages = async (msgs: unknown[], sessionId = "s1") => {
194+
seed(store, "e", {})
195+
const req = await buildAgentRequest("e", msgs, {sessionId, store})
196+
return (req!.requestBody.data as any).inputs.messages
197+
}
198+
199+
it("sends only the trailing user message when enabled", async () => {
200+
enableFlag()
201+
expect(await outMessages([u1, a1, u2])).toEqual([u2])
202+
})
203+
204+
it("sends the full history by default (flag off)", async () => {
205+
const out = await outMessages([u1, a1, u2])
206+
expect(out).toEqual([u1, a1, u2])
207+
})
208+
209+
it("keeps the full history on a resume (trailing assistant) even when enabled", async () => {
210+
enableFlag()
211+
const out = await outMessages([u1, a1])
212+
expect(out.length).toBeGreaterThan(1)
213+
expect(out[out.length - 1].role).toBe("assistant")
214+
})
215+
216+
it("sends the full history when there is no session id", async () => {
217+
enableFlag()
218+
expect(await outMessages([u1, a1, u2], "")).toEqual([u1, a1, u2])
219+
})
220+
})
221+
180222
it("nests messages under data.inputs + draft-aware parameters under data, with session_id", async () => {
181223
seed(store, "e", {config: {temperature: 0.9, prompt: {x: 1}}})
182224
const req = await buildAgentRequest("e", [{role: "user"}], {sessionId: "s1", store})

web/packages/agenta-shared/src/api/env.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const processEnv = {
3939
NEXT_PUBLIC_AGENTA_SANDBOX_LOCAL_ENABLED: process.env.NEXT_PUBLIC_AGENTA_SANDBOX_LOCAL_ENABLED,
4040
NEXT_PUBLIC_AGENTA_ENABLED_SANDBOX_PROVIDERS:
4141
process.env.NEXT_PUBLIC_AGENTA_ENABLED_SANDBOX_PROVIDERS,
42+
NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY: process.env.NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY,
4243
}
4344

4445
/**
@@ -84,6 +85,17 @@ export const isSandboxLocalEnabled = (): boolean => {
8485
return SANDBOX_LOCAL_TRUTHY.has(raw.trim().toLowerCase())
8586
}
8687

88+
/**
89+
* Send only the trailing user message per agent turn and let the runner rebuild prior history
90+
* from the durable record log. Default OFF — enable ONLY where the backend runs with
91+
* `AGENTA_SESSIONS_RECONSTRUCT=true` (they must be flipped together), or a cold turn loses its
92+
* context.
93+
*/
94+
export const isSessionsLastMessageOnlyEnabled = (): boolean => {
95+
const raw = getEnv("NEXT_PUBLIC_SESSIONS_LAST_MESSAGE_ONLY") || "false"
96+
return SANDBOX_LOCAL_TRUTHY.has(raw.trim().toLowerCase())
97+
}
98+
8799
/** The sandbox providers this deployment enabled, normalized to lowercase ids. Unset/empty
88100
* falls back to `["local"]` so the picker never hides every option. */
89101
export const getEnabledSandboxProviders = (): string[] => {

web/packages/agenta-shared/src/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export {
77
getAgentaApiUrl,
88
getAgentaWebUrl,
99
isSandboxLocalEnabled,
10+
isSessionsLastMessageOnlyEnabled,
1011
getEnabledSandboxProviders,
1112
processEnv,
1213
} from "./env"

0 commit comments

Comments
 (0)