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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +295 -0
- data/LICENSE +21 -0
- data/README.md +136 -2
- data/bin/insika +351 -0
- data/docs/AGENTS.md +494 -0
- data/docs/ARCHITECTURE.md +333 -0
- data/docs/BENCHMARK.md +114 -0
- data/docs/CHANNELS.md +453 -0
- data/docs/CONTEXT.md +100 -0
- data/docs/DEPLOY.md +334 -0
- data/docs/EMBEDDING.md +194 -0
- data/docs/EVALS.md +273 -0
- data/docs/LOADTEST.md +231 -0
- data/docs/OBSERVABILITY.md +365 -0
- data/docs/PLUGINS.md +211 -0
- data/docs/REFINEMENT.md +477 -0
- data/docs/RELEASING.md +70 -0
- data/docs/RUNNING-LOCAL.md +153 -0
- data/docs/SANDBOX.md +114 -0
- data/docs/SECURITY.md +362 -0
- data/docs/SKILLS.md +98 -0
- data/docs/TOOLS.md +302 -0
- data/docs/WHY.md +137 -0
- data/docs/WORKFLOWS.md +225 -0
- data/docs/build.md +14 -0
- data/docs/index.md +68 -0
- data/docs/onboarding/start.md +126 -0
- data/docs/operate.md +12 -0
- data/docs/ship.md +10 -0
- data/docs/understand.md +10 -0
- data/lib/insika/agent_file_store.rb +125 -0
- data/lib/insika/agent_profile.rb +188 -0
- data/lib/insika/allowlist.rb +28 -0
- data/lib/insika/baseline_store.rb +74 -0
- data/lib/insika/capability/resolved_tool.rb +34 -0
- data/lib/insika/capability_registry.rb +112 -0
- data/lib/insika/channel_delivery.rb +150 -0
- data/lib/insika/channel_registry.rb +30 -0
- data/lib/insika/channels/relay.rb +178 -0
- data/lib/insika/channels/web/widget.js +283 -0
- data/lib/insika/channels/web.rb +211 -0
- data/lib/insika/chat_builder.rb +254 -0
- data/lib/insika/checkpoint.rb +13 -0
- data/lib/insika/checkpoint_store.rb +153 -0
- data/lib/insika/coercion.rb +50 -0
- data/lib/insika/command.rb +32 -0
- data/lib/insika/command_bus.rb +39 -0
- data/lib/insika/commands/agent_payload.rb +41 -0
- data/lib/insika/commands/approve_action.rb +46 -0
- data/lib/insika/commands/cancel_task.rb +33 -0
- data/lib/insika/commands/create_agent.rb +54 -0
- data/lib/insika/commands/create_session.rb +67 -0
- data/lib/insika/commands/delete_agent.rb +33 -0
- data/lib/insika/commands/delete_agent_file.rb +50 -0
- data/lib/insika/commands/delete_data_tool.rb +33 -0
- data/lib/insika/commands/delete_llm_provider.rb +36 -0
- data/lib/insika/commands/delete_mcp.rb +30 -0
- data/lib/insika/commands/delete_system_file.rb +29 -0
- data/lib/insika/commands/gate_refinement.rb +245 -0
- data/lib/insika/commands/import_mcp_tools.rb +48 -0
- data/lib/insika/commands/import_tools.rb +81 -0
- data/lib/insika/commands/memory_add_note.rb +32 -0
- data/lib/insika/commands/memory_forget_fact.rb +32 -0
- data/lib/insika/commands/memory_put_fact.rb +35 -0
- data/lib/insika/commands/pause_task.rb +29 -0
- data/lib/insika/commands/resolve_refinement.rb +126 -0
- data/lib/insika/commands/restore_agent_file.rb +36 -0
- data/lib/insika/commands/restore_data_tool.rb +34 -0
- data/lib/insika/commands/restore_system_file.rb +31 -0
- data/lib/insika/commands/resume_task.rb +85 -0
- data/lib/insika/commands/run_refinement.rb +133 -0
- data/lib/insika/commands/send_message.rb +150 -0
- data/lib/insika/commands/set_agent_tools.rb +39 -0
- data/lib/insika/commands/set_skill_agents.rb +71 -0
- data/lib/insika/commands/trigger_workflow.rb +80 -0
- data/lib/insika/commands/update_agent.rb +49 -0
- data/lib/insika/commands/update_settings.rb +33 -0
- data/lib/insika/commands/upsert_llm_provider.rb +34 -0
- data/lib/insika/commands/upsert_mcp.rb +32 -0
- data/lib/insika/commands/write_agent_file.rb +57 -0
- data/lib/insika/commands/write_data_tool.rb +43 -0
- data/lib/insika/commands/write_golden.rb +58 -0
- data/lib/insika/commands/write_skill.rb +50 -0
- data/lib/insika/commands/write_system_file.rb +31 -0
- data/lib/insika/config_store.rb +85 -0
- data/lib/insika/context/builder.rb +166 -0
- data/lib/insika/context/catalog_provider.rb +23 -0
- data/lib/insika/context/fragment.rb +19 -0
- data/lib/insika/context/priority.rb +29 -0
- data/lib/insika/context/provider.rb +19 -0
- data/lib/insika/context/providers/memory.rb +60 -0
- data/lib/insika/context/providers/prompt.rb +105 -0
- data/lib/insika/context/providers/request.rb +32 -0
- data/lib/insika/context/providers/session.rb +108 -0
- data/lib/insika/context/providers/skill.rb +20 -0
- data/lib/insika/context/providers/tool_search.rb +20 -0
- data/lib/insika/delegation_store.rb +153 -0
- data/lib/insika/doctor.rb +294 -0
- data/lib/insika/dsl/definition.rb +55 -0
- data/lib/insika/dsl/runtime.rb +379 -0
- data/lib/insika/dsl/server_boot.rb +97 -0
- data/lib/insika/dsl/system.rb +93 -0
- data/lib/insika/dsl/workflow_adapter.rb +59 -0
- data/lib/insika/dsl.rb +307 -0
- data/lib/insika/edge_limiter.rb +130 -0
- data/lib/insika/egress_guard.rb +75 -0
- data/lib/insika/env_schema.rb +246 -0
- data/lib/insika/errors.rb +145 -0
- data/lib/insika/evals/assertions.rb +247 -0
- data/lib/insika/evals/baseline.rb +69 -0
- data/lib/insika/evals/golden.rb +172 -0
- data/lib/insika/evals/judge.rb +225 -0
- data/lib/insika/evals/pairwise.rb +178 -0
- data/lib/insika/evals/report.rb +115 -0
- data/lib/insika/evals/runner.rb +141 -0
- data/lib/insika/evals/transport.rb +178 -0
- data/lib/insika/event.rb +18 -0
- data/lib/insika/event_stream.rb +114 -0
- data/lib/insika/executor.rb +1680 -0
- data/lib/insika/frontmatter.rb +42 -0
- data/lib/insika/golden_store.rb +145 -0
- data/lib/insika/hooks.rb +48 -0
- data/lib/insika/http_client.rb +63 -0
- data/lib/insika/inbound_log.rb +84 -0
- data/lib/insika/llm_configurator.rb +99 -0
- data/lib/insika/llm_provider_store.rb +83 -0
- data/lib/insika/mcp_http_client.rb +67 -0
- data/lib/insika/mcp_store.rb +115 -0
- data/lib/insika/mcp_tool_ingestor.rb +143 -0
- data/lib/insika/memory_store.rb +93 -0
- data/lib/insika/message_origin.rb +76 -0
- data/lib/insika/middleware.rb +36 -0
- data/lib/insika/model_policy.rb +52 -0
- data/lib/insika/model_resolver.rb +176 -0
- data/lib/insika/model_selection.rb +114 -0
- data/lib/insika/onboarding.rb +208 -0
- data/lib/insika/outbox_store.rb +166 -0
- data/lib/insika/overlay_tool_registry.rb +103 -0
- data/lib/insika/pack.rb +102 -0
- data/lib/insika/pack_importer.rb +121 -0
- data/lib/insika/pending_action_store.rb +120 -0
- data/lib/insika/plugin/loader.rb +356 -0
- data/lib/insika/plugin.rb +35 -0
- data/lib/insika/policy/engine.rb +83 -0
- data/lib/insika/policy/policy.rb +120 -0
- data/lib/insika/policy_registry.rb +23 -0
- data/lib/insika/profile_source.rb +137 -0
- data/lib/insika/prompt_catalog.rb +61 -0
- data/lib/insika/queue_policy.rb +167 -0
- data/lib/insika/recovery.rb +127 -0
- data/lib/insika/refinement/candidate.rb +159 -0
- data/lib/insika/refinement/evidence_collector.rb +371 -0
- data/lib/insika/refinement/gate.rb +234 -0
- data/lib/insika/refinement/panel.rb +222 -0
- data/lib/insika/refinement/proposer.rb +262 -0
- data/lib/insika/refinement_store.rb +295 -0
- data/lib/insika/registry.rb +59 -0
- data/lib/insika/safety/config.rb +109 -0
- data/lib/insika/safety/detectors.rb +176 -0
- data/lib/insika/safety/factory.rb +102 -0
- data/lib/insika/safety/input_guardrail.rb +87 -0
- data/lib/insika/safety/moderator.rb +86 -0
- data/lib/insika/safety/output_filter.rb +79 -0
- data/lib/insika/safety/output_validator.rb +101 -0
- data/lib/insika/safety/safe_responses.rb +47 -0
- data/lib/insika/sandbox/boundary.rb +93 -0
- data/lib/insika/sandbox/docker.rb +74 -0
- data/lib/insika/sandbox/local.rb +33 -0
- data/lib/insika/sandbox/runner.rb +80 -0
- data/lib/insika/sandbox.rb +85 -0
- data/lib/insika/schema_guard.rb +147 -0
- data/lib/insika/secret_masking.rb +34 -0
- data/lib/insika/server/a2a/agent_card.rb +27 -0
- data/lib/insika/server/a2a/app.rb +112 -0
- data/lib/insika/server/a2a/client.rb +101 -0
- data/lib/insika/server/a2a/errors.rb +32 -0
- data/lib/insika/server/a2a/http.rb +42 -0
- data/lib/insika/server/a2a/message.rb +27 -0
- data/lib/insika/server/a2a/protocol.rb +45 -0
- data/lib/insika/server/a2a/remotes.rb +25 -0
- data/lib/insika/server/a2a/task_projection.rb +40 -0
- data/lib/insika/server/admin_auth.rb +29 -0
- data/lib/insika/server/app.rb +850 -0
- data/lib/insika/server/boot.rb +119 -0
- data/lib/insika/server/rack_app.rb +110 -0
- data/lib/insika/server/responses.rb +155 -0
- data/lib/insika/server/sse_body.rb +96 -0
- data/lib/insika/session_actor.rb +162 -0
- data/lib/insika/session_store.rb +143 -0
- data/lib/insika/settings_store.rb +154 -0
- data/lib/insika/shutdown.rb +125 -0
- data/lib/insika/skill_catalog.rb +113 -0
- data/lib/insika/skill_store.rb +79 -0
- data/lib/insika/steer_injector.rb +110 -0
- data/lib/insika/store.rb +52 -0
- data/lib/insika/stores/memory.rb +123 -0
- data/lib/insika/stores/sqlite.rb +183 -0
- data/lib/insika/studio/app.rb +1571 -0
- data/lib/insika/studio/assets/dist/application.css +1 -0
- data/lib/insika/studio/assets/dist/application.js +69 -0
- data/lib/insika/studio/forms.rb +340 -0
- data/lib/insika/studio/nav_icons.rb +31 -0
- data/lib/insika/studio/views/_message.erb +44 -0
- data/lib/insika/studio/views/agent_detail.erb +285 -0
- data/lib/insika/studio/views/agents.erb +63 -0
- data/lib/insika/studio/views/approvals.erb +41 -0
- data/lib/insika/studio/views/chats.erb +34 -0
- data/lib/insika/studio/views/evals.erb +83 -0
- data/lib/insika/studio/views/home.erb +72 -0
- data/lib/insika/studio/views/layout.erb +94 -0
- data/lib/insika/studio/views/login.erb +17 -0
- data/lib/insika/studio/views/mcp.erb +91 -0
- data/lib/insika/studio/views/not_found.erb +5 -0
- data/lib/insika/studio/views/playground.erb +47 -0
- data/lib/insika/studio/views/refinement.erb +234 -0
- data/lib/insika/studio/views/session.erb +62 -0
- data/lib/insika/studio/views/settings.erb +173 -0
- data/lib/insika/studio/views/skills.erb +86 -0
- data/lib/insika/studio/views/system_files.erb +65 -0
- data/lib/insika/studio/views/task.erb +105 -0
- data/lib/insika/studio/views/tasks.erb +33 -0
- data/lib/insika/studio/views/tool_edit.erb +107 -0
- data/lib/insika/studio/views/tools.erb +89 -0
- data/lib/insika/subagent_graph.rb +96 -0
- data/lib/insika/system_file_store.rb +96 -0
- data/lib/insika/task_actor.rb +128 -0
- data/lib/insika/task_store.rb +250 -0
- data/lib/insika/telemetry/pricing.rb +104 -0
- data/lib/insika/telemetry/recorder.rb +228 -0
- data/lib/insika/telemetry.rb +127 -0
- data/lib/insika/testing/store_contract.rb +270 -0
- data/lib/insika/token_estimator.rb +16 -0
- data/lib/insika/tool_assembly.rb +140 -0
- data/lib/insika/tool_catalog.rb +89 -0
- data/lib/insika/tool_definition.rb +518 -0
- data/lib/insika/tool_envelope.rb +140 -0
- data/lib/insika/tool_manifest.rb +218 -0
- data/lib/insika/tool_registry.rb +21 -0
- data/lib/insika/tool_store.rb +135 -0
- data/lib/insika/tool_trace_store.rb +92 -0
- data/lib/insika/tools/a2a_remote.rb +48 -0
- data/lib/insika/tools/agent_enum.rb +68 -0
- data/lib/insika/tools/concurrency.rb +54 -0
- data/lib/insika/tools/data_defined_tool.rb +220 -0
- data/lib/insika/tools/load_skill.rb +41 -0
- data/lib/insika/tools/remember.rb +53 -0
- data/lib/insika/tools/subagent.rb +75 -0
- data/lib/insika/tools/subagents.rb +77 -0
- data/lib/insika/tools/tool_search.rb +94 -0
- data/lib/insika/turn_output.rb +139 -0
- data/lib/insika/turn_state.rb +158 -0
- data/lib/insika/turn_timing.rb +56 -0
- data/lib/insika/usage_ledger.rb +47 -0
- data/lib/insika/version.rb +3 -1
- data/lib/insika/wiring/graph.rb +198 -0
- data/lib/insika/workflow.rb +185 -0
- data/lib/insika/workflow_registry.rb +33 -0
- data/lib/insika.rb +203 -4
- metadata +395 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 73bf4ba4e364c79f87d77202bc0f667bae1a9ea46b1c4b880350b10f4b2325cc
|
|
4
|
+
data.tar.gz: d1cfc50cc5968f041f4edf4ad803b33fbcbaef7ec68930fc00543d8bf30a7a8d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
3
|
+
**Your agent is the idea. Insika is what holds it up in production.**
|
|
4
4
|
|
|
5
|
-
|
|
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)).
|