Skip to content

Commit ea3e366

Browse files
feat(B.1): add tasks.assignee column + claim semantics on task start
Schema: - Added `assignee TEXT` column to tasks table (Drizzle schema + migration) - Added `idx_tasks_assignee` index for query performance - Migration: 20260325010000_add-assignee-column Contracts: - Task interface: added `assignee?: string | null` - TaskFieldUpdates: added `assignee?: string | null` - DataAccessor.updateTaskFields() accepts assignee Core: - converters.ts: rowToTask/taskToRow handle assignee - db-helpers.ts: upsert includes assignee - task-store.ts: updateTask handles assignee field - sqlite-data-accessor.ts: updateTaskFields field map includes assignee - task-work/index.ts: startTask() auto-sets assignee from CLEO_AGENT_ID (claim semantics — /claim verb maps to tasks.start via nexus.route) Also: fixed stale @ts-expect-error in lafs tokenEstimator.ts (Intl.Segmenter now in TS 5.9 lib types) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3331843 commit ea3e366

10 files changed

Lines changed: 71 additions & 61 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE tasks ADD COLUMN assignee TEXT;--> statement-breakpoint
2+
CREATE INDEX idx_tasks_assignee ON tasks (assignee);

packages/contracts/src/data-accessor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export interface TaskFieldUpdates {
103103
modifiedBy?: string | null;
104104
sessionId?: string | null;
105105
updatedAt?: string | null;
106+
assignee?: string | null;
106107
}
107108

108109
/**

packages/contracts/src/task.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ export interface Task {
214214
* @task T060
215215
*/
216216
pipelineStage?: string | null;
217+
218+
/** Agent ID that has claimed/is assigned to this task. Null when unclaimed. */
219+
assignee?: string | null;
217220
}
218221

219222
// ---------------------------------------------------------------------------

packages/core/src/store/converters.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export function rowToTask(row: TaskRow): Task {
5757
}
5858
: undefined,
5959
pipelineStage: row.pipelineStage ?? undefined,
60+
assignee: row.assignee ?? undefined,
6061
};
6162
}
6263

@@ -92,6 +93,7 @@ export function taskToRow(task: Partial<Task> & { id: string }): NewTaskRow {
9293
modifiedBy: task.provenance?.modifiedBy ?? null,
9394
sessionId: task.provenance?.sessionId ?? null,
9495
pipelineStage: task.pipelineStage ?? null,
96+
assignee: task.assignee ?? null,
9597
};
9698
}
9799

packages/core/src/store/db-helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export async function upsertTask(
7979
sessionId: row.sessionId,
8080
// T060: pipeline stage name (RCASD-IVTR+C)
8181
pipelineStage: row.pipelineStage ?? null,
82+
assignee: row.assignee ?? null,
8283
// Always include archive metadata so unarchive clears stale values (T5034)
8384
archivedAt: archiveFields?.archivedAt ?? null,
8485
archiveReason: archiveFields?.archiveReason ?? null,

packages/core/src/store/sqlite-data-accessor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ export async function createSqliteDataAccessor(cwd?: string): Promise<DataAccess
783783
['createdBy', 'createdBy'],
784784
['modifiedBy', 'modifiedBy'],
785785
['sessionId', 'sessionId'],
786+
['assignee', 'assignee'],
786787
];
787788

788789
for (const [key, col] of fieldMap) {

packages/core/src/store/task-store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export async function updateTask(
9999
updateRow.cancellationReason = updates.cancellationReason;
100100
if (updates.verification !== undefined)
101101
updateRow.verificationJson = JSON.stringify(updates.verification);
102+
if (updates.assignee !== undefined) updateRow.assignee = updates.assignee;
102103

103104
db.update(schema.tasks).set(updateRow).where(eq(schema.tasks.id, taskId)).run();
104105

packages/core/src/store/tasks-schema.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ export const tasks = sqliteTable(
196196
// Not referencing lifecycle_stages.id so that stage binding works without
197197
// requiring a lifecycle pipeline record for every task.
198198
pipelineStage: text('pipeline_stage'),
199+
/** Agent ID that has claimed/is assigned to this task. */
200+
assignee: text('assignee'),
199201
},
200202
(table) => [
201203
index('idx_tasks_status').on(table.status),
@@ -205,6 +207,7 @@ export const tasks = sqliteTable(
205207
index('idx_tasks_priority').on(table.priority),
206208
index('idx_tasks_session_id').on(table.sessionId),
207209
index('idx_tasks_pipeline_stage').on(table.pipelineStage),
210+
index('idx_tasks_assignee').on(table.assignee),
208211
// T033 composite indexes
209212
index('idx_tasks_parent_status').on(table.parentId, table.status),
210213
index('idx_tasks_status_priority').on(table.status, table.priority),

packages/core/src/task-work/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ export async function startTask(
113113

114114
await acc.setMetaValue('focus_state', focus);
115115

116+
// Set assignee on the task (claim semantics)
117+
if (!task.assignee) {
118+
await acc.updateTaskFields(taskId, { assignee: process.env['CLEO_AGENT_ID'] ?? 'local' });
119+
}
120+
116121
await logOperation(
117122
'task_start',
118123
taskId,

0 commit comments

Comments
 (0)