Skip to content
Merged
Show file tree
Hide file tree
Changes from 110 commits
Commits
Show all changes
137 commits
Select commit Hold shift + click to select a range
26e9e30
chore(frontend): rebase session work onto request-optimizations base
ardaerzin Jul 10, 2026
a40a6e6
feat(frontend): hydrate session history from records in AgentConversa…
ardaerzin Jul 10, 2026
63e3cf6
Merge remote-tracking branch 'origin/big-agents' into big-agents-add-…
ardaerzin Jul 10, 2026
ad050e1
fix(frontend): re-export partitionElicitationDraft from @agenta/share…
ardaerzin Jul 10, 2026
b665d7d
Merge remote-tracking branch 'origin/big-agents' into big-agents-add-…
ardaerzin Jul 10, 2026
8e451d5
Merge remote-tracking branch 'origin/feat/sessions-continuity' into b…
ardaerzin Jul 10, 2026
9e882dd
feat(frontend): mark unresolved connected-app tools in the agent conf…
ardaerzin Jul 11, 2026
0d9b145
feat(entities): session liveness derivation, killSession, low-priorit…
ardaerzin Jul 11, 2026
12c4242
feat(agent-chat): server-backed session history, liveness badge, End …
ardaerzin Jul 11, 2026
e51332b
refactor(session-inspector): DRY onto liveness derivation, fix intera…
ardaerzin Jul 11, 2026
246e014
docs(sessions): frontend integration status + follow-ups
ardaerzin Jul 11, 2026
e0ad0d0
feat(entities): mount file listing/read + deriveMountRows browser der…
ardaerzin Jul 11, 2026
10632e1
feat(session-inspector): read-only mount file browser
ardaerzin Jul 11, 2026
447262c
feat(agent-chat): unified resizable right panel (turn + session)
ardaerzin Jul 11, 2026
e8badc9
feat(agent-config): separate operational sections; stub Mounts section
ardaerzin Jul 11, 2026
bd6c188
Merge remote-tracking branch 'origin/big-agents' into big-agents-add-…
ardaerzin Jul 11, 2026
9addddd
docs(sessions): record agent-mounts (#5215) FE dependency + wiring seam
ardaerzin Jul 11, 2026
cc9ab07
fix(frontend): drop the Operations divider in the agent config panel
ardaerzin Jul 11, 2026
428c13d
fix(frontend): style the Operations group header like the Configurati…
ardaerzin Jul 11, 2026
a370fce
fix(frontend): promote Triggers and Mounts to top-level regions
ardaerzin Jul 11, 2026
b58dc69
fix(frontend): rename the App triggers sub-section to Subscriptions
ardaerzin Jul 11, 2026
ad86853
fix(frontend): render Triggers/Mounts region headers as Configuration…
ardaerzin Jul 11, 2026
742de5f
feat(frontend): Subscriptions/Schedules as config-style sections unde…
ardaerzin Jul 11, 2026
13a2299
fix(frontend): animate the chat right panel open/close
ardaerzin Jul 11, 2026
147c32e
fix(frontend): make the right-panel slide actually fire
ardaerzin Jul 11, 2026
8aaf7ac
fix(frontend): Triggers and Mounts as true sibling sections of Config…
ardaerzin Jul 11, 2026
221213c
fix(frontend): skeletons mirror the three-section config panel
ardaerzin Jul 11, 2026
d9eb7c8
feat(frontend): Storage region with App drive / Session drive sections
ardaerzin Jul 11, 2026
8490568
docs(sessions): agent-mounts FE seam moved to the Storage region
ardaerzin Jul 11, 2026
d1e404e
feat(frontend): functional Session drive section in the Storage region
ardaerzin Jul 11, 2026
890833d
feat(frontend): centralized mount/drive store with turn-finish revali…
ardaerzin Jul 11, 2026
e36b640
feat(frontend): mid-stream file-activity signals + drive revalidation
ardaerzin Jul 12, 2026
b667c96
feat(frontend): drive build-mode surfaces per direction-1a spec
ardaerzin Jul 12, 2026
f9b2dee
feat(frontend): drive chat-mode surfaces per direction-1a spec (view E)
ardaerzin Jul 12, 2026
0d8dd70
fix(frontend): drive drawer breadcrumb + control sizing per spec
ardaerzin Jul 12, 2026
5c1c91c
feat(frontend): chat context rail + enriched in-thread file cards (sp…
ardaerzin Jul 12, 2026
419b980
fix(frontend): context rail starts closed and slides like the right p…
ardaerzin Jul 12, 2026
54d3f70
fix(frontend): plain-language tooltips on the context rail
ardaerzin Jul 12, 2026
81e426e
fix(frontend): coordinate the context rail with the Build/Chat pane ease
ardaerzin Jul 12, 2026
4ff0ff3
feat(frontend): standalone Files window in chat (spec E3)
ardaerzin Jul 12, 2026
a5c1d21
refactor(frontend): Files window becomes a drawer (app-standard pattern)
ardaerzin Jul 12, 2026
1b0f91f
feat(frontend): file-type renderer registry for drive previews (build…
ardaerzin Jul 12, 2026
bd60fab
perf(frontend): bound drive file memory to what's actually being viewed
ardaerzin Jul 12, 2026
3409503
perf(frontend): stream media directly and lazy image thumbnails
ardaerzin Jul 12, 2026
ec78393
feat(frontend): syntax-highlighted code previews via the lexical/Shik…
ardaerzin Jul 12, 2026
18a992c
fix(frontend): root-cause the Build/Chat transition jumpiness
ardaerzin Jul 12, 2026
c11cf7a
fix(frontend): orchestrate nested splitter panes — driven pane animat…
ardaerzin Jul 12, 2026
c5a7c0f
feat(frontend): App drive lights up against #5247's agent-mount query
ardaerzin Jul 12, 2026
4b3313a
chore(preview): cherry-pick #5247 agent mounts (api+runner only) for …
ardaerzin Jul 12, 2026
d9f2c07
feat(frontend): records-backed unified Inspector (turn + session, one…
ardaerzin Jul 12, 2026
e277cae
fix(frontend): gate the Inspector to build mode (not env)
ardaerzin Jul 12, 2026
48027ef
refactor(frontend): retire the old Turn/Session panels + inspector dr…
ardaerzin Jul 12, 2026
9c0f1b1
feat(frontend): inspector drill-to-turn + session lifecycle chips (sp…
ardaerzin Jul 12, 2026
9417d7e
feat(frontend): errors + interactions as typed events in place (spec …
ardaerzin Jul 12, 2026
75f04ac
Merge remote-tracking branch 'origin/big-agents' into big-agents-add-…
ardaerzin Jul 12, 2026
63809de
feat(entities): durable per-file recency from the session record log
ardaerzin Jul 13, 2026
665643f
feat(frontend): agent playground file surfaces + inspector overhaul
ardaerzin Jul 13, 2026
331c23c
Merge remote-tracking branch 'origin/big-agents' into big-agents-add-…
ardaerzin Jul 13, 2026
3c7169f
fix(frontend): session bar as absolute overlay — content padding driv…
ardaerzin Jul 13, 2026
f8a443f
fix(frontend): inspector uses semantic theme tokens, not ad-hoc dark hex
ardaerzin Jul 13, 2026
a693f1b
fix(frontend): config panel shows a single "Files" region, no app/ses…
ardaerzin Jul 13, 2026
445bc4f
fix(frontend): restore missing triggerCount in AgentTemplateControl
ardaerzin Jul 13, 2026
584dddf
feat(frontend): thumbnail file items in the chat files rail via Drive…
ardaerzin Jul 13, 2026
5cfed68
fix(frontend): add top padding to the config Files region body
ardaerzin Jul 13, 2026
a35a52e
fix(frontend): chat sessions rail scrolls instead of squashing rows
ardaerzin Jul 13, 2026
0005839
fix(frontend): show in-thread file cards in Build chat too, matching …
ardaerzin Jul 13, 2026
dbe38d4
Revert "fix(frontend): show in-thread file cards in Build chat too, m…
ardaerzin Jul 13, 2026
ae3326d
fix(frontend): one file card per write — drop the duplicate tool-step…
ardaerzin Jul 13, 2026
1e75395
fix(frontend): context rail no longer rides the build->chat transitio…
ardaerzin Jul 13, 2026
9332753
feat(frontend): stamp turn trace_id into replayed messages when the r…
ardaerzin Jul 13, 2026
0477cb5
fix(frontend): agent chat session UX — autofocus, husk cleanup, per-s…
ardaerzin Jul 13, 2026
81b87f2
Merge origin/big-agents into big-agents-add-sessions
ardaerzin Jul 13, 2026
c3f4962
fix(frontend): drop artifactId threading incompatible with the unifie…
ardaerzin Jul 13, 2026
ceac262
fix(frontend): stop the inspector divider riding up over the session bar
ardaerzin Jul 13, 2026
7647c56
fix(frontend): infer folders in the drive listing so convention dirs …
ardaerzin Jul 13, 2026
4fd0732
fix(frontend): floor session tab width so hover actions don't block s…
ardaerzin Jul 13, 2026
cdbe532
fix(frontend): show the agent's durable agent-files mount in the sess…
ardaerzin Jul 13, 2026
6f697f3
feat(frontend): mark agent (durable) vs session files + filter the Fi…
ardaerzin Jul 13, 2026
047003b
fix(frontend): align file rows — group the origin tag with the name, …
ardaerzin Jul 13, 2026
8c72f5a
feat(frontend): add a Session origin tag alongside Agent; show both o…
ardaerzin Jul 13, 2026
17397f8
feat(frontend): origin tags in the drive drawer + rename it 'Files', …
ardaerzin Jul 13, 2026
8484f63
fix(frontend): only fade the session tab strip edges where content is…
ardaerzin Jul 13, 2026
4a25425
fix(frontend): show the agent's durable files even with no conversati…
ardaerzin Jul 13, 2026
47d48df
feat(api,frontend): thread object-store mtime into the mount listing …
ardaerzin Jul 13, 2026
65830b3
feat(frontend): animate drive file lists on add / remove / recency re…
ardaerzin Jul 13, 2026
7b4aac8
fix(frontend): tool step renders the file card; prose mentions render…
ardaerzin Jul 13, 2026
84dbc4a
fix(frontend): revalidate the agent (durable) mount after a turn, not…
ardaerzin Jul 13, 2026
2505a41
feat(frontend): Stop can also kill the session behind NEXT_PUBLIC_AGE…
ardaerzin Jul 13, 2026
ea933e6
feat(frontend): live-revalidate the inspector Streams/State tabs + cl…
ardaerzin Jul 13, 2026
045497e
docs(sessions): mark resume (#5197/#5225) merged in the FE integratio…
ardaerzin Jul 13, 2026
d8d1082
feat(frontend): distinguish an expired/pruned session from a brand-ne…
ardaerzin Jul 13, 2026
77fc478
fix(frontend): treat Fern-wrapped AbortError as a cancellation, not a…
ardaerzin Jul 13, 2026
0dc0b6e
feat(frontend): carry turn usage (tokens/cost) from records so the me…
ardaerzin Jul 13, 2026
86722bb
feat(runner): stamp the run's trace_id on the terminal done event for…
ardaerzin Jul 14, 2026
4a90a69
fix(frontend): clear antd 6 deprecation warnings (Spin, Drawer, Split…
ardaerzin Jul 14, 2026
dd29366
fix(frontend): drop the redundant 'open' tag from the session history…
ardaerzin Jul 14, 2026
a57cdad
fix(frontend): show only the file basename in drive rows, full path o…
ardaerzin Jul 14, 2026
8eee11a
fix(frontend): gate turn click-to-inspect on the inspector panel bein…
ardaerzin Jul 14, 2026
f2ff6f7
Merge origin/big-agents into big-agents-add-sessions
ardaerzin Jul 14, 2026
4716c1f
fix(frontend): hide runner-internal artifacts (Pi transcripts, .agent…
ardaerzin Jul 14, 2026
67ec0bc
Merge origin/big-agents into big-agents-add-sessions
ardaerzin Jul 14, 2026
ca0b98a
fix(frontend): satisfy @typescript-eslint/array-type in fileActivity …
ardaerzin Jul 14, 2026
f5cd196
fix(api): return typed StoreObject from list_objects_v2 instead of a …
ardaerzin Jul 14, 2026
913f7ec
fix(frontend): address code-review items on agent sessions/files/insp…
ardaerzin Jul 14, 2026
bb90151
style(frontend): condense invalidateSessionInspector doc comment to o…
ardaerzin Jul 14, 2026
6eba5c9
Merge remote-tracking branch 'origin/big-agents' into big-agents-add-…
ardaerzin Jul 14, 2026
6b7aaca
feat(frontend): file count + browse entry in the config Files header
ardaerzin Jul 15, 2026
f7114f4
fix(frontend): markdown headings inherit colour instead of the global…
ardaerzin Jul 15, 2026
6731493
refactor(frontend): lazy-load drive drawer bodies + fix files grid/pr…
ardaerzin Jul 15, 2026
0c5f88e
Merge remote-tracking branch 'origin/main' into big-agents-add-sessions
ardaerzin Jul 15, 2026
5a13aff
fix(frontend): remove polynomial-ReDoS slash regexes on mount paths (…
ardaerzin Jul 15, 2026
5f56823
fix(frontend): serve pdfjs worker from public/ so the webpack product…
ardaerzin Jul 15, 2026
4410f13
fix(frontend): TriggerDeliveriesDrawer uses width, not size, for its …
ardaerzin Jul 15, 2026
d739777
Merge remote-tracking branch 'origin/main' into big-agents-add-sessions
ardaerzin Jul 16, 2026
546d35d
fix(frontend): scrollable markdown in the file preview + drop the obs…
ardaerzin Jul 16, 2026
e9f0312
feat(frontend): file-drawer feedback — details toggle, resizable/scro…
ardaerzin Jul 16, 2026
a850e6c
feat(frontend): give the SKILL.md editor most of the drawer height (w…
ardaerzin Jul 16, 2026
7990a5b
feat(frontend): copy-path button on the file preview breadcrumb
ardaerzin Jul 16, 2026
1e4f6ad
feat(frontend): move the file metadata Details toggle into the chat d…
ardaerzin Jul 16, 2026
6a71d5f
fix(frontend): markdown fenced code no longer shows per-line grey bac…
ardaerzin Jul 16, 2026
b9cb644
feat(frontend): chat file-drawer header — animate metadata, icon togg…
ardaerzin Jul 16, 2026
e76007e
feat(frontend): render HTML file previews in a sandboxed iframe (Prev…
ardaerzin Jul 16, 2026
d585774
feat(frontend): unify the build drawer file preview with the chat sin…
ardaerzin Jul 16, 2026
25b7dd3
fix(frontend): pin the build drawer file header — h-full, not flex-1,…
ardaerzin Jul 16, 2026
78ed4d2
feat(frontend): HTML preview inlines mount CSS + images so multi-file…
ardaerzin Jul 16, 2026
8ff764d
feat(frontend): clickable breadcrumb + folder view in the file drawer
ardaerzin Jul 16, 2026
ebd2c98
feat(frontend): uniform folder-view grid — folder tiles match file tiles
ardaerzin Jul 16, 2026
16a9d50
feat(frontend): HTML preview links open the target file in the drive
ardaerzin Jul 16, 2026
c61548d
feat(frontend): hide internal (dot-prefixed) files from the config pa…
ardaerzin Jul 16, 2026
89eca3a
fix(frontend): collapsing a drive folder no longer changes the preview
ardaerzin Jul 16, 2026
9a4048a
feat(frontend): refine drive surfaces — accent borders, focus rings, …
ardaerzin Jul 17, 2026
7dc4788
fix(frontend): unique keys for empty Subscriptions/Schedules accordio…
ardaerzin Jul 17, 2026
35c5514
Merge branch 'release/v0.105.2' into big-agents-add-sessions
bekossy Jul 17, 2026
377252a
Merge branch 'main' into big-agents-add-sessions
jp-agenta Jul 17, 2026
457fef1
Merge pull request #5358 from Agenta-AI/revert/pr-5337
bekossy Jul 17, 2026
93366a6
Merge remote-tracking branch 'origin/release/v0.105.2' into big-agent…
bekossy Jul 17, 2026
1d4c32d
Merge branch 'release/v0.105.3' into big-agents-add-sessions
bekossy Jul 17, 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
109 changes: 109 additions & 0 deletions SESSIONS_BACKEND_QUESTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Sessions FE ↔ Backend — points to align on

Tags: **🔴 BLOCKER** (FE can't proceed) · **🟡 CONFIRM** (FE can build once the contract is agreed).
Ordered by how much FE work each unblocks.

## Framing

PR #4916 shipped the durable **read** plane (transcripts) live — FE step 1 (server-backed
history) is wired and working. The **control** planes (interactions/HITL, streams/live-run)
are scaffolded in the API but **not wired to the runner**, so the FE-side API surface is
built and parked. The points below are what it takes to light those up.

## A. Transcripts (read plane) — LIVE 🟡 confirm contract

1. **Payload stability** — is `payload` guaranteed 1:1 with the ACP `AgentEvent` union
(`services/agent/src/protocol.ts`)? Any event types the FE should expect that aren't in
that union?
2. **Ordering & pagination** — is `queryTranscripts` always ordered ascending by uuid7
`id`? `SessionTranscriptQueryRequest` only has `session_id` — is there a result cap /
cursor for long sessions, or does it return the whole log?
3. **`sender` values** — confirm the exact set (`user` / `runner` / `system`?). FE groups
messages by this.
4. **User-turn persistence** — is the user's *own* message persisted as a transcript event
(`sender=user`), and with what payload shape (plain text? multimodal/files)? Determines
how we replay user turns.
5. **Deltas vs coalesced** — does the transcript store `message_start/delta/end` rows or
only the coalesced `message`? (FE handles both; just want to know.)
6. **64KB truncation** — payloads over the cap become `{"_truncated": true}`. How should
the FE render a truncated event?

## B. State / resume — runner-owned 🟡 confirm division of labor

7. Confirm FE treats `/sessions/states` as **read-only** (only `getState` for sandbox
awareness) and must **never** `setState(data)` (it'd clobber the runner's
`SessionRecord`). Correct?
8. Is sandbox **resume fully automatic** by `session_id` once a run goes through the runner
— i.e. the FE does nothing? Or is there anything FE-side (e.g. `setStateSandboxId`)?
9. Is there a state value worth surfacing in the UI (e.g. "sandbox alive")?

## C. Interactions / HITL — INCOMPLETE 🔴 blockers

10. **🔴 Rows aren't created** — the runner emits `interaction_request` to the transcript
but does **not** create a `SessionInteraction` row, so `queryInteractions` is empty for
real runs. Who creates the row, and when (should be on park)?
11. **🔴 Respond doesn't transition status** — `respondInteraction` enqueues a re-invoke but
leaves `status.code = pending` forever (only the admin `transition` endpoint flips it).
Should respond transition pending→resolved/denied?
12. **ID correlation** — transcript `interaction_request.id` appears to map to
`SessionInteraction.token`, not `.id` (unique on `project+session+token`). Confirm the
intended FE flow: query by token → respond by row id? Or will the transcript event carry
the interaction row id directly?
13. **Answer shape** — `respondInteraction.answer` is an unvalidated `Dict`. What exact
shape for approve vs deny (`{approved: true/false}`?), and is there an **"always allow"**
option — where is it stored/honored on resume?
14. **Resume-after-respond** — responding does **not** continue the run inline; does the FE
then call `invokeStream`? Confirm the intended sequence (and whether it only applies to
the streams path).
15. **Scope** — confirm v1 = `user_approval` only; FE keeps `user_input`/`tool_call` gated
off.

## D. Streams / live run — STUB 🔴 biggest blocker

16. **🔴 invoke is a stub** — `SessionStreamsService.invoke`/`_start_run`
(`api/oss/src/core/sessions/streams/service.py`) only acquires Redis locks + writes a
`SessionStream`/`session_runners` row; it **does not invoke the runner** and streams
nothing.
17. **🔴 No stream-return path** — there's **no SSE/attach endpoint** delivering v6 chunks.
The "watch live run" path is unspecified + NOT STARTED
(`docs/designs/sessions/streams/tasks.md`). **What's the planned transport** — a
`GET /sessions/streams/attach` returning `text/event-stream` v6 chunks? WebSocket? The
FE `useChat` transport needs a `request → streamed-response`, so we need the endpoint +
content-type.
18. **Replace vs complement** — the FE currently streams fine from the **runner** service
(`/api/agent/chat`). Does the streams model **replace** that, or is it
**invoke = coordination** layered on top of the existing runner stream? This decides
whether step 4 is a transport rewire or just adding force/attach/detach/liveness around
the current path.
19. **Mode semantics** — when wired, what should the FE do per
`SessionInvokeResponseModel.mode` (`send`/`steer`/`cancel`/`attach`/`detach`) and for
`force`/`detached`?
20. **Timeline** — the brief frames streams as the multi-replica-correct path; what's the
rough sequencing for 16–18?

## E. Cross-device session discovery 🟡

21. FE history (the picker) is localStorage-only, so a session this browser never ran won't
appear. Transcripts is per-session (no list). Is there / will there be a
**sessions-list endpoint** (by project/app), or should FE keep using the observability
`ag.session.id`-off-traces aggregation? Align on the canonical "list sessions" source.

## F. Mounts (step 5) 🟡

22. Is `client.mounts.*` (agent working-dir file CRUD) actually live/wired, and what's the
priority? (FE panel is optional/last.)

## G. Process / verification 🟡

23. What's the canonical way to get a **populated local env** — which compose profile +
entrypoints (e.g. `worker_transcripts`) must be running for `queryTranscripts` to return
rows? Several endpoints depend on a runner/worker rebuild.
24. The design docs are labelled "draft / not implemented." Can they be marked with **what's
actually live** so the FE has a contract source of truth?

## What the FE already has ready

The full `@agenta/entities/session` surface — `querySessionTranscripts` (in use), plus
parked `getSessionState`, `queryInteractions` / `fetchInteraction` / `respondInteraction`,
and `queryStreams` / `getStreamLiveness` / `invokeSessionStream` — all matching the
generated client, ready to wire the day each plane lands.
81 changes: 81 additions & 0 deletions SESSIONS_FE_BRIEF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Frontend brief — wire the durable Sessions API into agent chat

## 0. Where you are
- **Worktree:** `/Users/ardaerzin/Documents/GitHub/agenta_open_source/.claude/worktrees/big-agents-sessions`
- **Branch:** `big-agents-add-sessions` = `origin/big-agents` + a clean merge of PR #4916 (`feat/add-sessions`).
- Read `web/AGENTS.md` and the `agenta-package-practices` skill before writing code. Frontend is a pnpm monorepo (Node ≥ 22.13; use Node 24). Run `pnpm lint-fix` in `web/` before committing.

## 1. What PR #4916 actually shipped (read this first)
It is ~95% **backend + generated client**, almost no FE feature code:
- New API domains under `api/oss/src/.../sessions/{states,streams,transcripts,interactions}` + standalone `mounts`.
- The **Fern-generated TS client** for them in `@agentaai/api-client` — this is your entire FE-facing surface. Nothing consumes it yet.
- Design docs in `docs/designs/sessions/` (states / streams / transcripts / interactions) and `docs/designs/mounts/`. **These docs are labelled "draft for discussion / not implemented"** — treat them as intent, and **verify the actual backend wiring is live before building UI on an endpoint** (esp. transcripts ingest worker, interactions beyond `user_approval`). v1 of interactions implements **`user_approval` only**; `user_input` and `tool_call` exist in the schema but are not wired.

So your job is **integration**, not greenfield: connect the existing agent-chat FE (today localStorage-only) to these durable endpoints.

## 2. The new API surface (`@agentaai/api-client` via `getAgentaSdkClient()`)
Get the client the standard way (mirror `web/packages/agenta-entities/src/trace/api/client.ts`):
```ts
import {getAgentaSdkClient} from "@agenta/sdk"
const sessions = getAgentaSdkClient().sessions
const mounts = getAgentaSdkClient().mounts
```
**Project/app scope ALWAYS rides queryParams, never the body** (standing rule, see `projectScopedRequest` in the trace client): pass `{queryParams: {project_id, application_id}, abortSignal}` as the per-request options arg. Fern methods **throw `AgentaApiError` on non-2xx** — wrap at the boundary.

### `client.sessions.*`
| Method | Endpoint | Purpose |
|---|---|---|
| `invokeStream({session_id, prompt?, force?, detached?})` | POST `/sessions/streams/invoke` | **Start/resume a live run.** `force` steals the run lock; `detached` = fire-and-forget (no held connection). |
| `queryStreams({session_id?, sandbox_live?})` | POST `/sessions/streams/query` | List live stream handles (who's running / attached / sandbox alive). |
| `getLiveness(...)` | GET `/sessions/streams/liveness` | Is a session's run currently alive. |
| `queryTranscripts({session_id})` | POST `/sessions/transcripts/query` | **Durable, append-only event log = the replay source for rendering a conversation.** |
| `getTranscriptEvent({event_id})` | GET `/sessions/transcripts/{event_id}` | One event by id. |
| `getState({session_id})` / `setState({session_id, data, sandbox_id})` | GET/POST `/sessions/states/{session_id}` | Durable SDK `SessionRecord` (opaque JSON) + sandbox resume pointer. |
| `setStateSandboxId(...)` | POST `/sessions/states/{session_id}/sandbox-id` | Update just the resume pointer. |
| `queryInteractions({query, windowing})` | POST `/sessions/interactions/query` | List HITL requests (pending approvals etc.). |
| `fetchInteraction({interaction_id})` | GET `/sessions/interactions/{interaction_id}` | One interaction. |
| `respondInteraction({interaction_id, answer})` | POST `/sessions/interactions/{interaction_id}/respond` | **Resolve a HITL request** (approve/deny/input/tool-result). |
| `querySessionMounts({...})` | POST `/sessions/mounts/query` | Session-scoped view of mounts. |

### `client.mounts.*` (agent working-directory files)
`queryMounts`, `createMount`, `fetchMount`, `editMount`, `archiveMount`/`unarchiveMount`, plus file ops `getMountFiles`, `uploadMountFile`, `writeMountFile`, `deleteMountFile`, `createMountFolder`.

### Key entity shapes (all under `AgentaApi.*` from the client)
- **`SessionTranscript`**: `{id, session_id, project_id, event_index?, sender?, session_update?, payload?, created_at?}` — `payload` is the ACP event (text / tool_call / result). This is what you map → chat messages.
- **`SessionState`**: `{session_id, data (opaque SessionRecord JSON), sandbox_id?}`.
- **`SessionStream`**: `{id, session_id, attached?, sandbox_live?, last_seen_at?, status:{code, message}}`. `StreamStatusCode = Running | Detached | Idle | Ended`.
- **`SessionInteraction`**: `{id, session_id, run_id?, token, kind, status?, data}`. `kind = user_approval | user_input | tool_call`. `status = pending | resolved | denied | cancelled`. `data = {request, references, selector, resolution}`.
- **`SessionMount` / `Mount`**: `{id, session_id, project_id, data: MountData, flags}`.

**Consistency model to respect:** the **transcript renders the question** (append-only, replayable); the **interaction record holds the answer-state** (is it still actionable + what was answered). Don't render HITL from the interactions table — render from the transcript, and use interactions to know if it's still pending/resolved.

## 3. The existing FE you're wiring into — `web/oss/src/components/AgentChatSlice/`
Today this slice is **localStorage-only** with an **ephemeral runner transport**. Files that matter:
- `state/sessions.ts` — the multi-session model. HISTORY (`sessionsByAppAtom`) vs OPEN TABS (`openIdsByAppAtom`) vs messages (`sessionMessagesAtom`), all `atomWithStorage` (localStorage), **scope-keyed** (app id, or `drawer:<entityId>` for the create/edit drawer — see `state/scope.tsx`). Messages keyed by globally-unique session id.
- `assets/loadSession.ts` — **the hydration seam.** `loadSessionMessages(sessionId)` returns `null` today. Its docstring still points at the **old** `/services/agent/v0/load-session` endpoint — that is now **superseded** by `client.sessions.queryTranscripts({session_id})`. This is the single cleanest place to light up server-backed history.
- `assets/transport.ts` + `assets/AgentChatTransport.ts` — the `useChat` (AI SDK v6) transports. Today they POST the agent-protocol envelope to the runner and stream a v6 UI Message Stream; `AgentChatTransport` also handles a batch→one-shot replay. The live run currently does **not** go through `/sessions/streams/invoke`.
- `assets/toAgentaMessage.ts`, `assets/trace.ts`, `assets/rewind.ts`, `assets/files.ts`, `assets/attachments.ts` — message adaptation, trace stamping, edit/rewind, inline `data:` file attachments.
- `components/SessionHistoryMenu.tsx` — the clock-icon history picker (already shipped, localStorage-backed). `components/AgentChatConversation.tsx`, `AgentMessage.tsx`, `ToolActivity.tsx`, `QueuedMessages.tsx` — render surface. `hooks/useAgentChatQueue.ts` — send queue.
- Pages: `web/oss/src/pages/.../apps/[app_id]/agent-chat/index.tsx` (standalone) and the playground panel `AgentChatPanel.tsx`.

Separately, observability already has a **read-only** session viewer (`web/oss/src/components/SharedDrawers/SessionDrawer/`, `components/pages/observability/components/SessionsTable/`) that lists sessions by aggregating `ag.session.id` off traces. That's a different surface — don't conflate it; but the new transcripts API could eventually back its message panel too.

## 4. Suggested work order (each is independently shippable)
1. **Server-backed history (highest value, smallest change).** Implement `loadSessionMessages` against `client.sessions.queryTranscripts({session_id})`: map `SessionTranscript.payload` events → v6 `UIMessage[]` (reuse the vercel/`toAgentaMessage` adapters). Caller already writes the result into `sessionMessagesAtom` before opening the tab. This makes open-from-deep-link / open-from-trace render conversations this browser never ran. **Confirm the transcript ingest worker is actually populating rows first** — if empty, this is a no-op.
2. **Session state / resume.** On session open, `getState` to restore the SDK record + `sandbox_id`; persist via `setState` so a run can resume the same sandbox across reloads/devices. Decide the localStorage-vs-server source-of-truth merge (server wins when present; localStorage is the offline fallback — keep the existing fallback semantics).
3. **HITL interactions.** When the transcript renders an `interaction_request`, cross-check `queryInteractions`/`fetchInteraction` for live status, and wire the approve/deny control to `respondInteraction({interaction_id, answer})`. Scope v1 to `user_approval` (deny/allow, maybe "always"); leave `user_input`/`tool_call` stubbed behind a flag. Build it as a derived view over the transcript, not a second source of truth.
4. **Live run via streams (bigger).** Move the live send from the current runner transport onto `/sessions/streams/invoke` (+ `queryStreams`/`getLiveness` for attach/detach/“someone else is running this” state, and `force` to steal). This is the multi-replica-correct path; verify the backend stream endpoint is live and returns the v6 chunk stream the transport expects before committing to it.
5. **Mounts / files (optional, last).** A file panel over `client.mounts.*` for the agent working directory.

## 5. Conventions (enforced — see memory/AGENTS)
- **Fern client only** (`@agentaai/api-client` via `getAgentaSdkClient()`), never axios, for all new API code.
- Package vs app placement per `agenta-package-practices`. New shared session entity logic likely belongs in a `@agenta/entities`-style package (mirror `trace/api/`), not in `web/oss`. OSS lint **blocks re-exporting `@agenta/*` from app barrels**.
- **Text size 12px (`text-xs`)**, never `text-sm`. **No `size="small"`** on antd controls. Dark mode: use **antd semantic tokens** (`--ag-colorText`, `--ag-colorBorder`, …); `dark:` / `bg-gray-*` / fixed hex are no-ops here.
- Thin-references pattern: query caches store IDs, the molecule/atom is the source of truth.
- Don't put `project_id`/`application_id` in request bodies — queryParams only.
- Before commit: `pnpm lint-fix` in `web/`; run the **oss tsc** manually (relocations can mask new errors — gate on an error-signature diff, not a count).
- Commit messages: no "Claude"/Anthropic/`Co-Authored-By`.

## 6. Verify before asserting done
- The fastest reality check is whether the endpoints return data: hit `queryTranscripts` / `queryStreams` / `queryInteractions` for a real session id and confirm non-empty, correctly-shaped responses. Several of these depend on a runner/worker rebuild that may not be running locally.
- Use the `verify` skill / run the app rather than trusting types — the generated client compiles regardless of whether the backend route is wired.
3 changes: 3 additions & 0 deletions api/oss/src/core/mounts/dtos.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class MountFile(BaseModel):
path: str
size: int = 0
is_folder: bool = False
# Object-store LastModified as epoch milliseconds; None when the store omits it. Lets the UI
# order files by recency regardless of how they were created (bash, Write tool, upload).
mtime: Optional[int] = None


class MountFileList(BaseModel):
Expand Down
9 changes: 5 additions & 4 deletions api/oss/src/core/mounts/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,15 +451,16 @@ async def list_files(
mount_base = base + "/"
files: List[MountFile] = []
folders: set[str] = set()
for key, size in objects:
for obj in objects:
key = obj.key
rel = key[len(mount_base) :] if key.startswith(mount_base) else key
# Hide bare trailing-slash marker objects (empty-folder markers).
if key.endswith("/"):
folder_rel = rel.rstrip("/")
if folder_rel:
folders.add(folder_rel)
continue
files.append(MountFile(path=rel, size=size))
files.append(MountFile(path=rel, size=obj.size, mtime=obj.mtime))

existing = {f.path for f in files}
for folder_rel in sorted(folders):
Expand Down Expand Up @@ -559,14 +560,14 @@ async def delete_path(
bucket=bucket,
prefix=folder_prefix,
)
keys = [key for key, _ in objects]
keys = [obj.key for obj in objects]

# The exact file (or the folder marker) may also exist alongside contents.
single = await self.mounts_store.list_objects_v2(
bucket=bucket,
prefix=exact_key,
)
if any(key == exact_key for key, _ in single):
if any(obj.key == exact_key for obj in single):
keys.append(exact_key)

unique_keys = list(dict.fromkeys(keys))
Expand Down
12 changes: 12 additions & 0 deletions api/oss/src/core/store/dtos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from typing import Optional

from pydantic import BaseModel


class StoreObject(BaseModel):
"""One object listed from the store: its key, byte size, and LastModified as epoch
milliseconds (None when the store omits it)."""

key: str
size: int = 0
mtime: Optional[int] = None
Loading
Loading