Skip to content

feat: store last turn id and status on session as last_turn_details (AGE-2315) - #1089

Open
hary73 wants to merge 1 commit into
mainfrom
hary/last-turn-status
Open

hary73 wants to merge 1 commit into
mainfrom
hary/last-turn-status

Conversation

@hary73

@hary73 hary73 commented Oct 19, 2026 •

Copy link
Copy Markdown

Summary

Adds a nullable last_turn_details JSON column on session holding { id, status } for the session's last turn, so the agent sessions page can show session status from the last turn (AGE-2315). Backend only: the public Session DTO and UI are unchanged in this PR.

Changes

  • TurnStatusSchema in schemas/turn.ts is now the single source of turn status literals; each TurnState arm uses it (OpenAPI output unchanged).
  • SessionLastTurnDetailsSchema ({ id, status }) in schemas/session.ts; SessionRecord.last_turn_details: SessionLastTurnDetails | null.
  • Writes always replace the whole document, never one key:
    • createTurn sets last_turn_id and last_turn_details in the same session UPDATE.
    • updateTurnNonTerminalState, updateTurnTerminalState, and the winning freezeAndGetTurn update it in the same transaction as the turn, only WHERE last_turn_details.id = <turn>. A turn that is no longer the last turn (e.g. a new root turn started while it was running) cannot overwrite the newer turn's document.
  • Postgres, SQLite, and in-memory stores all implement this; SQLite adds the column to JSON_RESULT_COLUMNS.
  • Migration 20261018_000001_session_last_turn_details (Postgres + SQLite): add the column, backfill from the turn last_turn_id points at; sessions without a matching turn stay null.

The guard reads the JSON id rather than last_turn_id, so a follow-up can drop last_turn_id after moving its remaining readers (createTurn's row lock read, the event-listing anchor, previous_turn_id: 'auto', cancelSession).

How was this tested?

  • New last_turn_details block in storeContractSuite: null before first turn, create, pause/resume/terminal, freeze (incl. repeat), and a superseded turn not touching the newer last turn. Passes on in-memory, SQLite, and Postgres (scripts/test-store-local.sh).
  • New migration tests for Postgres and SQLite: backfill, missing-turn and no-turn sessions stay null, rollback drops the column.
  • pnpm test:trueforge-core and pnpm test:trueforge pass; typecheck, lint, format, and check-migration-order.sh pass.

Checklist

  • Tests added/updated where it makes sense
  • No hand-edits to generated code
  • Patch changeset for @truefoundry/trueforge and @truefoundry/trueforge-core

Made with Cursor


Note

Medium Risk
Touches session persistence and migrations across Postgres/SQLite with concurrent turn updates; behavior is guarded and tested but wrong writes could desync session tip status from turns.

Reviewed by Cursor Bugbot for commit fa50563. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fa50563

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@truefoundry/trueforge Patch
@truefoundry/trueforge-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fff93ee. Configure here.

Comment thread .changeset/session-last-turn-details.md
@hary73
hary73 marked this pull request as draft October 19, 2026 06:10
…AGE-2315)

Co-authored-by: Cursor <cursoragent@cursor.com>
@hary73
hary73 force-pushed the hary/last-turn-status branch from fff93ee to fa50563 Compare October 19, 2026 06:11
@hary73
hary73 changed the base branch from ha/emit-pause to main October 19, 2026 06:11
@hary73
hary73 force-pushed the hary/last-turn-status branch from 9815307 to fa50563 Compare October 19, 2026 12:46
@hary73
hary73 marked this pull request as ready for review October 19, 2026 12:48
Comment on lines +45 to +48
/**
* Id and status of the last turn; null until the first turn. Replaced whole on
* createTurn and on that turn's status changes, never patched per key.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comment, not needed

@@ -36,6 +42,11 @@ export interface SessionRecord<TCustom extends object = Record<string, never>> {
* listing anchors. Advanced only by `createTurn` (atomic link).
*/
last_turn_id: string | null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we not removing this?

export const SessionLastTurnDetailsSchema = z
.object({
id: z.string().min(1),
status: TurnStatusSchema,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need all fields in status?

@@ -0,0 +1,88 @@
import { sql } from 'kysely';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need tests for this migration if this has been tested manually. These tests are kinda not useful later.

Comment on lines 922 to +927
await addSessionCostAndDuration(trx, {
session_id: input.session_id,
turn_created_at: new Date(result.created_at),
turn_state: input.state,
});
await updateLastTurnDetails(trx, input);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like these always happen together. Lets have a single store method for these

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants