phronomy 0.2.2 → 0.3.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -30
  3. data/README.md +26 -110
  4. data/lib/phronomy/agent/base.rb +127 -54
  5. data/lib/phronomy/agent/checkpoint.rb +53 -0
  6. data/lib/phronomy/agent/react_agent.rb +18 -28
  7. data/lib/phronomy/agent/suspend_signal.rb +35 -0
  8. data/lib/phronomy/agent.rb +2 -1
  9. data/lib/phronomy/configuration.rb +0 -24
  10. data/lib/phronomy/guardrail/builtin/pii_pattern_detector.rb +10 -27
  11. data/lib/phronomy/railtie.rb +0 -6
  12. data/lib/phronomy/ruby_llm_patches.rb +20 -0
  13. data/lib/phronomy/tool/mcp_tool.rb +23 -26
  14. data/lib/phronomy/tracing/langfuse_tracer.rb +3 -6
  15. data/lib/phronomy/trust_pipeline.rb +1 -2
  16. data/lib/phronomy/vector_store/redis_search.rb +4 -4
  17. data/lib/phronomy/version.rb +1 -1
  18. data/lib/phronomy/workflow.rb +4 -7
  19. data/lib/phronomy/workflow_runner.rb +1 -8
  20. data/lib/phronomy.rb +1 -0
  21. data/scripts/check_readme_ruby.rb +38 -0
  22. metadata +5 -33
  23. data/docs/trustworthy_ai_enhancements.md +0 -332
  24. data/lib/phronomy/active_record/acts_as.rb +0 -48
  25. data/lib/phronomy/active_record/checkpoint.rb +0 -20
  26. data/lib/phronomy/active_record/extensions.rb +0 -14
  27. data/lib/phronomy/active_record/message.rb +0 -20
  28. data/lib/phronomy/actor.rb +0 -68
  29. data/lib/phronomy/memory/compression/base.rb +0 -37
  30. data/lib/phronomy/memory/compression/summary.rb +0 -107
  31. data/lib/phronomy/memory/compression/tool_output_pruner.rb +0 -67
  32. data/lib/phronomy/memory/compression.rb +0 -11
  33. data/lib/phronomy/memory/conversation_manager.rb +0 -213
  34. data/lib/phronomy/memory/retrieval/base.rb +0 -22
  35. data/lib/phronomy/memory/retrieval/composite.rb +0 -76
  36. data/lib/phronomy/memory/retrieval/recent.rb +0 -35
  37. data/lib/phronomy/memory/retrieval/semantic.rb +0 -114
  38. data/lib/phronomy/memory/retrieval.rb +0 -12
  39. data/lib/phronomy/memory/storage/active_record.rb +0 -248
  40. data/lib/phronomy/memory/storage/base.rb +0 -155
  41. data/lib/phronomy/memory/storage/in_memory.rb +0 -152
  42. data/lib/phronomy/memory/storage.rb +0 -11
  43. data/lib/phronomy/memory.rb +0 -21
  44. data/lib/phronomy/rails/agent_job.rb +0 -75
  45. data/lib/phronomy/state_store/active_record.rb +0 -76
  46. data/lib/phronomy/state_store/base.rb +0 -112
  47. data/lib/phronomy/state_store/encryptor/active_support.rb +0 -49
  48. data/lib/phronomy/state_store/encryptor/base.rb +0 -34
  49. data/lib/phronomy/state_store/encryptor.rb +0 -16
  50. data/lib/phronomy/state_store/file.rb +0 -85
  51. data/lib/phronomy/state_store/in_memory.rb +0 -53
  52. data/lib/phronomy/state_store/redis.rb +0 -70
  53. data/lib/phronomy/state_store.rb +0 -9
  54. data/lib/phronomy/thread_actor_registry.rb +0 -85
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phronomy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Raizo T.C.S
@@ -65,23 +65,19 @@ files:
65
65
  - CHANGELOG.md
66
66
  - README.md
67
67
  - Rakefile
68
- - docs/trustworthy_ai_enhancements.md
69
68
  - lib/generators/phronomy/install/install_generator.rb
70
69
  - lib/generators/phronomy/install/templates/create_phronomy_messages.rb.tt
71
70
  - lib/generators/phronomy/install/templates/initializer.rb.tt
72
71
  - lib/generators/phronomy/install/templates/message_model.rb.tt
73
72
  - lib/phronomy.rb
74
- - lib/phronomy/active_record/acts_as.rb
75
- - lib/phronomy/active_record/checkpoint.rb
76
- - lib/phronomy/active_record/extensions.rb
77
- - lib/phronomy/active_record/message.rb
78
- - lib/phronomy/actor.rb
79
73
  - lib/phronomy/agent.rb
80
74
  - lib/phronomy/agent/base.rb
81
75
  - lib/phronomy/agent/before_completion_context.rb
76
+ - lib/phronomy/agent/checkpoint.rb
82
77
  - lib/phronomy/agent/handoff.rb
83
78
  - lib/phronomy/agent/react_agent.rb
84
79
  - lib/phronomy/agent/runner.rb
80
+ - lib/phronomy/agent/suspend_signal.rb
85
81
  - lib/phronomy/configuration.rb
86
82
  - lib/phronomy/context.rb
87
83
  - lib/phronomy/context/assembler.rb
@@ -124,43 +120,18 @@ files:
124
120
  - lib/phronomy/loader/csv_loader.rb
125
121
  - lib/phronomy/loader/markdown_loader.rb
126
122
  - lib/phronomy/loader/plain_text_loader.rb
127
- - lib/phronomy/memory.rb
128
- - lib/phronomy/memory/compression.rb
129
- - lib/phronomy/memory/compression/base.rb
130
- - lib/phronomy/memory/compression/summary.rb
131
- - lib/phronomy/memory/compression/tool_output_pruner.rb
132
- - lib/phronomy/memory/conversation_manager.rb
133
- - lib/phronomy/memory/retrieval.rb
134
- - lib/phronomy/memory/retrieval/base.rb
135
- - lib/phronomy/memory/retrieval/composite.rb
136
- - lib/phronomy/memory/retrieval/recent.rb
137
- - lib/phronomy/memory/retrieval/semantic.rb
138
- - lib/phronomy/memory/storage.rb
139
- - lib/phronomy/memory/storage/active_record.rb
140
- - lib/phronomy/memory/storage/base.rb
141
- - lib/phronomy/memory/storage/in_memory.rb
142
123
  - lib/phronomy/output_parser.rb
143
124
  - lib/phronomy/output_parser/base.rb
144
125
  - lib/phronomy/output_parser/json_parser.rb
145
126
  - lib/phronomy/output_parser/structured_parser.rb
146
127
  - lib/phronomy/prompt_template.rb
147
- - lib/phronomy/rails/agent_job.rb
148
128
  - lib/phronomy/railtie.rb
129
+ - lib/phronomy/ruby_llm_patches.rb
149
130
  - lib/phronomy/runnable.rb
150
131
  - lib/phronomy/splitter.rb
151
132
  - lib/phronomy/splitter/base.rb
152
133
  - lib/phronomy/splitter/fixed_size_splitter.rb
153
134
  - lib/phronomy/splitter/recursive_splitter.rb
154
- - lib/phronomy/state_store.rb
155
- - lib/phronomy/state_store/active_record.rb
156
- - lib/phronomy/state_store/base.rb
157
- - lib/phronomy/state_store/encryptor.rb
158
- - lib/phronomy/state_store/encryptor/active_support.rb
159
- - lib/phronomy/state_store/encryptor/base.rb
160
- - lib/phronomy/state_store/file.rb
161
- - lib/phronomy/state_store/in_memory.rb
162
- - lib/phronomy/state_store/redis.rb
163
- - lib/phronomy/thread_actor_registry.rb
164
135
  - lib/phronomy/token_usage.rb
165
136
  - lib/phronomy/tool.rb
166
137
  - lib/phronomy/tool/agent_tool.rb
@@ -181,6 +152,7 @@ files:
181
152
  - lib/phronomy/workflow.rb
182
153
  - lib/phronomy/workflow_context.rb
183
154
  - lib/phronomy/workflow_runner.rb
155
+ - scripts/check_readme_ruby.rb
184
156
  - sig/phronomy.rbs
185
157
  homepage: https://github.com/Raizo-TCS/phronomy
186
158
  licenses:
@@ -1,332 +0,0 @@
1
- # Trustworthy AI Enhancements
2
-
3
- Specification for features that address the NIST AI Risk Management Framework (AI RMF 1.0)
4
- trustworthiness characteristics, as applied to the phronomy gem.
5
-
6
- Reference: NIST AI 100-1 — https://doi.org/10.6028/NIST.AI.100-1
7
- Japanese translation: https://aisi.go.jp/assets/pdf/NIST_AI_RMF_jp_20240806.pdf
8
-
9
- ---
10
-
11
- ## Responsibility Model
12
-
13
- Three layers share responsibility for trustworthy AI:
14
-
15
- ```
16
- ┌─────────────────────────────────────────┐
17
- │ Application Domain logic / UX │
18
- ├─────────────────────────────────────────┤
19
- │ phronomy Control flow / observation / boundary enforcement │
20
- ├─────────────────────────────────────────┤
21
- │ LLM Probabilistic reasoning / generation │
22
- └─────────────────────────────────────────┘
23
- ```
24
-
25
- Key principle: **the LLM is untrusted**. phronomy acts as the deterministic control
26
- layer that validates, constrains, and observes LLM behaviour. Characteristics that
27
- cannot be delegated to the LLM must be enforced by phronomy or the application layer.
28
-
29
- ---
30
-
31
- ## Trustworthiness Characteristics — Status and Plan
32
-
33
- ### 3.1 Valid and Reliable
34
-
35
- | Layer | Responsibility | Status |
36
- |---|---|---|
37
- | LLM | Base reasoning capability | Model-dependent |
38
- | **phronomy** | Eval infrastructure, output type validation | ✅ `Eval::Runner`, `Eval::Dataset`, `Eval::Metrics` — see `lib/phronomy/eval/` |
39
- | **phronomy** | Drift / accuracy monitoring hooks | ❌ Not implemented — **planned** |
40
- | Application | Test-case design, accuracy thresholds | Application responsibility |
41
-
42
- **Planned work:**
43
- - None in this iteration. `Eval` infrastructure is sufficient for current needs.
44
-
45
- ---
46
-
47
- ### 3.2 Safe
48
-
49
- | Layer | Responsibility | Status |
50
- |---|---|---|
51
- | LLM | Basic harmful-content avoidance (RLHF) | Model-dependent, not guaranteed |
52
- | **phronomy** | Intervention points, iteration limits, approval gates | ✅ `wait_state`/`send_event`, `requires_approval`, `max_iterations` — see `lib/phronomy/workflow.rb`, `lib/phronomy/agent/base.rb` |
53
- | **phronomy** | Built-in guardrails (PII, prompt injection) | ❌ Not implemented — **planned (Feature A)** |
54
- | Application | Concrete guardrail logic, approval workflows | Application responsibility |
55
-
56
- ---
57
-
58
- ### 3.3 Secure and Resilient
59
-
60
- | Layer | Responsibility | Status |
61
- |---|---|---|
62
- | LLM | Partial prompt-injection resistance | Model-dependent, partial |
63
- | **phronomy** | State persistence across process restarts | ✅ `StateStore::ActiveRecord` — see `lib/phronomy/state_store/` |
64
- | **phronomy** | Encrypted state store adapter interface | ❌ Not implemented — **planned (Feature C)** |
65
- | Application | Authentication / authorisation / infrastructure encryption | Application / infrastructure responsibility |
66
-
67
- ---
68
-
69
- ### 3.4 Accountable and Transparent
70
-
71
- | Layer | Responsibility | Status |
72
- |---|---|---|
73
- | LLM | Token usage reporting | ✅ `TokenUsage` — see `lib/phronomy/token_usage.rb` |
74
- | **phronomy** | Tracing / span recording | ✅ `Tracing::LangfuseTracer`, `OpenTelemetryTracer` — see `lib/phronomy/tracing/` |
75
- | **phronomy** | Caller identity propagation to tracers | ❌ Not implemented — **planned (Feature B)** |
76
- | Application | User-facing AI disclosure, business audit requirements | Application responsibility |
77
-
78
- ---
79
-
80
- ### 3.5 Explainable and Interpretable
81
-
82
- | Layer | Responsibility | Status |
83
- |---|---|---|
84
- | LLM | Chain-of-thought generation | Prompt-dependent |
85
- | **phronomy** | Processing step recording via Graph and Tracing | ✅ Partial — `Workflow`/`WorkflowRunner`, `Tracing` |
86
- | Application | Explanation UI, CoT prompt design | Application responsibility |
87
-
88
- **Planned work:** None in this iteration.
89
-
90
- ---
91
-
92
- ### 3.6 Privacy-Enhanced
93
-
94
- | Layer | Responsibility | Status |
95
- |---|---|---|
96
- | LLM | Training data handling | Provider responsibility |
97
- | **phronomy** | Memory compression (data minimisation) | ✅ `Memory::Compression` — see `lib/phronomy/memory/compression/` |
98
- | **phronomy** | Built-in PII detection guardrail | ❌ Not implemented — **planned (Feature A)** |
99
- | **phronomy** | TTL and explicit purge API on ConversationManager | ❌ Not implemented — **planned (Feature D)** |
100
- | Application | Privacy policy, user consent management | Application responsibility |
101
-
102
- ---
103
-
104
- ### 3.7 Fair — with Harmful Bias Managed
105
-
106
- | Layer | Responsibility | Status |
107
- |---|---|---|
108
- | LLM | Bias reduction via RLHF | Provider responsibility |
109
- | **phronomy** | Eval infrastructure for custom metrics | ✅ `Eval::Metrics` — extensible |
110
- | Application | Fairness test-set design, threshold definition | Application responsibility |
111
-
112
- **Planned work:** None in this iteration. The existing `Eval::Metrics` extension
113
- point is sufficient; fairness metrics are domain-specific and belong to the
114
- application layer.
115
-
116
- ---
117
-
118
- ## Planned Features (This Branch)
119
-
120
- ### Feature A — `Phronomy::Guardrail::Builtin` module
121
-
122
- **Addresses:** 3.2 Safe, 3.6 Privacy-Enhanced
123
-
124
- **Motivation:**
125
- Prompt injection and PII leakage are the two most common, high-severity risks for
126
- any LLM application. They require deterministic, regex/heuristic-based detection
127
- that the LLM cannot reliably provide. phronomy should ship sensible defaults so
128
- that applications do not have to re-implement these from scratch.
129
-
130
- **Design:**
131
- - New module: `Phronomy::Guardrail::Builtin`
132
- - Two concrete classes, both under `lib/phronomy/guardrail/builtin/`:
133
- - `PromptInjectionDetector < InputGuardrail`
134
- - `PIIPatternDetector < InputGuardrail`
135
- - Existing base classes (`InputGuardrail`, `OutputGuardrail`) are unchanged — see
136
- `lib/phronomy/guardrail/input_guardrail.rb` and `output_guardrail.rb`.
137
-
138
- **`PromptInjectionDetector`:**
139
- - Detects common prompt-injection patterns in input strings:
140
- - "ignore previous instructions", "disregard all prior", "system prompt:" prefixes,
141
- jailbreak keywords, role-switch attempts.
142
- - Pattern list is configurable via constructor argument `additional_patterns: []`.
143
- - Raises `GuardrailError` with message `"Potential prompt injection detected"`.
144
-
145
- **`PIIPatternDetector`:**
146
- - Detects common Japanese and international PII patterns:
147
- - Japanese My Number (12-digit number): `/\b\d{4}[- ]?\d{4}[- ]?\d{4}\b/`
148
- - Credit card numbers: `/\b(?:\d{4}[- ]?){3}\d{4}\b/`
149
- - Email addresses: standard RFC 5322 simplified pattern
150
- - Phone numbers (JP): `/\b0\d{1,4}[- ]?\d{1,4}[- ]?\d{4}\b/`
151
- - Each pattern category is independently togglable via constructor:
152
- `PIIPatternDetector.new(detect: [:my_number, :credit_card, :email, :phone])`
153
- - Default: all four categories active.
154
- - Raises `GuardrailError` with message `"PII detected in input: <category>"`.
155
-
156
- **Usage example:**
157
- ```ruby
158
- agent = MyAgent.new
159
- agent.add_input_guardrail(Phronomy::Guardrail::Builtin::PromptInjectionDetector.new)
160
- agent.add_input_guardrail(Phronomy::Guardrail::Builtin::PIIPatternDetector.new(detect: [:my_number, :credit_card]))
161
- ```
162
-
163
- **Files to create:**
164
- - `lib/phronomy/guardrail/builtin/prompt_injection_detector.rb`
165
- - `lib/phronomy/guardrail/builtin/pii_pattern_detector.rb`
166
- - `lib/phronomy/guardrail/builtin.rb` (requires both, defines module)
167
- - Update `lib/phronomy/guardrail.rb` to require `builtin`
168
-
169
- **Tests:**
170
- - Unit: `spec/phronomy/guardrail/builtin/prompt_injection_detector_spec.rb`
171
- - Unit: `spec/phronomy/guardrail/builtin/pii_pattern_detector_spec.rb`
172
- - Integration: extend `spec/integration/tool_guardrail_spec.rb` with builtin guardrail factors
173
-
174
- ---
175
-
176
- ### Feature B — Caller identity propagation in `config:`
177
-
178
- **Addresses:** 3.4 Accountable and Transparent
179
-
180
- **Motivation:**
181
- `Tracing` already records what happened (spans, token usage). What is missing is
182
- **who** triggered the action. Without a caller identity, audit logs cannot be
183
- attributed to users or sessions, which is a requirement for accountability under
184
- NIST AI RMF 3.4.
185
-
186
- **Design:**
187
- - `Agent::Base#invoke` and `WorkflowRunner#invoke` already accept `config: {}` — see
188
- `lib/phronomy/agent/base.rb` and `lib/phronomy/graph/workflow_runner.rb`.
189
- - Add two new optional keys to `config:`:
190
- - `user_id:` (String | nil) — caller identity
191
- - `session_id:` (String | nil) — session / request identity
192
- - Both are extracted in `invoke_once` / `call` and forwarded to
193
- `Tracing::Base#start_span` as span attributes.
194
- - `Tracing::Base#start_span` already accepts `**attributes` — no signature change needed.
195
- - `LangfuseTracer` and `OpenTelemetryTracer` will automatically forward them as
196
- metadata/attributes respectively.
197
-
198
- **Usage example:**
199
- ```ruby
200
- agent.invoke("What is the weather?", config: {
201
- thread_id: "conv-123",
202
- user_id: "user-42",
203
- session_id: "sess-abc"
204
- })
205
- ```
206
-
207
- **Files to modify:**
208
- - `lib/phronomy/agent/base.rb` — extract `user_id` and `session_id` from config, pass to tracer
209
- - `lib/phronomy/graph/compiled_graph.rb` — same for graph invocations
210
-
211
- **Tests:**
212
- - Unit: extend `spec/phronomy/agent_spec.rb` with `user_id`/`session_id` forwarding assertions
213
- - Unit: extend `spec/phronomy/tracing/langfuse_tracer_spec.rb` with attribute forwarding
214
-
215
- ---
216
-
217
- ### Feature C — `StateStore` encryption adapter interface
218
-
219
- **Addresses:** 3.3 Secure and Resilient
220
-
221
- **Motivation:**
222
- `StateStore::ActiveRecord` persists conversation state as plain-text JSON. In
223
- regulated environments (healthcare, finance, government) this violates data-at-rest
224
- requirements. phronomy should define a standard interface so that an encryption
225
- adapter can be layered transparently without modifying `StateStore::ActiveRecord`.
226
-
227
- **Design:**
228
- - New abstract class: `Phronomy::StateStore::Encryptor::Base`
229
- - `encrypt(plaintext) → ciphertext` (abstract)
230
- - `decrypt(ciphertext) → plaintext` (abstract)
231
- - New concrete class: `Phronomy::StateStore::Encryptor::ActiveSupport`
232
- - Delegates to `ActiveSupport::MessageEncryptor` when available.
233
- - Constructor: `ActiveSupport.new(secret_key_base:, cipher: "aes-256-gcm")`
234
- - `StateStore::ActiveRecord` accepts an optional `encryptor:` constructor argument:
235
- - When present, `serialize_state` output is passed through `encryptor.encrypt`
236
- before writing to the DB, and `encryptor.decrypt` before `deserialize_state`.
237
- - When absent, behaviour is unchanged (backwards compatible).
238
-
239
- **Usage example:**
240
- ```ruby
241
- encryptor = Phronomy::StateStore::Encryptor::ActiveSupport.new(
242
- secret_key_base: ENV.fetch("SECRET_KEY_BASE")
243
- )
244
- store = Phronomy::StateStore::ActiveRecord.new(
245
- model_class: PhronomyStateRecord,
246
- encryptor: encryptor
247
- )
248
- ```
249
-
250
- **Files to create:**
251
- - `lib/phronomy/state_store/encryptor/base.rb`
252
- - `lib/phronomy/state_store/encryptor/active_support.rb`
253
- - `lib/phronomy/state_store/encryptor.rb`
254
-
255
- **Files to modify:**
256
- - `lib/phronomy/state_store/active_record.rb` — accept `encryptor:`, apply in save/load
257
- - `lib/phronomy/state_store.rb` — require `encryptor`
258
-
259
- **Tests:**
260
- - Unit: `spec/phronomy/state_store/encryptor/base_spec.rb`
261
- - Unit: `spec/phronomy/state_store/encryptor/active_support_spec.rb`
262
- - Unit: extend `spec/phronomy/state_store_spec.rb` with encrypted save/load round-trip
263
-
264
- ---
265
-
266
- ### Feature D — TTL and `purge` API on `ConversationManager`
267
-
268
- **Addresses:** 3.6 Privacy-Enhanced
269
-
270
- **Motivation:**
271
- Users have a right to be forgotten. `ConversationManager` currently has no way to
272
- delete stored messages for a given thread, nor does it enforce data retention limits.
273
-
274
- **Design:**
275
- - `ConversationManager#purge(thread_id:)` — deletes all stored messages for the
276
- thread from both the storage backend and the retrieval index.
277
- - Optional `ttl:` constructor argument (Integer seconds | nil):
278
- - When set, messages older than `ttl` seconds are filtered out on `load_messages`.
279
- - Storage backends that support native TTL (e.g. Redis) should be informed via
280
- a `Storage::Base#purge_older_than(thread_id:, older_than:)` hook.
281
- - Default: `nil` (no expiry — current behaviour unchanged).
282
- - `Storage::ActiveRecord` gains a `purge_older_than` implementation using
283
- `where("created_at < ?", Time.now - ttl).destroy_all`.
284
-
285
- **Usage example:**
286
- ```ruby
287
- memory = Phronomy::Memory::ConversationManager.new(
288
- storage: Phronomy::Memory::Storage::ActiveRecord.new(model_class: PhronomyMessageRecord),
289
- ttl: 60 * 60 * 24 * 30 # 30 days
290
- )
291
- # Later:
292
- memory.purge(thread_id: "conv-123")
293
- ```
294
-
295
- **Files to modify:**
296
- - `lib/phronomy/memory/conversation_manager.rb` — add `purge`, accept `ttl:`
297
- - `lib/phronomy/memory/storage/base.rb` — add `purge(thread_id:)` abstract method, `purge_older_than` hook
298
- - `lib/phronomy/memory/storage/active_record.rb` — implement both
299
- - `lib/phronomy/memory/storage/in_memory.rb` — implement both
300
-
301
- **Tests:**
302
- - Unit: extend `spec/phronomy/memory_spec.rb` with `purge` and TTL filtering tests
303
- - Unit: extend `spec/phronomy/active_record/message_spec.rb` with `purge_older_than`
304
-
305
- ---
306
-
307
- ## Implementation Order
308
-
309
- | Step | Feature | Rationale |
310
- |---|---|---|
311
- | 1 | Feature A — BuiltinGuardrails | Self-contained, no dependencies, highest safety impact |
312
- | 2 | Feature B — Caller identity | Small change, high accountability value |
313
- | 3 | Feature C — Encryptor I/F | More complex, depends on no other feature |
314
- | 4 | Feature D — TTL / purge | Touches storage layer, do last to avoid churn |
315
-
316
- Each feature follows the same workflow:
317
- 1. Implement source files
318
- 2. Run StandardRB on new files
319
- 3. Run unit tests: `bundle exec rspec <spec_file>`
320
- 4. Run full unit suite: `bundle exec rspec --format progress`
321
- 5. Run integration suite: `bundle exec rspec --tag integration --format progress`
322
- 6. Present diff for commit approval
323
-
324
- ---
325
-
326
- ## Out of Scope (This Branch)
327
-
328
- - Fairness metrics / demographic parity (`Eval::Metrics` extension) — domain-specific,
329
- belongs to application layer
330
- - Kill switch / forced shutdown — infrastructure concern
331
- - Differential privacy — academic/research topic, not yet practical for gem scope
332
- - Authentication / authorisation — application / infrastructure concern
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module ActiveRecord
5
- # DSL mixin that can be included in ApplicationRecord subclasses
6
- # to declaratively associate them with Phronomy persistence roles.
7
- #
8
- # @example
9
- # class PhronomyMessage < ApplicationRecord
10
- # acts_as_phronomy_message
11
- # end
12
- module ActsAs
13
- def self.included(base)
14
- base.extend(ClassMethods)
15
- end
16
-
17
- module ClassMethods
18
- # Configures this model as a Phronomy checkpoint store.
19
- # Applies validations and exposes a convenience checkpointer factory.
20
- #
21
- # @param encryptor [StateStore::Encryptor::Base, nil] optional encryptor
22
- # for encrypting +state_json+ at rest.
23
- def acts_as_phronomy_checkpoint(encryptor: nil)
24
- include ::Phronomy::ActiveRecord::Checkpoint
25
-
26
- define_singleton_method(:phronomy_checkpointer) do |enc: encryptor|
27
- ::Phronomy::StateStore::ActiveRecord.new(model_class: self, encryptor: enc)
28
- end
29
- end
30
-
31
- # Configures this model as a Phronomy message store.
32
- # Applies validations and exposes a convenience factory method.
33
- #
34
- # @return [Phronomy::Memory::ConversationManager] a ready-to-use memory object
35
- def acts_as_phronomy_message
36
- include ::Phronomy::ActiveRecord::Message
37
-
38
- define_singleton_method(:phronomy_memory) do
39
- ::Phronomy::Memory::ConversationManager.new(
40
- storage: ::Phronomy::Memory::Storage::ActiveRecord.new(model_class: self),
41
- retrieval: ::Phronomy::Memory::Retrieval::Recent.new
42
- )
43
- end
44
- end
45
- end
46
- end
47
- end
48
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module ActiveRecord
5
- # Mixin for models backed by the phronomy_checkpoints table.
6
- # Validates the required columns and exposes a convenience factory.
7
- #
8
- # @example
9
- # class PhronomyCheckpoint < ApplicationRecord
10
- # include Phronomy::ActiveRecord::ActsAs
11
- # acts_as_phronomy_checkpoint
12
- # end
13
- module Checkpoint
14
- def self.included(base)
15
- base.validates :thread_id, presence: true
16
- base.validates :state_json, presence: true
17
- end
18
- end
19
- end
20
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module ActiveRecord
5
- # Convenience namespace loaded by Railtie when ActiveRecord is available.
6
- # Ensures all Phronomy::ActiveRecord mixins are eager-loaded in Rails context.
7
- module Extensions
8
- end
9
- end
10
- end
11
-
12
- require_relative "checkpoint"
13
- require_relative "message"
14
- require_relative "acts_as"
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module ActiveRecord
5
- # Mixin for models backed by the phronomy_messages table.
6
- #
7
- # @example
8
- # class PhronomyMessage < ApplicationRecord
9
- # include Phronomy::ActiveRecord::Message
10
- # end
11
- module Message
12
- def self.included(base)
13
- base.validates :thread_id, presence: true
14
- base.validates :role, presence: true
15
- # content may be blank for assistant messages that carry only tool calls
16
- base.validates :content, presence: false, allow_nil: true
17
- end
18
- end
19
- end
20
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- # Lightweight synchronous actor backed by a dedicated +Thread+ and a +Queue+.
5
- #
6
- # A caller submits work via {#call}, which blocks until the actor's thread
7
- # finishes executing the block and then returns the result (or re-raises any
8
- # exception that occurred inside the actor).
9
- #
10
- # === Reentrant safety
11
- #
12
- # If {#call} is invoked from within the actor's own thread (i.e. from inside
13
- # a block that is already executing on this actor), the block is executed
14
- # directly in the current thread instead of being pushed onto the queue.
15
- # This prevents deadlocks in deeply nested call paths without requiring
16
- # callers to track whether they are already "inside" the actor.
17
- #
18
- # === Usage
19
- #
20
- # actor = Phronomy::Actor.new
21
- # result = actor.call { expensive_operation() } # blocks caller; runs on actor thread
22
- # actor.stop # graceful shutdown
23
- class Actor
24
- def initialize
25
- @queue = Queue.new
26
- @thread = Thread.new do
27
- loop do
28
- task = @queue.pop
29
- break if task == :stop
30
- task.call
31
- end
32
- end
33
- end
34
-
35
- # Run +block+ on the actor's thread and return its result.
36
- #
37
- # If the current thread is already the actor's thread (reentrant call),
38
- # the block is executed inline to prevent deadlocks.
39
- #
40
- # Any exception raised inside the block is captured and re-raised in the
41
- # calling thread.
42
- #
43
- # @yield block to execute on the actor's thread
44
- # @return the return value of the block
45
- def call(&block)
46
- return block.call if Thread.current == @thread
47
-
48
- done = Queue.new
49
- @queue.push(-> {
50
- begin
51
- done.push([true, block.call])
52
- rescue => e
53
- done.push([false, e])
54
- end
55
- })
56
- success, value = done.pop
57
- raise value unless success
58
- value
59
- end
60
-
61
- # Send a +:stop+ sentinel to gracefully terminate the actor's thread.
62
- # Pending tasks already in the queue will still be processed before
63
- # the thread exits.
64
- def stop
65
- @queue.push(:stop)
66
- end
67
- end
68
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Memory
5
- module Compression
6
- # Abstract base class for compression strategies.
7
- #
8
- # Two kinds of compression exist:
9
- #
10
- # * Content pruning (e.g. ToolOutputPruner) — modifies individual message
11
- # content in-place (e.g. truncates oversized tool outputs). The original
12
- # data loss is limited and intentional (tool outputs are auxiliary).
13
- # These subclasses return { messages: Array, compaction: nil }.
14
- #
15
- # * Compaction (e.g. Summary) — replaces multiple messages with an LLM
16
- # summary. Originals are preserved in Storage via a compaction record.
17
- # These subclasses return { messages: Array, compaction: Hash } where
18
- # compaction is { start_seq:, end_seq:, summary_text: }.
19
- #
20
- # ConversationManager inspects the :compaction key and persists the record
21
- # in Storage when present.
22
- #
23
- # @abstract Subclass and implement #compress.
24
- class Base
25
- # Compress a message array and return a result hash.
26
- #
27
- # @param thread_id [String] thread identifier (used by stateful compressors)
28
- # @param messages [Array] message history to compress
29
- # @param seq_offset [Integer] seq number of messages[0] in the raw history
30
- # @return [Hash] { messages: Array, compaction: Hash|nil }
31
- def compress(thread_id:, messages:, seq_offset: 0)
32
- raise NotImplementedError, "#{self.class}#compress is not implemented"
33
- end
34
- end
35
- end
36
- end
37
- end