insika 0.2.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +325 -17
- data/README.md +52 -11
- data/bin/insika +895 -8
- data/bin/insika-router +87 -0
- data/docs/AGENTS.md +229 -163
- data/docs/API.md +56 -0
- data/docs/ARCHITECTURE.md +3 -2
- data/docs/ARTIFACTS.md +95 -0
- data/docs/BENCHMARK.md +2 -2
- data/docs/CHANNELS.md +111 -21
- data/docs/CONTEXT.md +132 -19
- data/docs/DEMO.md +80 -0
- data/docs/DEPLOY.md +75 -4
- data/docs/EMBEDDING.md +1 -1
- data/docs/EVALS.md +146 -2
- data/docs/FACTS.md +135 -0
- data/docs/HARVEST.md +116 -0
- data/docs/KNOWLEDGE.md +290 -0
- data/docs/LOADTEST.md +16 -10
- data/docs/MEDIA.md +128 -0
- data/docs/OBSERVABILITY.md +65 -6
- data/docs/OUTCOMES.md +137 -0
- data/docs/PLUGINS.md +51 -6
- data/docs/POLICY.md +216 -0
- data/docs/REFINEMENT.md +17 -12
- data/docs/RELEASING.md +27 -0
- data/docs/ROUTER.md +213 -0
- data/docs/RUNNING-LOCAL.md +6 -6
- data/docs/SCHEDULING.md +121 -0
- data/docs/SECURITY.md +79 -2
- data/docs/SKILLS.md +13 -4
- data/docs/SOAK.md +127 -0
- data/docs/TEMPLATES.md +134 -0
- data/docs/TOOLS.md +220 -27
- data/docs/WHY.md +1 -1
- data/docs/WORKFLOWS.md +2 -2
- data/docs/_includes/head_custom.html +5 -0
- data/docs/_includes/title.html +13 -0
- data/docs/_sass/color_schemes/insika.scss +32 -0
- data/docs/_sass/custom/custom.scss +199 -0
- data/docs/_sass/custom/setup.scss +26 -0
- data/docs/assets/img/favicon.svg +7 -0
- data/docs/assets/img/insika-mark.svg +7 -0
- data/docs/core-concepts.md +21 -0
- data/docs/domain.md +115 -0
- data/docs/improve.md +20 -0
- data/docs/index.md +9 -6
- data/docs/integrate.md +20 -0
- data/docs/operate.md +13 -6
- data/docs/prompts/ADD-TOOL.md +118 -0
- data/docs/prompts/DIAGNOSE-TURN.md +65 -0
- data/docs/prompts/GO-LIVE.md +138 -0
- data/docs/prompts/RUN-EXAMPLES.md +70 -0
- data/docs/reference.md +19 -0
- data/docs/ship.md +10 -2
- data/docs/start-here.md +18 -0
- data/lib/insika/agent_profile.rb +202 -10
- data/lib/insika/artifact_signing.rb +82 -0
- data/lib/insika/artifact_store.rb +160 -0
- data/lib/insika/balloon_splitter.rb +102 -0
- data/lib/insika/budget_ledger.rb +34 -3
- data/lib/insika/cache_series_store.rb +49 -0
- data/lib/insika/channel_delivery.rb +119 -14
- data/lib/insika/channels/relay.rb +77 -3
- data/lib/insika/channels/web.rb +2 -2
- data/lib/insika/chat_builder.rb +105 -2
- data/lib/insika/checkpoint_store.rb +16 -0
- data/lib/insika/commands/agent_payload.rb +3 -3
- data/lib/insika/commands/backfill_knowledge.rb +145 -0
- data/lib/insika/commands/cancel_followup.rb +49 -0
- data/lib/insika/commands/delete_artifact.rb +35 -0
- data/lib/insika/commands/delete_concept.rb +34 -0
- data/lib/insika/commands/delete_mcp.rb +6 -2
- data/lib/insika/commands/delete_tenant_data.rb +107 -0
- data/lib/insika/commands/export_customer_memory.rb +48 -0
- data/lib/insika/commands/forget_customer.rb +117 -0
- data/lib/insika/commands/freeze_funnel_baseline.rb +113 -0
- data/lib/insika/commands/gate_harvest.rb +138 -0
- data/lib/insika/commands/gate_refinement.rb +1 -1
- data/lib/insika/commands/judge_shadow_pairs.rb +124 -0
- data/lib/insika/commands/memory_forget_fact.rb +20 -4
- data/lib/insika/commands/memory_put_fact.rb +23 -4
- data/lib/insika/commands/promote_harvest.rb +130 -0
- data/lib/insika/commands/record_outcome.rb +46 -0
- data/lib/insika/commands/record_shadow_reply.rb +68 -0
- data/lib/insika/commands/refresh_mcp_tools.rb +47 -0
- data/lib/insika/commands/reject_harvest.rb +38 -0
- data/lib/insika/commands/resolve_proposal.rb +108 -0
- data/lib/insika/commands/restore_concept.rb +34 -0
- data/lib/insika/commands/revoke_contact.rb +49 -0
- data/lib/insika/commands/rollback_harvest.rb +86 -0
- data/lib/insika/commands/run_distillation.rb +186 -0
- data/lib/insika/commands/run_harvest.rb +393 -0
- data/lib/insika/commands/seed_demo_data.rb +31 -0
- data/lib/insika/commands/send_message.rb +103 -6
- data/lib/insika/commands/session_purge.rb +67 -0
- data/lib/insika/commands/upsert_mcp.rb +6 -3
- data/lib/insika/commands/write_concept.rb +57 -0
- data/lib/insika/contact_store.rb +183 -0
- data/lib/insika/context/builder.rb +21 -3
- data/lib/insika/context/fragment.rb +7 -3
- data/lib/insika/context/priority.rb +5 -0
- data/lib/insika/context/provider.rb +17 -3
- data/lib/insika/context/providers/briefing.rb +96 -0
- data/lib/insika/context/providers/knowledge.rb +108 -0
- data/lib/insika/context/providers/memory.rb +16 -7
- data/lib/insika/context/providers/prompt.rb +57 -23
- data/lib/insika/context/providers/skill.rb +2 -0
- data/lib/insika/context/providers/tool_search.rb +2 -0
- data/lib/insika/context_trace_store.rb +38 -2
- data/lib/insika/cron.rb +189 -0
- data/lib/insika/demo/agent_attrs.rb +43 -0
- data/lib/insika/demo/golden_cases.rb +81 -0
- data/lib/insika/demo/seeder.rb +336 -0
- data/lib/insika/distill.rb +224 -0
- data/lib/insika/distill_engine.rb +169 -0
- data/lib/insika/doctor.rb +889 -11
- data/lib/insika/dsl/definition.rb +3 -2
- data/lib/insika/dsl/runtime.rb +69 -82
- data/lib/insika/dsl/server_boot.rb +92 -1
- data/lib/insika/dsl/system.rb +10 -2
- data/lib/insika/dsl.rb +186 -5
- data/lib/insika/edge_limiter.rb +28 -7
- data/lib/insika/env_schema.rb +25 -3
- data/lib/insika/errors.rb +11 -0
- data/lib/insika/evals/assertions.rb +3 -2
- data/lib/insika/evals/golden.rb +41 -4
- data/lib/insika/evals/judge.rb +47 -2
- data/lib/insika/evals/pairwise.rb +29 -6
- data/lib/insika/evals/persona.rb +98 -0
- data/lib/insika/evals/runner.rb +9 -0
- data/lib/insika/evals/simulator.rb +225 -0
- data/lib/insika/evals/transport.rb +83 -1
- data/lib/insika/event_stream.rb +10 -0
- data/lib/insika/evidence.rb +183 -0
- data/lib/insika/executor.rb +869 -76
- data/lib/insika/followup_engine.rb +207 -0
- data/lib/insika/followup_policy.rb +198 -0
- data/lib/insika/followup_store.rb +306 -0
- data/lib/insika/funnel_declaration.rb +106 -0
- data/lib/insika/funnel_fold.rb +179 -0
- data/lib/insika/funnel_store.rb +163 -0
- data/lib/insika/golden_store.rb +17 -2
- data/lib/insika/grounding/matcher.rb +69 -0
- data/lib/insika/grounding.rb +44 -0
- data/lib/insika/harvest/conversion_gate.rb +159 -0
- data/lib/insika/harvest/criterion.rb +98 -0
- data/lib/insika/harvest/gate.rb +194 -0
- data/lib/insika/harvest/negative_list.rb +199 -0
- data/lib/insika/harvest.rb +241 -0
- data/lib/insika/harvest_engine.rb +193 -0
- data/lib/insika/harvest_store.rb +548 -0
- data/lib/insika/knowledge.rb +680 -0
- data/lib/insika/knowledge_store.rb +140 -0
- data/lib/insika/mcp_client.rb +94 -0
- data/lib/insika/mcp_json.rb +74 -0
- data/lib/insika/mcp_live_tool.rb +43 -0
- data/lib/insika/mcp_store.rb +98 -26
- data/lib/insika/mcp_tool_ingestor.rb +30 -8
- data/lib/insika/mcp_tool_registry.rb +100 -0
- data/lib/insika/media.rb +382 -0
- data/lib/insika/memory_audit_store.rb +85 -0
- data/lib/insika/memory_store.rb +264 -23
- data/lib/insika/message_origin.rb +7 -2
- data/lib/insika/middleware.rb +9 -0
- data/lib/insika/model_visible.rb +87 -0
- data/lib/insika/model_visible_trace_store.rb +66 -0
- data/lib/insika/onboarding.rb +22 -1
- data/lib/insika/outbox_store.rb +42 -4
- data/lib/insika/outcome_store.rb +147 -0
- data/lib/insika/overlay_tool_registry.rb +37 -17
- data/lib/insika/packaging.rb +163 -0
- data/lib/insika/parity/criterion.rb +79 -0
- data/lib/insika/parity/verdict.rb +318 -0
- data/lib/insika/prefix_fingerprint.rb +58 -0
- data/lib/insika/profile_source.rb +29 -1
- data/lib/insika/prompt_catalog.rb +10 -0
- data/lib/insika/proposal_store.rb +271 -0
- data/lib/insika/queue_policy.rb +4 -1
- data/lib/insika/refinement/proposer.rb +1 -1
- data/lib/insika/reliability.rb +32 -6
- data/lib/insika/retention.rb +316 -0
- data/lib/insika/router/app.rb +157 -0
- data/lib/insika/router/backend_pool.rb +98 -0
- data/lib/insika/router/hash_ring.rb +55 -0
- data/lib/insika/router/proxy_body.rb +34 -0
- data/lib/insika/router/session_key.rb +54 -0
- data/lib/insika/router.rb +18 -0
- data/lib/insika/routing.rb +101 -0
- data/lib/insika/safety/config.rb +44 -4
- data/lib/insika/safety/corpus.rb +255 -0
- data/lib/insika/safety/detectors.rb +32 -113
- data/lib/insika/safety/factory.rb +15 -2
- data/lib/insika/safety/grounding_enforcer.rb +59 -0
- data/lib/insika/safety/grounding_validator.rb +49 -0
- data/lib/insika/safety/input_guardrail.rb +1 -1
- data/lib/insika/safety/moderator.rb +1 -1
- data/lib/insika/safety/output_filter.rb +9 -5
- data/lib/insika/safety/output_validator.rb +11 -5
- data/lib/insika/schedule.rb +177 -0
- data/lib/insika/schedule_engine.rb +314 -0
- data/lib/insika/schedule_store.rb +208 -0
- data/lib/insika/schema_guard.rb +35 -0
- data/lib/insika/server/app.rb +247 -17
- data/lib/insika/server/rack_app.rb +21 -1
- data/lib/insika/server/responses.rb +40 -1
- data/lib/insika/session_actor.rb +8 -4
- data/lib/insika/session_store.rb +65 -2
- data/lib/insika/settings_store.rb +10 -0
- data/lib/insika/shadow_pair_store.rb +258 -0
- data/lib/insika/skill_catalog.rb +16 -0
- data/lib/insika/soak/envelope.rb +140 -0
- data/lib/insika/soak/report.rb +392 -0
- data/lib/insika/soak/runner.rb +554 -0
- data/lib/insika/steer_injector.rb +21 -10
- data/lib/insika/store.rb +10 -1
- data/lib/insika/stores/memory.rb +6 -0
- data/lib/insika/stores/sqlite.rb +8 -0
- data/lib/insika/studio/app.rb +1437 -54
- data/lib/insika/studio/assets/dist/application.css +1 -1
- data/lib/insika/studio/assets/dist/application.js +23 -23
- data/lib/insika/studio/assets/dist/favicon.svg +6 -0
- data/lib/insika/studio/forms.rb +308 -10
- data/lib/insika/studio/nav_icons.rb +19 -1
- data/lib/insika/studio/views/_agent_tab_cache.erb +25 -0
- data/lib/insika/studio/views/_agent_tab_config.erb +514 -0
- data/lib/insika/studio/views/_agent_tab_history.erb +24 -0
- data/lib/insika/studio/views/_agent_tab_loops.erb +54 -0
- data/lib/insika/studio/views/_agent_tab_memory.erb +51 -0
- data/lib/insika/studio/views/_agent_tab_outcomes.erb +31 -0
- data/lib/insika/studio/views/_agent_tab_prompts.erb +108 -0
- data/lib/insika/studio/views/_agent_tab_skills.erb +38 -0
- data/lib/insika/studio/views/_agents_master.erb +44 -0
- data/lib/insika/studio/views/_message.erb +49 -32
- data/lib/insika/studio/views/agent_detail.erb +62 -278
- data/lib/insika/studio/views/agents.erb +71 -54
- data/lib/insika/studio/views/approvals.erb +4 -1
- data/lib/insika/studio/views/artifact.erb +23 -0
- data/lib/insika/studio/views/artifacts.erb +59 -0
- data/lib/insika/studio/views/chats.erb +4 -1
- data/lib/insika/studio/views/customer.erb +94 -0
- data/lib/insika/studio/views/customers.erb +32 -0
- data/lib/insika/studio/views/evals.erb +6 -3
- data/lib/insika/studio/views/facts.erb +133 -0
- data/lib/insika/studio/views/followups.erb +125 -0
- data/lib/insika/studio/views/funnel.erb +106 -0
- data/lib/insika/studio/views/harvest.erb +234 -0
- data/lib/insika/studio/views/home.erb +106 -66
- data/lib/insika/studio/views/knowledge.erb +123 -0
- data/lib/insika/studio/views/layout.erb +15 -11
- data/lib/insika/studio/views/mcp.erb +174 -80
- data/lib/insika/studio/views/parity.erb +147 -0
- data/lib/insika/studio/views/playground.erb +7 -1
- data/lib/insika/studio/views/session.erb +233 -124
- data/lib/insika/studio/views/settings.erb +41 -1
- data/lib/insika/studio/views/skills.erb +1 -2
- data/lib/insika/studio/views/system_files.erb +1 -1
- data/lib/insika/studio/views/task.erb +13 -0
- data/lib/insika/studio/views/tasks.erb +4 -1
- data/lib/insika/studio/views/tools.erb +24 -10
- data/lib/insika/task_store.rb +21 -1
- data/lib/insika/templates/browser-agent/README.md +36 -0
- data/lib/insika/templates/browser-agent/agent.rb +49 -0
- data/lib/insika/templates/daily-digest/README.md +38 -0
- data/lib/insika/templates/daily-digest/agent.rb +77 -0
- data/lib/insika/templates/repo-explorer/README.md +36 -0
- data/lib/insika/templates/repo-explorer/agent.rb +45 -0
- data/lib/insika/templates/research-analyst/README.md +26 -0
- data/lib/insika/templates/research-analyst/agent.rb +58 -0
- data/lib/insika/templates/review-panel/README.md +20 -0
- data/lib/insika/templates/review-panel/agent.rb +50 -0
- data/lib/insika/templates/travel-planner/README.md +35 -0
- data/lib/insika/templates/travel-planner/agent.rb +87 -0
- data/lib/insika/templates.rb +112 -0
- data/lib/insika/testing/store_contract.rb +27 -6
- data/lib/insika/tick.rb +47 -11
- data/lib/insika/timezone.rb +45 -0
- data/lib/insika/tool_definition.rb +17 -7
- data/lib/insika/tool_envelope.rb +69 -0
- data/lib/insika/tool_manifest.rb +5 -1
- data/lib/insika/tools/data_defined_tool.rb +10 -0
- data/lib/insika/tools/generate_image.rb +89 -0
- data/lib/insika/tools/load_knowledge.rb +74 -0
- data/lib/insika/tools/run_persona_eval.rb +328 -0
- data/lib/insika/tools/save_artifact.rb +95 -0
- data/lib/insika/tools/schedule_followup.rb +164 -0
- data/lib/insika/tools/tts.rb +47 -0
- data/lib/insika/tools/update_briefing.rb +126 -0
- data/lib/insika/turn_output.rb +1 -1
- data/lib/insika/turn_state.rb +49 -1
- data/lib/insika/turn_timing.rb +22 -2
- data/lib/insika/version.rb +1 -1
- data/lib/insika/vitals.rb +84 -0
- data/lib/insika/wiring/graph.rb +472 -13
- data/lib/insika/wiring/graph_chat.rb +102 -0
- data/lib/insika.rb +149 -3
- metadata +181 -5
- data/docs/build.md +0 -14
- data/docs/understand.md +0 -10
data/docs/ARTIFACTS.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Artifacts
|
|
3
|
+
parent: Operate
|
|
4
|
+
nav_order: 3
|
|
5
|
+
permalink: /artifacts/
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Artifacts — a report the agent can hand you a URL to
|
|
9
|
+
|
|
10
|
+
A channel message is ephemeral, unformatted and capped. A scheduled report turn
|
|
11
|
+
(see [Schedules](SCHEDULING.md)) produces something no message can carry: a page
|
|
12
|
+
— tables, sections, inline charts. An **artifact** is that page as a thing that
|
|
13
|
+
exists afterwards: listable, linkable, and — because it carries customer PII —
|
|
14
|
+
deletable on the same terms as everything else the engine stores.
|
|
15
|
+
|
|
16
|
+
Deliberately small: a store, a tool, a route. Not a CMS.
|
|
17
|
+
|
|
18
|
+
## The `save_artifact` tool
|
|
19
|
+
|
|
20
|
+
A registry tool, allowlisted per agent like every tool — **the allowlist IS the
|
|
21
|
+
switch**:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
agent = Insika.agent("reporter") do
|
|
25
|
+
instructions "…"
|
|
26
|
+
tools_allow %w[save_artifact] # without this, the tool is not even offered
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The agent hands in `title` + `content` (default mime `text/html`; also
|
|
31
|
+
`text/markdown` and `image/svg+xml`) and gets the URL back, which it can include
|
|
32
|
+
in a channel message ("today's report: <url>"). When a signing key is
|
|
33
|
+
configured (below), the result also carries a `signed_url` that expires.
|
|
34
|
+
|
|
35
|
+
```jsonc
|
|
36
|
+
{ "id": "…", "url": "https://…/studio/artifacts/<id>/content",
|
|
37
|
+
"signed_url": "https://…/studio/artifacts/s/<id>?exp=…&sig=…" } // only with a key
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The tenant binding is **inherited, never chosen**: an artifact belongs to the
|
|
41
|
+
tenant of the agent that saved it — a binding of the tool instance, never a
|
|
42
|
+
parameter the model types. Store A's report can never appear in, or be linked
|
|
43
|
+
from, store B.
|
|
44
|
+
|
|
45
|
+
## Serving
|
|
46
|
+
|
|
47
|
+
- `GET /studio/artifacts` — the Studio's per-agent list (the listing IS the
|
|
48
|
+
history; no versioning — one report per run).
|
|
49
|
+
- `GET /studio/artifacts/:id` — the preview page, rendered **inside a sandboxed
|
|
50
|
+
iframe** (no scripts, no same-origin, no forms).
|
|
51
|
+
- `GET /studio/artifacts/:id/content` — the raw page (authenticated).
|
|
52
|
+
- `GET /studio/artifacts/s/:id?exp=…&sig=…` — the **signed link**: the only
|
|
53
|
+
artifact route that works without a Studio session. HMAC-SHA256 over
|
|
54
|
+
`(id, expiry)` with `INSIKA_ARTIFACT_SIGNING_KEY`, verified in constant time.
|
|
55
|
+
Expired or bad signatures **404 (never 403 — no oracle)**. Rotating the key
|
|
56
|
+
invalidates every outstanding link — the documented behavior, not a bug.
|
|
57
|
+
Without `INSIKA_ARTIFACT_SIGNING_KEY` there is no signed surface at all.
|
|
58
|
+
|
|
59
|
+
**Artifact content is untrusted.** It is LLM output. Both content routes send:
|
|
60
|
+
|
|
61
|
+
```http
|
|
62
|
+
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src data:
|
|
63
|
+
X-Content-Type-Options: nosniff
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
No script, no external fetch, no forms. The model writes HTML with **inline
|
|
67
|
+
SVG** for charts — that is a skill instruction (palette, tables, pure-SVG bars),
|
|
68
|
+
not engine code. A "real" charting need is a plugin.
|
|
69
|
+
|
|
70
|
+
## Limits and retention
|
|
71
|
+
|
|
72
|
+
- **Size cap** — `INSIKA_ARTIFACT_MAX_BYTES` (default 1 MB): an artifact is a
|
|
73
|
+
page, not an attachment. The mime allowlist is `text/html`, `text/markdown`,
|
|
74
|
+
`image/svg+xml`; no binaries, no uploads.
|
|
75
|
+
- **Expiry** — the settings key `artifact_ttl_days` (Integer days; absent = OFF)
|
|
76
|
+
ages artifacts out on the retention sweep's own daily pass, **independent of
|
|
77
|
+
`retention_days`**: a deployment that keeps its conversations forever must
|
|
78
|
+
still expire the reports. This is the guarantee that PII inside a report
|
|
79
|
+
expires — the honest reach, because no reader can see inside the opaque HTML.
|
|
80
|
+
|
|
81
|
+
## Privacy
|
|
82
|
+
|
|
83
|
+
- `delete_tenant_data` deletes the tenant's artifacts (the tenant binding is
|
|
84
|
+
the isolation boundary).
|
|
85
|
+
- `forget_customer` **cannot** know which artifacts mention a customer (content
|
|
86
|
+
is opaque HTML), so per-customer redaction inside a report is not pretended to
|
|
87
|
+
exist; the `artifact_ttl_days` knob is the guarantee that a report's PII
|
|
88
|
+
expires.
|
|
89
|
+
|
|
90
|
+
## See also
|
|
91
|
+
|
|
92
|
+
- [Schedules](SCHEDULING.md) — the recurring turns whose output lands here.
|
|
93
|
+
- [Tools](TOOLS.md) — how a tool enters the per-agent allowlist.
|
|
94
|
+
- [`examples/scheduled-report/`](https://github.com/guizaols/insika/tree/main/examples/scheduled-report/)
|
|
95
|
+
— schedule + skill + data tool + artifact, tenant-bound, end to end.
|
data/docs/BENCHMARK.md
CHANGED
data/docs/CHANNELS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Channels
|
|
3
|
-
parent:
|
|
4
|
-
nav_order:
|
|
3
|
+
parent: Integrate
|
|
4
|
+
nav_order: 2
|
|
5
5
|
permalink: /channels/
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -176,21 +176,24 @@ again.
|
|
|
176
176
|
## Relay or the drop-in API?
|
|
177
177
|
|
|
178
178
|
If you already own your messaging platform, you can reach the engine two ways: the
|
|
179
|
-
drop-in [`POST /v1/responses`](
|
|
179
|
+
drop-in [`POST /v1/responses`](ARCHITECTURE.md) — you hold an SSE connection for the
|
|
180
180
|
whole turn and read the answer off it — or the relay, where the engine acks in
|
|
181
181
|
milliseconds and POSTs the answer to you when it exists.
|
|
182
182
|
|
|
183
183
|
The instinct is that streaming gets the customer their reply sooner, and that the
|
|
184
|
-
relay trades that away. **
|
|
185
|
-
publishes `:content` as the ANSWER, whole, after the
|
|
186
|
-
([what crosses the edge](
|
|
187
|
-
stream carries tool activity; the text arrives in one piece at
|
|
188
|
-
a real store agent, the text frames span **0 ms** — there is
|
|
189
|
-
progressively,
|
|
184
|
+
relay trades that away. **For the default relay it does not, and the reason is
|
|
185
|
+
structural:** the engine publishes `:content` as the ANSWER, whole, after the
|
|
186
|
+
turn's hooks ([what crosses the edge](ARCHITECTURE.md#what-crosses-the-edge)).
|
|
187
|
+
During the turn the stream carries tool activity; the text arrives in one piece at
|
|
188
|
+
the end. Measured on a real store agent, the text frames span **0 ms** — there is
|
|
189
|
+
nothing to deliver progressively. That is the `:at_end` fact, true of `/v1/responses`
|
|
190
|
+
and of a relay that never opted into [progressive delivery](#delivery-policy).
|
|
191
|
+
The relay can opt out of it per channel — the opt-in is below, and it is the one
|
|
192
|
+
thing that changes the 0 ms span.
|
|
190
193
|
|
|
191
194
|
| | drop-in `/v1/responses` | relay |
|
|
192
195
|
|---|---|---|
|
|
193
|
-
| What the customer receives | one message, at the end | one message
|
|
196
|
+
| What the customer receives | one message, at the end | one message at the end (`:at_end`), or one WhatsApp balloon per paragraph with the first one as soon as the answer exists (`delivery: :progressive`) |
|
|
194
197
|
| Your app's request | held open for the whole turn (seconds) | acked in **milliseconds** |
|
|
195
198
|
| A turn that outlives your HTTP timeout | your problem | already handled — the answer arrives later |
|
|
196
199
|
| Retry on a failed handover | yours to build | the engine's outbox, bounded, at-most-once |
|
|
@@ -201,7 +204,7 @@ That last row is the one that cannot be had the other way. `/v1/responses` answe
|
|
|
201
204
|
the request it was given, so a message that arrives while a turn is running is a
|
|
202
205
|
second turn — the engine has no way to tell you "this joined the previous one". The
|
|
203
206
|
relay's `merged` / `steered` acks exist precisely to say that, which is why
|
|
204
|
-
[the inbound queue](
|
|
207
|
+
[the inbound queue](AGENTS.md#queue_mode--when-a-message-arrives-while-the-agent-is-busy)
|
|
205
208
|
is only reachable from here.
|
|
206
209
|
|
|
207
210
|
**Measured, so you can judge it rather than take our word:** same agent, same
|
|
@@ -279,18 +282,20 @@ answers, and **they are four different facts**:
|
|
|
279
282
|
answer two or three times.** That is the one contract mistake that is visible to
|
|
280
283
|
the end user, so it is worth a line of code: only deliver for a `202`.
|
|
281
284
|
|
|
282
|
-
`merged` and `steered` come from the [inbound queue](
|
|
285
|
+
`merged` and `steered` come from the [inbound queue](AGENTS.md#queue_mode--when-a-message-arrives-while-the-agent-is-busy)
|
|
283
286
|
(`limits[:queue_mode]`). They only ever occur if you turned that on for the agent;
|
|
284
287
|
with the default `followup` you will only see `202` and `duplicate`.
|
|
285
288
|
|
|
286
289
|
### Outbound
|
|
287
290
|
|
|
288
|
-
One POST per
|
|
291
|
+
One POST per **balloon**, to the URL you configured. For the default `:at_end`
|
|
292
|
+
delivery a turn emits exactly one; a progressive turn may emit several — `task_id`
|
|
293
|
+
is the correlation, `index` is the order.
|
|
289
294
|
|
|
290
295
|
```jsonc
|
|
291
296
|
POST <INSIKA_RELAY_DELIVER_URL>
|
|
292
297
|
Authorization: Bearer <INSIKA_RELAY_DELIVER_TOKEN> // omitted if unset
|
|
293
|
-
X-Insika-Delivery: 0f2c… // stable idempotency key
|
|
298
|
+
X-Insika-Delivery: 0f2c… // stable idempotency key, PER balloon
|
|
294
299
|
Content-Type: application/json
|
|
295
300
|
|
|
296
301
|
{
|
|
@@ -307,9 +312,49 @@ short backoff, and then the delivery is marked `failed` and stops.
|
|
|
307
312
|
`content` is the turn's **answer** — one message, whole. The model's narration on
|
|
308
313
|
the way to an answer ("vou verificar o cardápio…") does not come through here; it
|
|
309
314
|
stays internal unless the agent opts in. That contract is
|
|
310
|
-
[the edge contract](
|
|
315
|
+
[the edge contract](ARCHITECTURE.md#what-crosses-the-edge), and it is why you can
|
|
311
316
|
forward `content` straight to the customer.
|
|
312
317
|
|
|
318
|
+
### Delivery policy
|
|
319
|
+
|
|
320
|
+
A relay declares **how the outbox flushes** — a property of the surface
|
|
321
|
+
(WhatsApp balloons), not of the model:
|
|
322
|
+
|
|
323
|
+
- **`:at_end`** (the default, also `INSIKA_RELAY_DELIVERY=at_end` or unset): one
|
|
324
|
+
POST at the end with the whole answer. Byte-identical to the contract above.
|
|
325
|
+
- **`:progressive`** (`INSIKA_RELAY_DELIVERY=progressive`): the answer is split
|
|
326
|
+
into balloons at paragraph boundaries (a soft 600-char cap splits a single long
|
|
327
|
+
paragraph on sentences; fenced code blocks are atomic), and each balloon is its
|
|
328
|
+
own POST, in order, starting as soon as the answer exists. The consumer sends
|
|
329
|
+
each POST as its own platform message.
|
|
330
|
+
|
|
331
|
+
A progressive POST carries two additive fields **only when the turn split into
|
|
332
|
+
more than one balloon** — a one-balloon progressive turn is indistinguishable
|
|
333
|
+
from `:at_end` on the wire:
|
|
334
|
+
|
|
335
|
+
```jsonc
|
|
336
|
+
{
|
|
337
|
+
"external_id": "5511999998888",
|
|
338
|
+
"session_id": "relay:5511999998888",
|
|
339
|
+
"task_id": "…",
|
|
340
|
+
"content": "Seu pedido saiu para entrega hoje",
|
|
341
|
+
"index": 0, // this balloon's position, 0-based
|
|
342
|
+
"final": false // true on the LAST balloon of this task_id
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
When `index`/`final` are present, this POST is **one balloon of several for the
|
|
347
|
+
same `task_id`** — forward `content` as its own platform message and honor
|
|
348
|
+
`X-Insika-Delivery` per balloon. If you only forward `content` and ignore the new
|
|
349
|
+
fields, progressive turns still read as N messages in arrival order (which is
|
|
350
|
+
index order — the engine dispatches the chain sequentially); single-balloon turns
|
|
351
|
+
behave exactly as before. A consumer that ignores unknown keys keeps working.
|
|
352
|
+
|
|
353
|
+
The engine measures the win in-process: every channel turn records
|
|
354
|
+
`first_balloon_ms` (inbound receipt → first outbox flush) on the task record and
|
|
355
|
+
the terminal event, so the Studio task page shows whether the 2 s target is being
|
|
356
|
+
hit without toggling any flag.
|
|
357
|
+
|
|
313
358
|
### Deduplication
|
|
314
359
|
|
|
315
360
|
Send `event_id` and a retried webhook costs you nothing: the engine recognizes the
|
|
@@ -334,7 +379,7 @@ your side: you already have the customer's conversation, and `GET /v1/tasks/:id`
|
|
|
334
379
|
tells you the turn's terminal state.
|
|
335
380
|
|
|
336
381
|
A turn that **failed** delivers nothing — an error string is not an answer. Watch
|
|
337
|
-
`GET /v1/tasks/:id` or the [event stream](
|
|
382
|
+
`GET /v1/tasks/:id` or the [event stream](OBSERVABILITY.md) for those.
|
|
338
383
|
|
|
339
384
|
## Setting up the relay
|
|
340
385
|
|
|
@@ -344,6 +389,7 @@ Three environment variables on the engine:
|
|
|
344
389
|
INSIKA_RELAY_TOKEN=<a long random secret> # the switch AND the credential
|
|
345
390
|
INSIKA_RELAY_DELIVER_URL=https://you.example/insika/deliver
|
|
346
391
|
INSIKA_RELAY_DELIVER_TOKEN=<another secret> # optional; what we send to you
|
|
392
|
+
INSIKA_RELAY_DELIVERY=progressive # optional; "at_end" (the default) = one POST
|
|
347
393
|
```
|
|
348
394
|
|
|
349
395
|
`INSIKA_RELAY_TOKEN` is the switch: without it the channel is not mounted and
|
|
@@ -351,7 +397,7 @@ INSIKA_RELAY_DELIVER_TOKEN=<another secret> # optional; what we send to you
|
|
|
351
397
|
without a credential — a public inbound route with an LLM behind it is a money
|
|
352
398
|
faucet, so it fails closed by construction.
|
|
353
399
|
|
|
354
|
-
The delivery POST goes through the same [egress guard](
|
|
400
|
+
The delivery POST goes through the same [egress guard](SECURITY.md#egress-the-ssrf-boundary) as
|
|
355
401
|
data-tools: **https only**, and private/loopback destinations blocked. For local
|
|
356
402
|
development, where your consumer is on `localhost`:
|
|
357
403
|
|
|
@@ -367,6 +413,50 @@ nothing at all).
|
|
|
367
413
|
A runnable consumer in ~40 lines lives in
|
|
368
414
|
[`examples/relay-channel/`](https://github.com/guizaols/insika/tree/main/examples/relay-channel).
|
|
369
415
|
|
|
416
|
+
## Shadow mode
|
|
417
|
+
|
|
418
|
+
Shadow mode lets one channel run every turn **end to end and deliver
|
|
419
|
+
nothing** — the experiment that answers "can we replace the incumbent?" before
|
|
420
|
+
any customer is handed over. The incumbent keeps answering; the engine records
|
|
421
|
+
what it *would* have answered, and the two replies are judged pairwise against a
|
|
422
|
+
**frozen criterion** — the file `INSIKA_PARITY_CRITERION` points at.
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
INSIKA_RELAY_SHADOW=1 # the switch
|
|
426
|
+
# INSIKA_PARITY_CRITERION — required in shadow mode
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Three things change when it is on:
|
|
430
|
+
|
|
431
|
+
- The turn still runs; the reply is recorded as a **pair** and never reaches the
|
|
432
|
+
customer. Zero outbox records, ever — and `Relay#deliver` refuses loudly if one
|
|
433
|
+
somehow exists.
|
|
434
|
+
- The inbound ack becomes `200 {"task_id": …, "shadow": true}` instead of `202`,
|
|
435
|
+
so a consumer wired to "202 means a reply is coming" cannot be misled.
|
|
436
|
+
- `event_id` becomes **required** — it is the correlation key both halves of the
|
|
437
|
+
pair are built from.
|
|
438
|
+
|
|
439
|
+
The incumbent's reply enters the same pair through one of two shapes: alongside
|
|
440
|
+
the mirror call itself (`"incumbent_reply": "…"` on `POST /channels/relay/events`),
|
|
441
|
+
or as a follow-up when the consumer answers first:
|
|
442
|
+
|
|
443
|
+
```jsonc
|
|
444
|
+
POST /channels/relay/shadow-reply
|
|
445
|
+
Authorization: Bearer <INSIKA_RELAY_TOKEN>
|
|
446
|
+
{ "external_id": "5511999998888", "event_id": "wamid.HBg…",
|
|
447
|
+
"reply": "Claro! Me passa o número do pedido?", "at": "2026-…Z" }
|
|
448
|
+
→ 202 { "pair_id": "9f2c…", "status": "open" }
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Both shapes land in one command; a retried reply is ignored (first write wins —
|
|
452
|
+
the customer received one reply, and a retry must not rewrite evidence).
|
|
453
|
+
|
|
454
|
+
**No criterion, no shadow.** Boot refuses when shadow is on and
|
|
455
|
+
the criterion file is missing or unparseable — a number nobody pre-registered
|
|
456
|
+
does not count. The Studio's Parity page folds the running verdict on demand
|
|
457
|
+
from the pair store; `insika doctor` reports the shadow configuration before
|
|
458
|
+
boot does.
|
|
459
|
+
|
|
370
460
|
## Sessions
|
|
371
461
|
|
|
372
462
|
The engine mints the session id: `relay:<your external_id>`. Namespacing is not
|
|
@@ -380,7 +470,7 @@ redirect its own conversation.
|
|
|
380
470
|
## Writing your own channel
|
|
381
471
|
|
|
382
472
|
A channel is a plain object — no base class. Register it from a plugin
|
|
383
|
-
(see [Plugins](
|
|
473
|
+
(see [Plugins](PLUGINS.md)) with `contracts: { channels: [<id>] }` in the manifest,
|
|
384
474
|
and it mounts under `/channels/<id>/`.
|
|
385
475
|
|
|
386
476
|
Two members are always there; the rest of the object decides which shape you get.
|
|
@@ -446,8 +536,8 @@ command carries `transport: "channel:<id>"`.
|
|
|
446
536
|
|
|
447
537
|
## See also
|
|
448
538
|
|
|
449
|
-
- [Security](
|
|
539
|
+
- [Security](SECURITY.md) — the tokens, the egress guard, and why the rate limit
|
|
450
540
|
matters for anything public.
|
|
451
|
-
- [Agents](
|
|
541
|
+
- [Agents](AGENTS.md) — `limits[:queue_mode]`, which is what produces `merged` and
|
|
452
542
|
`steered`.
|
|
453
|
-
- [Observability](
|
|
543
|
+
- [Observability](OBSERVABILITY.md) — the event stream and OpenTelemetry.
|
data/docs/CONTEXT.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Context
|
|
3
|
-
parent:
|
|
4
|
-
nav_order:
|
|
3
|
+
parent: Core concepts
|
|
4
|
+
nav_order: 5
|
|
5
5
|
permalink: /context/
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -20,26 +20,30 @@ Providers are chosen by a double gate (the provider opts in for the profile **an
|
|
|
20
20
|
the agent's `context_providers` allowlist permits it), then assembled by priority
|
|
21
21
|
into a deterministic prompt:
|
|
22
22
|
|
|
23
|
-
| Provider | Block | Priority | Notes |
|
|
24
|
-
|
|
25
|
-
| **Identity** | system | **100 — pinned** | The agent's prompt files (global system files first). Never cut. |
|
|
26
|
-
| **
|
|
27
|
-
| **
|
|
28
|
-
| **
|
|
29
|
-
| **
|
|
30
|
-
| **
|
|
31
|
-
| **
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
| Provider | Block | Priority | Layer | Notes |
|
|
24
|
+
|----------|-------|:--------:|-------|-------|
|
|
25
|
+
| **Identity** | system | **100 — pinned** | identity | The agent's prompt files (global system files first). Never cut. |
|
|
26
|
+
| **Skills** | `<available_skills>` | 80 | identity | Level-1 skill list, minus whatever is already eager — see [Skills](SKILLS.md). |
|
|
27
|
+
| **Tool search** | `<available_tools>` | 70 | identity | Level-1 list of deferred tools — see [Tools](TOOLS.md). |
|
|
28
|
+
| **Skill trigger** | `<active_skill>` | 85 | volatile | Level-2 bodies: the agent's `skills_eager` set, plus the ones whose `triggers:` match the message — see [Skills](SKILLS.md). |
|
|
29
|
+
| **Knowledge** | `<knowledge>` | 77 | volatile | Level-1 top-K learned concepts for the turn's message (+ one-hop `[[links]]`), only if `knowledge.retrieve` is on. Cuttable — see [Knowledge](KNOWLEDGE.md). |
|
|
30
|
+
| **Memory** | `<memory>` | 75 | volatile | Durable facts + recent notes, only if `memory` is on. Cuttable. |
|
|
31
|
+
| **Briefing** | `<briefing>` | 65 | volatile | The session's working state (known fields, still-missing list, next step) — only if the pack declared `briefing_fields`. Cuttable. |
|
|
32
|
+
| **Session** | history | 60–79 | volatile | The running transcript; priority scales with recency. |
|
|
33
|
+
| **Request** | `<request_context>` | 40 | volatile | Turn variables + tenant. Most cuttable; sits last. |
|
|
34
|
+
|
|
35
|
+
The ordering is deliberate: the render order is **identity layer first, volatile
|
|
36
|
+
layer after** — nothing volatile can sit above the cache boundary, whatever its
|
|
37
|
+
priority — and within each layer the priority sort above holds. That keeps the
|
|
38
|
+
cacheable prefix byte-stable (see the prefix cache below).
|
|
36
39
|
|
|
37
40
|
## Budget and eviction — the actual "compaction"
|
|
38
41
|
|
|
39
42
|
- The cap is `profile.limits[:context_budget]`, **default 8000 tokens**.
|
|
40
43
|
- To fit the budget, the builder cuts **non-pinned** fragments
|
|
41
44
|
lowest-priority-first (ties broken by oldest history first). Under pressure you
|
|
42
|
-
lose
|
|
45
|
+
lose request context first, then old history, then briefing, then memory, then
|
|
46
|
+
learned knowledge, then the skill/tool-search level-1 lists — **the pinned
|
|
43
47
|
identity is never truncated**.
|
|
44
48
|
- A **pinned** fragment (the identity) that *alone* exceeds the budget raises an
|
|
45
49
|
error — the turn fails rather than shipping a truncated identity.
|
|
@@ -78,19 +82,82 @@ budget first.
|
|
|
78
82
|
With `memory` enabled, an agent gains a built-in `remember` tool for durable
|
|
79
83
|
facts, and those facts (plus recent notes) are injected back into the prompt on
|
|
80
84
|
later turns — **including turns in a different session**. Memory is scoped per
|
|
81
|
-
agent
|
|
85
|
+
agent, per `(tenant, customer)` when the message carries a `customer`, and per
|
|
86
|
+
session otherwise — a session's own memory lives in a marked `memory:chat:<session id>`
|
|
87
|
+
cell, never a bare one, so the Customers drill cannot read a conversation as a
|
|
88
|
+
customer. This is distinct from *session history*, which is the transcript of one
|
|
82
89
|
conversation; memory is the small set of facts that should outlive any single
|
|
83
90
|
conversation. Facts and notes are editable from the Studio agent page. See
|
|
84
91
|
[`examples/memory/`](https://github.com/guizaols/insika/tree/main/examples/memory/) for a runnable cross-session example.
|
|
85
92
|
|
|
93
|
+
Facts carry **provenance metadata**: every fact record stores `origin`
|
|
94
|
+
(who wrote it — `"engine"`, `"operator"`, `"legacy"` or `"distilled"`),
|
|
95
|
+
`created_at` / `updated_at` timestamps, and an optional `expires_at` (ISO8601) —
|
|
96
|
+
**an expired fact is never injected**, even before the daily sweep prunes it. The
|
|
97
|
+
Studio Customers drill reads and edits the same cell the next turn reads (injection
|
|
98
|
+
unchanged), and every operator mutation lands in the content-free audit trail
|
|
99
|
+
(digests, never values). The sweep honors the `memory_ttl_days` setting on its own
|
|
100
|
+
knob — see [Security](SECURITY.md#memory-and-the-right-to-be-forgotten-lgpd).
|
|
101
|
+
|
|
102
|
+
An **approved distilled fact** (see [Facts](FACTS.md)) lands in the
|
|
103
|
+
same cell this provider injects, stamped `distilled:<session_ref>` — approved on
|
|
104
|
+
the Studio Facts page, never applied automatically.
|
|
105
|
+
|
|
106
|
+
## Briefing — the session's working state
|
|
107
|
+
|
|
108
|
+
The **briefing** is the per-conversation working state the agent keeps and asks
|
|
109
|
+
for: which facts it already learned (size, budget, delivery day) and the agreed
|
|
110
|
+
next step. It is **engine-owned data** — one `"briefing"` key on the session
|
|
111
|
+
record, written only by the agent through tools — whose *fields* come from the
|
|
112
|
+
pack:
|
|
113
|
+
|
|
114
|
+
```jsonc
|
|
115
|
+
// pack agent.config.json
|
|
116
|
+
{ "id": "store-support", "briefing_fields": ["size", "budget", "delivery_day"] }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
# or the DSL — [] = the feature is off (no block, no tools)
|
|
121
|
+
briefing_fields "size", "budget", "delivery_day"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
With fields declared, the turn's `:system` context gains a `<briefing>` block
|
|
125
|
+
(priority 65 — below identity/skill/memory so it never breaks the cacheable
|
|
126
|
+
prefix, above the turn's own `<request_context>`):
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
<briefing>
|
|
130
|
+
known:
|
|
131
|
+
size: M
|
|
132
|
+
still missing: budget, delivery_day
|
|
133
|
+
next step: send the payment link tomorrow at 10
|
|
134
|
+
</briefing>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The `still missing` list is the point: the *model* sees which declared fields are
|
|
138
|
+
still unanswered, so it stops re-asking for something already given. Stored keys
|
|
139
|
+
that the pack no longer declares are never rendered. The Studio session screen
|
|
140
|
+
shows the persisted state (known fields + next step), read-only.
|
|
141
|
+
|
|
142
|
+
The agent writes the briefing through two built-in tools, wired only when the
|
|
143
|
+
pack declared fields:
|
|
144
|
+
|
|
145
|
+
- `update_briefing(field:, value:)` — records a field. An undeclared `field`
|
|
146
|
+
returns an envelope error (`unknown field '…'; declared: …`) and nothing is
|
|
147
|
+
persisted; a blank `value` clears the field.
|
|
148
|
+
- `set_next_step(text:)` — records the agreed next step; a blank `text` clears it.
|
|
149
|
+
|
|
150
|
+
Both are deterministic in-process writes (never enveloped) and survive across
|
|
151
|
+
turns and resumes — a resumed conversation re-opens with the briefing intact.
|
|
152
|
+
|
|
86
153
|
## The provider prefix cache
|
|
87
154
|
|
|
88
155
|
Two distinct caching mechanisms — don't conflate them:
|
|
89
156
|
|
|
90
157
|
- **Automatic server-side prefix cache.** Some providers prefix-cache a stable
|
|
91
158
|
system prefix automatically, at no cost to configure. This works **only because**
|
|
92
|
-
the
|
|
93
|
-
|
|
159
|
+
the engine renders the system in two layers (below) and the volatile half sits
|
|
160
|
+
**under** the identity boundary, keeping the cacheable prefix byte-stable.
|
|
94
161
|
Anything that injects volatile content high in the system block breaks the cache.
|
|
95
162
|
- **Manual cache breakpoints (opt-in).** With `prompt_caching` on **and** a
|
|
96
163
|
provider that supports explicit cache control, the builder sets one cache
|
|
@@ -100,6 +167,52 @@ Two distinct caching mechanisms — don't conflate them:
|
|
|
100
167
|
Cache accounting surfaces as `cached_tokens` (reads) and `cache_creation_tokens`
|
|
101
168
|
(writes), visible in telemetry and the Studio tokens chip.
|
|
102
169
|
|
|
170
|
+
### The two layers
|
|
171
|
+
|
|
172
|
+
The system block is partitioned into two cache layers:
|
|
173
|
+
|
|
174
|
+
- **Identity** — bytes that change only on deploy/config edit: the persona
|
|
175
|
+
prompt (`Prompt`), the level-1 skill list (`Skill`) and the deferred-tool
|
|
176
|
+
catalog (`ToolSearch`). This is the cacheable prefix.
|
|
177
|
+
- **Volatile** — bytes that may change per turn: memory, session history,
|
|
178
|
+
triggered skill bodies, the `<request_context>`. Everything else.
|
|
179
|
+
|
|
180
|
+
The layer is a **provider-class contract**, not profile data: `ContextProvider`
|
|
181
|
+
declares `def layer = :volatile` (conservative — nothing gets pinned by
|
|
182
|
+
accident) and the three identity builtins override to `:identity`. A pack does
|
|
183
|
+
not set it — a pack reorganizes *which content goes into the Prompt provider vs
|
|
184
|
+
the volatile providers*. The Builder stamps the layer on every fragment at
|
|
185
|
+
production, and the render order is **identity first, volatile after** — a
|
|
186
|
+
volatile block can never land above the cache boundary, whatever its priority.
|
|
187
|
+
Within each partition the existing priority sort is untouched.
|
|
188
|
+
|
|
189
|
+
The engine's own `doctor` check verifies the declaration: an engine-known
|
|
190
|
+
volatile provider (Memory, Session, Request, SkillTrigger) that overrides to
|
|
191
|
+
`:identity` is an **error** (guaranteed cache kill); any other custom
|
|
192
|
+
`:identity` provider is a **warning** (purity unverifiable from outside — the
|
|
193
|
+
output must be byte-stable across turns).
|
|
194
|
+
|
|
195
|
+
### The observable cache: fingerprints and the invalidation reason
|
|
196
|
+
|
|
197
|
+
Each turn, the Executor hashes the rendered prefix into a PII-free fingerprint
|
|
198
|
+
chain — one SHA-256 per system category in render order, one for the tool
|
|
199
|
+
schemas, one cumulative `prefix` — and compares it against the previous turn's
|
|
200
|
+
entry. The **invalidation reason** is the first category whose bytes changed (or
|
|
201
|
+
vanished); a turn whose prefix held reports nothing. History is deliberately
|
|
202
|
+
excluded: a new user message is a divergence every turn, which would be noise,
|
|
203
|
+
not a reason.
|
|
204
|
+
|
|
205
|
+
The Studio surfaces it in two places: the **session Context card** shows the
|
|
206
|
+
turn's cache-hit percentage and the `broke: <category>` line (plus the
|
|
207
|
+
`identity` marker on the category rows), and the **agent detail** carries a
|
|
208
|
+
cache tab with the per-agent hit series over time. The per-agent series lives
|
|
209
|
+
in its own capped store, because a session does not stamp its author — the
|
|
210
|
+
per-session trace cannot answer "cache-hit over time for *this* agent".
|
|
211
|
+
|
|
212
|
+
With the prefix stable by construction, the existing `prompt_caching` breakpoint
|
|
213
|
+
sits on bytes that stay put — the first (write) turn of a deployment pays the
|
|
214
|
+
cache write once, every subsequent turn reads.
|
|
215
|
+
|
|
103
216
|
## The volume
|
|
104
217
|
|
|
105
218
|
Agents, prompts, skills, and tools are **data in SQLite**, not files on a volume.
|
data/docs/DEMO.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Demo data
|
|
3
|
+
parent: Start here
|
|
4
|
+
nav_order: 4
|
|
5
|
+
permalink: /demo/
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Demo data — see every loop working at once
|
|
9
|
+
|
|
10
|
+
Most of what makes Insika worth looking at only shows up after data has
|
|
11
|
+
accumulated: a [funnel](OUTCOMES.md#the-outcome-funnel) with a frozen
|
|
12
|
+
baseline needs weeks of folded outcomes, a [refinement](REFINEMENT.md)
|
|
13
|
+
proposal needs a run that actually found something, an
|
|
14
|
+
[approval](POLICY.md#layer-2-policies-and-approvals) needs a tool call
|
|
15
|
+
someone is waiting on. A fresh instance shows none of that — every one of
|
|
16
|
+
those pages renders its empty state, which makes it hard to tell "nothing
|
|
17
|
+
happened yet" from "this doesn't work."
|
|
18
|
+
|
|
19
|
+
`insika demo:seed` closes that gap in one shot: it provisions a single
|
|
20
|
+
fictional agent (`demo-store`, an e-commerce support bot) and writes enough
|
|
21
|
+
realistic-looking data to see every loop at once.
|
|
22
|
+
|
|
23
|
+
## What it creates
|
|
24
|
+
|
|
25
|
+
| Page | What you'll see |
|
|
26
|
+
|------|------------------|
|
|
27
|
+
| [Funnel](OUTCOMES.md#the-outcome-funnel) — `/studio/funnel?agent=demo-store` | 40 days of folded outcomes (`greeted → browsing → cart_started → checkout_started → purchased`) and a **frozen baseline** |
|
|
28
|
+
| [Follow-ups](OUTCOMES.md#follow-ups--the-seller-who-comes-back) — `/studio/followups?agent=demo-store` | one record in each state: `pending`, `fired` (one per arm, so the A/B card has something to compare), `cancelled`, `blocked` |
|
|
29
|
+
| [Refinement](REFINEMENT.md) — `/studio/refinement?agent=demo-store` | four runs across the lifecycle: `awaiting_approval`, `applied`, `rejected` (gate failed), `no_findings` |
|
|
30
|
+
| [Approvals](POLICY.md#layer-2-policies-and-approvals) — `/studio/approvals` | two pending tool calls waiting on a human, one already resolved |
|
|
31
|
+
| [Facts](FACTS.md) — `/studio/facts` | three distillation proposals (`pending`, `approved` — with the resulting memory fact, `rejected`) |
|
|
32
|
+
| [Evals](EVALS.md) — `/studio/evals?agent=demo-store` | six golden cases and a baseline run with a mix of passes and one failure |
|
|
33
|
+
|
|
34
|
+
Every record is written through the same store APIs a real turn would use
|
|
35
|
+
(`OutcomeStore#create` + the funnel fold, `FollowupStore#create` + its
|
|
36
|
+
transitions, and so on) — there is no bulk-insert shortcut, and no bundled
|
|
37
|
+
`.rb` script outside `lib/` (nothing here needs a checkout; it ships in the
|
|
38
|
+
gem).
|
|
39
|
+
|
|
40
|
+
## Running it
|
|
41
|
+
|
|
42
|
+
From the CLI, against whichever store the rest of your commands already use
|
|
43
|
+
([Running locally](RUNNING-LOCAL.md#variables-all-optional) — `INSIKA_DB`
|
|
44
|
+
unset means an ephemeral, in-memory store, which is a fine place to try this):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
insika demo:seed
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A second run is a safe no-op once `demo-store` exists; pass `--force` to seed
|
|
51
|
+
another batch on top (the funnel baseline recomputes cleanly, but follow-ups,
|
|
52
|
+
refinement runs, approvals, proposals and goldens accumulate rather than
|
|
53
|
+
reset — none of those stores expose a per-agent bulk-delete that a shared
|
|
54
|
+
"platform" tenant could call without risking another agent's data).
|
|
55
|
+
|
|
56
|
+
From the Studio, open **Settings → Demo data** and click **Seed demo data**.
|
|
57
|
+
It dispatches the exact same command the CLI runs — the Studio never writes a
|
|
58
|
+
store directly, here or anywhere else.
|
|
59
|
+
|
|
60
|
+
**This writes into whatever store the running instance already has open.**
|
|
61
|
+
There's no separate demo database and no isolation: point `INSIKA_DB` at a
|
|
62
|
+
scratch file (or leave it unset, for an ephemeral store) before seeding —
|
|
63
|
+
never at a deployment holding real tenant data.
|
|
64
|
+
|
|
65
|
+
## Then look around
|
|
66
|
+
|
|
67
|
+
Once seeded, the CLI prints the same six paths listed above. If Studio isn't
|
|
68
|
+
running yet, boot it the way you already do — see [Running
|
|
69
|
+
locally](RUNNING-LOCAL.md#boot) for a checkout, or [Embedding](EMBEDDING.md)
|
|
70
|
+
for `Insika.agent { … }.serve` — pointed at the same `INSIKA_DB`. Studio reads
|
|
71
|
+
every agent's profile from the same config store, so it will show
|
|
72
|
+
`demo-store` next to whatever agent you're actually building, no matter which
|
|
73
|
+
one the running process itself defines.
|
|
74
|
+
|
|
75
|
+
## See also
|
|
76
|
+
|
|
77
|
+
- [Refinement](REFINEMENT.md), [Facts](FACTS.md), [Evals](EVALS.md) — what
|
|
78
|
+
each seeded page actually means.
|
|
79
|
+
- [Running locally](RUNNING-LOCAL.md) — booting Studio against a durable
|
|
80
|
+
`INSIKA_DB`.
|