@endyai/atq 0.0.1
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 +109 -0
- package/cli.js +35 -0
- package/index.js +69 -0
- package/package.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# atq
|
|
2
|
+
|
|
3
|
+
A task queue for agentic workloads.
|
|
4
|
+
|
|
5
|
+
## The problem
|
|
6
|
+
|
|
7
|
+
You have a large batch of tasks that need to be processed using an agent (language, reasoning, tool use tasks).
|
|
8
|
+
|
|
9
|
+
Running these through a single long-lived agent doesn't work:
|
|
10
|
+
|
|
11
|
+
- **Context bloat.** The agent accumulates results from previous tasks. Gets slower, more expensive, less focused.
|
|
12
|
+
- **Fragile batching.** One failure midway can stall everything. Tracking what succeeded is painful.
|
|
13
|
+
- **No concurrency.** Processing items one at a time when each task is independent is just slow.
|
|
14
|
+
|
|
15
|
+
## The approach
|
|
16
|
+
|
|
17
|
+
Each task gets its own fresh agent with a clean context. A pool of agents process items concurrently — one finishes, the next starts immediately.
|
|
18
|
+
|
|
19
|
+
- **Fresh context per task.** Each agent only sees the item it's working on. Better focus, lower cost, no cross-contamination.
|
|
20
|
+
- **Concurrent by default.** Control the pool size with `concurrency`.
|
|
21
|
+
- **Stream results.** Results stream to stdout as agents complete.
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
graph LR
|
|
25
|
+
stdin["stdin (JSONL)"] --> atq
|
|
26
|
+
atq --> A1[Agent 1]
|
|
27
|
+
atq --> A2[Agent 2]
|
|
28
|
+
atq --> A3[Agent N]
|
|
29
|
+
A1 --> stdout["stdout (JSONL)"]
|
|
30
|
+
A2 --> stdout
|
|
31
|
+
A3 --> stdout
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## CLI
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cat companies.jsonl | atq -p "Normalize this company name. Return just the name." -c 10 -m claude-sonnet-4-6
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Input is piped via stdin (one JSON object per line):
|
|
41
|
+
```jsonl
|
|
42
|
+
{"name": "Google LLC"}
|
|
43
|
+
{"name": "APPLE INC."}
|
|
44
|
+
{"name": "Meta Platforms, Inc."}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Output** (stdout, one JSON line per completed item):
|
|
48
|
+
```jsonl
|
|
49
|
+
{"item":{"name":"Google LLC"},"output":"Google"}
|
|
50
|
+
{"item":{"name":"APPLE INC."},"output":"Apple"}
|
|
51
|
+
{"item":{"name":"Meta Platforms, Inc."},"output":"Meta"}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Progress** (stderr):
|
|
55
|
+
```
|
|
56
|
+
[1/3]
|
|
57
|
+
[2/3]
|
|
58
|
+
[3/3]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Flags
|
|
62
|
+
|
|
63
|
+
| Short | Long | Required | Default | Description |
|
|
64
|
+
|-------|------|----------|---------|-------------|
|
|
65
|
+
| `-p` | `--prompt` | yes | — | Instructions for the agent |
|
|
66
|
+
| `-c` | `--concurrency` | no | `10` | Max parallel agents |
|
|
67
|
+
| `-m` | `--model` | no | — | Model name |
|
|
68
|
+
| `-k` | `--api-key` | no | — | Anthropic API key |
|
|
69
|
+
|
|
70
|
+
## SDK
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { Task } from 'atq';
|
|
74
|
+
|
|
75
|
+
const task = new Task({
|
|
76
|
+
prompt: 'Normalize this company name. Return just the name.',
|
|
77
|
+
concurrency: 10,
|
|
78
|
+
model: 'claude-sonnet-4-6',
|
|
79
|
+
items: [{ name: 'Google LLC' }, { name: 'APPLE INC.' }],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
for await (const { item, output, progress } of task.run()) {
|
|
83
|
+
console.log(`[${progress.completed}/${progress.total}] ${item.name} → ${output}`);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `new Task(options)`
|
|
88
|
+
|
|
89
|
+
| Option | Type | Default | Description |
|
|
90
|
+
|--------|------|---------|-------------|
|
|
91
|
+
| `prompt` | `string` | — | Instructions for the agent |
|
|
92
|
+
| `concurrency` | `number` | `10` | Max parallel agents |
|
|
93
|
+
| `items` | `array` | `[]` | Items to pre-load into the queue |
|
|
94
|
+
| `model` | `string` | — | Model name |
|
|
95
|
+
| `apiKey` | `string` | — | Anthropic API key |
|
|
96
|
+
|
|
97
|
+
### `.add(item)`
|
|
98
|
+
|
|
99
|
+
Add an item to the queue.
|
|
100
|
+
|
|
101
|
+
### `.run()`
|
|
102
|
+
|
|
103
|
+
Async generator. Each yield: `{ item, output, progress: { completed, total } }`
|
|
104
|
+
|
|
105
|
+
## Install
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
npm install atq
|
|
109
|
+
```
|
package/cli.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { parseArgs } from 'node:util';
|
|
5
|
+
import { Task } from './index.js';
|
|
6
|
+
|
|
7
|
+
const { values } = parseArgs({
|
|
8
|
+
options: {
|
|
9
|
+
prompt: { type: 'string', short: 'p' },
|
|
10
|
+
concurrency: { type: 'string', short: 'c' },
|
|
11
|
+
model: { type: 'string', short: 'm' },
|
|
12
|
+
'api-key': { type: 'string', short: 'k' },
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (!values.prompt) {
|
|
17
|
+
console.error('Usage: cat items.jsonl | atq -p "..." [-c 10] [-m model] [-k api-key]');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const lines = readFileSync('/dev/stdin', 'utf8').trim().split('\n');
|
|
22
|
+
const items = lines.map(line => JSON.parse(line));
|
|
23
|
+
|
|
24
|
+
const task = new Task({
|
|
25
|
+
prompt: values.prompt,
|
|
26
|
+
concurrency: values.concurrency ? parseInt(values.concurrency) : 10,
|
|
27
|
+
model: values.model,
|
|
28
|
+
apiKey: values['api-key'],
|
|
29
|
+
items,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
for await (const { item, output, progress } of task.run()) {
|
|
33
|
+
process.stderr.write(`[${progress.completed}/${progress.total}]\n`);
|
|
34
|
+
console.log(JSON.stringify({ item, output }));
|
|
35
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
|
|
3
|
+
export class Task {
|
|
4
|
+
constructor({ prompt, concurrency = 10, items = [], model, apiKey }) {
|
|
5
|
+
this.prompt = prompt;
|
|
6
|
+
this.concurrency = concurrency;
|
|
7
|
+
this.model = model;
|
|
8
|
+
this.apiKey = apiKey;
|
|
9
|
+
this.items = [...items];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
add(item) {
|
|
13
|
+
this.items.push(item);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async *run() {
|
|
17
|
+
const total = this.items.length;
|
|
18
|
+
if (total === 0) return;
|
|
19
|
+
|
|
20
|
+
const prompt = this.prompt || '';
|
|
21
|
+
|
|
22
|
+
let completed = 0;
|
|
23
|
+
let next = 0;
|
|
24
|
+
let running = 0;
|
|
25
|
+
|
|
26
|
+
const buffer = [];
|
|
27
|
+
let waiting = null;
|
|
28
|
+
|
|
29
|
+
const push = (result) => {
|
|
30
|
+
if (waiting) { const r = waiting; waiting = null; r(result); }
|
|
31
|
+
else buffer.push(result);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const pull = () => {
|
|
35
|
+
if (buffer.length > 0) return Promise.resolve(buffer.shift());
|
|
36
|
+
return new Promise(r => { waiting = r; });
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const exec = async (item) => {
|
|
40
|
+
let result = '';
|
|
41
|
+
const opts = { systemPrompt: prompt };
|
|
42
|
+
if (this.model) opts.model = this.model;
|
|
43
|
+
if (this.apiKey) opts.apiKey = this.apiKey;
|
|
44
|
+
for await (const msg of query(JSON.stringify(item), opts)) {
|
|
45
|
+
if (msg.type === 'text') result += msg.content;
|
|
46
|
+
}
|
|
47
|
+
return result.trim();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const fill = () => {
|
|
51
|
+
while (next < total && running < this.concurrency) {
|
|
52
|
+
const item = this.items[next++];
|
|
53
|
+
running++;
|
|
54
|
+
exec(item).then(raw => {
|
|
55
|
+
running--;
|
|
56
|
+
completed++;
|
|
57
|
+
push({ item, output: raw, progress: { completed, total } });
|
|
58
|
+
fill();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
fill();
|
|
64
|
+
|
|
65
|
+
while (completed < total || buffer.length > 0) {
|
|
66
|
+
yield await pull();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@endyai/atq",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A task queue for agentic workloads",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": "./index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"atq": "./cli.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.81"
|
|
13
|
+
}
|
|
14
|
+
}
|