insika 0.2.0 → 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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +145 -17
  3. data/README.md +7 -1
  4. data/bin/insika +211 -8
  5. data/docs/AGENTS.md +377 -2
  6. data/docs/API.md +56 -0
  7. data/docs/CHANNELS.md +99 -9
  8. data/docs/CONTEXT.md +127 -16
  9. data/docs/DEPLOY.md +5 -2
  10. data/docs/EVALS.md +19 -0
  11. data/docs/FACTS.md +135 -0
  12. data/docs/HARVEST.md +117 -0
  13. data/docs/LOADTEST.md +14 -8
  14. data/docs/OBSERVABILITY.md +54 -0
  15. data/docs/REFINEMENT.md +3 -3
  16. data/docs/RELEASING.md +27 -0
  17. data/docs/RUNNING-LOCAL.md +3 -3
  18. data/docs/SECURITY.md +61 -0
  19. data/docs/SKILLS.md +2 -2
  20. data/docs/SOAK.md +127 -0
  21. data/docs/TOOLS.md +70 -2
  22. data/docs/domain.md +115 -0
  23. data/docs/index.md +1 -1
  24. data/lib/insika/agent_profile.rb +145 -10
  25. data/lib/insika/balloon_splitter.rb +102 -0
  26. data/lib/insika/budget_ledger.rb +34 -3
  27. data/lib/insika/cache_series_store.rb +49 -0
  28. data/lib/insika/channel_delivery.rb +118 -13
  29. data/lib/insika/channels/relay.rb +77 -3
  30. data/lib/insika/channels/web.rb +2 -2
  31. data/lib/insika/chat_builder.rb +84 -1
  32. data/lib/insika/checkpoint_store.rb +16 -0
  33. data/lib/insika/commands/agent_payload.rb +3 -3
  34. data/lib/insika/commands/cancel_followup.rb +49 -0
  35. data/lib/insika/commands/delete_tenant_data.rb +95 -0
  36. data/lib/insika/commands/export_customer_memory.rb +48 -0
  37. data/lib/insika/commands/forget_customer.rb +117 -0
  38. data/lib/insika/commands/freeze_funnel_baseline.rb +113 -0
  39. data/lib/insika/commands/gate_harvest.rb +138 -0
  40. data/lib/insika/commands/gate_refinement.rb +1 -1
  41. data/lib/insika/commands/judge_shadow_pairs.rb +124 -0
  42. data/lib/insika/commands/memory_forget_fact.rb +20 -4
  43. data/lib/insika/commands/memory_put_fact.rb +23 -4
  44. data/lib/insika/commands/promote_harvest.rb +130 -0
  45. data/lib/insika/commands/record_outcome.rb +46 -0
  46. data/lib/insika/commands/record_shadow_reply.rb +68 -0
  47. data/lib/insika/commands/reject_harvest.rb +38 -0
  48. data/lib/insika/commands/resolve_proposal.rb +108 -0
  49. data/lib/insika/commands/revoke_contact.rb +49 -0
  50. data/lib/insika/commands/rollback_harvest.rb +86 -0
  51. data/lib/insika/commands/run_distillation.rb +186 -0
  52. data/lib/insika/commands/run_harvest.rb +393 -0
  53. data/lib/insika/commands/send_message.rb +103 -6
  54. data/lib/insika/commands/session_purge.rb +67 -0
  55. data/lib/insika/contact_store.rb +183 -0
  56. data/lib/insika/context/builder.rb +21 -3
  57. data/lib/insika/context/fragment.rb +7 -3
  58. data/lib/insika/context/priority.rb +3 -0
  59. data/lib/insika/context/provider.rb +17 -3
  60. data/lib/insika/context/providers/briefing.rb +96 -0
  61. data/lib/insika/context/providers/memory.rb +16 -7
  62. data/lib/insika/context/providers/prompt.rb +30 -2
  63. data/lib/insika/context/providers/skill.rb +2 -0
  64. data/lib/insika/context/providers/tool_search.rb +2 -0
  65. data/lib/insika/context_trace_store.rb +38 -2
  66. data/lib/insika/distill.rb +224 -0
  67. data/lib/insika/distill_engine.rb +169 -0
  68. data/lib/insika/doctor.rb +716 -6
  69. data/lib/insika/dsl/runtime.rb +9 -3
  70. data/lib/insika/dsl/server_boot.rb +70 -1
  71. data/lib/insika/dsl.rb +83 -3
  72. data/lib/insika/edge_limiter.rb +28 -7
  73. data/lib/insika/env_schema.rb +9 -2
  74. data/lib/insika/errors.rb +11 -0
  75. data/lib/insika/evals/assertions.rb +3 -2
  76. data/lib/insika/evals/pairwise.rb +18 -6
  77. data/lib/insika/evidence.rb +183 -0
  78. data/lib/insika/executor.rb +687 -70
  79. data/lib/insika/followup_engine.rb +207 -0
  80. data/lib/insika/followup_policy.rb +221 -0
  81. data/lib/insika/followup_store.rb +306 -0
  82. data/lib/insika/funnel_declaration.rb +106 -0
  83. data/lib/insika/funnel_fold.rb +179 -0
  84. data/lib/insika/funnel_store.rb +163 -0
  85. data/lib/insika/golden_store.rb +1 -1
  86. data/lib/insika/grounding/matcher.rb +69 -0
  87. data/lib/insika/grounding.rb +44 -0
  88. data/lib/insika/harvest/conversion_gate.rb +159 -0
  89. data/lib/insika/harvest/criterion.rb +98 -0
  90. data/lib/insika/harvest/gate.rb +194 -0
  91. data/lib/insika/harvest/negative_list.rb +199 -0
  92. data/lib/insika/harvest.rb +241 -0
  93. data/lib/insika/harvest_engine.rb +193 -0
  94. data/lib/insika/harvest_store.rb +548 -0
  95. data/lib/insika/media.rb +298 -0
  96. data/lib/insika/memory_audit_store.rb +85 -0
  97. data/lib/insika/memory_store.rb +264 -23
  98. data/lib/insika/message_origin.rb +6 -1
  99. data/lib/insika/model_visible.rb +87 -0
  100. data/lib/insika/model_visible_trace_store.rb +66 -0
  101. data/lib/insika/onboarding.rb +6 -1
  102. data/lib/insika/outbox_store.rb +42 -4
  103. data/lib/insika/outcome_store.rb +147 -0
  104. data/lib/insika/packaging.rb +163 -0
  105. data/lib/insika/parity/criterion.rb +79 -0
  106. data/lib/insika/parity/verdict.rb +318 -0
  107. data/lib/insika/prefix_fingerprint.rb +58 -0
  108. data/lib/insika/profile_source.rb +22 -1
  109. data/lib/insika/proposal_store.rb +271 -0
  110. data/lib/insika/queue_policy.rb +4 -1
  111. data/lib/insika/refinement/proposer.rb +1 -1
  112. data/lib/insika/reliability.rb +32 -6
  113. data/lib/insika/retention.rb +281 -0
  114. data/lib/insika/routing.rb +101 -0
  115. data/lib/insika/safety/config.rb +44 -4
  116. data/lib/insika/safety/corpus.rb +255 -0
  117. data/lib/insika/safety/detectors.rb +32 -113
  118. data/lib/insika/safety/factory.rb +15 -2
  119. data/lib/insika/safety/grounding_enforcer.rb +59 -0
  120. data/lib/insika/safety/grounding_validator.rb +49 -0
  121. data/lib/insika/safety/input_guardrail.rb +1 -1
  122. data/lib/insika/safety/moderator.rb +1 -1
  123. data/lib/insika/safety/output_filter.rb +9 -5
  124. data/lib/insika/safety/output_validator.rb +11 -5
  125. data/lib/insika/schema_guard.rb +35 -0
  126. data/lib/insika/server/app.rb +144 -4
  127. data/lib/insika/server/rack_app.rb +16 -0
  128. data/lib/insika/server/responses.rb +40 -1
  129. data/lib/insika/session_actor.rb +8 -4
  130. data/lib/insika/session_store.rb +65 -2
  131. data/lib/insika/settings_store.rb +10 -0
  132. data/lib/insika/shadow_pair_store.rb +258 -0
  133. data/lib/insika/skill_catalog.rb +4 -0
  134. data/lib/insika/soak/envelope.rb +140 -0
  135. data/lib/insika/soak/report.rb +392 -0
  136. data/lib/insika/soak/runner.rb +554 -0
  137. data/lib/insika/store.rb +10 -1
  138. data/lib/insika/stores/memory.rb +6 -0
  139. data/lib/insika/stores/sqlite.rb +8 -0
  140. data/lib/insika/studio/app.rb +875 -14
  141. data/lib/insika/studio/assets/dist/application.css +1 -1
  142. data/lib/insika/studio/assets/dist/application.js +22 -22
  143. data/lib/insika/studio/assets/dist/favicon.svg +6 -0
  144. data/lib/insika/studio/forms.rb +262 -5
  145. data/lib/insika/studio/nav_icons.rb +6 -1
  146. data/lib/insika/studio/views/agent_detail.erb +627 -84
  147. data/lib/insika/studio/views/agents.erb +10 -6
  148. data/lib/insika/studio/views/approvals.erb +4 -1
  149. data/lib/insika/studio/views/chats.erb +4 -1
  150. data/lib/insika/studio/views/customer.erb +94 -0
  151. data/lib/insika/studio/views/customers.erb +32 -0
  152. data/lib/insika/studio/views/evals.erb +4 -1
  153. data/lib/insika/studio/views/facts.erb +133 -0
  154. data/lib/insika/studio/views/followups.erb +125 -0
  155. data/lib/insika/studio/views/funnel.erb +106 -0
  156. data/lib/insika/studio/views/harvest.erb +234 -0
  157. data/lib/insika/studio/views/home.erb +2 -1
  158. data/lib/insika/studio/views/layout.erb +1 -0
  159. data/lib/insika/studio/views/parity.erb +147 -0
  160. data/lib/insika/studio/views/playground.erb +7 -1
  161. data/lib/insika/studio/views/session.erb +56 -1
  162. data/lib/insika/studio/views/settings.erb +2 -0
  163. data/lib/insika/studio/views/skills.erb +0 -1
  164. data/lib/insika/studio/views/system_files.erb +1 -1
  165. data/lib/insika/studio/views/task.erb +13 -0
  166. data/lib/insika/studio/views/tasks.erb +4 -1
  167. data/lib/insika/studio/views/tools.erb +0 -1
  168. data/lib/insika/task_store.rb +21 -1
  169. data/lib/insika/testing/store_contract.rb +27 -6
  170. data/lib/insika/tick.rb +25 -1
  171. data/lib/insika/tool_definition.rb +17 -7
  172. data/lib/insika/tool_envelope.rb +69 -0
  173. data/lib/insika/tool_manifest.rb +5 -1
  174. data/lib/insika/tools/data_defined_tool.rb +10 -0
  175. data/lib/insika/tools/generate_image.rb +44 -0
  176. data/lib/insika/tools/schedule_followup.rb +164 -0
  177. data/lib/insika/tools/tts.rb +47 -0
  178. data/lib/insika/tools/update_briefing.rb +126 -0
  179. data/lib/insika/turn_state.rb +38 -1
  180. data/lib/insika/turn_timing.rb +22 -2
  181. data/lib/insika/version.rb +1 -1
  182. data/lib/insika/vitals.rb +84 -0
  183. data/lib/insika/wiring/graph.rb +300 -13
  184. data/lib/insika.rb +92 -3
  185. metadata +77 -1
@@ -45,6 +45,10 @@ module Insika
45
45
  pending_action_store = Insika::PendingActionStore.new(store: backend)
46
46
  delegation_store = Insika::DelegationStore.new(store: backend)
47
47
  memory_store = Insika::MemoryStore.new(store: backend)
48
+ # the append-only, content-free audit of OPERATOR memory
49
+ # mutations. Built unconditionally (empty and free when nothing writes)
50
+ # so a deployment that turns it on later finds its audit already durable.
51
+ memory_audit_store = Insika::MemoryAuditStore.new(store: backend)
48
52
  token_store = Insika::TokenStore.new(store: backend)
49
53
  budget_ledger = Insika::BudgetLedger.new(store: backend)
50
54
  circuit_state = Insika::CircuitState.new(store: backend)
@@ -54,11 +58,41 @@ module Insika
54
58
  # (they are empty and free when no channel is registered) so a deployment
55
59
  # that turns a channel on later finds its state already durable.
56
60
  outbox_store = Insika::OutboxStore.new(store: backend)
61
+ # the shadow pair store — one durable record per mirrored
62
+ # exchange, written by our half and the incumbent's. Built unconditionally
63
+ # (empty and free when shadow is off) so turning it on later finds the
64
+ # state already durable, beside the outbox on the same runtime backend.
65
+ shadow_pair_store = Insika::ShadowPairStore.new(store: backend)
57
66
  inbound_log = Insika::InboundLog.new(store: backend)
67
+ # WS7: business outcomes per conversation, recorded by the operator or
68
+ # the integration (POST /v1/outcomes). Built unconditionally (empty and
69
+ # free when nothing records) so the Studio's scorecard always has a store.
70
+ outcome_store = Insika::OutcomeStore.new(store: backend)
71
+ # the funnel's durable aggregates — per-day stage counts,
72
+ # fold cursors and baseline snapshots. Built unconditionally (empty and
73
+ # free when no pack declares a funnel) so the fold, the doctor and the
74
+ # Studio always have a store to read/write.
75
+ funnel_store = Insika::FunnelStore.new(store: backend)
76
+ # the contact-state cells and the follow-up schedule
77
+ # records. Built unconditionally (empty and free when no pack declares
78
+ # followup) so the engine, the tools and the Studio always have stores.
79
+ contact_store = Insika::ContactStore.new(store: backend)
80
+ followup_store = Insika::FollowupStore.new(store: backend)
81
+ # the distilled proposals + the latched dedup ledger +
82
+ # the per-session markers. Built unconditionally (empty and free when
83
+ # no pack declares distill) so the wiki, the engine and the LGPD
84
+ # purges always have a store.
85
+ proposal_store = Insika::ProposalStore.new(store: backend)
58
86
  # refinement RUNS (reports over real traffic). Runtime data,
59
87
  # same backend as sessions/tasks — the collector and the command that write it
60
88
  # are the root's business (deployment-only, like the memory commands).
61
89
  refinement_store = Insika::RefinementStore.new(store: backend)
90
+ # the harvest's durable half — mining runs, the per-
91
+ # candidate lifecycle, the append-only promotion log, the snapshots
92
+ # and the per-session markers. Built unconditionally (empty and free
93
+ # when no pack declares harvest) so the engine, the Studio and the
94
+ # LGPD purges always have a store.
95
+ harvest_store = Insika::HarvestStore.new(store: backend)
62
96
 
63
97
  code_tool_registry = Insika::ToolRegistry.new
64
98
  workflow_registry = Insika::WorkflowRegistry.new
@@ -74,9 +108,17 @@ module Insika
74
108
  session_store: session_store, task_store: task_store,
75
109
  checkpoint_store: checkpoint_store, pending_action_store: pending_action_store,
76
110
  delegation_store: delegation_store,
77
- memory_store: memory_store, refinement_store: refinement_store,
111
+ memory_store: memory_store, memory_audit_store: memory_audit_store,
112
+ refinement_store: refinement_store,
113
+ harvest_store: harvest_store,
78
114
  token_store: token_store, budget_ledger: budget_ledger, circuit_state: circuit_state,
79
- outbox_store: outbox_store, inbound_log: inbound_log,
115
+ outbox_store: outbox_store, shadow_pair_store: shadow_pair_store,
116
+ inbound_log: inbound_log,
117
+ outcome_store: outcome_store,
118
+ funnel_store: funnel_store,
119
+ contact_store: contact_store,
120
+ followup_store: followup_store,
121
+ proposal_store: proposal_store,
80
122
  code_tool_registry: code_tool_registry,
81
123
  workflow_registry: workflow_registry, policy_registry: policy_registry,
82
124
  capability_registry: Insika::CapabilityRegistry.new, hooks: Insika::Hooks.new,
@@ -115,7 +157,8 @@ module Insika
115
157
  # only production exercises.
116
158
  channel_delivery = Insika::ChannelDelivery.new(
117
159
  channels: spine.channel_registry, outbox: spine.outbox_store,
118
- session_store: spine.session_store, event_stream: spine.event_stream
160
+ session_store: spine.session_store, event_stream: spine.event_stream,
161
+ shadow_pairs: spine.shadow_pair_store
119
162
  )
120
163
 
121
164
  # WS3 provider reliability (retries/backoff/fallback/breaker) is ALWAYS wired —
@@ -135,23 +178,118 @@ module Insika
135
178
  delegation_store: spine.delegation_store, # async delegation durability
136
179
  channel_delivery: channel_delivery, # out-of-band reply delivery
137
180
  reliability: reliability, # WS3: retries/fallback/breaker (data-gated)
181
+ grounding_enforcer: guardrails.grounding_enforcer, # :enforce cut
182
+ # the follow-up stores — the ChatBuilder wires the
183
+ # schedule/cancel_followup tools on them (parity when no pack declares).
184
+ contact_store: spine.contact_store,
185
+ followup_store: spine.followup_store,
186
+ # the model-visible trace rides executor_extra like the
187
+ # context trace — a module graph that omits it still builds (nil =
188
+ # parity). The base graph wires it over the same backend the context
189
+ # trace uses.
190
+ model_visible_trace_store: Insika::ModelVisibleTraceStore.new(store: spine.backend),
138
191
  **executor_extra
139
192
  )
140
193
 
141
- bus = build_core_bus(spine: spine, profiles: profiles, executor: executor)
194
+ # the ONE path that writes proposals, shared by the
195
+ # automatic DistillEngine loop and the bus command behind the Studio's
196
+ # "Run distillation now" button (one code path for both).
197
+ run_distillation = Insika::Commands::RunDistillation.new(
198
+ profiles: profiles, proposal_store: spine.proposal_store,
199
+ session_store: spine.session_store, memory_store: spine.memory_store,
200
+ settings_store: executor_extra[:settings_store],
201
+ event_stream: spine.event_stream
202
+ )
203
+
204
+ bus = build_core_bus(spine: spine, profiles: profiles, executor: executor,
205
+ executor_extra: executor_extra, skill_catalog: skill_catalog,
206
+ run_distillation: run_distillation)
142
207
 
143
208
  # the periodic tick (outbox drain + stale recovery sweep). Built
144
209
  # here, after the bus, because its recovery half dispatches resume_task
145
210
  # through it; handed to the Executor, which starts it as a child of the
146
211
  # turn supervisor in serving mode. `INSIKA_TICK_INTERVAL=0` disables.
212
+ # WS8 retention rides the tick: always built, the settings knob
213
+ # (`retention_days`) gates it — the base graph (no settings_store)
214
+ # reads as OFF.
147
215
  executor.tick = Insika::Tick.new(
148
216
  store: spine.backend, channel_delivery: channel_delivery,
149
217
  recovery: Insika::Recovery.new(
150
218
  task_store: spine.task_store, checkpoint_store: spine.checkpoint_store, command_bus: bus
151
219
  ),
220
+ retention: Insika::Retention.new(
221
+ store: spine.backend, session_store: spine.session_store,
222
+ task_store: spine.task_store, checkpoint_store: spine.checkpoint_store,
223
+ memory_store: spine.memory_store, outcome_store: spine.outcome_store,
224
+ tool_trace_store: executor_extra[:tool_trace_store],
225
+ context_trace_store: executor_extra[:context_trace_store],
226
+ # the model-visible traces die with their checkpoints.
227
+ model_visible_trace_store: Insika::ModelVisibleTraceStore.new(store: spine.backend),
228
+ outbox_store: spine.outbox_store,
229
+ shadow_pair_store: spine.shadow_pair_store,
230
+ settings_store: executor_extra[:settings_store],
231
+ budget_ledger: spine.budget_ledger, # WS2 counter GC (retention-independent)
232
+ funnel_store: spine.funnel_store, # fold dies with its source
233
+ followup_store: spine.followup_store, # records age out too
234
+ contact_store: spine.contact_store, # cells age out too
235
+ proposal_store: spine.proposal_store, # proposals age out too
236
+ harvest_store: spine.harvest_store # candidates/log/snapshots too
237
+ ),
152
238
  interval: tick_env("INSIKA_TICK_INTERVAL") || Insika::Tick::DEFAULT_INTERVAL,
153
239
  stale_after: tick_env("INSIKA_TICK_STALE_AFTER") || Insika::Tick::DEFAULT_STALE_AFTER
154
240
  )
241
+ # the tick-driven outcome fold — wired here, after the
242
+ # Tick, because it reads the outcome/funnel stores of the spine and the
243
+ # profiles; the per-pair declaration gates it (nil funnel everywhere =
244
+ # an inert fold). Always built: a pack that declares a funnel later
245
+ # finds its fold already on the tick.
246
+ executor.tick.funnel = Insika::FunnelFold.new(
247
+ outcome_store: spine.outcome_store, funnel_store: spine.funnel_store,
248
+ profiles: profiles, store: spine.backend
249
+ )
250
+ # the tick-driven follow-up firer — the tick's third
251
+ # duty, wired here after the Tick, gated by its own claim window.
252
+ # Always built: a pack that declares followup later finds its firer
253
+ # already on the tick (inert when no profile declares followup).
254
+ executor.tick.followup = Insika::FollowupEngine.new(
255
+ store: spine.backend, followup_store: spine.followup_store,
256
+ contact_store: spine.contact_store, task_store: spine.task_store,
257
+ profiles: profiles, executor: executor, event_stream: spine.event_stream
258
+ )
259
+ # the distillation engine — the tick-duty that finds idle
260
+ # customer sessions and distills them on its own worker fiber (a
261
+ # supervisor child, started in serving mode next to the tick).
262
+ # Always built: a pack that declares `distill:` later finds its engine
263
+ # already wired — and INERT until one does (the engine gates its start
264
+ # and its passes on a declaring profile).
265
+ executor.distill_engine = Insika::DistillEngine.new(
266
+ store: spine.backend, proposal_store: spine.proposal_store,
267
+ session_store: spine.session_store,
268
+ runner: run_distillation,
269
+ profiles: profiles,
270
+ window: Insika::DistillEngine::DEFAULT_WINDOW
271
+ )
272
+ # the harvest engine — the tick-duty that finds idle,
273
+ # unmined sessions whose agent declares `harvest.enabled` and mines
274
+ # them on its own worker fiber (a supervisor child, started in serving
275
+ # mode next to the tick). Always built: INERT until a profile declares
276
+ # harvest (the engine gates its start and its passes on data). The
277
+ # runner is the same RunHarvest the bus serves, so the manual CLI and
278
+ # the automated loop share one code path.
279
+ run_harvest = Insika::Commands::RunHarvest.new(
280
+ profiles: profiles, harvest_store: spine.harvest_store,
281
+ session_store: spine.session_store, task_store: spine.task_store,
282
+ skill_store: skill_catalog.store, # the harvest's dedup reads the authored skills
283
+ tool_trace_store: executor_extra[:tool_trace_store],
284
+ settings_store: executor_extra[:settings_store],
285
+ negative_list: nil, miner_factory: nil,
286
+ event_stream: spine.event_stream
287
+ )
288
+ executor.harvest_engine = Insika::HarvestEngine.new(
289
+ store: spine.backend, harvest_store: spine.harvest_store,
290
+ session_store: spine.session_store, runner: run_harvest,
291
+ profiles: profiles, window: Insika::HarvestEngine::DEFAULT_WINDOW
292
+ )
155
293
  # WS6 operator alerts: answers budget_warning / breaker_open /
156
294
  # delivery_failed per agent (`alerts.webhook`) via the outbox+claim
157
295
  # pipeline. Always wired — the per-agent data gates it (parity).
@@ -166,8 +304,16 @@ module Insika
166
304
  session_store: spine.session_store, task_store: spine.task_store,
167
305
  checkpoint_store: spine.checkpoint_store, pending_action_store: spine.pending_action_store,
168
306
  delegation_store: spine.delegation_store,
169
- memory_store: spine.memory_store, refinement_store: spine.refinement_store,
170
- outbox_store: spine.outbox_store, inbound_log: spine.inbound_log,
307
+ memory_store: spine.memory_store, memory_audit_store: spine.memory_audit_store,
308
+ refinement_store: spine.refinement_store,
309
+ harvest_store: spine.harvest_store,
310
+ outbox_store: spine.outbox_store, shadow_pair_store: spine.shadow_pair_store,
311
+ inbound_log: spine.inbound_log,
312
+ outcome_store: spine.outcome_store,
313
+ funnel_store: spine.funnel_store,
314
+ contact_store: spine.contact_store,
315
+ followup_store: spine.followup_store,
316
+ proposal_store: spine.proposal_store,
171
317
  token_store: spine.token_store, budget_ledger: spine.budget_ledger,
172
318
  circuit_state: spine.circuit_state,
173
319
  channel_registry: spine.channel_registry, channel_delivery: channel_delivery,
@@ -184,7 +330,8 @@ module Insika
184
330
  # The CORE command surface every root needs — turn essentials + the operator
185
331
  # controls (pause/approve) the Studio dispatches. Registering pause_task/
186
332
  # approve_action HERE is the crux of: it retires the config.ru:28-34 patch.
187
- def build_core_bus(spine:, profiles:, executor:)
333
+ def build_core_bus(spine:, profiles:, executor:, executor_extra: {}, skill_catalog: nil,
334
+ run_distillation: nil)
188
335
  bus = Insika::CommandBus.new
189
336
  bus.register(:create_session,
190
337
  Insika::Commands::CreateSession.new(session_store: spine.session_store, event_stream: spine.event_stream))
@@ -198,7 +345,10 @@ module Insika
198
345
  bus.register(:send_message,
199
346
  Insika::Commands::SendMessage.new(profiles: profiles, session_store: spine.session_store,
200
347
  task_store: spine.task_store, executor: executor,
201
- inbound_log: spine.inbound_log))
348
+ inbound_log: spine.inbound_log,
349
+ contact_store: spine.contact_store,
350
+ followup_store: spine.followup_store,
351
+ store: spine.backend))
202
352
  bus.register(:resume_task,
203
353
  Insika::Commands::ResumeTask.new(profiles: profiles, task_store: spine.task_store,
204
354
  checkpoint_store: spine.checkpoint_store, executor: executor))
@@ -214,6 +364,135 @@ module Insika
214
364
  bus.register(:rotate_tenant_token,
215
365
  Insika::Commands::RotateTenantToken.new(token_store: spine.token_store,
216
366
  event_stream: spine.event_stream))
367
+ # WS7: a business outcome per conversation (operator or integration).
368
+ bus.register(:record_outcome,
369
+ Insika::Commands::RecordOutcome.new(outcome_store: spine.outcome_store,
370
+ event_stream: spine.event_stream))
371
+ # the incumbent's half of a shadow pair, one command behind
372
+ # both mirror shapes (the mirror call itself + the follow-up route).
373
+ bus.register(:record_shadow_reply,
374
+ Insika::Commands::RecordShadowReply.new(shadow_pairs: spine.shadow_pair_store,
375
+ event_stream: spine.event_stream))
376
+ # WS8 (LGPD): purge one customer's memory + the whole footprint of their
377
+ # sessions (traces, tasks, checkpoints, outbox). The trace stores are
378
+ # deployment components (nil at the base — skipped). the
379
+ # audit collaborator records a content-free purge line.
380
+ bus.register(:forget_customer,
381
+ Insika::Commands::ForgetCustomer.new(
382
+ memory_store: spine.memory_store, session_store: spine.session_store,
383
+ tool_trace_store: executor_extra[:tool_trace_store],
384
+ context_trace_store: executor_extra[:context_trace_store],
385
+ # the model-visible traces die with the
386
+ # checkpoints (the same SessionPurge list).
387
+ model_visible_trace_store: Insika::ModelVisibleTraceStore.new(store: spine.backend),
388
+ task_store: spine.task_store, checkpoint_store: spine.checkpoint_store,
389
+ outbox_store: spine.outbox_store,
390
+ shadow_pairs: spine.shadow_pair_store,
391
+ audit_store: spine.memory_audit_store,
392
+ followup_store: spine.followup_store,
393
+ contact_store: spine.contact_store,
394
+ proposal_store: spine.proposal_store,
395
+ event_stream: spine.event_stream
396
+ ))
397
+ # the LGPD access right — export one customer's memory
398
+ # cell as content (the Studio download); the event stays counts-only.
399
+ bus.register(:export_customer_memory,
400
+ Insika::Commands::ExportCustomerMemory.new(
401
+ memory_store: spine.memory_store, event_stream: spine.event_stream
402
+ ))
403
+ # WS8 (LGPD): purge ONE TENANT's data — sessions and their footprint,
404
+ # memory cells and outcomes. Operator-only by construction (ingress).
405
+ bus.register(:delete_tenant_data,
406
+ Insika::Commands::DeleteTenantData.new(
407
+ memory_store: spine.memory_store, session_store: spine.session_store,
408
+ tool_trace_store: executor_extra[:tool_trace_store],
409
+ context_trace_store: executor_extra[:context_trace_store],
410
+ # the model-visible traces die with the tenant.
411
+ model_visible_trace_store: Insika::ModelVisibleTraceStore.new(store: spine.backend),
412
+ outcome_store: spine.outcome_store,
413
+ funnel_store: spine.funnel_store, # the fold dies with the tenant
414
+ followup_store: spine.followup_store,
415
+ contact_store: spine.contact_store,
416
+ proposal_store: spine.proposal_store,
417
+ harvest_store: spine.harvest_store,
418
+ task_store: spine.task_store, checkpoint_store: spine.checkpoint_store,
419
+ outbox_store: spine.outbox_store,
420
+ shadow_pairs: spine.shadow_pair_store,
421
+ token_store: spine.token_store, # revoked BEFORE the sweep
422
+ event_stream: spine.event_stream
423
+ ))
424
+ # the operator's baseline freeze — the number
425
+ # read. Synchronous control command, dispatched from the Studio.
426
+ bus.register(:freeze_funnel_baseline,
427
+ Insika::Commands::FreezeFunnelBaseline.new(
428
+ funnel_store: spine.funnel_store, profiles: profiles,
429
+ event_stream: spine.event_stream
430
+ ))
431
+ # the follow-up mutations — the Studio's Cancel button
432
+ # and the channel opt-out event (a tenant principal never reaches the
433
+ # generic command ingress; these ride the same operator-grade path).
434
+ bus.register(:cancel_followup,
435
+ Insika::Commands::CancelFollowup.new(followup_store: spine.followup_store,
436
+ event_stream: spine.event_stream))
437
+ bus.register(:revoke_contact,
438
+ Insika::Commands::RevokeContact.new(contact_store: spine.contact_store,
439
+ followup_store: spine.followup_store,
440
+ store: spine.backend,
441
+ event_stream: spine.event_stream))
442
+ # the human's answer on a distilled proposal — the ONLY
443
+ # mutation behind the Facts (wiki) page. Synchronous control command.
444
+ bus.register(:resolve_proposal,
445
+ Insika::Commands::ResolveProposal.new(
446
+ proposal_store: spine.proposal_store,
447
+ memory_store: spine.memory_store,
448
+ event_stream: spine.event_stream
449
+ ))
450
+ # /C10: the gated harvest — the mining pass, the double
451
+ # gate, the human's promote/rollback/dismiss. The BASE graph wires the
452
+ # nil factories (no eval surface, no criterion, no funnel): every flow
453
+ # refuses with a named reason or skips (parity). The deployment root
454
+ # re-registers with the real gate/criterion/negative list.
455
+ bus.register(:run_harvest,
456
+ Insika::Commands::RunHarvest.new(
457
+ profiles: profiles, harvest_store: spine.harvest_store,
458
+ session_store: spine.session_store, task_store: spine.task_store,
459
+ skill_store: skill_catalog.store,
460
+ tool_trace_store: executor_extra[:tool_trace_store],
461
+ settings_store: executor_extra[:settings_store],
462
+ negative_list: nil, miner_factory: nil,
463
+ event_stream: spine.event_stream
464
+ ))
465
+ bus.register(:gate_harvest,
466
+ Insika::Commands::GateHarvest.new(
467
+ harvest_store: spine.harvest_store, gate: nil,
468
+ conversion_gate: nil, criterion: nil,
469
+ event_stream: spine.event_stream
470
+ ))
471
+ bus.register(:promote_harvest,
472
+ Insika::Commands::PromoteHarvest.new(
473
+ harvest_store: spine.harvest_store,
474
+ skill_store: skill_catalog.store,
475
+ skill_catalog: skill_catalog,
476
+ profile_source: profiles, criterion: nil, conversion_gate: nil,
477
+ event_stream: spine.event_stream
478
+ ))
479
+ bus.register(:rollback_harvest,
480
+ Insika::Commands::RollbackHarvest.new(
481
+ harvest_store: spine.harvest_store,
482
+ skill_store: skill_catalog.store,
483
+ skill_catalog: skill_catalog,
484
+ profile_source: profiles, event_stream: spine.event_stream
485
+ ))
486
+ bus.register(:reject_harvest,
487
+ Insika::Commands::RejectHarvest.new(
488
+ harvest_store: spine.harvest_store, event_stream: spine.event_stream
489
+ ))
490
+ # the ONE path that writes proposals, served to the
491
+ # Studio's "Run distillation now" button. The same instance the
492
+ # DistillEngine's loop uses, so the manual and the automatic share one
493
+ # code path; the engine keeps the automatic pass when the root does
494
+ # not pass a runner here (nil = the command is not on the bus).
495
+ bus.register(:run_distillation, run_distillation) if run_distillation
217
496
  bus
218
497
  end
219
498
 
@@ -221,9 +500,13 @@ module Insika
221
500
  # them to their historic public constants (SESSION_STORE, REGISTRY, ...).
222
501
  Spine = Struct.new(
223
502
  :backend, :event_stream, :session_store, :task_store, :checkpoint_store,
224
- :pending_action_store, :delegation_store, :memory_store, :refinement_store,
225
- :token_store, :budget_ledger, :circuit_state, :outbox_store, :inbound_log, :code_tool_registry,
226
- :workflow_registry, :policy_registry, :capability_registry, :hooks,
503
+ :pending_action_store, :delegation_store, :memory_store, :memory_audit_store,
504
+ :refinement_store,
505
+ :harvest_store,
506
+ :token_store, :budget_ledger, :circuit_state, :outbox_store, :shadow_pair_store,
507
+ :inbound_log, :outcome_store, :funnel_store,
508
+ :contact_store, :followup_store, :proposal_store,
509
+ :code_tool_registry, :workflow_registry, :policy_registry, :capability_registry, :hooks,
227
510
  :channel_registry, keyword_init: true
228
511
  )
229
512
 
@@ -233,8 +516,12 @@ module Insika
233
516
  Result = Struct.new(
234
517
  :backend, :event_stream,
235
518
  :session_store, :task_store, :checkpoint_store, :pending_action_store, :delegation_store,
236
- :memory_store, :refinement_store, :outbox_store, :inbound_log, :token_store,
237
- :budget_ledger, :circuit_state, :channel_registry, :channel_delivery,
519
+ :memory_store, :memory_audit_store, :refinement_store, :outbox_store, :shadow_pair_store,
520
+ :inbound_log, :token_store,
521
+ :budget_ledger, :circuit_state, :outcome_store, :funnel_store,
522
+ :contact_store, :followup_store, :proposal_store,
523
+ :harvest_store,
524
+ :channel_registry, :channel_delivery,
238
525
  :code_tool_registry, :tool_registry, :workflow_registry, :policy_registry, :capability_registry,
239
526
  :tool_catalog, :skill_catalog, :prompt_catalog,
240
527
  :hooks, :guardrails, :middleware,
data/lib/insika.rb CHANGED
@@ -2,12 +2,32 @@
2
2
 
3
3
  require_relative "insika/version"
4
4
  require_relative "insika/errors"
5
+ # the payload selection + the domain-boundary audit yardstick.
6
+ # Pure module — loaded by the gemspec too, so it carries no app requires.
7
+ require_relative "insika/packaging"
5
8
  require_relative "insika/provider_error_classifier"
6
9
  require_relative "insika/token_store"
7
10
  require_relative "insika/budget_ledger"
8
11
  require_relative "insika/circuit_state"
9
12
  require_relative "insika/reliability"
13
+ require_relative "insika/routing"
14
+ require_relative "insika/media"
10
15
  require_relative "insika/alert_dispatcher"
16
+ require_relative "insika/outcome_store"
17
+ require_relative "insika/retention"
18
+ require_relative "insika/funnel_declaration"
19
+ require_relative "insika/funnel_store"
20
+ require_relative "insika/funnel_fold"
21
+ # the parsed follow-up policy of ONE agent — the ONLY shape the
22
+ # engine accepts (tool/engine/doctor/Studio share it). Pure value object.
23
+ require_relative "insika/followup_policy"
24
+ # the contact-state cells and the follow-up schedule records.
25
+ # Both are dumb domain stores over the injected backend.
26
+ require_relative "insika/contact_store"
27
+ require_relative "insika/followup_store"
28
+ # the tick-driven firer — rides the same tick as retention/funnel.
29
+ require_relative "insika/followup_engine"
30
+ require_relative "insika/commands/freeze_funnel_baseline"
11
31
  require_relative "insika/channels/webhook"
12
32
  require_relative "insika/coercion"
13
33
  require_relative "insika/message_origin"
@@ -21,6 +41,9 @@ require_relative "insika/model_selection"
21
41
  require_relative "insika/model_resolver"
22
42
  require_relative "insika/tool_definition"
23
43
  require_relative "insika/tool_manifest"
44
+ # the pack's grounding policy (Insika::Grounding + the
45
+ # GroundingMatcher it wraps). Pure Ruby — profile data, parsed per turn.
46
+ require_relative "insika/grounding"
24
47
  require_relative "insika/frontmatter"
25
48
  require_relative "insika/token_estimator"
26
49
  require_relative "insika/checkpoint"
@@ -33,9 +56,12 @@ require_relative "insika/usage_ledger"
33
56
  require_relative "insika/budget_ledger"
34
57
  require_relative "insika/edge_limiter"
35
58
  require_relative "insika/tool_output_compressor"
36
- # Content safety / guardrails. detectors.rb is self-contained (the eval
37
- # requires it directly); the rest hang off Middleware/hooks seams. No ruby_llm
38
- # at load-time Factory requires the gem lazily, like the Executor's create_chat.
59
+ # Content safety / guardrails. The pattern SOURCE is the
60
+ # language-tagged data in safety/corpus.rb; detectors.rb compiles it. Both are
61
+ # self-contained (the eval requires detectors.rb directly); the rest hang off
62
+ # Middleware/hooks seams. No ruby_llm at load-time — Factory requires the gem
63
+ # lazily, like the Executor's create_chat.
64
+ require_relative "insika/safety/corpus"
39
65
  require_relative "insika/safety/detectors"
40
66
  require_relative "insika/safety/safe_responses"
41
67
  require_relative "insika/safety/config"
@@ -56,6 +82,11 @@ require_relative "insika/context/providers/skill_trigger"
56
82
  require_relative "insika/context/providers/tool_search"
57
83
  require_relative "insika/context/providers/memory"
58
84
  require_relative "insika/context/providers/session"
85
+ require_relative "insika/context/providers/briefing"
86
+ # the cache-prefix hash chain + the per-agent cache-hit series.
87
+ # Both are referenced by the Executor at runtime, so they load before it.
88
+ require_relative "insika/prefix_fingerprint"
89
+ require_relative "insika/cache_series_store"
59
90
  require_relative "insika/policy/policy"
60
91
  require_relative "insika/policy/engine"
61
92
  require_relative "insika/registry"
@@ -78,8 +109,12 @@ require_relative "insika/delegation_store"
78
109
  # Channels: the outbound record + the inbound retry window. Both are
79
110
  # plain stores; the channel objects themselves come after the HTTP client.
80
111
  require_relative "insika/outbox_store"
112
+ require_relative "insika/shadow_pair_store"
81
113
  require_relative "insika/inbound_log"
82
114
  require_relative "insika/memory_store"
115
+ # the operator-mutation audit trail. After memory_store (no
116
+ # mutual deps). Pure stdlib — `load_guard_spec` stays green.
117
+ require_relative "insika/memory_audit_store"
83
118
  require_relative "insika/config_store"
84
119
  require_relative "insika/profile_source"
85
120
  require_relative "insika/agent_file_store"
@@ -88,6 +123,10 @@ require_relative "insika/secret_masking"
88
123
  require_relative "insika/tool_store"
89
124
  require_relative "insika/tool_trace_store"
90
125
  require_relative "insika/context_trace_store"
126
+ # the model-visible payload of one ask + its durable trace —
127
+ # "model-visible means logged" (the conformance suite's reconstruction half).
128
+ require_relative "insika/model_visible"
129
+ require_relative "insika/model_visible_trace_store"
91
130
  # Refinement: the run record + the evidence half of the loop.
92
131
  # Reads sessions/tasks/traces above; requires Safety::Detectors (loaded earlier) for
93
132
  # the PII redaction of the snippets it puts in a report.
@@ -101,6 +140,9 @@ require_relative "insika/refinement/candidate"
101
140
  require_relative "insika/refinement/proposer"
102
141
  require_relative "insika/egress_guard"
103
142
  require_relative "insika/schema_guard"
143
+ # the evidence contract (Spec + Processor + EvidenceLedger). Pure Ruby.
144
+ # Required BEFORE tool_envelope.rb — the envelope references it at runtime.
145
+ require_relative "insika/evidence"
104
146
  require_relative "insika/sandbox"
105
147
  require_relative "insika/http_client"
106
148
  # Channels: the registry, the bundled relay adapter and the
@@ -130,6 +172,10 @@ require_relative "insika/evals/runner"
130
172
  require_relative "insika/evals/report"
131
173
  require_relative "insika/evals/baseline"
132
174
  require_relative "insika/evals/transport"
175
+ # shadow parity: the frozen criterion and the mechanical
176
+ # fold over the pair store (C6). Pure — no store, no provider gem.
177
+ require_relative "insika/parity/criterion"
178
+ require_relative "insika/parity/verdict"
133
179
  # Authored eval cases: the corpus becomes editable without a
134
180
  # checkout. Requires the loader above (it is the one validator).
135
181
  require_relative "insika/golden_store"
@@ -177,6 +223,42 @@ require_relative "insika/commands/delete_llm_provider"
177
223
  require_relative "insika/commands/issue_tenant_token"
178
224
  require_relative "insika/commands/revoke_token"
179
225
  require_relative "insika/commands/rotate_tenant_token"
226
+ require_relative "insika/commands/record_outcome"
227
+ require_relative "insika/commands/record_shadow_reply"
228
+ require_relative "insika/commands/judge_shadow_pairs"
229
+ require_relative "insika/commands/session_purge"
230
+ require_relative "insika/commands/forget_customer"
231
+ require_relative "insika/commands/delete_tenant_data"
232
+ # the LGPD access right — export one customer's memory cell as
233
+ # content (the Studio download); the event stays counts-only.
234
+ require_relative "insika/commands/export_customer_memory"
235
+ # the operator/integration follow-up mutations (cancel one
236
+ # record; revoke a contact + fall its pending records atomically).
237
+ require_relative "insika/commands/cancel_followup"
238
+ require_relative "insika/commands/revoke_contact"
239
+ # session distillation — the distiller (pure, injected ask)
240
+ # and the proposal store (proposals + the latched dedup ledger + the
241
+ # per-session distilled marker).
242
+ require_relative "insika/distill"
243
+ require_relative "insika/proposal_store"
244
+ require_relative "insika/commands/run_distillation"
245
+ require_relative "insika/distill_engine"
246
+ require_relative "insika/commands/resolve_proposal"
247
+ # the gated harvest — the miner (pure, injected ask), the
248
+ # frozen conversion criterion and the versioned negative list. The provider
249
+ # touch stays inside MinerFactory's lazy ask (load_guard stays green).
250
+ require_relative "insika/harvest"
251
+ require_relative "insika/harvest/criterion"
252
+ require_relative "insika/harvest/negative_list"
253
+ require_relative "insika/harvest/conversion_gate"
254
+ require_relative "insika/harvest/gate"
255
+ require_relative "insika/harvest_store"
256
+ require_relative "insika/commands/run_harvest"
257
+ require_relative "insika/commands/gate_harvest"
258
+ require_relative "insika/commands/promote_harvest"
259
+ require_relative "insika/commands/rollback_harvest"
260
+ require_relative "insika/commands/reject_harvest"
261
+ require_relative "insika/harvest_engine"
180
262
  require_relative "insika/commands/upsert_mcp"
181
263
  require_relative "insika/commands/delete_mcp"
182
264
  require_relative "insika/commands/write_system_file"
@@ -195,6 +277,7 @@ require_relative "insika/skill_catalog"
195
277
  require_relative "insika/tool_catalog"
196
278
  require_relative "insika/steer_injector"
197
279
  require_relative "insika/loop_detector"
280
+ require_relative "insika/balloon_splitter"
198
281
  require_relative "insika/turn_output"
199
282
  require_relative "insika/turn_state"
200
283
  require_relative "insika/turn_timing"
@@ -211,6 +294,12 @@ require_relative "insika/telemetry"
211
294
  # Strict config + diagnosis. Doctor reads the config stores above;
212
295
  # EnvSchema (required at the top) is its env layer.
213
296
  require_relative "insika/doctor"
297
+ # Soak: the 72h uptime-degradation harness. Pure stdlib — the
298
+ # envelope and the report fold are deterministic, the runner only talks Net::HTTP.
299
+ require_relative "insika/soak/envelope"
300
+ require_relative "insika/soak/report"
301
+ require_relative "insika/soak/runner"
302
+ require_relative "insika/vitals"
214
303
  # Shared composition core for both roots (config/wiring.rb + config/deployment.rb).
215
304
  # Only references the classes above at call-time, so require order is unconstrained.
216
305
  require_relative "insika/wiring/graph"