@semiont/jobs 0.6.7 → 0.6.8
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 +27 -64
- package/dist/index.d.ts +215 -522
- package/dist/index.js +9281 -974
- package/dist/index.js.map +1 -1
- package/dist/worker-main.js +4 -21
- package/dist/worker-main.js.map +1 -1
- package/package.json +9 -10
package/README.md
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/@semiont/jobs)
|
|
7
7
|
[](https://github.com/The-AI-Alliance/semiont/blob/main/LICENSE)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
The job worker for [Semiont](https://github.com/The-AI-Alliance/semiont): the processors for each job type, the job types, and the worker process that claims jobs over the bus. The job queue is the dispatcher's ([apps/dispatcher](../../apps/dispatcher/README.md)), and what it does with a job is [docs/protocol/JOBS.md](../../docs/protocol/JOBS.md).
|
|
10
10
|
|
|
11
11
|
## Architecture Context
|
|
12
12
|
|
|
13
|
-
Workers run in a separate process and connect to the Knowledge System (KS) over HTTP/SSE using a `SemiontSession` (from `@semiont/sdk`) driven by a `JobClaimAdapter`.
|
|
13
|
+
Workers run in a separate process and connect to the Knowledge System (KS) over HTTP/SSE using a `SemiontSession` (from `@semiont/sdk`) driven by a `JobClaimAdapter`. Whenever a worker is idle it claims the next pending job of its types with `job:claim` (a `job:queued` announcement wakes a parked worker), and it emits domain events back to the KS via `session.client.transport.emit(...)`. The KS ingests these events onto its EventBus for SSE delivery to the Browser.
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
@@ -19,8 +19,8 @@ npm install @semiont/jobs
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
**Dependencies:**
|
|
22
|
-
- `@semiont/core` — Core types, `SemiontProject`, EventBus
|
|
23
|
-
- `@semiont/sdk` — `SemiontSession`, `
|
|
22
|
+
- `@semiont/core` — Core types, `SemiontProject`, EventBus, `BusRequestPrimitive`
|
|
23
|
+
- `@semiont/sdk` — `SemiontSession`, `SemiontClient` (worker process)
|
|
24
24
|
- `@semiont/http-transport` — HTTP transport, OpenAPI types
|
|
25
25
|
- `@semiont/inference` — InferenceClient for AI operations
|
|
26
26
|
- `@semiont/content` — Content storage URI derivation
|
|
@@ -29,44 +29,10 @@ npm install @semiont/jobs
|
|
|
29
29
|
|
|
30
30
|
## Quick Start
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// Initialize — jobs are stored under project.jobsDir
|
|
38
|
-
const eventBus = new EventBus();
|
|
39
|
-
const project = new SemiontProject('/path/to/project', {
|
|
40
|
-
anchoredTextDir: process.env.SEMIONT_ANCHORED_TEXT_DIR!,
|
|
41
|
-
});
|
|
42
|
-
const jobQueue = new FsJobQueue(project, logger, eventBus);
|
|
43
|
-
await jobQueue.initialize();
|
|
44
|
-
|
|
45
|
-
// Create a job
|
|
46
|
-
const job: PendingJob<DetectionParams> = {
|
|
47
|
-
status: 'pending',
|
|
48
|
-
metadata: {
|
|
49
|
-
id: jobId('job-abc123'),
|
|
50
|
-
type: 'reference-annotation',
|
|
51
|
-
userId: userId('did:web:example.com:users:f47ac10b-58cc-4372-a567-0e02b2c3d479'),
|
|
52
|
-
created: new Date().toISOString(),
|
|
53
|
-
retryCount: 0,
|
|
54
|
-
maxRetries: 1,
|
|
55
|
-
},
|
|
56
|
-
params: {
|
|
57
|
-
resourceId: resourceId('doc-456'),
|
|
58
|
-
entityTypes: ['Person', 'Organization'],
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
await jobQueue.createJob(job);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Generation jobs are enqueued the same way, but their params are a
|
|
66
|
-
[`GenerationJobParams`](./docs/JobTypes.md#generation-generation) bag whose
|
|
67
|
-
`context` carries the anchor — writing one straight to the queue bypasses the
|
|
68
|
-
dispatcher that normally derives and stamps `resourceId` from
|
|
69
|
-
`context.focus`, so prefer `client.yield.fromContext(...)`.
|
|
32
|
+
A job is created on the bus (`job:create`), answered by the dispatcher, and claimed by a worker. A
|
|
33
|
+
client starts one through the SDK — `semiont.mark.assist(...)` for an annotation pass,
|
|
34
|
+
`semiont.yield.fromContext(...)` for generation — and follows it with `semiont.job`. A worker is this
|
|
35
|
+
package's `worker-main`, run as the `semiont-worker` image.
|
|
70
36
|
|
|
71
37
|
## Job Types
|
|
72
38
|
|
|
@@ -92,12 +58,17 @@ interface JobMetadata {
|
|
|
92
58
|
created: string;
|
|
93
59
|
retryCount: number;
|
|
94
60
|
maxRetries: number;
|
|
95
|
-
completedUnits?: string[]; // Checkpoint: work units
|
|
96
|
-
//
|
|
61
|
+
completedUnits?: string[]; // Checkpoint: work units already persisted —
|
|
62
|
+
// the retry skips them
|
|
63
|
+
unitCursors?: Record<string, UnitCursor>; // How far each unfinished unit got
|
|
97
64
|
}
|
|
98
65
|
```
|
|
99
66
|
|
|
100
|
-
`userId` is the job's only identity — the DID the gateway verified on the `job:create`. The dispatcher records it as the requester when it accepts a claim, and that record is what lets the knowledge base attribute a write citing this job; a worker never states it. `completedUnits`
|
|
67
|
+
`userId` is the job's only identity — the DID the gateway verified on the `job:create`. The dispatcher records it as the requester when it accepts a claim, and that record is what lets the knowledge base attribute a write citing this job; a worker never states it. `completedUnits` and `unitCursors` are written by `checkpointUnits` (from `job:checkpoint`, as work lands) and by `failJob` (from `job:fail`), merged across attempts — see Failure discipline below.
|
|
68
|
+
|
|
69
|
+
## Progress
|
|
70
|
+
|
|
71
|
+
A running job's `progress` is `StoredProgress`: the last `JobProgress` its worker reported with `job:report-progress`, or `{}` before the first report. It is one shape for every job type — the spec's `JobRunning.progress` — so there are no per-type progress types. See [Job Types Guide](./docs/JobTypes.md#progress).
|
|
101
72
|
|
|
102
73
|
## Annotation Workers
|
|
103
74
|
|
|
@@ -112,9 +83,9 @@ The worker process (`worker-main.ts` → `startWorkerProcess` in `worker-process
|
|
|
112
83
|
| `comment-annotation` | `processCommentJob` |
|
|
113
84
|
| `tag-annotation` | `processTagJob` |
|
|
114
85
|
|
|
115
|
-
Detection logic lives in the `AnnotationDetection` class (`src/workers/annotation-detection.ts`); generation synthesis in `generateResourceFromTopic()` (`src/workers/generation/resource-generation.ts`). Processors never fetch content themselves — the worker process
|
|
86
|
+
Detection logic lives in the `AnnotationDetection` class (`src/workers/annotation-detection.ts`); generation synthesis in `generateResourceFromTopic()` (`src/workers/generation/resource-generation.ts`). Processors never fetch content themselves — the worker process prepares it with `prepareDetection` (bytes through `contentReads` for text media, the Smelter's anchored text for geometry-bearing media such as PDF) and passes it in.
|
|
116
87
|
|
|
117
|
-
Workers emit lifecycle events via `session.client.transport.emit('job:start' | 'job:report-progress' | 'job:checkpoint' | 'job:complete' | 'job:fail', payload)` and persist annotations through the **awaited `mark:commit` operation** — a batch per unit of work that resolves only once the Stower actor in @semiont/make-meaning has appended every annotation to the event log. Unit completion and `job:complete` gate on that acknowledgement, never on emission, so a down persistence sink is a retryable failure instead of silent loss. The job command handlers mirror the lifecycle events into the queue
|
|
88
|
+
Workers emit lifecycle events via `session.client.transport.emit('job:start' | 'job:report-progress' | 'job:checkpoint' | 'job:complete' | 'job:fail', payload)` and persist annotations through the **awaited `mark:commit` operation** — a batch per unit of work that resolves only once the Stower actor in @semiont/make-meaning has appended every annotation to the event log. Unit completion and `job:complete` gate on that acknowledgement, never on emission, so a down persistence sink is a retryable failure instead of silent loss. The dispatcher's job command handlers mirror the lifecycle events into the queue (completion, checkpoints, retry-on-failure with `maxRetries`, progress-as-heartbeat). `job:fail` carries the fields the worker computes: `completedUnits` and `unitCursors` (the checkpoint), `failureClass`, and `willRetry` — see Failure discipline.
|
|
118
89
|
|
|
119
90
|
## Failure discipline
|
|
120
91
|
|
|
@@ -126,13 +97,13 @@ Long inference work fails in bounded, classified, resumable ways — every piece
|
|
|
126
97
|
- **Successful-looking extractions are verified** (`assertYieldNotCollapsed`). A local model can return a clean, schema-conforming response carrying a fraction of the entities present — deterministic and otherwise invisible. When the provider declares `verifyDetectionYield` (all real providers do), each chunk's item count is checked against a cheap parallel count call; an extraction under half the count is flagged and subdivided. Every anchoring outcome and every call — including flagged and failed ones — is recorded to `semiont.detection.*` metrics (`@semiont/observability`).
|
|
127
98
|
- **Entity types run concurrently up to the provider's declared capacity** (`client.maxConcurrency`): a hosted API with rate headroom runs several types at once; a local single-model server runs them sequentially, because concurrent requests only split one GPU. Jobs never switches on provider identity — both behaviors are capabilities declared on the `InferenceClient`.
|
|
128
99
|
- **Failures are classified at the worker, where errors are still typed** (`failure-class.ts`). Only KNOWN-deterministic failures — truncation at the subdivision floor, unsupported media, non-throttle 4xx — skip the retry budget; everything unrecognized stays retryable. The class rides `job:fail` as `failureClass`.
|
|
129
|
-
- **Retries resume from the checkpoint.** Reference-annotation persists each entity type's annotations as that unit completes; completed units ride `job:fail` into `metadata.completedUnits`, and the retry processes only what's left.
|
|
100
|
+
- **Retries resume from the checkpoint.** Reference-annotation persists each entity type's annotations as that unit completes; completed units ride `job:checkpoint` and `job:fail` into `metadata.completedUnits`, and the retry processes only what's left.
|
|
130
101
|
|
|
131
102
|
## Adding a Job Type
|
|
132
103
|
|
|
133
104
|
Workers are not subclassed. To add a job type:
|
|
134
105
|
|
|
135
|
-
1. Add the new `JobType` and its params
|
|
106
|
+
1. Add the new `JobType` and its params type in `src/types.ts`, and in the spec: the type in `JobType.json`, its category in `specs/src/jobs/storage.json`, and its result schema in the `JobResult` union. There is no progress type — every job reports `JobProgress`.
|
|
136
107
|
2. Add a `process*Job` function in `src/processors.ts` that runs the inference and returns the annotations/result.
|
|
137
108
|
3. Dispatch the new `jobType` to that processor in `handleJobInner()` in `src/worker-process.ts`.
|
|
138
109
|
|
|
@@ -145,7 +116,7 @@ Jobs use TypeScript discriminated unions for type safety:
|
|
|
145
116
|
```typescript
|
|
146
117
|
function handleJob(job: AnyJob) {
|
|
147
118
|
if (job.status === 'running') {
|
|
148
|
-
console.log(job.progress); // Available
|
|
119
|
+
console.log(job.progress); // Available — StoredProgress, `{}` until the first report
|
|
149
120
|
// console.log(job.result); // Compile error
|
|
150
121
|
}
|
|
151
122
|
if (job.status === 'complete') {
|
|
@@ -157,24 +128,16 @@ function handleJob(job: AnyJob) {
|
|
|
157
128
|
|
|
158
129
|
## Storage Format
|
|
159
130
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
{project.jobsDir}/
|
|
164
|
-
pending/job-abc123.json
|
|
165
|
-
running/job-def456.json
|
|
166
|
-
complete/job-ghi789.json
|
|
167
|
-
failed/job-jkl012.json
|
|
168
|
-
cancelled/job-mno345.json
|
|
169
|
-
```
|
|
131
|
+
The dispatcher keeps one `JobRecord` per job in a JetStream key-value bucket, in the layout
|
|
132
|
+
`specs/src/jobs/storage.json` states. A worker never reads it: it sees a job only as the dispatcher
|
|
133
|
+
hands it over on `job:claimed`.
|
|
170
134
|
|
|
171
135
|
## Documentation
|
|
172
136
|
|
|
173
|
-
- **[Job Queue Guide](./docs/JobQueue.md)** — JobQueue API and job management
|
|
174
137
|
- **[Workers Guide](./docs/Workers.md)** — Building custom workers
|
|
175
138
|
- **[Job Types Guide](./docs/JobTypes.md)** — All job type definitions
|
|
176
139
|
- **[Type System Guide](./docs/TYPES.md)** — Discriminated unions and type safety
|
|
177
|
-
- **[Configuration Guide](./docs/Configuration.md)** —
|
|
140
|
+
- **[Configuration Guide](./docs/Configuration.md)** — Running a worker
|
|
178
141
|
- **[API Reference](./docs/API.md)** — Complete API reference
|
|
179
142
|
|
|
180
143
|
## License
|
|
@@ -183,8 +146,8 @@ Apache-2.0
|
|
|
183
146
|
|
|
184
147
|
## Related Packages
|
|
185
148
|
|
|
186
|
-
- [`@semiont/core`](../core/) — Domain types, `SemiontProject`, EventBus
|
|
187
|
-
- [`@semiont/sdk`](../sdk/) — `SemiontSession`, `
|
|
149
|
+
- [`@semiont/core`](../core/) — Domain types, `SemiontProject`, EventBus, `BusRequestPrimitive`
|
|
150
|
+
- [`@semiont/sdk`](../sdk/) — `SemiontSession`, `SemiontClient`
|
|
188
151
|
- [`@semiont/http-transport`](../http-transport/) — HTTP transport, OpenAPI types
|
|
189
152
|
- [`@semiont/inference`](../inference/) — AI inference client
|
|
190
153
|
- [`@semiont/make-meaning`](../make-meaning/) — Actor model, Knowledge Base, service orchestration
|