insika 0.8.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 +312 -2
- data/README.md +10 -6
- data/bin/insika +4 -1
- data/bin/insika-router +0 -1
- data/docs/AGENTS.md +142 -18
- data/docs/API.md +73 -0
- data/docs/ARCHITECTURE.md +56 -50
- data/docs/ARTIFACTS.md +33 -3
- data/docs/BENCHMARK.md +50 -7
- data/docs/CHANNELS.md +19 -2
- data/docs/CONTEXT.md +96 -27
- data/docs/CROSS-HARNESS-BENCH.md +157 -0
- data/docs/DEPLOY.md +70 -13
- data/docs/EMBEDDING.md +10 -0
- data/docs/EVALS.md +129 -10
- data/docs/FACTS.md +12 -6
- data/docs/HARVEST.md +8 -3
- data/docs/KNOWLEDGE.md +14 -0
- data/docs/MEDIA.md +6 -2
- data/docs/OBSERVABILITY.md +164 -28
- data/docs/PERFORMANCE.md +110 -0
- data/docs/PLUGINS.md +4 -4
- data/docs/POLICY.md +26 -14
- data/docs/RELEASING.md +9 -0
- data/docs/ROUTER.md +7 -0
- data/docs/RUBYLLM_2_MIGRATION.md +307 -0
- data/docs/SECURITY.md +34 -1
- data/docs/SHARED-CONVERSATIONS.md +40 -0
- data/docs/SKILLS.md +5 -3
- data/docs/STUDIO.md +122 -0
- data/docs/TOOLS.md +238 -42
- data/docs/operate.md +3 -0
- data/docs/prompts/ADD-TOOL.md +12 -2
- data/docs/prompts/DIAGNOSE-TURN.md +3 -0
- data/docs/prompts/GO-LIVE.md +3 -1
- data/lib/insika/agent_defaults.rb +140 -0
- data/lib/insika/agent_file_store.rb +12 -0
- data/lib/insika/agent_profile.rb +99 -17
- data/lib/insika/artifact_signing.rb +11 -0
- data/lib/insika/channels/web/widget.js +33 -0
- data/lib/insika/channels/web.rb +5 -2
- data/lib/insika/chat_builder.rb +124 -28
- 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_distillation.rb +5 -8
- data/lib/insika/commands/run_harvest.rb +17 -1
- data/lib/insika/commands/seed_session.rb +128 -0
- 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/builder.rb +29 -9
- data/lib/insika/context/priority.rb +5 -0
- data/lib/insika/context/provider.rb +7 -2
- data/lib/insika/context/providers/briefing.rb +11 -8
- data/lib/insika/context/providers/fence_notice.rb +27 -0
- data/lib/insika/context/providers/knowledge.rb +32 -6
- data/lib/insika/context/providers/memory.rb +61 -8
- 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 +46 -6
- data/lib/insika/context/reranker.rb +75 -0
- data/lib/insika/distill.rb +22 -13
- data/lib/insika/doctor.rb +142 -4
- data/lib/insika/dsl/runtime.rb +37 -4
- data/lib/insika/dsl/server_boot.rb +4 -12
- data/lib/insika/dsl.rb +19 -5
- data/lib/insika/edge_limiter.rb +27 -16
- data/lib/insika/egress_guard.rb +11 -3
- data/lib/insika/env_schema.rb +33 -73
- data/lib/insika/errors.rb +1 -0
- data/lib/insika/evals/assertions.rb +259 -18
- data/lib/insika/evals/golden.rb +230 -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 +64 -4
- data/lib/insika/evals/simulator.rb +21 -3
- data/lib/insika/evals/transport.rb +317 -15
- data/lib/insika/event_stream.rb +63 -7
- data/lib/insika/evidence.rb +105 -17
- data/lib/insika/executor.rb +698 -164
- data/lib/insika/fence.rb +96 -0
- data/lib/insika/golden_store.rb +3 -0
- 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 +37 -3
- data/lib/insika/mcp_tool_registry.rb +76 -26
- data/lib/insika/media.rb +38 -25
- data/lib/insika/memory_store.rb +16 -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 +30 -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/prefix_fingerprint.rb +32 -27
- data/lib/insika/profile_source.rb +5 -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 +58 -7
- data/lib/insika/server/rack_app.rb +2 -0
- data/lib/insika/server/responses.rb +50 -5
- data/lib/insika/session_actor.rb +58 -28
- data/lib/insika/session_store.rb +163 -11
- data/lib/insika/settings_store.rb +57 -2
- 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/spoken_transcript.rb +31 -0
- 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 +1459 -163
- 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 +189 -12
- 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 +83 -243
- 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} +38 -11
- 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 +153 -118
- 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 +115 -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_assembly.rb +21 -13
- data/lib/insika/tool_batch.rb +1 -1
- data/lib/insika/tool_catalog.rb +12 -2
- data/lib/insika/tool_definition.rb +82 -13
- data/lib/insika/tool_envelope.rb +266 -29
- data/lib/insika/tool_manifest.rb +1 -0
- data/lib/insika/tool_store.rb +12 -4
- data/lib/insika/tool_trace_store.rb +15 -8
- data/lib/insika/tool_usage_report.rb +14 -4
- 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 +24 -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 +115 -0
- data/lib/insika/tools/remember.rb +7 -2
- data/lib/insika/tools/run_persona_eval.rb +9 -6
- 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 +11 -7
- 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 +31 -1
- data/lib/insika/turn_timing.rb +51 -14
- data/lib/insika/version.rb +1 -1
- data/lib/insika/wiring/graph.rb +88 -16
- data/lib/insika/wiring/graph_chat.rb +4 -0
- data/lib/insika/workflow.rb +25 -64
- data/lib/insika.rb +23 -3
- metadata +74 -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
data/docs/SKILLS.md
CHANGED
|
@@ -249,9 +249,11 @@ in the allowlist → the model never sees it in `<available_skills>`.
|
|
|
249
249
|
Two ways to satisfy both:
|
|
250
250
|
|
|
251
251
|
- **Via a definition/pack import.** The import writes each skill directory into
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
252
|
+
that agent's scope, without overwriting another agent's or a shared skill, and
|
|
253
|
+
sets the agent's `skills` allowlist **authoritatively** from the skills present.
|
|
254
|
+
Dropping a skill removes it from the allowlist, not storage. Keep the definition
|
|
255
|
+
complete. Packs previously imported into shared scope must be reimported from
|
|
256
|
+
their original sources; the overwritten shared body cannot recover each agent's version.
|
|
255
257
|
- **Directly (Studio / API / DSL).** Write the skill (upserts the row and reloads
|
|
256
258
|
the catalog atomically — live immediately), then attach it to the agent(s) by
|
|
257
259
|
adding its name to the `skills` allowlist.
|
data/docs/STUDIO.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Studio
|
|
3
|
+
parent: Operate
|
|
4
|
+
nav_order: 0
|
|
5
|
+
permalink: /studio/
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Studio — the operator UI
|
|
9
|
+
|
|
10
|
+
The Studio is the web UI at `/studio`. Sign in with `ADMIN_TOKEN` (see
|
|
11
|
+
[Deploy](DEPLOY.md#environment-variables)). This page covers the screens for
|
|
12
|
+
watching conversations, reviewing what waits on a person, and seeing what a
|
|
13
|
+
change did. Model metrics are on [Observability](OBSERVABILITY.md#studio-models).
|
|
14
|
+
|
|
15
|
+
Outside production, a banner above every page names the environment
|
|
16
|
+
(`INSIKA_ENV`, else `RACK_ENV`, else `local`): amber for staging, blue for
|
|
17
|
+
anything else. Production (`production` or `prod`) shows no banner.
|
|
18
|
+
|
|
19
|
+
## Navigation
|
|
20
|
+
|
|
21
|
+
The sidebar groups pages as Operate, Review, Build, Insights and Platform. Each
|
|
22
|
+
Review page shows a badge with how many items wait on it. `⌘K` (or Search in the
|
|
23
|
+
sidebar) opens a palette that jumps to any page or agent.
|
|
24
|
+
|
|
25
|
+
## Overview
|
|
26
|
+
|
|
27
|
+
`/studio/home`. The Overview shows the headline numbers for the agent filter and
|
|
28
|
+
a **Needs attention** row. The row lists the review queues, customers waiting for
|
|
29
|
+
a reply and tools failing in the last 24 hours, each linking to its page. Only
|
|
30
|
+
non-zero items show.
|
|
31
|
+
|
|
32
|
+
## Live
|
|
33
|
+
|
|
34
|
+
`/studio/live` shows one card per recent conversation: customer, agent, the last
|
|
35
|
+
message, a presence dot (activity in the last 5 minutes) and the guardrail flag.
|
|
36
|
+
Cards move with the event stream. While a turn runs, a card shows the tools it
|
|
37
|
+
calls and "agent is writing…". A turn paused for an approval stops writing until
|
|
38
|
+
it resumes. A ticker lists the latest events, without the streaming deltas.
|
|
39
|
+
|
|
40
|
+
`?agent=<id>` shows only that agent's conversations.
|
|
41
|
+
|
|
42
|
+
## Chats
|
|
43
|
+
|
|
44
|
+
`/studio/chats` lists conversations with three tabs: **All**, **Active now**
|
|
45
|
+
(activity in the last 5 minutes) and **Guardrail flagged**, each with its count.
|
|
46
|
+
The session page shows the customer (key, name, phone) and links to their other
|
|
47
|
+
conversations.
|
|
48
|
+
|
|
49
|
+
### Why this reply
|
|
50
|
+
|
|
51
|
+
Each agent reply has a collapsed **why this reply** panel for its own turn:
|
|
52
|
+
|
|
53
|
+
- context tokens by category, and the skills injected with the reason;
|
|
54
|
+
- the tool calls with status and timing;
|
|
55
|
+
- the guardrail flags on the customer's message.
|
|
56
|
+
|
|
57
|
+
From the panel, **Replay in Playground** opens the conversation in the
|
|
58
|
+
Playground, and **Save as eval
|
|
59
|
+
case** opens the Evals editor prefilled from the reply (the customer's turns,
|
|
60
|
+
the transcript as the reference, the tools the turn called). A reply that
|
|
61
|
+
streams in live offers the panel when its turn finishes.
|
|
62
|
+
|
|
63
|
+
### Tool calls
|
|
64
|
+
|
|
65
|
+
A turn with two or more tool calls draws a waterfall of when each one ran.
|
|
66
|
+
Tool calls are grouped by turn within each task, since the turn number restarts
|
|
67
|
+
at 1 for every task.
|
|
68
|
+
|
|
69
|
+
### Markup tags
|
|
70
|
+
|
|
71
|
+
Tags an agent or a host writes in the text, like `<carrossel/>`,
|
|
72
|
+
`<vitrine formato="lista">` or `</b>`, show as highlighted tokens: in replies,
|
|
73
|
+
customer messages, and the Live and Chats previews. They are escaped and never
|
|
74
|
+
rendered. The channel decides what a tag does; the Studio only shows it is
|
|
75
|
+
there. A tag inside inline code stays code.
|
|
76
|
+
|
|
77
|
+
## Guardrail false positives
|
|
78
|
+
|
|
79
|
+
On a guardrail flag in the session page, **Mark false positive** records the
|
|
80
|
+
verdict on the flag itself, inside the session, so retention and tenant purges
|
|
81
|
+
cover it. The flag can later be marked resolved, or the verdict undone.
|
|
82
|
+
Reviewing a flag does not count as conversation activity: the session keeps its
|
|
83
|
+
place in the recent lists. The command is `:review_guardrail_flag`.
|
|
84
|
+
|
|
85
|
+
## Inbox
|
|
86
|
+
|
|
87
|
+
`/studio/inbox` is one list, oldest first, of everything that waits on a person:
|
|
88
|
+
approvals, fact proposals, mined skills, refinement runs and reported guardrail
|
|
89
|
+
false positives. Tabs filter by type, with counts. Each row opens the page where
|
|
90
|
+
the item is decided.
|
|
91
|
+
|
|
92
|
+
## Agent History
|
|
93
|
+
|
|
94
|
+
The **History** tab of an agent shows outcomes per day over the last 30 days:
|
|
95
|
+
the funnel's primary outcome when the agent declares a funnel, else all
|
|
96
|
+
outcomes. A dashed line marks each change to a prompt file or to one of this
|
|
97
|
+
agent's own skills (shared skills change for every agent, so they are not
|
|
98
|
+
marked).
|
|
99
|
+
|
|
100
|
+
Under the chart, one card per change shows the file or skill, when, a short line
|
|
101
|
+
diff, and the outcomes in the 7 days after against the 7 days before. A window
|
|
102
|
+
that is not complete yet, or that starts before the outcome data, reads "not
|
|
103
|
+
enough data". A prompt card has the Restore form; a skill card links to the
|
|
104
|
+
skill.
|
|
105
|
+
|
|
106
|
+
## Funnel: where conversations stop
|
|
107
|
+
|
|
108
|
+
The Funnel page marks the biggest loss between two consecutive declared stages.
|
|
109
|
+
Each loss opens (`?drop=<stage>`) the conversations that reached the stage but
|
|
110
|
+
not the next one in the period, grouped by the last thing that happened:
|
|
111
|
+
|
|
112
|
+
| Reason | Meaning |
|
|
113
|
+
|--------|---------|
|
|
114
|
+
| Last tool call failed | the conversation's last tool call returned an error |
|
|
115
|
+
| A guardrail flagged a message | a guardrail flagged one of its messages |
|
|
116
|
+
| Customer waiting on a reply | the customer wrote last |
|
|
117
|
+
| Customer went quiet after the agent's reply | the agent wrote last |
|
|
118
|
+
| No message to read | the conversation has no message to classify |
|
|
119
|
+
| Conversation no longer stored | the conversation was deleted |
|
|
120
|
+
|
|
121
|
+
Only the newest 200 stuck conversations are classified; the page says how many
|
|
122
|
+
were left out.
|
data/docs/TOOLS.md
CHANGED
|
@@ -12,16 +12,16 @@ kinds, and the distinction that matters is **who can change one at runtime**:
|
|
|
12
12
|
|
|
13
13
|
| | **Code tool** | **Data tool** | **MCP tool** |
|
|
14
14
|
|---|---|---|---|
|
|
15
|
-
| What | a Ruby class (`< RubyLLM::Tool`) | an HTTP call described by config
|
|
15
|
+
| What | a Ruby class (`< RubyLLM::Tool`) | an HTTP call or card presentation described by config | an MCP server's tool, called LIVE |
|
|
16
16
|
| Lives | in the deployment image | as a row in SQLite | on the MCP server, behind a live client |
|
|
17
17
|
| Editable at runtime | no (shipped in the image) | **yes** (DSL / API / manifest / Studio) | **yes** — enable/edit the *instance* (DSL / CLI / API / JSON import / Studio); the server owns its own tools |
|
|
18
|
-
| Reach for it when | logic must run in-process (file edit, shell, subagent) | calling an
|
|
18
|
+
| Reach for it when | logic must run in-process (file edit, shell, subagent) | calling an HTTP API or selecting evidence cards | adopting a whole external MCP server's toolset |
|
|
19
19
|
|
|
20
20
|
**MCP tools are not data tools.** Configuring an enabled MCP **instance** (any
|
|
21
21
|
surface below) is enough — its tools appear automatically, tagged
|
|
22
22
|
`mcp:<instance>`, and each CALL goes straight to the server through a live,
|
|
23
|
-
held client (stdio
|
|
24
|
-
handshake) — never a frozen snapshot. See [MCP servers](#mcp-servers) below.
|
|
23
|
+
held native RubyLLM client (stdio / Streamable HTTP, with discovery and legacy
|
|
24
|
+
handshake fallback) — never a frozen snapshot. See [MCP servers](#mcp-servers) below.
|
|
25
25
|
|
|
26
26
|
Code tools **win name collisions** — you cannot register a data tool whose name
|
|
27
27
|
shadows a code tool.
|
|
@@ -55,6 +55,9 @@ write path (`POST /v1/tools/manifest`) — writing this same shape via the DSL
|
|
|
55
55
|
or Studio needs the literal header value instead; see
|
|
56
56
|
"[The one gotcha](#the-one-gotcha-envsecret-templating-is-manifest-only)" below.
|
|
57
57
|
|
|
58
|
+
`{{ctx.chat_id}}` is the external session ID. In multi-tenant mode, it excludes
|
|
59
|
+
the authenticated tenant prefix used by Insika to isolate stored sessions.
|
|
60
|
+
|
|
58
61
|
### Parameters: the schema is the contract
|
|
59
62
|
|
|
60
63
|
`parameters` is **JSON Schema**, and it reaches the provider verbatim — it is the only
|
|
@@ -120,7 +123,7 @@ once, at ingestion — see the gotcha below before reaching for it:
|
|
|
120
123
|
(`object/array/string/number/integer/boolean`); `oneOf`/`anyOf`/`allOf`/`$ref`/
|
|
121
124
|
`if`/`then`/`else` are forbidden (not every provider supports them).
|
|
122
125
|
- `side_effect` defaults from the method (GET/HEAD → false, else true) and drives
|
|
123
|
-
checkpoint/replay semantics (a completed side-effecting tool is not re-run on
|
|
126
|
+
serial execution within a session and checkpoint/replay semantics (a completed side-effecting tool is not re-run on
|
|
124
127
|
resume — see [Architecture](ARCHITECTURE.md#durability-checkpoints-and-resume)).
|
|
125
128
|
|
|
126
129
|
### `halt_when`: when the answer is already out
|
|
@@ -199,12 +202,10 @@ backend, not of whoever calls it. Every agent sharing the tool gets the same val
|
|
|
199
202
|
|
|
200
203
|
## Evidence: the lean envelope and grounding
|
|
201
204
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
down to what the model sees (the lean envelope) **and** records every returned id on
|
|
207
|
-
the session's evidence ledger. There is no "lean but not evidence" mode.
|
|
205
|
+
An `evidence` declaration reshapes a tool result into a lean list and records its
|
|
206
|
+
IDs in the session ledger. The ledger supplies the write gate, presentation tools
|
|
207
|
+
and optional output grounding below. Declaring evidence alone does not prevent
|
|
208
|
+
unsupported claims in the final answer.
|
|
208
209
|
|
|
209
210
|
```jsonc
|
|
210
211
|
{ "name": "search_products",
|
|
@@ -226,14 +227,98 @@ the session's evidence ledger. There is no "lean but not evidence" mode.
|
|
|
226
227
|
never a null. A malformed evidence result becomes `{ "error": … }` back to the
|
|
227
228
|
model — a correctable tool answer, exactly like a malformed call.
|
|
228
229
|
- **Attachments** are the optional second half: `[{ "type": "card"|"image",
|
|
229
|
-
"url": "…", "caption": "…" }]` (≤ 16, url ≤ 500 chars, malformed dropped). They
|
|
230
|
+
"url": "…", "caption": "…", "id": "…" }]` (≤ 16, url ≤ 500 chars, malformed dropped). They
|
|
230
231
|
**never** reach the model context or the transcript — they ride the channel
|
|
231
232
|
delivery as an additive `attachments` key on the outbox payload, and the channel
|
|
232
|
-
(or its consumer) decides what a card looks like.
|
|
233
|
+
(or its consumer) decides what a card looks like. Supply an explicit `id` when
|
|
234
|
+
cards are not one-to-one with items in the same order. Without one, an attachment
|
|
235
|
+
takes its item's ID at the original position, before malformed cards are dropped.
|
|
236
|
+
- Lean `line` passes through the tool-result sanitizer only when `fencing` is on.
|
|
237
|
+
Attachment captions are normalized to UTF-8 but are not fenced. See
|
|
238
|
+
[Fencing](AGENTS.md#fencing--third-party-text-is-data-never-instructions).
|
|
233
239
|
- A **code tool** opts in the same way: it either returns `{ items, attachments }`
|
|
234
240
|
directly and declares `evidence` in its registry metadata, or exposes an
|
|
235
241
|
`evidence` reader. No declaration = today's tool behavior, byte for byte.
|
|
236
242
|
|
|
243
|
+
### Provenance: checking IDs before a write
|
|
244
|
+
|
|
245
|
+
Declare `requires_evidence` on a data-defined tool to accept only IDs previously
|
|
246
|
+
returned by an `evidence` tool in the same session:
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{ "requires_evidence": ["product_id"] }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The full form is `{ "requires_evidence": { "params": ["product_id"] } }`.
|
|
253
|
+
The list must be non-empty and name declared top-level parameters. Scalar values
|
|
254
|
+
and every element of an array are converted to strings and compared exactly:
|
|
255
|
+
`SKU-1` and `sku-1` are different IDs. IDs typed by a customer do not count.
|
|
256
|
+
The ledger includes earlier turns and completed evidence results in the current
|
|
257
|
+
turn, capped at the latest 1,000 distinct IDs. Every declared parameter the call
|
|
258
|
+
carries must pass before any write occurs; a parameter the schema marks optional
|
|
259
|
+
and the model leaves out has nothing to check, a required one left out blocks.
|
|
260
|
+
Search first, then write in a later batch: a search and write in the same parallel
|
|
261
|
+
batch have no dependency ordering guarantee.
|
|
262
|
+
|
|
263
|
+
An unknown ID returns `status: "blocked"`, `gate: "provenance"`, the parameter,
|
|
264
|
+
the value, and an instruction to search or look it up before retrying. The backend
|
|
265
|
+
is never called and no operator approval is requested. A missing ledger blocks the
|
|
266
|
+
call too. Omit the declaration to keep the existing behavior; MCP tools do not
|
|
267
|
+
support this declaration.
|
|
268
|
+
|
|
269
|
+
The Studio tool editor exposes `requires_evidence`. Blocked calls appear in session
|
|
270
|
+
traces and `insika tools:report`, emit `tool_blocked` with name/gate/parameter only,
|
|
271
|
+
and increment `insika.tool.blocked`. `insika doctor` warns when an agent allows a
|
|
272
|
+
gated data tool without an allowed data tool declaring `evidence`.
|
|
273
|
+
|
|
274
|
+
### Side effects in parallel batches
|
|
275
|
+
|
|
276
|
+
With `limits.tool_concurrency > 1`, tools marked `side_effect` execute one at a time
|
|
277
|
+
within the session's runtime. Unmarked tools still run concurrently, including
|
|
278
|
+
while a write is running. MCP tools are marked `side_effect: true` unless the server
|
|
279
|
+
annotates them `readOnlyHint`; a read-only `POST` data tool needs an explicit
|
|
280
|
+
`"side_effect": false` to keep its concurrency.
|
|
281
|
+
A queued write holds no concurrency slot. Different sessions remain independent;
|
|
282
|
+
backend rules such as quantity limits remain the backend's responsibility.
|
|
283
|
+
|
|
284
|
+
The per-tool timeout starts after both gates are acquired. Trace duration includes
|
|
285
|
+
queueing time, so it measures how long the model waited, not just backend execution.
|
|
286
|
+
|
|
287
|
+
### Customer confirmation: a write the conversation approves
|
|
288
|
+
|
|
289
|
+
Some writes should not happen on the strength of one ambiguous line — closing an
|
|
290
|
+
order, deleting a record, sending a message on someone's behalf. Name them on the
|
|
291
|
+
agent and the engine holds the call for the customer's word:
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
customer_confirm "create_order" # DSL; the Studio field is "customer confirm"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
When the model calls a held tool, nothing runs. The engine records a pending action
|
|
298
|
+
for the session and returns a `pending_confirmation` result carrying the arguments
|
|
299
|
+
and an instruction: tell the customer exactly what will happen and ask. The turn ends
|
|
300
|
+
with that question. On the next message the open hold is rendered at the tail of the
|
|
301
|
+
context, and two system tools are available: `confirm_pending` runs the original call
|
|
302
|
+
with the arguments recorded at the hold (no path exists to confirm different ones),
|
|
303
|
+
`cancel_pending` drops it. A hold that the next message neither confirms nor cancels
|
|
304
|
+
expires on its own — a customer who changed the subject did not agree.
|
|
305
|
+
|
|
306
|
+
Three controls now sit on a write, and they compose in this order:
|
|
307
|
+
|
|
308
|
+
| Control | Who decides | What happens to the turn | Declared on |
|
|
309
|
+
| --- | --- | --- | --- |
|
|
310
|
+
| Provenance (`requires_evidence`) | the ledger | the call is refused, the model is told how to resolve the id | the tool |
|
|
311
|
+
| Customer confirmation (`customer_confirm`) | the customer, next message | the turn ends with a question | the agent |
|
|
312
|
+
| Operator approval (`approvals_required`) | a person in the Studio | the turn suspends and waits | the agent |
|
|
313
|
+
|
|
314
|
+
A tool is confirmed by the customer or approved by the operator, never both. The
|
|
315
|
+
confirmed run still passes provenance, fencing, the side-effect record and the trace;
|
|
316
|
+
the trace shows the hold as `gate: confirmation`, and the `:tool_result` event and the
|
|
317
|
+
Responses stream report it as `status: held`, which the evals treat as neither a
|
|
318
|
+
success nor an error. Whether the customer's "sim" means yes is the model's reading;
|
|
319
|
+
what the engine guarantees is that the write runs only after an explicit question, a
|
|
320
|
+
customer turn, and an explicit tool call.
|
|
321
|
+
|
|
237
322
|
### Grounding: policing claims against the ledger
|
|
238
323
|
|
|
239
324
|
With the ledger fed, the pack declares how claims are policed — data on the agent,
|
|
@@ -263,20 +348,70 @@ grounding mode: :flag, matcher: { sku: '\b[A-Z]{2,4}\d{4,8}\b' }
|
|
|
263
348
|
- Grounding is **independent of the guardrails opt-in**: an agent with guardrails
|
|
264
349
|
off and `grounding.mode: :flag` still gets the check.
|
|
265
350
|
|
|
351
|
+
## Presentation tools: the model picks ids, the engine shows the cards
|
|
352
|
+
|
|
353
|
+
A **presentation tool** selects which evidence cards to show. Declare it with
|
|
354
|
+
`presentation` instead of `request`; it runs in-process and is always
|
|
355
|
+
`side_effect: false`. The model supplies IDs, never card URLs or captions.
|
|
356
|
+
|
|
357
|
+
```jsonc
|
|
358
|
+
{ "name": "present_products",
|
|
359
|
+
"description": "Show product cards to the customer. Pass only ids a search returned.",
|
|
360
|
+
"parameters": [{ "name": "product_ids", "type": "array:string" },
|
|
361
|
+
{ "name": "title", "type": "string", "required": false }],
|
|
362
|
+
"presentation": { "component": "product_cards", // what the channel renders
|
|
363
|
+
"ids": "product_ids", // the array:string parameter
|
|
364
|
+
"max": 8 } } // 1..16 (the attachment cap; default 16)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Exactly one of `request` / `presentation`; `ids` must name a declared `array:string`
|
|
368
|
+
parameter; `component` follows the tool-name rule. Create it through the DSL,
|
|
369
|
+
manifest or API. The Studio form preserves an existing `presentation` declaration
|
|
370
|
+
on save but does not expose its fields. Requested IDs are deduplicated in order
|
|
371
|
+
and compared case-sensitively. The engine then:
|
|
372
|
+
|
|
373
|
+
1. keeps only ids the session's **evidence ledger** has seen — the rest are dropped
|
|
374
|
+
with reason `unknown` (an id the model invented, or the customer typed);
|
|
375
|
+
2. joins each kept id to the card an evidence tool returned **this session** — this
|
|
376
|
+
turn's first, then the last 64 the ledger kept (cards carry the `id` of the item
|
|
377
|
+
they stand for); a known id with no card is dropped with reason `no_card`;
|
|
378
|
+
3. truncates to `max` — the overflow is dropped with reason `max`;
|
|
379
|
+
4. records the selection on the turn and emits `:ui` on the stream (published as
|
|
380
|
+
`insika.ui` on `/v1/responses`, as the `ui` frame on the web channel);
|
|
381
|
+
5. answers the model with `shown` (IDs) and `dropped` (objects with `id` and `reason`) — plus one
|
|
382
|
+
instruction when nothing could be shown ("name the products in text or search
|
|
383
|
+
again").
|
|
384
|
+
|
|
385
|
+
**Delivery.** When a turn made a presentation call, the outbox `attachments` are
|
|
386
|
+
*exactly the presented cards*, in call order, each stamped with the call's `component`
|
|
387
|
+
and `title`. A turn with no presentation call delivers every hoarded card, as before —
|
|
388
|
+
a pack that declares no presentation tool sees no change. An empty selection also
|
|
389
|
+
suppresses that fallback: it emits `count: 0` and delivers no cards from that call.
|
|
390
|
+
An earlier turn's ID can be shown without a fresh lookup while its card remains
|
|
391
|
+
in the session's 64-card ledger. IDs and cards have separate caps: a known ID can
|
|
392
|
+
still return `no_card`. Cards are stored snapshots, not a stock or price refresh.
|
|
393
|
+
|
|
394
|
+
A presentation tool needs an evidence source to populate the session's cards.
|
|
395
|
+
`insika doctor` warns when an agent allows presentation without an allowed evidence
|
|
396
|
+
data tool (`presentation-tools`); it cannot verify a code-tool evidence source. The line that tells the model
|
|
397
|
+
*when* to show cards ("show cards with present_products, ids only") is the pack's.
|
|
398
|
+
|
|
399
|
+
Not here: partial rendering while arguments stream, per-component enrichment (price
|
|
400
|
+
today, stock — the card is what the evidence tool returned), and a second component
|
|
401
|
+
such as suggestion chips (same mechanism, when a channel asks for it).
|
|
402
|
+
|
|
266
403
|
## Registering a tool
|
|
267
404
|
|
|
268
405
|
A tool appears in the Studio panel and enters an agent's tool-loop when it is
|
|
269
406
|
**registered** in the catalog **and** allowed by the agent's policy allowlist.
|
|
270
|
-
|
|
271
|
-
reload, no restart):
|
|
407
|
+
Three ways to write a data tool into the store — all **hot** (registry and catalog
|
|
408
|
+
reload, no restart; with several workers, the others pick the change up within ~2 s):
|
|
272
409
|
|
|
273
410
|
1. **DSL** — `data_tool(name:, …)` in a `Insika.agent { … }` block.
|
|
274
411
|
2. **Studio** — the Tools panel editor.
|
|
275
412
|
3. **Manifest** — `POST /v1/tools/manifest`. Partial failure is isolated: one
|
|
276
413
|
malformed tool becomes an `errors[]` entry; only a structural manifest error
|
|
277
414
|
fails the whole request. The response reports `{ version, created, updated, errors }`.
|
|
278
|
-
4. ~~MCP ingestion~~ — retired. An MCP server's tools are no
|
|
279
|
-
longer written into this store at all; see [MCP servers](#mcp-servers).
|
|
280
415
|
|
|
281
416
|
### The one gotcha: env/secret templating is manifest-only
|
|
282
417
|
|
|
@@ -293,28 +428,67 @@ any other way must ship literal values** — a real URL, and a real (masked on
|
|
|
293
428
|
read) header value. `{{ctx.*}}` and `{{param}}` work everywhere (they resolve
|
|
294
429
|
at turn time, not ingestion).
|
|
295
430
|
|
|
431
|
+
The Studio **Tools** screen groups permissions by origin: native tools, HTTP
|
|
432
|
+
tools, and MCP tools by server instance. Selection remains per tool; grouping
|
|
433
|
+
does not change tool names or grant permissions.
|
|
434
|
+
Each section can be collapsed. Filtering by tool or server name opens matching
|
|
435
|
+
sections without changing selections; clearing the filter restores their state.
|
|
436
|
+
Section selection buttons affect every tool in that section, including filtered
|
|
437
|
+
tools, but never override denied tools. Save tools applies the changes.
|
|
438
|
+
|
|
439
|
+
An MCP instance is a connection, not a single tool. Enable its individual tools
|
|
440
|
+
for the agent in Studio. **Test connection** validates discovery with that
|
|
441
|
+
instance's credentials; it does not validate a separate HTTP data tool or every
|
|
442
|
+
remote tool's execution permissions. When replacing an HTTP tool with MCP,
|
|
443
|
+
remove the old tool from the agent's permissions and update prompt references
|
|
444
|
+
to the actual MCP tool names. Keep the egress allowlist restricted to required hosts.
|
|
445
|
+
|
|
296
446
|
## MCP servers
|
|
297
447
|
|
|
298
448
|
An MCP **instance** is durable config — transport, target, credentials, an
|
|
299
449
|
`enabled` flag — held in its own store, separate from data tools. Once an
|
|
300
450
|
instance is enabled, its tools appear in the catalog automatically (group
|
|
301
|
-
`mcp:<instance>`, `side_effect: true`), and every call goes straight to the
|
|
451
|
+
`mcp:<instance>`, `side_effect: true` unless annotated `readOnlyHint`), and every call goes straight to the
|
|
302
452
|
server through a live, held client — the runtime never converts an MCP tool
|
|
303
453
|
into a stored data tool.
|
|
304
454
|
|
|
305
|
-
|
|
455
|
+
Each tool is named after its instance: `<instance>__<tool>`, with the instance
|
|
456
|
+
lowercased and anything outside `a-z0-9` turned into `_` (instance
|
|
457
|
+
`shop-staging`, tool `list_products` → `shop_staging__list_products`). That is
|
|
458
|
+
the name the model calls and the one an agent's allowlist holds, so two
|
|
459
|
+
instances that offer the same tool (a store's production and staging) stay two
|
|
460
|
+
tools, each reaching its own server. Per-tool overrides (`tools:`) still use the
|
|
461
|
+
server's own tool name. A qualified name over 64 characters is skipped with a
|
|
462
|
+
warning, since providers reject it: shorten the instance name.
|
|
463
|
+
|
|
464
|
+
The deployment's legacy `import_mcp_tools` command now aliases
|
|
465
|
+
`refresh_mcp_tools`: it returns `{ instance:, tools: }` and emits
|
|
466
|
+
`mcp_tools_refreshed`, replacing the old created/updated import report.
|
|
467
|
+
Previously imported data tools remain unchanged and take precedence over live
|
|
468
|
+
tools with the same name. After refreshing and verifying the live instance,
|
|
469
|
+
delete those snapshots through the normal data-tool controls to use live calls.
|
|
470
|
+
`insika doctor` identifies them by their `mcp:<instance>` group.
|
|
471
|
+
|
|
472
|
+
**Two supported transports**, picked by `transport:`:
|
|
306
473
|
|
|
307
474
|
| Transport | Target | Notes |
|
|
308
475
|
|---|---|---|
|
|
309
476
|
| `stdio` | `command` + `args`, run as a child process, `env` is its process environment | requires `INSIKA_MCP_STDIO=1` — see below |
|
|
310
|
-
| `http` | `url` + `headers` (Streamable HTTP
|
|
311
|
-
|
|
477
|
+
| `http` | `url` + `headers` (Streamable HTTP) | egress-guarded; HTTPS required outside loopback |
|
|
478
|
+
|
|
479
|
+
Legacy `sse` records remain stored, but cannot connect through the native client.
|
|
480
|
+
Change the record to `http` with the server's Streamable HTTP endpoint. Event-stream
|
|
481
|
+
responses over Streamable HTTP remain supported; only the old SSE transport is removed.
|
|
312
482
|
|
|
313
483
|
**The stdio gate.** A stdio instance is arbitrary command execution by
|
|
314
484
|
config — it saves, but refuses to start ("stdio disabled by env") until the
|
|
315
485
|
operator sets `INSIKA_MCP_STDIO=1` (config-over-convention, the same pattern
|
|
316
|
-
as the egress envs). `http
|
|
317
|
-
|
|
486
|
+
as the egress envs). `http` needs no such gate; its URL goes through the
|
|
487
|
+
egress guard instead: HTTPS, and a host that resolves to a private address
|
|
488
|
+
needs `INSIKA_EGRESS_ALLOW_PRIVATE` and a place in `INSIKA_EGRESS_HOSTS`. A
|
|
489
|
+
public host is not pinned by that list, since the operator fixed the URL and
|
|
490
|
+
the model cannot change it. Native MCP additionally refuses plain HTTP
|
|
491
|
+
outside loopback, even when `INSIKA_EGRESS_ALLOW_HTTP` is enabled.
|
|
318
492
|
|
|
319
493
|
**Credentials are never visible in plaintext.** `env` (stdio) and `headers`
|
|
320
494
|
(http/sse) mask every value as `__OCULTO__` on read, everywhere (CLI, API,
|
|
@@ -346,6 +520,31 @@ held client, which does its own discovery on first use regardless of whether
|
|
|
346
520
|
`enabled` flag and its credentials are the **operator's** — a Studio/CLI/API
|
|
347
521
|
edit made after boot is never clobbered back by the next restart.
|
|
348
522
|
|
|
523
|
+
The **first** boot after a declaration also lists the server's tools, because
|
|
524
|
+
until they are listed the agent is offered none of them and answers out of its
|
|
525
|
+
own head instead. A server that cannot be listed (stdio behind a closed gate, a
|
|
526
|
+
host that is down) does not stop the boot — it is warned about by name, and the
|
|
527
|
+
agent runs without those tools until `insika mcp refresh <name>` succeeds.
|
|
528
|
+
|
|
529
|
+
**What the deployment trusts each tool with** — a server describes what its tools
|
|
530
|
+
*do*; only the deployment can say which of its answers are evidence and which
|
|
531
|
+
parameter may only ever carry an id something already returned:
|
|
532
|
+
|
|
533
|
+
```ruby
|
|
534
|
+
mcp "store", transport: :http, url: ENV.fetch("STORE_MCP"),
|
|
535
|
+
tools: {
|
|
536
|
+
# the store's own field names: no server renames them for us
|
|
537
|
+
"search_products" => { evidence: { kind: "products", items: "products",
|
|
538
|
+
id: "product_id", line: "line" } },
|
|
539
|
+
# a write that may only take an id the customer was actually shown
|
|
540
|
+
"add_to_cart" => { requires_evidence: ["product_id"] }
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Both are refused at ingestion, never at the turn: an `evidence:` typo that
|
|
545
|
+
survived would extract nothing and the gate below it would block every write.
|
|
546
|
+
A tool nobody names here behaves exactly as it did before.
|
|
547
|
+
|
|
349
548
|
2. **CLI** — `insika mcp list | add | remove | import <file.json> | test <name> |
|
|
350
549
|
refresh <name>`. `add` takes `--name`, `--transport`, `--command`/`--arg`
|
|
351
550
|
(repeatable) or `--url`/`--header "Name: value"` (repeatable)/`--env
|
|
@@ -367,7 +566,7 @@ held client, which does its own discovery on first use regardless of whether
|
|
|
367
566
|
|
|
368
567
|
`insika mcp import FILE.json` upserts every entry (a bare `command` implies
|
|
369
568
|
`stdio`; a bare `url` implies `http`; add `"transport": "sse"` explicitly
|
|
370
|
-
|
|
569
|
+
only to retain a legacy SSE record pending migration). The same parser
|
|
371
570
|
backs `PUT /v1/mcp` and Studio's "Import JSON" box; `export` produces the
|
|
372
571
|
document back with secrets masked as `__OCULTO__`, so round-tripping an
|
|
373
572
|
export never wipes a stored credential.
|
|
@@ -405,9 +604,10 @@ held client, which does its own discovery on first use regardless of whether
|
|
|
405
604
|
A model can ask for several tools in one step. By default the engine runs them one
|
|
406
605
|
at a time. Set `limits[:tool_concurrency]` above 1 (see
|
|
407
606
|
[Agents](AGENTS.md#tool_concurrency--parallel-tool-calls)) and the calls in that
|
|
408
|
-
batch run concurrently, **at most N in flight**, on the turn's own reactor
|
|
409
|
-
|
|
410
|
-
|
|
607
|
+
batch run concurrently, **at most N in flight**, on the turn's own reactor.
|
|
608
|
+
Tools marked `side_effect` still execute one at a time per session; queued writes
|
|
609
|
+
acquire that serial gate before taking a shared concurrency slot. See
|
|
610
|
+
[Side effects](#side-effects-in-parallel-batches). The cap covers every enveloped tool, including the
|
|
411
611
|
ones `tool_search` promotes mid-turn.
|
|
412
612
|
|
|
413
613
|
It applies only to what the *model* fans out. Two primitives already parallelize
|
|
@@ -434,7 +634,7 @@ Turning it on changes three things, all of them worth knowing before you do:
|
|
|
434
634
|
Approvals and concurrency are mutually exclusive per turn — the approval gate wins
|
|
435
635
|
and the turn goes serial. That is a deadlock avoided, not a preference.
|
|
436
636
|
|
|
437
|
-
## Egress: the SSRF guard
|
|
637
|
+
## Egress: the SSRF guard
|
|
438
638
|
|
|
439
639
|
Data tools make outbound HTTP, so every call passes through the **EgressGuard**, a
|
|
440
640
|
Server-Side Request Forgery defense. The default posture is **strict: public
|
|
@@ -446,17 +646,10 @@ Server-Side Request Forgery defense. The default posture is **strict: public
|
|
|
446
646
|
| `INSIKA_EGRESS_ALLOW_HTTP=1` | permit plain `http` — **loopback dev only** |
|
|
447
647
|
| `INSIKA_EGRESS_ALLOW_PRIVATE=1` | permit private/loopback IPs — **dev only** |
|
|
448
648
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
> plausible failure and the conversation *looks* like it worked. You will not see
|
|
454
|
-
> an exception.
|
|
455
|
-
>
|
|
456
|
-
> **Always verify by the trace, never by the reply:** open the Studio session
|
|
457
|
-
> viewer — a healthy call shows the request, args, and the backend's `200`; a
|
|
458
|
-
> missing or errored call is almost always egress (host not in the allowlist, or
|
|
459
|
-
> `http`/private without the opt-in).
|
|
649
|
+
An egress rejection returns `{ error: … }` to the model without making the HTTP
|
|
650
|
+
request. Inspect the session trace for the actual error; a plausible reply does
|
|
651
|
+
not prove the backend ran. Provenance refusals instead report `status: "blocked"`
|
|
652
|
+
and `gate: "provenance"`.
|
|
460
653
|
|
|
461
654
|
Egress is **orthogonal** to registration and allowlisting: a tool can be
|
|
462
655
|
registered, allowed, offered to the model, and still blocked at call time.
|
|
@@ -471,8 +664,9 @@ Work down this checklist:
|
|
|
471
664
|
`data-tools` check is the only place that says so.
|
|
472
665
|
2. **Allowed for this agent?** In `tools_allow` (or an allowed group), and not in
|
|
473
666
|
`tools_deny`?
|
|
474
|
-
3. **
|
|
475
|
-
|
|
667
|
+
3. **Call refused or failed?** Inspect the trace. For `gate: "provenance"`, look up
|
|
668
|
+
the ID through an evidence tool before retrying. For an error, check its message
|
|
669
|
+
for schema, egress, timeout or backend failures.
|
|
476
670
|
4. **URL literal?** For non-manifest tools, an unresolved `{{env.*}}` would have
|
|
477
671
|
422'd at import — re-check the definition.
|
|
478
672
|
|
|
@@ -490,7 +684,7 @@ routes, the signed link and the retention/LGPD reach.
|
|
|
490
684
|
The per-session trace answers "what did this conversation call"; nothing used to
|
|
491
685
|
answer "what does this agent carry and never use". The report aggregates the
|
|
492
686
|
stored traces per agent (tasks → sessions → `tool_traces`, the same read the
|
|
493
|
-
Studio does) and flags
|
|
687
|
+
Studio does) and flags four shapes:
|
|
494
688
|
|
|
495
689
|
- **`never_called`** — in `tools_allow`, zero calls in any stored trace. Dead
|
|
496
690
|
weight: its schema ships on every request and buys nothing.
|
|
@@ -498,6 +692,8 @@ Studio does) and flags three shapes:
|
|
|
498
692
|
the window (default 14 days). Either the tool is broken or the model cannot
|
|
499
693
|
hold its contract.
|
|
500
694
|
- **`stale`** — called at some point, but not once inside the window.
|
|
695
|
+
- **`blocked`** — gate refusals in the window, counted by gate. These do not count
|
|
696
|
+
as conventional tool errors.
|
|
501
697
|
|
|
502
698
|
```bash
|
|
503
699
|
insika tools:report # every stored agent
|
data/docs/operate.md
CHANGED
|
@@ -10,10 +10,13 @@ permalink: /operate/
|
|
|
10
10
|
Running it once it is live: seeing what happened, firing turns nobody sent,
|
|
11
11
|
handing back a report, and knowing what the box will take before it takes it.
|
|
12
12
|
|
|
13
|
+
- **[Studio](STUDIO.md)** — the operator UI: live conversations, why each reply, the review inbox, what a change did.
|
|
13
14
|
- **[Observability](OBSERVABILITY.md)** — the event stream, and turns as OpenTelemetry traces and metrics.
|
|
14
15
|
- **[Schedules](SCHEDULING.md)** — recurring turns the engine fires on its own tick.
|
|
15
16
|
- **[Artifacts](ARTIFACTS.md)** — a report the agent can hand you a URL to.
|
|
16
17
|
- **[Load test](LOADTEST.md)** — end-to-end load testing, and comparing topologies before you choose one.
|
|
17
18
|
- **[Soak](SOAK.md)** — the long run that finds what a short one cannot.
|
|
18
19
|
- **[Benchmark](BENCHMARK.md)** — the engine's measured overhead, reproducible without an API key.
|
|
20
|
+
- **[Cross-harness bench](CROSS-HARNESS-BENCH.md)** — the same store answered by six harnesses, graded on the store's own state.
|
|
21
|
+
- **[Performance](PERFORMANCE.md)** — what keeps a turn cheap, and what was tried and left out.
|
|
19
22
|
{: .card-grid }
|
data/docs/prompts/ADD-TOOL.md
CHANGED
|
@@ -30,6 +30,7 @@ with one turn. Nothing more.
|
|
|
30
30
|
| The need | The kind | Where it lives |
|
|
31
31
|
|---|---|---|
|
|
32
32
|
| Call an external HTTP API | **data tool** (`data_tool` in the DSL block) | a row in SQLite, editable at runtime |
|
|
33
|
+
| Show selected evidence cards | **presentation data tool** (`presentation`, no `request`) | the tool store; see [Tools](../TOOLS.md#presentation-tools-the-model-picks-ids-the-engine-shows-the-cards) |
|
|
33
34
|
| Logic must run in-process | **code tool** (a Ruby class `< RubyLLM::Tool`) | the deployment image |
|
|
34
35
|
| Adopt a whole external MCP server | **`mcp` instance** | durable config; its tools appear tagged `mcp:<name>` |
|
|
35
36
|
| Teach a procedure (no data fetching) | **skill** (`skill "name", description:, instructions:`) | loads on demand via `load_skill` |
|
|
@@ -78,6 +79,12 @@ RULES:
|
|
|
78
79
|
verbatim and arguments are checked against it at call time.
|
|
79
80
|
- Author the FINAL url: the HTTP client does not follow redirects, and the egress guard
|
|
80
81
|
cleared that host only.
|
|
82
|
+
- For a write that accepts catalog IDs, declare `requires_evidence` for those
|
|
83
|
+
parameters and ensure an allowed lookup declares `evidence`. Mark the write
|
|
84
|
+
`side_effect: true`; the engine serializes marked writes within a session.
|
|
85
|
+
- For presentation, follow the [card contract](../TOOLS.md#presentation-tools-the-model-picks-ids-the-engine-shows-the-cards):
|
|
86
|
+
pass known IDs to the presentation tool. Look up again if their cards are absent
|
|
87
|
+
from the session ledger or current information is needed.
|
|
81
88
|
- Do not add a second capability "while we're here".
|
|
82
89
|
|
|
83
90
|
## Step 3 — Make sure it enters the tool-loop
|
|
@@ -96,8 +103,11 @@ instead of fighting it.
|
|
|
96
103
|
## Step 4 — Prove it with ONE turn
|
|
97
104
|
|
|
98
105
|
Run one `reply()` whose message forces the call ("how many BRL is 1 USD right now?").
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
Inspect the trace to confirm the call and result; the reply alone is not proof.
|
|
107
|
+
For a gated write, also verify that an unknown ID is blocked before any backend
|
|
108
|
+
request. For presentation, verify the selected `insika.ui` items. Use
|
|
109
|
+
[snapshot eval assertions](../EVALS.md#graders--what-a-turns-calls-and-reply-are-checked-against)
|
|
110
|
+
when the flow needs a repeatable precondition.
|
|
101
111
|
|
|
102
112
|
## Step 5 — Self-check
|
|
103
113
|
|
|
@@ -43,6 +43,9 @@ text), roughly **when**, and expected vs. actual. Reproduce once locally if chea
|
|
|
43
43
|
| turn completed, customer got nothing | delivery is separate from the turn: check `channel_delivered` / `delivery_failed` | [Channels](../CHANNELS.md) |
|
|
44
44
|
| freshly created agent returns empty turns | persona overflows the default `context_budget` (8000) | [Context](../CONTEXT.md) |
|
|
45
45
|
| tool never called (or "missing") | not registered OR not allowed (`tools_allow`) | [Tools](../TOOLS.md) § troubleshooting |
|
|
46
|
+
| `tool_blocked` with `gate: provenance` | ID absent from session evidence; look up before retrying | [Tools](../TOOLS.md#provenance-checking-ids-before-a-write) |
|
|
47
|
+
| presentation shows no cards | inspect dropped IDs: `unknown`, `no_card`, `max`; a known ID may have no retained card | [API](../API.md#tool-and-presentation-sse-events) |
|
|
48
|
+
| cache reuse dropped | compare identity/tool-schema fingerprints with provider cache token accounting | [Context](../CONTEXT.md#the-provider-prefix-cache) |
|
|
46
49
|
| identical `tool_call` repeated, then abort | the `max_tool_repeat` loop guard | [Agents](../AGENTS.md) § limits |
|
|
47
50
|
| model gave up after one empty result | `tool_persistence` off (it is ON by default) | same |
|
|
48
51
|
| `turn_stuck` event | the agent declared it cannot proceed — escalation signal, not a bug | [Agents](../AGENTS.md) § stuck |
|
data/docs/prompts/GO-LIVE.md
CHANGED
|
@@ -87,7 +87,9 @@ In order, each with evidence:
|
|
|
87
87
|
|
|
88
88
|
1. `curl https://<host>/up` → `{"status":"ok"}`.
|
|
89
89
|
2. `bin/insika doctor` against the deployed volume (or via the platform's shell) —
|
|
90
|
-
relay its findings verbatim; fix errors before continuing.
|
|
90
|
+
relay its findings verbatim; fix errors before continuing. Check evidence sources
|
|
91
|
+
for gated writes/presentation, review the fencing warning, and leave
|
|
92
|
+
`evals.seeding` off after snapshot evals. See [Deploy](../DEPLOY.md#strict-config-and-insika-doctor).
|
|
91
93
|
3. One authenticated turn:
|
|
92
94
|
|
|
93
95
|
```bash
|