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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +88 -30
- data/README.md +26 -110
- data/lib/phronomy/agent/base.rb +127 -54
- data/lib/phronomy/agent/checkpoint.rb +53 -0
- data/lib/phronomy/agent/react_agent.rb +18 -28
- data/lib/phronomy/agent/suspend_signal.rb +35 -0
- data/lib/phronomy/agent.rb +2 -1
- data/lib/phronomy/configuration.rb +0 -24
- data/lib/phronomy/guardrail/builtin/pii_pattern_detector.rb +10 -27
- data/lib/phronomy/railtie.rb +0 -6
- data/lib/phronomy/ruby_llm_patches.rb +20 -0
- data/lib/phronomy/tool/mcp_tool.rb +23 -26
- data/lib/phronomy/tracing/langfuse_tracer.rb +3 -6
- data/lib/phronomy/trust_pipeline.rb +1 -2
- data/lib/phronomy/vector_store/redis_search.rb +4 -4
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow.rb +4 -7
- data/lib/phronomy/workflow_runner.rb +1 -8
- data/lib/phronomy.rb +1 -0
- data/scripts/check_readme_ruby.rb +38 -0
- metadata +5 -33
- data/docs/trustworthy_ai_enhancements.md +0 -332
- data/lib/phronomy/active_record/acts_as.rb +0 -48
- data/lib/phronomy/active_record/checkpoint.rb +0 -20
- data/lib/phronomy/active_record/extensions.rb +0 -14
- data/lib/phronomy/active_record/message.rb +0 -20
- data/lib/phronomy/actor.rb +0 -68
- data/lib/phronomy/memory/compression/base.rb +0 -37
- data/lib/phronomy/memory/compression/summary.rb +0 -107
- data/lib/phronomy/memory/compression/tool_output_pruner.rb +0 -67
- data/lib/phronomy/memory/compression.rb +0 -11
- data/lib/phronomy/memory/conversation_manager.rb +0 -213
- data/lib/phronomy/memory/retrieval/base.rb +0 -22
- data/lib/phronomy/memory/retrieval/composite.rb +0 -76
- data/lib/phronomy/memory/retrieval/recent.rb +0 -35
- data/lib/phronomy/memory/retrieval/semantic.rb +0 -114
- data/lib/phronomy/memory/retrieval.rb +0 -12
- data/lib/phronomy/memory/storage/active_record.rb +0 -248
- data/lib/phronomy/memory/storage/base.rb +0 -155
- data/lib/phronomy/memory/storage/in_memory.rb +0 -152
- data/lib/phronomy/memory/storage.rb +0 -11
- data/lib/phronomy/memory.rb +0 -21
- data/lib/phronomy/rails/agent_job.rb +0 -75
- data/lib/phronomy/state_store/active_record.rb +0 -76
- data/lib/phronomy/state_store/base.rb +0 -112
- data/lib/phronomy/state_store/encryptor/active_support.rb +0 -49
- data/lib/phronomy/state_store/encryptor/base.rb +0 -34
- data/lib/phronomy/state_store/encryptor.rb +0 -16
- data/lib/phronomy/state_store/file.rb +0 -85
- data/lib/phronomy/state_store/in_memory.rb +0 -53
- data/lib/phronomy/state_store/redis.rb +0 -70
- data/lib/phronomy/state_store.rb +0 -9
- 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.
|
|
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
|
data/lib/phronomy/actor.rb
DELETED
|
@@ -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
|