@agentium/queue 3.1.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -19
- package/dist/connection.d.ts +27 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/durable.d.ts +58 -0
- package/dist/durable.d.ts.map +1 -0
- package/dist/index.cjs +480 -298
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +477 -278
- package/dist/job-producer.d.ts +47 -21
- package/dist/job-producer.d.ts.map +1 -1
- package/dist/job-types.d.ts +4 -0
- package/dist/job-types.d.ts.map +1 -1
- package/dist/job-worker.d.ts +0 -2
- package/dist/job-worker.d.ts.map +1 -1
- package/package.json +10 -7
package/README.md
CHANGED
|
@@ -1,36 +1,80 @@
|
|
|
1
1
|
# @agentium/queue
|
|
2
2
|
|
|
3
|
-
Background
|
|
3
|
+
Background Agent, Team and Workflow execution on BullMQ **5.81.5+ within v5 or 6.3.11+ within v6** and Redis. Node 22.18+ or 24.11+ is required. New queues can use v6; migrate persisted legacy repeat records with v5 before deploying v6 workers or writers.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```sh
|
|
6
|
+
npm install @agentium/queue bullmq@^6.3.11 ioredis
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { AgentQueue, AgentWorker } from "@agentium/queue";
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
const connection = "redis://127.0.0.1:6379/0";
|
|
13
|
+
const queue = new AgentQueue({ connection, defaultJobOptions: {
|
|
14
|
+
attempts: 3, backoff: { type: "exponential", delay: 1000 },
|
|
15
|
+
} });
|
|
16
|
+
const worker = new AgentWorker({ connection, agentRegistry: { assistant: agent } });
|
|
17
|
+
await queue.enqueueAgentRun({ agentName: "assistant", input: "Summarize the report" });
|
|
18
|
+
await queue.schedule({ id: "daily-summary", cron: "0 9 * * *", timezone: "UTC",
|
|
19
|
+
agent: { name: "assistant", input: "Summarize new reports" },
|
|
20
|
+
});
|
|
21
|
+
// The same ID updates its schedule; it does not create another scheduler.
|
|
22
|
+
await queue.unschedule("daily-summary");
|
|
23
|
+
await worker.stop();
|
|
24
|
+
await queue.close();
|
|
9
25
|
```
|
|
10
26
|
|
|
11
|
-
|
|
27
|
+
The default queue name is now `agentium-jobs`; the old `agentium:jobs` was rejected by current BullMQ because queue names cannot contain colons. Set the same explicit, valid `queueName` on producer and worker to continue an existing deployment. Constructors load BullMQ through Node's ESM-compatible `createRequire`. Redis URLs preserve credentials, database and `rediss:` TLS settings.
|
|
12
28
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
`schedule` accepts exactly one `agent`, `team` or `workflow` target, and uses a stable caller-supplied Job Scheduler ID. Workflow payloads forward `initialState`, `sessionId`, `userId` and `tenantId` into `Workflow.run`. Agents and Teams also forward identity. Queue access is a trusted host boundary: authenticate producers and authorize these claims before enqueueing; a Redis payload is not proof of identity.
|
|
30
|
+
|
|
31
|
+
Retry configuration belongs to the producer's `defaultJobOptions` or individual enqueue options. The previous `WorkerConfig.attempts/backoffDelay` fields never controlled BullMQ jobs; they have been removed from the TypeScript API. JavaScript callers still receive a migration diagnostic before BullMQ or a connection is initialized. Retrying effectful agent work requires application idempotency. `cancelJob` removes a waiting/delayed job and cannot cancel an active locked job. `stop` drains active work and reports its timeout; it does not claim that remote effects were rolled back.
|
|
32
|
+
|
|
33
|
+
## Persisted repeat schedule migration
|
|
16
34
|
|
|
17
|
-
|
|
18
|
-
const connection = { host: "localhost", port: 6379 };
|
|
35
|
+
BullMQ v6 removes legacy repeat APIs. `listLegacySchedules()` and `removeLegacySchedule()` require a BullMQ v5 installation and fail clearly on v6, including on clean queues. On v6, `schedule`, recurring enqueue and `listSchedules` refuse a queue containing legacy metadata; they do not migrate it or silently hide it. See the [official v5-to-v6 migration guide](https://docs.bullmq.io/guide/migrations/migrate-from-v5-to-v6).
|
|
19
36
|
|
|
20
|
-
|
|
21
|
-
const worker = new AgentWorker({ agents: { assistant: agent }, connection });
|
|
37
|
+
Perform the following maintenance with BullMQ 5.81.5 (or a compatible v5 release). Legacy records stay in Redis until an operator removes them. Do not run old and new schedule writers concurrently.
|
|
22
38
|
|
|
23
|
-
|
|
39
|
+
1. Stop schedule writers, call `queue.pause()`, let active jobs finish, then stop workers. `removeLegacySchedule` requires both a paused queue and zero active jobs.
|
|
40
|
+
2. Save `await queue.listLegacySchedules()` and the original application payloads/options. The inventory includes keys, names, cron, timezone and next timestamp; it is not a complete payload backup. Back up Redis before changes.
|
|
41
|
+
3. Choose stable new IDs and deduplicate the desired schedule set. Remove each legacy key with `removeLegacySchedule(key)` before calling `schedule`. New creation refuses a matching legacy name/cron to prevent accidental overlap.
|
|
42
|
+
4. Verify `listLegacySchedules()` is empty for the whole queue and `listSchedules()` contains exactly the intended IDs. Close the v5 maintenance client. Deploy v6 workers and writers, verify `listSchedules()` again, then `await queue.resume()` and enable schedule writers.
|
|
43
|
+
5. To roll back, stop v6 workers/writers and return to v5 while paused and drained. Remove replacement IDs using `unschedule`, restore the old records with saved original BullMQ repeat options and payloads (or restore the Redis backup), verify the inventory, and restart the old deployment. Never restore legacy records while replacements are active.
|
|
44
|
+
|
|
45
|
+
The convenience `enqueue*({repeat})` APIs now upsert schedulers using a hash of target, payload and repeat options. Changing the payload changes that hash; use `schedule({id})` when you need stable updates. `listSchedules` excludes legacy records on v5 and rejects them on v6. BullMQ stores both formats in the repeat index: the adapter recognizes new schedulers by their returned `iterationCount` and also handles the v6 SDK diagnostic for older legacy keys. The migration check is not a lock against concurrent legacy writers; stopping them remains required.
|
|
46
|
+
|
|
47
|
+
## Verification
|
|
48
|
+
|
|
49
|
+
Run this command from the repository root after building core:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
AGENTIUM_REDIS_TEST=1 AGENTIUM_REDIS_PORT=6389 npx vitest run packages/queue/src/__tests__
|
|
24
53
|
```
|
|
25
54
|
|
|
26
|
-
|
|
55
|
+
Use a disposable Redis database. Fixtures create unique queues and remove only those queues. CI runs them against an isolated Redis 7 service. They exercise the real BullMQ 5.81.5 and 6.3.11 SDKs: scheduler upserts/removal, v5 persisted legacy data rejected by v6, explicit paused v5 migration, v6 use of migrated scheduler records, older legacy-key rejection, worker identity/progress isolation and durable delivery. The fixture uses the `bullmq-v5` development alias for the v5 maintenance process; it does not emulate SDK methods. Redis Cluster and alternative BullMQ backends are outside this fixture.
|
|
56
|
+
|
|
57
|
+
## Opt-in durable delivery
|
|
27
58
|
|
|
28
|
-
|
|
59
|
+
`DurableAgentQueue` and `DurableAgentWorker` use a separate `agentium-durable` queue. The task store owns execution state; Redis delivers a versioned hint containing only tenant/task IDs and an immutable definition digest. Existing Agent/Team/Workflow workers keep their current behavior.
|
|
29
60
|
|
|
30
|
-
|
|
61
|
+
```ts
|
|
62
|
+
import { DurableAgentQueue, DurableAgentWorker } from '@agentium/queue';
|
|
63
|
+
// supervisor uses the same initialized durable store on all workers.
|
|
64
|
+
const durable = new DurableAgentQueue({ connection, supervisor, admit: admitTask });
|
|
65
|
+
const durableWorker = new DurableAgentWorker({
|
|
66
|
+
connection, supervisor, admit: recheckCurrentGrants,
|
|
67
|
+
drivers: [{ id: 'save-document', version: 1, recoverable: true, execute: saveDocument }],
|
|
68
|
+
});
|
|
69
|
+
await durable.enqueue({
|
|
70
|
+
id: taskId, identity: verifiedIdentity, manifestHash, inputRef,
|
|
71
|
+
policyRevision: 1, grantRefs: approvedGrants,
|
|
72
|
+
driver: { id: 'save-document', version: 1 }, input: { documentRef },
|
|
73
|
+
});
|
|
74
|
+
```
|
|
31
75
|
|
|
32
|
-
|
|
76
|
+
Both callbacks are mandatory trusted-host admission boundaries. Producer admission happens before persistence; worker admission happens before and after the atomic claim. Versioned drivers must explicitly support replay with stable action IDs and reconciliation. Registering an ordinary Agent/Team/Workflow function does not establish that property.
|
|
33
77
|
|
|
34
|
-
|
|
78
|
+
Persistence precedes Redis enqueue. If enqueue fails, the task remains recoverable: retry the same immutable definition or call `wake({tenantId, taskId})`. Wake explicitly after approval or reconciliation. Duplicate delivery is fenced by the store; an early retry remains delayed until an existing lease can be reclaimed. Completed task deliveries return only task/state/revision metadata to Redis. Unknown external outcomes block handler execution until reconciliation.
|
|
35
79
|
|
|
36
|
-
|
|
80
|
+
`cancel` persists cancellation and wakes a worker; it does not claim immediate remote termination. The host must retry `wake` when Redis is unavailable. `stop` drains active work. Maintain a host recovery sweep for persisted work without a delivery hint; this package does not scan arbitrary stores or start hidden background services. See the [durable store and recovery contract](../core/src/durable/README.md).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare function queueConnection(value: string | {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
password?: string;
|
|
5
|
+
db?: number;
|
|
6
|
+
tls?: boolean;
|
|
7
|
+
}): {
|
|
8
|
+
tls: {};
|
|
9
|
+
host: string;
|
|
10
|
+
port: number;
|
|
11
|
+
password?: string;
|
|
12
|
+
db?: number;
|
|
13
|
+
} | {
|
|
14
|
+
tls: undefined;
|
|
15
|
+
host: string;
|
|
16
|
+
port: number;
|
|
17
|
+
password?: string;
|
|
18
|
+
db?: number;
|
|
19
|
+
} | {
|
|
20
|
+
tls?: {} | undefined;
|
|
21
|
+
password?: string | undefined;
|
|
22
|
+
username?: string | undefined;
|
|
23
|
+
host: string;
|
|
24
|
+
port: number;
|
|
25
|
+
db: number;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE;;UAArE,MAAM;UAAQ,MAAM;eAAa,MAAM;SAAO,MAAM;;;UAApD,MAAM;UAAQ,MAAM;eAAa,MAAM;SAAO,MAAM;;;;;;;;EAe7E"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type DurableTaskHandler, type DurableTaskInput, type DurableTaskKey, type DurableTaskRecord, type DurableTaskSupervisor } from "@agentium/core";
|
|
2
|
+
import type { QueueConfig } from "./job-producer.js";
|
|
3
|
+
export interface DurableJobEnvelope {
|
|
4
|
+
schemaVersion: 1;
|
|
5
|
+
type: "durable";
|
|
6
|
+
tenantId: string;
|
|
7
|
+
taskId: string;
|
|
8
|
+
inputDigest: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DurableDriverRegistration {
|
|
11
|
+
id: string;
|
|
12
|
+
version: number;
|
|
13
|
+
/** Explicit host assertion: resumes use stable action IDs and reconcile uncertain effects. */
|
|
14
|
+
recoverable: true;
|
|
15
|
+
execute: DurableTaskHandler;
|
|
16
|
+
}
|
|
17
|
+
export interface DurableQueueConfig extends QueueConfig {
|
|
18
|
+
supervisor: DurableTaskSupervisor;
|
|
19
|
+
/** Verify identity, definition, policy and grants before writing any task/queue record. */
|
|
20
|
+
admit: (task: Readonly<DurableTaskInput>) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/** Queue delivery is a hint; Mongo/another atomic task store owns authoritative state.
|
|
23
|
+
* Persistence precedes enqueue. If Redis fails, call wake() after retrying admission.
|
|
24
|
+
* The stable task key prevents a delivery retry from creating a second execution.
|
|
25
|
+
*/
|
|
26
|
+
export declare class DurableAgentQueue {
|
|
27
|
+
private readonly config;
|
|
28
|
+
private queue;
|
|
29
|
+
constructor(config: DurableQueueConfig);
|
|
30
|
+
enqueue(task: DurableTaskInput): Promise<{
|
|
31
|
+
taskId: string;
|
|
32
|
+
deliveryId: string;
|
|
33
|
+
}>;
|
|
34
|
+
/** Explicit re-delivery after approval, reconciliation, or a failed initial enqueue. */
|
|
35
|
+
wake(key: DurableTaskKey): Promise<{
|
|
36
|
+
taskId: string;
|
|
37
|
+
deliveryId: string;
|
|
38
|
+
}>;
|
|
39
|
+
cancel(key: DurableTaskKey, reason?: string): Promise<void>;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export interface DurableWorkerConfig extends Omit<QueueConfig, "defaultJobOptions"> {
|
|
43
|
+
supervisor: DurableTaskSupervisor;
|
|
44
|
+
workerId?: string;
|
|
45
|
+
concurrency?: number;
|
|
46
|
+
drivers: readonly DurableDriverRegistration[];
|
|
47
|
+
/** Revalidate policy/grants from authoritative host state; a payload is not authentication. */
|
|
48
|
+
admit: (task: Readonly<DurableTaskRecord>) => Promise<void>;
|
|
49
|
+
/** Observe delivery failures; durable task outcome remains available from the store. */
|
|
50
|
+
onError?: (error: Error) => void;
|
|
51
|
+
}
|
|
52
|
+
export declare class DurableAgentWorker {
|
|
53
|
+
private worker;
|
|
54
|
+
constructor(config: DurableWorkerConfig);
|
|
55
|
+
/** Drains owned work. Active durable cancellation is explicit through queue.cancel(). */
|
|
56
|
+
stop(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=durable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable.d.ts","sourceRoot":"","sources":["../src/durable.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAG3B,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,CAAC,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AACD,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,8FAA8F;IAC9F,WAAW,EAAE,IAAI,CAAC;IAClB,OAAO,EAAE,kBAAkB,CAAC;CAC7B;AACD,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,UAAU,EAAE,qBAAqB,CAAC;IAClC,2FAA2F;IAC3F,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AA6BD;;;GAGG;AACH,qBAAa,iBAAiB;IAEhB,OAAO,CAAC,QAAQ,CAAC,MAAM;IADnC,OAAO,CAAC,KAAK,CAAM;gBACU,MAAM,EAAE,kBAAkB;IAOjD,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAoBtF,wFAAwF;IAClF,IAAI,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAO1E,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC;IACjF,UAAU,EAAE,qBAAqB,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,SAAS,yBAAyB,EAAE,CAAC;IAC9C,+FAA+F;IAC/F,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,wFAAwF;IACxF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AACD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAM;gBACR,MAAM,EAAE,mBAAmB;IAoDvC,yFAAyF;IACnF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B"}
|