@hotmeshio/hotmesh 0.4.3 → 0.5.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 +148 -7
- package/build/modules/errors.d.ts +1 -0
- package/build/modules/errors.js +1 -0
- package/build/package.json +2 -2
- package/build/services/activities/trigger.d.ts +1 -1
- package/build/services/activities/trigger.js +4 -1
- package/build/services/memflow/schemas/factory.d.ts +1 -1
- package/build/services/memflow/schemas/factory.js +27 -1
- package/build/services/memflow/worker.js +1 -0
- package/build/services/memflow/workflow/execChild.js +2 -1
- package/build/types/activity.d.ts +2 -0
- package/build/types/error.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -211,15 +211,156 @@ async function externalHookExample() {
|
|
|
211
211
|
|
|
212
212
|
## 🤖 Building Durable AI Agents
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
HotMesh's permanent memory enables a revolutionary approach to AI agents: **perspective-oriented intelligence**. Instead of monolithic agents, you build **multi-faceted agents** where:
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
* **Entity state** = the agent's living, evolving context
|
|
217
|
+
* **Hooks** = different perspectives that operate on that context
|
|
218
|
+
* **Child workflows** = specialized co-agents with their own entity types
|
|
219
|
+
* **Shared memory** = enables rich self-reflection and multi-agent collaboration
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
### Example: A Research Agent with Multiple Perspectives
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
/* ------------ Main Research Agent ------------ */
|
|
225
|
+
export async function researchAgent(query: string): Promise<any> {
|
|
226
|
+
const entity = await MemFlow.workflow.entity();
|
|
227
|
+
|
|
228
|
+
// Initialize agent context
|
|
229
|
+
await entity.set({
|
|
230
|
+
query,
|
|
231
|
+
findings: [],
|
|
232
|
+
perspectives: {},
|
|
233
|
+
confidence: 0,
|
|
234
|
+
status: 'researching'
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Spawn perspective hooks that operate on shared context
|
|
238
|
+
const optimisticView = MemFlow.workflow.execHook({
|
|
239
|
+
taskQueue: 'perspectives',
|
|
240
|
+
workflowName: 'optimisticPerspective',
|
|
241
|
+
args: [query]
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const skepticalView = MemFlow.workflow.execHook({
|
|
245
|
+
taskQueue: 'perspectives',
|
|
246
|
+
workflowName: 'skepticalPerspective',
|
|
247
|
+
args: [query]
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Spawn a fact-checker child agent with its own entity type
|
|
251
|
+
const factChecker = await MemFlow.workflow.execChild({
|
|
252
|
+
entity: 'fact-checker',
|
|
253
|
+
workflowName: 'factCheckAgent',
|
|
254
|
+
workflowId: `fact-check-${Date.now()}`,
|
|
255
|
+
args: [query],
|
|
256
|
+
taskQueue: 'agents'
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Wait for all perspectives to contribute
|
|
260
|
+
await Promise.all([optimisticView, skepticalView, factChecker]);
|
|
261
|
+
|
|
262
|
+
// Self-reflection: analyze conflicting perspectives
|
|
263
|
+
await MemFlow.workflow.execHook({
|
|
264
|
+
taskQueue: 'perspectives',
|
|
265
|
+
workflowName: 'synthesizePerspectives',
|
|
266
|
+
args: []
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const finalContext = await entity.get();
|
|
270
|
+
return finalContext;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* ------------ Optimistic Perspective Hook ------------ */
|
|
274
|
+
export async function optimisticPerspective(query: string): Promise<void> {
|
|
275
|
+
const entity = await MemFlow.workflow.entity();
|
|
276
|
+
|
|
277
|
+
// Optimistic perspective: look for supporting evidence
|
|
278
|
+
const findings = await searchForSupportingEvidence(query);
|
|
279
|
+
|
|
280
|
+
await entity.merge({
|
|
281
|
+
perspectives: {
|
|
282
|
+
optimistic: {
|
|
283
|
+
findings,
|
|
284
|
+
confidence: 0.8,
|
|
285
|
+
bias: 'Tends to emphasize positive evidence'
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* ------------ Skeptical Perspective Hook ------------ */
|
|
292
|
+
export async function skepticalPerspective(query: string): Promise<void> {
|
|
293
|
+
const entity = await MemFlow.workflow.entity();
|
|
294
|
+
|
|
295
|
+
// Skeptical perspective: challenge assumptions
|
|
296
|
+
const counterEvidence = await searchForCounterEvidence(query);
|
|
297
|
+
|
|
298
|
+
await entity.merge({
|
|
299
|
+
perspectives: {
|
|
300
|
+
skeptical: {
|
|
301
|
+
counterEvidence,
|
|
302
|
+
confidence: 0.6,
|
|
303
|
+
bias: 'Challenges assumptions and seeks contradictory evidence'
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* ------------ Fact-Checker Child Agent ------------ */
|
|
310
|
+
export async function factCheckAgent(query: string): Promise<any> {
|
|
311
|
+
// This child has its own entity type for specialized fact-checking context
|
|
312
|
+
const entity = await MemFlow.workflow.entity();
|
|
313
|
+
|
|
314
|
+
await entity.set({
|
|
315
|
+
query,
|
|
316
|
+
sources: [],
|
|
317
|
+
verifications: [],
|
|
318
|
+
credibilityScore: 0
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Fact-checker can spawn its own specialized hooks
|
|
322
|
+
await MemFlow.workflow.execHook({
|
|
323
|
+
taskQueue: 'verification',
|
|
324
|
+
workflowName: 'verifySourceCredibility',
|
|
325
|
+
args: [query]
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return await entity.get();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* ------------ Synthesis Perspective Hook ------------ */
|
|
332
|
+
export async function synthesizePerspectives(): Promise<void> {
|
|
333
|
+
const entity = await MemFlow.workflow.entity();
|
|
334
|
+
const context = await entity.get();
|
|
335
|
+
|
|
336
|
+
// Analyze conflicting perspectives and synthesize
|
|
337
|
+
const synthesis = await analyzePerspectives(context.perspectives);
|
|
338
|
+
|
|
339
|
+
await entity.merge({
|
|
340
|
+
perspectives: {
|
|
341
|
+
synthesis: {
|
|
342
|
+
finalAssessment: synthesis,
|
|
343
|
+
confidence: calculateConfidence(context.perspectives),
|
|
344
|
+
reasoning: 'Balanced analysis of optimistic and skeptical viewpoints'
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
status: 'completed'
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### The Power of Perspective-Oriented Agents
|
|
353
|
+
|
|
354
|
+
This approach enables agents that can:
|
|
355
|
+
|
|
356
|
+
* **Question themselves** – different hooks challenge each other's assumptions
|
|
357
|
+
* **Spawn specialized co-agents** – child workflows with their own entity types tackle specific domains
|
|
358
|
+
* **Maintain rich context** – all perspectives contribute to a shared, evolving knowledge base
|
|
359
|
+
* **Scale horizontally** – each perspective can run on different workers
|
|
360
|
+
* **Learn continuously** – entity state accumulates insights across all interactions
|
|
361
|
+
* **Self-reflect** – synthesis hooks analyze conflicting perspectives and improve decision-making
|
|
362
|
+
|
|
363
|
+
Because every perspective operates on the same durable entity, agents can pause, restart, and evolve their world-model indefinitely while maintaining coherent, multi-faceted intelligence.
|
|
223
364
|
|
|
224
365
|
---
|
|
225
366
|
|
package/build/modules/errors.js
CHANGED
|
@@ -56,6 +56,7 @@ class MemFlowChildError extends Error {
|
|
|
56
56
|
this.persistent = params.persistent;
|
|
57
57
|
this.signalIn = params.signalIn;
|
|
58
58
|
this.originJobId = params.originJobId;
|
|
59
|
+
this.entity = params.entity;
|
|
59
60
|
this.index = params.index;
|
|
60
61
|
this.workflowDimension = params.workflowDimension;
|
|
61
62
|
this.code = enums_1.HMSH_CODE_MEMFLOW_CHILD;
|
package/build/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Permanent-Memory Workflows & AI Agents",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
7
|
-
"homepage": "https://
|
|
7
|
+
"homepage": "https://github.com/hotmeshio/sdk-typescript/",
|
|
8
8
|
"publishConfig": {
|
|
9
9
|
"access": "public"
|
|
10
10
|
},
|
|
@@ -31,7 +31,7 @@ declare class Trigger extends Activity {
|
|
|
31
31
|
getJobStatus(): number;
|
|
32
32
|
resolveJobId(context: Partial<JobState>): string;
|
|
33
33
|
resolveJobKey(context: Partial<JobState>): string;
|
|
34
|
-
setStateNX(status?: number, entity?: string): Promise<void>;
|
|
34
|
+
setStateNX(status?: number, entity?: string | undefined): Promise<void>;
|
|
35
35
|
setStats(transaction?: ProviderTransaction): Promise<void>;
|
|
36
36
|
}
|
|
37
37
|
export { Trigger };
|
|
@@ -9,6 +9,7 @@ const reporter_1 = require("../reporter");
|
|
|
9
9
|
const serializer_1 = require("../serializer");
|
|
10
10
|
const telemetry_1 = require("../telemetry");
|
|
11
11
|
const activity_1 = require("./activity");
|
|
12
|
+
const mapper_1 = require("../mapper");
|
|
12
13
|
class Trigger extends activity_1.Activity {
|
|
13
14
|
constructor(config, data, metadata, hook, engine, context) {
|
|
14
15
|
super(config, data, metadata, hook, engine, context);
|
|
@@ -27,7 +28,9 @@ class Trigger extends activity_1.Activity {
|
|
|
27
28
|
this.mapJobData();
|
|
28
29
|
this.adjacencyList = await this.filterAdjacent();
|
|
29
30
|
const initialStatus = this.initStatus(options, this.adjacencyList.length);
|
|
30
|
-
|
|
31
|
+
//config.entity is a pipe expression; if 'entity' exists, it will resolve
|
|
32
|
+
const resolvedEntity = new mapper_1.MapperService({ entity: this.config.entity }, this.context).mapRules()?.entity;
|
|
33
|
+
await this.setStateNX(initialStatus, options?.entity || resolvedEntity);
|
|
31
34
|
await this.setStatus(initialStatus);
|
|
32
35
|
this.bindSearchData(options);
|
|
33
36
|
this.bindMarkerData(options);
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
22
|
exports.APP_ID = exports.APP_VERSION = exports.getWorkflowYAML = void 0;
|
|
23
|
-
const APP_VERSION = '
|
|
23
|
+
const APP_VERSION = '5';
|
|
24
24
|
exports.APP_VERSION = APP_VERSION;
|
|
25
25
|
const APP_ID = 'memflow';
|
|
26
26
|
exports.APP_ID = APP_ID;
|
|
@@ -86,6 +86,9 @@ const getWorkflowYAML = (app, version) => {
|
|
|
86
86
|
signalIn:
|
|
87
87
|
description: if false, the job will not support subordinated hooks
|
|
88
88
|
type: boolean
|
|
89
|
+
entity:
|
|
90
|
+
description: the entity type for this workflow instance
|
|
91
|
+
type: string
|
|
89
92
|
|
|
90
93
|
output:
|
|
91
94
|
schema:
|
|
@@ -120,6 +123,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
120
123
|
trigger:
|
|
121
124
|
title: Main Flow Trigger
|
|
122
125
|
type: trigger
|
|
126
|
+
entity: '{$self.input.data.entity}'
|
|
123
127
|
job:
|
|
124
128
|
maps:
|
|
125
129
|
done: false
|
|
@@ -249,6 +253,9 @@ const getWorkflowYAML = (app, version) => {
|
|
|
249
253
|
await:
|
|
250
254
|
type: string
|
|
251
255
|
description: when set to false, do not await the child flow's completion
|
|
256
|
+
entity:
|
|
257
|
+
type: string
|
|
258
|
+
description: the entity type for the child workflow
|
|
252
259
|
591:
|
|
253
260
|
schema:
|
|
254
261
|
type: object
|
|
@@ -367,6 +374,9 @@ const getWorkflowYAML = (app, version) => {
|
|
|
367
374
|
description: the arguments to pass to the activity
|
|
368
375
|
items:
|
|
369
376
|
type: string
|
|
377
|
+
entity:
|
|
378
|
+
type: string
|
|
379
|
+
description: the entity type for the child workflow
|
|
370
380
|
maps:
|
|
371
381
|
arguments: '{worker.output.data.arguments}'
|
|
372
382
|
workflowDimension: '{worker.output.data.workflowDimension}'
|
|
@@ -379,6 +389,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
379
389
|
workflowId: '{worker.output.data.workflowId}'
|
|
380
390
|
workflowName: '{worker.output.data.workflowName}'
|
|
381
391
|
workflowTopic: '{worker.output.data.workflowTopic}'
|
|
392
|
+
entity: '{worker.output.data.entity}'
|
|
382
393
|
backoffCoefficient:
|
|
383
394
|
'@pipe':
|
|
384
395
|
- ['{worker.output.data.backoffCoefficient}','{trigger.output.data.backoffCoefficient}']
|
|
@@ -992,6 +1003,9 @@ const getWorkflowYAML = (app, version) => {
|
|
|
992
1003
|
await:
|
|
993
1004
|
type: string
|
|
994
1005
|
description: when set to false, do not await the child flow's completion
|
|
1006
|
+
entity:
|
|
1007
|
+
type: string
|
|
1008
|
+
description: the entity type for the child workflow
|
|
995
1009
|
591:
|
|
996
1010
|
schema:
|
|
997
1011
|
type: object
|
|
@@ -1109,6 +1123,9 @@ const getWorkflowYAML = (app, version) => {
|
|
|
1109
1123
|
description: the arguments to pass to the activity
|
|
1110
1124
|
items:
|
|
1111
1125
|
type: string
|
|
1126
|
+
entity:
|
|
1127
|
+
type: string
|
|
1128
|
+
description: the entity type for the child workflow
|
|
1112
1129
|
maps:
|
|
1113
1130
|
arguments: '{signaler_worker.output.data.arguments}'
|
|
1114
1131
|
workflowDimension: '{signaler_worker.output.data.workflowDimension}'
|
|
@@ -1121,6 +1138,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
1121
1138
|
workflowId: '{signaler_worker.output.data.workflowId}'
|
|
1122
1139
|
workflowName: '{signaler_worker.output.data.workflowName}'
|
|
1123
1140
|
workflowTopic: '{signaler_worker.output.data.workflowTopic}'
|
|
1141
|
+
entity: '{signaler_worker.output.data.entity}'
|
|
1124
1142
|
backoffCoefficient:
|
|
1125
1143
|
'@pipe':
|
|
1126
1144
|
- ['{signaler_worker.output.data.backoffCoefficient}','{trigger.output.data.backoffCoefficient}']
|
|
@@ -1875,6 +1893,9 @@ const getWorkflowYAML = (app, version) => {
|
|
|
1875
1893
|
description: the arguments to pass to the activity
|
|
1876
1894
|
items:
|
|
1877
1895
|
type: string
|
|
1896
|
+
entity:
|
|
1897
|
+
type: string
|
|
1898
|
+
description: the entity type for the child workflow
|
|
1878
1899
|
maps:
|
|
1879
1900
|
arguments:
|
|
1880
1901
|
'@pipe':
|
|
@@ -1946,6 +1967,11 @@ const getWorkflowYAML = (app, version) => {
|
|
|
1946
1967
|
- ['{collator_trigger.output.data.items}', '{collator_cycle_hook.output.data.cur_index}']
|
|
1947
1968
|
- ['{@array.get}', maximumInterval]
|
|
1948
1969
|
- ['{@object.get}']
|
|
1970
|
+
entity:
|
|
1971
|
+
'@pipe':
|
|
1972
|
+
- ['{collator_trigger.output.data.items}', '{collator_cycle_hook.output.data.cur_index}']
|
|
1973
|
+
- ['{@array.get}', entity]
|
|
1974
|
+
- ['{@object.get}']
|
|
1949
1975
|
output:
|
|
1950
1976
|
schema:
|
|
1951
1977
|
type: object
|
|
@@ -431,6 +431,7 @@ class WorkerService {
|
|
|
431
431
|
maximumAttempts: err.maximumAttempts || enums_1.HMSH_MEMFLOW_MAX_ATTEMPTS,
|
|
432
432
|
maximumInterval: err.maximumInterval || (0, utils_1.s)(enums_1.HMSH_MEMFLOW_MAX_INTERVAL),
|
|
433
433
|
originJobId: err.originJobId,
|
|
434
|
+
entity: err.entity,
|
|
434
435
|
parentWorkflowId: err.parentWorkflowId,
|
|
435
436
|
expire: err.expire,
|
|
436
437
|
persistent: err.persistent,
|
|
@@ -22,7 +22,7 @@ function getChildInterruptPayload(context, options, execIndex) {
|
|
|
22
22
|
}
|
|
23
23
|
const parentWorkflowId = workflowId;
|
|
24
24
|
const taskQueueName = options.taskQueue ?? options.entity;
|
|
25
|
-
const workflowName = options.entity ?? options.workflowName;
|
|
25
|
+
const workflowName = options.taskQueue ? options.workflowName : (options.entity ?? options.workflowName);
|
|
26
26
|
const workflowTopic = `${taskQueueName}-${workflowName}`;
|
|
27
27
|
return {
|
|
28
28
|
arguments: [...(options.args || [])],
|
|
@@ -32,6 +32,7 @@ function getChildInterruptPayload(context, options, execIndex) {
|
|
|
32
32
|
maximumAttempts: options?.config?.maximumAttempts ?? common_1.HMSH_MEMFLOW_MAX_ATTEMPTS,
|
|
33
33
|
maximumInterval: (0, common_1.s)(options?.config?.maximumInterval ?? common_1.HMSH_MEMFLOW_MAX_INTERVAL),
|
|
34
34
|
originJobId: originJobId ?? workflowId,
|
|
35
|
+
entity: options.entity,
|
|
35
36
|
expire: options.expire ?? expire,
|
|
36
37
|
persistent: options.persistent,
|
|
37
38
|
signalIn: options.signalIn,
|
|
@@ -9,6 +9,7 @@ interface BaseActivity {
|
|
|
9
9
|
statusThreshold?: number;
|
|
10
10
|
statusThresholdType?: 'stop' | 'throw' | 'stall';
|
|
11
11
|
input?: Record<string, any>;
|
|
12
|
+
entity?: string;
|
|
12
13
|
output?: Record<string, any>;
|
|
13
14
|
settings?: Record<string, any>;
|
|
14
15
|
job?: Record<string, any>;
|
|
@@ -73,6 +74,7 @@ interface TriggerActivityStats {
|
|
|
73
74
|
interface TriggerActivity extends BaseActivity {
|
|
74
75
|
type: 'trigger';
|
|
75
76
|
stats?: TriggerActivityStats;
|
|
77
|
+
entity?: string;
|
|
76
78
|
}
|
|
77
79
|
interface AwaitActivity extends BaseActivity {
|
|
78
80
|
type: 'await';
|
package/build/types/error.d.ts
CHANGED