insika 0.0.1 → 0.1.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 (260) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +295 -0
  3. data/LICENSE +21 -0
  4. data/README.md +136 -2
  5. data/bin/insika +351 -0
  6. data/docs/AGENTS.md +494 -0
  7. data/docs/ARCHITECTURE.md +333 -0
  8. data/docs/BENCHMARK.md +114 -0
  9. data/docs/CHANNELS.md +453 -0
  10. data/docs/CONTEXT.md +100 -0
  11. data/docs/DEPLOY.md +334 -0
  12. data/docs/EMBEDDING.md +194 -0
  13. data/docs/EVALS.md +273 -0
  14. data/docs/LOADTEST.md +231 -0
  15. data/docs/OBSERVABILITY.md +365 -0
  16. data/docs/PLUGINS.md +211 -0
  17. data/docs/REFINEMENT.md +477 -0
  18. data/docs/RELEASING.md +70 -0
  19. data/docs/RUNNING-LOCAL.md +153 -0
  20. data/docs/SANDBOX.md +114 -0
  21. data/docs/SECURITY.md +362 -0
  22. data/docs/SKILLS.md +98 -0
  23. data/docs/TOOLS.md +302 -0
  24. data/docs/WHY.md +137 -0
  25. data/docs/WORKFLOWS.md +225 -0
  26. data/docs/build.md +14 -0
  27. data/docs/index.md +68 -0
  28. data/docs/onboarding/start.md +126 -0
  29. data/docs/operate.md +12 -0
  30. data/docs/ship.md +10 -0
  31. data/docs/understand.md +10 -0
  32. data/lib/insika/agent_file_store.rb +125 -0
  33. data/lib/insika/agent_profile.rb +188 -0
  34. data/lib/insika/allowlist.rb +28 -0
  35. data/lib/insika/baseline_store.rb +74 -0
  36. data/lib/insika/capability/resolved_tool.rb +34 -0
  37. data/lib/insika/capability_registry.rb +112 -0
  38. data/lib/insika/channel_delivery.rb +150 -0
  39. data/lib/insika/channel_registry.rb +30 -0
  40. data/lib/insika/channels/relay.rb +178 -0
  41. data/lib/insika/channels/web/widget.js +283 -0
  42. data/lib/insika/channels/web.rb +211 -0
  43. data/lib/insika/chat_builder.rb +254 -0
  44. data/lib/insika/checkpoint.rb +13 -0
  45. data/lib/insika/checkpoint_store.rb +153 -0
  46. data/lib/insika/coercion.rb +50 -0
  47. data/lib/insika/command.rb +32 -0
  48. data/lib/insika/command_bus.rb +39 -0
  49. data/lib/insika/commands/agent_payload.rb +41 -0
  50. data/lib/insika/commands/approve_action.rb +46 -0
  51. data/lib/insika/commands/cancel_task.rb +33 -0
  52. data/lib/insika/commands/create_agent.rb +54 -0
  53. data/lib/insika/commands/create_session.rb +67 -0
  54. data/lib/insika/commands/delete_agent.rb +33 -0
  55. data/lib/insika/commands/delete_agent_file.rb +50 -0
  56. data/lib/insika/commands/delete_data_tool.rb +33 -0
  57. data/lib/insika/commands/delete_llm_provider.rb +36 -0
  58. data/lib/insika/commands/delete_mcp.rb +30 -0
  59. data/lib/insika/commands/delete_system_file.rb +29 -0
  60. data/lib/insika/commands/gate_refinement.rb +245 -0
  61. data/lib/insika/commands/import_mcp_tools.rb +48 -0
  62. data/lib/insika/commands/import_tools.rb +81 -0
  63. data/lib/insika/commands/memory_add_note.rb +32 -0
  64. data/lib/insika/commands/memory_forget_fact.rb +32 -0
  65. data/lib/insika/commands/memory_put_fact.rb +35 -0
  66. data/lib/insika/commands/pause_task.rb +29 -0
  67. data/lib/insika/commands/resolve_refinement.rb +126 -0
  68. data/lib/insika/commands/restore_agent_file.rb +36 -0
  69. data/lib/insika/commands/restore_data_tool.rb +34 -0
  70. data/lib/insika/commands/restore_system_file.rb +31 -0
  71. data/lib/insika/commands/resume_task.rb +85 -0
  72. data/lib/insika/commands/run_refinement.rb +133 -0
  73. data/lib/insika/commands/send_message.rb +150 -0
  74. data/lib/insika/commands/set_agent_tools.rb +39 -0
  75. data/lib/insika/commands/set_skill_agents.rb +71 -0
  76. data/lib/insika/commands/trigger_workflow.rb +80 -0
  77. data/lib/insika/commands/update_agent.rb +49 -0
  78. data/lib/insika/commands/update_settings.rb +33 -0
  79. data/lib/insika/commands/upsert_llm_provider.rb +34 -0
  80. data/lib/insika/commands/upsert_mcp.rb +32 -0
  81. data/lib/insika/commands/write_agent_file.rb +57 -0
  82. data/lib/insika/commands/write_data_tool.rb +43 -0
  83. data/lib/insika/commands/write_golden.rb +58 -0
  84. data/lib/insika/commands/write_skill.rb +50 -0
  85. data/lib/insika/commands/write_system_file.rb +31 -0
  86. data/lib/insika/config_store.rb +85 -0
  87. data/lib/insika/context/builder.rb +166 -0
  88. data/lib/insika/context/catalog_provider.rb +23 -0
  89. data/lib/insika/context/fragment.rb +19 -0
  90. data/lib/insika/context/priority.rb +29 -0
  91. data/lib/insika/context/provider.rb +19 -0
  92. data/lib/insika/context/providers/memory.rb +60 -0
  93. data/lib/insika/context/providers/prompt.rb +105 -0
  94. data/lib/insika/context/providers/request.rb +32 -0
  95. data/lib/insika/context/providers/session.rb +108 -0
  96. data/lib/insika/context/providers/skill.rb +20 -0
  97. data/lib/insika/context/providers/tool_search.rb +20 -0
  98. data/lib/insika/delegation_store.rb +153 -0
  99. data/lib/insika/doctor.rb +294 -0
  100. data/lib/insika/dsl/definition.rb +55 -0
  101. data/lib/insika/dsl/runtime.rb +379 -0
  102. data/lib/insika/dsl/server_boot.rb +97 -0
  103. data/lib/insika/dsl/system.rb +93 -0
  104. data/lib/insika/dsl/workflow_adapter.rb +59 -0
  105. data/lib/insika/dsl.rb +307 -0
  106. data/lib/insika/edge_limiter.rb +130 -0
  107. data/lib/insika/egress_guard.rb +75 -0
  108. data/lib/insika/env_schema.rb +246 -0
  109. data/lib/insika/errors.rb +145 -0
  110. data/lib/insika/evals/assertions.rb +247 -0
  111. data/lib/insika/evals/baseline.rb +69 -0
  112. data/lib/insika/evals/golden.rb +172 -0
  113. data/lib/insika/evals/judge.rb +225 -0
  114. data/lib/insika/evals/pairwise.rb +178 -0
  115. data/lib/insika/evals/report.rb +115 -0
  116. data/lib/insika/evals/runner.rb +141 -0
  117. data/lib/insika/evals/transport.rb +178 -0
  118. data/lib/insika/event.rb +18 -0
  119. data/lib/insika/event_stream.rb +114 -0
  120. data/lib/insika/executor.rb +1680 -0
  121. data/lib/insika/frontmatter.rb +42 -0
  122. data/lib/insika/golden_store.rb +145 -0
  123. data/lib/insika/hooks.rb +48 -0
  124. data/lib/insika/http_client.rb +63 -0
  125. data/lib/insika/inbound_log.rb +84 -0
  126. data/lib/insika/llm_configurator.rb +99 -0
  127. data/lib/insika/llm_provider_store.rb +83 -0
  128. data/lib/insika/mcp_http_client.rb +67 -0
  129. data/lib/insika/mcp_store.rb +115 -0
  130. data/lib/insika/mcp_tool_ingestor.rb +143 -0
  131. data/lib/insika/memory_store.rb +93 -0
  132. data/lib/insika/message_origin.rb +76 -0
  133. data/lib/insika/middleware.rb +36 -0
  134. data/lib/insika/model_policy.rb +52 -0
  135. data/lib/insika/model_resolver.rb +176 -0
  136. data/lib/insika/model_selection.rb +114 -0
  137. data/lib/insika/onboarding.rb +208 -0
  138. data/lib/insika/outbox_store.rb +166 -0
  139. data/lib/insika/overlay_tool_registry.rb +103 -0
  140. data/lib/insika/pack.rb +102 -0
  141. data/lib/insika/pack_importer.rb +121 -0
  142. data/lib/insika/pending_action_store.rb +120 -0
  143. data/lib/insika/plugin/loader.rb +356 -0
  144. data/lib/insika/plugin.rb +35 -0
  145. data/lib/insika/policy/engine.rb +83 -0
  146. data/lib/insika/policy/policy.rb +120 -0
  147. data/lib/insika/policy_registry.rb +23 -0
  148. data/lib/insika/profile_source.rb +137 -0
  149. data/lib/insika/prompt_catalog.rb +61 -0
  150. data/lib/insika/queue_policy.rb +167 -0
  151. data/lib/insika/recovery.rb +127 -0
  152. data/lib/insika/refinement/candidate.rb +159 -0
  153. data/lib/insika/refinement/evidence_collector.rb +371 -0
  154. data/lib/insika/refinement/gate.rb +234 -0
  155. data/lib/insika/refinement/panel.rb +222 -0
  156. data/lib/insika/refinement/proposer.rb +262 -0
  157. data/lib/insika/refinement_store.rb +295 -0
  158. data/lib/insika/registry.rb +59 -0
  159. data/lib/insika/safety/config.rb +109 -0
  160. data/lib/insika/safety/detectors.rb +176 -0
  161. data/lib/insika/safety/factory.rb +102 -0
  162. data/lib/insika/safety/input_guardrail.rb +87 -0
  163. data/lib/insika/safety/moderator.rb +86 -0
  164. data/lib/insika/safety/output_filter.rb +79 -0
  165. data/lib/insika/safety/output_validator.rb +101 -0
  166. data/lib/insika/safety/safe_responses.rb +47 -0
  167. data/lib/insika/sandbox/boundary.rb +93 -0
  168. data/lib/insika/sandbox/docker.rb +74 -0
  169. data/lib/insika/sandbox/local.rb +33 -0
  170. data/lib/insika/sandbox/runner.rb +80 -0
  171. data/lib/insika/sandbox.rb +85 -0
  172. data/lib/insika/schema_guard.rb +147 -0
  173. data/lib/insika/secret_masking.rb +34 -0
  174. data/lib/insika/server/a2a/agent_card.rb +27 -0
  175. data/lib/insika/server/a2a/app.rb +112 -0
  176. data/lib/insika/server/a2a/client.rb +101 -0
  177. data/lib/insika/server/a2a/errors.rb +32 -0
  178. data/lib/insika/server/a2a/http.rb +42 -0
  179. data/lib/insika/server/a2a/message.rb +27 -0
  180. data/lib/insika/server/a2a/protocol.rb +45 -0
  181. data/lib/insika/server/a2a/remotes.rb +25 -0
  182. data/lib/insika/server/a2a/task_projection.rb +40 -0
  183. data/lib/insika/server/admin_auth.rb +29 -0
  184. data/lib/insika/server/app.rb +850 -0
  185. data/lib/insika/server/boot.rb +119 -0
  186. data/lib/insika/server/rack_app.rb +110 -0
  187. data/lib/insika/server/responses.rb +155 -0
  188. data/lib/insika/server/sse_body.rb +96 -0
  189. data/lib/insika/session_actor.rb +162 -0
  190. data/lib/insika/session_store.rb +143 -0
  191. data/lib/insika/settings_store.rb +154 -0
  192. data/lib/insika/shutdown.rb +125 -0
  193. data/lib/insika/skill_catalog.rb +113 -0
  194. data/lib/insika/skill_store.rb +79 -0
  195. data/lib/insika/steer_injector.rb +110 -0
  196. data/lib/insika/store.rb +52 -0
  197. data/lib/insika/stores/memory.rb +123 -0
  198. data/lib/insika/stores/sqlite.rb +183 -0
  199. data/lib/insika/studio/app.rb +1571 -0
  200. data/lib/insika/studio/assets/dist/application.css +1 -0
  201. data/lib/insika/studio/assets/dist/application.js +69 -0
  202. data/lib/insika/studio/forms.rb +340 -0
  203. data/lib/insika/studio/nav_icons.rb +31 -0
  204. data/lib/insika/studio/views/_message.erb +44 -0
  205. data/lib/insika/studio/views/agent_detail.erb +285 -0
  206. data/lib/insika/studio/views/agents.erb +63 -0
  207. data/lib/insika/studio/views/approvals.erb +41 -0
  208. data/lib/insika/studio/views/chats.erb +34 -0
  209. data/lib/insika/studio/views/evals.erb +83 -0
  210. data/lib/insika/studio/views/home.erb +72 -0
  211. data/lib/insika/studio/views/layout.erb +94 -0
  212. data/lib/insika/studio/views/login.erb +17 -0
  213. data/lib/insika/studio/views/mcp.erb +91 -0
  214. data/lib/insika/studio/views/not_found.erb +5 -0
  215. data/lib/insika/studio/views/playground.erb +47 -0
  216. data/lib/insika/studio/views/refinement.erb +234 -0
  217. data/lib/insika/studio/views/session.erb +62 -0
  218. data/lib/insika/studio/views/settings.erb +173 -0
  219. data/lib/insika/studio/views/skills.erb +86 -0
  220. data/lib/insika/studio/views/system_files.erb +65 -0
  221. data/lib/insika/studio/views/task.erb +105 -0
  222. data/lib/insika/studio/views/tasks.erb +33 -0
  223. data/lib/insika/studio/views/tool_edit.erb +107 -0
  224. data/lib/insika/studio/views/tools.erb +89 -0
  225. data/lib/insika/subagent_graph.rb +96 -0
  226. data/lib/insika/system_file_store.rb +96 -0
  227. data/lib/insika/task_actor.rb +128 -0
  228. data/lib/insika/task_store.rb +250 -0
  229. data/lib/insika/telemetry/pricing.rb +104 -0
  230. data/lib/insika/telemetry/recorder.rb +228 -0
  231. data/lib/insika/telemetry.rb +127 -0
  232. data/lib/insika/testing/store_contract.rb +270 -0
  233. data/lib/insika/token_estimator.rb +16 -0
  234. data/lib/insika/tool_assembly.rb +140 -0
  235. data/lib/insika/tool_catalog.rb +89 -0
  236. data/lib/insika/tool_definition.rb +518 -0
  237. data/lib/insika/tool_envelope.rb +140 -0
  238. data/lib/insika/tool_manifest.rb +218 -0
  239. data/lib/insika/tool_registry.rb +21 -0
  240. data/lib/insika/tool_store.rb +135 -0
  241. data/lib/insika/tool_trace_store.rb +92 -0
  242. data/lib/insika/tools/a2a_remote.rb +48 -0
  243. data/lib/insika/tools/agent_enum.rb +68 -0
  244. data/lib/insika/tools/concurrency.rb +54 -0
  245. data/lib/insika/tools/data_defined_tool.rb +220 -0
  246. data/lib/insika/tools/load_skill.rb +41 -0
  247. data/lib/insika/tools/remember.rb +53 -0
  248. data/lib/insika/tools/subagent.rb +75 -0
  249. data/lib/insika/tools/subagents.rb +77 -0
  250. data/lib/insika/tools/tool_search.rb +94 -0
  251. data/lib/insika/turn_output.rb +139 -0
  252. data/lib/insika/turn_state.rb +158 -0
  253. data/lib/insika/turn_timing.rb +56 -0
  254. data/lib/insika/usage_ledger.rb +47 -0
  255. data/lib/insika/version.rb +3 -1
  256. data/lib/insika/wiring/graph.rb +198 -0
  257. data/lib/insika/workflow.rb +185 -0
  258. data/lib/insika/workflow_registry.rb +33 -0
  259. data/lib/insika.rb +203 -4
  260. metadata +395 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 322be62a1519f7aaf61e4f0012f18be315cac9ae276b2dae62a4c0b1c41eea98
4
- data.tar.gz: f78e5f6aee96aa0fcf17d703827da9aa4784146f512aa8724474ed161b6c7b23
3
+ metadata.gz: 73bf4ba4e364c79f87d77202bc0f667bae1a9ea46b1c4b880350b10f4b2325cc
4
+ data.tar.gz: d1cfc50cc5968f041f4edf4ad803b33fbcbaef7ec68930fc00543d8bf30a7a8d
5
5
  SHA512:
6
- metadata.gz: 6f6739512bf4d07534faee7675fd157c2bcc5b95bfcbabbe9aa7acfc0fd03dede861140562aa5173ee751dcbef9108bd4a7a64bc8776a2d9da3a95aa9912d7d5
7
- data.tar.gz: 83fee89e2b6041280bb0442da9e50628c6a23dc951513c2b6a742bf6c2a205075f23711ad06bcfbe1965496bbc23385d22b7ca5e1e8fe84d0734cfdaa24545b7
6
+ metadata.gz: fef7d8d0d368700e24cd9646e1545592d3cfb1745eba671ee4b2fc130cb165d20b9a6eb775567d6ad981889d3bba9cbf36e29fef7cdb7eb011d4ef506a145025
7
+ data.tar.gz: 5b259bd2f22a8f781dbe1d66b0c6f40e6a635147239c79387f419364efd1f3f3c989483a912585185e226db421b0ca8c9df00718c51aa310e84a2883c5a4d152
data/CHANGELOG.md ADDED
@@ -0,0 +1,295 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
6
+ project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once
7
+ it is released. Entries land with the pull request that makes the change.
8
+
9
+ ## [Unreleased]
10
+
11
+ Nothing has been released yet — `Insika::VERSION` is `0.1.0` and no version is tagged.
12
+ Everything below is what the first release will contain.
13
+
14
+ ### Added
15
+
16
+ - **A publishable core (RFC-0018)** — `gem install insika` gives every shape: `reply`
17
+ in-process, `serve`, `Insika::Server.rack_app` mounted, and `embed(backend:)`. The
18
+ server and the Studio moved under `lib/insika/` and ship in the gem; the exported
19
+ store contract (`lib/insika/testing/store_contract.rb`) is what a third-party
20
+ backend specs against, now with an opt-in multi-worker group that fails a backend
21
+ whose `transaction` yields without isolation.
22
+ - **Turn pipeline** — a durable, resumable turn: command bus → context builder → policy
23
+ engine → middleware → executor tool-loop → event stream. Every turn checkpoints, so a
24
+ crash resumes without repeating side-effects.
25
+ - **Drop-in HTTP API** — `POST /v1/responses` speaking the OpenAI-Responses shape, with
26
+ SSE streaming and usage, plus an `/a2a` surface. One deployment serves many agents;
27
+ the `model` field is the agent id.
28
+ - **Agents as data** — an immutable `AgentProfile` stored as a row, created and edited
29
+ at runtime via the DSL, `POST /v1/agents` (pack import), or the Studio. Every edit is
30
+ hot; an in-flight turn keeps the profile it started with.
31
+ - **`Insika.agent { … }` DSL** — thin sugar that *generates* the same provisioning pack
32
+ the API and UI produce. `reply` for one in-process turn, `serve` for the server.
33
+ - **Embedding** — `Insika.embed(backend:) { … }` builds a graph that owns its store and
34
+ its LLM credentials, and `Insika::Server.rack_app(graph, token:)` hands you the `/v1`
35
+ transport as a value to mount in your own router. Two graphs in one process no longer
36
+ share a provider key or a database. What the process still owns — signals, the
37
+ reactor, the Studio — is written down in [Embedding](docs/EMBEDDING.md).
38
+ - **Tools** — code tools, tools defined as data (declarative HTTP manifests), and MCP
39
+ import, all behind a tool envelope with timeouts and optional human approval.
40
+ - **`halt_when`** — a data-tool can end the turn from its own **response**
41
+ (`{ "json_path": "tool_result.status", "equals": ["SUBSCRIBED"] }`), for backends that
42
+ perform the side effect *and* notify the customer themselves: without it the model
43
+ writes a second confirmation and the person gets the message twice. Per result, not
44
+ per tool — the same call still lets the model explain a failure. See
45
+ [Tools](docs/TOOLS.md#halt_when-when-the-answer-is-already-out).
46
+ - **Refinement: a gated, reviewable prompt edit** — a refinement run can now carry a
47
+ *proposal*, and the whole design is one sentence: an edit is scored by **running
48
+ it**, and a human approves it before it reaches anyone. The candidate is applied to
49
+ a throwaway clone of the agent, the golden set is replayed against the clone over
50
+ the ordinary `/v1/responses`, and any regression against the accepted baseline
51
+ disqualifies it. Nothing asks a model whether an edit looks good.
52
+ Edits are anchored and bounded (`before` must still match the file, exactly and
53
+ once) so the diff is a five-second decision, the gate's result is attributable, and
54
+ a proposal built from a stale snapshot cannot overwrite something you wrote — the
55
+ apply re-checks every anchor and refuses the whole proposal if anything drifted.
56
+ Approving writes through the versioned file store, so rollback is the Restore
57
+ button that was already there. Opt-in per agent (`refine mode: :propose, files:
58
+ %w[TOOLS.md]`); an agent with no golden cases, with no recorded baseline, or with a
59
+ baseline in which nothing passes, **cannot be edited at all** — the gate refuses
60
+ rather than passing vacuously (a regression is measured against a case that *was*
61
+ passing, so an all-red baseline would wave everything through). See
62
+ [Refinement](docs/REFINEMENT.md#changing-the-agent-the-gate).
63
+ - **A panel of proposers, a budget, and an optional unattended apply** — `proposers`
64
+ asks several models the same question and gates every answer independently; the gate
65
+ arbitrates and you are shown the best survivor with the others listed under it.
66
+ Convergence only breaks a tie (two models agreeing on wording is weak evidence; a
67
+ golden case passing is strong evidence), identical candidates are gated once, and a
68
+ model that answers prose or dies takes only itself out of the panel. `budget.tokens`
69
+ bounds what one run may spend across every proposal and every replay — checked before
70
+ each step, never mid-flight, with the candidates it could not afford recorded as such
71
+ and unmetered legs tallied rather than counted as free. `mode: :auto_apply` lets a
72
+ gate-passing edit land with nobody watching: off by default, and when on it still
73
+ needs zero regressions and a diff within `auto_apply_max_edits` (default 1) — a
74
+ larger one waits for a person instead of being thrown away. It reuses the human
75
+ approval path, so the staleness re-check, the versioned write and the undo are the
76
+ same. See [Refinement](docs/REFINEMENT.md#more-than-one-proposer).
77
+ - **The gate refuses to grade a judged baseline without a judge** — a rubric'd case
78
+ with no verdict counts as a pass, so replaying without a judge against a baseline
79
+ recorded with one does not measure less, it measures backwards. Found by running the
80
+ panel against the real pilot: the gate reported *6/6, no regression* against a
81
+ baseline the same corpus had just scored *2/6*, and both candidates cleared. With the
82
+ judge configured, the same two candidates were correctly rejected on judge-score
83
+ drops. Third member of the same family as the missing and all-red baseline refusals.
84
+ - **The refinement budget counts the prompt cache** — the engine's `total_tokens` is
85
+ input + output and excludes the cached prefix (a 27 KB pack reports `88` total against
86
+ `26624` cached), so a ceiling built on it alone let a run send hundreds of times what
87
+ it said. Cost now bills `total + cached` and records the cached share, which on a real
88
+ panel run was 95% of the spend. A run that cannot be gated is also refused **before**
89
+ the proposal is paid for, not after.
90
+ - **The eval baseline is a per-agent record, not only a file** — `evals/baseline.json`
91
+ works from a checkout; the refinement gate runs inside a deployment that has none.
92
+ `insika evals:baseline import|show|export` moves it into the store and back, with
93
+ the file staying the export format. A case the golden store does not know is
94
+ reported rather than guessed at: a silently shrunk baseline is a weaker gate.
95
+ - **Skills** — the `SKILL.md` format with progressive loading, so an agent's context
96
+ grows only when it needs to.
97
+ - **Memory** — cross-session facts and notes, per agent.
98
+ - **Guardrails** — opt-in, per-agent content safety on input and output:
99
+ prompt-injection detection, PII/secret redaction, abuse moderation.
100
+ - **Sandbox** — a confined-execution primitive for code that must run somewhere.
101
+ - **Egress guard** — bounds which hosts a tool can reach (the SSRF boundary).
102
+ - **Edge limits** — per-session rate limits and a per-agent token ceiling that halt a
103
+ flood before it costs an LLM call, backed by a usage ledger.
104
+ - **Subagents** — bounded fan-out delegation with a concurrency cap, and workflows
105
+ exposed over the API.
106
+ - **Parallel tool calls** — opt-in via `limits[:tool_concurrency]`, one number that is
107
+ both the switch and the cap on how many of a batch run at once. Off by default, and
108
+ automatically off for any turn holding an approval-required tool (two calls suspended
109
+ for an operator would deadlock on the single per-task mailbox). Documented trade-offs:
110
+ `max_tool_calls` becomes approximate, tool results are recorded in completion order,
111
+ and a turn deadline waits for in-flight calls instead of cancelling them.
112
+ - **Message coalescing** — opt-in via `limits[:queue_mode] = "collect"` plus
113
+ `debounce_ms`, so the fragments a person types in a row ("oi" / "queria saber do
114
+ pedido" / "1234567") become one turn instead of three. The quiet window is held on
115
+ the session's own fiber, never on the request, so the caller is still acked
116
+ immediately. `debounce_max_ms` caps the total deferral. Off by default
117
+ (`followup` = one turn per message, today's behavior), resolved session vars →
118
+ agent → platform, where a key set explicitly to `nil`/`0` means *off*, not
119
+ *inherit*. Only offered on surfaces whose response can report
120
+ `{"merged": true}` — a caller that cannot hear that verdict would deliver the same
121
+ answer once per fragment, so `/v1/responses` and open streams are refused rather
122
+ than silently coalesced.
123
+ - **Message steering** — opt-in via `limits[:queue_mode] = "steer"`, so a message that
124
+ arrives while the agent is *already running tools* is appended to that run instead of
125
+ waiting for it. It lands at a **tool-batch boundary**, appended at the tail: after the
126
+ last result of a batch and before the model's next step — never between two tool
127
+ results (which Anthropic rejects outright), and never rewriting anything already sent,
128
+ so the prompt cache survives. A steered message is a first-class transcript message
129
+ with no origin, because a person wrote it. Bounded by `steer_max_messages`, worded by
130
+ an optional `steer_join` template. Four cases the run cannot absorb — a turn with no
131
+ tool call, a batch ending in `halt_when`, a workflow run, and overflow past the bound —
132
+ release the message as the next turn on the session rather than losing it. Same
133
+ surface rule as coalescing: only a caller that can hear `{"steered": true}` may steer,
134
+ since the reply belongs to the turn it joined.
135
+ - **Message interrupt** — opt-in via `limits[:queue_mode] = "interrupt"`, for the message
136
+ that makes the turn in flight *wrong* ("não, esquece isso"). That turn is abandoned at
137
+ its next boundary and the new message becomes an ordinary turn, with its own `task_id`
138
+ and its own reply — so unlike coalescing and steering it needs no verdict field and
139
+ works on every surface. A tool call in flight still runs to completion and is recorded:
140
+ the batch is one unit of work, and faking failures would teach the model that tools
141
+ failed when they did not.
142
+ - **A cancelled turn publishes nothing.** A cancel that arrived while the provider was
143
+ working used to be observed only after the answer had already been streamed, so the
144
+ customer read the reply of a turn that then terminated `:cancelled` and persisted
145
+ nothing — text delivered, transcript silent about it. There is now a boundary between
146
+ the provider's last word and publishing, so the two never disagree. Applies to every
147
+ cancel, not just `interrupt`.
148
+ - **Channels** — a way in and out for people, registered by id and mounted on one
149
+ generic route family under `/channels/<id>/`. A channel translates and authenticates,
150
+ and does nothing else: it may refuse a request, never widen one. Plugins register
151
+ their own through `contracts.channels`, and a deployment that registers none has no
152
+ such route at all. See [Channels](docs/CHANNELS.md).
153
+ - **The web widget** — one `<script>` tag on a site and the adopter has an agent: a
154
+ bubble, a panel, and the answer streaming in token by token, with no backend of
155
+ theirs, no build step and no npm. It is the channel for a team with *no* messaging
156
+ stack of its own, and the first one where the reply rides the request's own
157
+ connection. Because the caller is an anonymous browser there is no secret to check,
158
+ so three things stand in for one: an exact-match origin allowlist, an agent
159
+ allowlist (a visitor addresses what the operator published and nothing else), and a
160
+ **mandatory chat rate limit** — the widget answers `503` until one is configured,
161
+ which is the only place the engine refuses to serve rather than warn. The engine
162
+ issues session ids and the client never proposes one. See
163
+ [Channels](docs/CHANNELS.md#the-web-widget).
164
+ - **The relay channel** — for an adopter who already owns a messaging stack (a WhatsApp
165
+ BSP, a Zendesk, a legacy app) and wants the engine for the **turn**, not the platform.
166
+ You POST the customer's message to `/channels/relay/events`; the engine acks
167
+ immediately and POSTs the answer to your own callback when there is one. Everything
168
+ platform-shaped — the 24-hour window, templates, media, read receipts, WhatsApp
169
+ formatting — stays yours, permanently: this is not a migration step toward a native
170
+ channel. Configured entirely by environment (`INSIKA_RELAY_TOKEN`, which is both the
171
+ switch and the credential, plus the callback URL and its optional bearer), so there is
172
+ no way to expose the route without a secret.
173
+ - **At-most-once outbound delivery** — a reply owed to a channel is written durably when
174
+ the turn commits and **claimed** before the HTTP call, so a crash loses a delivery
175
+ rather than duplicating it; bounded retry with backoff handles a recipient that is
176
+ merely down, and boot re-dispatches what was recorded but never claimed. Each attempt
177
+ reports on `:channel_delivered`, because a turn completing says nothing about whether
178
+ the customer received it.
179
+ - **Inbound deduplication** — an optional `event_id` on a channel message is remembered
180
+ for 24 hours, so a platform retrying a webhook it already delivered gets
181
+ `{"duplicate": true}` and the same `task_id` back instead of a second LLM turn and a
182
+ second message. No id means at-least-once turns, said out loud rather than papered
183
+ over with a content hash.
184
+ - **Studio** — a web control UI for agents, prompts, skills, tools, sessions, tasks,
185
+ approvals, and settings, with live transcripts over SSE.
186
+ - **LLM-first onboarding** — `GET /start.md`, `GET /models.json`, and `GET /docs/*.md`,
187
+ so a coding agent can set up the first agent by reading a running instance.
188
+ - **Evals** — a small corpus of real conversations, replayed against a running deployment
189
+ and checked two ways: deterministic assertions (was the tool called, did a CPF leak)
190
+ and a **rubric** scored by a judge. Cases are YAML data, authorable in the Studio as
191
+ well as in the repo, and the graders are a **panel of distinct models** with
192
+ configurable aggregation and agreement — sampling one model N times measures its
193
+ variance, not its bias. A baseline turns a run into a pre-merge gate that blocks only
194
+ on regressions. A case can also carry the **incumbent's real conversation** for the
195
+ same opening (`reference:`), and `--pairwise` asks the panel which one served the
196
+ customer better — the question an absolute score cannot answer when you are replacing
197
+ a system that already works. The judge never learns which transcript is Insika's, and
198
+ every judge is asked twice with the sides swapped, so a preference that depends on
199
+ presentation order is reported as such instead of counted. See
200
+ [Evals](docs/EVALS.md).
201
+ - **Refinement** — an agent's own traffic read back as a ranked report of what broke:
202
+ repeated tool errors grouped by their normalized signature, failed turns, customers
203
+ repeating themselves, canned safe replies served instead of answers, and granted tools
204
+ that never fired. Provenance is session ids; snippets go through the same redaction as
205
+ a customer-facing turn. Fired by hand (`insika refine --agent <id>` or the Studio
206
+ button) — the engine grows no scheduler. It calls no model and edits nothing.
207
+ `exclude_sessions` keeps load-test and debug traffic from burying real findings, and
208
+ reports what it dropped. See [Refinement](docs/REFINEMENT.md).
209
+ - **Observability** — an event stream and per-session tool-call traces always on, plus
210
+ opt-in OpenTelemetry traces *and* metrics under a documented, vendor-neutral attribute
211
+ convention with an operator-declared pricing table for estimated cost.
212
+ - **Operations** — SQLite (or in-memory) stores, Falcon on the Async fiber scheduler,
213
+ strict configuration validation with a `doctor` check, and a provider-free benchmark
214
+ that measures engine overhead alone.
215
+ - **Documentation site** — the `docs/*.md` files are also a Jekyll (Just the Docs) site
216
+ published at <https://guizaols.github.io/insika/>. Same files, no copy: what GitHub
217
+ renders, what `GET /docs/<name>.md` serves, and what the site publishes are one source.
218
+
219
+ ### Changed
220
+
221
+ - `GET /docs/<name>.md` strips the docs-site frontmatter, so the raw markdown a coding
222
+ agent receives is the prose only.
223
+
224
+ ### Fixed
225
+
226
+ - **A prompt file could silently become a serialized object.** `Pack.from_h` normalized
227
+ only the KEYS of `files`/`skills`, and both `WriteAgentFile` and `AgentFileStore#write`
228
+ called `to_s` on whatever they were handed — so a pack shaped
229
+ `{"files": {"AGENTS.md": {"content": "…"}}}`, or an entry read and written back, was
230
+ stored as Ruby's `#inspect` of the object. The agent then received its whole prompt as
231
+ one line of `{"content" => "…\n…"}`, escapes and all, on every turn, while the file
232
+ looked perfectly healthy: present, non-empty, and the agent still answered. All three
233
+ layers now refuse a Hash or an Array instead of coercing it, and `insika doctor` sweeps
234
+ existing prompt files for the same shape.
235
+
236
+ ### Security
237
+
238
+ - **The `/v1` surface is closed by default.** `POST /v1/commands/<type>` — the generic
239
+ Command ingress, which dispatches **any** registered authoring Command
240
+ (`write_agent_file`, `write_data_tool`, `upsert_llm_provider`, `update_settings`,
241
+ `delete_agent`) — answered **without any Authorization header**, as did
242
+ `POST /v1/sessions`, `POST /v1/messages`, `POST /v1/workflows/<name>`, and the
243
+ `GET /v1/sessions/:id` · `/v1/tasks/:id` · `/v1/events` reads. Anyone who knew a
244
+ deployment's URL could rewrite an agent's prompt, repoint a tool at their own host,
245
+ swap the LLM provider's credentials, or read every conversation.
246
+
247
+ The gate was each handler's job to call, and the generic route never did. It now runs
248
+ in the router, before any dispatch, against an **allowlist** of public routes (`/up`,
249
+ the opt-in onboarding surface, the A2A agent card) — so the next route added is closed
250
+ until someone publishes it deliberately. With no token configured the whole surface is
251
+ `503`, never open by omission. `/a2a` is now behind the same Bearer.
252
+
253
+ ### Fixed
254
+
255
+ - **An agent's inline identity now reaches the model.** `base_prompt` — what the DSL's
256
+ `instructions` and a pack manifest set — was stored, round-tripped and advertised on
257
+ the A2A agent card, but never injected into the system prompt: every composition root
258
+ wires the prompt provider with `base: ""`, so an agent whose identity was inline ran
259
+ with *no* identity. Agents whose identity comes from `prompt_files` (the production
260
+ path) were unaffected.
261
+ - **`max_tokens` now reaches the provider.** The agent param was authored in the DSL,
262
+ the Studio and a pack, resolved onto the turn — and then sent to the chat as
263
+ `with_max_output_tokens`, a method no version of `ruby_llm` has. Guarded by
264
+ `respond_to?`, it was skipped in silence, so an agent with a token ceiling ran without
265
+ one. It now rides `with_params(max_tokens:)`, merged into a single call with the
266
+ reasoning toggle (`with_params` replaces the gem's whole params hash, so two calls
267
+ would drop the first one's keys).
268
+ - **A data-tool whose API moved now says so.** A 3xx counted as success, and since
269
+ servers send a redirect with an empty body, the model received `""` and narrated a
270
+ plausible outage. The HTTP client still does not follow the hop — the egress guard
271
+ cleared the authored URL, not the redirect's destination — but the tool now returns
272
+ `HTTP 301: moved to <url>`, which names the definition to fix. This is what broke
273
+ `examples/data-tool/currency_agent.rb` (its endpoint moved host).
274
+ - **A data tool's parameters are no longer half-invented by the engine.** In the flat
275
+ authoring form, a param typed `array` used to lift to `items: {type: "string"}` — an
276
+ item type nobody wrote. So a param whose API takes a list of *objects*
277
+ (`[{query, filters}]`) reached the provider declared as a list of *strings*: the model
278
+ obeyed the schema it was given, the backend answered `200`, and the results were
279
+ wrong with no error anywhere. Three changes close that loop:
280
+ - bare `array` is refused at ingestion (the JSON Schema path already refused an array
281
+ without `items`); the flat form now spells a list of scalars `array:string` /
282
+ `array:number` / `array:integer` / `array:boolean`, and a list of objects is written
283
+ as JSON Schema. **Breaking for authoring**; `insika doctor` reports any stored tool
284
+ left behind and offers the meaning-preserving rewrite.
285
+ - the Studio tool editor no longer flattens what it cannot render. A nested schema
286
+ shows as JSON Schema and saves back unchanged — before, opening and saving a nested
287
+ tool silently replaced its schema with the broken flat one.
288
+ - a tool call's arguments are validated against the tool's own schema before the
289
+ request is built, so a malformed call becomes an `{ error: }` naming the offending
290
+ path (`query_filter_pairs[0]: expected an object, got a string`) that the model can
291
+ act on. Structure is strict; scalars accept their lossless string form (`"2"`,
292
+ `"true"`) and are never coerced.
293
+ - **`insika doctor` now sees broken data tools.** A stored definition that no longer
294
+ builds is dropped by the tool overlay with only a stderr warning — the agent quietly
295
+ loses the tool. The new `data-tools` check is that drop's report.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Guilherme Lages Santos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,5 +1,139 @@
1
1
  # Insika
2
2
 
3
- Name reserved for the **Insika** project. Under active development no public API yet.
3
+ **Your agent is the idea. Insika is what holds it up in production.**
4
4
 
5
- Stay tuned.
5
+ *Insika* is Zulu for the pillar that carries a structure — the part nobody admires and
6
+ everything rests on. A turn that survives a crash, tools that cannot wander off, limits
7
+ that hold under load, and an API your clients already speak. Build the agent; the
8
+ scaffolding is already here.
9
+
10
+ ---
11
+
12
+ Insika is a Ruby runtime for **LLM agents in production**: a durable, resumable turn
13
+ pipeline behind an **OpenAI-Responses-compatible** HTTP API (`POST /v1/responses`), with
14
+ tools, skills, cross-session memory, per-agent policy, content-safety guardrails, and a
15
+ web control UI. Point an existing Responses client at it and serve many agents from one
16
+ deployment.
17
+
18
+ - **Drop-in `/v1/responses`** — SSE streaming and usage, the shape existing clients already speak.
19
+ - **Durable & resumable** — every turn checkpoints; a crash resumes without repeating side-effects.
20
+ - **Agents as data** — agents, prompts, skills and tools are editable at runtime (UI or API), no redeploy.
21
+ - **Tools & skills** — code tools, data-defined tools, MCP import; skills load on demand.
22
+ - **Safe by default** — content-safety guardrails, an egress guard, confined execution, approvals, edge limits.
23
+ - **Observable** — an event stream, per-session tool-call traces, optional OpenTelemetry.
24
+ - **~0.4 ms of engine per turn** — p50 overhead on a neutral, key-free benchmark you can rerun yourself ([methodology](docs/BENCHMARK.md)).
25
+
26
+ ## Your first agent
27
+
28
+ Ruby `>= 3.3` and a provider key (the demo uses DeepSeek). The whole program:
29
+
30
+ ```bash
31
+ gem install insika
32
+ ```
33
+
34
+ ```ruby
35
+ require "insika"
36
+
37
+ assistant = Insika.agent("assistant") do
38
+ model "deepseek-chat"
39
+ provider :deepseek
40
+ instructions "You are Bia, a concise and friendly assistant. Answer briefly."
41
+ end
42
+
43
+ puts assistant.reply("hi, what can you do?") # one turn, in-process
44
+ ```
45
+
46
+ Swap `reply` for `serve` and the same agent is a server — the control UI at `/studio`
47
+ plus the drop-in API, on `:9292`:
48
+
49
+ ```bash
50
+ DEEPSEEK_API_KEY=sk-... ruby quickstart.rb
51
+
52
+ curl -N http://localhost:9292/v1/responses \
53
+ -H "Authorization: Bearer local-demo" -H "Content-Type: application/json" \
54
+ -d '{"model":"assistant","user":"chat-1","stream":true,"input":"hi"}'
55
+ ```
56
+
57
+ `model` is the agent id; `user` is the session id. The DSL is thin sugar over
58
+ config-over-code: `Insika.agent { … }.to_pack` emits the same portable pack you can
59
+ create or edit at runtime through the UI or `POST /v1/agents` — nothing in the DSL is a
60
+ bypass, it just generates the data. → [Agents](docs/AGENTS.md),
61
+ [Running locally](docs/RUNNING-LOCAL.md)
62
+
63
+ ## Or let your coding agent build it
64
+
65
+ A running instance serves its own **LLM-first onboarding**. Point Claude Code, Cursor or
66
+ any IDE assistant at it and let it do the setup:
67
+
68
+ ```
69
+ Read http://localhost:9292/start.md then help me build my first agent
70
+ ```
71
+
72
+ `start.md` is a skill-structured prompt (gather context → decide → build → self-check →
73
+ guard against known failure modes). Alongside it: **`GET /models.json`** (configured
74
+ providers and model ids, the defaults, the valid `thinking` levels, the agent ids already
75
+ served — no secrets) and **`GET /docs`** + **`GET /docs/<name>.md`** (these docs as raw
76
+ markdown). Public and on by default when you `serve`; opt-in in production
77
+ (`INSIKA_ONBOARDING=1`).
78
+
79
+ ## Docs by goal
80
+
81
+ **Understand the idea**
82
+
83
+ - [Why Insika](docs/WHY.md) — a runtime vs a DIY loop, an assembled framework, or a hosted gateway.
84
+ - [Architecture](docs/ARCHITECTURE.md) — the turn pipeline, the tool-loop, checkpoint recovery, composition roots, diagrams.
85
+
86
+ **Build an agent**
87
+
88
+ - [Agents](docs/AGENTS.md) — the AgentProfile and its five access layers; create and edit at runtime.
89
+ - [Tools](docs/TOOLS.md) — code vs data vs MCP tools, manifests, egress troubleshooting.
90
+ - [Skills](docs/SKILLS.md) — the SKILL.md format and progressive loading.
91
+ - [Context](docs/CONTEXT.md) — what fills a turn's prompt; budget, eviction, memory.
92
+ - [Workflows](docs/WORKFLOWS.md) — deterministic orchestration of several agents: the five patterns, and when to let the model choose instead.
93
+ - [Channels](docs/CHANNELS.md) — how people reach the agent: a widget on your site in one `<script>` tag, or keep your own WhatsApp/Slack stack (relay).
94
+ - [Plugins](docs/PLUGINS.md) — the two extension tiers: config-only, or a gem the engine loads.
95
+ - [Running locally](docs/RUNNING-LOCAL.md) — the local demo, the control UI, wiring tools to your own backend.
96
+ - [examples/](examples/) — one small runnable project per capability.
97
+
98
+ **Ship it**
99
+
100
+ - [Security](docs/SECURITY.md) — guardrails, egress, approvals, edge limits, secrets.
101
+ - [Sandbox](docs/SANDBOX.md) — the confined-execution primitive.
102
+ - [Deploy](docs/DEPLOY.md) — Falcon, a durable SQLite volume, tokens.
103
+ - [Embedding](docs/EMBEDDING.md) — mount Insika into the Ruby app you already have: `Insika.embed(backend:)` and a Rack app for your router.
104
+
105
+ **Operate & prove it**
106
+
107
+ - [Observability](docs/OBSERVABILITY.md) — OpenTelemetry (opt-in): turns as traces and metrics, the attribute convention, dashboard recipes.
108
+ - [Benchmark](docs/BENCHMARK.md) — the neutral, reproducible, provider-free engine benchmark.
109
+ - [Load test](docs/LOADTEST.md) — load-testing and data topology.
110
+ - [Evals](docs/EVALS.md) — the cases that grade an agent: rubrics, the judge panel, and the pre-merge gate.
111
+ - [Refinement](docs/REFINEMENT.md) — read an agent's own traffic back as a ranked report of what broke.
112
+
113
+ All of the above is also browsable, searchable and cross-linked at
114
+ **[guizaols.github.io/insika](https://guizaols.github.io/insika/)** — the same files,
115
+ rendered. Reading this repo as an agent? [llms.txt](llms.txt) indexes the docs;
116
+ [AGENTS.md](AGENTS.md) is for working *on* the code.
117
+
118
+ ## Under the hood, in one line
119
+
120
+ Command Bus → Context Builder → Policy Engine → Middleware → Executor (tool-loop) →
121
+ Event Stream / SSE, checkpointed to SQLite (or memory, for dev). It runs on the
122
+ [Async](https://github.com/socketry/async) fiber scheduler under
123
+ [Falcon](https://github.com/socketry/falcon): an LLM turn is almost entirely spent
124
+ waiting on the provider, so one process carries many concurrent turns on a few
125
+ connections instead of a thread per request — the model
126
+ [RubyLLM's async guide](https://rubyllm.com/async/) recommends. Full pipeline in
127
+ [Architecture](docs/ARCHITECTURE.md).
128
+
129
+ ## Contributing
130
+
131
+ Bug reports with a reproduction and small, focused PRs are the most useful thing right
132
+ now — see [CONTRIBUTING.md](CONTRIBUTING.md) (setup, house rules, `bundle exec rspec`)
133
+ and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Vulnerabilities go through
134
+ [SECURITY.md](SECURITY.md), never a public issue.
135
+
136
+ ## Status
137
+
138
+ Pre-release — APIs may still change, nothing is tagged yet
139
+ ([CHANGELOG.md](CHANGELOG.md)). Licensed **MIT** ([LICENSE](LICENSE)).