reflex-state 0.1.0-alpha.1

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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +255 -0
  3. package/README_ja.md +253 -0
  4. package/dist/cli_io.d.ts +8 -0
  5. package/dist/cli_io.js +45 -0
  6. package/dist/composition.d.ts +3 -0
  7. package/dist/composition.js +8 -0
  8. package/dist/core/config.d.ts +37 -0
  9. package/dist/core/config.js +27 -0
  10. package/dist/core/config_validation.d.ts +6 -0
  11. package/dist/core/config_validation.js +109 -0
  12. package/dist/core/engine.d.ts +28 -0
  13. package/dist/core/engine.js +72 -0
  14. package/dist/core/events.d.ts +7 -0
  15. package/dist/core/events.js +56 -0
  16. package/dist/core/extraction.d.ts +10 -0
  17. package/dist/core/extraction.js +119 -0
  18. package/dist/core/metrics.d.ts +52 -0
  19. package/dist/core/metrics.js +100 -0
  20. package/dist/core/reducer.d.ts +18 -0
  21. package/dist/core/reducer.js +206 -0
  22. package/dist/core/serialization.d.ts +11 -0
  23. package/dist/core/serialization.js +160 -0
  24. package/dist/core/state_view.d.ts +27 -0
  25. package/dist/core/state_view.js +28 -0
  26. package/dist/core/types.d.ts +188 -0
  27. package/dist/core/types.js +1 -0
  28. package/dist/core/updater.d.ts +31 -0
  29. package/dist/core/updater.js +27 -0
  30. package/dist/core/verification.d.ts +20 -0
  31. package/dist/core/verification.js +234 -0
  32. package/dist/export_trace_cli.d.ts +2 -0
  33. package/dist/export_trace_cli.js +45 -0
  34. package/dist/index.d.ts +13 -0
  35. package/dist/index.js +8 -0
  36. package/dist/pi/commands.d.ts +3 -0
  37. package/dist/pi/commands.js +123 -0
  38. package/dist/pi/configuration.d.ts +12 -0
  39. package/dist/pi/configuration.js +48 -0
  40. package/dist/pi/extension.d.ts +3 -0
  41. package/dist/pi/extension.js +113 -0
  42. package/dist/pi/index.d.ts +2 -0
  43. package/dist/pi/index.js +5 -0
  44. package/dist/pi/normalization.d.ts +31 -0
  45. package/dist/pi/normalization.js +72 -0
  46. package/dist/pi/persistence.d.ts +15 -0
  47. package/dist/pi/persistence.js +65 -0
  48. package/dist/pi/projection.d.ts +11 -0
  49. package/dist/pi/projection.js +159 -0
  50. package/dist/pi/runtime.d.ts +35 -0
  51. package/dist/pi/runtime.js +139 -0
  52. package/dist/pi/state_block.d.ts +10 -0
  53. package/dist/pi/state_block.js +123 -0
  54. package/dist/pi/trace.d.ts +14 -0
  55. package/dist/pi/trace.js +106 -0
  56. package/dist/replay/runner.d.ts +50 -0
  57. package/dist/replay/runner.js +21 -0
  58. package/dist/replay/trace.d.ts +2 -0
  59. package/dist/replay/trace.js +7 -0
  60. package/dist/replay_cli.d.ts +2 -0
  61. package/dist/replay_cli.js +71 -0
  62. package/dist/typesafe/client.d.ts +21 -0
  63. package/dist/typesafe/client.js +49 -0
  64. package/dist/typesafe/deadline.d.ts +8 -0
  65. package/dist/typesafe/deadline.js +35 -0
  66. package/dist/typesafe/decisions.d.ts +5 -0
  67. package/dist/typesafe/decisions.js +94 -0
  68. package/dist/typesafe/gating.d.ts +7 -0
  69. package/dist/typesafe/gating.js +48 -0
  70. package/dist/typesafe/input.d.ts +3 -0
  71. package/dist/typesafe/input.js +115 -0
  72. package/dist/typesafe/questions.d.ts +5 -0
  73. package/dist/typesafe/questions.js +50 -0
  74. package/dist/typesafe/request_plan.d.ts +19 -0
  75. package/dist/typesafe/request_plan.js +96 -0
  76. package/dist/typesafe/updater.d.ts +17 -0
  77. package/dist/typesafe/updater.js +82 -0
  78. package/package.json +97 -0
@@ -0,0 +1,5 @@
1
+ import type { StateUpdateContext } from "../core/updater.js";
2
+ import type { Question } from "./client.js";
3
+ export declare const phases: readonly ["planning", "exploring", "editing", "testing", "debugging", "done", "unknown"];
4
+ export declare const categories: readonly ["implementation", "environment", "dependency", "test", "permissions", "network", "unknown"];
5
+ export declare function buildQuestions(context: StateUpdateContext): Record<string, Question>;
@@ -0,0 +1,50 @@
1
+ import { buildRequestPlan } from "./request_plan.js";
2
+ export const phases = [
3
+ "planning",
4
+ "exploring",
5
+ "editing",
6
+ "testing",
7
+ "debugging",
8
+ "done",
9
+ "unknown",
10
+ ];
11
+ export const categories = [
12
+ "implementation",
13
+ "environment",
14
+ "dependency",
15
+ "test",
16
+ "permissions",
17
+ "network",
18
+ "unknown",
19
+ ];
20
+ export function buildQuestions(context) {
21
+ const questions = {};
22
+ const plan = buildRequestPlan(context);
23
+ for (const item of plan.items) {
24
+ if (item.kind === "blocker_introduced")
25
+ questions[item.id] = noul("Does this observation introduce a currently unresolved blocker to the user's goal?");
26
+ if (item.kind === "failure_category")
27
+ questions[item.id] = choice("Classify the cause of this failure using the evidence. Choose unknown when it does not establish a cause.", categories);
28
+ if (item.kind === "resolve")
29
+ questions[item.id] = noul("Does the latest observation resolve or supersede blocker " +
30
+ item.eventId +
31
+ "? Success alone is insufficient without evidence of a relationship.");
32
+ if (item.kind === "relevance")
33
+ questions[item.id] = noul("Is evidence " + item.eventId + " still relevant to the current user goal?");
34
+ if (item.kind === "task_complete")
35
+ questions[item.id] = noul("Does the final response and verification evidence establish that the current user goal is fully completed? An aborted or failed run is not completion.");
36
+ if (item.kind === "phase_shadow")
37
+ questions[item.id] = choice("What phase is the coding task currently in? This answer is recorded for comparison only.", phases);
38
+ }
39
+ return questions;
40
+ }
41
+ function noul(instructions) {
42
+ return { type: "noul", instructions };
43
+ }
44
+ function choice(instructions, values) {
45
+ return {
46
+ type: "choice",
47
+ instructions,
48
+ criteria: Object.fromEntries(values.map((value) => [value, value.replaceAll("_", " ")])),
49
+ };
50
+ }
@@ -0,0 +1,19 @@
1
+ import type { EventId } from "../core/types.js";
2
+ import type { StateUpdateContext } from "../core/updater.js";
3
+ type RequestQuestionKind = "blocker_introduced" | "failure_category" | "resolve" | "relevance" | "task_complete" | "phase_shadow";
4
+ interface RequestPlanItem {
5
+ readonly id: string;
6
+ readonly kind: RequestQuestionKind;
7
+ readonly eventId?: EventId;
8
+ readonly requiredEvidence: readonly EventId[];
9
+ }
10
+ interface RequestPlan {
11
+ readonly items: readonly RequestPlanItem[];
12
+ readonly skipped: readonly {
13
+ readonly id: string;
14
+ readonly reason: string;
15
+ }[];
16
+ readonly evidenceIds: readonly EventId[];
17
+ }
18
+ export declare function buildRequestPlan(context: StateUpdateContext): RequestPlan;
19
+ export {};
@@ -0,0 +1,96 @@
1
+ import { admitsEvidence } from "../core/reducer.js";
2
+ const MAX_QUESTIONS = 8;
3
+ const MAX_INPUT_BYTES = 24_000;
4
+ const INPUT_OVERHEAD_BYTES = 6_000;
5
+ export function buildRequestPlan(context) {
6
+ const { event, facts, state, evidence, config } = context;
7
+ if (isReadEvent(event))
8
+ return { items: [], skipped: [], evidenceIds: [] };
9
+ const candidates = [];
10
+ const latest = event.id;
11
+ if (event.type === "tool_result" && event.isError) {
12
+ if (facts.verification?.status !== "failed")
13
+ candidates.push({
14
+ id: "blocker_introduced",
15
+ kind: "blocker_introduced",
16
+ requiredEvidence: [latest],
17
+ });
18
+ candidates.push({
19
+ id: "failure_category",
20
+ kind: "failure_category",
21
+ requiredEvidence: [latest],
22
+ });
23
+ }
24
+ if (event.type === "tool_result" && !event.isError) {
25
+ for (const blocker of state.activeBlockers.filter((item) => item.origin === "tool_error")) {
26
+ candidates.push({
27
+ id: "resolves_" + blocker.eventId,
28
+ kind: "resolve",
29
+ eventId: blocker.eventId,
30
+ requiredEvidence: [blocker.eventId, latest],
31
+ });
32
+ }
33
+ }
34
+ if (event.type === "agent_end")
35
+ candidates.push({
36
+ id: "task_complete",
37
+ kind: "task_complete",
38
+ requiredEvidence: completionEvidence(state, latest),
39
+ });
40
+ const remaining = state.workingSet.filter((id) => !facts.supersededInWorkingSet.includes(id) && !facts.deterministicallyResolved.includes(id));
41
+ if (admitsEvidence(event, facts) && remaining.length + 1 > config.limits.maxWorkingSetEvents) {
42
+ for (const id of remaining.slice(0, 4))
43
+ candidates.push({
44
+ id: "relevant_" + id,
45
+ kind: "relevance",
46
+ eventId: id,
47
+ requiredEvidence: [id],
48
+ });
49
+ }
50
+ const items = [];
51
+ const skipped = [];
52
+ const available = candidates.filter((item) => {
53
+ const missing = item.requiredEvidence.filter((id) => !evidence.has(id) && id !== latest);
54
+ if (!missing.length)
55
+ return true;
56
+ skipped.push({ id: item.id, reason: "evidence_unavailable" });
57
+ return false;
58
+ });
59
+ if (config.shadowQuestions.includes("phase") && available.length)
60
+ available.push({ id: "phase_shadow", kind: "phase_shadow", requiredEvidence: [latest] });
61
+ const selected = [];
62
+ let estimatedBytes = INPUT_OVERHEAD_BYTES + Buffer.byteLength(JSON.stringify({ state, event }));
63
+ for (const item of available) {
64
+ const evidenceBytes = item.requiredEvidence.reduce((total, id) => {
65
+ const source = id === latest ? event : evidence.get(id);
66
+ return total + (source ? Buffer.byteLength(JSON.stringify(source)) : 0);
67
+ }, 0);
68
+ const itemBytes = Buffer.byteLength(JSON.stringify(item)) + evidenceBytes;
69
+ if (estimatedBytes + itemBytes > MAX_INPUT_BYTES) {
70
+ skipped.push({ id: item.id, reason: "input_budget" });
71
+ continue;
72
+ }
73
+ selected.push(item);
74
+ estimatedBytes += itemBytes;
75
+ }
76
+ for (const item of available) {
77
+ if (!selected.includes(item))
78
+ continue;
79
+ if (items.length >= MAX_QUESTIONS) {
80
+ skipped.push({ id: item.id, reason: "question_limit" });
81
+ continue;
82
+ }
83
+ items.push(item);
84
+ }
85
+ const evidenceIds = [...new Set(items.flatMap((item) => item.requiredEvidence))];
86
+ return { items, skipped, evidenceIds };
87
+ }
88
+ function completionEvidence(state, latest) {
89
+ const verification = Object.values(state.verification).flatMap((item) => item.evidence ? [item.evidence] : []);
90
+ return [
91
+ ...new Set([...state.activeBlockers.map((item) => item.eventId), ...verification, latest]),
92
+ ];
93
+ }
94
+ function isReadEvent(event) {
95
+ return event.type === "tool_result" && ["read", "grep", "find", "ls"].includes(event.toolName);
96
+ }
@@ -0,0 +1,17 @@
1
+ import type { SemanticDecisions } from "../core/types.js";
2
+ import type { StateUpdateContext, StateUpdater, UpdaterHealth } from "../core/updater.js";
3
+ import type { TypeSafeSystemOneClient } from "./client.js";
4
+ export declare class JevStateUpdater implements StateUpdater {
5
+ private readonly client;
6
+ private readonly options;
7
+ readonly name = "jev";
8
+ private failures;
9
+ private openedUntil;
10
+ private disabledReason;
11
+ constructor(client: TypeSafeSystemOneClient, options?: {
12
+ notify?: (message: string) => void;
13
+ });
14
+ get health(): UpdaterHealth;
15
+ evaluate(context: StateUpdateContext, signal?: AbortSignal): Promise<SemanticDecisions>;
16
+ private recordFailure;
17
+ }
@@ -0,0 +1,82 @@
1
+ import { emptyDecisions } from "../core/updater.js";
2
+ import { failureKind } from "./client.js";
3
+ import { RequestCancelledError, withinDeadline } from "./deadline.js";
4
+ import { decodeDecisions, describeResponse, errorDecisions } from "./decisions.js";
5
+ import { buildInput } from "./input.js";
6
+ import { buildQuestions } from "./questions.js";
7
+ export class JevStateUpdater {
8
+ client;
9
+ options;
10
+ name = "jev";
11
+ failures = 0;
12
+ openedUntil = 0;
13
+ disabledReason;
14
+ constructor(client, options = {}) {
15
+ this.client = client;
16
+ this.options = options;
17
+ }
18
+ get health() {
19
+ if (this.disabledReason)
20
+ return { status: "disabled", circuit: "closed", reason: this.disabledReason };
21
+ if (this.openedUntil > Date.now())
22
+ return { status: "degraded", circuit: "open" };
23
+ if (this.openedUntil)
24
+ return { status: "degraded", circuit: "half_open" };
25
+ return { status: this.failures ? "degraded" : "ok", circuit: "closed" };
26
+ }
27
+ async evaluate(context, signal) {
28
+ if (!context.config.jev.enabled)
29
+ return emptyDecisions();
30
+ if (this.disabledReason)
31
+ return errorDecisions([], this.disabledReason);
32
+ if (this.openedUntil > Date.now())
33
+ return errorDecisions([], "circuit_open");
34
+ const questions = buildQuestions(context);
35
+ const ids = Object.keys(questions);
36
+ if (!ids.length)
37
+ return emptyDecisions();
38
+ const started = performance.now();
39
+ let invoked = false;
40
+ let response;
41
+ let responseShape;
42
+ let decisions;
43
+ try {
44
+ response = await withinDeadline((requestSignal) => {
45
+ const state = buildInput(context);
46
+ invoked = true;
47
+ return this.client.systemOne({ state, questions, model: context.config.jev.model }, { signal: requestSignal });
48
+ }, { deadlineMs: context.config.jev.deadlineMs, signal });
49
+ decisions = decodeDecisions(response, ids, context.config.thresholds);
50
+ this.failures = 0;
51
+ this.openedUntil = 0;
52
+ }
53
+ catch (error) {
54
+ const kind = error instanceof RequestCancelledError ? error.kind : failureKind(error);
55
+ if (kind === "invalid_response")
56
+ responseShape = describeResponse(response, ids);
57
+ if (kind !== "aborted")
58
+ this.recordFailure(kind, context);
59
+ decisions = errorDecisions(ids, kind);
60
+ }
61
+ return {
62
+ ...decisions,
63
+ telemetry: {
64
+ ...decisions.telemetry,
65
+ questionsAsked: invoked ? ids.length : 0,
66
+ questionIds: invoked ? ids : [],
67
+ ...(invoked ? { latencyMs: performance.now() - started } : {}),
68
+ ...(responseShape ? { responseShape } : {}),
69
+ },
70
+ };
71
+ }
72
+ recordFailure(kind, context) {
73
+ this.failures++;
74
+ if (kind === "auth_error") {
75
+ this.disabledReason = kind;
76
+ this.options.notify?.("ReflexState: Jev authentication failed; deterministic updates remain active.");
77
+ }
78
+ else if (this.failures >= 3) {
79
+ this.openedUntil = Date.now() + context.config.jev.cooldownMs;
80
+ }
81
+ }
82
+ }
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "reflex-state",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "SKILL.state + Jev for Pi: execution state maintained by typed decisions and code, separate from the reasoning LLM.",
5
+ "keywords": [
6
+ "agent-memory",
7
+ "agent-state",
8
+ "ai-agents",
9
+ "coding-agent",
10
+ "context-engineering",
11
+ "context-management",
12
+ "jev",
13
+ "pi-coding-agent",
14
+ "pi-extension",
15
+ "pi-package",
16
+ "replay",
17
+ "skill-state",
18
+ "typesafe-ai"
19
+ ],
20
+ "homepage": "https://github.com/furedea/reflex-state#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/furedea/reflex-state/issues"
23
+ },
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/furedea/reflex-state.git"
28
+ },
29
+ "bin": {
30
+ "reflex-state-export": "./dist/export_trace_cli.js",
31
+ "reflex-state-replay": "./dist/replay_cli.js"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md",
36
+ "README_ja.md",
37
+ "LICENSE"
38
+ ],
39
+ "type": "module",
40
+ "exports": {
41
+ ".": {
42
+ "types": "./dist/index.d.ts",
43
+ "import": "./dist/index.js"
44
+ }
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "tag": "next"
49
+ },
50
+ "scripts": {
51
+ "build": "tsc -p tsconfig.build.json",
52
+ "package:check": "node scripts/check_package.mjs",
53
+ "replay": "pnpm --silent run build && node dist/replay_cli.js",
54
+ "export-trace": "pnpm --silent run build && node dist/export_trace_cli.js",
55
+ "format": "oxfmt .",
56
+ "format:check": "oxfmt --check .",
57
+ "lint": "oxlint .",
58
+ "lint:fix": "oxlint --fix .",
59
+ "typecheck": "oxlint --type-aware --type-check .",
60
+ "test": "vitest run",
61
+ "test:release": "node --test scripts/*.test.mjs",
62
+ "test:watch": "vitest",
63
+ "knip": "knip",
64
+ "knip:production": "knip --production",
65
+ "check": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run test:release && pnpm run knip"
66
+ },
67
+ "dependencies": {
68
+ "@typesafe-ai/sdk": "0.6.0"
69
+ },
70
+ "devDependencies": {
71
+ "@earendil-works/pi-coding-agent": "0.83.0",
72
+ "@types/node": "24.13.3",
73
+ "knip": "6.4.1",
74
+ "oxfmt": "0.45.0",
75
+ "oxlint": "1.60.0",
76
+ "oxlint-tsgolint": "0.21.1",
77
+ "typescript": "6.0.3",
78
+ "vitest": "4.1.11"
79
+ },
80
+ "peerDependencies": {
81
+ "@earendil-works/pi-coding-agent": "*"
82
+ },
83
+ "peerDependenciesMeta": {
84
+ "@earendil-works/pi-coding-agent": {
85
+ "optional": true
86
+ }
87
+ },
88
+ "engines": {
89
+ "node": ">=22.19.0"
90
+ },
91
+ "packageManager": "pnpm@10.33.0",
92
+ "pi": {
93
+ "extensions": [
94
+ "./dist/pi/index.js"
95
+ ]
96
+ }
97
+ }