Goals end at achieved, not at an empty board: the goal-review loop

The board draining to 280/280 done left the company idle under a
still-active goal, with 24 markdown files and zero code as its life's work.
Two structural gaps caused it: goals had no lifecycle (status was written
once as 'active' and never read again — nothing re-engages anyone when the
last task closes), and nothing anywhere distinguished plans from product,
so every "engineering" objective closed on documents.

The loop, closed:
- tasks.origin (migration 055) records provenance: chat_wake for mention
  vehicles (backfilled from their runs), goal_review for checkpoints, null
  for real work. Chatter and checkpoints can therefore never count as
  progress.
- spawnGoalReviews (each worker tick): an active goal with zero open tasks
  and real progress since the last review gets ONE review task for the goal
  owner. The review demands evidence — list the workspace, read the code,
  run the tests; documents are how work starts, not proof it happened — and
  must end in exactly one of: complete_goal, or the next wave of tasks with
  requires_filesystem engineering. A review closed with neither spawns
  nothing: termination is structural, not a counter.
- complete_goal (CEO or goal owner) finally lets someone say a goal is
  achieved, audited.
- CEO and department-head voices now state the doctrine: a goal that
  promises software closes on code that builds and passes tests in the
  workspace, and implementation is delegated with requires_filesystem: true.

On the live profile this arms one review: the active platform goal has 0
open tasks and 21 real completed ones, so the CEO gets the checkpoint on
next launch and the company starts building instead of stopping.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-09 20:29:42 -04:00
1 parent 291816aa06
commit 26de3129b1
14 files changed
+365 -4

No files matched your search

+7
View File
@@ -1,6 +1,8 @@
import type { BrowserWindow } from "electron";
import type { DatabaseConnection } from "../database/connection";
import { IPC_CHANNELS } from "../../shared/ipc";
import { OrganizationService } from "../services/organization-service";
import { spawnGoalReviews } from "../services/goal-review-service";
import { TaskExecutionService } from "../services/task-execution-service";
const DEFAULT_INTERVAL_MS = 5_000;
@@ -58,6 +60,7 @@ export function startTaskQueueWorker(input: TaskQueueWorkerInput): () => void {
}
};
const taskExecution = new TaskExecutionService(input.connection.db, undefined, undefined, notifyDataChanged);
const organization = new OrganizationService(input.connection.db);
const intervalMs = input.intervalMs ?? DEFAULT_INTERVAL_MS;
let running = false;
let wasPaused = false;
@@ -74,6 +77,10 @@ export function startTaskQueueWorker(input: TaskQueueWorkerInput): () => void {
wasPaused = paused;
}
if (paused) return;
// Goal checkpoints first, so a review task created this tick is
// eligible for the same tick's heartbeat enqueue below.
const reviews = input.connection.transaction(() => spawnGoalReviews(input.connection.db, organization));
if (reviews.length > 0) notifyDataChanged();
const enqueued = input.connection.transaction(() => taskExecution.enqueueHeartbeatRuns());
const claimed = claimNextQueuedRun(input.connection, taskExecution.cooledProviderIds());
if (!claimed) {
@@ -0,0 +1,18 @@
export const taskOriginSchema = `
-- Task provenance. 'chat_wake' marks conversation vehicles created by a
-- mention wake, 'goal_review' marks the CEO's goal checkpoints; plain work
-- (kickoffs, agent- and operator-created tasks) stays null. The goal-review
-- scheduler needs this to tell real progress from chatter — otherwise every
-- auto-closed reply task would count as progress and reviews would spawn
-- forever.
alter table tasks add column origin text;
-- Backfill: every existing wake vehicle is identifiable by its chat_mention
-- run (only the wake service ever creates those runs).
update tasks set origin = 'chat_wake'
where origin is null
and exists (select 1 from task_runs r where r.task_id = tasks.id and r.trigger = 'chat_mention');
insert into schema_migrations(version, name, applied_at)
values (55, 'task_origin', datetime('now'));
`;
+2
View File
@@ -53,6 +53,7 @@ import { purgeRuntimeSnapshotBloatSchema } from "./migrations/051-purge-runtime-
import { avatarImagesSchema } from "./migrations/052-avatar-images";
import { autonomousPauseSchema } from "./migrations/053-autonomous-pause";
import { closeStaleWakeTasksSchema } from "./migrations/054-close-stale-wake-tasks";
import { taskOriginSchema } from "./migrations/055-task-origin";
interface Migration {
version: number;
@@ -115,6 +116,7 @@ const MIGRATIONS: Migration[] = [
{ version: 52, name: "avatar_images", sql: avatarImagesSchema },
{ version: 53, name: "autonomous_pause", sql: autonomousPauseSchema },
{ version: 54, name: "close_stale_wake_tasks", sql: closeStaleWakeTasksSchema },
{ version: 55, name: "task_origin", sql: taskOriginSchema },
];
const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
+36
View File
@@ -0,0 +1,36 @@
import { AuditService } from "./audit-service";
import type { AgentToolContext, AgentToolDeps } from "./agent-tools";
import type { ToolDefinition } from "./provider-tool-turn";
export const GOAL_TOOLS: ToolDefinition[] = [
{
name: "complete_goal",
description: "Mark the goal your current task belongs to as achieved. CEO or the goal's owner only. Call it only when the evidence shows the goal is genuinely delivered — for software, that means code in the workspace that builds and passes its tests, not documents about it.",
parameters: { type: "object", additionalProperties: false, properties: {}, required: [] },
},
];
export function completeGoalTool(deps: AgentToolDeps, ctx: AgentToolContext): string {
if (!ctx.taskGoalId) return "ERROR: this task is not attached to a goal, so there is nothing to complete.";
const goal = deps.db.prepare("select id, title, status, owner_agent_id from goals where id = ? and org_id = ?")
.get(ctx.taskGoalId, ctx.orgId) as { id: string; title: string; status: string; owner_agent_id: string | null } | undefined;
if (!goal) throw new Error("Goal was not found.");
const acting = deps.db.prepare("select role_level from agents where id = ? and org_id = ?")
.get(ctx.agent.id, ctx.orgId) as { role_level: string } | undefined;
if (acting?.role_level !== "c_suite" && ctx.agent.id !== goal.owner_agent_id) {
return "ERROR: Only the CEO or the goal's owner can complete a goal. Report your evidence to them instead.";
}
if (goal.status !== "active") return `Goal "${goal.title}" is already ${goal.status}.`;
deps.db.prepare("update goals set status = 'achieved', updated_at = ? where id = ? and org_id = ?")
.run(new Date().toISOString(), goal.id, ctx.orgId);
new AuditService(deps.db).record({
orgId: ctx.orgId,
entityType: "goal",
entityId: goal.id,
actorType: "agent",
actorId: ctx.agent.id,
action: "status_changed",
payload: { status: "achieved" },
});
return `Goal "${goal.title}" is marked achieved. Tell the team.`;
}
+4
View File
@@ -8,6 +8,7 @@ import { readMessagesTool } from "./agent-read-messages";
import { grantConnectorTool, listConnectorsTool } from "./agent-connector-tools";
import { bindSkillSlugs, grantSkillTool, listSkillsTool } from "./agent-skill-tools";
import { createDocumentTool, writeDeliverableTool } from "./agent-deliverable-tools";
import { completeGoalTool, GOAL_TOOLS } from "./agent-goal-tools";
import { describeSend } from "./agent-message-presence";
import { optionalString, optionalStringArray, requireString } from "./agent-tool-args";
import type { JsonRecord } from "./provider-http";
@@ -216,6 +217,7 @@ export const AGENT_TOOLS: ToolDefinition[] = [
required: ["agent_name", "skill"],
},
},
...GOAL_TOOLS,
];
export function dispatchAgentTool(
@@ -232,6 +234,8 @@ export function dispatchAgentTool(
return createTask(deps, ctx, call.arguments, options);
case "update_task_status":
return updateTaskStatus(deps, ctx, call.arguments);
case "complete_goal":
return completeGoalTool(deps, ctx);
case "write_deliverable":
return writeDeliverableTool(deps, ctx, call.arguments);
case "send_message":
@@ -58,6 +58,7 @@ export class ChatAgentWakeService {
workspaceId: workspace?.id ?? null,
priority: TASK_PRIORITY.HIGH,
requiresFilesystem: false,
origin: "chat_wake",
});
const runId = this.createQueuedRun({ orgId: input.orgId, taskId, agent, workspace, messageId, threadId, now });
this.recordPresence(agent.id, { command: "mention", args: "" }, now);
@@ -80,6 +81,7 @@ export class ChatAgentWakeService {
workspaceId: workspace?.id ?? null,
priority: TASK_PRIORITY.HIGH,
requiresFilesystem: false,
origin: "chat_wake",
});
const runId = this.createQueuedRun({ orgId: input.orgId, taskId, agent, workspace, messageId, threadId, now });
this.recordPresence(agent.id, slash, now);
+94
View File
@@ -0,0 +1,94 @@
import type { DatabaseSync } from "node:sqlite";
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS } from "../../shared/domain";
import { ensureAgentHeartbeat, resolveGoalKickoff } from "./goal-kickoff";
import type { OrganizationService } from "./organization-service";
interface ReviewableGoalRow {
id: string;
org_id: string;
title: string;
description: string;
owner_agent_id: string | null;
}
/**
* Closes the loop the task pipeline leaves open: tasks end at `done`, but a
* company is finished when its GOAL is achieved. Whenever an active goal has
* no open tasks left and real work landed since the last checkpoint, the goal
* owner (usually the CEO) gets one review task: verify the goal against the
* actual state of the workspace, then either mark it achieved (complete_goal)
* or plan the next wave of tasks. Without this, the company drains its board,
* declares nothing, and idles forever under a still-active goal.
*
* Termination is structural: chatter (`chat_wake`) and reviews themselves
* never count as progress, so a review that ends with no new work and no
* completed goal does NOT respawn — the loop only continues while real tasks
* keep completing or the goal is closed.
*/
export function spawnGoalReviews(db: DatabaseSync, organization: OrganizationService): string[] {
const goals = db.prepare(`
select g.id, g.org_id, g.title, g.description, g.owner_agent_id
from goals g
where g.status = 'active'
and not exists (
select 1 from tasks t
where t.goal_id = g.id and t.status in ('backlog', 'in_progress', 'review')
)
and exists (
select 1 from tasks t
where t.goal_id = g.id and t.status = 'done' and t.origin is null
)
and (
not exists (
select 1 from tasks r where r.goal_id = g.id and r.origin = 'goal_review'
)
or exists (
select 1 from tasks t
where t.goal_id = g.id and t.status = 'done' and t.origin is null
and t.updated_at > (
select max(r.created_at) from tasks r
where r.goal_id = g.id and r.origin = 'goal_review'
)
)
)
`).all() as unknown as ReviewableGoalRow[];
const created: string[] = [];
for (const goal of goals) {
const plan = resolveGoalKickoff(db, {
orgId: goal.org_id,
goalTitle: goal.title,
goalDescription: goal.description,
ownerAgentId: goal.owner_agent_id,
});
if (!plan) continue;
ensureAgentHeartbeat(db, goal.org_id, plan.assigneeAgentId);
const taskId = organization.createTask({
orgId: goal.org_id,
title: `Goal review: ${goal.title.trim()}`,
description: goalReviewDescription(goal.title),
status: TASK_STATUS.BACKLOG,
assigneeAgentId: plan.assigneeAgentId,
risk: RISK_LEVEL.MEDIUM,
projectId: null,
goalId: goal.id,
workspaceId: plan.workspaceId,
priority: TASK_PRIORITY.HIGH,
requiresFilesystem: plan.workspaceId !== null,
origin: "goal_review",
});
created.push(taskId);
console.info(`[goal-review] all tasks for goal "${goal.title}" are finished; created a review checkpoint for the goal owner.`);
}
return created;
}
function goalReviewDescription(goalTitle: string): string {
return [
`Every task under the goal "${goalTitle.trim()}" is finished, but the goal itself is still open. Decide what happens next — this is a checkpoint, not a formality.`,
"First gather evidence: list_files and read_file the workspace, and read what the team reported. Plans, specs, and status documents are not the product. If the goal promises working software, the proof is code in the workspace that builds and passes its tests (run_command).",
"Then take exactly one of these actions before closing this task:",
"1. The goal is genuinely achieved -> call complete_goal.",
"2. It is not achieved -> create the next wave of tasks (create_task, assigned by name). Implementation work must set requires_filesystem: true so the assignee can change code and run commands. Documents alone must not close the next wave either.",
].join("\n\n");
}
+3 -2
View File
@@ -234,9 +234,9 @@ export class OrganizationService {
this.db.prepare(`
insert into tasks(
id, org_id, title, description, status, assignee_agent_id, risk,
project_id, goal_id, workspace_id, priority, requires_filesystem, created_at, updated_at
project_id, goal_id, workspace_id, priority, requires_filesystem, origin, created_at, updated_at
)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
id,
input.orgId,
@@ -250,6 +250,7 @@ export class OrganizationService {
input.workspaceId,
input.priority,
input.requiresFilesystem ? 1 : 0,
input.origin ?? null,
now,
now,
);
+2 -1
View File
@@ -139,10 +139,11 @@ function roleVoice(input: AgentInstructionContext): string {
return [
"You run this company. Think in outcomes, make the call when it is yours to make, and put the right person on each piece instead of doing everything yourself — then tell the team the why, not just the what.",
"Build a real organization, the way OKR-run companies do: the company goal is your objective; hire chief officers for the functions it needs (hire_agent with seniority \"director\" and a title like CTO, CFO, CMO, COO — 2-4 of them, not a flat pool of ICs), hand each officer their department's objective as a task, and let them hire their own managers (seniority \"lead\") and staff. You manage the officers; they manage their departments.",
"The goal closes on evidence, not paperwork: if it promises a product, the finish line is working code in the workspace that builds and passes its tests. Plans and specs are how work starts — make sure implementation lands as requires_filesystem tasks so the builders can actually build.",
].join(" ");
}
if (input.hasReports) {
return "You lead a department. Break your objective into key results your team can own: hire the managers (seniority \"lead\") and staff your department needs with hire_agent, assign each report concrete tasks with create_task, and hold the line on quality. Clear the path for your reports, give them specific direction instead of 'looks good', raise blockers to your manager early, and report progress up the chain.";
return "You lead a department. Break your objective into key results your team can own: hire the managers (seniority \"lead\") and staff your department needs with hire_agent, assign each report concrete tasks with create_task, and hold the line on quality. Delegate implementation with requires_filesystem: true so your engineers can change code and run tests — an objective that promises software is not done until it builds and passes in the workspace. Clear the path for your reports, give them specific direction instead of 'looks good', raise blockers to your manager early, and report progress up the chain.";
}
return "You own your craft. Ask the question early instead of guessing, do the work properly, and when you are stuck for more than a few minutes say so in the thread instead of spinning in place.";
}
+2
View File
@@ -86,6 +86,8 @@ export interface CreateTaskInput {
workspaceId: string | null;
priority: TaskPriority;
requiresFilesystem: boolean;
/** Provenance: 'chat_wake' for mention-wake vehicles, 'goal_review' for CEO goal checkpoints, omitted for plain work. */
origin?: "chat_wake" | "goal_review";
}
export interface UpdateTaskStatusInput {
+56
View File
@@ -0,0 +1,56 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { dispatchAgentTool, type AgentToolContext, type AgentToolDeps } from "../../src/main/services/agent-tools";
import type { ToolCall } from "../../src/main/services/provider-tool-turn";
function call(name: string): ToolCall {
return { id: `call_${randomUUID()}`, name, arguments: {} };
}
describe("complete_goal tool", () => {
test("the CEO can close their goal; a staff engineer cannot", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-goal-tool-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let staffId = "";
let goalId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "GoalCorp", employeeName: "Yang", mission: "m", goal: "g" });
const providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null, roleLevel: "c_suite",
});
staffId = organization.createAgent({
orgId, name: "Dev", role: "Engineer", department: "Engineering",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 10000, managerAgentId: ceoId, roleLevel: "staff",
});
goalId = organization.createGoal({ orgId, title: "Ship it", description: "For real.", ownerAgentId: ceoId });
});
const deps: AgentToolDeps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctxFor = (agentId: string): AgentToolContext => ({
orgId, agent: { id: agentId, modelProviderId: null, modelId: null },
taskProjectId: null, taskGoalId: goalId, workspace: null,
});
expect(dispatchAgentTool(deps, ctxFor(staffId), call("complete_goal"))).toContain("ERROR");
expect(connection.db.prepare("select status from goals where id = ?").get(goalId)).toEqual({ status: "active" });
expect(dispatchAgentTool(deps, ctxFor(ceoId), call("complete_goal"))).toContain("achieved");
expect(connection.db.prepare("select status from goals where id = ?").get(goalId)).toEqual({ status: "achieved" });
// Idempotent, and honest about it.
expect(dispatchAgentTool(deps, ctxFor(ceoId), call("complete_goal"))).toContain("already achieved");
// A task with no goal attached cannot complete anything.
const noGoalCtx = { ...ctxFor(ceoId), taskGoalId: null };
expect(dispatchAgentTool(deps, noGoalCtx, call("complete_goal"))).toContain("ERROR");
connection.close();
});
});
+1 -1
View File
@@ -24,7 +24,7 @@ describe("domain services", () => {
const connection = createConnection();
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54]);
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55]);
connection.close();
});
+108
View File
@@ -0,0 +1,108 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { spawnGoalReviews } from "../../src/main/services/goal-review-service";
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS } from "../../src/shared/domain";
interface Harness {
connection: DatabaseConnection;
organization: OrganizationService;
orgId: string;
ceoId: string;
goalId: string;
kickoffId: string;
}
function createHarness(): Harness {
const connection = new DatabaseConnection(join(tmpdir(), `ac-goal-review-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let goalId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "LoopCorp", employeeName: "Yang", mission: "m", goal: "g" });
const providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null, roleLevel: "c_suite",
});
goalId = organization.createGoal({ orgId, title: "Ship the platform", description: "Deliver it for real.", ownerAgentId: ceoId });
});
const kickoffId = (connection.db.prepare("select id from tasks where goal_id = ?").get(goalId) as { id: string }).id;
return { connection, organization, orgId, ceoId, goalId, kickoffId };
}
function setTaskStatus(h: Harness, taskId: string, status: string, updatedAt?: string): void {
h.connection.db.prepare("update tasks set status = ?, updated_at = ? where id = ?")
.run(status, updatedAt ?? new Date().toISOString(), taskId);
}
function addDoneTask(h: Harness, title: string, origin: string | null, updatedAt: string): string {
let taskId = "";
h.connection.transaction(() => {
taskId = h.organization.createTask({
orgId: h.orgId, title, description: "d", status: TASK_STATUS.DONE, assigneeAgentId: h.ceoId,
risk: RISK_LEVEL.LOW, projectId: null, goalId: h.goalId, workspaceId: null,
priority: TASK_PRIORITY.NORMAL, requiresFilesystem: false,
...(origin ? { origin: origin as "chat_wake" | "goal_review" } : {}),
});
});
h.connection.db.prepare("update tasks set updated_at = ? where id = ?").run(updatedAt, taskId);
return taskId;
}
function reviews(h: Harness): Array<{ id: string; status: string }> {
return h.connection.db.prepare("select id, status from tasks where goal_id = ? and origin = 'goal_review' order by created_at")
.all(h.goalId) as Array<{ id: string; status: string }>;
}
describe("goal review service", () => {
test("spawns one review when the board drains, and never duplicates while it is open", () => {
const h = createHarness();
// Open kickoff blocks the checkpoint.
expect(h.connection.transaction(() => spawnGoalReviews(h.connection.db, h.organization))).toEqual([]);
setTaskStatus(h, h.kickoffId, TASK_STATUS.DONE);
const first = h.connection.transaction(() => spawnGoalReviews(h.connection.db, h.organization));
expect(first).toHaveLength(1);
const review = h.connection.db.prepare("select title, origin, status, assignee_agent_id from tasks where id = ?")
.get(first[0]) as { title: string; origin: string; status: string; assignee_agent_id: string };
expect(review).toMatchObject({ origin: "goal_review", status: "backlog", assignee_agent_id: h.ceoId });
expect(review.title).toContain("Goal review:");
// The open review itself blocks a duplicate.
expect(h.connection.transaction(() => spawnGoalReviews(h.connection.db, h.organization))).toEqual([]);
h.connection.close();
});
test("a review that ends without new work does not respawn; real progress does", () => {
const h = createHarness();
setTaskStatus(h, h.kickoffId, TASK_STATUS.DONE);
const [reviewId] = h.connection.transaction(() => spawnGoalReviews(h.connection.db, h.organization));
// CEO closes the review without creating tasks or completing the goal: silence, not a loop.
setTaskStatus(h, reviewId, TASK_STATUS.DONE, "2026-08-01T10:00:00.000Z");
expect(h.connection.transaction(() => spawnGoalReviews(h.connection.db, h.organization))).toEqual([]);
// Chatter never counts as progress.
addDoneTask(h, "Reply to CEO", "chat_wake", "2026-08-01T12:00:00.000Z");
expect(h.connection.transaction(() => spawnGoalReviews(h.connection.db, h.organization))).toEqual([]);
// A real completed task after the last review re-arms the checkpoint.
addDoneTask(h, "Implement tenant module", null, "2026-08-01T13:00:00.000Z");
expect(h.connection.transaction(() => spawnGoalReviews(h.connection.db, h.organization))).toHaveLength(1);
expect(reviews(h)).toHaveLength(2);
h.connection.close();
});
test("achieved goals never spawn reviews", () => {
const h = createHarness();
setTaskStatus(h, h.kickoffId, TASK_STATUS.DONE);
h.connection.db.prepare("update goals set status = 'achieved' where id = ?").run(h.goalId);
expect(h.connection.transaction(() => spawnGoalReviews(h.connection.db, h.organization))).toEqual([]);
h.connection.close();
});
});
+30
View File
@@ -0,0 +1,30 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { describe, expect, test } from "vitest";
import { taskOriginSchema } from "../../src/main/database/migrations/055-task-origin";
describe("task origin migration", () => {
test("adds the column and backfills historical wake vehicles from their runs", () => {
const db = new DatabaseSync(join(tmpdir(), `agentcompany-origin-migration-${randomUUID()}.db`));
db.exec(`
create table schema_migrations(version integer primary key, name text not null, applied_at text not null);
create table tasks(id text primary key, status text not null, updated_at text not null);
create table task_runs(id text primary key, task_id text not null, trigger text not null, state text not null);
insert into schema_migrations(version, name, applied_at) values (54, 'close_stale_wake_tasks', datetime('now'));
insert into tasks(id, status, updated_at) values ('tsk_wake', 'done', datetime('now'));
insert into tasks(id, status, updated_at) values ('tsk_real', 'done', datetime('now'));
insert into task_runs(id, task_id, trigger, state) values ('run_1', 'tsk_wake', 'chat_mention', 'completed');
insert into task_runs(id, task_id, trigger, state) values ('run_2', 'tsk_real', 'heartbeat', 'completed');
`);
db.exec(taskOriginSchema);
const origins = Object.fromEntries(
(db.prepare("select id, origin from tasks").all() as Array<{ id: string; origin: string | null }>).map((row) => [row.id, row.origin]),
);
expect(origins).toEqual({ tsk_wake: "chat_wake", tsk_real: null });
expect(db.prepare("select version from schema_migrations order by version desc limit 1").get()).toEqual({ version: 55 });
});
});