feat(core): Add sub-agent session linkage migration (#31534)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
bjorger 2026-06-02 13:38:58 +02:00 committed by GitHub
parent 4e0e0ed11a
commit 25f2d3cf32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 105 additions and 1 deletions

View File

@ -0,0 +1,86 @@
import type { IrreversibleMigration, MigrationContext } from '../migration-types';
/**
* Adds the sub-agent session-linkage columns to `agent_execution_threads`
* (parentThreadId / parentAgentId) and widens the agent thread-id columns to
* varchar(128).
*
* Agent thread ids are not bare uuids several surfaces scope them with a
* prefix and a user id (e.g. the test chat's `test-<agentId>:<userId>`, the
* builder's `builder:<agentId>`). Those values exceed the original varchar(36)
* of the SDK memory thread (`agents_threads.id`) and the session records
* (`agent_execution_threads.id`, `agent_execution.threadId`), so widen those id
* columns to varchar(128). `parentThreadId` holds such a parent thread id, so it
* is created at varchar(128) directly. Known generated formats stay well below
* 128 chars (for example, `test-<agentId>:<userId>` is about 78 chars with UUIDs).
*/
const COLUMNS_TO_WIDEN: Array<{ table: string; column: string }> = [
{ table: 'agents_threads', column: 'id' },
{ table: 'agent_execution_threads', column: 'id' },
{ table: 'agent_execution', column: 'threadId' },
];
const SQLITE_DECLARED_TYPE_REPLACEMENTS: Array<{ table: string; from: string; to: string }> = [
{ table: 'agents_threads', from: '"id" varchar(36)', to: '"id" varchar(128)' },
{
table: 'agent_execution_threads',
from: '"id" varchar(36)',
to: '"id" varchar(128)',
},
{
table: 'agent_execution',
from: '"threadId" varchar(36)',
to: '"threadId" varchar(128)',
},
];
export class AddSubAgentLinkageToAgentExecutionThreads1784000000022
implements IrreversibleMigration
{
async up({
schemaBuilder: { addColumns, column },
isPostgres,
isSqlite,
runQuery,
escape,
tablePrefix,
}: MigrationContext) {
await addColumns('agent_execution_threads', [
column('parentThreadId')
.varchar(128)
.comment('Parent session thread id that delegated this subagent run.'),
column('parentAgentId')
.varchar(36)
.comment('Saved agent id of the parent that delegated this subagent run.'),
]);
if (isPostgres) {
for (const { table, column: columnName } of COLUMNS_TO_WIDEN) {
await runQuery(
`ALTER TABLE ${escape.tableName(table)} ALTER COLUMN ${escape.columnName(columnName)} TYPE VARCHAR(128);`,
);
}
} else if (isSqlite) {
// SQLite does not enforce varchar limits, but keep the declared schema in sync for documentation.
await this.widenSqliteDeclaredColumnTypes({ runQuery, tablePrefix });
}
}
private async widenSqliteDeclaredColumnTypes({
runQuery,
tablePrefix,
}: Pick<MigrationContext, 'runQuery' | 'tablePrefix'>) {
await runQuery('PRAGMA writable_schema = 1;');
try {
for (const { table, from, to } of SQLITE_DECLARED_TYPE_REPLACEMENTS) {
await runQuery(
"UPDATE sqlite_master SET sql = replace(sql, :from, :to) WHERE type = 'table' AND name = :tableName",
{ from, to, tableName: `${tablePrefix}${table}` },
);
}
} finally {
await runQuery('PRAGMA writable_schema = 0;');
}
}
}

View File

@ -197,6 +197,7 @@ import { CreateAgentFilesTable1784000000018 } from '../common/1784000000018-Crea
import { AddCustomTelemetryTagsToProject1784000000019 } from '../common/1784000000019-AddCustomTelemetryTagsToProject';
import { CreateWorkflowPublicationOutboxTable1784000000020 } from '../common/1784000000020-CreateWorkflowPublicationOutboxTable';
import { CreateAgentTaskDefinitionTable1784000000021 } from '../common/1784000000021-CreateAgentTaskDefinitionTable';
import { AddSubAgentLinkageToAgentExecutionThreads1784000000022 } from '../common/1784000000022-AddSubAgentLinkageToAgentExecutionThreads';
import type { Migration } from '../migration-types';
export const postgresMigrations: Migration[] = [
@ -399,4 +400,5 @@ export const postgresMigrations: Migration[] = [
AddCustomTelemetryTagsToProject1784000000019,
CreateWorkflowPublicationOutboxTable1784000000020,
CreateAgentTaskDefinitionTable1784000000021,
AddSubAgentLinkageToAgentExecutionThreads1784000000022,
];

View File

@ -189,6 +189,7 @@ import { AddLastUsedAtToApiKey1784000000017 } from '../common/1784000000017-AddL
import { CreateAgentFilesTable1784000000018 } from '../common/1784000000018-CreateAgentFilesTable';
import { AddCustomTelemetryTagsToProject1784000000019 } from '../common/1784000000019-AddCustomTelemetryTagsToProject';
import { CreateWorkflowPublicationOutboxTable1784000000020 } from '../common/1784000000020-CreateWorkflowPublicationOutboxTable';
import { AddSubAgentLinkageToAgentExecutionThreads1784000000022 } from '../common/1784000000022-AddSubAgentLinkageToAgentExecutionThreads';
import type { Migration } from '../migration-types';
import { CreateAgentTaskDefinitionTable1784000000021 } from './1784000000021-CreateAgentTaskDefinitionTable';
@ -385,6 +386,7 @@ const sqliteMigrations: Migration[] = [
AddCustomTelemetryTagsToProject1784000000019,
CreateWorkflowPublicationOutboxTable1784000000020,
CreateAgentTaskDefinitionTable1784000000021,
AddSubAgentLinkageToAgentExecutionThreads1784000000022,
];
export { sqliteMigrations };

View File

@ -40,6 +40,17 @@ export class AgentExecutionThread extends WithTimestampsAndStringId {
@Column({ type: 'varchar', length: 8, nullable: true })
emoji: string | null;
/**
* Parent session thread id that delegated this run, for navigating back to
* it. Holds another thread's id, so it matches the id column width (128).
*/
@Column({ type: 'varchar', length: 128, nullable: true })
parentThreadId: string | null;
/** Saved agent id of the parent that delegated this run. */
@Column({ type: 'varchar', length: 36, nullable: true })
parentAgentId: string | null;
@ManyToOne(() => Project, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'projectId' })
project: Project;

View File

@ -25,7 +25,10 @@ export class AgentExecution extends WithTimestampsAndStringId {
@JoinColumn({ name: 'threadId' })
thread: AgentExecutionThread;
@Column({ type: 'varchar', length: 36 })
// Thread ids are scoped with prefixes/user ids on some surfaces (e.g.
// `test-<agentId>:<userId>`), so they exceed a bare uuid — widened to 128 in
// AddSubAgentLinkageToAgentExecutionThreads1784000000022.
@Column({ type: 'varchar', length: 128 })
threadId: string;
@Column({ type: 'varchar', length: 16 })