insika 0.2.0 → 0.7.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 (300) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +325 -17
  3. data/README.md +52 -11
  4. data/bin/insika +895 -8
  5. data/bin/insika-router +87 -0
  6. data/docs/AGENTS.md +229 -163
  7. data/docs/API.md +56 -0
  8. data/docs/ARCHITECTURE.md +3 -2
  9. data/docs/ARTIFACTS.md +95 -0
  10. data/docs/BENCHMARK.md +2 -2
  11. data/docs/CHANNELS.md +111 -21
  12. data/docs/CONTEXT.md +132 -19
  13. data/docs/DEMO.md +80 -0
  14. data/docs/DEPLOY.md +75 -4
  15. data/docs/EMBEDDING.md +1 -1
  16. data/docs/EVALS.md +146 -2
  17. data/docs/FACTS.md +135 -0
  18. data/docs/HARVEST.md +116 -0
  19. data/docs/KNOWLEDGE.md +290 -0
  20. data/docs/LOADTEST.md +16 -10
  21. data/docs/MEDIA.md +128 -0
  22. data/docs/OBSERVABILITY.md +65 -6
  23. data/docs/OUTCOMES.md +137 -0
  24. data/docs/PLUGINS.md +51 -6
  25. data/docs/POLICY.md +216 -0
  26. data/docs/REFINEMENT.md +17 -12
  27. data/docs/RELEASING.md +27 -0
  28. data/docs/ROUTER.md +213 -0
  29. data/docs/RUNNING-LOCAL.md +6 -6
  30. data/docs/SCHEDULING.md +121 -0
  31. data/docs/SECURITY.md +79 -2
  32. data/docs/SKILLS.md +13 -4
  33. data/docs/SOAK.md +127 -0
  34. data/docs/TEMPLATES.md +134 -0
  35. data/docs/TOOLS.md +220 -27
  36. data/docs/WHY.md +1 -1
  37. data/docs/WORKFLOWS.md +2 -2
  38. data/docs/_includes/head_custom.html +5 -0
  39. data/docs/_includes/title.html +13 -0
  40. data/docs/_sass/color_schemes/insika.scss +32 -0
  41. data/docs/_sass/custom/custom.scss +199 -0
  42. data/docs/_sass/custom/setup.scss +26 -0
  43. data/docs/assets/img/favicon.svg +7 -0
  44. data/docs/assets/img/insika-mark.svg +7 -0
  45. data/docs/core-concepts.md +21 -0
  46. data/docs/domain.md +115 -0
  47. data/docs/improve.md +20 -0
  48. data/docs/index.md +9 -6
  49. data/docs/integrate.md +20 -0
  50. data/docs/operate.md +13 -6
  51. data/docs/prompts/ADD-TOOL.md +118 -0
  52. data/docs/prompts/DIAGNOSE-TURN.md +65 -0
  53. data/docs/prompts/GO-LIVE.md +138 -0
  54. data/docs/prompts/RUN-EXAMPLES.md +70 -0
  55. data/docs/reference.md +19 -0
  56. data/docs/ship.md +10 -2
  57. data/docs/start-here.md +18 -0
  58. data/lib/insika/agent_profile.rb +202 -10
  59. data/lib/insika/artifact_signing.rb +82 -0
  60. data/lib/insika/artifact_store.rb +160 -0
  61. data/lib/insika/balloon_splitter.rb +102 -0
  62. data/lib/insika/budget_ledger.rb +34 -3
  63. data/lib/insika/cache_series_store.rb +49 -0
  64. data/lib/insika/channel_delivery.rb +119 -14
  65. data/lib/insika/channels/relay.rb +77 -3
  66. data/lib/insika/channels/web.rb +2 -2
  67. data/lib/insika/chat_builder.rb +105 -2
  68. data/lib/insika/checkpoint_store.rb +16 -0
  69. data/lib/insika/commands/agent_payload.rb +3 -3
  70. data/lib/insika/commands/backfill_knowledge.rb +145 -0
  71. data/lib/insika/commands/cancel_followup.rb +49 -0
  72. data/lib/insika/commands/delete_artifact.rb +35 -0
  73. data/lib/insika/commands/delete_concept.rb +34 -0
  74. data/lib/insika/commands/delete_mcp.rb +6 -2
  75. data/lib/insika/commands/delete_tenant_data.rb +107 -0
  76. data/lib/insika/commands/export_customer_memory.rb +48 -0
  77. data/lib/insika/commands/forget_customer.rb +117 -0
  78. data/lib/insika/commands/freeze_funnel_baseline.rb +113 -0
  79. data/lib/insika/commands/gate_harvest.rb +138 -0
  80. data/lib/insika/commands/gate_refinement.rb +1 -1
  81. data/lib/insika/commands/judge_shadow_pairs.rb +124 -0
  82. data/lib/insika/commands/memory_forget_fact.rb +20 -4
  83. data/lib/insika/commands/memory_put_fact.rb +23 -4
  84. data/lib/insika/commands/promote_harvest.rb +130 -0
  85. data/lib/insika/commands/record_outcome.rb +46 -0
  86. data/lib/insika/commands/record_shadow_reply.rb +68 -0
  87. data/lib/insika/commands/refresh_mcp_tools.rb +47 -0
  88. data/lib/insika/commands/reject_harvest.rb +38 -0
  89. data/lib/insika/commands/resolve_proposal.rb +108 -0
  90. data/lib/insika/commands/restore_concept.rb +34 -0
  91. data/lib/insika/commands/revoke_contact.rb +49 -0
  92. data/lib/insika/commands/rollback_harvest.rb +86 -0
  93. data/lib/insika/commands/run_distillation.rb +186 -0
  94. data/lib/insika/commands/run_harvest.rb +393 -0
  95. data/lib/insika/commands/seed_demo_data.rb +31 -0
  96. data/lib/insika/commands/send_message.rb +103 -6
  97. data/lib/insika/commands/session_purge.rb +67 -0
  98. data/lib/insika/commands/upsert_mcp.rb +6 -3
  99. data/lib/insika/commands/write_concept.rb +57 -0
  100. data/lib/insika/contact_store.rb +183 -0
  101. data/lib/insika/context/builder.rb +21 -3
  102. data/lib/insika/context/fragment.rb +7 -3
  103. data/lib/insika/context/priority.rb +5 -0
  104. data/lib/insika/context/provider.rb +17 -3
  105. data/lib/insika/context/providers/briefing.rb +96 -0
  106. data/lib/insika/context/providers/knowledge.rb +108 -0
  107. data/lib/insika/context/providers/memory.rb +16 -7
  108. data/lib/insika/context/providers/prompt.rb +57 -23
  109. data/lib/insika/context/providers/skill.rb +2 -0
  110. data/lib/insika/context/providers/tool_search.rb +2 -0
  111. data/lib/insika/context_trace_store.rb +38 -2
  112. data/lib/insika/cron.rb +189 -0
  113. data/lib/insika/demo/agent_attrs.rb +43 -0
  114. data/lib/insika/demo/golden_cases.rb +81 -0
  115. data/lib/insika/demo/seeder.rb +336 -0
  116. data/lib/insika/distill.rb +224 -0
  117. data/lib/insika/distill_engine.rb +169 -0
  118. data/lib/insika/doctor.rb +889 -11
  119. data/lib/insika/dsl/definition.rb +3 -2
  120. data/lib/insika/dsl/runtime.rb +69 -82
  121. data/lib/insika/dsl/server_boot.rb +92 -1
  122. data/lib/insika/dsl/system.rb +10 -2
  123. data/lib/insika/dsl.rb +186 -5
  124. data/lib/insika/edge_limiter.rb +28 -7
  125. data/lib/insika/env_schema.rb +25 -3
  126. data/lib/insika/errors.rb +11 -0
  127. data/lib/insika/evals/assertions.rb +3 -2
  128. data/lib/insika/evals/golden.rb +41 -4
  129. data/lib/insika/evals/judge.rb +47 -2
  130. data/lib/insika/evals/pairwise.rb +29 -6
  131. data/lib/insika/evals/persona.rb +98 -0
  132. data/lib/insika/evals/runner.rb +9 -0
  133. data/lib/insika/evals/simulator.rb +225 -0
  134. data/lib/insika/evals/transport.rb +83 -1
  135. data/lib/insika/event_stream.rb +10 -0
  136. data/lib/insika/evidence.rb +183 -0
  137. data/lib/insika/executor.rb +869 -76
  138. data/lib/insika/followup_engine.rb +207 -0
  139. data/lib/insika/followup_policy.rb +198 -0
  140. data/lib/insika/followup_store.rb +306 -0
  141. data/lib/insika/funnel_declaration.rb +106 -0
  142. data/lib/insika/funnel_fold.rb +179 -0
  143. data/lib/insika/funnel_store.rb +163 -0
  144. data/lib/insika/golden_store.rb +17 -2
  145. data/lib/insika/grounding/matcher.rb +69 -0
  146. data/lib/insika/grounding.rb +44 -0
  147. data/lib/insika/harvest/conversion_gate.rb +159 -0
  148. data/lib/insika/harvest/criterion.rb +98 -0
  149. data/lib/insika/harvest/gate.rb +194 -0
  150. data/lib/insika/harvest/negative_list.rb +199 -0
  151. data/lib/insika/harvest.rb +241 -0
  152. data/lib/insika/harvest_engine.rb +193 -0
  153. data/lib/insika/harvest_store.rb +548 -0
  154. data/lib/insika/knowledge.rb +680 -0
  155. data/lib/insika/knowledge_store.rb +140 -0
  156. data/lib/insika/mcp_client.rb +94 -0
  157. data/lib/insika/mcp_json.rb +74 -0
  158. data/lib/insika/mcp_live_tool.rb +43 -0
  159. data/lib/insika/mcp_store.rb +98 -26
  160. data/lib/insika/mcp_tool_ingestor.rb +30 -8
  161. data/lib/insika/mcp_tool_registry.rb +100 -0
  162. data/lib/insika/media.rb +382 -0
  163. data/lib/insika/memory_audit_store.rb +85 -0
  164. data/lib/insika/memory_store.rb +264 -23
  165. data/lib/insika/message_origin.rb +7 -2
  166. data/lib/insika/middleware.rb +9 -0
  167. data/lib/insika/model_visible.rb +87 -0
  168. data/lib/insika/model_visible_trace_store.rb +66 -0
  169. data/lib/insika/onboarding.rb +22 -1
  170. data/lib/insika/outbox_store.rb +42 -4
  171. data/lib/insika/outcome_store.rb +147 -0
  172. data/lib/insika/overlay_tool_registry.rb +37 -17
  173. data/lib/insika/packaging.rb +163 -0
  174. data/lib/insika/parity/criterion.rb +79 -0
  175. data/lib/insika/parity/verdict.rb +318 -0
  176. data/lib/insika/prefix_fingerprint.rb +58 -0
  177. data/lib/insika/profile_source.rb +29 -1
  178. data/lib/insika/prompt_catalog.rb +10 -0
  179. data/lib/insika/proposal_store.rb +271 -0
  180. data/lib/insika/queue_policy.rb +4 -1
  181. data/lib/insika/refinement/proposer.rb +1 -1
  182. data/lib/insika/reliability.rb +32 -6
  183. data/lib/insika/retention.rb +316 -0
  184. data/lib/insika/router/app.rb +157 -0
  185. data/lib/insika/router/backend_pool.rb +98 -0
  186. data/lib/insika/router/hash_ring.rb +55 -0
  187. data/lib/insika/router/proxy_body.rb +34 -0
  188. data/lib/insika/router/session_key.rb +54 -0
  189. data/lib/insika/router.rb +18 -0
  190. data/lib/insika/routing.rb +101 -0
  191. data/lib/insika/safety/config.rb +44 -4
  192. data/lib/insika/safety/corpus.rb +255 -0
  193. data/lib/insika/safety/detectors.rb +32 -113
  194. data/lib/insika/safety/factory.rb +15 -2
  195. data/lib/insika/safety/grounding_enforcer.rb +59 -0
  196. data/lib/insika/safety/grounding_validator.rb +49 -0
  197. data/lib/insika/safety/input_guardrail.rb +1 -1
  198. data/lib/insika/safety/moderator.rb +1 -1
  199. data/lib/insika/safety/output_filter.rb +9 -5
  200. data/lib/insika/safety/output_validator.rb +11 -5
  201. data/lib/insika/schedule.rb +177 -0
  202. data/lib/insika/schedule_engine.rb +314 -0
  203. data/lib/insika/schedule_store.rb +208 -0
  204. data/lib/insika/schema_guard.rb +35 -0
  205. data/lib/insika/server/app.rb +247 -17
  206. data/lib/insika/server/rack_app.rb +21 -1
  207. data/lib/insika/server/responses.rb +40 -1
  208. data/lib/insika/session_actor.rb +8 -4
  209. data/lib/insika/session_store.rb +65 -2
  210. data/lib/insika/settings_store.rb +10 -0
  211. data/lib/insika/shadow_pair_store.rb +258 -0
  212. data/lib/insika/skill_catalog.rb +16 -0
  213. data/lib/insika/soak/envelope.rb +140 -0
  214. data/lib/insika/soak/report.rb +392 -0
  215. data/lib/insika/soak/runner.rb +554 -0
  216. data/lib/insika/steer_injector.rb +21 -10
  217. data/lib/insika/store.rb +10 -1
  218. data/lib/insika/stores/memory.rb +6 -0
  219. data/lib/insika/stores/sqlite.rb +8 -0
  220. data/lib/insika/studio/app.rb +1437 -54
  221. data/lib/insika/studio/assets/dist/application.css +1 -1
  222. data/lib/insika/studio/assets/dist/application.js +23 -23
  223. data/lib/insika/studio/assets/dist/favicon.svg +6 -0
  224. data/lib/insika/studio/forms.rb +308 -10
  225. data/lib/insika/studio/nav_icons.rb +19 -1
  226. data/lib/insika/studio/views/_agent_tab_cache.erb +25 -0
  227. data/lib/insika/studio/views/_agent_tab_config.erb +514 -0
  228. data/lib/insika/studio/views/_agent_tab_history.erb +24 -0
  229. data/lib/insika/studio/views/_agent_tab_loops.erb +54 -0
  230. data/lib/insika/studio/views/_agent_tab_memory.erb +51 -0
  231. data/lib/insika/studio/views/_agent_tab_outcomes.erb +31 -0
  232. data/lib/insika/studio/views/_agent_tab_prompts.erb +108 -0
  233. data/lib/insika/studio/views/_agent_tab_skills.erb +38 -0
  234. data/lib/insika/studio/views/_agents_master.erb +44 -0
  235. data/lib/insika/studio/views/_message.erb +49 -32
  236. data/lib/insika/studio/views/agent_detail.erb +62 -278
  237. data/lib/insika/studio/views/agents.erb +71 -54
  238. data/lib/insika/studio/views/approvals.erb +4 -1
  239. data/lib/insika/studio/views/artifact.erb +23 -0
  240. data/lib/insika/studio/views/artifacts.erb +59 -0
  241. data/lib/insika/studio/views/chats.erb +4 -1
  242. data/lib/insika/studio/views/customer.erb +94 -0
  243. data/lib/insika/studio/views/customers.erb +32 -0
  244. data/lib/insika/studio/views/evals.erb +6 -3
  245. data/lib/insika/studio/views/facts.erb +133 -0
  246. data/lib/insika/studio/views/followups.erb +125 -0
  247. data/lib/insika/studio/views/funnel.erb +106 -0
  248. data/lib/insika/studio/views/harvest.erb +234 -0
  249. data/lib/insika/studio/views/home.erb +106 -66
  250. data/lib/insika/studio/views/knowledge.erb +123 -0
  251. data/lib/insika/studio/views/layout.erb +15 -11
  252. data/lib/insika/studio/views/mcp.erb +174 -80
  253. data/lib/insika/studio/views/parity.erb +147 -0
  254. data/lib/insika/studio/views/playground.erb +7 -1
  255. data/lib/insika/studio/views/session.erb +233 -124
  256. data/lib/insika/studio/views/settings.erb +41 -1
  257. data/lib/insika/studio/views/skills.erb +1 -2
  258. data/lib/insika/studio/views/system_files.erb +1 -1
  259. data/lib/insika/studio/views/task.erb +13 -0
  260. data/lib/insika/studio/views/tasks.erb +4 -1
  261. data/lib/insika/studio/views/tools.erb +24 -10
  262. data/lib/insika/task_store.rb +21 -1
  263. data/lib/insika/templates/browser-agent/README.md +36 -0
  264. data/lib/insika/templates/browser-agent/agent.rb +49 -0
  265. data/lib/insika/templates/daily-digest/README.md +38 -0
  266. data/lib/insika/templates/daily-digest/agent.rb +77 -0
  267. data/lib/insika/templates/repo-explorer/README.md +36 -0
  268. data/lib/insika/templates/repo-explorer/agent.rb +45 -0
  269. data/lib/insika/templates/research-analyst/README.md +26 -0
  270. data/lib/insika/templates/research-analyst/agent.rb +58 -0
  271. data/lib/insika/templates/review-panel/README.md +20 -0
  272. data/lib/insika/templates/review-panel/agent.rb +50 -0
  273. data/lib/insika/templates/travel-planner/README.md +35 -0
  274. data/lib/insika/templates/travel-planner/agent.rb +87 -0
  275. data/lib/insika/templates.rb +112 -0
  276. data/lib/insika/testing/store_contract.rb +27 -6
  277. data/lib/insika/tick.rb +47 -11
  278. data/lib/insika/timezone.rb +45 -0
  279. data/lib/insika/tool_definition.rb +17 -7
  280. data/lib/insika/tool_envelope.rb +69 -0
  281. data/lib/insika/tool_manifest.rb +5 -1
  282. data/lib/insika/tools/data_defined_tool.rb +10 -0
  283. data/lib/insika/tools/generate_image.rb +89 -0
  284. data/lib/insika/tools/load_knowledge.rb +74 -0
  285. data/lib/insika/tools/run_persona_eval.rb +328 -0
  286. data/lib/insika/tools/save_artifact.rb +95 -0
  287. data/lib/insika/tools/schedule_followup.rb +164 -0
  288. data/lib/insika/tools/tts.rb +47 -0
  289. data/lib/insika/tools/update_briefing.rb +126 -0
  290. data/lib/insika/turn_output.rb +1 -1
  291. data/lib/insika/turn_state.rb +49 -1
  292. data/lib/insika/turn_timing.rb +22 -2
  293. data/lib/insika/version.rb +1 -1
  294. data/lib/insika/vitals.rb +84 -0
  295. data/lib/insika/wiring/graph.rb +472 -13
  296. data/lib/insika/wiring/graph_chat.rb +102 -0
  297. data/lib/insika.rb +149 -3
  298. metadata +181 -5
  299. data/docs/build.md +0 -14
  300. data/docs/understand.md +0 -10
@@ -11,11 +11,12 @@ module Insika
11
11
  # exactly like any other pack. That is what makes `#profile` identical to a
12
12
  # hand-written equivalent pack (the parity spec) — there is only one path.
13
13
  class Definition
14
- attr_reader :pack, :runtime_options
14
+ attr_reader :pack, :runtime_options, :mcp_instances
15
15
 
16
- def initialize(pack:, runtime: {})
16
+ def initialize(pack:, runtime: {}, mcp_instances: [])
17
17
  @pack = pack
18
18
  @runtime_options = runtime
19
+ @mcp_instances = mcp_instances
19
20
  end
20
21
 
21
22
  def id = pack.config[:id].to_s
@@ -14,8 +14,6 @@ module Insika
14
14
  # Built lazily by Definition#runtime, so `require "insika"` never pays for
15
15
  # ruby_llm or the HTTP server.
16
16
  class Runtime
17
- TERMINAL = %i[task_completed task_failed task_cancelled].freeze
18
-
19
17
  # graph: the assembled Wiring::Graph::Result; pack: the PRIMARY imported
20
18
  # Pack; packs: every imported Pack (one per agent — a System has N, a
21
19
  # Definition has one). All read by the server boot and the specs.
@@ -40,6 +38,11 @@ module Insika
40
38
  # rather than reading a process-wide singleton at call time.
41
39
  @llm = build_llm_context
42
40
  @graph = build_graph
41
+ # the dispatch+drain seam `#chat`/`#run_workflow` delegate to —
42
+ # `Insika::Wiring::GraphChat`, shared with `config/deployment.rb`'s own
43
+ # registration of `run_persona_eval` (see `register_persona_eval_tool`
44
+ # below).
45
+ @chat_runtime = Insika::Wiring::GraphChat.new(graph: @graph)
43
46
  seed_default_model
44
47
  import_packs
45
48
  end
@@ -62,12 +65,8 @@ module Insika
62
65
  # one-shot; a value threads multi-turn memory (created on first use).
63
66
  # `agent:` targets one agent of a system (default: the primary).
64
67
  def chat(message, session_id: nil, timeout: nil, agent: nil)
65
- command = Insika::Command.build(
66
- :send_message,
67
- { agent: (agent || @definition.id).to_s, message: message, session_id: session_id },
68
- transport: :cli
69
- )
70
- run_command(command, session_id: session_id, timeout: timeout)[:text]
68
+ @chat_runtime.chat(message, agent: (agent || @definition.id).to_s,
69
+ session_id: session_id, timeout: timeout)
71
70
  end
72
71
 
73
72
  # Triggers a declared workflow and returns its OUTPUT (the typed value the
@@ -78,7 +77,7 @@ module Insika
78
77
  command = Insika::Command.build(
79
78
  :trigger_workflow, { workflow: name, agent: agent, input: input }, transport: :cli
80
79
  )
81
- outcome = run_command(command, session_id: nil, timeout: timeout)
80
+ outcome = @chat_runtime.run_command(command, session_id: nil, timeout: timeout)
82
81
  outcome.key?(:output) ? outcome[:output] : outcome[:text]
83
82
  end
84
83
 
@@ -93,70 +92,6 @@ module Insika
93
92
 
94
93
  private
95
94
 
96
- # Dispatch + drain, shared by a turn and a workflow run: subscribe BEFORE
97
- # dispatching (the fiber may emit eagerly), bind to the task, read to the
98
- # terminal event. A synchronous handler error (unknown agent/workflow, bad
99
- # input against the schema) propagates from `dispatch` untouched — the
100
- # caller sees the real ValidationError/NotFoundError, not a turn failure.
101
- def run_command(command, session_id:, timeout:)
102
- require "async"
103
- outcome = {}
104
- rejected = nil
105
- Async do |task|
106
- serving = !session_id.nil?
107
- @graph.executor.supervised = true if serving
108
- ensure_session(session_id) if session_id
109
- sub = @graph.event_stream.subscribe
110
- res = begin
111
- @graph.bus.dispatch(command)
112
- rescue StandardError => e
113
- # CAPTURED, not re-raised inside the reactor: letting it escape the
114
- # Async block logs "Task may have ended with unhandled exception" —
115
- # alarming noise for a DOCUMENTED rejection (a schema violation, an
116
- # unknown agent). Re-raised verbatim below, outside the reactor.
117
- sub.close
118
- rejected = e
119
- next
120
- end
121
- sub.bind(task_id: res[:task_id])
122
- drain(sub, outcome, task, timeout)
123
- teardown_serving if serving
124
- end.wait
125
- raise rejected if rejected
126
- raise Insika::Error, "turn #{outcome[:error]}" if outcome[:error]
127
-
128
- outcome
129
- end
130
-
131
- # Reads the stream until this turn reaches a terminal event, then stops.
132
- # A workflow run also carries its typed OUTPUT, which is not the turn text.
133
- def drain(sub, outcome, task, timeout)
134
- reader = task.async do
135
- sub.each do |ev|
136
- case ev.type
137
- when :task_completed then outcome[:text] = ev.data[:content].to_s
138
- when :workflow_completed then outcome[:output] = ev.data[:output]
139
- when :task_failed then outcome[:error] = "failed: #{ev.data[:message] || ev.data[:error]}"
140
- when :task_cancelled then outcome[:error] = "cancelled"
141
- end
142
- sub.close if TERMINAL.include?(ev.type)
143
- end
144
- end
145
- timeout ? task.with_timeout(timeout) { reader.wait } : reader.wait
146
- rescue Async::TimeoutError
147
- outcome[:error] = "timed out after #{timeout}s"
148
- sub.close
149
- end
150
-
151
- def ensure_session(session_id)
152
- @graph.session_store.find(session_id) || @graph.session_store.create(id: session_id, vars: {})
153
- end
154
-
155
- def teardown_serving
156
- @graph.executor.stop_session_actors
157
- @graph.executor.instance_variable_get(:@supervisor)&.stop
158
- end
159
-
160
95
  # --- graph assembly (mirrors config/deployment.rb, generically) ------
161
96
 
162
97
  def build_graph
@@ -178,10 +113,25 @@ module Insika
178
113
  guardrails: c[:guardrails], context_providers: context_providers(spine, c),
179
114
  edge_limiter: c[:edge_limiter],
180
115
  executor_extra: { settings_store: c[:settings_store], tool_trace_store: c[:tool_trace_store],
181
- context_trace_store: c[:context_trace_store], llm: @llm }
116
+ context_trace_store: c[:context_trace_store],
117
+ cache_series_store: c[:cache_series_store], llm: @llm }
182
118
  )
183
119
  register_authoring_commands(graph, c)
184
120
  register_workflows(graph)
121
+ import_mcp_declarations(c)
122
+ # `run_persona_eval` (C3.1): the SAME registration `config/deployment.rb`
123
+ # does for its own graph — see `Wiring::Graph.register_persona_eval_tool`.
124
+ # One implementation, both roots; this one just already has its
125
+ # `golden_store`/`settings_store` at hand from `build_components`.
126
+ Insika::Wiring::Graph.register_persona_eval_tool(
127
+ graph, golden_store: Insika::GoldenStore.new(config_store: c[:config_store]),
128
+ settings_store: c[:settings_store], llm: @llm
129
+ )
130
+ # Same boot step the server roots run: an installed `insika-plugin-*`
131
+ # gem (or INSIKA_PLUGIN_DIR) extends a DSL-run agent too — a plugin
132
+ # works everywhere or it is not a plugin. No bundled root here: the DSL
133
+ # runs outside the repo checkout.
134
+ Insika::Wiring::Graph.load_plugins(graph)
185
135
  graph
186
136
  end
187
137
 
@@ -209,15 +159,42 @@ module Insika
209
159
  ))
210
160
  end
211
161
 
162
+ # Upserts every DSL-declared `mcp` instance into the McpStore at boot.
163
+ # Code is the TEMPLATE — transport/command/args/url/
164
+ # description always follow the declaration — but `enabled` and the
165
+ # credentials (`env`/`headers`) are the operator's once the instance
166
+ # exists: an already-present record's own values are re-sent verbatim
167
+ # instead of the DSL's, so a Studio/CLI/API edit made after boot is never
168
+ # clobbered by the next restart (motor-vs-forja: the store's DATA wins).
169
+ def import_mcp_declarations(c)
170
+ declared = Array(@definition.mcp_instances)
171
+ return if declared.empty?
172
+
173
+ mcp_store = c[:mcp_store]
174
+ declared.each do |decl|
175
+ attrs = decl.dup
176
+ existing = mcp_store.get_raw(attrs[:name])
177
+ if existing
178
+ attrs[:enabled] = existing["enabled"]
179
+ attrs[:env] = existing["env"]
180
+ attrs[:headers] = existing["headers"]
181
+ end
182
+ mcp_store.upsert(attrs)
183
+ end
184
+ end
185
+
212
186
  def build_components(backend, spine)
213
187
  config_store = Insika::ConfigStore.new(store: backend)
214
188
  settings_store = Insika::SettingsStore.new(config_store: config_store)
215
189
  provider_store = Insika::LLMProviderStore.new(config_store: config_store)
216
190
  tool_store = Insika::ToolStore.new(config_store: config_store)
217
191
  skill_store = Insika::SkillStore.new(config_store: config_store)
192
+ mcp_store = Insika::McpStore.new(config_store: config_store)
193
+ mcp_tool_registry = Insika::McpToolRegistry.new(mcp_store: mcp_store)
218
194
  tool_registry = Insika::OverlayToolRegistry.new(
219
195
  base: spine.code_tool_registry, tool_store: tool_store,
220
- http: Insika::HttpClient.new, event_stream: spine.event_stream
196
+ http: Insika::HttpClient.new, event_stream: spine.event_stream,
197
+ mcp_registry: mcp_tool_registry
221
198
  )
222
199
  {
223
200
  backend: backend, config_store: config_store,
@@ -226,9 +203,11 @@ module Insika
226
203
  agent_file_store: Insika::AgentFileStore.new(config_store: config_store),
227
204
  skill_store: skill_store, tool_store: tool_store,
228
205
  system_file_store: Insika::SystemFileStore.new(config_store: config_store),
229
- mcp_store: Insika::McpStore.new(config_store: config_store),
206
+ mcp_store: mcp_store, mcp_tool_registry: mcp_tool_registry,
230
207
  tool_trace_store: Insika::ToolTraceStore.new(store: backend),
231
208
  context_trace_store: Insika::ContextTraceStore.new(store: backend),
209
+ # the per-AGENT cache-hit series (the Studio agent-detail card).
210
+ cache_series_store: Insika::CacheSeriesStore.new(store: backend),
232
211
  tool_registry: tool_registry,
233
212
  tool_catalog: Insika::ToolCatalog.new(tool_registry: tool_registry),
234
213
  skill_catalog: Insika::SkillCatalog.new([], store: skill_store),
@@ -249,15 +228,16 @@ module Insika
249
228
  [
250
229
  Insika::Context::Providers::Request.new,
251
230
  Insika::Context::Providers::Prompt.new(
252
- base: "", files: [], catalog: c[:prompt_catalog],
231
+ base: "", catalog: c[:prompt_catalog],
253
232
  agent_files: c[:agent_file_store], system_files: c[:system_file_store]
254
233
  ),
255
234
  Insika::Context::Providers::Skill.new(catalog: c[:skill_catalog]),
256
235
  Insika::Context::Providers::SkillTrigger.new(catalog: c[:skill_catalog]),
257
236
  Insika::Context::Providers::ToolSearch.new(catalog: c[:tool_catalog]),
258
237
  Insika::Context::Providers::Memory.new(store: spine.memory_store),
238
+ Insika::Context::Providers::Knowledge.new(store: spine.knowledge_store),
259
239
  Insika::Context::Providers::Session.new(session_store: spine.session_store)
260
- ].freeze
240
+ ] # NOT frozen: load_plugins appends plugin providers at boot
261
241
  end
262
242
 
263
243
  # The runtime-authoring surface a pack import needs (create_agent +
@@ -267,6 +247,7 @@ module Insika
267
247
  bus = graph.bus
268
248
  es = graph.event_stream
269
249
  mem = graph.memory_store
250
+ audit = graph.memory_audit_store
270
251
  bus.register(:create_agent, Insika::Commands::CreateAgent.new(profile_source: c[:profile_source], event_stream: es))
271
252
  bus.register(:update_agent, Insika::Commands::UpdateAgent.new(profile_source: c[:profile_source], event_stream: es))
272
253
  bus.register(:delete_agent, Insika::Commands::DeleteAgent.new(profile_source: c[:profile_source], event_stream: es))
@@ -276,20 +257,26 @@ module Insika
276
257
  bus.register(:restore_agent_file, Insika::Commands::RestoreAgentFile.new(profile_source: c[:profile_source], agent_file_store: c[:agent_file_store], event_stream: es))
277
258
  bus.register(:write_skill, Insika::Commands::WriteSkill.new(skill_store: c[:skill_store], skill_catalog: c[:skill_catalog], event_stream: es))
278
259
  bus.register(:set_skill_agents, Insika::Commands::SetSkillAgents.new(profile_source: c[:profile_source], event_stream: es))
279
- bus.register(:memory_put_fact, Insika::Commands::MemoryPutFact.new(memory_store: mem, event_stream: es))
280
- bus.register(:memory_forget_fact, Insika::Commands::MemoryForgetFact.new(memory_store: mem, event_stream: es))
260
+ bus.register(:memory_put_fact, Insika::Commands::MemoryPutFact.new(memory_store: mem, event_stream: es, audit_store: audit))
261
+ bus.register(:memory_forget_fact, Insika::Commands::MemoryForgetFact.new(memory_store: mem, event_stream: es, audit_store: audit))
281
262
  bus.register(:memory_add_note, Insika::Commands::MemoryAddNote.new(memory_store: mem, event_stream: es))
263
+ # the LGPD access right — the Studio Customers drill exports.
264
+ bus.register(:export_customer_memory, Insika::Commands::ExportCustomerMemory.new(memory_store: mem, event_stream: es))
282
265
  bus.register(:update_settings, Insika::Commands::UpdateSettings.new(settings_store: c[:settings_store], event_stream: es))
283
266
  bus.register(:upsert_llm_provider, Insika::Commands::UpsertLLMProvider.new(provider_store: c[:provider_store], configurator: c[:configurator], event_stream: es))
284
267
  bus.register(:delete_llm_provider, Insika::Commands::DeleteLLMProvider.new(provider_store: c[:provider_store], configurator: c[:configurator], event_stream: es))
285
- bus.register(:upsert_mcp, Insika::Commands::UpsertMcp.new(mcp_store: c[:mcp_store], event_stream: es))
286
- bus.register(:delete_mcp, Insika::Commands::DeleteMcp.new(mcp_store: c[:mcp_store], event_stream: es))
268
+ bus.register(:upsert_mcp, Insika::Commands::UpsertMcp.new(mcp_store: c[:mcp_store], mcp_registry: c[:mcp_tool_registry], event_stream: es))
269
+ bus.register(:delete_mcp, Insika::Commands::DeleteMcp.new(mcp_store: c[:mcp_store], mcp_registry: c[:mcp_tool_registry], event_stream: es))
270
+ bus.register(:refresh_mcp_tools, Insika::Commands::RefreshMcpTools.new(mcp_registry: c[:mcp_tool_registry], event_stream: es))
287
271
  bus.register(:write_system_file, Insika::Commands::WriteSystemFile.new(system_file_store: c[:system_file_store], event_stream: es))
288
272
  bus.register(:delete_system_file, Insika::Commands::DeleteSystemFile.new(system_file_store: c[:system_file_store], event_stream: es))
289
273
  bus.register(:restore_system_file, Insika::Commands::RestoreSystemFile.new(system_file_store: c[:system_file_store], event_stream: es))
290
274
  bus.register(:write_data_tool, Insika::Commands::WriteDataTool.new(tool_store: c[:tool_store], registry: c[:tool_registry], tool_catalog: c[:tool_catalog], event_stream: es))
291
275
  bus.register(:delete_data_tool, Insika::Commands::DeleteDataTool.new(tool_store: c[:tool_store], registry: c[:tool_registry], tool_catalog: c[:tool_catalog], event_stream: es))
292
276
  bus.register(:restore_data_tool, Insika::Commands::RestoreDataTool.new(tool_store: c[:tool_store], registry: c[:tool_registry], tool_catalog: c[:tool_catalog], event_stream: es))
277
+ bus.register(:write_concept, Insika::Commands::WriteConcept.new(knowledge_store: graph.knowledge_store, event_stream: es))
278
+ bus.register(:delete_concept, Insika::Commands::DeleteConcept.new(knowledge_store: graph.knowledge_store, event_stream: es))
279
+ bus.register(:restore_concept, Insika::Commands::RestoreConcept.new(knowledge_store: graph.knowledge_store, event_stream: es))
293
280
  end
294
281
 
295
282
  # --- RubyLLM configuration ------------------------------------------
@@ -31,6 +31,11 @@ module Insika
31
31
  end
32
32
 
33
33
  def run
34
+ # The boot refusal (shadow + no criterion) lives in the AppBuilder; run it
35
+ # BEFORE the Studio is configured so the criterion the Studio folds is the
36
+ # same object the delivery path stamps pairs with.
37
+ @builder.channels?
38
+ register_parity_commands
34
39
  configure_studio
35
40
  dispatch = Rack::URLMap.new("/studio" => Studio::App, "/" => @builder.app)
36
41
  endpoint = Async::HTTP::Endpoint.parse("http://#{@host}:#{@port}")
@@ -46,6 +51,10 @@ module Insika
46
51
  Insika::Shutdown.install(executor: @graph.executor)
47
52
  Async do
48
53
  @graph.executor.supervised = true # serving mode: turns survive disconnects
54
+ # forces the tick/alert/distill/harvest supervisor up now — otherwise a
55
+ # deployment with only scheduled agents (no live chat) never ticks until
56
+ # unrelated traffic happens to hit turn_parent first.
57
+ @graph.executor.start_supervisor!
49
58
  Insika::Telemetry.attach(event_stream: @graph.event_stream, recorder: telemetry)
50
59
  Async::HTTP::Server.new(middleware, endpoint).run
51
60
  end
@@ -56,6 +65,23 @@ module Insika
56
65
  def workflows? = @builder.workflows?
57
66
  def channels? = @builder.channels?
58
67
 
68
+ # The parity page renders whenever a shadow channel exists, so the judge
69
+ # button must reach a registered command — the deployment bus registers the
70
+ # same one (config/deployment.rb); a page that answers "unknown command"
71
+ # would make this root a second-class citizen. Registered only when the
72
+ # criterion loaded (a command without one would be a fake judge).
73
+ def register_parity_commands
74
+ criterion = @builder.criterion
75
+ return unless criterion
76
+
77
+ @graph.bus.register(:judge_shadow_pairs,
78
+ Insika::Commands::JudgeShadowPairs.new(
79
+ shadow_pairs: @graph.shadow_pair_store,
80
+ settings_store: @rt.component(:settings_store),
81
+ criterion: criterion, event_stream: @graph.event_stream
82
+ ))
83
+ end
84
+
59
85
  def configure_studio
60
86
  Studio::App.configure(
61
87
  command_bus: @graph.bus, profile_source: @graph.profiles,
@@ -64,16 +90,81 @@ module Insika
64
90
  agent_file_store: @rt.component(:agent_file_store), skill_store: @rt.component(:skill_store),
65
91
  skill_catalog: @graph.skill_catalog, tool_catalog: @graph.tool_catalog,
66
92
  tool_store: @rt.component(:tool_store), memory_store: @graph.memory_store,
93
+ # the Customers drill renders the audit lines.
94
+ memory_audit_store: @graph.memory_audit_store,
67
95
  session_store: @graph.session_store,
68
96
  settings_store: @rt.component(:settings_store), llm_provider_store: @rt.component(:provider_store),
69
97
  mcp_store: @rt.component(:mcp_store), system_file_store: @rt.component(:system_file_store),
70
98
  tool_trace_store: @rt.component(:tool_trace_store),
71
99
  context_trace_store: @rt.component(:context_trace_store),
100
+ # the per-agent cache-hit series (agent-detail card).
101
+ cache_series_store: @rt.component(:cache_series_store),
72
102
  task_store: @graph.task_store, checkpoint_store: @graph.checkpoint_store,
73
- pending_action_store: @graph.pending_action_store
103
+ pending_action_store: @graph.pending_action_store,
104
+ # WS7: the agents-grid scorecard reads the outcome store.
105
+ outcome_store: @graph.outcome_store,
106
+ # the outcome-funnel page reads the fold's cells and the
107
+ # BudgetLedger's current counters (D6); the only mutation — the
108
+ # baseline freeze — dispatches :freeze_funnel_baseline on the bus.
109
+ funnel_store: @graph.funnel_store,
110
+ budget_ledger: @graph.budget_ledger,
111
+ # the parity page (only rendered when a shadow channel exists —
112
+ # the nav row keys off the registry).
113
+ shadow_pair_store: @graph.shadow_pair_store,
114
+ parity_criterion: @builder.criterion,
115
+ channel_registry: @graph.channel_registry,
116
+ # the Follow-ups page reads the stores directly;
117
+ # its only mutations (cancel, force-revoke) dispatch bus commands.
118
+ followup_store: @graph.followup_store,
119
+ contact_store: @graph.contact_store,
120
+ # the Facts (wiki) page reads the proposal store
121
+ # directly; its mutations dispatch :resolve_proposal on the bus.
122
+ proposal_store: @graph.proposal_store,
123
+ # the Harvest page reads the harvest store + the two
124
+ # pre-registered artifacts directly; its mutations dispatch the
125
+ # harvest bus commands. The criterion/negative list load best-effort
126
+ # (nil = the page renders the refusal/empty states).
127
+ harvest_store: @graph.harvest_store,
128
+ harvest_criterion: harvest_criterion,
129
+ negative_list: harvest_negative_list,
130
+ # the Knowledge page reads the store directly; its
131
+ # mutations (write/delete/restore) dispatch bus commands.
132
+ knowledge_store: @graph.knowledge_store,
133
+ # the Artifacts tab reads the report store; the signing
134
+ # config (nil without INSIKA_ARTIFACT_SIGNING_KEY) decides whether a
135
+ # signed sharing link exists at all.
136
+ artifact_store: @graph.artifact_store,
137
+ artifact_signing: artifact_signing
74
138
  )
75
139
  end
76
140
 
141
+ # -> { key:, ttl:, base_url: } | nil (no signing key -> no signed surface).
142
+ def artifact_signing
143
+ key = Insika::EnvSchema.read("INSIKA_ARTIFACT_SIGNING_KEY")
144
+ return nil if key.to_s.empty?
145
+
146
+ { key: key,
147
+ ttl: (Insika::EnvSchema.read("INSIKA_ARTIFACT_SIGNING_TTL") || 604_800).to_i,
148
+ base_url: Insika::Coercion.presence(Insika::EnvSchema.read("INSIKA_PUBLIC_URL")).to_s }
149
+ end
150
+
151
+ # The frozen criterion — best-effort at boot: a bare install
152
+ # without the env var renders the ruler's hole instead of
153
+ # crashing the Studio.
154
+ def harvest_criterion
155
+ path = Insika::EnvSchema.read("INSIKA_HARVEST_CRITERION")
156
+ path && Insika::Harvest::Criterion.load(path)
157
+ rescue Insika::ConfigError, Insika::ValidationError
158
+ nil
159
+ end
160
+
161
+ def harvest_negative_list
162
+ path = Insika::EnvSchema.read("INSIKA_HARVEST_NEGATIVE")
163
+ path && Insika::Harvest::NegativeList.parse(File.read(path))
164
+ rescue Errno::ENOENT
165
+ nil
166
+ end
167
+
77
168
  def persistence
78
169
  @graph.durable? ? "durable (sqlite)" : "ephemeral (memory)"
79
170
  end
@@ -15,13 +15,21 @@ module Insika
15
15
  # require the child agents to be resolvable in the SAME ProfileSource. A
16
16
  # Definition owns exactly one pack, so those patterns had no home in the DSL.
17
17
  class System
18
- attr_reader :definitions, :workflows, :runtime_options
18
+ attr_reader :definitions, :workflows, :mcp_instances, :runtime_options
19
19
 
20
20
  # `backend`: the store this system's graph owns. nil = the
21
21
  # historic path (INSIKA_DB, or memory when unset). Set by `Insika.embed`.
22
- def initialize(definitions:, workflows: [], runtime: {}, backend: nil)
22
+ def initialize(definitions:, workflows: [], mcp_instances: [], runtime: {}, backend: nil)
23
23
  @definitions = definitions.freeze
24
24
  @workflows = workflows.freeze
25
+ # MCP instances are global to the graph (one McpStore, not per-agent),
26
+ # so a system-level `mcp` declaration and one nested inside a member
27
+ # `agent { … }` block land in the same set. A name declared both places
28
+ # is NOT an error here (unlike within one collection) — the later one
29
+ # (system-level, applied last) simply wins; Insika::DSL::Runtime upserts
30
+ # this combined list once at boot.
31
+ @mcp_instances = (definitions.flat_map(&:mcp_instances) + mcp_instances)
32
+ .each_with_object({}) { |m, acc| acc[m[:name]] = m }.values.freeze
25
33
  @backend = backend
26
34
  # Agent-level runtime knobs merged in declaration order, then the
27
35
  # system-level ones on top (an explicit `provider`/`api_key` in the