@semiont/jobs 0.2.45 → 0.2.46

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 CHANGED
@@ -6,18 +6,13 @@
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@semiont/jobs.svg)](https://www.npmjs.com/package/@semiont/jobs)
7
7
  [![License](https://img.shields.io/npm/l/@semiont/jobs.svg)](https://github.com/The-AI-Alliance/semiont/blob/main/LICENSE)
8
8
 
9
- Filesystem-based job queue and worker infrastructure for [Semiont](https://github.com/The-AI-Alliance/semiont) - provides async job processing, background workers, and long-running task management.
9
+ Filesystem-based job queue, worker infrastructure, and annotation workers for [Semiont](https://github.com/The-AI-Alliance/semiont).
10
10
 
11
- ## What is a Job Queue?
11
+ ## Architecture Context
12
12
 
13
- A job queue is a pattern for processing work asynchronously outside of the HTTP request/response cycle. Jobs are persisted to storage, processed by workers, and can be monitored for progress and completion.
13
+ In production, the job queue and workers are created by `@semiont/make-meaning`'s `startMakeMeaning()` function. Workers emit commands on the **EventBus** the **Stower** actor (in @semiont/make-meaning) handles all persistence to the Knowledge Base.
14
14
 
15
- **Benefits:**
16
- - **Decoupled processing** - HTTP responses return immediately while work continues
17
- - **Reliability** - Jobs are persisted to disk and survive process restarts
18
- - **Progress tracking** - Long-running tasks can report status updates
19
- - **Retry logic** - Failed jobs can be retried with exponential backoff
20
- - **Scalability** - Multiple workers can process jobs concurrently
15
+ Workers are **not** actors. They use a polling loop, not RxJS subscriptions. But they emit the same EventBus commands as any other caller in the system.
21
16
 
22
17
  ## Installation
23
18
 
@@ -25,43 +20,33 @@ A job queue is a pattern for processing work asynchronously outside of the HTTP
25
20
  npm install @semiont/jobs
26
21
  ```
27
22
 
28
- **Prerequisites:**
29
- - Node.js >= 20.18.1
30
- - `@semiont/core` and `@semiont/api-client` (peer dependencies)
31
-
32
- ## Architecture Context
33
-
34
- **Infrastructure Ownership**: In production applications, the job queue is **created and managed by [@semiont/make-meaning](../make-meaning/)'s `startMakeMeaning()` function**, which serves as the single orchestration point for all infrastructure components (EventStore, GraphDB, RepStore, InferenceClient, JobQueue, Workers).
35
-
36
- The quick start example below shows direct initialization for **testing, CLI tools, or standalone workers**. For backend integration, access the job queue through the `makeMeaning` context object.
23
+ **Dependencies:**
24
+ - `@semiont/core` Core types, EventBus
25
+ - `@semiont/api-client` OpenAPI types
26
+ - `@semiont/inference` — InferenceClient for AI operations
37
27
 
38
28
  ## Quick Start
39
29
 
40
30
  ```typescript
41
- import {
42
- JobQueue,
43
- initializeJobQueue,
44
- getJobQueue,
45
- JobWorker,
46
- type PendingJob,
47
- type RunningJob,
48
- type GenerationParams,
49
- type AnyJob,
50
- } from '@semiont/jobs';
31
+ import { JobQueue, type PendingJob, type GenerationParams } from '@semiont/jobs';
32
+ import { EventBus, userId, resourceId, annotationId } from '@semiont/core';
51
33
  import { jobId } from '@semiont/api-client';
52
- import { userId, resourceId, annotationId } from '@semiont/core';
53
34
 
54
- // 1. Initialize job queue
55
- await initializeJobQueue({ dataDir: './data' });
35
+ // Initialize
36
+ const eventBus = new EventBus();
37
+ const jobQueue = new JobQueue({ dataDir: './data' }, logger, eventBus);
38
+ await jobQueue.initialize();
56
39
 
57
- // 2. Create a job
58
- const jobQueue = getJobQueue();
40
+ // Create a job
59
41
  const job: PendingJob<GenerationParams> = {
60
42
  status: 'pending',
61
43
  metadata: {
62
44
  id: jobId('job-abc123'),
63
45
  type: 'generation',
64
46
  userId: userId('user@example.com'),
47
+ userName: 'Jane Doe',
48
+ userEmail: 'jane@example.com',
49
+ userDomain: 'example.com',
65
50
  created: new Date().toISOString(),
66
51
  retryCount: 0,
67
52
  maxRetries: 3,
@@ -69,6 +54,8 @@ const job: PendingJob<GenerationParams> = {
69
54
  params: {
70
55
  referenceId: annotationId('ref-123'),
71
56
  sourceResourceId: resourceId('doc-456'),
57
+ sourceResourceName: 'Source Document',
58
+ annotation: { /* full W3C Annotation */ },
72
59
  title: 'Generated Article',
73
60
  prompt: 'Write about AI',
74
61
  language: 'en-US',
@@ -76,562 +63,120 @@ const job: PendingJob<GenerationParams> = {
76
63
  };
77
64
 
78
65
  await jobQueue.createJob(job);
79
-
80
- // 3. Create a worker to process jobs
81
- class MyGenerationWorker extends JobWorker {
82
- protected getWorkerName(): string {
83
- return 'MyGenerationWorker';
84
- }
85
-
86
- protected canProcessJob(job: AnyJob): boolean {
87
- return job.metadata.type === 'generation';
88
- }
89
-
90
- protected async executeJob(job: AnyJob): Promise<void> {
91
- // Type guard ensures job is running
92
- if (job.status !== 'running') {
93
- throw new Error('Job must be running');
94
- }
95
-
96
- const genJob = job as RunningJob<GenerationParams>;
97
- console.log(`Generating resource: ${genJob.params.title}`);
98
- // Your processing logic here
99
- }
100
- }
101
-
102
- // 4. Start worker
103
- const worker = new MyGenerationWorker();
104
- await worker.start();
105
- ```
106
-
107
- ## Architecture
108
-
109
- The jobs package follows a simple status-directory pattern:
110
-
111
- ```
112
- data/
113
- jobs/
114
- pending/ ← Jobs waiting to be processed
115
- job-123.json
116
- job-456.json
117
- running/ ← Jobs currently being processed
118
- job-789.json
119
- complete/ ← Successfully completed jobs
120
- job-111.json
121
- failed/ ← Failed jobs (with error info)
122
- job-222.json
123
- cancelled/ ← Cancelled jobs
124
- job-333.json
125
66
  ```
126
67
 
127
- **Key Components:**
128
-
129
- - **JobQueue** - Manages job lifecycle and persistence
130
- - **JobWorker** - Abstract base class for workers that process jobs
131
- - **Job Types** - Strongly-typed job definitions for different task types
132
-
133
- ## Core Concepts
134
-
135
- ### Jobs
136
-
137
- Jobs use discriminated unions based on their status, ensuring type safety and preventing invalid state access:
68
+ ## Job Types
138
69
 
139
70
  ```typescript
140
- import type { PendingJob, RunningJob, CompleteJob, GenerationParams, GenerationProgress, GenerationResult } from '@semiont/jobs';
141
-
142
- // Pending job - waiting to be processed
143
- const pendingJob: PendingJob<GenerationParams> = {
144
- status: 'pending',
145
- metadata: {
146
- id: jobId('job-123'),
147
- type: 'generation',
148
- userId: userId('user@example.com'),
149
- created: '2024-01-01T00:00:00Z',
150
- retryCount: 0,
151
- maxRetries: 3,
152
- },
153
- params: {
154
- referenceId: annotationId('ref-456'),
155
- sourceResourceId: resourceId('doc-789'),
156
- title: 'AI Generated Article',
157
- prompt: 'Write about quantum computing',
158
- language: 'en-US',
159
- },
160
- };
161
-
162
- // Running job - currently being processed
163
- const runningJob: RunningJob<GenerationParams, GenerationProgress> = {
164
- status: 'running',
165
- metadata: { /* same as above */ },
166
- params: { /* same as above */ },
167
- startedAt: '2024-01-01T00:01:00Z',
168
- progress: {
169
- stage: 'generating',
170
- percentage: 45,
171
- message: 'Generating content...',
172
- },
173
- };
174
-
175
- // Complete job - successfully finished
176
- const completeJob: CompleteJob<GenerationParams, GenerationResult> = {
177
- status: 'complete',
178
- metadata: { /* same as above */ },
179
- params: { /* same as above */ },
180
- startedAt: '2024-01-01T00:01:00Z',
181
- completedAt: '2024-01-01T00:05:00Z',
182
- result: {
183
- resourceId: resourceId('doc-new'),
184
- resourceName: 'Generated Article',
185
- },
186
- };
187
-
188
- // TypeScript prevents accessing progress on pending jobs!
189
- // pendingJob.progress // ❌ Compile error
190
- // runningJob.progress // ✅ Available
191
- // completeJob.result // ✅ Available
71
+ type JobType =
72
+ | 'reference-annotation' // Entity reference detection
73
+ | 'generation' // AI content generation
74
+ | 'highlight-annotation' // Key passage highlighting
75
+ | 'assessment-annotation' // Evaluative assessments
76
+ | 'comment-annotation' // Explanatory comments
77
+ | 'tag-annotation' // Structural role tagging
192
78
  ```
193
79
 
194
- ### Job Types
80
+ ## Job Metadata
195
81
 
196
- The package supports multiple job types for different tasks, each with their own parameter types:
82
+ All jobs share common metadata:
197
83
 
198
84
  ```typescript
199
- import type {
200
- DetectionParams, // Entity detection in resources
201
- GenerationParams, // AI content generation
202
- HighlightDetectionParams, // Identify key passages
203
- AssessmentDetectionParams, // Generate evaluative comments
204
- CommentDetectionParams, // Generate explanatory comments
205
- TagDetectionParams, // Structural role detection
206
- } from '@semiont/jobs';
207
- ```
208
-
209
- ### Job Status
210
-
211
- Jobs progress through status states stored as directories:
212
-
213
- ```typescript
214
- type JobStatus =
215
- | 'pending' // Waiting to be processed
216
- | 'running' // Currently being processed
217
- | 'complete' // Successfully finished
218
- | 'failed' // Failed with error
219
- | 'cancelled' // Cancelled by user
220
- ```
221
-
222
- ### Workers
223
-
224
- Workers poll the queue and process jobs:
225
-
226
- ```typescript
227
- import { JobWorker, type AnyJob, type RunningJob, type CustomParams } from '@semiont/jobs';
228
-
229
- class CustomWorker extends JobWorker {
230
- // Worker identification
231
- protected getWorkerName(): string {
232
- return 'CustomWorker';
233
- }
234
-
235
- // Filter which jobs this worker processes
236
- protected canProcessJob(job: AnyJob): boolean {
237
- return job.metadata.type === 'custom-type';
238
- }
239
-
240
- // Implement job processing logic
241
- protected async executeJob(job: AnyJob): Promise<void> {
242
- // 1. Type guard - job must be running
243
- if (job.status !== 'running') {
244
- throw new Error('Job must be running');
245
- }
246
-
247
- // 2. Access typed job data
248
- const customJob = job as RunningJob<CustomParams>;
249
- const params = customJob.params;
250
-
251
- // 3. Perform async work
252
- const result = await doWork(params);
253
-
254
- // 4. Create updated job with result (immutable pattern)
255
- const updatedJob: RunningJob<CustomParams> = {
256
- ...customJob,
257
- progress: { stage: 'complete', percentage: 100 },
258
- };
259
- await this.updateJobProgress(updatedJob);
260
- }
85
+ interface JobMetadata {
86
+ id: JobId;
87
+ type: JobType;
88
+ userId: UserId;
89
+ userName: string; // For building W3C Agent creator
90
+ userEmail: string; // For building W3C Agent creator
91
+ userDomain: string; // For building W3C Agent creator
92
+ created: string;
93
+ retryCount: number;
94
+ maxRetries: number;
261
95
  }
262
96
  ```
263
97
 
264
- ## Documentation
265
-
266
- 📚 **[Job Queue Guide](./docs/JobQueue.md)** - JobQueue API and job management
267
-
268
- 👷 **[Workers Guide](./docs/Workers.md)** - Building custom workers
269
-
270
- 📝 **[Job Types Guide](./docs/JobTypes.md)** - All job type definitions and usage
271
-
272
- 🔷 **[Type System Guide](./docs/TYPES.md)** - Discriminated unions and type safety
273
-
274
- ⚙️ **[Configuration Guide](./docs/Configuration.md)** - Setup and options
275
-
276
- ## Key Features
277
-
278
- - **Type-safe** - Full TypeScript support with discriminated union types
279
- - **Filesystem-based** - No external database required (JSON files for jobs)
280
- - **Status directories** - Jobs organized by status for easy polling
281
- - **Atomic operations** - Safe concurrent access to job files
282
- - **Progress tracking** - Jobs can report progress updates during processing
283
- - **Retry logic** - Built-in retry handling with configurable max attempts
284
- - **Framework-agnostic** - Pure TypeScript, no web framework dependencies
285
-
286
- ## Use Cases
287
-
288
- ✅ **AI generation** - Long-running LLM inference tasks
289
-
290
- ✅ **Background processing** - Resource analysis, entity detection
291
-
292
- ✅ **Worker microservices** - Separate processes for compute-intensive work
98
+ The `userName`, `userEmail`, and `userDomain` fields are used by workers to build the W3C `Agent` for annotation `creator` attribution via `userToAgent()`.
293
99
 
294
- **CLI tools** - Command-line tools that queue batch operations
100
+ ## Annotation Workers
295
101
 
296
- **Testing** - Isolated job queues for unit/integration tests
102
+ Six workers process different annotation types:
297
103
 
298
- **Not for frontend** - Backend infrastructure only (workers need filesystem access)
104
+ | Worker | Job Type | Constructor |
105
+ |--------|----------|------------|
106
+ | `ReferenceAnnotationWorker` | `reference-annotation` | `(jobQueue, config, inferenceClient, eventBus, contentFetcher, logger)` |
107
+ | `GenerationWorker` | `generation` | `(jobQueue, config, inferenceClient, eventBus, logger)` |
108
+ | `HighlightAnnotationWorker` | `highlight-annotation` | `(jobQueue, config, inferenceClient, eventBus, contentFetcher, logger)` |
109
+ | `AssessmentAnnotationWorker` | `assessment-annotation` | `(jobQueue, config, inferenceClient, eventBus, contentFetcher, logger)` |
110
+ | `CommentAnnotationWorker` | `comment-annotation` | `(jobQueue, config, inferenceClient, eventBus, contentFetcher, logger)` |
111
+ | `TagAnnotationWorker` | `tag-annotation` | `(jobQueue, config, inferenceClient, eventBus, contentFetcher, logger)` |
299
112
 
300
- ## API Overview
113
+ Workers emit EventBus commands (`mark:create`, `job:start`, `job:complete`, etc.) — the Stower actor in @semiont/make-meaning handles persistence.
301
114
 
302
- ### JobQueue
115
+ ## Custom Workers
303
116
 
304
117
  ```typescript
305
- const queue = getJobQueue();
118
+ import { JobWorker, type AnyJob } from '@semiont/jobs';
119
+ import type { Logger } from '@semiont/core';
306
120
 
307
- // Create job
308
- await queue.createJob(job);
309
-
310
- // Get job by ID
311
- const job = await queue.getJob(jobId);
312
-
313
- // Poll for next pending job
314
- const next = await queue.pollNextPendingJob();
315
-
316
- // Update job status
317
- job.status = 'complete';
318
- await queue.updateJob(job, 'running');
319
-
320
- // Query jobs by status
321
- const pending = await queue.queryJobs({ status: 'pending' });
322
- const failed = await queue.queryJobs({ status: 'failed' });
323
-
324
- // Cleanup old jobs
325
- await queue.cleanupCompletedJobs(Date.now() - 86400000); // 1 day ago
326
- ```
327
-
328
- ### JobWorker
329
-
330
- ```typescript
331
- // Create worker
332
121
  class MyWorker extends JobWorker {
333
- constructor() {
334
- super(
335
- 1000, // Poll interval (ms)
336
- 5000 // Error backoff (ms)
337
- );
122
+ constructor(jobQueue: JobQueue, logger: Logger) {
123
+ super(jobQueue, 1000, 5000, logger);
124
+ // ^^^^ ^^^^
125
+ // poll error backoff
338
126
  }
339
127
 
340
128
  protected getWorkerName(): string {
341
129
  return 'MyWorker';
342
130
  }
343
131
 
344
- protected canProcessJob(job: Job): boolean {
345
- return job.type === 'my-type';
346
- }
347
-
348
- protected async executeJob(job: Job): Promise<void> {
349
- // Process job
132
+ protected canProcessJob(job: AnyJob): boolean {
133
+ return job.metadata.type === 'generation';
350
134
  }
351
- }
352
-
353
- // Start worker
354
- const worker = new MyWorker();
355
- await worker.start();
356
-
357
- // Stop worker (graceful shutdown)
358
- await worker.stop();
359
- ```
360
-
361
- ### Singleton Pattern
362
-
363
- ```typescript
364
- import { initializeJobQueue, getJobQueue } from '@semiont/jobs';
365
-
366
- // Initialize once at startup
367
- await initializeJobQueue({ dataDir: './data' });
368
-
369
- // Get queue instance anywhere
370
- const queue = getJobQueue();
371
- ```
372
-
373
- ## Storage Format
374
-
375
- Jobs are stored as individual JSON files:
376
-
377
- ```
378
- data/
379
- jobs/
380
- pending/
381
- job-abc123.json
382
- running/
383
- job-def456.json
384
- complete/
385
- job-ghi789.json
386
- ```
387
135
 
388
- Each job file contains the complete job object using the discriminated union structure:
389
-
390
- ```json
391
- {
392
- "status": "complete",
393
- "metadata": {
394
- "id": "job-abc123",
395
- "type": "generation",
396
- "userId": "user@example.com",
397
- "created": "2024-01-01T00:00:00Z",
398
- "retryCount": 0,
399
- "maxRetries": 3
400
- },
401
- "params": {
402
- "referenceId": "ref-456",
403
- "sourceResourceId": "doc-789",
404
- "title": "Generated Article",
405
- "prompt": "Write about AI",
406
- "language": "en-US"
407
- },
408
- "startedAt": "2024-01-01T00:01:00Z",
409
- "completedAt": "2024-01-01T00:05:00Z",
410
- "result": {
411
- "resourceId": "doc-new",
412
- "resourceName": "Generated Article"
136
+ protected async executeJob(job: AnyJob): Promise<any> {
137
+ // Your processing logic — return result object
413
138
  }
414
139
  }
415
140
  ```
416
141
 
417
- ## Performance
418
-
419
- - **Polling-based** - Workers poll pending directory at configurable intervals
420
- - **Filesystem limits** - Performance degrades with >1000 pending jobs per directory
421
- - **Atomic moves** - Jobs move between status directories atomically (delete + write)
422
- - **No locks needed** - Status-based organization prevents race conditions
423
-
424
- **Scaling considerations:**
425
- - Multiple workers can run concurrently (same or different machines)
426
- - Workers use `pollNextPendingJob()` for FIFO processing
427
- - Completed jobs should be cleaned up periodically
428
- - For high throughput (>1000 jobs/min), consider Redis/database-backed queue
429
-
430
- ## Error Handling
142
+ ## Discriminated Unions
431
143
 
432
- ### Worker Error Recovery
144
+ Jobs use TypeScript discriminated unions for type safety:
433
145
 
434
146
  ```typescript
435
- class ResilientWorker extends JobWorker {
436
- protected async executeJob(job: AnyJob): Promise<void> {
437
- if (job.status !== 'running') {
438
- throw new Error('Job must be running');
439
- }
440
-
441
- try {
442
- await doWork(job);
443
- } catch (error) {
444
- // JobWorker base class handles:
445
- // 1. Moving job to 'failed' status
446
- // 2. Recording error message
447
- // 3. Retry logic (if retryCount < maxRetries)
448
- throw error; // Let base class handle it
449
- }
147
+ function handleJob(job: AnyJob) {
148
+ if (job.status === 'running') {
149
+ console.log(job.progress); // Available
150
+ // console.log(job.result); // Compile error
450
151
  }
451
- }
452
- ```
453
-
454
- ### Manual Retry
455
-
456
- ```typescript
457
- const queue = getJobQueue();
458
- const failedJobs = await queue.queryJobs({ status: 'failed' });
459
-
460
- for (const job of failedJobs) {
461
- if (job.status === 'failed' && job.metadata.retryCount < job.metadata.maxRetries) {
462
- // Create new pending job from failed job
463
- const retryJob: PendingJob<any> = {
464
- status: 'pending',
465
- metadata: {
466
- ...job.metadata,
467
- retryCount: job.metadata.retryCount + 1,
468
- },
469
- params: job.params,
470
- };
471
- await queue.updateJob(retryJob, 'failed');
152
+ if (job.status === 'complete') {
153
+ console.log(job.result); // Available
154
+ // console.log(job.progress); // Compile error
472
155
  }
473
156
  }
474
157
  ```
475
158
 
476
- ## Testing
477
-
478
- ```typescript
479
- import { initializeJobQueue, getJobQueue } from '@semiont/jobs';
480
- import type { PendingJob, GenerationParams } from '@semiont/jobs';
481
- import { describe, it, beforeEach } from 'vitest';
482
-
483
- describe('Job queue', () => {
484
- beforeEach(async () => {
485
- await initializeJobQueue({ dataDir: './test-data' });
486
- });
487
-
488
- it('should create and retrieve jobs', async () => {
489
- const queue = getJobQueue();
490
-
491
- const job: PendingJob<GenerationParams> = {
492
- status: 'pending',
493
- metadata: {
494
- id: jobId('test-1'),
495
- type: 'generation',
496
- userId: userId('user@test.com'),
497
- created: new Date().toISOString(),
498
- retryCount: 0,
499
- maxRetries: 3,
500
- },
501
- params: {
502
- referenceId: annotationId('ref-1'),
503
- sourceResourceId: resourceId('doc-1'),
504
- title: 'Test',
505
- prompt: 'Test prompt',
506
- language: 'en-US',
507
- },
508
- };
509
-
510
- await queue.createJob(job);
511
- const retrieved = await queue.getJob(jobId('test-1'));
512
-
513
- expect(retrieved).toEqual(job);
514
- });
515
- });
516
- ```
517
-
518
- ## Examples
519
-
520
- ### Building a Background Worker
521
-
522
- ```typescript
523
- import { JobWorker, type AnyJob, type RunningJob, type GenerationParams, type GenerationProgress } from '@semiont/jobs';
524
- import { InferenceService } from './inference';
525
-
526
- class GenerationWorker extends JobWorker {
527
- private inference: InferenceService;
528
-
529
- constructor(inference: InferenceService) {
530
- super(1000, 5000);
531
- this.inference = inference;
532
- }
159
+ ## Storage Format
533
160
 
534
- protected getWorkerName(): string {
535
- return 'GenerationWorker';
536
- }
161
+ Jobs are stored as individual JSON files organized by status:
537
162
 
538
- protected canProcessJob(job: AnyJob): boolean {
539
- return job.metadata.type === 'generation';
540
- }
541
-
542
- protected async executeJob(job: AnyJob): Promise<void> {
543
- // Type guard
544
- if (job.status !== 'running') {
545
- throw new Error('Job must be running');
546
- }
547
-
548
- const genJob = job as RunningJob<GenerationParams, GenerationProgress>;
549
-
550
- // Report progress (create new object - immutable pattern)
551
- const updatedJob1: RunningJob<GenerationParams, GenerationProgress> = {
552
- ...genJob,
553
- progress: {
554
- stage: 'generating',
555
- percentage: 0,
556
- message: 'Starting generation...',
557
- },
558
- };
559
- await getJobQueue().updateJob(updatedJob1);
560
-
561
- // Generate content
562
- const content = await this.inference.generate({
563
- prompt: genJob.params.prompt,
564
- context: genJob.params.context,
565
- temperature: genJob.params.temperature,
566
- maxTokens: genJob.params.maxTokens,
567
- });
568
-
569
- // Update progress
570
- const updatedJob2: RunningJob<GenerationParams, GenerationProgress> = {
571
- ...updatedJob1,
572
- progress: {
573
- stage: 'creating',
574
- percentage: 75,
575
- message: 'Creating resource...',
576
- },
577
- };
578
- await getJobQueue().updateJob(updatedJob2);
579
-
580
- // Create resource (simplified)
581
- const resourceId = await createResource(content, genJob.params.title);
582
-
583
- // Set result (will be handled by base class transition to complete)
584
- return {
585
- resourceId,
586
- resourceName: genJob.params.title,
587
- };
588
- }
589
- }
163
+ ```
164
+ data/jobs/
165
+ pending/job-abc123.json
166
+ running/job-def456.json
167
+ complete/job-ghi789.json
168
+ failed/job-jkl012.json
169
+ cancelled/job-mno345.json
590
170
  ```
591
171
 
592
- ### Progress Monitoring
593
-
594
- ```typescript
595
- import { getJobQueue } from '@semiont/jobs';
596
-
597
- async function monitorJob(jobId: JobId): Promise<void> {
598
- const queue = getJobQueue();
599
-
600
- while (true) {
601
- const job = await queue.getJob(jobId);
602
-
603
- if (!job) {
604
- console.log('Job not found');
605
- break;
606
- }
607
-
608
- console.log(`Status: ${job.status}`);
609
-
610
- // Type-safe progress access - only available on running jobs
611
- if (job.status === 'running') {
612
- console.log(`Progress: ${job.progress.percentage}%`);
613
- console.log(`Stage: ${job.progress.stage}`);
614
- console.log(`Message: ${job.progress.message || 'Processing...'}`);
615
- }
616
-
617
- // Type-safe result access - only available on complete jobs
618
- if (job.status === 'complete') {
619
- console.log(`Result: ${JSON.stringify(job.result)}`);
620
- }
621
-
622
- // Type-safe error access - only available on failed jobs
623
- if (job.status === 'failed') {
624
- console.log(`Error: ${job.error}`);
625
- }
172
+ ## Documentation
626
173
 
627
- if (job.status === 'complete' || job.status === 'failed') {
628
- break;
629
- }
630
-
631
- await new Promise(resolve => setTimeout(resolve, 1000));
632
- }
633
- }
634
- ```
174
+ - **[Job Queue Guide](./docs/JobQueue.md)** JobQueue API and job management
175
+ - **[Workers Guide](./docs/Workers.md)** — Building custom workers
176
+ - **[Job Types Guide](./docs/JobTypes.md)** — All job type definitions
177
+ - **[Type System Guide](./docs/TYPES.md)** — Discriminated unions and type safety
178
+ - **[Configuration Guide](./docs/Configuration.md)** Setup and options
179
+ - **[API Reference](./docs/API.md)** — Complete API reference
635
180
 
636
181
  ## License
637
182
 
@@ -639,13 +184,7 @@ Apache-2.0
639
184
 
640
185
  ## Related Packages
641
186
 
642
- - [`@semiont/api-client`](../api-client/) - API types and utilities
643
- - [`@semiont/core`](../core/) - Domain types and utilities
644
- - [`@semiont/event-sourcing`](../event-sourcing/) - Event persistence
645
- - [`semiont-backend`](../../apps/backend/) - Backend API server
646
-
647
- ## Learn More
648
-
649
- - [Background Jobs Pattern](https://www.enterpriseintegrationpatterns.com/patterns/messaging/MessageQueueing.html) - Queue-based processing
650
- - [Job Types Guide](./docs/JobTypes.md) - Detailed job type documentation
651
- - [Workers Guide](./docs/Workers.md) - Building custom workers
187
+ - [`@semiont/core`](../core/) Domain types, EventBus
188
+ - [`@semiont/api-client`](../api-client/) OpenAPI types
189
+ - [`@semiont/inference`](../inference/) AI inference client
190
+ - [`@semiont/make-meaning`](../make-meaning/) Actor model, Knowledge Base, service orchestration