Q7: an agent runs one session at a time

Claims skip agents that already have a running run, manual runs refuse a
busy agent, a run whose task was reassigned fails before executing, and
tasks with a run in progress cannot be deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 03:11:39 -04:00
1 parent 0fb4ca9ae6
commit 72eb645067
6 files changed
+129 -14

No files matched your search

+3 -1
View File
@@ -46,7 +46,8 @@ export function isAutonomousPaused(connection: DatabaseConnection): boolean {
* (the founder DMing the CEO, a board member asking in a channel) means a
* person is waiting at the keyboard, so it outranks any amount of queued
* autonomous work; within each lane, first come first served. Non-chat runs
* carry no sourceSenderType and fall into the autonomous lane.
* carry no sourceSenderType and fall into the autonomous lane. An agent
* runs one session at a time: its queued runs wait while one is running.
*/
export function claimNextQueuedRun(connection: DatabaseConnection, excludedProviderIds: string[] = [], lane: ClaimLane = "any"): QueuedRun | null {
return connection.transaction(() => {
@@ -59,6 +60,7 @@ export function claimNextQueuedRun(connection: DatabaseConnection, excludedProvi
const row = connection.db
.prepare(`
select id, org_id, provider_id from task_runs where state = 'queued' ${exclusion} ${laneFilter}
and not exists (select 1 from task_runs busy where busy.state = 'running' and busy.agent_id = task_runs.agent_id)
order by
case when json_extract(secret_scope_json, '$.sourceSenderType') in ('employee', 'board') then 0 else 1 end asc,
created_at asc
@@ -512,6 +512,9 @@ export class OrganizationService {
}
deleteTask(input: DeleteTaskInput): void {
const running = this.db.prepare("select 1 from task_runs where task_id = ? and org_id = ? and state = 'running' limit 1")
.get(input.taskId, input.orgId);
if (running) throw new Error("This task has a run in progress; wait for it to finish before deleting the task.");
const result = this.db.prepare("delete from tasks where id = ? and org_id = ?").run(input.taskId, input.orgId);
if (result.changes === 0) throw new Error("Task was not found.");
this.audit.record({
+35 -11
View File
@@ -107,17 +107,38 @@ export class TaskExecutionService {
from task_runs where id = ? and org_id = ?
`).get(runId, orgId) as RunRow | undefined;
if (!run) throw new Error("Task run was not found.");
const task = this.requireTask(orgId, run.task_id);
const agent = this.requireAssignedAgent(orgId, task);
const provider = this.requireAgentProvider(orgId, agent);
const workspace = this.workspace(task.workspace_id);
const isolation: TaskRunIsolation = {
executionContextId: run.execution_context_id,
serviceIdentityId: run.service_identity_id,
workspaceMountJson: run.workspace_mount_json,
secretScopeJson: run.secret_scope_json,
};
return this.executeAgentRun({ orgId, runId, task, agent, provider, workspace, isolation });
let prepared: Parameters<TaskExecutionService["executeAgentRun"]>[0];
try {
const task = this.requireTask(orgId, run.task_id);
if (task.assignee_agent_id !== run.agent_id) {
throw new Error(`Task was reassigned after this run was queued (run agent ${run.agent_id}, assignee ${task.assignee_agent_id ?? "none"}).`);
}
const agent = this.requireAssignedAgent(orgId, task);
const provider = this.requireAgentProvider(orgId, agent);
const workspace = this.workspace(task.workspace_id);
const isolation: TaskRunIsolation = {
executionContextId: run.execution_context_id,
serviceIdentityId: run.service_identity_id,
workspaceMountJson: run.workspace_mount_json,
secretScopeJson: run.secret_scope_json,
};
prepared = { orgId, runId, task, agent, provider, workspace, isolation };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`[task-run] run ${runId} failed before execution: ${message}`);
this.failRun(orgId, runId, null, message);
this.audit.record({
orgId,
entityType: "task_run",
entityId: runId,
actorType: "system",
actorId: null,
action: "failed",
payload: { taskId: run.task_id, agentId: run.agent_id, providerId: run.provider_id, stage: "preparation", errorMessage: message },
});
throw error;
}
return this.executeAgentRun(prepared);
}
private async executeAgentRun(args: {
@@ -297,6 +318,9 @@ export class TaskExecutionService {
}
private createRun(orgId: string, taskId: string, agentId: string, providerId: string, isolation: TaskRunIsolation): string {
const busy = this.db.prepare("select t.title from task_runs r join tasks t on t.id = r.task_id where r.agent_id = ? and r.state = 'running' limit 1")
.get(agentId) as { title: string } | undefined;
if (busy) throw new Error(`This agent is already working on "${busy.title}"; run again when that session ends.`);
const runId = createId("run");
const now = new Date().toISOString();
this.db.prepare(`