Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion services/platform/app/features/tasks/lib/display.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ describe('run-admission refusals', () => {
const codes = recordedRefusals();
// A walk that found nothing would pass anything.
expect(codes.has('task_circuit_breaker')).toBe(true);
expect(codes.has('agent_busy')).toBe(true);
expect(
[...codes].filter((code) => !TASK_RUN_REFUSAL_LABEL_KEY[code]),
).toEqual([]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ describe('agent worker sessions', () => {
}
});

it('never takes a member worker for a standing one [SBX-R20]', () => {
it('never takes a member worker for a standing one [SBX-R21]', () => {
for (const worker of [1, 2, 3]) {
expect(
isStandingProjectAgentSession(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ describe('idle release worker', () => {
);
});

it('lets only a live run that names the stopped worker keep it [SBX-R18]', async () => {
it('lets only a live run that names the stopped worker keep it [SBX-R19]', async () => {
const texts: string[] = [];
const query = (strings: TemplateStringsArray) => {
texts.push(strings.join('?').replaceAll(/\s+/g, ' '));
Expand Down
2 changes: 1 addition & 1 deletion services/platform/backend/domains/sandbox/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ describe('sandbox settings read and write authority', () => {
]);
});

it('counts every run waiting for room beside the rows [SBX-R17]', async () => {
it('counts every run waiting for room beside the rows [SBX-R18]', async () => {
listViews.mockResolvedValue([]);
const counts = {
total: 3,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ describe('reserveSessionSlot — each workload against its own limit [SBX-R8]',
});
});

describe('every agent worker holds a slot of its own [SBX-R17]', () => {
describe('every agent worker holds a slot of its own [SBX-R18]', () => {
const SECOND_WORKER = { ...AGENT_SESSION, sessionId: 'pa-agent-1-w2' };

it("admits an agent's second worker beside its first while a slot is left", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('releaseProjectAgentSessionSlot', () => {
expect(update?.values).toEqual(['agent-1', 'org-1']);
});

it("stops a worker once its own run ended, whatever the agent's other workers do [SBX-R18]", async () => {
it("stops a worker once its own run ended, whatever the agent's other workers do [SBX-R19]", async () => {
const { sql, statements } = fakeSql([{ id: 'row-1' }]);

await releaseProjectAgentSessionSlot(sql, ARGS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ describe('listSandboxViewsForOrg', () => {
describe('agent workers on the Sandboxes page', () => {
const AGENT = '0b7e7a4c-1f7e-4a39-9c55-6f1d3c1f2a10';

test('names each worker of an agent and the task it works [SBX-R17]', async () => {
test('names each worker of an agent and the task it works [SBX-R18]', async () => {
const worker = (sessionId: string, createdAt: number): SessionRow => ({
...session(sessionId, createdAt),
ownerId: AGENT,
Expand Down Expand Up @@ -242,7 +242,7 @@ describe('agent workers on the Sandboxes page', () => {
]);
});

test('counts every waiting run by its reason, a park without one as unknown [SBX-R17]', async () => {
test('counts every waiting run by its reason, a park without one as unknown [SBX-R18]', async () => {
const query = (strings: TemplateStringsArray, ...values: unknown[]) => {
const text = strings.join('?');
// A run waits while it is parked, as every read of one tells it.
Expand Down
8 changes: 4 additions & 4 deletions services/platform/backend/domains/sandbox/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ limit, because what frees is an empty workspace, not room.
Every run of a project agent that works at the same time as another works in a sandbox of its
own, a worker. One agent working three tasks at once has three workers.

### SBX-R17 · Every agent worker is one sandbox and holds one agent-worker slot
### SBX-R18 · Every agent worker is one sandbox and holds one agent-worker slot

The limit of agent workers counts workers, not agents: one agent working three tasks at once
holds three slots. A run that would need one more worker than the limit allows waits for a
Expand All @@ -137,7 +137,7 @@ slot; a burst of starts opens no more workers than there are slots left.
- **Example**: Ada's organization allows 2 agent workers. Scribe works two tasks → 2 of 2 are
in use. Lector is started on a task → its run waits until one of Scribe's workers frees.

### SBX-R18 · A worker gives its slot back as soon as its own run ends
### SBX-R19 · A worker gives its slot back as soon as its own run ends

A worker stops and frees its slot once no run of its own is left: none working in it, and none
that has taken it and not started yet. The agent's other workers do not keep it up. A pinned
Expand All @@ -148,7 +148,7 @@ until that process has ended.
another. "Release notes" finishes → its worker stops and its slot is free at once, while
"Changelog" keeps working.

### SBX-R19 · A run takes a free worker before a new one is opened
### SBX-R20 · A run takes a free worker before a new one is opened

A starting run takes, in order: its task's previous worker, a worker that is still up, a
stopped worker (lowest number first), and only then a new one. When the only free worker is
Expand All @@ -158,7 +158,7 @@ it.
- **Example**: Scribe's workers 1 and 2 are stopped. Ada starts Scribe on a new task → it
works in worker 1, and no worker 3 is created.

### SBX-R20 · A member's runs work in that member's own workers
### SBX-R21 · A member's runs work in that member's own workers

The runs a member starts work in workers kept for that member and the agent, apart from the
workers of the runs editors start, and stay limited to their own task (`SBX-R7`) in whichever
Expand Down
2 changes: 1 addition & 1 deletion services/platform/backend/domains/tasks/agent-runs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1419,7 +1419,7 @@ describe('what every read shows of a waiting run [TASK-R25]', () => {
});
});

describe('wakeAgentParkedAgentRun — a worker that stays up wakes its own family [SBX-R18]', () => {
describe('wakeAgentParkedAgentRun — a worker that stays up wakes its own family [SBX-R19]', () => {
const AGENT = '0b7e7a4c-1f7e-4a39-9c55-6f1d3c1f2a10';
const CANDIDATES =
'SELECT id, session_id AS "sessionId" FROM app.project_agent_runs';
Expand Down
18 changes: 9 additions & 9 deletions services/platform/backend/domains/tasks/agent-workers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,20 @@ describe('which worker a run starts in', () => {
).toEqual({ sessionId: w(1), worker: 1 });
});

it('takes a stopped worker before it opens a new one [SBX-R19]', () => {
it('takes a stopped worker before it opens a new one [SBX-R20]', () => {
// Workers 1 and 2 are stopped: the run works in worker 1, no worker 3.
expect(
chooseWorker(facts({ workers: [worker(2), worker(1)], room: 2 })),
).toEqual({ sessionId: w(1), worker: 1 });
});

it('takes a worker that is still up before a stopped one [SBX-R19]', () => {
it('takes a worker that is still up before a stopped one [SBX-R20]', () => {
expect(
chooseWorker(facts({ workers: [worker(1), worker(2, { warm: true })] })),
).toEqual({ sessionId: w(2), worker: 2 });
});

it('opens the lowest free number, worker 1 again once it is gone [SBX-R19]', () => {
it('opens the lowest free number, worker 1 again once it is gone [SBX-R20]', () => {
expect(chooseWorker(facts({ workers: [] }))).toEqual({
sessionId: w(1),
worker: 1,
Expand All @@ -118,13 +118,13 @@ describe('which worker a run starts in', () => {
).toEqual({ sessionId: w(2), worker: 2 });
});

it('waits for a Destroy rather than opening a worker beside it [SBX-R19]', () => {
it('waits for a Destroy rather than opening a worker beside it [SBX-R20]', () => {
expect(
chooseWorker(facts({ workers: [worker(1, { destroyPending: true })] })),
).toEqual({ wait: 'destroy_pending' });
});

it('opens and wakes no worker the organization has no slot for [SBX-R17]', () => {
it('opens and wakes no worker the organization has no slot for [SBX-R18]', () => {
// Every slot is held: a stopped worker would need one, a new one too.
expect(
chooseWorker(
Expand Down Expand Up @@ -170,7 +170,7 @@ describe('which worker a run starts in', () => {
).toEqual({ sessionId: w(3), worker: 3 });
});

it("names a member's workers within the member's family [SBX-R20]", () => {
it("names a member's workers within the member's family [SBX-R21]", () => {
const base = memberWorkerSessionId(AGENT, 'user-mia', 1);
expect(
chooseWorker(
Expand Down Expand Up @@ -395,7 +395,7 @@ describe('claiming a worker', () => {
);
});

it("moves a run whose starter lost the editor role into the member's family [SBX-R20]", async () => {
it("moves a run whose starter lost the editor role into the member's family [SBX-R21]", async () => {
const member = memberWorkerSessionId(AGENT, 'user-ada', 1);
vi.mocked(sessionIdForAgentRun).mockResolvedValue(member);
const { sql } = claimSql({
Expand All @@ -408,7 +408,7 @@ describe('claiming a worker', () => {
});
});

it('never moves a member run into the standing family [SBX-R20]', async () => {
it('never moves a member run into the standing family [SBX-R21]', async () => {
const member = memberWorkerSessionId(AGENT, 'user-mia', 1);
const { sql } = claimSql({
run: { sessionId: member },
Expand Down Expand Up @@ -466,7 +466,7 @@ describe('a start that has not waited and a run woken for room [TASK-R25]', () =
);
});

it('still takes a worker that is up, which needs no room [SBX-R19]', async () => {
it('still takes a worker that is up, which needs no room [SBX-R20]', async () => {
const { sql } = claimSql({
family: [{ sessionId: w(1), status: 'active', pinned: false }],
inFlight: 1,
Expand Down
2 changes: 1 addition & 1 deletion services/platform/backend/domains/tasks/agent-workers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ async function readWorkerFacts(
// every kick names that one, so counting the name would send a task's
// next run away from its own worker, and a burst of starts to new
// workers, whenever another start came a moment earlier. The name still
// guards the worker against an idle release (`SBX-R18`). A start by an
// guards the worker against an idle release (`SBX-R19`). A start by an
// image that never claims shares worker 1 with a claim while both images
// serve; the per-session limit on processes bounds that.
const others = await tx<{ sessionId: string }[]>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ vi.mock('../projects/service.ts', async (importOriginal) => ({

// After the service: imported first, it leaves the service bound to the
// project service without the mock above.
import { parkedRunSql, parkedWaitingReasonSql } from './agent-runs.ts';
import {
getTaskOpsIndicators,
getTaskOpsIndicatorsForAccessibleProjects,
Expand Down Expand Up @@ -165,7 +164,10 @@ describe('the board reads every live agent run with its worker and wait', () =>
"ORDER BY (status = 'running') DESC, started_at_ms, seq",
);
// Whether a run waits, and why, is read through the predicate every
// read of a waiting run shares.
// read of a waiting run shares. Loaded here, not at the top: imported
// ahead of the service, it binds the service to the real project service.
const { parkedRunSql, parkedWaitingReasonSql } =
await import('./agent-runs.ts');
expect(read?.values).toEqual([
parkedRunSql(),
parkedWaitingReasonSql(),
Expand Down
2 changes: 1 addition & 1 deletion services/platform/backend/domains/tasks/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ on its day, also after it is moved.
## One agent on several tasks

An agent is a configuration; each of its runs works in a worker, a sandbox of its own. The
organization's limit of agent workers (`SBX-R17`) decides how many work at once.
organization's limit of agent workers (`SBX-R18`) decides how many work at once.

### TASK-R24 · An agent works each of its running tasks in a worker of its own

Expand Down
Loading