@pikku/skills 0.12.43 → 0.12.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.
@@ -73,6 +73,7 @@ import {
73
73
  MongoDBChannelStore,
74
74
  MongoDBWorkflowService,
75
75
  } from '@pikku/mongodb'
76
+ import { RedisLeaseService } from '@pikku/redis'
76
77
 
77
78
  const createSingletonServices = pikkuServices(async (config) => {
78
79
  const logger = new PinoLogger()
@@ -82,9 +83,23 @@ const createSingletonServices = pikkuServices(async (config) => {
82
83
  const channelStore = new MongoDBChannelStore(mongo.db)
83
84
  await channelStore.init()
84
85
 
85
- const workflowService = new MongoDBWorkflowService(mongo.db)
86
+ const leaseService = new RedisLeaseService(config.redisUrl)
87
+ const workflowService = new MongoDBWorkflowService(mongo.db, {
88
+ leaseService,
89
+ })
86
90
  await workflowService.init()
87
91
 
88
- return { config, logger, database: mongo, channelStore, workflowService }
92
+ return {
93
+ config,
94
+ logger,
95
+ database: mongo,
96
+ channelStore,
97
+ leaseService,
98
+ workflowService,
99
+ }
89
100
  })
90
101
  ```
102
+
103
+ `MongoDBWorkflowService` requires a `leaseService` to lock runs and steps, and
104
+ `@pikku/mongodb` ships none — pair it with Redis, a Kysely lease service, or
105
+ `InMemoryLeaseService` for a single process.
@@ -9,16 +9,17 @@ Redis-backed implementations of Pikku's core service interfaces, using
9
9
  connection — an ioredis `Redis` instance, `RedisOptions`, or a connection
10
10
  string — in its constructor. None of them need an `init()` call.
11
11
 
12
- | Service | Interface | Purpose |
13
- | --- | --- | --- |
14
- | `RedisChannelStore` | `ChannelStore` | WebSocket channel state persistence |
15
- | `RedisEventHubStore` | `EventHubStore` | Event hub state persistence |
16
- | `RedisWorkflowService` | `PikkuWorkflowService` | Workflow definition storage |
17
- | `RedisWorkflowRunService` | `WorkflowRunService` | Workflow execution tracking |
18
- | `RedisDeploymentService` | `DeploymentService` | Deployment state management |
19
- | `RedisAgentRunService` | `AgentRunService` | Agent execution tracking |
20
- | `RedisSecretService` | `SecretService` | Encrypted secret storage (envelope encryption) |
21
- | `RedisSessionStore` | `SessionStore` | Persisted user sessions |
12
+ | Service | Interface | Purpose |
13
+ | ------------------------- | ---------------------- | ---------------------------------------------- |
14
+ | `RedisChannelStore` | `ChannelStore` | WebSocket channel state persistence |
15
+ | `RedisEventHubStore` | `EventHubStore` | Event hub state persistence |
16
+ | `RedisWorkflowService` | `PikkuWorkflowService` | Workflow definition storage |
17
+ | `RedisWorkflowRunService` | `WorkflowRunService` | Workflow execution tracking |
18
+ | `RedisDeploymentService` | `DeploymentService` | Deployment state management |
19
+ | `RedisAgentRunService` | `AgentRunService` | Agent execution tracking |
20
+ | `RedisSecretService` | `SecretService` | Encrypted secret storage (envelope encryption) |
21
+ | `RedisSessionStore` | `SessionStore` | Persisted user sessions |
22
+ | `RedisLeaseService` | `LeaseService` | Named leases with fencing tokens |
22
23
 
23
24
  There is no Redis implementation of `AgentStorageService` — AI conversation
24
25
  storage is MongoDB-only.
@@ -51,11 +52,38 @@ await secrets.rotateKEK(): Promise<number>
51
52
  await secrets.close(): Promise<void>
52
53
  ```
53
54
 
55
+ ## `RedisLeaseService`
56
+
57
+ Each lease is a hash Redis expires itself, and every check-and-set is one Lua
58
+ script that reads the time with `TIME`, so a lease is judged — and its
59
+ `expiresAt` reported — on Redis's clock, never the worker's. A fast worker
60
+ clock can neither take a live lease nor stretch its own.
61
+
62
+ The token comes from a counter stored beside the lease that release never
63
+ deletes, so the next holder always gets a higher token. That counter is one
64
+ small key per lease name, kept forever. On a replicated Redis with async
65
+ failover a promoted replica can miss the latest `INCR`, so a token is only as
66
+ durable as the write it rode on.
67
+
68
+ ```typescript
69
+ import { RedisLeaseService } from '@pikku/redis'
70
+ import { holdLease } from '@pikku/core/services'
71
+
72
+ const leaseService = new RedisLeaseService(config.redisUrl, {
73
+ keyPrefix: 'pikku', // lease keys are `<prefix>:lease:{<key>}`
74
+ })
75
+
76
+ await holdLease(leaseService, 'nightly-report', async (lease, signal) => {
77
+ // ...
78
+ })
79
+ ```
80
+
54
81
  ## Full setup
55
82
 
56
83
  ```typescript
57
84
  import {
58
85
  RedisChannelStore,
86
+ RedisLeaseService,
59
87
  RedisWorkflowService,
60
88
  RedisSecretService,
61
89
  } from '@pikku/redis'
@@ -64,12 +92,22 @@ const createSingletonServices = pikkuServices(async (config) => {
64
92
  const logger = new PinoLogger()
65
93
 
66
94
  const channelStore = new RedisChannelStore(config.redisUrl)
67
- const workflowService = new RedisWorkflowService(config.redisUrl)
95
+ const leaseService = new RedisLeaseService(config.redisUrl)
96
+ const workflowService = new RedisWorkflowService(config.redisUrl, {
97
+ leaseService,
98
+ })
68
99
 
69
100
  const secrets = new RedisSecretService(config.redisUrl, {
70
101
  key: config.kekPassphrase,
71
102
  })
72
103
 
73
- return { config, logger, channelStore, workflowService, secrets }
104
+ return {
105
+ config,
106
+ logger,
107
+ channelStore,
108
+ leaseService,
109
+ workflowService,
110
+ secrets,
111
+ }
74
112
  })
75
113
  ```