insika 0.0.1 → 0.2.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 (277) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +361 -0
  3. data/LICENSE +21 -0
  4. data/README.md +136 -2
  5. data/bin/insika +366 -0
  6. data/docs/AGENTS.md +618 -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 +117 -0
  11. data/docs/DEPLOY.md +354 -0
  12. data/docs/EMBEDDING.md +198 -0
  13. data/docs/EVALS.md +273 -0
  14. data/docs/LOADTEST.md +232 -0
  15. data/docs/OBSERVABILITY.md +374 -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 +375 -0
  22. data/docs/SKILLS.md +284 -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 +255 -0
  34. data/lib/insika/alert_dispatcher.rb +139 -0
  35. data/lib/insika/allowlist.rb +28 -0
  36. data/lib/insika/baseline_store.rb +74 -0
  37. data/lib/insika/budget_ledger.rb +135 -0
  38. data/lib/insika/capability/resolved_tool.rb +34 -0
  39. data/lib/insika/capability_registry.rb +112 -0
  40. data/lib/insika/channel_delivery.rb +153 -0
  41. data/lib/insika/channel_registry.rb +30 -0
  42. data/lib/insika/channels/relay.rb +178 -0
  43. data/lib/insika/channels/web/widget.js +283 -0
  44. data/lib/insika/channels/web.rb +211 -0
  45. data/lib/insika/channels/webhook.rb +58 -0
  46. data/lib/insika/chat_builder.rb +303 -0
  47. data/lib/insika/checkpoint.rb +13 -0
  48. data/lib/insika/checkpoint_store.rb +153 -0
  49. data/lib/insika/circuit_state.rb +114 -0
  50. data/lib/insika/coercion.rb +58 -0
  51. data/lib/insika/command.rb +32 -0
  52. data/lib/insika/command_bus.rb +39 -0
  53. data/lib/insika/commands/agent_payload.rb +43 -0
  54. data/lib/insika/commands/approve_action.rb +46 -0
  55. data/lib/insika/commands/cancel_task.rb +33 -0
  56. data/lib/insika/commands/create_agent.rb +54 -0
  57. data/lib/insika/commands/create_session.rb +67 -0
  58. data/lib/insika/commands/delete_agent.rb +33 -0
  59. data/lib/insika/commands/delete_agent_file.rb +50 -0
  60. data/lib/insika/commands/delete_data_tool.rb +33 -0
  61. data/lib/insika/commands/delete_llm_provider.rb +36 -0
  62. data/lib/insika/commands/delete_mcp.rb +30 -0
  63. data/lib/insika/commands/delete_skill.rb +43 -0
  64. data/lib/insika/commands/delete_system_file.rb +29 -0
  65. data/lib/insika/commands/gate_refinement.rb +245 -0
  66. data/lib/insika/commands/import_mcp_tools.rb +48 -0
  67. data/lib/insika/commands/import_tools.rb +81 -0
  68. data/lib/insika/commands/issue_tenant_token.rb +41 -0
  69. data/lib/insika/commands/memory_add_note.rb +32 -0
  70. data/lib/insika/commands/memory_forget_fact.rb +32 -0
  71. data/lib/insika/commands/memory_put_fact.rb +35 -0
  72. data/lib/insika/commands/pause_task.rb +29 -0
  73. data/lib/insika/commands/resolve_refinement.rb +126 -0
  74. data/lib/insika/commands/restore_agent_file.rb +36 -0
  75. data/lib/insika/commands/restore_data_tool.rb +34 -0
  76. data/lib/insika/commands/restore_system_file.rb +31 -0
  77. data/lib/insika/commands/resume_task.rb +85 -0
  78. data/lib/insika/commands/revoke_token.rb +39 -0
  79. data/lib/insika/commands/rotate_tenant_token.rb +43 -0
  80. data/lib/insika/commands/run_refinement.rb +133 -0
  81. data/lib/insika/commands/send_message.rb +150 -0
  82. data/lib/insika/commands/set_agent_tools.rb +39 -0
  83. data/lib/insika/commands/set_skill_agents.rb +112 -0
  84. data/lib/insika/commands/trigger_workflow.rb +80 -0
  85. data/lib/insika/commands/update_agent.rb +49 -0
  86. data/lib/insika/commands/update_settings.rb +33 -0
  87. data/lib/insika/commands/upsert_llm_provider.rb +34 -0
  88. data/lib/insika/commands/upsert_mcp.rb +32 -0
  89. data/lib/insika/commands/write_agent_file.rb +57 -0
  90. data/lib/insika/commands/write_data_tool.rb +43 -0
  91. data/lib/insika/commands/write_golden.rb +58 -0
  92. data/lib/insika/commands/write_skill.rb +60 -0
  93. data/lib/insika/commands/write_system_file.rb +31 -0
  94. data/lib/insika/config_store.rb +89 -0
  95. data/lib/insika/context/builder.rb +166 -0
  96. data/lib/insika/context/catalog_provider.rb +23 -0
  97. data/lib/insika/context/fragment.rb +43 -0
  98. data/lib/insika/context/priority.rb +30 -0
  99. data/lib/insika/context/provider.rb +19 -0
  100. data/lib/insika/context/providers/memory.rb +60 -0
  101. data/lib/insika/context/providers/prompt.rb +105 -0
  102. data/lib/insika/context/providers/request.rb +32 -0
  103. data/lib/insika/context/providers/session.rb +123 -0
  104. data/lib/insika/context/providers/skill.rb +24 -0
  105. data/lib/insika/context/providers/skill_trigger.rb +128 -0
  106. data/lib/insika/context/providers/tool_search.rb +20 -0
  107. data/lib/insika/context_trace_store.rb +92 -0
  108. data/lib/insika/delegation_store.rb +153 -0
  109. data/lib/insika/doctor.rb +539 -0
  110. data/lib/insika/dsl/definition.rb +55 -0
  111. data/lib/insika/dsl/runtime.rb +382 -0
  112. data/lib/insika/dsl/server_boot.rb +98 -0
  113. data/lib/insika/dsl/system.rb +93 -0
  114. data/lib/insika/dsl/workflow_adapter.rb +59 -0
  115. data/lib/insika/dsl.rb +364 -0
  116. data/lib/insika/edge_limiter.rb +268 -0
  117. data/lib/insika/egress_guard.rb +75 -0
  118. data/lib/insika/env_schema.rb +249 -0
  119. data/lib/insika/errors.rb +201 -0
  120. data/lib/insika/evals/assertions.rb +247 -0
  121. data/lib/insika/evals/baseline.rb +69 -0
  122. data/lib/insika/evals/golden.rb +172 -0
  123. data/lib/insika/evals/judge.rb +225 -0
  124. data/lib/insika/evals/pairwise.rb +178 -0
  125. data/lib/insika/evals/report.rb +115 -0
  126. data/lib/insika/evals/runner.rb +141 -0
  127. data/lib/insika/evals/transport.rb +178 -0
  128. data/lib/insika/event.rb +18 -0
  129. data/lib/insika/event_stream.rb +132 -0
  130. data/lib/insika/executor.rb +1995 -0
  131. data/lib/insika/frontmatter.rb +42 -0
  132. data/lib/insika/golden_store.rb +145 -0
  133. data/lib/insika/hooks.rb +48 -0
  134. data/lib/insika/http_client.rb +63 -0
  135. data/lib/insika/inbound_log.rb +84 -0
  136. data/lib/insika/llm_configurator.rb +99 -0
  137. data/lib/insika/llm_provider_store.rb +83 -0
  138. data/lib/insika/loop_detector.rb +143 -0
  139. data/lib/insika/mcp_http_client.rb +67 -0
  140. data/lib/insika/mcp_store.rb +115 -0
  141. data/lib/insika/mcp_tool_ingestor.rb +143 -0
  142. data/lib/insika/memory_store.rb +93 -0
  143. data/lib/insika/message_origin.rb +76 -0
  144. data/lib/insika/middleware.rb +36 -0
  145. data/lib/insika/model_policy.rb +52 -0
  146. data/lib/insika/model_resolver.rb +176 -0
  147. data/lib/insika/model_selection.rb +115 -0
  148. data/lib/insika/onboarding.rb +208 -0
  149. data/lib/insika/outbox_store.rb +166 -0
  150. data/lib/insika/overlay_tool_registry.rb +102 -0
  151. data/lib/insika/pack.rb +102 -0
  152. data/lib/insika/pack_importer.rb +123 -0
  153. data/lib/insika/pending_action_store.rb +120 -0
  154. data/lib/insika/plugin/loader.rb +356 -0
  155. data/lib/insika/plugin.rb +35 -0
  156. data/lib/insika/policy/engine.rb +83 -0
  157. data/lib/insika/policy/policy.rb +120 -0
  158. data/lib/insika/policy_registry.rb +23 -0
  159. data/lib/insika/profile_source.rb +143 -0
  160. data/lib/insika/prompt_catalog.rb +61 -0
  161. data/lib/insika/provider_error_classifier.rb +160 -0
  162. data/lib/insika/queue_policy.rb +167 -0
  163. data/lib/insika/recovery.rb +168 -0
  164. data/lib/insika/refinement/candidate.rb +159 -0
  165. data/lib/insika/refinement/evidence_collector.rb +371 -0
  166. data/lib/insika/refinement/gate.rb +234 -0
  167. data/lib/insika/refinement/panel.rb +222 -0
  168. data/lib/insika/refinement/proposer.rb +262 -0
  169. data/lib/insika/refinement_store.rb +295 -0
  170. data/lib/insika/registry.rb +59 -0
  171. data/lib/insika/reliability.rb +185 -0
  172. data/lib/insika/safety/config.rb +109 -0
  173. data/lib/insika/safety/detectors.rb +176 -0
  174. data/lib/insika/safety/factory.rb +102 -0
  175. data/lib/insika/safety/input_guardrail.rb +102 -0
  176. data/lib/insika/safety/moderator.rb +94 -0
  177. data/lib/insika/safety/output_filter.rb +79 -0
  178. data/lib/insika/safety/output_validator.rb +101 -0
  179. data/lib/insika/safety/safe_responses.rb +47 -0
  180. data/lib/insika/sandbox/boundary.rb +93 -0
  181. data/lib/insika/sandbox/docker.rb +74 -0
  182. data/lib/insika/sandbox/local.rb +33 -0
  183. data/lib/insika/sandbox/runner.rb +80 -0
  184. data/lib/insika/sandbox.rb +85 -0
  185. data/lib/insika/schema_guard.rb +147 -0
  186. data/lib/insika/secret_masking.rb +34 -0
  187. data/lib/insika/server/a2a/agent_card.rb +27 -0
  188. data/lib/insika/server/a2a/app.rb +112 -0
  189. data/lib/insika/server/a2a/client.rb +101 -0
  190. data/lib/insika/server/a2a/errors.rb +32 -0
  191. data/lib/insika/server/a2a/http.rb +42 -0
  192. data/lib/insika/server/a2a/message.rb +27 -0
  193. data/lib/insika/server/a2a/protocol.rb +45 -0
  194. data/lib/insika/server/a2a/remotes.rb +25 -0
  195. data/lib/insika/server/a2a/task_projection.rb +40 -0
  196. data/lib/insika/server/app.rb +1022 -0
  197. data/lib/insika/server/boot.rb +119 -0
  198. data/lib/insika/server/rack_app.rb +118 -0
  199. data/lib/insika/server/responses.rb +165 -0
  200. data/lib/insika/server/sse_body.rb +96 -0
  201. data/lib/insika/server/tenant_auth.rb +61 -0
  202. data/lib/insika/session_actor.rb +162 -0
  203. data/lib/insika/session_store.rb +143 -0
  204. data/lib/insika/settings_store.rb +154 -0
  205. data/lib/insika/shutdown.rb +125 -0
  206. data/lib/insika/skill_catalog.rb +220 -0
  207. data/lib/insika/skill_store.rb +127 -0
  208. data/lib/insika/steer_injector.rb +110 -0
  209. data/lib/insika/store.rb +52 -0
  210. data/lib/insika/stores/memory.rb +123 -0
  211. data/lib/insika/stores/sqlite.rb +183 -0
  212. data/lib/insika/studio/app.rb +1693 -0
  213. data/lib/insika/studio/assets/dist/application.css +1 -0
  214. data/lib/insika/studio/assets/dist/application.js +70 -0
  215. data/lib/insika/studio/forms.rb +335 -0
  216. data/lib/insika/studio/nav_icons.rb +31 -0
  217. data/lib/insika/studio/views/_message.erb +44 -0
  218. data/lib/insika/studio/views/agent_detail.erb +285 -0
  219. data/lib/insika/studio/views/agents.erb +63 -0
  220. data/lib/insika/studio/views/approvals.erb +41 -0
  221. data/lib/insika/studio/views/chats.erb +34 -0
  222. data/lib/insika/studio/views/evals.erb +83 -0
  223. data/lib/insika/studio/views/home.erb +72 -0
  224. data/lib/insika/studio/views/layout.erb +94 -0
  225. data/lib/insika/studio/views/login.erb +17 -0
  226. data/lib/insika/studio/views/mcp.erb +91 -0
  227. data/lib/insika/studio/views/not_found.erb +5 -0
  228. data/lib/insika/studio/views/playground.erb +47 -0
  229. data/lib/insika/studio/views/refinement.erb +234 -0
  230. data/lib/insika/studio/views/session.erb +137 -0
  231. data/lib/insika/studio/views/settings.erb +168 -0
  232. data/lib/insika/studio/views/skills.erb +141 -0
  233. data/lib/insika/studio/views/system_files.erb +65 -0
  234. data/lib/insika/studio/views/task.erb +105 -0
  235. data/lib/insika/studio/views/tasks.erb +33 -0
  236. data/lib/insika/studio/views/tool_edit.erb +107 -0
  237. data/lib/insika/studio/views/tools.erb +89 -0
  238. data/lib/insika/subagent_graph.rb +96 -0
  239. data/lib/insika/system_file_store.rb +96 -0
  240. data/lib/insika/task_actor.rb +128 -0
  241. data/lib/insika/task_store.rb +250 -0
  242. data/lib/insika/telemetry/pricing.rb +104 -0
  243. data/lib/insika/telemetry/recorder.rb +228 -0
  244. data/lib/insika/telemetry.rb +127 -0
  245. data/lib/insika/testing/store_contract.rb +270 -0
  246. data/lib/insika/tick.rb +122 -0
  247. data/lib/insika/token_estimator.rb +16 -0
  248. data/lib/insika/token_store.rb +168 -0
  249. data/lib/insika/tool_assembly.rb +140 -0
  250. data/lib/insika/tool_catalog.rb +89 -0
  251. data/lib/insika/tool_definition.rb +518 -0
  252. data/lib/insika/tool_envelope.rb +140 -0
  253. data/lib/insika/tool_manifest.rb +218 -0
  254. data/lib/insika/tool_output_compressor.rb +100 -0
  255. data/lib/insika/tool_registry.rb +21 -0
  256. data/lib/insika/tool_store.rb +135 -0
  257. data/lib/insika/tool_trace_store.rb +92 -0
  258. data/lib/insika/tools/a2a_remote.rb +48 -0
  259. data/lib/insika/tools/agent_enum.rb +68 -0
  260. data/lib/insika/tools/concurrency.rb +54 -0
  261. data/lib/insika/tools/data_defined_tool.rb +219 -0
  262. data/lib/insika/tools/load_skill.rb +99 -0
  263. data/lib/insika/tools/remember.rb +53 -0
  264. data/lib/insika/tools/stuck_signal.rb +44 -0
  265. data/lib/insika/tools/subagent.rb +75 -0
  266. data/lib/insika/tools/subagents.rb +77 -0
  267. data/lib/insika/tools/tool_search.rb +94 -0
  268. data/lib/insika/turn_output.rb +139 -0
  269. data/lib/insika/turn_state.rb +162 -0
  270. data/lib/insika/turn_timing.rb +56 -0
  271. data/lib/insika/usage_ledger.rb +47 -0
  272. data/lib/insika/version.rb +3 -1
  273. data/lib/insika/wiring/graph.rb +249 -0
  274. data/lib/insika/workflow.rb +185 -0
  275. data/lib/insika/workflow_registry.rb +33 -0
  276. data/lib/insika.rb +220 -4
  277. metadata +412 -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: 9e7913378bd8be19a7d9422524e62c6786c2abc8d26b5194ef8b1db8fef82be5
4
+ data.tar.gz: f1a279b7284d9756d7ad3dcef56329314062ef77c0fbb3d24df98e10c9bcb4ce
5
5
  SHA512:
6
- metadata.gz: 6f6739512bf4d07534faee7675fd157c2bcc5b95bfcbabbe9aa7acfc0fd03dede861140562aa5173ee751dcbef9108bd4a7a64bc8776a2d9da3a95aa9912d7d5
7
- data.tar.gz: 83fee89e2b6041280bb0442da9e50628c6a23dc951513c2b6a742bf6c2a205075f23711ad06bcfbe1965496bbc23385d22b7ca5e1e8fe84d0734cfdaa24545b7
6
+ metadata.gz: dcbf5403a982de5ee9dc8ed0c5fde2b21b5b63667cde0b47d2b9985b99fe7a9708d53fb82d43ce8464f46c38989d5b2b261156a84c8245e16199017515d24191
7
+ data.tar.gz: 349d532c16f5baed05fcd79318f3f68056e40f2a931e5e33ff12c339791edaa6fcdfe811d419cf3aff490118bd46698486470f234d9e820f0e568bb875e8b2cc
data/CHANGELOG.md ADDED
@@ -0,0 +1,361 @@
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 yet.
12
+
13
+ ## [0.2.0] - 2026-08-13
14
+
15
+ The workstreams between the first release and the one the gem actually became:
16
+ multi-tenancy at the edge (WS1), calendar budgets (WS2), provider reliability
17
+ (WS3), the stuck signal (WS5), operator alerts + live TTFB (WS6), and the
18
+ failure-classification core (B9) — plus the two fix rounds that made them
19
+ safe to ship.
20
+
21
+ ### Added
22
+
23
+ - **Multi-tenant at the edge (WS1)** — `INSIKA_TENANCY=multi_tenant` resolves the
24
+ Bearer to a principal before the routes: per-tenant + operator tokens stored
25
+ only as SHA-256 hashes, a tenant's sessions/tasks/streams living under its own
26
+ `<tenant>:` namespace (fail-closed: another tenant's reads as `404`), and every
27
+ authoring/config surface refused to a tenant.
28
+ - **Calendar budgets (WS2)** — `AgentProfile#budget` caps the billed spend
29
+ (input + output + cached + cache-creation) per calendar day/month and
30
+ (tenant, agent): HARD (default) fails the turn with the typed
31
+ `Insika::BudgetExceeded` + `retry_after`; `soft: true` runs the turn and warns
32
+ once per window — with the `alert_at` (`0.8`) crossing and the real cap
33
+ crossing as separate events.
34
+ - **Reliability (WS3)** — retries with backoff, mid-turn rotation to the
35
+ fallback chain, a per-`(tenant, provider/model)` circuit breaker with
36
+ half-open trials (a failed trial reopens), and a per-attempt `timeout`
37
+ (default 30s) counted as retryable. A `:fatal` provider error is never
38
+ retried.
39
+ - **Stuck signal (WS5)** — an agent declared stuck ends its turn with
40
+ `outcome: "stuck"` on the envelope and a dedicated `:turn_stuck` event — the
41
+ deterministic point a consumer escalates on.
42
+ - **Operator alerts + live TTFB (WS6)** — `budget_warning`, `breaker_open` and
43
+ `delivery_failed` POSTed to a per-agent `alerts.webhook` over the at-most-once
44
+ outbox pipeline (boot-recoverable); under `INSIKA_TURN_TIMING` the first
45
+ content chunk emits a live `:ttft` on the streaming envelope.
46
+ - **Failure classification (B9)** — provider/transport failures classified by
47
+ action (`:fatal` / `:retryable` / `:rate_limited_*`) and wrapped with the
48
+ provider's `retry_after`; mechanical tool-output dedupe (C3) back-references a
49
+ byte-identical repeat only when the reference is genuinely shorter.
50
+ - **The periodic tick** — durability no longer waits for a reboot. Serving
51
+ workers run a tick every `INSIKA_TICK_INTERVAL` (default 60s, `0` disables)
52
+ as a child of the turn supervisor: it re-drives outbox records left `:pending`
53
+ and sweeps orphaned `:queued`/`:running` tasks untouched past
54
+ `INSIKA_TICK_STALE_AFTER` (default 900s) — the orphans of a worker respawned
55
+ mid-generation are recovered without a deploy. One worker per window sweeps (a
56
+ single transactional claim); a task someone alive owns is skipped, never
57
+ failed. `:waiting`/`:paused` stay boot recovery's.
58
+
59
+ ### Fixed
60
+
61
+ - **WS2/WS3/WS6 criticals** — the budget alert marker no longer returns inside
62
+ the store transaction (a leaked `BEGIN IMMEDIATE` locked SQLite on the 2nd
63
+ over-threshold turn); the monthly reset is December-safe and UTC-aligned; an
64
+ unset reliability timeout is 30s, not 1s, and a timeout retries/rotates
65
+ instead of dying as "fatal"; a failed half-open trial reopens the circuit;
66
+ webhook deliveries pass the egress guard (SSRF); `:ttft` is emitted once per
67
+ turn; webhook channels pre-register so the boot sweep recovers pending alerts;
68
+ the alert dispatcher subscribes typed and re-subscribes on overflow.
69
+ - **WS1** — `#revoke` rides the store transaction; a `tenant_id` containing
70
+ `:` is refused (the session-namespace delimiter); `POST /v1/sessions` mints a
71
+ tenant's session under its own prefix.
72
+ - **WS2/WS3 softs** — a failed turn's consumed tokens count against the budget;
73
+ `:breaker_open` alerts only on the closed→open transition; the fallback chain
74
+ dedupes `"model"` vs `"provider/model"` spellings.
75
+
76
+ ## [0.1.0] - 2026-08-10
77
+
78
+ The first release: `gem install insika`.
79
+
80
+ ### Added
81
+
82
+ - **A publishable core** — `gem install insika` gives every shape: `reply`
83
+ in-process, `serve`, `Insika::Server.rack_app` mounted, and `embed(backend:)`. The
84
+ server and the Studio moved under `lib/insika/` and ship in the gem; the exported
85
+ store contract (`lib/insika/testing/store_contract.rb`) is what a third-party
86
+ backend specs against, now with an opt-in multi-worker group that fails a backend
87
+ whose `transaction` yields without isolation.
88
+ - **Turn pipeline** — a durable, resumable turn: command bus → context builder → policy
89
+ engine → middleware → executor tool-loop → event stream. Every turn checkpoints, so a
90
+ crash resumes without repeating side-effects.
91
+ - **Drop-in HTTP API** — `POST /v1/responses` speaking the OpenAI-Responses shape, with
92
+ SSE streaming and usage, plus an `/a2a` surface. One deployment serves many agents;
93
+ the `model` field is the agent id.
94
+ - **Agents as data** — an immutable `AgentProfile` stored as a row, created and edited
95
+ at runtime via the DSL, `POST /v1/agents` (pack import), or the Studio. Every edit is
96
+ hot; an in-flight turn keeps the profile it started with.
97
+ - **`Insika.agent { … }` DSL** — thin sugar that *generates* the same provisioning pack
98
+ the API and UI produce. `reply` for one in-process turn, `serve` for the server.
99
+ - **Embedding** — `Insika.embed(backend:) { … }` builds a graph that owns its store and
100
+ its LLM credentials, and `Insika::Server.rack_app(graph, token:)` hands you the `/v1`
101
+ transport as a value to mount in your own router. Two graphs in one process no longer
102
+ share a provider key or a database. What the process still owns — signals, the
103
+ reactor, the Studio — is written down in [Embedding](docs/EMBEDDING.md).
104
+ - **Tools** — code tools, tools defined as data (declarative HTTP manifests), and MCP
105
+ import, all behind a tool envelope with timeouts and optional human approval.
106
+ - **`halt_when`** — a data-tool can end the turn from its own **response**
107
+ (`{ "json_path": "tool_result.status", "equals": ["SUBSCRIBED"] }`), for backends that
108
+ perform the side effect *and* notify the customer themselves: without it the model
109
+ writes a second confirmation and the person gets the message twice. Per result, not
110
+ per tool — the same call still lets the model explain a failure. See
111
+ [Tools](docs/TOOLS.md#halt_when-when-the-answer-is-already-out).
112
+ - **Refinement: a gated, reviewable prompt edit** — a refinement run can now carry a
113
+ *proposal*, and the whole design is one sentence: an edit is scored by **running
114
+ it**, and a human approves it before it reaches anyone. The candidate is applied to
115
+ a throwaway clone of the agent, the golden set is replayed against the clone over
116
+ the ordinary `/v1/responses`, and any regression against the accepted baseline
117
+ disqualifies it. Nothing asks a model whether an edit looks good.
118
+ Edits are anchored and bounded (`before` must still match the file, exactly and
119
+ once) so the diff is a five-second decision, the gate's result is attributable, and
120
+ a proposal built from a stale snapshot cannot overwrite something you wrote — the
121
+ apply re-checks every anchor and refuses the whole proposal if anything drifted.
122
+ Approving writes through the versioned file store, so rollback is the Restore
123
+ button that was already there. Opt-in per agent (`refine mode: :propose, files:
124
+ %w[TOOLS.md]`); an agent with no golden cases, with no recorded baseline, or with a
125
+ baseline in which nothing passes, **cannot be edited at all** — the gate refuses
126
+ rather than passing vacuously (a regression is measured against a case that *was*
127
+ passing, so an all-red baseline would wave everything through). See
128
+ [Refinement](docs/REFINEMENT.md#changing-the-agent-the-gate).
129
+ - **A panel of proposers, a budget, and an optional unattended apply** — `proposers`
130
+ asks several models the same question and gates every answer independently; the gate
131
+ arbitrates and you are shown the best survivor with the others listed under it.
132
+ Convergence only breaks a tie (two models agreeing on wording is weak evidence; a
133
+ golden case passing is strong evidence), identical candidates are gated once, and a
134
+ model that answers prose or dies takes only itself out of the panel. `budget.tokens`
135
+ bounds what one run may spend across every proposal and every replay — checked before
136
+ each step, never mid-flight, with the candidates it could not afford recorded as such
137
+ and unmetered legs tallied rather than counted as free. `mode: :auto_apply` lets a
138
+ gate-passing edit land with nobody watching: off by default, and when on it still
139
+ needs zero regressions and a diff within `auto_apply_max_edits` (default 1) — a
140
+ larger one waits for a person instead of being thrown away. It reuses the human
141
+ approval path, so the staleness re-check, the versioned write and the undo are the
142
+ same. See [Refinement](docs/REFINEMENT.md#more-than-one-proposer).
143
+ - **The gate refuses to grade a judged baseline without a judge** — a rubric'd case
144
+ with no verdict counts as a pass, so replaying without a judge against a baseline
145
+ recorded with one does not measure less, it measures backwards. Found by running the
146
+ panel against the real pilot: the gate reported *6/6, no regression* against a
147
+ baseline the same corpus had just scored *2/6*, and both candidates cleared. With the
148
+ judge configured, the same two candidates were correctly rejected on judge-score
149
+ drops. Third member of the same family as the missing and all-red baseline refusals.
150
+ - **The refinement budget counts the prompt cache** — the engine's `total_tokens` is
151
+ input + output and excludes the cached prefix (a 27 KB pack reports `88` total against
152
+ `26624` cached), so a ceiling built on it alone let a run send hundreds of times what
153
+ it said. Cost now bills `total + cached` and records the cached share, which on a real
154
+ panel run was 95% of the spend. A run that cannot be gated is also refused **before**
155
+ the proposal is paid for, not after.
156
+ - **The eval baseline is a per-agent record, not only a file** — `evals/baseline.json`
157
+ works from a checkout; the refinement gate runs inside a deployment that has none.
158
+ `insika evals:baseline import|show|export` moves it into the store and back, with
159
+ the file staying the export format. A case the golden store does not know is
160
+ reported rather than guessed at: a silently shrunk baseline is a weaker gate.
161
+ - **Skills** — the `SKILL.md` format with progressive loading, so an agent's context
162
+ grows only when it needs to.
163
+ - **Memory** — cross-session facts and notes, per agent.
164
+ - **Guardrails** — opt-in, per-agent content safety on input and output:
165
+ prompt-injection detection, PII/secret redaction, abuse moderation.
166
+ - **Sandbox** — a confined-execution primitive for code that must run somewhere.
167
+ - **Egress guard** — bounds which hosts a tool can reach (the SSRF boundary).
168
+ - **Edge limits** — per-session rate limits and a per-agent token ceiling that halt a
169
+ flood before it costs an LLM call, backed by a usage ledger.
170
+ - **Subagents** — bounded fan-out delegation with a concurrency cap, and workflows
171
+ exposed over the API.
172
+ - **Parallel tool calls** — opt-in via `limits[:tool_concurrency]`, one number that is
173
+ both the switch and the cap on how many of a batch run at once. Off by default, and
174
+ automatically off for any turn holding an approval-required tool (two calls suspended
175
+ for an operator would deadlock on the single per-task mailbox). Documented trade-offs:
176
+ `max_tool_calls` becomes approximate, tool results are recorded in completion order,
177
+ and a turn deadline waits for in-flight calls instead of cancelling them.
178
+ - **Message coalescing** — opt-in via `limits[:queue_mode] = "collect"` plus
179
+ `debounce_ms`, so the fragments a person types in a row ("oi" / "queria saber do
180
+ pedido" / "1234567") become one turn instead of three. The quiet window is held on
181
+ the session's own fiber, never on the request, so the caller is still acked
182
+ immediately. `debounce_max_ms` caps the total deferral. Off by default
183
+ (`followup` = one turn per message, today's behavior), resolved session vars →
184
+ agent → platform, where a key set explicitly to `nil`/`0` means *off*, not
185
+ *inherit*. Only offered on surfaces whose response can report
186
+ `{"merged": true}` — a caller that cannot hear that verdict would deliver the same
187
+ answer once per fragment, so `/v1/responses` and open streams are refused rather
188
+ than silently coalesced.
189
+ - **Message steering** — opt-in via `limits[:queue_mode] = "steer"`, so a message that
190
+ arrives while the agent is *already running tools* is appended to that run instead of
191
+ waiting for it. It lands at a **tool-batch boundary**, appended at the tail: after the
192
+ last result of a batch and before the model's next step — never between two tool
193
+ results (which Anthropic rejects outright), and never rewriting anything already sent,
194
+ so the prompt cache survives. A steered message is a first-class transcript message
195
+ with no origin, because a person wrote it. Bounded by `steer_max_messages`, worded by
196
+ an optional `steer_join` template. Four cases the run cannot absorb — a turn with no
197
+ tool call, a batch ending in `halt_when`, a workflow run, and overflow past the bound —
198
+ release the message as the next turn on the session rather than losing it. Same
199
+ surface rule as coalescing: only a caller that can hear `{"steered": true}` may steer,
200
+ since the reply belongs to the turn it joined.
201
+ - **Message interrupt** — opt-in via `limits[:queue_mode] = "interrupt"`, for the message
202
+ that makes the turn in flight *wrong* ("não, esquece isso"). That turn is abandoned at
203
+ its next boundary and the new message becomes an ordinary turn, with its own `task_id`
204
+ and its own reply — so unlike coalescing and steering it needs no verdict field and
205
+ works on every surface. A tool call in flight still runs to completion and is recorded:
206
+ the batch is one unit of work, and faking failures would teach the model that tools
207
+ failed when they did not.
208
+ - **A cancelled turn publishes nothing.** A cancel that arrived while the provider was
209
+ working used to be observed only after the answer had already been streamed, so the
210
+ customer read the reply of a turn that then terminated `:cancelled` and persisted
211
+ nothing — text delivered, transcript silent about it. There is now a boundary between
212
+ the provider's last word and publishing, so the two never disagree. Applies to every
213
+ cancel, not just `interrupt`.
214
+ - **Channels** — a way in and out for people, registered by id and mounted on one
215
+ generic route family under `/channels/<id>/`. A channel translates and authenticates,
216
+ and does nothing else: it may refuse a request, never widen one. Plugins register
217
+ their own through `contracts.channels`, and a deployment that registers none has no
218
+ such route at all. See [Channels](docs/CHANNELS.md).
219
+ - **The web widget** — one `<script>` tag on a site and the adopter has an agent: a
220
+ bubble, a panel, and the answer streaming in token by token, with no backend of
221
+ theirs, no build step and no npm. It is the channel for a team with *no* messaging
222
+ stack of its own, and the first one where the reply rides the request's own
223
+ connection. Because the caller is an anonymous browser there is no secret to check,
224
+ so three things stand in for one: an exact-match origin allowlist, an agent
225
+ allowlist (a visitor addresses what the operator published and nothing else), and a
226
+ **mandatory chat rate limit** — the widget answers `503` until one is configured,
227
+ which is the only place the engine refuses to serve rather than warn. The engine
228
+ issues session ids and the client never proposes one. See
229
+ [Channels](docs/CHANNELS.md#the-web-widget).
230
+ - **The relay channel** — for an adopter who already owns a messaging stack (a WhatsApp
231
+ BSP, a Zendesk, a legacy app) and wants the engine for the **turn**, not the platform.
232
+ You POST the customer's message to `/channels/relay/events`; the engine acks
233
+ immediately and POSTs the answer to your own callback when there is one. Everything
234
+ platform-shaped — the 24-hour window, templates, media, read receipts, WhatsApp
235
+ formatting — stays yours, permanently: this is not a migration step toward a native
236
+ channel. Configured entirely by environment (`INSIKA_RELAY_TOKEN`, which is both the
237
+ switch and the credential, plus the callback URL and its optional bearer), so there is
238
+ no way to expose the route without a secret.
239
+ - **At-most-once outbound delivery** — a reply owed to a channel is written durably when
240
+ the turn commits and **claimed** before the HTTP call, so a crash loses a delivery
241
+ rather than duplicating it; bounded retry with backoff handles a recipient that is
242
+ merely down, and boot re-dispatches what was recorded but never claimed. Each attempt
243
+ reports on `:channel_delivered`, because a turn completing says nothing about whether
244
+ the customer received it.
245
+ - **Inbound deduplication** — an optional `event_id` on a channel message is remembered
246
+ for 24 hours, so a platform retrying a webhook it already delivered gets
247
+ `{"duplicate": true}` and the same `task_id` back instead of a second LLM turn and a
248
+ second message. No id means at-least-once turns, said out loud rather than papered
249
+ over with a content hash.
250
+ - **Studio** — a web control UI for agents, prompts, skills, tools, sessions, tasks,
251
+ approvals, and settings, with live transcripts over SSE.
252
+ - **LLM-first onboarding** — `GET /start.md`, `GET /models.json`, and `GET /docs/*.md`,
253
+ so a coding agent can set up the first agent by reading a running instance.
254
+ - **Evals** — a small corpus of real conversations, replayed against a running deployment
255
+ and checked two ways: deterministic assertions (was the tool called, did a CPF leak)
256
+ and a **rubric** scored by a judge. Cases are YAML data, authorable in the Studio as
257
+ well as in the repo, and the graders are a **panel of distinct models** with
258
+ configurable aggregation and agreement — sampling one model N times measures its
259
+ variance, not its bias. A baseline turns a run into a pre-merge gate that blocks only
260
+ on regressions. A case can also carry the **incumbent's real conversation** for the
261
+ same opening (`reference:`), and `--pairwise` asks the panel which one served the
262
+ customer better — the question an absolute score cannot answer when you are replacing
263
+ a system that already works. The judge never learns which transcript is Insika's, and
264
+ every judge is asked twice with the sides swapped, so a preference that depends on
265
+ presentation order is reported as such instead of counted. See
266
+ [Evals](docs/EVALS.md).
267
+ - **Refinement** — an agent's own traffic read back as a ranked report of what broke:
268
+ repeated tool errors grouped by their normalized signature, failed turns, customers
269
+ repeating themselves, canned safe replies served instead of answers, and granted tools
270
+ that never fired. Provenance is session ids; snippets go through the same redaction as
271
+ a customer-facing turn. Fired by hand (`insika refine --agent <id>` or the Studio
272
+ button) — the engine grows no scheduler. It calls no model and edits nothing.
273
+ `exclude_sessions` keeps load-test and debug traffic from burying real findings, and
274
+ reports what it dropped. See [Refinement](docs/REFINEMENT.md).
275
+ - **Observability** — an event stream and per-session tool-call traces always on, plus
276
+ opt-in OpenTelemetry traces *and* metrics under a documented, vendor-neutral attribute
277
+ convention with an operator-declared pricing table for estimated cost.
278
+ - **Operations** — SQLite (or in-memory) stores, Falcon on the Async fiber scheduler,
279
+ strict configuration validation with a `doctor` check, and a provider-free benchmark
280
+ that measures engine overhead alone.
281
+ - **Documentation site** — the `docs/*.md` files are also a Jekyll (Just the Docs) site
282
+ published at <https://guizaols.github.io/insika/>. Same files, no copy: what GitHub
283
+ renders, what `GET /docs/<name>.md` serves, and what the site publishes are one source.
284
+
285
+ ### Changed
286
+
287
+ - `GET /docs/<name>.md` strips the docs-site frontmatter, so the raw markdown a coding
288
+ agent receives is the prose only.
289
+
290
+ ### Fixed
291
+
292
+ - **A prompt file could silently become a serialized object.** `Pack.from_h` normalized
293
+ only the KEYS of `files`/`skills`, and both `WriteAgentFile` and `AgentFileStore#write`
294
+ called `to_s` on whatever they were handed — so a pack shaped
295
+ `{"files": {"AGENTS.md": {"content": "…"}}}`, or an entry read and written back, was
296
+ stored as Ruby's `#inspect` of the object. The agent then received its whole prompt as
297
+ one line of `{"content" => "…\n…"}`, escapes and all, on every turn, while the file
298
+ looked perfectly healthy: present, non-empty, and the agent still answered. All three
299
+ layers now refuse a Hash or an Array instead of coercing it, and `insika doctor` sweeps
300
+ existing prompt files for the same shape.
301
+
302
+ ### Security
303
+
304
+ - **The `/v1` surface is closed by default.** `POST /v1/commands/<type>` — the generic
305
+ Command ingress, which dispatches **any** registered authoring Command
306
+ (`write_agent_file`, `write_data_tool`, `upsert_llm_provider`, `update_settings`,
307
+ `delete_agent`) — answered **without any Authorization header**, as did
308
+ `POST /v1/sessions`, `POST /v1/messages`, `POST /v1/workflows/<name>`, and the
309
+ `GET /v1/sessions/:id` · `/v1/tasks/:id` · `/v1/events` reads. Anyone who knew a
310
+ deployment's URL could rewrite an agent's prompt, repoint a tool at their own host,
311
+ swap the LLM provider's credentials, or read every conversation.
312
+
313
+ The gate was each handler's job to call, and the generic route never did. It now runs
314
+ in the router, before any dispatch, against an **allowlist** of public routes (`/up`,
315
+ the opt-in onboarding surface, the A2A agent card) — so the next route added is closed
316
+ until someone publishes it deliberately. With no token configured the whole surface is
317
+ `503`, never open by omission. `/a2a` is now behind the same Bearer.
318
+
319
+ ### Fixed
320
+
321
+ - **An agent's inline identity now reaches the model.** `base_prompt` — what the DSL's
322
+ `instructions` and a pack manifest set — was stored, round-tripped and advertised on
323
+ the A2A agent card, but never injected into the system prompt: every composition root
324
+ wires the prompt provider with `base: ""`, so an agent whose identity was inline ran
325
+ with *no* identity. Agents whose identity comes from `prompt_files` (the production
326
+ path) were unaffected.
327
+ - **`max_tokens` now reaches the provider.** The agent param was authored in the DSL,
328
+ the Studio and a pack, resolved onto the turn — and then sent to the chat as
329
+ `with_max_output_tokens`, a method no version of `ruby_llm` has. Guarded by
330
+ `respond_to?`, it was skipped in silence, so an agent with a token ceiling ran without
331
+ one. It now rides `with_params(max_tokens:)`, merged into a single call with the
332
+ reasoning toggle (`with_params` replaces the gem's whole params hash, so two calls
333
+ would drop the first one's keys).
334
+ - **A data-tool whose API moved now says so.** A 3xx counted as success, and since
335
+ servers send a redirect with an empty body, the model received `""` and narrated a
336
+ plausible outage. The HTTP client still does not follow the hop — the egress guard
337
+ cleared the authored URL, not the redirect's destination — but the tool now returns
338
+ `HTTP 301: moved to <url>`, which names the definition to fix. This is what broke
339
+ `examples/data-tool/currency_agent.rb` (its endpoint moved host).
340
+ - **A data tool's parameters are no longer half-invented by the engine.** In the flat
341
+ authoring form, a param typed `array` used to lift to `items: {type: "string"}` — an
342
+ item type nobody wrote. So a param whose API takes a list of *objects*
343
+ (`[{query, filters}]`) reached the provider declared as a list of *strings*: the model
344
+ obeyed the schema it was given, the backend answered `200`, and the results were
345
+ wrong with no error anywhere. Three changes close that loop:
346
+ - bare `array` is refused at ingestion (the JSON Schema path already refused an array
347
+ without `items`); the flat form now spells a list of scalars `array:string` /
348
+ `array:number` / `array:integer` / `array:boolean`, and a list of objects is written
349
+ as JSON Schema. **Breaking for authoring**; `insika doctor` reports any stored tool
350
+ left behind and offers the meaning-preserving rewrite.
351
+ - the Studio tool editor no longer flattens what it cannot render. A nested schema
352
+ shows as JSON Schema and saves back unchanged — before, opening and saving a nested
353
+ tool silently replaced its schema with the broken flat one.
354
+ - a tool call's arguments are validated against the tool's own schema before the
355
+ request is built, so a malformed call becomes an `{ error: }` naming the offending
356
+ path (`query_filter_pairs[0]: expected an object, got a string`) that the model can
357
+ act on. Structure is strict; scalars accept their lossless string form (`"2"`,
358
+ `"true"`) and are never coerced.
359
+ - **`insika doctor` now sees broken data tools.** A stored definition that no longer
360
+ builds is dropped by the tool overlay with only a stderr warning — the agent quietly
361
+ 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-v4-flash"
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)).