@reactive-skills/runtime 0.4.1 โ 0.4.2
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/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/dist/core/fsm-engine.d.ts +12 -3
- package/dist/core/fsm-engine.js +60 -6
- package/dist/core/types.d.ts +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
|
|
2
|
+
## [0.4.2] - 2026-09-14
|
|
3
|
+
|
|
4
|
+
- feat(runtime): add in-engine telemetry, template caching, and performance budget verification (7dfae37)
|
|
5
|
+
- feat(telemetry): record real-time transition_duration_ms, slice_duration_ms, and slice_tokens_est in STATE_TRANSITION events
|
|
6
|
+
- feat(fsm): L1 Handlebars compiled template delegate cache for sub-millisecond prompt slicing
|
|
7
|
+
- feat(alarms): emit PERF_DEGRADATION event on slice (>10ms) or transition (>25ms) threshold breaches
|
|
8
|
+
- test(perf): add automated Vitest test suite enforcing P0 (<5ms) and P1 (<25ms) latency budgets
|
|
9
|
+
|
|
2
10
|
## [0.4.1] - 2026-09-14
|
|
3
11
|
|
|
4
12
|
- feat(runtime,axi): add mcp job tools, template jobId context, and flexible jobs cli arguments (2b31451)
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Reactive Skills Architecture (RSA) core runtime โ FSM engine, event store, guard evaluator, projection engine, job manager, and MCP server.
|
|
4
4
|
|
|
5
|
-
> ๐ **What's New in v0.4.
|
|
5
|
+
> ๐ **What's New in v0.4.2:** In-engine telemetry, sub-millisecond Handlebars template caching, automated performance budget tests, and degradation detection alarms. [Read Full Release Notes โ](https://github.com/Reactive-Skills/reactive-skills/releases/tag/v0.4.2) ยท [View Changelog](https://github.com/Reactive-Skills/reactive-skills/blob/main/CHANGELOG.md)
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { SkillManifest, StateDefinition, SignalEvent, PromptSlice, EventContext, ChildRunSummary, DecisionRecord } from './types.js';
|
|
1
|
+
import { SkillManifest, StateDefinition, SignalEvent, PromptSlice, ExecutionMetrics, EventContext, ChildRunSummary, DecisionRecord } from './types.js';
|
|
2
2
|
import { EventStore } from './event-store.js';
|
|
3
|
-
import { JobManager } from './job-manager.js';
|
|
4
3
|
export interface FSMEngineOptions {
|
|
5
4
|
skillDir: string;
|
|
6
5
|
workspaceDir?: string;
|
|
@@ -10,6 +9,10 @@ export interface FSMEngineOptions {
|
|
|
10
9
|
runId?: string;
|
|
11
10
|
initialContext?: Record<string, any>;
|
|
12
11
|
autoRehydrate?: boolean;
|
|
12
|
+
perfThresholds?: {
|
|
13
|
+
maxSliceDurationMs?: number;
|
|
14
|
+
maxTransitionDurationMs?: number;
|
|
15
|
+
};
|
|
13
16
|
}
|
|
14
17
|
export declare class FSMEngine {
|
|
15
18
|
private skillDir;
|
|
@@ -28,6 +31,10 @@ export declare class FSMEngine {
|
|
|
28
31
|
private jobId?;
|
|
29
32
|
private isActiveJob;
|
|
30
33
|
private jobManager;
|
|
34
|
+
private templateCache;
|
|
35
|
+
private lastSliceMetrics?;
|
|
36
|
+
private maxSliceDurationMs;
|
|
37
|
+
private maxTransitionDurationMs;
|
|
31
38
|
constructor(options: FSMEngineOptions);
|
|
32
39
|
/**
|
|
33
40
|
* Rehydrate state machine state and context from immutable event history,
|
|
@@ -86,6 +93,7 @@ export declare class FSMEngine {
|
|
|
86
93
|
event: SignalEvent;
|
|
87
94
|
handledAtDepth?: number;
|
|
88
95
|
deliverablesWritten: string[];
|
|
96
|
+
metrics?: ExecutionMetrics;
|
|
89
97
|
}>;
|
|
90
98
|
/**
|
|
91
99
|
* Transition between two hierarchical paths executing exit, transition, and entry hooks
|
|
@@ -99,6 +107,7 @@ export declare class FSMEngine {
|
|
|
99
107
|
private executeExitHook;
|
|
100
108
|
getJobId(): string | undefined;
|
|
101
109
|
isJobActive(): boolean;
|
|
102
|
-
|
|
110
|
+
getLastMetrics(): ExecutionMetrics | undefined;
|
|
111
|
+
clearTemplateCache(): void;
|
|
103
112
|
close(): void;
|
|
104
113
|
}
|
package/dist/core/fsm-engine.js
CHANGED
|
@@ -28,6 +28,10 @@ export class FSMEngine {
|
|
|
28
28
|
jobId;
|
|
29
29
|
isActiveJob;
|
|
30
30
|
jobManager;
|
|
31
|
+
templateCache = new Map();
|
|
32
|
+
lastSliceMetrics;
|
|
33
|
+
maxSliceDurationMs = 10;
|
|
34
|
+
maxTransitionDurationMs = 25;
|
|
31
35
|
constructor(options) {
|
|
32
36
|
this.skillDir = path.resolve(options.skillDir);
|
|
33
37
|
this.workspaceDir = options.workspaceDir || process.cwd();
|
|
@@ -35,6 +39,12 @@ export class FSMEngine {
|
|
|
35
39
|
this.strictExecution = this.manifest.strict_execution === true;
|
|
36
40
|
this.turnsSinceLastSignal = 0;
|
|
37
41
|
this.inBypassState = false;
|
|
42
|
+
if (options.perfThresholds?.maxSliceDurationMs !== undefined) {
|
|
43
|
+
this.maxSliceDurationMs = options.perfThresholds.maxSliceDurationMs;
|
|
44
|
+
}
|
|
45
|
+
if (options.perfThresholds?.maxTransitionDurationMs !== undefined) {
|
|
46
|
+
this.maxTransitionDurationMs = options.perfThresholds.maxTransitionDurationMs;
|
|
47
|
+
}
|
|
38
48
|
const effectiveJobId = options.jobId || options.runId;
|
|
39
49
|
this.jobManager = new JobManager(this.workspaceDir);
|
|
40
50
|
const activeJobId = this.jobManager.getActiveJobId(this.manifest.name);
|
|
@@ -357,6 +367,7 @@ export class FSMEngine {
|
|
|
357
367
|
* Generates the prompt slice for the active state hierarchy
|
|
358
368
|
*/
|
|
359
369
|
generatePromptSlice() {
|
|
370
|
+
const startTime = performance.now();
|
|
360
371
|
const activeLeaf = this.getStateDefinition(this.activeStatePath);
|
|
361
372
|
if (!activeLeaf) {
|
|
362
373
|
throw new Error(`Active state definition not found: ${this.getCurrentState()}`);
|
|
@@ -392,8 +403,12 @@ export class FSMEngine {
|
|
|
392
403
|
}
|
|
393
404
|
let rawPrompt = '';
|
|
394
405
|
if (templatePath && fs.existsSync(templatePath)) {
|
|
395
|
-
|
|
396
|
-
|
|
406
|
+
let compiled = this.templateCache.get(templatePath);
|
|
407
|
+
if (!compiled) {
|
|
408
|
+
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
409
|
+
compiled = Handlebars.compile(templateContent);
|
|
410
|
+
this.templateCache.set(templatePath, compiled);
|
|
411
|
+
}
|
|
397
412
|
rawPrompt = compiled({
|
|
398
413
|
state: this.getCurrentState(),
|
|
399
414
|
activeStatePath: this.activeStatePath,
|
|
@@ -458,6 +473,22 @@ export class FSMEngine {
|
|
|
458
473
|
` </transition_contracts>`,
|
|
459
474
|
`</reactive_skill_state>`,
|
|
460
475
|
].filter(Boolean).join('\n');
|
|
476
|
+
const durationMs = Number((performance.now() - startTime).toFixed(3));
|
|
477
|
+
const estTokens = Math.ceil(formattedXml.length / 4);
|
|
478
|
+
const metrics = {
|
|
479
|
+
slice_duration_ms: durationMs,
|
|
480
|
+
slice_tokens_est: estTokens,
|
|
481
|
+
allowed_tools_count: allowedTools.length,
|
|
482
|
+
};
|
|
483
|
+
this.lastSliceMetrics = metrics;
|
|
484
|
+
if (durationMs > this.maxSliceDurationMs) {
|
|
485
|
+
this.eventStore.append('PERF_DEGRADATION', {
|
|
486
|
+
operation: 'generatePromptSlice',
|
|
487
|
+
duration_ms: durationMs,
|
|
488
|
+
threshold_ms: this.maxSliceDurationMs,
|
|
489
|
+
state: this.getCurrentState(),
|
|
490
|
+
}, { state: this.getCurrentState() });
|
|
491
|
+
}
|
|
461
492
|
return {
|
|
462
493
|
state: this.getCurrentState(),
|
|
463
494
|
rawPrompt,
|
|
@@ -465,6 +496,7 @@ export class FSMEngine {
|
|
|
465
496
|
allowedTools,
|
|
466
497
|
context: { ...this.context },
|
|
467
498
|
exitConditions,
|
|
499
|
+
metrics,
|
|
468
500
|
};
|
|
469
501
|
}
|
|
470
502
|
/**
|
|
@@ -478,6 +510,7 @@ export class FSMEngine {
|
|
|
478
510
|
* Process an incoming signal event, evaluating transitions with HSM bubbling and draining queued signals
|
|
479
511
|
*/
|
|
480
512
|
async handleSignal(signalName, payload = {}, metadata = {}) {
|
|
513
|
+
const startTime = performance.now();
|
|
481
514
|
const previousState = this.getCurrentState();
|
|
482
515
|
const prevPath = [...this.activeStatePath];
|
|
483
516
|
const event = this.eventStore.append('SIGNAL_EMITTED', { signal: signalName, ...payload }, { source: metadata.source, causationId: metadata.causationId, state: previousState });
|
|
@@ -531,8 +564,24 @@ export class FSMEngine {
|
|
|
531
564
|
// Parse target path (supports dot notation, e.g. "REFACTOR.EXTRACT_METHOD" or "GREEN_CODE")
|
|
532
565
|
const targetSegments = transDef.target.split('.');
|
|
533
566
|
const fullTargetPath = this.resolveInitialPath(targetSegments);
|
|
567
|
+
const transitionDurationMs = Number((performance.now() - startTime).toFixed(3));
|
|
568
|
+
const metrics = {
|
|
569
|
+
transition_duration_ms: transitionDurationMs,
|
|
570
|
+
slice_duration_ms: this.lastSliceMetrics?.slice_duration_ms,
|
|
571
|
+
slice_tokens_est: this.lastSliceMetrics?.slice_tokens_est,
|
|
572
|
+
};
|
|
573
|
+
if (transitionDurationMs > this.maxTransitionDurationMs) {
|
|
574
|
+
this.eventStore.append('PERF_DEGRADATION', {
|
|
575
|
+
operation: 'handleSignal',
|
|
576
|
+
duration_ms: transitionDurationMs,
|
|
577
|
+
threshold_ms: this.maxTransitionDurationMs,
|
|
578
|
+
signal: signalName,
|
|
579
|
+
from: previousState,
|
|
580
|
+
to: fullTargetPath.join('.'),
|
|
581
|
+
}, { state: fullTargetPath.join('.') });
|
|
582
|
+
}
|
|
534
583
|
// Execute exit hooks and entry hooks along the transition path
|
|
535
|
-
this.transitionBetweenPaths(prevPath, fullTargetPath, signalName, event.id, payload);
|
|
584
|
+
this.transitionBetweenPaths(prevPath, fullTargetPath, signalName, event.id, payload, metrics);
|
|
536
585
|
// PERF-02 / INV-08: Persist state snapshot for fast cold-boot rehydration
|
|
537
586
|
this.eventStore.saveSnapshot(this.eventStore.getLatestSequence(), this.getCurrentState(), this.context);
|
|
538
587
|
// Render deliverables
|
|
@@ -576,6 +625,7 @@ export class FSMEngine {
|
|
|
576
625
|
event,
|
|
577
626
|
handledAtDepth: depth,
|
|
578
627
|
deliverablesWritten,
|
|
628
|
+
metrics,
|
|
579
629
|
};
|
|
580
630
|
}
|
|
581
631
|
}
|
|
@@ -591,7 +641,7 @@ export class FSMEngine {
|
|
|
591
641
|
/**
|
|
592
642
|
* Transition between two hierarchical paths executing exit, transition, and entry hooks
|
|
593
643
|
*/
|
|
594
|
-
transitionBetweenPaths(fromPath, toPath, signalName, causationId, payload) {
|
|
644
|
+
transitionBetweenPaths(fromPath, toPath, signalName, causationId, payload, metrics) {
|
|
595
645
|
// 1. Find Lowest Common Ancestor (LCA)
|
|
596
646
|
let lcaDepth = 0;
|
|
597
647
|
const maxDepth = Math.min(fromPath.length, toPath.length);
|
|
@@ -609,6 +659,7 @@ export class FSMEngine {
|
|
|
609
659
|
to: toPath.join('.'),
|
|
610
660
|
signal: signalName,
|
|
611
661
|
payload,
|
|
662
|
+
metrics,
|
|
612
663
|
}, {
|
|
613
664
|
state: toPath.join('.'),
|
|
614
665
|
causationId,
|
|
@@ -692,8 +743,11 @@ export class FSMEngine {
|
|
|
692
743
|
isJobActive() {
|
|
693
744
|
return this.isActiveJob;
|
|
694
745
|
}
|
|
695
|
-
|
|
696
|
-
return this.
|
|
746
|
+
getLastMetrics() {
|
|
747
|
+
return this.lastSliceMetrics;
|
|
748
|
+
}
|
|
749
|
+
clearTemplateCache() {
|
|
750
|
+
this.templateCache.clear();
|
|
697
751
|
}
|
|
698
752
|
close() {
|
|
699
753
|
this.eventStore.close();
|
package/dist/core/types.d.ts
CHANGED
|
@@ -116,6 +116,16 @@ export interface SkillManifest {
|
|
|
116
116
|
states: Record<string, StateDefinition>;
|
|
117
117
|
deliverable_projections?: DeliverableProjection[];
|
|
118
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Real-time execution performance metrics
|
|
121
|
+
*/
|
|
122
|
+
export interface ExecutionMetrics {
|
|
123
|
+
slice_duration_ms?: number;
|
|
124
|
+
slice_tokens_est?: number;
|
|
125
|
+
transition_duration_ms?: number;
|
|
126
|
+
allowed_tools_count?: number;
|
|
127
|
+
[key: string]: any;
|
|
128
|
+
}
|
|
119
129
|
/**
|
|
120
130
|
* Hydrated prompt slice generated for an active LLM turn
|
|
121
131
|
*/
|
|
@@ -126,6 +136,7 @@ export interface PromptSlice {
|
|
|
126
136
|
allowedTools: string[];
|
|
127
137
|
context: Record<string, any>;
|
|
128
138
|
exitConditions: string[];
|
|
139
|
+
metrics?: ExecutionMetrics;
|
|
129
140
|
}
|
|
130
141
|
/**
|
|
131
142
|
* Zod Schema for validation of skill.yaml
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactive-skills/runtime",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Reactive Skills Architecture (RSA) core runtime โ FSM engine, event store, guard evaluator, projection engine, MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|