insika 0.9.0 → 1.0.1
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 +237 -2
- data/README.md +5 -3
- data/bin/insika +3 -0
- data/bin/insika-router +0 -1
- data/docs/AGENTS.md +90 -7
- data/docs/ARCHITECTURE.md +12 -7
- data/docs/ARTIFACTS.md +33 -3
- data/docs/BENCHMARK.md +50 -7
- data/docs/CONTEXT.md +63 -0
- data/docs/CROSS-HARNESS-BENCH.md +157 -0
- data/docs/DEPLOY.md +57 -11
- data/docs/EMBEDDING.md +10 -0
- data/docs/EVALS.md +33 -4
- data/docs/FACTS.md +8 -6
- data/docs/HARVEST.md +8 -3
- data/docs/KNOWLEDGE.md +7 -0
- data/docs/MEDIA.md +6 -2
- data/docs/OBSERVABILITY.md +143 -22
- data/docs/PERFORMANCE.md +110 -0
- data/docs/PLUGINS.md +4 -4
- data/docs/POLICY.md +22 -13
- data/docs/RELEASING.md +5 -0
- data/docs/ROUTER.md +7 -0
- data/docs/RUBYLLM_2_MIGRATION.md +307 -0
- data/docs/SECURITY.md +7 -0
- data/docs/SHARED-CONVERSATIONS.md +40 -0
- data/docs/SKILLS.md +5 -3
- data/docs/STUDIO.md +122 -0
- data/docs/TOOLS.md +111 -9
- data/docs/operate.md +3 -0
- data/lib/insika/agent_defaults.rb +140 -0
- data/lib/insika/agent_file_store.rb +12 -0
- data/lib/insika/agent_profile.rb +78 -8
- data/lib/insika/artifact_signing.rb +11 -0
- data/lib/insika/chat_builder.rb +79 -25
- data/lib/insika/checkpoint.rb +6 -1
- data/lib/insika/checkpoint_store.rb +59 -7
- data/lib/insika/commands/agent_payload.rb +3 -3
- data/lib/insika/commands/delete_kit.rb +26 -0
- data/lib/insika/commands/gate_harvest.rb +3 -0
- data/lib/insika/commands/review_guardrail_flag.rb +38 -0
- data/lib/insika/commands/run_harvest.rb +17 -1
- data/lib/insika/commands/seed_session.rb +11 -1
- data/lib/insika/commands/send_message.rb +9 -3
- data/lib/insika/commands/update_settings.rb +23 -1
- data/lib/insika/commands/write_kit.rb +27 -0
- data/lib/insika/compaction.rb +33 -7
- data/lib/insika/config_store.rb +87 -7
- data/lib/insika/context/priority.rb +3 -0
- data/lib/insika/context/provider.rb +2 -2
- data/lib/insika/context/providers/knowledge.rb +25 -2
- data/lib/insika/context/providers/memory.rb +53 -4
- data/lib/insika/context/providers/pending_confirmation.rb +52 -0
- data/lib/insika/context/providers/prompt.rb +17 -4
- data/lib/insika/context/providers/session.rb +39 -3
- data/lib/insika/context/reranker.rb +75 -0
- data/lib/insika/distill.rb +22 -13
- data/lib/insika/doctor.rb +34 -4
- data/lib/insika/dsl/runtime.rb +36 -4
- data/lib/insika/dsl/server_boot.rb +4 -12
- data/lib/insika/dsl.rb +13 -5
- data/lib/insika/edge_limiter.rb +23 -15
- data/lib/insika/egress_guard.rb +11 -3
- data/lib/insika/env_schema.rb +33 -73
- data/lib/insika/evals/assertions.rb +171 -16
- data/lib/insika/evals/golden.rb +141 -4
- data/lib/insika/evals/judge.rb +19 -10
- data/lib/insika/evals/moderator_compare.rb +47 -0
- data/lib/insika/evals/report.rb +61 -9
- data/lib/insika/evals/runner.rb +44 -4
- data/lib/insika/evals/simulator.rb +10 -1
- data/lib/insika/evals/transport.rb +200 -0
- data/lib/insika/event_stream.rb +63 -7
- data/lib/insika/evidence.rb +29 -8
- data/lib/insika/executor.rb +668 -141
- data/lib/insika/harvest.rb +26 -14
- data/lib/insika/harvest_engine.rb +1 -0
- data/lib/insika/harvest_store.rb +12 -0
- data/lib/insika/http_client.rb +87 -4
- data/lib/insika/kits.rb +54 -0
- data/lib/insika/knowledge.rb +34 -20
- data/lib/insika/llm_configurator.rb +46 -4
- data/lib/insika/llm_provider_store.rb +1 -3
- data/lib/insika/llm_trace_store.rb +54 -0
- data/lib/insika/mcp_client.rb +27 -63
- data/lib/insika/mcp_live_tool.rb +55 -9
- data/lib/insika/mcp_store.rb +32 -1
- data/lib/insika/mcp_tool_registry.rb +69 -26
- data/lib/insika/media.rb +38 -25
- data/lib/insika/memory_store.rb +4 -3
- data/lib/insika/memory_trim.rb +60 -0
- data/lib/insika/middleware.rb +12 -3
- data/lib/insika/model_metrics_store.rb +285 -0
- data/lib/insika/model_registry.rb +110 -0
- data/lib/insika/model_resolver.rb +1 -1
- data/lib/insika/model_selection.rb +14 -21
- data/lib/insika/model_visible.rb +17 -6
- data/lib/insika/onboarding.rb +1 -0
- data/lib/insika/outcome_store.rb +1 -1
- data/lib/insika/overlay_tool_registry.rb +25 -2
- data/lib/insika/pack_importer.rb +54 -9
- data/lib/insika/packaging.rb +14 -5
- data/lib/insika/pending_action_store.rb +45 -18
- data/lib/insika/plugin/loader.rb +4 -2
- data/lib/insika/profile_source.rb +4 -0
- data/lib/insika/provider_error_classifier.rb +18 -3
- data/lib/insika/queue_policy.rb +7 -2
- data/lib/insika/refinement/proposer.rb +42 -17
- data/lib/insika/reliability.rb +12 -3
- data/lib/insika/router/app.rb +14 -5
- data/lib/insika/router/session_key.rb +35 -5
- data/lib/insika/safety/config.rb +20 -3
- data/lib/insika/safety/corpus.rb +10 -3
- data/lib/insika/safety/factory.rb +39 -1
- data/lib/insika/safety/input_guardrail.rb +21 -1
- data/lib/insika/safety/judge_moderator.rb +70 -0
- data/lib/insika/safety/output_validator.rb +29 -5
- data/lib/insika/safety/prompt_echo.rb +89 -0
- data/lib/insika/schema_guard.rb +53 -76
- data/lib/insika/server/app.rb +17 -8
- data/lib/insika/server/responses.rb +19 -1
- data/lib/insika/session_actor.rb +58 -28
- data/lib/insika/session_store.rb +159 -10
- data/lib/insika/settings_store.rb +47 -1
- data/lib/insika/shared_conversations.rb +305 -0
- data/lib/insika/skill_catalog.rb +28 -2
- data/lib/insika/skill_store.rb +6 -0
- data/lib/insika/soak/runner.rb +1 -1
- data/lib/insika/steer_injector.rb +7 -48
- data/lib/insika/store.rb +29 -1
- data/lib/insika/stores/json_value.rb +37 -0
- data/lib/insika/stores/memory.rb +22 -35
- data/lib/insika/stores/postgres.rb +295 -0
- data/lib/insika/stores/sqlite.rb +257 -43
- data/lib/insika/stores/turn_counter.rb +25 -0
- data/lib/insika/studio/app.rb +1453 -161
- data/lib/insika/studio/assets/dist/application.css +1 -1
- data/lib/insika/studio/assets/dist/application.js +25 -25
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-sans-latin-wght-normal.woff2 +0 -0
- data/lib/insika/studio/forms.rb +171 -9
- data/lib/insika/studio/nav_icons.rb +3 -0
- data/lib/insika/studio/views/_agent_config_retrieval.erb +73 -0
- data/lib/insika/studio/views/_agent_picker.erb +15 -0
- data/lib/insika/studio/views/_agent_tab_config.erb +78 -242
- data/lib/insika/studio/views/_agent_tab_history.erb +56 -1
- data/lib/insika/studio/views/_agent_tab_performance.erb +34 -0
- data/lib/insika/studio/views/_agent_tab_prompts.erb +7 -9
- data/lib/insika/studio/views/_agent_tab_skills.erb +16 -7
- data/lib/insika/studio/views/_agent_tab_tools.erb +90 -0
- data/lib/insika/studio/views/_agents_master.erb +8 -12
- data/lib/insika/studio/views/_cfg_alerts.erb +4 -0
- data/lib/insika/studio/views/_cfg_budget.erb +20 -0
- data/lib/insika/studio/views/_cfg_distill.erb +26 -0
- data/lib/insika/studio/views/_cfg_followup.erb +30 -0
- data/lib/insika/studio/views/_cfg_grounding.erb +21 -0
- data/lib/insika/studio/views/_cfg_guardrails.erb +47 -0
- data/lib/insika/studio/views/_cfg_harvest.erb +35 -0
- data/lib/insika/studio/views/_cfg_reliability.erb +32 -0
- data/lib/insika/studio/views/_chats_master.erb +66 -0
- data/lib/insika/studio/views/_customers_master.erb +37 -0
- data/lib/insika/studio/views/_empty.erb +15 -0
- data/lib/insika/studio/views/_facts_master.erb +42 -0
- data/lib/insika/studio/views/_harvest_candidate_detail.erb +14 -0
- data/lib/insika/studio/views/_identity.erb +16 -0
- data/lib/insika/studio/views/_kpi.erb +17 -0
- data/lib/insika/studio/views/_message.erb +40 -2
- data/lib/insika/studio/views/_model_chart.erb +82 -0
- data/lib/insika/studio/views/_refinement_master.erb +38 -0
- data/lib/insika/studio/views/{tool_edit.erb → _tool_edit.erb} +32 -9
- data/lib/insika/studio/views/_tool_users.erb +17 -0
- data/lib/insika/studio/views/_why.erb +53 -0
- data/lib/insika/studio/views/agent_detail.erb +17 -10
- data/lib/insika/studio/views/agents.erb +28 -16
- data/lib/insika/studio/views/approvals.erb +16 -11
- data/lib/insika/studio/views/artifact.erb +20 -7
- data/lib/insika/studio/views/artifacts.erb +19 -14
- data/lib/insika/studio/views/chats.erb +22 -29
- data/lib/insika/studio/views/customer.erb +88 -43
- data/lib/insika/studio/views/customers.erb +16 -26
- data/lib/insika/studio/views/evals.erb +49 -31
- data/lib/insika/studio/views/fact_detail.erb +114 -0
- data/lib/insika/studio/views/facts.erb +15 -116
- data/lib/insika/studio/views/followups.erb +63 -52
- data/lib/insika/studio/views/funnel.erb +104 -43
- data/lib/insika/studio/views/harvest.erb +85 -59
- data/lib/insika/studio/views/home.erb +138 -23
- data/lib/insika/studio/views/inbox.erb +36 -0
- data/lib/insika/studio/views/kits.erb +176 -0
- data/lib/insika/studio/views/knowledge.erb +92 -80
- data/lib/insika/studio/views/layout.erb +42 -11
- data/lib/insika/studio/views/live.erb +45 -0
- data/lib/insika/studio/views/mcp.erb +43 -13
- data/lib/insika/studio/views/models.erb +187 -0
- data/lib/insika/studio/views/parity.erb +38 -32
- data/lib/insika/studio/views/playground.erb +13 -9
- data/lib/insika/studio/views/refinement.erb +15 -224
- data/lib/insika/studio/views/refinement_detail.erb +243 -0
- data/lib/insika/studio/views/session.erb +152 -117
- data/lib/insika/studio/views/settings.erb +86 -22
- data/lib/insika/studio/views/skills.erb +53 -19
- data/lib/insika/studio/views/system_files.erb +7 -7
- data/lib/insika/studio/views/task.erb +45 -15
- data/lib/insika/studio/views/tasks.erb +35 -18
- data/lib/insika/studio/views/tools.erb +62 -73
- data/lib/insika/task_actor.rb +4 -1
- data/lib/insika/task_store.rb +67 -1
- data/lib/insika/telemetry/pricing.rb +16 -14
- data/lib/insika/telemetry/recorder.rb +103 -9
- data/lib/insika/telemetry/ruby_llm_instrumenter.rb +110 -0
- data/lib/insika/telemetry.rb +12 -2
- data/lib/insika/templates/daily-digest/README.md +13 -0
- data/lib/insika/templates/daily-digest/agent.rb +22 -11
- data/lib/insika/testing/store_contract.rb +65 -0
- data/lib/insika/text_tool_call.rb +66 -0
- data/lib/insika/tick.rb +7 -1
- data/lib/insika/token_store.rb +5 -0
- data/lib/insika/tool_batch.rb +1 -1
- data/lib/insika/tool_catalog.rb +12 -2
- data/lib/insika/tool_definition.rb +10 -4
- data/lib/insika/tool_envelope.rb +165 -28
- data/lib/insika/tool_manifest.rb +1 -0
- data/lib/insika/tool_store.rb +3 -0
- data/lib/insika/tool_trace_store.rb +15 -8
- data/lib/insika/tool_usage_report.rb +2 -2
- data/lib/insika/tools/a2a_remote.rb +1 -1
- data/lib/insika/tools/agent_enum.rb +1 -1
- data/lib/insika/tools/confirm_pending.rb +112 -0
- data/lib/insika/tools/data_defined_tool.rb +23 -15
- data/lib/insika/tools/generate_image.rb +2 -2
- data/lib/insika/tools/load_knowledge.rb +7 -3
- data/lib/insika/tools/load_skill.rb +3 -3
- data/lib/insika/tools/present.rb +1 -8
- data/lib/insika/tools/remember.rb +7 -2
- data/lib/insika/tools/run_persona_eval.rb +3 -5
- data/lib/insika/tools/save_artifact.rb +16 -6
- data/lib/insika/tools/schedule_followup.rb +3 -3
- data/lib/insika/tools/stuck_signal.rb +3 -3
- data/lib/insika/tools/subagent.rb +5 -5
- data/lib/insika/tools/subagents.rb +3 -3
- data/lib/insika/tools/tool_search.rb +7 -5
- data/lib/insika/tools/tts.rb +2 -2
- data/lib/insika/tools/update_briefing.rb +4 -4
- data/lib/insika/turn_output.rb +2 -2
- data/lib/insika/turn_state.rb +18 -0
- data/lib/insika/turn_timing.rb +51 -14
- data/lib/insika/version.rb +1 -1
- data/lib/insika/wiring/graph.rb +81 -16
- data/lib/insika/workflow.rb +25 -64
- data/lib/insika.rb +19 -3
- metadata +69 -13
- data/lib/insika/commands/import_mcp_tools.rb +0 -48
- data/lib/insika/mcp_http_client.rb +0 -67
- data/lib/insika/mcp_tool_ingestor.rb +0 -165
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b98a9d84df7cda373881cc11d0658bc3dd208ceb4bc52c23ed4fed4bd641fbb4
|
|
4
|
+
data.tar.gz: cecc0e4671a335efc5670f297aaf53ebafd5f1662f25a3121894825582494b30
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 332d1cea74cdbee9e3241dfefc73538824643fe3b65964fb2584ed57fb4d85c7b1cb547c7d2ccf3ff2acd4893d49063bed60fbf6e94ed6968e1c30bbd9671b30
|
|
7
|
+
data.tar.gz: d203a16b2954fb36829ed074a463ebac6dce26fea980e546debf5e908e4bf6bfb77c7d04b2e703816254022eaf3da9dbbce1a09c845525454dc6126996ce00b0
|
data/CHANGELOG.md
CHANGED
|
@@ -3,11 +3,246 @@
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
|
|
6
|
-
project
|
|
7
|
-
it is released. Entries land with the pull request that makes the change.
|
|
6
|
+
project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html) from 1.0.0 on. Entries land with the pull request that makes the change.
|
|
8
7
|
|
|
9
8
|
## [Unreleased]
|
|
10
9
|
|
|
10
|
+
## [1.0.1] - 2026-10-23
|
|
11
|
+
|
|
12
|
+
### Removed
|
|
13
|
+
|
|
14
|
+
- Internal planning notes (`docs/superpowers/`) are no longer in the repository or the
|
|
15
|
+
gem. 1.0.0 shipped them by mistake; the packaging now never ships that directory.
|
|
16
|
+
|
|
17
|
+
## [1.0.0] - 2026-10-23
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Harvest page: each pending or blocked candidate shows its skill text, rationale and origin conversation; a blocked one can be re-gated once the missing piece is fixed; the page says when the loop skips an agent (harvest off, or no grounding matcher).
|
|
22
|
+
- Studio screens for watching conversations and what waits on a person (see
|
|
23
|
+
[Studio](docs/STUDIO.md)):
|
|
24
|
+
- **Live** (`/studio/live`): one card per recent conversation, moved by the
|
|
25
|
+
event stream (tools in the running turn, "agent is writing…", guardrail
|
|
26
|
+
flags), with an event ticker. `?agent=` shows one agent's conversations.
|
|
27
|
+
- **Why this reply**: under each agent reply, its turn's context by category,
|
|
28
|
+
skills injected and why, tool calls with timing and guardrail flags, plus
|
|
29
|
+
Replay in Playground and Save as eval case.
|
|
30
|
+
- **Inbox** (`/studio/inbox`): approvals, fact proposals, mined skills,
|
|
31
|
+
refinement runs and guardrail false-positive reports in one list, oldest
|
|
32
|
+
first, with tabs per type.
|
|
33
|
+
- **Guardrail false positives**: a flag can be marked as a false positive,
|
|
34
|
+
then resolved or undone (`:review_guardrail_flag`). The verdict lives on the
|
|
35
|
+
flag inside the session.
|
|
36
|
+
- **Agent History**: outcomes per day over 30 days with a marker per change
|
|
37
|
+
to a prompt file or to the agent's own skills, and per change a short diff
|
|
38
|
+
and the outcomes 7 days after vs 7 days before.
|
|
39
|
+
- **Funnel drops**: the biggest loss between stages, and per loss the
|
|
40
|
+
conversations that stopped there, grouped by the last thing that happened.
|
|
41
|
+
- Chats tabs (All / Active now / Guardrail flagged), a customer card on the
|
|
42
|
+
session, a tool-call waterfall per turn, an Overview "Needs attention" row,
|
|
43
|
+
a `⌘K` palette, and a sidebar grouped as Operate / Review / Build /
|
|
44
|
+
Insights / Platform with a count badge on each Review page.
|
|
45
|
+
- Markup tags in message text (`<carrossel/>`, `<vitrine formato="lista">`)
|
|
46
|
+
show as highlighted tokens, escaped and never rendered.
|
|
47
|
+
- `Store#amend(scope, key, value)`: overwrites a key without moving it in
|
|
48
|
+
`#recent`. The default is `#set`; a backend whose `#recent` follows its own
|
|
49
|
+
write order overrides both (the contract suite checks it). Memory, SQLite and
|
|
50
|
+
Postgres implement it.
|
|
51
|
+
- Persisted turn messages and tool-call traces carry `task_id`, so the Studio
|
|
52
|
+
ties each message to its own turn.
|
|
53
|
+
|
|
54
|
+
- `scripts/migrate_openclaw.rb seed`: carries recent OpenClaw conversations into
|
|
55
|
+
Insika sessions (`POST /v1/conversations/:id/seed`) so a returning customer
|
|
56
|
+
keeps the context. Reads root and per-process SQLite transcripts, follows the
|
|
57
|
+
active branch from the last compaction, keeps only customer text and agent
|
|
58
|
+
replies, and skips conversations that already have messages. `--only`/`--as`
|
|
59
|
+
rehearse one conversation on a test chat.
|
|
60
|
+
Each session is stamped with `--agent`, so the Studio lists it under that
|
|
61
|
+
agent; re-running stamps sessions seeded before (they still answer 409).
|
|
62
|
+
- Platform agent defaults (Studio › Settings › Agent defaults, or
|
|
63
|
+
`update_settings` with `agent_defaults`): an agent with no value of its own
|
|
64
|
+
for reliability, knowledge, memory retrieval, budget, alerts, guardrails,
|
|
65
|
+
grounding, follow-ups, distillation or harvest runs with the platform's. The
|
|
66
|
+
agent's own value wins whole. Each of those sections in the agent config has
|
|
67
|
+
a "use the platform default" box; saving with it checked keeps the agent
|
|
68
|
+
inheriting. A default is validated on save, and one that stops building
|
|
69
|
+
later leaves agents on their own values instead of failing turns. Agents
|
|
70
|
+
saved in the Studio before this have their own knowledge, guardrails,
|
|
71
|
+
distillation and harvest values (the form always wrote them): check the box
|
|
72
|
+
to make them inherit.
|
|
73
|
+
|
|
74
|
+
- **Optional Postgres store** (`INSIKA_DATABASE_URL`, `INSIKA_DATABASE_POOL`). It passes the
|
|
75
|
+
same store contract as SQLite; `pg` is not a gem dependency and loads only when the
|
|
76
|
+
variable is set. `scripts/store_copy.rb` moves an existing store. See
|
|
77
|
+
[Postgres store](docs/DEPLOY.md#postgres-store-optional) for when it pays off.
|
|
78
|
+
- `Store#entries` and `Store#recent`: a whole scope, or its most recently written keys, in
|
|
79
|
+
one call. The default implementation keeps third-party backends working; SQLite and
|
|
80
|
+
Postgres answer each in one query. The Studio's session and task lists use them.
|
|
81
|
+
- `INSIKA_SLOW_STORE_MS` also logs each slow write batch: its size, bytes, scopes, and how
|
|
82
|
+
long it waited for the lock versus how long its commit took.
|
|
83
|
+
|
|
84
|
+
### Changed
|
|
85
|
+
- Session stats are versioned (`"v" => 2`, adds the guardrail flagged and
|
|
86
|
+
reported counts). `scripts/backfill_session_stats.rb` now also rewrites stats
|
|
87
|
+
older than the current version, not only missing ones.
|
|
88
|
+
- Reviewing a guardrail flag no longer moves the session to the top of the
|
|
89
|
+
recent lists.
|
|
90
|
+
- **MCP tools are named after their instance**: `<instance>__<tool>` (e.g.
|
|
91
|
+
`shop_staging__list_products`). Two instances offering the same tool used to share
|
|
92
|
+
one name, and every call went to whichever instance came first. Agents that allowed
|
|
93
|
+
MCP tools by name must select them again; per-tool overrides keep the server's name.
|
|
94
|
+
- `INSIKA_EGRESS_HOSTS` pins an MCP server only when its host resolves to a private
|
|
95
|
+
address. The operator registers the URL and the model cannot change it, so adding a
|
|
96
|
+
public server needs no redeploy. HTTPS still applies, and a private server still needs
|
|
97
|
+
`INSIKA_EGRESS_ALLOW_PRIVATE` and a listed host.
|
|
98
|
+
|
|
99
|
+
- An input guardrail injection hit no longer replaces the turn with the canned
|
|
100
|
+
refusal by default. The message reaches the model with a per-turn
|
|
101
|
+
`<security_notice>` (never persisted) and an audit `guardrail_flagged` with
|
|
102
|
+
`action: "flag"`, so a legitimate request in the same message is still served.
|
|
103
|
+
Sexual and abuse hits still block, now also when the message carries an
|
|
104
|
+
injection too. `guardrails.injection: "block"` (Studio › Guardrails › on
|
|
105
|
+
injection) restores the old behavior.
|
|
106
|
+
- `Store#recent` takes an optional fourth argument, `offset`, to read the next page; a
|
|
107
|
+
backend that implements the three-argument form keeps working (the stores pass
|
|
108
|
+
`offset` only when paging). SQLite reads a page's keys from the index and then only
|
|
109
|
+
their values, instead of reading every value in the range to sort it.
|
|
110
|
+
- Studio: the tasks list shows 50 tasks per page, newest first, instead of every task.
|
|
111
|
+
The home reads only the sessions of the last 14 days; its messages card counts that
|
|
112
|
+
window (and says so), and the conversations total comes from the keys alone.
|
|
113
|
+
- Every session write also writes a small stats record (last update, message count,
|
|
114
|
+
agent) in the `session_stats` scope, and the Studio home reads those instead of whole
|
|
115
|
+
sessions (every stat, filtered by date, so the order they were written in does not
|
|
116
|
+
matter). Sessions stored before this change have no stats until
|
|
117
|
+
`scripts/backfill_session_stats.rb` runs once (idempotent, safe with the app running;
|
|
118
|
+
it also removes stats whose session is gone).
|
|
119
|
+
- RubyLLM is pinned to a newer `2.0.0` commit: streamed responses are priced once when
|
|
120
|
+
read instead of on every chunk, and HTTP connections are shared per process and
|
|
121
|
+
settings. Specs stub LLM HTTP with `stub_llm_http`, because swapping the adapter on an
|
|
122
|
+
already-built connection no longer takes effect.
|
|
123
|
+
|
|
124
|
+
- Studio tools are grouped by origin and MCP server, with name/server filtering,
|
|
125
|
+
collapsible sections and per-section select/deselect actions. Selection keeps
|
|
126
|
+
denied tools blocked and requires saving before permissions change.
|
|
127
|
+
|
|
128
|
+
- Workflow and tool argument validation now use JSONSchemer. Explicit nulls and
|
|
129
|
+
declared constraints are enforced; existing tool scalar leniency remains.
|
|
130
|
+
- Distillation, harvest, knowledge and refinement use native structured responses.
|
|
131
|
+
Retired MCP import delegates to live discovery instead of generating HTTP snapshots.
|
|
132
|
+
|
|
133
|
+
- RubyLLM 2 migration: native loop control, tool schemas, cache markers, and usage
|
|
134
|
+
readers. MCP now uses the native RubyLLM client, replacing `ruby_llm-mcp` and the
|
|
135
|
+
Origin-header patch. Legacy SSE requires migration to Streamable HTTP. See
|
|
136
|
+
[migration results](docs/RUBYLLM_2_MIGRATION.md).
|
|
137
|
+
- Requires RubyLLM `~> 2.1` from RubyGems; the Git pin is gone. A provider's 400
|
|
138
|
+
quota message ("quota exceeded", "wait before trying again") now arrives as
|
|
139
|
+
`RubyLLM::RateLimitError` and is retried and rotated instead of failing the turn.
|
|
140
|
+
|
|
141
|
+
- SQLite writes from concurrent turns share one transaction and one commit (group commit),
|
|
142
|
+
so N worker processes take the file's write lock far less often. A turn opens and
|
|
143
|
+
closes its task record in one write each, and skips an empty evidence write.
|
|
144
|
+
- Litestream 0.5 with its blocking checkpoint disabled (`truncate-page-n: 0`); it restores
|
|
145
|
+
backups made by 0.3.
|
|
146
|
+
- Less CPU per turn: the prompt reads an agent's files once per build, the prompt-echo
|
|
147
|
+
check caches each squeezed prompt, and streamed events are matched only against their
|
|
148
|
+
own session's subscriptions.
|
|
149
|
+
|
|
150
|
+
### Removed
|
|
151
|
+
|
|
152
|
+
- **The `HARNESS_*` environment variables (breaking).** Only the `INSIKA_*` names are
|
|
153
|
+
read. A leftover `HARNESS_*` key is no longer honored: it is reported as an error
|
|
154
|
+
naming its `INSIKA_*` replacement (a warning at boot, fatal under
|
|
155
|
+
`INSIKA_CONFIG_STRICT`, and an error in `insika doctor`), so a stale `HARNESS_DB`
|
|
156
|
+
cannot silently boot the engine on the memory backend. `EnvSchema.reconcile_legacy!`
|
|
157
|
+
is gone, and the bundled `insika-code` plugin and example read `INSIKA_CODE_*`.
|
|
158
|
+
- **The `plugin.yml` manifest name (breaking).** Only `insika.plugin.yml` loads. A
|
|
159
|
+
leftover `plugin.yml` is not loaded and warns to rename it.
|
|
160
|
+
- `Insika::LLMHTTPAdapter` (`faraday_adapter = :insika_net_http`). RubyLLM 2.1 shares
|
|
161
|
+
one connection per process and settings, so the stock `:net_http` no longer builds a
|
|
162
|
+
CA store per chat. A deployment that set `:insika_net_http` drops that line.
|
|
163
|
+
|
|
164
|
+
### Fixed
|
|
165
|
+
|
|
166
|
+
- Skills written in one worker (pack import, Studio edit, `write_skill`) now reach the other workers within 2 s. The catalog only reloaded in the worker that handled the write, so the others kept the skills they booted with until a restart — in the Studio and in customer turns.
|
|
167
|
+
- Harvest no longer mines simulator/eval sessions (`sim-…`, `eval-…`), and the miner sees the candidates still open so it stops re-proposing the same idea under new names.
|
|
168
|
+
- Harvest: a bare `miner.model` ref (no `provider/`) now uses the agent's provider; it used to fail every run with `Provider must be specified`.
|
|
169
|
+
- Studio: `/studio` without a trailing slash answered 404; it now opens the
|
|
170
|
+
Overview.
|
|
171
|
+
- Studio: the agent History chart and arm conversions counted outcome kinds
|
|
172
|
+
against the primary stage name, so they read 0 whenever the kind and stage
|
|
173
|
+
names differ; they now map kinds through `advance_on`.
|
|
174
|
+
- Studio: a negative message or flag index on a guardrail review counted from
|
|
175
|
+
the end and reviewed the wrong flag; it is now not found.
|
|
176
|
+
- An error that is fatal only for one model or account no longer kills the turn:
|
|
177
|
+
no balance (402), a bad key or missing access (401/403) and a model the provider
|
|
178
|
+
does not have (404) move straight to the next fallback node — no retry on that
|
|
179
|
+
node, no backoff, and no circuit-breaker count (an open primary circuit fails
|
|
180
|
+
fast without rotating). On the last node it still raises; a 400 still never
|
|
181
|
+
rotates.
|
|
182
|
+
- Studio model fields are type-to-search pickers over the configured providers'
|
|
183
|
+
`provider/model` refs (their declared models, plus the chat models of key-only
|
|
184
|
+
providers): the agent model, a new agent, the playground pin and Settings'
|
|
185
|
+
default, fallback (one slot per position) and utility models. The server
|
|
186
|
+
refuses a ref outside the list — a typed `openai/gpt-x` under provider `openai`
|
|
187
|
+
used to reach the API as `openai/openai/gpt-x`. A stored value outside the list
|
|
188
|
+
is shown marked "not configured" and may be saved back unchanged. The plain
|
|
189
|
+
`model`/`provider` fields still pass as given for API clients.
|
|
190
|
+
The same pickers cover the reliability fallback chain (agent and Agent
|
|
191
|
+
defaults) and the refinement proposers, one slot per position, and the
|
|
192
|
+
knowledge, distill, harvest-miner and compaction models.
|
|
193
|
+
- Data-tools imported or edited in one worker now reach the others: the tool
|
|
194
|
+
registry checks the config version token (at most every 2 s) and rebuilds when
|
|
195
|
+
it moved, and the tool catalog follows. Before, the other workers kept the
|
|
196
|
+
catalog they booted with until a restart.
|
|
197
|
+
- LLM providers authored at runtime reach every worker process, and survive a
|
|
198
|
+
restart: each chat re-applies the stored providers when they changed. Before,
|
|
199
|
+
only the worker that served the edit had the key, so a fallback on that
|
|
200
|
+
provider failed as unconfigured. A cleared key stops being used; a key from
|
|
201
|
+
the environment is never cleared by it.
|
|
202
|
+
- Deleting an LLM provider authored at runtime puts back the key and base the
|
|
203
|
+
process started with (for example from the environment) instead of leaving
|
|
204
|
+
the provider without credentials; a worker that never applied the provider
|
|
205
|
+
leaves its config alone. Delete no longer disables a provider that also has a
|
|
206
|
+
key from the environment.
|
|
207
|
+
- Native chat usage includes tool rounds and retries, and failed turns expose
|
|
208
|
+
recorded usage. Responses projects cache-inclusive totals; negotiated pricing
|
|
209
|
+
no longer subtracts cache reads from already non-cached input.
|
|
210
|
+
- Reliability honors session model pins and model allowlists without multiplying
|
|
211
|
+
its attempt budget by RubyLLM transport retries.
|
|
212
|
+
- Speech response reads stop at the byte limit before buffering the entire body.
|
|
213
|
+
- Workflow schemas accept valid `false` values.
|
|
214
|
+
|
|
215
|
+
- **A merged fragment is framed by `steer_join`, like a steered one already was.** The
|
|
216
|
+
two burst doors disagreed on what the model sees: a steered message becomes its OWN
|
|
217
|
+
`user` message in the transcript (framed by `steer_join`), while a merged one was
|
|
218
|
+
concatenated into the queued turn's single payload by a bare `\n`. A burst therefore
|
|
219
|
+
reached the model as one run-on paragraph with no boundary between the asks, and a
|
|
220
|
+
trailing constraint could be answered by nothing (staging, Acme Beauty,
|
|
221
|
+
2026-09-18: five messages merged 4/4, and the answer never mentioned the fifth).
|
|
222
|
+
`steer_join` now applies to both doors; the default `nil` returns the text untouched,
|
|
223
|
+
so a wiring that never set it is unchanged.
|
|
224
|
+
|
|
225
|
+
- **The door is open for as long as a turn is queued, not only during a debounce
|
|
226
|
+
window.** `collect`/`steer` merged a fragment only while a turn sat in a
|
|
227
|
+
`debounce_ms` window; a message that arrived while a turn waited behind the one in
|
|
228
|
+
flight joined nothing and became a turn of its own, third in line. A
|
|
229
|
+
`POST /v1/messages?stream=false` caller then held its connection through TWO turns
|
|
230
|
+
and timed out — on the WhatsApp integration that surfaced as a technical apology
|
|
231
|
+
to the customer, once per excess message of a burst. A queued turn is now mergeable
|
|
232
|
+
with or without a window, so every burst message answers immediately with `merged`
|
|
233
|
+
or `steered`. `followup` (the default) is untouched.
|
|
234
|
+
- **Re-importing a pack no longer erases the knobs it did not send.** `limits` and
|
|
235
|
+
`params` now MERGE per key on an update (`PackImporter::KNOB_BAGS`); everything
|
|
236
|
+
else keeps replacing. A provisioning client that publishes
|
|
237
|
+
`limits: {turn_timeout:, context_budget:}` used to drop every other knob on the
|
|
238
|
+
agent — `queue_mode`, `debounce_ms`, `chat_rate_limit` — so an unrelated playbook
|
|
239
|
+
publish silently reset the queue policy an operator had set in the Studio. The
|
|
240
|
+
authoritative allowlists (`prompt_files`/`skills`/`tools_allow`) are unchanged: a
|
|
241
|
+
name that left the pack still leaves the agent.
|
|
242
|
+
- One door per queued turn: with two turns waiting, the second used to take over the
|
|
243
|
+
first's fragment count, and the first then ran from a message snapshot the store had
|
|
244
|
+
already grown past.
|
|
245
|
+
|
|
11
246
|
## [0.9.0] - 2026-09-07
|
|
12
247
|
|
|
13
248
|
The commerce-agent wave: the engine now enforces what the prompt used to ask for —
|
data/README.md
CHANGED
|
@@ -16,7 +16,7 @@ web control UI. Point an existing Responses client at it and serve many agents f
|
|
|
16
16
|
deployment.
|
|
17
17
|
|
|
18
18
|
- **Drop-in `/v1/responses`** — SSE streaming and usage, the shape existing clients already speak.
|
|
19
|
-
- **Durable & resumable** — every turn checkpoints;
|
|
19
|
+
- **Durable & resumable** — every turn checkpoints; recorded side-effect calls are deduplicated on replay. External writes still need idempotency.
|
|
20
20
|
- **Agents as data** — agents, prompts, skills and tools are editable at runtime (UI or API), no redeploy.
|
|
21
21
|
- **Tools & skills** — code, HTTP data and live MCP tools; skills load on demand.
|
|
22
22
|
- **Evidence-backed actions** — ID checks before writes, serial side effects per session, and selected evidence cards ([Tools](docs/TOOLS.md)).
|
|
@@ -139,6 +139,7 @@ navigates by.
|
|
|
139
139
|
- [Load test](docs/LOADTEST.md) — load-testing and data topology.
|
|
140
140
|
- [Soak](docs/SOAK.md) — the long run that finds what a short one cannot.
|
|
141
141
|
- [Benchmark](docs/BENCHMARK.md) — the neutral, reproducible, provider-free engine benchmark.
|
|
142
|
+
- [Performance](docs/PERFORMANCE.md) — what keeps a turn cheap (fibers, SQLite group commit, memory trim) and what was tried and left out.
|
|
142
143
|
|
|
143
144
|
**Improve**
|
|
144
145
|
|
|
@@ -178,6 +179,7 @@ and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Vulnerabilities go through
|
|
|
178
179
|
|
|
179
180
|
## Status
|
|
180
181
|
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
Published on [RubyGems](https://rubygems.org/gems/insika) and follows
|
|
183
|
+
[Semantic Versioning](https://semver.org/) from 1.0.0: a breaking change only lands in
|
|
184
|
+
a new major version ([CHANGELOG.md](CHANGELOG.md)). Licensed
|
|
183
185
|
**MIT** ([LICENSE](LICENSE)).
|
data/bin/insika
CHANGED
|
@@ -505,6 +505,9 @@ def local_side_effect_tools(agent)
|
|
|
505
505
|
profile = Insika::StoredProfileSource.new(config_store: config_store).fetch(agent)
|
|
506
506
|
return nil if profile.nil?
|
|
507
507
|
|
|
508
|
+
# Kits add reachable tools at turn time; the swap list must count them too.
|
|
509
|
+
profile = Insika::Kits.apply(profile, Insika::SettingsStore.new(config_store: config_store).kits)
|
|
510
|
+
|
|
508
511
|
registry = Insika::OverlayToolRegistry.new(
|
|
509
512
|
base: Insika::ToolRegistry.new, tool_store: Insika::ToolStore.new(config_store: config_store),
|
|
510
513
|
http: Insika::HttpClient.new
|
data/bin/insika-router
CHANGED
data/docs/AGENTS.md
CHANGED
|
@@ -132,7 +132,9 @@ block after the agent's identity in the system prompt: a weak or empty tool
|
|
|
132
132
|
result means *try again with a different approach — a rephrased query, a
|
|
133
133
|
synonym, a broader term — before telling the user you found nothing* (and don't
|
|
134
134
|
narrate the retries); a tool error means *read it and fix the arguments*, never
|
|
135
|
-
repeat the exact same call
|
|
135
|
+
repeat the exact same call; and an action is *reported as done only after its
|
|
136
|
+
tool call returned success* — never before the call, never over a failed or
|
|
137
|
+
blocked one. Without it, a search that returns 0 results reads as
|
|
136
138
|
final and the model answers "I couldn't find it" when a synonym one call away
|
|
137
139
|
would have.
|
|
138
140
|
|
|
@@ -251,7 +253,7 @@ Which mode you want depends on **when** the message arrives:
|
|
|
251
253
|
| `queue_mode` | The message arrives… | What happens |
|
|
252
254
|
|---|---|---|
|
|
253
255
|
| `followup` (default) | any time | it waits its turn in the queue — today's behavior, named |
|
|
254
|
-
| `collect` | before the turn starts | the fragments merge into ONE turn |
|
|
256
|
+
| `collect` | before the turn starts — including while it waits behind another turn | the fragments merge into ONE turn |
|
|
255
257
|
| `steer` | while the turn is running tools | it is appended to the run in flight |
|
|
256
258
|
| `interrupt` | while a turn is running that is now **wrong** | that turn is abandoned; this message becomes its own turn |
|
|
257
259
|
|
|
@@ -270,6 +272,13 @@ limit :debounce_max_ms, 10_000 # ceiling on the total wait, so typing forever
|
|
|
270
272
|
With those settings the three fragments above become one turn carrying
|
|
271
273
|
`"oi\nqueria saber do pedido\n1234567"`, released 2 s after the last one.
|
|
272
274
|
|
|
275
|
+
**The window is not what opens the door.** A turn is "not started" from the moment
|
|
276
|
+
it is created until it runs, and that includes the whole time it waits behind the
|
|
277
|
+
turn in flight — so with `debounce_ms` at 0 a fragment still merges into a queued
|
|
278
|
+
turn. What the window buys is the case where nothing is running: it holds the turn
|
|
279
|
+
at the front of the queue so fragments have something to land on. Without it a
|
|
280
|
+
burst on an idle session becomes one turn per message, each answered in order.
|
|
281
|
+
|
|
273
282
|
All three follow the platform-layer rule above, with one extra rung on top:
|
|
274
283
|
**session vars → this agent's limits → the platform default (Studio, `queue.*`) →
|
|
275
284
|
off**. Pinning `queue_mode` in a session's vars is how an operator takes one
|
|
@@ -304,11 +313,13 @@ separately lives in one event, emitted when the window closes:
|
|
|
304
313
|
Times and counts, never content. That is what answers "the customer says they
|
|
305
314
|
sent the order number" without keeping a throwaway task per fragment.
|
|
306
315
|
|
|
307
|
-
> **`steer` also collects at the door.** The two
|
|
308
|
-
>
|
|
309
|
-
>
|
|
310
|
-
>
|
|
311
|
-
>
|
|
316
|
+
> **`steer` also collects at the door.** The two are the same policy's halves, not
|
|
317
|
+
> two modes: a `steer` agent merges the fragments that land before the turn starts
|
|
318
|
+
> and appends the ones that land after it — `debounce_ms` only widens the first
|
|
319
|
+
> half. That pairing is what makes a burst answerable at all: whatever the customer
|
|
320
|
+
> sends either joins the run in flight (`steered`) or the turn behind it (`merged`),
|
|
321
|
+
> and the caller hears which one immediately instead of holding a connection open
|
|
322
|
+
> until someone else's turn ends.
|
|
312
323
|
|
|
313
324
|
#### `steer` — the message arrives while the turn is already running
|
|
314
325
|
|
|
@@ -632,6 +643,24 @@ a running agent.
|
|
|
632
643
|
|
|
633
644
|
Photos, voice notes and documents travel through the message contract as additive content parts, and the engine can hand generated images back the same way. See [Media](MEDIA.md).
|
|
634
645
|
|
|
646
|
+
## Caller context next to the message
|
|
647
|
+
|
|
648
|
+
A caller that wraps each message with its own text (channel rules, the products
|
|
649
|
+
the customer was just shown) sends that text in `context`, not in `message`:
|
|
650
|
+
|
|
651
|
+
```bash
|
|
652
|
+
curl -X POST /v1/messages?stream=false -H "Authorization: Bearer $TOKEN" \
|
|
653
|
+
-d '{ "agent": "store-support", "session_id": "chat-7",
|
|
654
|
+
"context": "<channel>sms</channel>", "message": "show me more" }'
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
The model is asked `context`, a blank line, then `message`, and the transcript
|
|
658
|
+
keeps the same text. The input guardrail, retrieval and routing read `message`
|
|
659
|
+
alone, so a product name in `context` never blocks the customer, and the
|
|
660
|
+
customer cannot slip past the guardrail by typing a caller's tags. Optional
|
|
661
|
+
String; absent = the message alone. `POST /v1/responses` takes the same `context`
|
|
662
|
+
next to `input`.
|
|
663
|
+
|
|
635
664
|
## Customer-scoped memory and the right to be forgotten
|
|
636
665
|
|
|
637
666
|
Memory is naturally **per customer, not per tenant**. A message that carries a
|
|
@@ -648,6 +677,10 @@ curl -X POST /v1/messages?stream=false -H "Authorization: Bearer $TOKEN" \
|
|
|
648
677
|
tenant never see each other, and the `<request_context>` tenant label (the
|
|
649
678
|
merchant) is untouched. Absent `customer` = today's per-tenant/per-chat
|
|
650
679
|
behavior.
|
|
680
|
+
- **Display labels** — optional `customer_name` and `customer_phone` (Strings)
|
|
681
|
+
label the conversation in the Studio (the name replaces the `customer` key;
|
|
682
|
+
the phone shows beside the agent) and the Chats search matches both. They
|
|
683
|
+
follow the latest value sent and have no effect on memory scope.
|
|
651
684
|
- **Right to be forgotten** — `POST /v1/commands/forget_customer` (operator)
|
|
652
685
|
purges the customer's memory cell, their sessions and everything those
|
|
653
686
|
sessions left behind — per-session traces, the tasks (the message text lives
|
|
@@ -733,11 +766,61 @@ Does each ID come from the server? Declare `evidence` on the lookup tool and
|
|
|
733
766
|
Mark writes as `side_effect` so parallel batches serialize them and recovery skips
|
|
734
767
|
completed calls. See [Tools](TOOLS.md#provenance-checking-ids-before-a-write).
|
|
735
768
|
|
|
769
|
+
## Do not offer the irreversible action
|
|
770
|
+
|
|
771
|
+
`evidence` and `requires_evidence` stop a write built on an id the customer was never
|
|
772
|
+
shown. They do not stop a write the model decides the customer asked for. That is a
|
|
773
|
+
prompt problem, and the [cross-harness bench](CROSS-HARNESS-BENCH.md) measured how
|
|
774
|
+
large it is.
|
|
775
|
+
|
|
776
|
+
One task: the customer has two units in the cart and says *"adiciona logo por favor"* —
|
|
777
|
+
ambiguous, and the correct reading is a request to add something, not to check out.
|
|
778
|
+
Ten rounds per harness, on the same model:
|
|
779
|
+
|
|
780
|
+
| Harness | Passed | Mentioned closing the order | Called `create_order` |
|
|
781
|
+
| --- | --- | --- | --- |
|
|
782
|
+
| Hermes | 10/10 | 0/10 | 0 |
|
|
783
|
+
| Insika, reasoning off | 9/10 | 10/10 | 1 |
|
|
784
|
+
| Insika, reasoning medium | 7/10 | 10/10 | 3 |
|
|
785
|
+
| Pi | 5/10 | 9/10 | 5 |
|
|
786
|
+
|
|
787
|
+
Every harness that offered to close the order sometimes closed it instead, at rates
|
|
788
|
+
from 10% to 50%. The one that never offered never closed it. It read the ambiguous
|
|
789
|
+
line as a new request and asked *which product* — so checkout was never in the
|
|
790
|
+
sentence the model was continuing.
|
|
791
|
+
|
|
792
|
+
**The rule for a prompt:** when the customer's intent is ambiguous, ask about the
|
|
793
|
+
thing they named, and do not volunteer the irreversible next step in the same breath.
|
|
794
|
+
An action the reply never proposes is one the model cannot slip into performing. This
|
|
795
|
+
is a writing rule, not an engine setting; nothing in the engine needs to change for
|
|
796
|
+
it, and no `halt_when` or allowlist expresses it, because the tool call is legitimate
|
|
797
|
+
in every other turn of the same conversation.
|
|
798
|
+
|
|
799
|
+
**Its cost, before you copy it.** Asking *"which product?"* about an item already in
|
|
800
|
+
the cart ignores context the customer just gave, and reads as an agent that was not
|
|
801
|
+
listening. The bench rewards it because the bench grades the store's state. A shop
|
|
802
|
+
might reasonably prefer the warmer reply and the 10% slip, or split the difference:
|
|
803
|
+
confirm what is in the cart, and stop there — let the customer be the one who says
|
|
804
|
+
close it. What you should not do is confirm the cart and offer checkout in the same
|
|
805
|
+
message, which is the shape that produced every failure above.
|
|
806
|
+
|
|
807
|
+
Reasoning level does not fix this. The same three tasks fail at `medium` and at `off`;
|
|
808
|
+
across ten rounds the two settings contradict each other task by task.
|
|
809
|
+
|
|
810
|
+
**The engine's alternative.** If the shop wants the warmer reply *and* zero slips,
|
|
811
|
+
take the write out of the same turn as the offer: declare the tool under
|
|
812
|
+
[`customer_confirm`](TOOLS.md#customer-confirmation-a-write-the-conversation-approves).
|
|
813
|
+
The model may then propose closing the order as freely as it likes — the engine holds
|
|
814
|
+
the call, the reply asks, and only the customer's next message runs it. The cost moves
|
|
815
|
+
from the wording to the conversation: one extra turn on every legitimate close.
|
|
816
|
+
|
|
736
817
|
## See also
|
|
737
818
|
|
|
738
819
|
- [Tools](TOOLS.md) — define, register, and troubleshoot tools.
|
|
739
820
|
- [Artifacts](ARTIFACTS.md) — the report destination: the tool, the routes, the signed link.
|
|
740
821
|
- [Skills](SKILLS.md) — progressive playbooks an agent loads on demand.
|
|
822
|
+
- [Cross-harness bench](CROSS-HARNESS-BENCH.md) — the same store, six harnesses, and
|
|
823
|
+
where the prompt rule above came from.
|
|
741
824
|
- [Context](CONTEXT.md) — what fills a turn's prompt, and memory.
|
|
742
825
|
- [Security](SECURITY.md) — guardrails, sandbox, approvals, edge limits.
|
|
743
826
|
- [Architecture](ARCHITECTURE.md) — how a turn actually runs.
|
data/docs/ARCHITECTURE.md
CHANGED
|
@@ -167,9 +167,11 @@ The order is not arbitrary:
|
|
|
167
167
|
- **The initial checkpoint is written before the model call.** "The checkpoint of
|
|
168
168
|
turn *n* holds the state at the *start* of turn *n*." Without it, a crash during
|
|
169
169
|
the model call would orphan the task with no checkpoint — unrecoverable.
|
|
170
|
-
- **Middleware wraps the
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
- **Middleware wraps the chat stages.** For opt-in reranking, the leading edge
|
|
171
|
+
limiter also wraps context preparation: admission runs once before paid retrieval.
|
|
172
|
+
Context still precedes policy; the input guardrail and plugin middleware receive
|
|
173
|
+
the prepared context, including any budget warning, before chat runs. Turns with
|
|
174
|
+
lexical-only retrieval keep the original order.
|
|
173
175
|
- **Persistence is a fixed order** (checkpoint → session → task) and a pure drain
|
|
174
176
|
point: the last stage never suspends, so a checkpoint is never left half-written.
|
|
175
177
|
- **Output validation** runs as an after-task hook on the produced content
|
|
@@ -210,7 +212,7 @@ flowchart TD
|
|
|
210
212
|
```
|
|
211
213
|
|
|
212
214
|
HTTP data tools and presentation tools are both stored definitions. Presentation
|
|
213
|
-
runs in-process; MCP remains a live server call (HTTP
|
|
215
|
+
runs in-process; MCP remains a live server call (Streamable HTTP or stdio). HTTP egress
|
|
214
216
|
is checked before a request. Tool errors reach the model as error results so it
|
|
215
217
|
can recover; a provenance refusal reports `blocked` without contacting the backend
|
|
216
218
|
or asking an operator. Completed side effects are recorded for resume.
|
|
@@ -256,9 +258,12 @@ flowchart LR
|
|
|
256
258
|
completed --> done((( )))
|
|
257
259
|
```
|
|
258
260
|
|
|
259
|
-
Resume always replays from the *start of the last checkpointed turn*.
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
Resume always replays from the *start of the last checkpointed turn*. Side-effect
|
|
262
|
+
calls whose completion was recorded in the checkpoint are skipped when replayed
|
|
263
|
+
with the same call id. An external effect can succeed before that record is saved;
|
|
264
|
+
a crash or failed checkpoint write in that window can repeat the effect on resume.
|
|
265
|
+
Use destination-supported idempotency keys or reconciliation for those operations:
|
|
266
|
+
checkpointing alone does not provide exactly-once external execution. A resumed
|
|
262
267
|
turn is also never re-counted against edge-limit ledgers. Cancellation is
|
|
263
268
|
cooperative — checked at stage boundaries, never in the middle of a store write.
|
|
264
269
|
|
data/docs/ARTIFACTS.md
CHANGED
|
@@ -63,9 +63,39 @@ Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src
|
|
|
63
63
|
X-Content-Type-Options: nosniff
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
No script, no external fetch, no forms. The model writes HTML
|
|
67
|
-
SVG**
|
|
68
|
-
|
|
66
|
+
No script, no external fetch, no forms. The model writes self-contained HTML/CSS
|
|
67
|
+
with **inline SVG** or HTML bars for charts. Native radio controls and
|
|
68
|
+
`details`/`summary` still work inside this boundary.
|
|
69
|
+
|
|
70
|
+
## Interactive executive reports
|
|
71
|
+
|
|
72
|
+
The tool description and `daily-digest` skill guide future HTML reports. Existing
|
|
73
|
+
artifacts keep their saved content. The
|
|
74
|
+
[example report](https://github.com/guizaols/insika/blob/main/examples/scheduled-report/interactive-report.html)
|
|
75
|
+
uses explicitly fictional data and works without JavaScript or dependencies.
|
|
76
|
+
|
|
77
|
+
- Lead with the title, reporting period, generation date and a concise takeaway
|
|
78
|
+
supported by the numbers. Include sources and limitations; mark missing data
|
|
79
|
+
and fictional examples honestly.
|
|
80
|
+
- Use warm neutral surfaces and one amber or merchant accent. Keep typography,
|
|
81
|
+
spacing and KPI cards consistent; support system dark mode and narrow screens.
|
|
82
|
+
- Label charts with units and exact values. Use responsive SVG or HTML bars and a
|
|
83
|
+
captioned table with column headers, so touch users can read every value.
|
|
84
|
+
- Switch Chart/Table with labeled, focusable native radios in a `fieldset` with
|
|
85
|
+
a `legend`, styled through CSS `:checked`. These are radios, not ARIA tabs.
|
|
86
|
+
- Use CSS hover/focus tooltips in reserved space, highlights, a visible 2px focus
|
|
87
|
+
outline, and `details`/`summary` for supporting evidence. Keep tooltips visible
|
|
88
|
+
when the pointer moves onto them. Do not make hover the only way to
|
|
89
|
+
read data or use `<title>` as the only tooltip.
|
|
90
|
+
- Limit color/opacity transitions to 140ms and disable them under
|
|
91
|
+
`prefers-reduced-motion: reduce`. Avoid decorative animation. Print styles
|
|
92
|
+
include the complete table and details regardless of selected/collapsed state,
|
|
93
|
+
hide controls and keep text legible; use the browser's print command.
|
|
94
|
+
|
|
95
|
+
Interactivity explores a **dated snapshot**. It does not fetch new data, recompute
|
|
96
|
+
the source or imply a live feed. A later report run saves a new artifact. Do not
|
|
97
|
+
add scripts, event handlers (including `onclick` print buttons), external resources
|
|
98
|
+
or form submissions to generated reports.
|
|
69
99
|
|
|
70
100
|
## Reasoning effort on a report turn
|
|
71
101
|
|
data/docs/BENCHMARK.md
CHANGED
|
@@ -45,6 +45,7 @@ reports only the engine's contribution:
|
|
|
45
45
|
| **gen** (p50/p95) | streaming the rest through the pipeline (filter/emit/event stream) |
|
|
46
46
|
| **throughput** | turns/s a single process sustains at a given concurrency |
|
|
47
47
|
| **pipeline overhead** | engine work per streamed token (µs) |
|
|
48
|
+
| **allocations** | Ruby objects allocated per measured turn, including benchmark driver bookkeeping, excluding warmup |
|
|
48
49
|
|
|
49
50
|
**Out of scope, on purpose:** end-to-end latency, time-to-first-token *against a
|
|
50
51
|
provider*, and tokens/s of *model generation*. Those are provider-bound — the
|
|
@@ -52,8 +53,9 @@ Insika has no lever on them — so this suite makes no claim about them.
|
|
|
52
53
|
|
|
53
54
|
The stub implements exactly the chat surface the executor touches, and each turn
|
|
54
55
|
runs the full engine path: context build, policy resolution, guardrail
|
|
55
|
-
detectors, chat assembly,
|
|
56
|
-
checkpointing, and the event stream.
|
|
56
|
+
detectors, chat assembly, tool-call callbacks, streamed output, persistence,
|
|
57
|
+
checkpointing, and the event stream. The default mode replaces RubyLLM's Chat;
|
|
58
|
+
use `--ruby-llm` below to include the real Chat and tool execution.
|
|
57
59
|
|
|
58
60
|
## Scenarios
|
|
59
61
|
|
|
@@ -62,8 +64,8 @@ imported the same way any pack is, so the measured path is the real one.
|
|
|
62
64
|
|
|
63
65
|
- **greeting** — a minimal turn: a short system prompt, no tools. Baseline
|
|
64
66
|
engine overhead.
|
|
65
|
-
- **tool_call** — a turn
|
|
66
|
-
|
|
67
|
+
- **tool_call** — a turn simulating one tool call; exercises tool assembly and
|
|
68
|
+
call/result callbacks without executing the tool.
|
|
67
69
|
- **multi_turn** — a one-shot carrying prior conversation messages; shows how
|
|
68
70
|
overhead moves as the context the engine assembles grows.
|
|
69
71
|
|
|
@@ -80,12 +82,46 @@ imported the same way any pack is, so the measured path is the real one.
|
|
|
80
82
|
| `--history-turns N` | `10` | prior messages for the `multi_turn` scenario |
|
|
81
83
|
| `--output-tokens N` | `48` | tokens the stub streams per turn |
|
|
82
84
|
| `--json` | off | emit results as JSON (for regression gating) |
|
|
85
|
+
| `--ruby-llm` | off | run the real RubyLLM Chat with an offline provider |
|
|
83
86
|
|
|
84
87
|
`--json` prints the engine version, Ruby/YJIT status, the full config, and every
|
|
85
88
|
metric — a stable shape to diff across commits.
|
|
86
89
|
|
|
90
|
+
## Offline RubyLLM benchmark
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bundle exec ruby scripts/bench.rb --ruby-llm --json
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This opt-in mode measures the engine **plus RubyLLM's real Chat and tool loop**.
|
|
97
|
+
Only the provider is synthetic: it yields deterministic chunks without HTTP,
|
|
98
|
+
credentials, or a model service. The normal executor creates the Chat, assembles
|
|
99
|
+
its history and tools, and registers the production callbacks. The `tool_call`
|
|
100
|
+
scenario executes two sequential lookup rounds; `greeting` streams an answer and
|
|
101
|
+
`multi_turn` seeds the requested history before streaming.
|
|
102
|
+
|
|
103
|
+
JSON includes the RubyLLM version, `mode`, and per-scenario `ruby_llm_activity`
|
|
104
|
+
counts for provider calls, executed tools, history messages, and streamed chunks.
|
|
105
|
+
Activity counts include warmup; allocation measurements exclude warmup and cover
|
|
106
|
+
both measured passes. Compare matching modes and settings across versions; the
|
|
107
|
+
default stub mode keeps its existing one-tool scenario. Neither mode measures
|
|
108
|
+
HTTP serialization, network latency, or actual model generation.
|
|
109
|
+
|
|
110
|
+
The offline provider supplies request `model_info`, as native RubyLLM 2 usage
|
|
111
|
+
tracking does. Omitting it adds spurious catalog lookups during cost accounting.
|
|
112
|
+
For an isolated, self-checking reproduction without Insika, run
|
|
113
|
+
`ruby scripts/rubyllm_profile.rb 2.0.0 --profile` and repeat with
|
|
114
|
+
`--request-context`. Use `1.16.0` for the old-gem comparison, outside Bundler.
|
|
115
|
+
Method timings are inclusive and must not be summed; use runs without `--profile`
|
|
116
|
+
for latency comparisons. This diagnostic still omits native HTTP processing.
|
|
117
|
+
|
|
87
118
|
## Reference numbers
|
|
88
119
|
|
|
120
|
+
For the five-run main versus RubyLLM 2 migration comparison repeated on
|
|
121
|
+
2026-09-24, see [current candidate results](RUBYLLM_2_MIGRATION.md#current-candidate-2026-09-24).
|
|
122
|
+
It uses this harness, plus the API-equivalent baseline harness, without YJIT.
|
|
123
|
+
The older YJIT reference below is not a migration acceptance result.
|
|
124
|
+
|
|
89
125
|
A reference run. **The absolute milliseconds are machine-specific** — reproduce
|
|
90
126
|
them on your own hardware with the command below; what travels across machines is
|
|
91
127
|
the shape (sub-millisecond overhead, flat p95, thousands of turns/s per process).
|
|
@@ -109,6 +145,13 @@ work. The rest of any real turn's latency is the provider.
|
|
|
109
145
|
## Publication rule
|
|
110
146
|
|
|
111
147
|
**Any public claim about the engine's performance must reference this suite.**
|
|
112
|
-
|
|
113
|
-
publishable — they are neither neutral nor reproducible. If a
|
|
114
|
-
reproduced by running `scripts/bench.rb`, it does not go in public
|
|
148
|
+
Performance numbers produced against a specific provider, deployment, or competitor
|
|
149
|
+
are not publishable — they are neither neutral nor reproducible. If a performance
|
|
150
|
+
claim cannot be reproduced by running `scripts/bench.rb`, it does not go in public
|
|
151
|
+
materials.
|
|
152
|
+
|
|
153
|
+
The rule is about **performance**, which is what this suite measures. Claims about
|
|
154
|
+
what an agent *gets right* live in a different suite with a different contract: the
|
|
155
|
+
[cross-harness bench](CROSS-HARNESS-BENCH.md) needs a provider key, because a
|
|
156
|
+
commerce conversation cannot be stubbed, and it names other harnesses. It carries its
|
|
157
|
+
own publication rule, which is stricter about the rows it did not write.
|