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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kaito Shigyo / 執行凱斗
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,255 @@
1
+ # ReflexState
2
+
3
+ **Your agent reasons. ReflexState keeps track.**
4
+
5
+ English | [日本語](README_ja.md)
6
+
7
+ ReflexState gives [Pi](https://github.com/earendil-works/pi/tree/main/packages/coding-agent)
8
+ coding agents a dedicated execution-state layer.
9
+ It tracks what changed, which checks are still current, and what remains
10
+ blocked—independently of the main reasoning LLM.
11
+
12
+ **[TypeSafe Jev](https://typesafe.ai/blog/introducing-system-one-models-and-jev) makes typed
13
+ semantic decisions. Code extracts facts and applies updates.**
14
+ Inspect the state as your agent works, or explicitly add it to the model's context.
15
+
16
+ Built around the execution-state idea from Google's
17
+ [SKILL.state](https://arxiv.org/abs/2608.26263), ReflexState explores a different division
18
+ of labor: keep reasoning and coding with the main model, and maintain execution state with
19
+ Jev and deterministic code.
20
+
21
+ ## Why ReflexState?
22
+
23
+ - **Separate reasoning from state maintenance.** Maintain structured execution state without
24
+ asking the main model to generate state summaries. Jev handles bounded semantic questions;
25
+ code controls how answers change the state.
26
+ - **Track what is still true.** Distinguish earlier test success from current verification.
27
+ Keep unresolved blockers even when their details do not fit in the displayed context.
28
+ - **Inspect the evidence. Replay the updates.** Trace state changes to execution events and
29
+ recorded decisions. Replay recorded state transitions without calling Jev again.
30
+
31
+ **Alpha preview.** Context injection is opt-in. Append mode adds state without removing
32
+ conversation history. End-to-end cost and performance gains have not yet been established.
33
+ See [verification and limitations](#verification-and-limitations) for current coverage.
34
+
35
+ ## Install the preview
36
+
37
+ The npm command requires the first preview release. Until it is available, use the local
38
+ checkout instructions below. With Pi installed and `TYPESAFE_API_KEY` set in your environment:
39
+
40
+ ```sh
41
+ pi install npm:reflex-state@next
42
+ pi
43
+ ```
44
+
45
+ Run `/state` to see the current goal, changed files, verification results, and blockers.
46
+ For a baseline using only deterministic code, start with `REFLEX_STATE_DISABLE_JEV=1 pi`;
47
+ that mode needs no TypeSafe key. Node.js 22.19 or later is required, and Pi 0.83.0 is the
48
+ verified host version.
49
+
50
+ ## Run locally
51
+
52
+ Use Node.js 22.19 or later, pnpm 10.33.0, and Pi 0.83.0. Dependencies are pinned; Pi is a
53
+ development dependency and TypeSafe SDK 0.6.0 is the only runtime dependency. Set
54
+ `TYPESAFE_API_KEY` in your environment to run with Jev.
55
+
56
+ ```sh
57
+ pnpm install --ignore-scripts
58
+ pnpm exec pi
59
+ ```
60
+
61
+ Trust this checkout in Pi to load its `.pi/extensions/reflex_state.ts` entry automatically.
62
+ To load an explicit path, including from another project:
63
+
64
+ ```sh
65
+ pnpm exec pi --no-extensions -e ./src/pi/index.ts
66
+ ```
67
+
68
+ The explicit command disables discovery to avoid loading both entries in this checkout.
69
+ When running elsewhere, use the absolute path to `src/pi/index.ts`. No Pi patch is required.
70
+
71
+ To try the deterministic baseline, use `REFLEX_STATE_DISABLE_JEV=1 pnpm exec pi`.
72
+ The SDK reads `TYPESAFE_API_KEY` from the environment; keys are rejected in ReflexState config
73
+ files. Missing or rejected credentials disable Jev for that runtime and produce one notification;
74
+ deterministic state updates continue. `/state jev on` creates a fresh updater after credentials
75
+ are corrected.
76
+
77
+ ## Architecture
78
+
79
+ | Component | Responsibility |
80
+ | ------------------------------------------- | --------------------------------------------------------------------------------- |
81
+ | `src/core/` | Raw events, pure extraction, gated decisions, reducer, serialized engine, metrics |
82
+ | `src/typesafe/` | Atomic Choice/Noul questions, redaction, confidence gates, deadlines and circuit |
83
+ | `src/pi/` | Pi hooks, branch restoration, commands, widget, ephemeral context projection |
84
+ | `src/replay/` | The same state pipeline applied to exported events and recorded decisions |
85
+ | `src/composition.ts` and executable entries | Assemble adapters through core interfaces |
86
+
87
+ Each processed event appends a `reflex-state.transition` custom entry containing the event,
88
+ resulting state, effective config, cwd, and decisions. Restoration follows only the active Pi
89
+ branch after its latest reset. IDs remain monotonic across resets and branch switches.
90
+ The reducer is shared by live execution and replay; core imports no Pi or TypeSafe code.
91
+
92
+ The SKILL.state paper supplies the latest observation at each step. ReflexState adapts this
93
+ to Pi by keeping execution state alongside the conversation.
94
+
95
+ Projection is disabled by default. `append` adds a bounded `<reflex-state>` block while keeping
96
+ every original message. Experimental `current-run` keeps the current run and the immediately
97
+ preceding ended run, including steers and matched tool exchanges, but may omit older ordinary
98
+ conversation. Neither mode removes messages from inside a run. Projection changes the outgoing
99
+ context only; it never rewrites or deletes the Pi session log. Incomplete exchanges, unknown
100
+ messages, missing goal text, compaction, or an insufficient block budget preserve the original
101
+ context.
102
+
103
+ ## Configuration and controls
104
+
105
+ Precedence is defaults, global config, trusted project config, environment, then session toggles.
106
+ The global file is `~/.pi/agent/reflex-state.json`, or
107
+ `$PI_CODING_AGENT_DIR/reflex-state.json` when Pi's directory override is set. Project config is
108
+ `.pi/reflex-state.json` and is read only for trusted projects. Toggles are reapplied from config
109
+ and environment when the runtime is restored, including branch switches.
110
+
111
+ Example project config:
112
+
113
+ ```json
114
+ {
115
+ "projection": {
116
+ "enabled": true,
117
+ "mode": "append",
118
+ "placement": "last-message"
119
+ },
120
+ "limits": { "maxProjectedBlockers": 8 },
121
+ "verificationCommands": { "test": ["^make check$"] }
122
+ }
123
+ ```
124
+
125
+ To try the experimental history selection explicitly:
126
+
127
+ ```json
128
+ {
129
+ "projection": { "enabled": true, "mode": "current-run" }
130
+ }
131
+ ```
132
+
133
+ `limits.maxActiveBlockers` is accepted as a deprecated alias with a warning. It no longer
134
+ limits the unresolved blocker record; `maxProjectedBlockers` limits display only.
135
+
136
+ Additional command regexes extend built-in test/build/lint detection. Unknown keys warn;
137
+ invalid types, thresholds, limits, regexes, or credential fields reject the file and restore
138
+ defaults. Full defaults live in [core/config.ts](https://github.com/furedea/reflex-state/blob/main/src/core/config.ts).
139
+
140
+ | Control | Effect |
141
+ | ---------------------------- | ------------------------------------------------------------------------------- |
142
+ | `/state` | Current goal reference, activity, files, verification, blockers and working set |
143
+ | `/state history [n]` | Recent transitions and decision gates |
144
+ | `/state stats` | Measured usage, latency, projection counts and updater health |
145
+ | `/state debug` | Latest semantic decisions, question IDs and raw probabilities |
146
+ | `/state reset` | Confirm, append a reset marker, and clear hot state |
147
+ | `/state projection on\|off` | Toggle context projection for the current runtime |
148
+ | `/state jev on\|off` | Toggle semantic decisions for the current runtime |
149
+ | `REFLEX_STATE_DISABLE=1` | Disable state recording and projection |
150
+ | `REFLEX_STATE_DISABLE_JEV=1` | Start with deterministic updates only |
151
+ | `REFLEX_STATE_PROJECTION=1` | Enable the configured projection mode |
152
+ | `REFLEX_STATE_PROJECTION=0` | Start with original Pi context |
153
+
154
+ Default Jev limits are a 3-second SDK timeout, zero retries, and a 4-second outer deadline
155
+ linked to Pi cancellation. Three consecutive failures open a 60-second circuit; a subsequent
156
+ probe can close it. Authentication failures disable the updater until recreated. Uncertain
157
+ answers do not apply semantic changes. Phase questions run in shadow alongside required
158
+ questions and never control activity directly.
159
+
160
+ ## What is sent to TypeSafe
161
+
162
+ When Jev is enabled, a bounded request plan selects each question together with the evidence it
163
+ needs. Inputs contain bounded user request text, the current typed state, file paths, relevant
164
+ bash/edit/write evidence, and bounded final assistant text when deciding completion. The JSON
165
+ input has a 24,000-byte ceiling; a question whose evidence cannot fit is omitted with a local
166
+ reason. Read/grep/find/ls results produce zero Jev calls and their output text is excluded from
167
+ other requests.
168
+
169
+ Common credential patterns, bearer credentials, private-key blocks and environment assignments
170
+ are redacted before sending. This is pattern-based filtering, not a guarantee that arbitrary
171
+ sensitive text is recognized. Source code can still appear in test errors or other tool output.
172
+ Raw local transition records retain bounded inputs and excerpts; outbound redaction does not
173
+ rewrite the original Pi history. SDK logging excludes request/response contents. Invalid
174
+ responses record expected field types, never unexpected response values.
175
+
176
+ ## Export and replay
177
+
178
+ From a published package, run the commands without cloning this repository:
179
+
180
+ ```sh
181
+ npm exec --package=reflex-state@next -- reflex-state-export /path/to/session.jsonl --out trace-output
182
+ npm exec --package=reflex-state@next -- reflex-state-replay trace-output/events.jsonl --updater noop --out replay-output
183
+ ```
184
+
185
+ From a local checkout:
186
+
187
+ ```sh
188
+ pnpm export-trace /path/to/session.jsonl --out trace-output
189
+ pnpm replay trace-output/events.jsonl --updater noop --out replay-output
190
+ pnpm replay trace-output/events.jsonl --updater recorded --out replay-recorded
191
+ ```
192
+
193
+ Export follows the last stored leaf, or `--leaf <entry-id>`. Existing ReflexState transitions
194
+ provide their original events and decisions after the latest reset; sessions without them are
195
+ normalized from Pi message entries. A legacy state is marked as legacy and is never presented as
196
+ a v2 recording. Export writes `events.jsonl`, `transitions.jsonl`, and `trace_meta.json`, leaving
197
+ the source file unchanged.
198
+
199
+ Replay writes `final_state.json`, `transitions.jsonl`, `metrics.json`, and `summary.txt`.
200
+ `noop` is deterministic; `jev` makes live semantic requests. `recorded` reads the neighboring
201
+ `transitions.jsonl`, or use `--updater recorded:/path/to/transitions.jsonl`. It verifies matching
202
+ events and reuses each recorded configuration, cwd, timestamp, and decision to reproduce the
203
+ final state. It requires an existing decision log; a raw Pi session alone cannot supply one.
204
+
205
+ `--config <file>` and `--cwd <directory>` override defaults for ordinary replay. Exported
206
+ metadata supplies those defaults when present; recorded mode uses each transition's settings.
207
+ Missing usage stays unavailable (`n/a` in `/state stats`, omitted from JSON), never estimated.
208
+ Jev call counts mean client invocations, including failed attempts, not confirmed billable calls.
209
+
210
+ ## Verification and limitations
211
+
212
+ ```sh
213
+ pnpm check
214
+ pnpm build
215
+ pnpm package:check
216
+ REFLEX_STATE_LIVE_JEV=1 pnpm exec vitest run src/typesafe/live_contract.test.ts
217
+ ```
218
+
219
+ The last command requires `TYPESAFE_API_KEY` and makes one live System One request. It is skipped
220
+ by default. Offline tests cover deterministic behavior, gating, redaction, deadlines, outage
221
+ handling, projection, branches, and exact replay. Pi smoke tests use the real 0.83.0 loader,
222
+ extension runner, local write/edit/bash tools, and on-disk session manager with model networking
223
+ disabled. Both explicit loading and trusted `.pi/extensions` discovery are exercised.
224
+
225
+ Current limitations:
226
+
227
+ - `append` retains history but does not reduce context size. `current-run` can omit runs older
228
+ than the immediately preceding ended run; it is experimental and does not establish token
229
+ savings or improved task success. Both modes preserve messages within a run.
230
+ - Bash is treated as a possible workspace change. Verification results become stale after
231
+ edits, writes, file-change events, bash, resume, or branch switching; the project does not
232
+ automatically rerun checks.
233
+ - Verification detection is conservative and heuristic, not a shell parser. Compound commands,
234
+ truncated commands, and incomplete old events are unknown and cannot clear a blocker.
235
+ - A passed result describes that command's observed result and target, not every assertion or all
236
+ checks in a project. Different commands and working directories remain different checks.
237
+ - Legacy v1 state is not automatically migrated. Stop state updates and projection, then use
238
+ the confirmed `/state reset` to begin a v2 state interval. Recorded replay of legacy state is
239
+ rejected; use the original implementation for strict legacy replay.
240
+ - Thresholds are heuristics without calibration. Live Jev outputs can vary; only recorded
241
+ decisions provide exact semantic replay.
242
+ - State blocks and working sets are bounded. There is no recall tool, within-run pruning,
243
+ benchmark framework, or adapter for another agent in v0.1.
244
+ - Pi delays creation of a new session file until its first assistant message. ReflexState uses
245
+ Pi's append API and inherits that persistence behavior.
246
+ - Live TypeSafe requests, interactive provider runs, and multi-text tool-result round trips
247
+ through Anthropic, OpenAI-compatible, and Google APIs have not been verified here. If a
248
+ provider rejects the appended tool-result block, set `projection.placement` to `run-start`
249
+ or disable projection. Run-start placement may reduce prompt-cache reuse.
250
+
251
+ [State safety contract](docs/state_safety_contract.md) defines the v2 state, freshness, blocker,
252
+ projection, budget, and legacy rules. [Validation notes](docs/state_safety_validation.md) map those
253
+ rules to regression tests and offline verification.
254
+
255
+ [ADR-0001](https://github.com/furedea/reflex-state/blob/main/docs/adr/0001_compose_adapters_at_entry_points.md) explains composition.
package/README_ja.md ADDED
@@ -0,0 +1,253 @@
1
+ # ReflexState
2
+
3
+ **エージェントは推論する.ReflexState は状態を追跡する.**
4
+
5
+ [English](README.md) | 日本語
6
+
7
+ ReflexState は [Pi](https://github.com/earendil-works/pi/tree/main/packages/coding-agent) の
8
+ コーディングエージェントに,専用の実行状態レイヤーを提供します.
9
+ 主推論 LLM から独立して,何が変更され,どの検証結果が現在も有効で,何が未解決かを追跡します.
10
+
11
+ **[TypeSafe Jev](https://typesafe.ai/blog/introducing-system-one-models-and-jev) は型付きの意味判定を行い,
12
+ コードは事実を抽出して状態を更新します.**
13
+ エージェントの作業中に状態を確認でき,必要な場合だけモデルのコンテキストへ明示的に追加できます.
14
+
15
+ Google の [SKILL.state](https://arxiv.org/abs/2608.26263) が示した実行状態の考え方を基に,
16
+ ReflexState は役割分担を探ります.主モデルが推論と実装を担い,Jev と決定論的なコードが
17
+ 実行状態を維持します.
18
+
19
+ ## ReflexState を使う理由
20
+
21
+ - **推論と状態管理を分離します.** 主モデルに状態の要約を生成させず,構造化された実行状態を維持します.
22
+ Jev は範囲を制限した意味判定を行い,コードは判定を状態へ反映します.
23
+ - **現在も真であるものを追跡します.** 過去のテスト成功と現在の検証結果を区別します.
24
+ 詳細が表示用コンテキストに収まらなくても,未解決の障害を保持します.
25
+ - **根拠を確認し,更新を再生できます.** 状態変更を実行イベントと記録済み判定に結び付け,
26
+ Jev を再度呼び出さずに状態遷移を再生します.
27
+
28
+ **アルファ版.** コンテキストへの注入はオプトインです.append モードは状態を追加しますが,
29
+ 会話履歴を削除しません.エンドツーエンドのコストと性能の向上は,まだ実証されていません.
30
+ 現在の確認範囲は [検証と制限](#検証と制限) を参照してください.
31
+
32
+ ## プレビュー版のインストール
33
+
34
+ npm からのインストールには,最初のプレビュー版の公開が必要です.公開前は,後述の
35
+ ローカル実行の手順を使ってください.Pi をインストールし,環境変数 `TYPESAFE_API_KEY` を
36
+ 設定したら,次を実行します.
37
+
38
+ ```sh
39
+ pi install npm:reflex-state@next
40
+ pi
41
+ ```
42
+
43
+ `/state` で現在の目標,変更したファイル,検証結果,障害を確認できます.
44
+ 決定論的なコードだけのベースラインを試すには,`REFLEX_STATE_DISABLE_JEV=1 pi` で起動します.
45
+ このモードでは TypeSafe のキーは不要です.Node.js 22.19 以降が必要で,動作確認済みの Pi は 0.83.0 です.
46
+
47
+ ## ローカルでの起動
48
+
49
+ Node.js 22.19 以降,pnpm 10.33.0,Pi 0.83.0 を使用します.依存バージョンは固定しています.
50
+ Pi は開発依存であり,実行時の依存は TypeSafe SDK 0.6.0 のみです.
51
+ Jev を使うため,環境変数 `TYPESAFE_API_KEY` を設定してください.
52
+
53
+ ```sh
54
+ pnpm install --ignore-scripts
55
+ pnpm exec pi
56
+ ```
57
+
58
+ Pi でこのチェックアウトを信頼すると,`.pi/extensions/reflex_state.ts` が自動で読み込まれます.
59
+ 別のプロジェクトから利用する場合など,パスを明示して読み込むには次を実行します.
60
+
61
+ ```sh
62
+ pnpm exec pi --no-extensions -e ./src/pi/index.ts
63
+ ```
64
+
65
+ このコマンドは,このチェックアウトで二つのエントリーが重複して読み込まれないよう,
66
+ 拡張の自動検出を無効にします.別のディレクトリから起動する場合は,
67
+ `src/pi/index.ts` の絶対パスを指定してください.Pi 本体の変更は不要です.
68
+
69
+ 決定論的なベースラインを試すには,`REFLEX_STATE_DISABLE_JEV=1 pnpm exec pi` を使います.
70
+ SDK は環境変数 `TYPESAFE_API_KEY` からキーを読み取ります.
71
+ ReflexState の設定ファイルに含まれるキーは拒否されます.
72
+ 認証情報がない場合や認証に失敗した場合は,そのランタイムの Jev を無効にして一度通知します.
73
+ 決定論的な状態更新は継続します.認証情報の修正後に `/state jev on` を実行すると,
74
+ 更新処理を新しく作成して再開できます.
75
+
76
+ ## アーキテクチャ
77
+
78
+ | コンポーネント | 役割 |
79
+ | ------------------------------------- | -------------------------------------------------------------------------------------------- |
80
+ | `src/core/` | 生イベント,純粋な抽出処理,閾値に基づく判定の適用,reducer,イベントの直列処理,メトリクス |
81
+ | `src/typesafe/` | 個別の Choice/Noul 質問,機密情報のマスキング,信頼度の閾値,時間制限,サーキットブレーカー |
82
+ | `src/pi/` | Pi のフック,分岐の状態復元,コマンド,ウィジェット,一時的なコンテキスト投影 |
83
+ | `src/replay/` | エクスポート済みイベントと記録済み判定への,共通の状態更新処理の適用 |
84
+ | `src/composition.ts` と実行エントリー | core のインターフェースを介したアダプターの組み立て |
85
+
86
+ イベントを処理するたびに,`reflex-state.transition` カスタムエントリーを追記します.
87
+ 各エントリーには,イベント,更新後の状態,適用した設定,作業ディレクトリ,判定を保存します.
88
+ 復元対象は,Pi の現在の分岐における直近のリセット以降の記録です.
89
+ ID はリセットや分岐の切り替えをまたいでも単調に増加します.
90
+ 通常実行と再生は同じ reducer を使い,core は Pi や TypeSafe のコードを import しません.
91
+
92
+ SKILL.state の論文は,各ステップで最新の観測を渡します.ReflexState はこれを Pi に合わせ,
93
+ 会話と並べて実行状態を保持します.
94
+
95
+ コンテキスト投影はデフォルトで無効です.`append` は元の全メッセージを残したまま,
96
+ サイズ制限付きの `<reflex-state>` ブロックを追加します.実験的な `current-run` は,
97
+ 現在の run と直前の終了済み run(steer と対応するツール呼び出し・結果を含む)を残し,
98
+ それより古い通常会話を省略することがあります.どちらのモードも run の途中の履歴は削りません.
99
+ 投影が変更するのは送信用のコンテキストだけで,Pi のセッションログを書き換えたり削除したりしません.
100
+ ツール呼び出しと結果の対応が不完全な場合,未知のメッセージを削除する必要がある場合,
101
+ 現在の依頼文がない場合,コンテキスト圧縮中,またはブロックのサイズ上限に収まらない場合は,
102
+ 元のコンテキストを保持します.
103
+
104
+ ## 設定と操作
105
+
106
+ 設定は,デフォルト,全体設定,信頼済みプロジェクトの設定,環境変数,セッション中の切り替えの順に優先されます.
107
+ 全体設定は `~/.pi/agent/reflex-state.json` です.Pi のディレクトリを変更している場合は,
108
+ `$PI_CODING_AGENT_DIR/reflex-state.json` を使います.
109
+ プロジェクト設定は `.pi/reflex-state.json` で,信頼済みのプロジェクトだけで読み込みます.
110
+ 分岐の切り替えなどでランタイムを復元すると,有効・無効の設定も設定ファイルと環境変数から再適用されます.
111
+
112
+ プロジェクト設定の例:
113
+
114
+ ```json
115
+ {
116
+ "projection": {
117
+ "enabled": true,
118
+ "mode": "append",
119
+ "placement": "last-message"
120
+ },
121
+ "limits": { "maxProjectedBlockers": 8 },
122
+ "verificationCommands": { "test": ["^make check$"] }
123
+ }
124
+ ```
125
+
126
+ 実験的に履歴を run 単位で選ぶ場合は,明示的に指定します.
127
+
128
+ ```json
129
+ {
130
+ "projection": { "enabled": true, "mode": "current-run" }
131
+ }
132
+ ```
133
+
134
+ `limits.maxActiveBlockers` は警告付きの旧キーとして受け付けますが,未解決障害の正本を
135
+ 制限しません.表示だけを `maxProjectedBlockers` で制限します.
136
+
137
+ コマンドの正規表現を追加すると,組み込みの test/build/lint の検出を拡張できます.
138
+ 不明なキーには警告を出します.型,閾値,上限値,正規表現が不正な場合や認証情報を含む場合は,
139
+ その設定ファイルを拒否してデフォルトに戻します.
140
+ 全デフォルト値は [core/config.ts](https://github.com/furedea/reflex-state/blob/main/src/core/config.ts) を参照してください.
141
+
142
+ | 操作 | 動作 |
143
+ | ---------------------------- | ------------------------------------------------------------------------------------ |
144
+ | `/state` | 現在の依頼への参照,作業フェーズ,ファイル,検証結果,障害,作業用イベント集合を表示 |
145
+ | `/state history [n]` | 最近の状態遷移と判定の適用状況を表示 |
146
+ | `/state stats` | 実測の使用量,待ち時間,投影の件数,更新処理の稼働状態を表示 |
147
+ | `/state debug` | 最新の意味判定,質問 ID,元の確率値を表示 |
148
+ | `/state reset` | 確認後にリセットマーカーを追記し,作業用の状態をクリア |
149
+ | `/state projection on\|off` | 現在のランタイムのコンテキスト投影を切り替え |
150
+ | `/state jev on\|off` | 現在のランタイムの意味判定を切り替え |
151
+ | `REFLEX_STATE_DISABLE=1` | 状態の記録と投影を無効化 |
152
+ | `REFLEX_STATE_DISABLE_JEV=1` | 決定論的な更新だけで起動 |
153
+ | `REFLEX_STATE_PROJECTION=1` | 設定した mode の投影を有効化 |
154
+ | `REFLEX_STATE_PROJECTION=0` | Pi の元のコンテキストを使って起動 |
155
+
156
+ Jev のデフォルトは,SDK のタイムアウトが 3 秒,リトライなし,外側の処理期限が 4 秒です.
157
+ Pi のキャンセルとも連動します.3 回連続で失敗すると,サーキットブレーカーが 60 秒間リクエストを停止します.
158
+ その後の試行が成功すれば通常動作に戻ります.認証失敗時は,更新処理を作り直すまで無効になります.
159
+ 不確かな回答は意味判定による状態変更に適用しません.
160
+ 作業フェーズの質問は,必要な質問に添えて比較用に記録するだけで,作業フェーズを直接変更しません.
161
+
162
+ ## TypeSafe に送信するデータ
163
+
164
+ Jev が有効な場合,質問と必要な根拠を一つのリクエスト計画で選びます.長さを制限したユーザーの依頼文,
165
+ 現在の型付き状態,ファイルパス,関連する bash/edit/write の根拠,完了判定時のアシスタント最終応答を送信します.
166
+ JSON 入力の上限は 24,000 バイトです.根拠を収められない質問は,理由をローカルに記録して送信しません.
167
+ read/grep/find/ls の結果では Jev を呼び出さず,それらの出力文を他のリクエストに含めることもありません.
168
+
169
+ 一般的な認証情報のパターン,Bearer 認証情報,秘密鍵のブロック,環境変数への代入は,送信前にマスキングします.
170
+ これはパターンに基づく処理であり,あらゆる機密情報を検出できる保証はありません.
171
+ テストエラーなどのツール出力にソースコードが含まれる場合もあります.
172
+ ローカルの状態遷移記録は,サイズ制限付きの入力と抜粋を保持します.
173
+ 送信時のマスキングは,Pi の元の履歴を書き換えません.
174
+ SDK のログにはリクエストやレスポンスの内容を含めません.
175
+ 不正なレスポンスについて記録するのは,想定フィールドの型だけで,想定外の応答値は記録しません.
176
+
177
+ ## エクスポートと再生
178
+
179
+ 公開済みパッケージからは,リポジトリを clone せずに実行できます.
180
+
181
+ ```sh
182
+ npm exec --package=reflex-state@next -- reflex-state-export /path/to/session.jsonl --out trace-output
183
+ npm exec --package=reflex-state@next -- reflex-state-replay trace-output/events.jsonl --updater noop --out replay-output
184
+ ```
185
+
186
+ ローカルのチェックアウトから実行する場合は,次を使います.
187
+
188
+ ```sh
189
+ pnpm export-trace /path/to/session.jsonl --out trace-output
190
+ pnpm replay trace-output/events.jsonl --updater noop --out replay-output
191
+ pnpm replay trace-output/events.jsonl --updater recorded --out replay-recorded
192
+ ```
193
+
194
+ エクスポートは,最後に保存された末端エントリー,または `--leaf <entry-id>` で指定したエントリーまでの分岐をたどります.
195
+ ReflexState の状態遷移がある場合は,直近のリセット以降の元イベントと判定を取り出します.
196
+ 状態遷移がないセッションは,Pi のメッセージエントリーから正規化します.
197
+ 旧 v1 状態は v2 として偽装せず,legacy として扱います.
198
+ 元のファイルを変更せず,`events.jsonl`,`transitions.jsonl`,`trace_meta.json` を出力します.
199
+
200
+ replay は,`final_state.json`,`transitions.jsonl`,`metrics.json`,`summary.txt` を出力します.
201
+ `noop` は決定論的に処理し,`jev` は意味判定のために実 API を呼び出します.
202
+ `recorded` は同じディレクトリの `transitions.jsonl` を読み込みます.
203
+ 別の記録を使うには,`--updater recorded:/path/to/transitions.jsonl` を指定します.
204
+ イベントの一致を確認し,記録済みの設定,作業ディレクトリ,タイムスタンプ,判定を再利用して最終状態を再現します.
205
+ 判定ログが必要なため,ReflexState の記録がない Pi セッションだけでは recorded 再生はできません.
206
+
207
+ 通常の再生では,`--config <file>` と `--cwd <directory>` で設定と作業ディレクトリを指定できます.
208
+ 指定がなく,エクスポートしたメタデータがある場合は,そこに含まれる値を使います.
209
+ recorded モードでは,各状態遷移に記録された設定を使います.
210
+ 使用量が取得できない場合は推定せず,`/state stats` では `n/a` と表示し,JSON では省略します.
211
+ Jev の呼び出し回数は,失敗を含むクライアント呼び出しの回数であり,課金対象になった回数ではありません.
212
+
213
+ ## 検証と制限
214
+
215
+ ```sh
216
+ pnpm check
217
+ pnpm build
218
+ pnpm package:check
219
+ REFLEX_STATE_LIVE_JEV=1 pnpm exec vitest run src/typesafe/live_contract.test.ts
220
+ ```
221
+
222
+ 最後のコマンドは `TYPESAFE_API_KEY` を必要とし,実際の System One リクエストを 1 回送信します.
223
+ このテストはデフォルトではスキップします.
224
+ オフラインのテストでは,決定論的な動作,判定の適用条件,マスキング,時間制限,障害時の処理,
225
+ 投影,分岐,記録に基づく完全な再生を確認します.
226
+ Pi のスモークテストは,モデルへのネットワーク通信を無効にして,0.83.0 の実際のローダー,
227
+ 拡張ランナー,ローカルの write/edit/bash ツール,ディスク保存するセッションマネージャーを使います.
228
+ パスの明示指定と,信頼済みプロジェクトの `.pi/extensions` の自動検出の両方を確認します.
229
+
230
+ 現在の制限:
231
+
232
+ - `append` は履歴を削減しません.`current-run` は直前の終了済み run より古い run を省略することがあります.
233
+ どちらも run 内の履歴を削らず,トークン削減やタスク成功率の向上を実証したものではありません.
234
+ - bash は作業領域を変更する可能性がある操作として扱います.edit/write/file_change/bash,セッション再開,
235
+ 分岐切り替えの後は,検証結果が stale になります.自動で再検証は実行しません.
236
+ - 検証コマンドの検出は保守的なヒューリスティックであり,シェル構文の完全な解析ではありません.
237
+ 複合・切り詰め済み・完全性不明のコマンドは unknown となり,障害を解消できません.
238
+ - passed は対象コマンドの観測結果だけを示し,プロジェクト全体の検証や全 assertion の成功を意味しません.
239
+ コマンドと作業ディレクトリが違えば別の検証です.
240
+ - 旧 v1 状態は自動移行しません.状態更新と投影を停止し,確認付き `/state reset` で v2 の状態区間を開始します.
241
+ 旧形式の recorded replay は拒否されるため,厳密な再生には元の実装を使ってください.
242
+ - 閾値は未校正のヒューリスティックです.実際の Jev の出力は変動し得るため,
243
+ 意味判定を厳密に再現できるのは記録済みの判定を使う場合だけです.
244
+ - 状態ブロックと作業用イベント集合には上限があります.v0.1 には,過去の根拠を取り出す recall ツール,
245
+ 現在の実行内の履歴削減,ベンチマーク基盤,他のエージェント用のアダプターはありません.
246
+ - Pi は最初のアシスタントメッセージまで,新規セッションファイルの作成を遅延します.
247
+ ReflexState は Pi の追記 API を使うため,同じ保存動作に従います.
248
+ - TypeSafe の実リクエスト,プロバイダーを使った対話実行,Anthropic・OpenAI 互換・Google API を介した
249
+ 複数テキストブロックのツール結果の送受信は,ここでは未検証です.
250
+ プロバイダーが追加したツール結果ブロックを拒否する場合は,`projection.placement` を `run-start` にするか,
251
+ 投影を無効にしてください.`run-start` はプロンプトキャッシュの再利用を減らす可能性があります.
252
+
253
+ [ADR-0001](https://github.com/furedea/reflex-state/blob/main/docs/adr/0001_compose_adapters_at_entry_points.md) にアダプターの組み立て方針を記載しています.
@@ -0,0 +1,8 @@
1
+ import type { ReflexStateConfig } from "./core/config.js";
2
+ export declare function readConfigFile(path: string | undefined): Promise<ReflexStateConfig>;
3
+ export declare function traceMetadata(path: string): Promise<{
4
+ cwd?: string;
5
+ config?: ReflexStateConfig;
6
+ }>;
7
+ export declare function writeArtifacts(directory: string, artifacts: Readonly<Record<string, string>>, inputs: readonly string[]): Promise<void>;
8
+ export declare function jsonLines(values: readonly unknown[]): string;
package/dist/cli_io.js ADDED
@@ -0,0 +1,45 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { defaultConfig } from "./core/config.js";
4
+ import { mergeConfig } from "./core/config_validation.js";
5
+ import { isRecord } from "./core/serialization.js";
6
+ export async function readConfigFile(path) {
7
+ if (!path)
8
+ return defaultConfig();
9
+ const result = mergeConfig(defaultConfig(), JSON.parse(await readFile(path, "utf8")));
10
+ for (const warning of result.warnings)
11
+ console.error(warning);
12
+ return result.config;
13
+ }
14
+ export async function traceMetadata(path) {
15
+ try {
16
+ const value = JSON.parse(await readFile(path, "utf8"));
17
+ if (!isRecord(value))
18
+ throw new Error("Invalid trace metadata");
19
+ const result = value.config === undefined ? undefined : mergeConfig(defaultConfig(), value.config);
20
+ for (const warning of result?.warnings ?? [])
21
+ console.error(warning);
22
+ return {
23
+ ...(typeof value.cwd === "string" ? { cwd: value.cwd } : {}),
24
+ ...(result ? { config: result.config } : {}),
25
+ };
26
+ }
27
+ catch (error) {
28
+ if (isRecord(error) && error.code === "ENOENT")
29
+ return {};
30
+ throw new Error("Invalid trace metadata");
31
+ }
32
+ }
33
+ export async function writeArtifacts(directory, artifacts, inputs) {
34
+ const sources = new Set(inputs.map((path) => resolve(path)));
35
+ for (const name of Object.keys(artifacts)) {
36
+ if (sources.has(resolve(directory, name)))
37
+ throw new Error("Output would overwrite an input file");
38
+ }
39
+ await mkdir(directory, { recursive: true });
40
+ for (const [name, content] of Object.entries(artifacts))
41
+ await writeFile(join(directory, name), content);
42
+ }
43
+ export function jsonLines(values) {
44
+ return values.map((value) => JSON.stringify(value)).join("\n") + (values.length ? "\n" : "");
45
+ }
@@ -0,0 +1,3 @@
1
+ import type { ReflexStateConfig } from "./core/config.js";
2
+ import type { StateUpdater } from "./core/updater.js";
3
+ export declare function createUpdater(config: ReflexStateConfig, notify?: (message: string) => void): StateUpdater;
@@ -0,0 +1,8 @@
1
+ import { NoopStateUpdater } from "./core/updater.js";
2
+ import { createTypeSafeClient } from "./typesafe/client.js";
3
+ import { JevStateUpdater } from "./typesafe/updater.js";
4
+ export function createUpdater(config, notify) {
5
+ if (!config.jev.enabled)
6
+ return new NoopStateUpdater();
7
+ return new JevStateUpdater(createTypeSafeClient(config), notify ? { notify } : {});
8
+ }
@@ -0,0 +1,37 @@
1
+ import type { VerificationKind } from "./types.js";
2
+ export interface ReflexStateConfig {
3
+ readonly enabled: boolean;
4
+ readonly jev: {
5
+ readonly enabled: boolean;
6
+ readonly model: string;
7
+ readonly timeoutMs: number;
8
+ readonly maxRetries: number;
9
+ readonly deadlineMs: number;
10
+ readonly cooldownMs: number;
11
+ };
12
+ readonly thresholds: {
13
+ readonly noulAccept: number;
14
+ readonly noulReject: number;
15
+ readonly minChoiceConfidence: number;
16
+ readonly minChoiceMargin: number;
17
+ };
18
+ readonly limits: {
19
+ readonly maxWorkingSetEvents: number;
20
+ /** @deprecated Use maxProjectedBlockers. */
21
+ readonly maxActiveBlockers: number;
22
+ readonly maxProjectedBlockers: number;
23
+ readonly maxExcerptHeadChars: number;
24
+ readonly maxExcerptTailChars: number;
25
+ readonly maxPromptChars: number;
26
+ readonly maxRecentUserPrompts: number;
27
+ readonly maxStateBlockChars: number;
28
+ };
29
+ readonly projection: {
30
+ readonly enabled: boolean;
31
+ readonly mode: "append" | "current-run";
32
+ readonly placement: "last-message" | "run-start";
33
+ };
34
+ readonly shadowQuestions: readonly "phase"[];
35
+ readonly verificationCommands: Readonly<Record<VerificationKind, readonly string[]>>;
36
+ }
37
+ export declare function defaultConfig(): ReflexStateConfig;