insika 0.7.0 → 0.8.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 +116 -0
- data/README.md +4 -3
- data/bin/insika +43 -2
- data/docs/AGENTS.md +22 -3
- data/docs/ARTIFACTS.md +42 -0
- data/docs/CONTEXT.md +54 -12
- data/docs/DEPLOY.md +16 -7
- data/docs/LOADTEST.md +15 -27
- data/docs/MEDIA.md +1 -1
- data/docs/OBSERVABILITY.md +31 -2
- data/docs/POLICY.md +10 -4
- data/docs/RUNNING-LOCAL.md +2 -2
- data/docs/SECURITY.md +1 -1
- data/docs/SOAK.md +1 -1
- data/docs/TOOLS.md +24 -0
- data/docs/prompts/GO-LIVE.md +3 -3
- data/lib/insika/agent_profile.rb +26 -1
- data/lib/insika/chat_builder.rb +28 -17
- data/lib/insika/compaction.rb +196 -0
- data/lib/insika/context/builder.rb +6 -2
- data/lib/insika/context/fragment.rb +4 -1
- data/lib/insika/context/priority.rb +6 -0
- data/lib/insika/context/providers/briefing.rb +53 -24
- data/lib/insika/context/providers/session.rb +46 -10
- data/lib/insika/context_trace_store.rb +11 -1
- data/lib/insika/doctor.rb +105 -10
- data/lib/insika/dsl/runtime.rb +4 -0
- data/lib/insika/env_schema.rb +5 -6
- data/lib/insika/evals/transport.rb +1 -1
- data/lib/insika/executor.rb +64 -0
- data/lib/insika/loop_detector.rb +5 -34
- data/lib/insika/profile_source.rb +7 -0
- data/lib/insika/server/responses.rb +4 -4
- data/lib/insika/session_store.rb +34 -4
- data/lib/insika/settings_store.rb +8 -1
- data/lib/insika/soak/runner.rb +4 -4
- data/lib/insika/studio/app.rb +28 -6
- data/lib/insika/studio/forms.rb +11 -0
- data/lib/insika/studio/views/settings.erb +11 -0
- data/lib/insika/telemetry/recorder.rb +49 -1
- data/lib/insika/templates/daily-digest/README.md +9 -0
- data/lib/insika/templates/research-analyst/agent.rb +10 -0
- data/lib/insika/tool_batch.rb +67 -0
- data/lib/insika/tool_usage_report.rb +162 -0
- data/lib/insika/turn_budget.rb +91 -0
- data/lib/insika/version.rb +1 -1
- data/lib/insika.rb +7 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 345e57c18a277e8ecd49b2481300c1474c60b2e0c3c384a086740e0de5ec3a2e
|
|
4
|
+
data.tar.gz: 800794431e279fec34f135e4e203a0488b04066b667bc691806ea5f575d81530
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 199cc16b9ea651ec2da1d2e6f7ddea5020194882da00cf96981e8d5d44086233862873d14640d11cd1d445f9d202992ed46691210f9fb1699b1a3d56a99b3145
|
|
7
|
+
data.tar.gz: 44d24bc53036da3803ee6eba6203d1addc8ccf0bd27c1aa15d1e9acfe763bca807ede55c2b7136ee2c245e5cd725541c719baa018c99e09638e964803bf688a9
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,122 @@ it is released. Entries land with the pull request that makes the change.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.8.0] - 2026-08-31
|
|
12
|
+
|
|
13
|
+
The post-consolidation hardening wave: the last two OpenClaw-era names are gone
|
|
14
|
+
from the wire (**breaking** — `insika:<agent>` / `X-Insika-Agent` and
|
|
15
|
+
`INSIKA_GATEWAY_TOKEN`, no aliases), and the engine learned to manage its own
|
|
16
|
+
long conversations — in-session compaction (RFC-0044), a tool budget that warns
|
|
17
|
+
before it kills, goal recitation at the tail of the context, and a tool-usage
|
|
18
|
+
audit report. Update `/v1` consumers and the gateway-token variable together
|
|
19
|
+
with this upgrade.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **In-session compaction (RFC-0044).** When a session's uncompacted
|
|
24
|
+
transcript grows past `compact_after` messages (default 40), everything but
|
|
25
|
+
the last `keep_last` (default 20) is summarized by a cheap model
|
|
26
|
+
(`compaction.model` → platform `utility_model`) into one
|
|
27
|
+
`<conversation_summary>` history fragment; the tail stays verbatim and the
|
|
28
|
+
boundary is stable between compactions, so the prompt cache holds after it.
|
|
29
|
+
Runs post-turn, off the critical path, best-effort; persisted on the session
|
|
30
|
+
record with a monotonic boundary. Opt-in via the long-reserved Settings
|
|
31
|
+
`compaction` hash (the Studio general form is back), `insika doctor` warns
|
|
32
|
+
when it is enabled with no model, and every compaction is observable:
|
|
33
|
+
`:context_compacted` event, `insika.context.compacted` counter, `{upto,
|
|
34
|
+
runs}` + its own category in the context trace. See
|
|
35
|
+
[Context](docs/CONTEXT.md).
|
|
36
|
+
|
|
37
|
+
- **`insika tools:report` — the tool audit.** The per-session trace never
|
|
38
|
+
aggregated, so "which tools does this agent carry and never use?" had no
|
|
39
|
+
answer. The new CLI report reads the stored traces per agent (tasks →
|
|
40
|
+
sessions → `tool_traces`) and flags allowlisted-but-never-called tools,
|
|
41
|
+
tools over 30% errors in the window, and tools not called in N days
|
|
42
|
+
(default 14). Read-only — the operator removes. `--agent`, `--days`,
|
|
43
|
+
`--json`. See [Tools](docs/TOOLS.md).
|
|
44
|
+
|
|
45
|
+
- **Two series promoted to OTEL metrics.** `insika.cache.hit_rate` (histogram,
|
|
46
|
+
`%` — cache reads over the billed prompt, per turn, same arithmetic as the
|
|
47
|
+
Studio's per-agent series) and `insika.tool.loop_intervened` (counter — the
|
|
48
|
+
loop detector's one-shot warning, labelled by tool). Both computed in the
|
|
49
|
+
telemetry bridge from events that already existed: zero cost when OTEL is
|
|
50
|
+
off, no engine change. See [Observability](docs/OBSERVABILITY.md).
|
|
51
|
+
|
|
52
|
+
- **The doctor warns on a prompt file that outgrew a prompt.** The
|
|
53
|
+
`prompt-files` sweep now WARNs (never errors) past ~6 000 estimated tokens
|
|
54
|
+
or 600 lines, with the actual counts — the LLM-generated merchant-pack
|
|
55
|
+
shape that costs 20%+ extra tokens per turn. Corrupted-wrapper detection
|
|
56
|
+
is unchanged.
|
|
57
|
+
|
|
58
|
+
- **The tool budget is announced before it kills.** `max_tool_calls` was
|
|
59
|
+
enforced but silent: the model met the ceiling only when the turn died with
|
|
60
|
+
`stage: :tool_limit`, having delivered nothing. At **10 / 5 / 2** calls
|
|
61
|
+
remaining the engine now appends a short, escalating notice at the next
|
|
62
|
+
tool-batch boundary and emits `:tool_budget_warned`. It is a `user` message,
|
|
63
|
+
never a system one, so the cacheable system prefix stays byte-stable.
|
|
64
|
+
Verified live (DeepSeek): a turn asking for 30 lookups on a budget of 20 used
|
|
65
|
+
to die at `tool_limit`; it now stops at 18 and answers with what it has. The
|
|
66
|
+
turn's counter, the notices and the abort are one object (`TurnBudget`), and
|
|
67
|
+
the batch-boundary arithmetic it shares with the loop detector moved into
|
|
68
|
+
`ToolBatch`.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- **The briefing recites the goal at the TAIL of the context.** `<briefing>`
|
|
73
|
+
used to carry known facts, the still-missing list and the next step in one
|
|
74
|
+
`:system` block at the top. The goal half was **moved** (not copied) into a
|
|
75
|
+
new `<recitation>` block rendered after the whole history, as the last thing
|
|
76
|
+
the model reads before the current message — attention is strongest at the
|
|
77
|
+
end, and a goal stated only at the top is the first thing a 30-call turn
|
|
78
|
+
forgets. New `:tail` fragment placement; the head keeps the durable facts.
|
|
79
|
+
|
|
80
|
+
- **The `/v1/responses` wire names are insika-native**: the agent arrives as
|
|
81
|
+
`model: "insika:<agent>"` and the fallback header is `X-Insika-Agent`. Hard
|
|
82
|
+
rename, no alias — a consumer still sending `openclaw:<agent>` gets
|
|
83
|
+
"model/agent missing". Update every `/v1` consumer together with this upgrade.
|
|
84
|
+
- **`INSIKA_GATEWAY_TOKEN` replaces `OPENCLAW_GATEWAY_TOKEN`** as the Bearer for
|
|
85
|
+
`/v1` + `/a2a`. Hard rename, no alias: the old name was a leftover of the
|
|
86
|
+
OpenClaw migration and is now a foreign var the engine ignores. Update the
|
|
87
|
+
variable on every deployment before upgrading. The dead `OPENCLAW_AGENTS_DIR`
|
|
88
|
+
env spec (nothing read it) is gone.
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- **The Studio home charts went blank for three hours every day.** The activity
|
|
93
|
+
buckets key on calendar parts (date, hour) of `updated_at`, which the engine
|
|
94
|
+
writes in UTC, but `render_home` read `Time.now` in the host's local zone. On a
|
|
95
|
+
UTC-3 host, from 21:00 local onward "today" was already tomorrow in UTC: the
|
|
96
|
+
14-day chart matched no session and the 24h sparkline's floor was set three
|
|
97
|
+
hours in the future, dropping the newest buckets. Stamps are now read through
|
|
98
|
+
one `utc_time` helper and `now` is UTC. Instant comparisons (`active_now`)
|
|
99
|
+
never had the bug — only the calendar arithmetic did.
|
|
100
|
+
- **The Conversations trend showed "−1" for the day's first conversation.** The
|
|
101
|
+
14-day series is oldest-first and ends at today, so its last pair reads
|
|
102
|
+
`[yesterday, today]`; it was destructured the other way round, inverting the
|
|
103
|
+
sign on every conversations delta. The Messages card was always correct.
|
|
104
|
+
- **Declaring a tool allow/deny list now opts the profile into the policy.** The
|
|
105
|
+
lists are applied by exactly one policy — the builtin `tool_allowlist` — and
|
|
106
|
+
the policy engine runs only the policies a profile *names*. So an agent with
|
|
107
|
+
`tools_allow: ["a", "b"]` and no `policies` sent **every registered tool** to
|
|
108
|
+
the model, silently: 508 tools / 52 405 tokens of schemas per request against
|
|
109
|
+
an allowlist of 2, observed on a real run. `tools_deny` was ignored the same
|
|
110
|
+
way. `AgentProfile.build` now appends `tool_allowlist` whenever `tools_allow`,
|
|
111
|
+
`tools_deny` or `tools_allow_groups` is declared — presence, not emptiness, so
|
|
112
|
+
`tools_allow: []` still means "no tools". A stored record left in the old
|
|
113
|
+
state is repaired on read but stays wrong on disk, so `doctor` gained a
|
|
114
|
+
`tool-allowlist` **error** naming each agent to re-save.
|
|
115
|
+
- **A DSL agent could write its briefing and never read it back.** The
|
|
116
|
+
`Insika.agent`/`Insika.system` runtime wired every context provider except
|
|
117
|
+
`Briefing`, so `update_briefing` / `set_next_step` persisted state that never
|
|
118
|
+
reached a prompt. Now wired, before `Session`, like `config/wiring.rb`.
|
|
119
|
+
- **Docker image shipped without `docs/`, so `GET /start.md` was a 500 with
|
|
120
|
+
`INSIKA_ONBOARDING=1`.** `.dockerignore` excluded the whole tree; the onboarding
|
|
121
|
+
surface reads `docs/onboarding/start.md`, `docs/prompts/` and the public
|
|
122
|
+
`docs/*.md` from disk at request time. Only the Jekyll build output and the
|
|
123
|
+
internal notes are excluded now, and a spec asserts `.dockerignore` never drops
|
|
124
|
+
a file `Insika::Onboarding` serves. Found on the first Railway deploy with
|
|
125
|
+
onboarding on; the gem was never affected (its payload keeps `docs/`).
|
|
126
|
+
|
|
11
127
|
## [0.7.0] - 2026-08-29
|
|
12
128
|
|
|
13
129
|
The pre-1.0 consolidation: MCP servers as first-class tools, a template gallery,
|
data/README.md
CHANGED
|
@@ -143,7 +143,7 @@ navigates by.
|
|
|
143
143
|
- [Evals](docs/EVALS.md) — the cases that grade an agent: rubrics, the judge panel, and the pre-merge gate.
|
|
144
144
|
- [Refinement](docs/REFINEMENT.md) — read an agent's own traffic back as a ranked report of what broke.
|
|
145
145
|
- [Outcomes and follow-ups](docs/OUTCOMES.md) — what the traffic was worth in business terms, and the tool that comes back on a promise.
|
|
146
|
-
- [Knowledge](docs/KNOWLEDGE.md) — extract durable concepts from finished conversations into a per-agent knowledge base; provenance-stamped, PII-redacted, best-effort with a re-scan recovery path
|
|
146
|
+
- [Knowledge](docs/KNOWLEDGE.md) — extract durable concepts from finished conversations into a per-agent knowledge base; provenance-stamped, PII-redacted, best-effort with a re-scan recovery path.
|
|
147
147
|
- [Facts](docs/FACTS.md) — distill finished customer conversations into proposed facts; a human approves them into memory (nothing is ever applied automatically).
|
|
148
148
|
- [Harvest](docs/HARVEST.md) — mine real traffic for SKILL proposals; the negative list, the evidence-ledger grounding filter and the double gate hold every proposal to a human approval and the append-only log (nothing is ever applied automatically).
|
|
149
149
|
|
|
@@ -176,5 +176,6 @@ and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Vulnerabilities go through
|
|
|
176
176
|
|
|
177
177
|
## Status
|
|
178
178
|
|
|
179
|
-
Pre-
|
|
180
|
-
([CHANGELOG.md](CHANGELOG.md)). Licensed
|
|
179
|
+
Pre-1.0 — published on [RubyGems](https://rubygems.org/gems/insika); APIs may
|
|
180
|
+
still change between minor versions ([CHANGELOG.md](CHANGELOG.md)). Licensed
|
|
181
|
+
**MIT** ([LICENSE](LICENSE)).
|
data/bin/insika
CHANGED
|
@@ -32,6 +32,7 @@ module Insika
|
|
|
32
32
|
when "harvest" then harvest(argv)
|
|
33
33
|
when "harvest:negative" then harvest_negative_import(argv)
|
|
34
34
|
when "harvest:criterion" then harvest_criterion_check(argv)
|
|
35
|
+
when "tools:report" then tools_report(argv)
|
|
35
36
|
when "knowledge:backfill" then knowledge_backfill(argv)
|
|
36
37
|
when "knowledge:export" then knowledge_export(argv)
|
|
37
38
|
when "soak" then exit(Insika::Soak::Runner.main(argv, stdout: $stdout, stderr: $stderr))
|
|
@@ -65,6 +66,11 @@ module Insika
|
|
|
65
66
|
seed the profile's negative list from a rules file
|
|
66
67
|
harvest:criterion check --file F
|
|
67
68
|
strict-load the frozen conversion criterion
|
|
69
|
+
tools:report [--agent ID] [--days N] [--json]
|
|
70
|
+
tool usage per agent over the stored traces:
|
|
71
|
+
allowlisted-but-never-called, error rate > 30%
|
|
72
|
+
in the window, and stale (not called in N days).
|
|
73
|
+
Read-only — the operator removes
|
|
68
74
|
knowledge:backfill --agent ID [--since ISO8601] [--json]
|
|
69
75
|
the recovery path for post-turn extraction: replays
|
|
70
76
|
the agent's stored sessions through the same
|
|
@@ -337,7 +343,7 @@ def evals_simulate(argv)
|
|
|
337
343
|
require "fileutils"
|
|
338
344
|
require "time"
|
|
339
345
|
opts = { base_url: ENV["INSIKA_URL"] || "http://localhost:9292",
|
|
340
|
-
token: ENV["
|
|
346
|
+
token: ENV["INSIKA_GATEWAY_TOKEN"] || ENV["ADMIN_TOKEN"] || "local-demo",
|
|
341
347
|
timeout: 120, persona_model: ENV["EVAL_PERSONA_MODEL"],
|
|
342
348
|
judge_model: ENV["EVAL_JUDGE_MODEL"], judge_provider: ENV["EVAL_JUDGE_PROVIDER"] }
|
|
343
349
|
OptionParser.new do |o|
|
|
@@ -345,7 +351,7 @@ def evals_simulate(argv)
|
|
|
345
351
|
o.on("--persona FILE", "the persona YAML — a case with a `persona:` key (required)") { |v| opts[:persona] = v }
|
|
346
352
|
o.on("--target T", "agent id (over --base-url) or an A2A http(s) URL (required)") { |v| opts[:target] = v }
|
|
347
353
|
o.on("--base-url URL", "deployment base URL (default #{opts[:base_url]})") { |v| opts[:base_url] = v }
|
|
348
|
-
o.on("--token T", "gateway token (default
|
|
354
|
+
o.on("--token T", "gateway token (default INSIKA_GATEWAY_TOKEN/local-demo)") { |v| opts[:token] = v }
|
|
349
355
|
o.on("--timeout N", Integer, "per-turn read timeout, and the A2A poll budget in seconds (default 120)") { |v| opts[:timeout] = v }
|
|
350
356
|
o.on("--staging", "the target is a staging deployment (side effects acceptable)") { opts[:staging] = true }
|
|
351
357
|
o.on("--eval-profile", "the target runs an eval profile (side-effect tools swapped for fakes)") { opts[:eval_profile] = true }
|
|
@@ -1162,6 +1168,41 @@ end
|
|
|
1162
1168
|
"last #{n} session(s)"
|
|
1163
1169
|
end
|
|
1164
1170
|
|
|
1171
|
+
# insika tools:report [--agent ID] [--days N] [--json]
|
|
1172
|
+
#
|
|
1173
|
+
# The tool audit: reads the SAME durable backend as `doctor` (tasks →
|
|
1174
|
+
# sessions → tool_traces), no app boot. Read-only — it names never-called
|
|
1175
|
+
# allowlisted tools, high-error tools and stale tools; the operator removes.
|
|
1176
|
+
def tools_report(argv)
|
|
1177
|
+
opts = { json: false, days: Insika::ToolUsageReport::WINDOW_DAYS }
|
|
1178
|
+
OptionParser.new do |o|
|
|
1179
|
+
o.banner = "Usage: insika tools:report [--agent ID] [--days N] [--json]"
|
|
1180
|
+
o.on("--agent ID", "narrow the report to one stored agent") { |v| opts[:agent] = v }
|
|
1181
|
+
o.on("--days N", Integer, "window for error-rate and staleness (default #{opts[:days]})") { |v| opts[:days] = v }
|
|
1182
|
+
o.on("--json", "emit the report as JSON") { opts[:json] = true }
|
|
1183
|
+
end.parse!(argv)
|
|
1184
|
+
|
|
1185
|
+
backend = Insika::Wiring::Graph.backend_from_env
|
|
1186
|
+
config_store = Insika::ConfigStore.new(store: backend)
|
|
1187
|
+
report = Insika::ToolUsageReport.new(
|
|
1188
|
+
task_store: Insika::TaskStore.new(store: backend),
|
|
1189
|
+
tool_trace_store: Insika::ToolTraceStore.new(store: backend),
|
|
1190
|
+
profile_source: Insika::StoredProfileSource.new(config_store: config_store)
|
|
1191
|
+
).generate(days: opts[:days], agent: opts[:agent])
|
|
1192
|
+
|
|
1193
|
+
if opts[:agent] && report.agents.empty?
|
|
1194
|
+
warn "insika tools:report: no stored agent '#{opts[:agent]}'"
|
|
1195
|
+
exit 2
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
if opts[:json]
|
|
1199
|
+
require "json"
|
|
1200
|
+
puts JSON.pretty_generate(report.to_h)
|
|
1201
|
+
else
|
|
1202
|
+
puts report.to_s
|
|
1203
|
+
end
|
|
1204
|
+
end
|
|
1205
|
+
|
|
1165
1206
|
# Config stores over the real durable backend — no app boot / no DEEPSEEK.
|
|
1166
1207
|
def build_doctor
|
|
1167
1208
|
backend = Insika::Wiring::Graph.backend_from_env
|
data/docs/AGENTS.md
CHANGED
|
@@ -32,9 +32,10 @@ All three land on the **same** config-over-code import path — they differ only
|
|
|
32
32
|
ergonomics, not in what they produce.
|
|
33
33
|
|
|
34
34
|
- **DSL** — `Insika.agent("id") { … }` builds an agent definition and imports it
|
|
35
|
-
into the durable store. Best for code-defined agents and examples. The DSL
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
into the durable store. Best for code-defined agents and examples. The DSL also
|
|
36
|
+
names the **skill**-allowlist policy for you (the tool one is implied by any
|
|
37
|
+
declared tool list, on every path). `model` is optional — a nil model resolves
|
|
38
|
+
the platform `default_model` at turn start.
|
|
38
39
|
- **API** — `POST /v1/agents` with a definition (a "pack": an agent config plus its
|
|
39
40
|
prompt files, skills, and data-tools). The import is **idempotent** and
|
|
40
41
|
**authoritative** — what leaves the definition leaves the agent, so a
|
|
@@ -108,6 +109,14 @@ DEFAULT_LIMITS = {
|
|
|
108
109
|
|
|
109
110
|
`build` merges your overrides over these — you set only the deltas.
|
|
110
111
|
|
|
112
|
+
`max_tool_calls` is announced before it kills. At **10 / 5 / 2** calls remaining
|
|
113
|
+
the engine appends a short user message at the next tool-batch boundary
|
|
114
|
+
("2 tool calls left — consolidate what you already have and answer now"), so a
|
|
115
|
+
long turn converges on an answer instead of dying with `stage: :tool_limit` and
|
|
116
|
+
delivering nothing. It is a `user` message, never a system one: the system prefix
|
|
117
|
+
stays byte-stable and `prompt_caching` keeps hitting. Each threshold fires at
|
|
118
|
+
most once per turn and emits `:tool_budget_warned` on the event stream.
|
|
119
|
+
|
|
111
120
|
`max_tool_repeat` is the loop guard: the same tool called with **identical
|
|
112
121
|
arguments** that many times in a row gets ONE in-turn warning (a user message at
|
|
113
122
|
the next tool-batch boundary: "you already ran this, answer with what you
|
|
@@ -382,6 +391,16 @@ The same three-state rule governs tools, skills, context providers, and workflow
|
|
|
382
391
|
For tools, a paired deny list (`tools_deny`) **always wins**, and
|
|
383
392
|
`tools_allow_groups` unions a per-group allowlist on top of `tools_allow`.
|
|
384
393
|
|
|
394
|
+
**Declaring a list is opting into it.** The lists are applied by one policy, the
|
|
395
|
+
builtin `tool_allowlist`, and the policy engine runs only the policies a profile
|
|
396
|
+
names — so declaring `tools_allow` without naming that policy used to mean the
|
|
397
|
+
model got *every* registered tool. The engine now adds `tool_allowlist` for you
|
|
398
|
+
the moment any of `tools_allow`, `tools_deny` or `tools_allow_groups` is
|
|
399
|
+
declared, on every creation path. Presence is the trigger, not content:
|
|
400
|
+
`tools_allow: []` means "no tools" and enforces just as hard. If a stored agent
|
|
401
|
+
predates this, `insika doctor` names it — the engine repairs it on read, but the
|
|
402
|
+
stored record stays wrong until you re-save it.
|
|
403
|
+
|
|
385
404
|
Three capabilities invert the default — `nil`/absent means **OFF**, not "all":
|
|
386
405
|
`subagents`, `memory`, and `guardrails` (each defaults to off or a conservative
|
|
387
406
|
setting, never "everything on"). `tool_output_compression` is a fourth: opt-in
|
data/docs/ARTIFACTS.md
CHANGED
|
@@ -67,6 +67,48 @@ No script, no external fetch, no forms. The model writes HTML with **inline
|
|
|
67
67
|
SVG** for charts — that is a skill instruction (palette, tables, pure-SVG bars),
|
|
68
68
|
not engine code. A "real" charting need is a plugin.
|
|
69
69
|
|
|
70
|
+
## Reasoning effort on a report turn
|
|
71
|
+
|
|
72
|
+
A report turn is not one shape of work — it plans, then it mines, then it
|
|
73
|
+
writes. `thinking` is one value per agent for the whole turn, so an agent set to
|
|
74
|
+
`high` pays deliberation on every one of the 30–50 tool calls a real report
|
|
75
|
+
makes, and that is where the 300 s turn timeout gets spent.
|
|
76
|
+
|
|
77
|
+
Split the phases across agents instead, which the engine already supports today:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
Insika.system do
|
|
81
|
+
# The miners: one narrow question each, no judgement to make.
|
|
82
|
+
agent("sales_miner") do
|
|
83
|
+
model "deepseek-v4-flash"
|
|
84
|
+
params thinking: "low"
|
|
85
|
+
tools %w[query_sales]
|
|
86
|
+
instructions "Answer ONE question about sales from the store data. Numbers, no prose."
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# The orchestrator: it plans the report and writes it. This is the turn
|
|
90
|
+
# that deserves the deliberation.
|
|
91
|
+
agent("reporter") do
|
|
92
|
+
model "deepseek-v4-flash"
|
|
93
|
+
params thinking: "high"
|
|
94
|
+
tools %w[save_artifact]
|
|
95
|
+
subagents "sales_miner"
|
|
96
|
+
instructions "Plan the report, call spawn_subagents ONCE for every number you need, then write the page and save_artifact it."
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Two things make this work: a child inherits the *environment* (model, thinking)
|
|
102
|
+
only as a **default**, so its own `params thinking:` wins; and `spawn_subagents`
|
|
103
|
+
runs the children in parallel, so wall-clock is the slowest miner rather than
|
|
104
|
+
the sum. Each child also mines in its own isolated context, which is what keeps
|
|
105
|
+
the orchestrator's context from filling with raw rows.
|
|
106
|
+
|
|
107
|
+
Measure it before reaching for anything cleverer: the numbers that matter are
|
|
108
|
+
the turn's wall-clock, the timeout rate, and the judge score on the same report.
|
|
109
|
+
Per-phase effort *inside* a single turn is a real idea, but it is only worth
|
|
110
|
+
building once this recipe is shown not to be enough.
|
|
111
|
+
|
|
70
112
|
## Limits and retention
|
|
71
113
|
|
|
72
114
|
- **Size cap** — `INSIKA_ARTIFACT_MAX_BYTES` (default 1 MB): an artifact is a
|
data/docs/CONTEXT.md
CHANGED
|
@@ -28,9 +28,10 @@ into a deterministic prompt:
|
|
|
28
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
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
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
|
|
31
|
+
| **Briefing** | `<briefing>` | 65 | volatile | The session's working state — the *known* fields only. Only if the pack declared `briefing_fields`. Cuttable. |
|
|
32
32
|
| **Session** | history | 60–79 | volatile | The running transcript; priority scales with recency. |
|
|
33
33
|
| **Request** | `<request_context>` | 40 | volatile | Turn variables + tenant. Most cuttable; sits last. |
|
|
34
|
+
| **Briefing (tail)** | `<recitation>` | 95 | volatile | The still-missing list + next step, rendered **after the whole history** as a `user` message — the last thing the model reads before the current message. |
|
|
34
35
|
|
|
35
36
|
The ordering is deliberate: the render order is **identity layer first, volatile
|
|
36
37
|
layer after** — nothing volatile can sit above the cache boundary, whatever its
|
|
@@ -53,14 +54,37 @@ cacheable prefix byte-stable (see the prefix cache below).
|
|
|
53
54
|
> returns empty turns, raise `context_budget` (e.g. to `60000`) before looking
|
|
54
55
|
> anywhere else. See [Agents](AGENTS.md#default-limits).
|
|
55
56
|
|
|
56
|
-
###
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
### In-session compaction (opt-in, platform Settings)
|
|
58
|
+
|
|
59
|
+
When enabled (Studio → Settings → General, or the `compaction` settings hash),
|
|
60
|
+
the engine summarizes old turns *inside* the session instead of losing them to
|
|
61
|
+
eviction:
|
|
62
|
+
|
|
63
|
+
- **Trigger:** after a turn commits, if the session's *uncompacted* message
|
|
64
|
+
count exceeds `compact_after` (default 40), everything but the last
|
|
65
|
+
`keep_last` messages (default 20) is summarized by a cheap model
|
|
66
|
+
(`compaction.model`, falling back to the platform `utility_model`; neither
|
|
67
|
+
set = the feature is inert and `insika doctor` warns). Runs off the critical
|
|
68
|
+
path — the customer already has the answer.
|
|
69
|
+
- **Read path:** the Session provider replaces the compacted prefix with ONE
|
|
70
|
+
history fragment — a `user` message wrapped in `<conversation_summary>` tags,
|
|
71
|
+
priority 59 (one step below the oldest verbatim message), source
|
|
72
|
+
`compaction` (its own category in the context trace). The tail stays
|
|
73
|
+
verbatim and the boundary is **stable** between compactions, so the prompt
|
|
74
|
+
cache holds after it.
|
|
75
|
+
- **What survives:** the default prompt orders the summary to preserve customer
|
|
76
|
+
facts (sizes, CEP, order numbers), the assistant's commitments, the still-open
|
|
77
|
+
questions and the decisions already made; on re-compaction the previous
|
|
78
|
+
summary is folded in, so a fact from turn 3 survives every later batch.
|
|
79
|
+
A platform `compaction.prompt` replaces the default wholesale.
|
|
80
|
+
- **Scope:** store-sourced history only — a checkpoint resume replays its own
|
|
81
|
+
tape and an explicit `history` is the caller's contract; neither is
|
|
82
|
+
rewritten. Observability: the `:context_compacted` event, the
|
|
83
|
+
`insika.context.compacted` counter and `{upto, runs}` in the context trace.
|
|
84
|
+
|
|
85
|
+
Compaction does not replace the budget: eviction stays as the hard backstop
|
|
86
|
+
for a single oversized turn. Tune `context_budget` and keep the identity lean
|
|
87
|
+
regardless.
|
|
64
88
|
|
|
65
89
|
One cheap half **is** wired, opt-in per agent: `tool_output_compression` (DSL
|
|
66
90
|
`tool_output_compression`, or `"tool_output_compression": true` in the pack).
|
|
@@ -121,19 +145,37 @@ pack:
|
|
|
121
145
|
briefing_fields "size", "budget", "delivery_day"
|
|
122
146
|
```
|
|
123
147
|
|
|
124
|
-
With fields declared, the
|
|
125
|
-
|
|
148
|
+
With fields declared, the briefing renders in **two places**, and the split is
|
|
149
|
+
deliberate.
|
|
150
|
+
|
|
151
|
+
The durable half — what is already known — sits in the `:system` context
|
|
152
|
+
(priority 65: below identity/skill/memory so it never breaks the cacheable
|
|
126
153
|
prefix, above the turn's own `<request_context>`):
|
|
127
154
|
|
|
128
155
|
```
|
|
129
156
|
<briefing>
|
|
130
157
|
known:
|
|
131
158
|
size: M
|
|
159
|
+
</briefing>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The half that is a *goal* — what is still missing and the agreed next step — is
|
|
163
|
+
**recited at the tail**, after the whole history, as the last thing the model
|
|
164
|
+
reads before the current user message:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
<recitation>
|
|
132
168
|
still missing: budget, delivery_day
|
|
133
169
|
next step: send the payment link tomorrow at 10
|
|
134
|
-
</
|
|
170
|
+
</recitation>
|
|
135
171
|
```
|
|
136
172
|
|
|
173
|
+
Attention is strongest at the end of the context: a goal stated only at the top
|
|
174
|
+
is the first thing a 30-call turn forgets. So the recitation was **moved** there,
|
|
175
|
+
not copied — the head never repeats it, and the turn pays for it once. It rides
|
|
176
|
+
as a `user` message, like every other engine append inside a turn, so the system
|
|
177
|
+
prefix stays byte-stable and the cache breakpoint at its end keeps hitting.
|
|
178
|
+
|
|
137
179
|
The `still missing` list is the point: the *model* sees which declared fields are
|
|
138
180
|
still unanswered, so it stops re-asking for something already given. Stored keys
|
|
139
181
|
that the pack no longer declares are never rendered. The Studio session screen
|
data/docs/DEPLOY.md
CHANGED
|
@@ -25,7 +25,7 @@ is durable SQLite (WAL) at `INSIKA_DB`; mount a volume and point it inside.
|
|
|
25
25
|
docker build -t insika .
|
|
26
26
|
docker run -p 9292:9292 -v insika-data:/data \
|
|
27
27
|
-e DEEPSEEK_API_KEY=sk-... \
|
|
28
|
-
-e
|
|
28
|
+
-e INSIKA_GATEWAY_TOKEN=change-me \
|
|
29
29
|
insika
|
|
30
30
|
curl localhost:9292/up # {"status":"ok"}
|
|
31
31
|
```
|
|
@@ -115,7 +115,7 @@ this section is the single source of truth for what changing it means.
|
|
|
115
115
|
| `INSIKA_DRAIN_TIMEOUT` | `20` | seconds a stopping worker waits for in-flight turns before abandoning them to the next boot's recovery (process model, item 4). The entrypoint sizes Falcon's `--graceful-stop` from it; on Railway also set `RAILWAY_DEPLOYMENT_DRAINING_SECONDS` ≥ drain + 10 |
|
|
116
116
|
| `INSIKA_TICK_INTERVAL` | `60` | seconds between tick passes — outbox drain + stale recovery sweep (process model, item 5). `0` disables |
|
|
117
117
|
| `INSIKA_TICK_STALE_AFTER` | `900` | seconds a `:queued`/`:running` task must sit untouched before the tick sweeps it. Must exceed the largest `turn_timeout` of the deployment |
|
|
118
|
-
| `
|
|
118
|
+
| `INSIKA_GATEWAY_TOKEN` | falls back to `ADMIN_TOKEN` | Bearer for `/v1/responses` and `/v1/agents` (the API contract) |
|
|
119
119
|
| `ADMIN_TOKEN` | `local-demo` | login token for `/studio` (**change in production**) |
|
|
120
120
|
| `DEEPSEEK_API_KEY` | — | provider key. **Without it the engine still boots** (`/up` green), but turns fail until it is configured (env or Studio → LLM providers) — cloud resilience |
|
|
121
121
|
| `DEEPSEEK_MODEL` | `deepseek-v4-flash` | model |
|
|
@@ -157,7 +157,7 @@ values (the API token falling back to `ADMIN_TOKEN` is a dev convenience only):
|
|
|
157
157
|
- **`ADMIN_TOKEN`** — the `/studio` login (cookie auth). This is the **operator**
|
|
158
158
|
surface (just you). Rotating it is **safe and independent**: change it, redeploy,
|
|
159
159
|
log in with the new value. It does not affect any API consumer.
|
|
160
|
-
- **`
|
|
160
|
+
- **`INSIKA_GATEWAY_TOKEN`** — the Bearer for `/v1/responses` and `/v1/agents`.
|
|
161
161
|
This is the **contract with your API consumers**. Rotating it means **changing
|
|
162
162
|
both sides together** (or the integration breaks): update the runtime var **and**
|
|
163
163
|
each consumer's token in the same step.
|
|
@@ -187,15 +187,24 @@ insika doctor # colored report; exits != 0 on any error
|
|
|
187
187
|
insika doctor --json # machine-readable (CI / monitoring)
|
|
188
188
|
insika doctor --fix # applies the safe autofixes and re-diagnoses
|
|
189
189
|
insika env # lists known keys + current values (secrets masked)
|
|
190
|
+
insika tools:report # tool audit over the stored traces: never-called
|
|
191
|
+
# allowlisted tools, error rate > 30%, stale tools —
|
|
192
|
+
# read-only, the operator removes ([--agent ID] [--days N] [--json])
|
|
190
193
|
```
|
|
191
194
|
|
|
192
195
|
Checks: env (the schema above), settings schema version (a pending migration →
|
|
193
196
|
`--fix` applies it), a missing platform `default_model` (`--fix` seeds it from
|
|
194
197
|
`DEEPSEEK_MODEL`), durable vs ephemeral backend, LLM provider configured,
|
|
195
|
-
`ADMIN_TOKEN` set, data-tool definitions still valid, **
|
|
198
|
+
`ADMIN_TOKEN` set, data-tool definitions still valid, **a stored agent whose
|
|
199
|
+
declared tool allow/deny list does not name the `tool_allowlist` policy** (the
|
|
200
|
+
engine repairs it on read, but as stored the list is inert — see
|
|
201
|
+
[Agents](AGENTS.md#the-allowlist-convention)), **prompt files that hold
|
|
196
202
|
text rather than a serialized object** (a file whose content is a stringified Hash
|
|
197
203
|
serves a mangled prompt on every turn while looking perfectly healthy — present,
|
|
198
|
-
non-empty, and the agent still answers),
|
|
204
|
+
non-empty, and the agent still answers), **a prompt file that outgrew a prompt**
|
|
205
|
+
(WARN past ~6 000 estimated tokens or 600 lines — the LLM-generated pack shape
|
|
206
|
+
that costs 20%+ extra tokens per turn for no better instruction-following), and
|
|
207
|
+
**skill drift** — a shared skill whose
|
|
199
208
|
body names one store, a prompt file routing to a skill the agent cannot load, a broken
|
|
200
209
|
companion pair, a stale `eager:` key (see
|
|
201
210
|
[Skills](SKILLS.md#drift-guards)). Settings-schema migrations are **explicit**
|
|
@@ -227,7 +236,7 @@ healthcheck, and a restart policy.
|
|
|
227
236
|
2. **Volume**: mount it at `/data` (the default `INSIKA_DB` points there) —
|
|
228
237
|
without a volume, SQLite is ephemeral and recovery resumes nothing after a
|
|
229
238
|
redeploy.
|
|
230
|
-
3. **Vars**: `DEEPSEEK_API_KEY`, `
|
|
239
|
+
3. **Vars**: `DEEPSEEK_API_KEY`, `INSIKA_GATEWAY_TOKEN`, `CONSUMER_INTERNAL_URL`,
|
|
231
240
|
`INSIKA_EGRESS_HOSTS`. **Leave `WEB_CONCURRENCY` at its default of 1** unless
|
|
232
241
|
you run [`insika-router`](ROUTER.md) in front. Railway's own docs say it
|
|
233
242
|
"does not support sticky sessions" and randomly distributes traffic across
|
|
@@ -402,7 +411,7 @@ cache hits, P50/P95, error rate. Runs against local or a remote deployment. See
|
|
|
402
411
|
[LOADTEST.md](LOADTEST.md).
|
|
403
412
|
|
|
404
413
|
```bash
|
|
405
|
-
INSIKA_URL=http://localhost:9292
|
|
414
|
+
INSIKA_URL=http://localhost:9292 INSIKA_GATEWAY_TOKEN=xxx \
|
|
406
415
|
bundle exec ruby scripts/loadtest.rb --agents assistant --concurrency 16 --iterations 3
|
|
407
416
|
```
|
|
408
417
|
|
data/docs/LOADTEST.md
CHANGED
|
@@ -67,7 +67,7 @@ frame that carries it.
|
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
69
|
INSIKA_URL=http://localhost:9292 \
|
|
70
|
-
|
|
70
|
+
INSIKA_GATEWAY_TOKEN=xxx \
|
|
71
71
|
bundle exec ruby scripts/loadtest.rb \
|
|
72
72
|
--agents demo,my-store --concurrency 16 --iterations 3 \
|
|
73
73
|
--message "hi, how are you?"
|
|
@@ -80,7 +80,7 @@ Runs against a local server **or** a remote one (e.g. Railway) — just point
|
|
|
80
80
|
|
|
81
81
|
| Flag | Default | Meaning |
|
|
82
82
|
|------|---------|---------|
|
|
83
|
-
| `--agents a,b,c` | `demo` | comma-separated agent ids (mapped to `model:
|
|
83
|
+
| `--agents a,b,c` | `demo` | comma-separated agent ids (mapped to `model: insika:<agent>`) |
|
|
84
84
|
| `--concurrency N` | `8` | concurrent turns per wave |
|
|
85
85
|
| `--iterations N` | `1` | number of waves per agent |
|
|
86
86
|
| `--message TEXT` | greeting | user message sent every turn |
|
|
@@ -95,14 +95,14 @@ Runs against a local server **or** a remote one (e.g. Railway) — just point
|
|
|
95
95
|
| Env | Default | Meaning |
|
|
96
96
|
|-----|---------|---------|
|
|
97
97
|
| `INSIKA_URL` | `http://localhost:9292` | base URL of the engine |
|
|
98
|
-
| `
|
|
98
|
+
| `INSIKA_GATEWAY_TOKEN` | falls back to `ADMIN_TOKEN`, then `local-demo` | Bearer for `/v1/responses` |
|
|
99
99
|
| `DEEPSEEK_API_KEY` | — | must be configured **on the server** for real turns (not read by the client) |
|
|
100
100
|
|
|
101
101
|
Use `--dry-run` to sanity-check your flags/URL/token before firing real traffic
|
|
102
102
|
(and to confirm the request body without needing a running server):
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
INSIKA_URL=http://localhost:9292
|
|
105
|
+
INSIKA_URL=http://localhost:9292 INSIKA_GATEWAY_TOKEN=xxx \
|
|
106
106
|
bundle exec ruby scripts/loadtest.rb --agents demo --concurrency 16 --dry-run
|
|
107
107
|
```
|
|
108
108
|
|
|
@@ -133,7 +133,7 @@ DEEPSEEK_API_KEY=sk-... ./scripts/loadtest-local.sh [WORKERS] [CONCURRENCY]
|
|
|
133
133
|
| Env | Default | Meaning |
|
|
134
134
|
|-----|---------|---------|
|
|
135
135
|
| `DEEPSEEK_API_KEY` | — (required) | real turns hit the provider; also auto-sourced from `.env.local` |
|
|
136
|
-
| `
|
|
136
|
+
| `INSIKA_GATEWAY_TOKEN` | falls back to `ADMIN_TOKEN`, then `local-demo` | Bearer for the sweep |
|
|
137
137
|
| `PORT` | `9299` | bind port for the local Falcon |
|
|
138
138
|
| `AGENT` | `demo` | agent id to load |
|
|
139
139
|
|
|
@@ -158,30 +158,18 @@ the engine, once at the gateway (its `/v1/responses` speaks the same protocol).
|
|
|
158
158
|
Keep `--agents`, `--concurrency`, `--iterations` and `--message` identical, and use
|
|
159
159
|
matching agents on both sides. Compare the printed TTFB/total/cache/error lines.
|
|
160
160
|
|
|
161
|
-
### 4b.
|
|
161
|
+
### 4b. Comparing against an OpenClaw gateway
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
The engine speaks its own wire names (`model: insika:<agent>`, header
|
|
164
|
+
`X-Insika-Agent`), so OpenClaw's `loadtest-gateway.mjs` cannot be pointed at it
|
|
165
|
+
unmodified. For a shadow comparison, run `loadtest.rb` (section 4a) against each
|
|
166
|
+
side with identical `--agents`, `--concurrency`, `--iterations` and `--message`,
|
|
167
|
+
and diff the reports. What you still need in hand:
|
|
166
168
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
node scripts/loadtest-gateway.mjs --agents demo --concurrency 16 --iterations 3
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
Then run the exact same command with `OPENCLAW_GATEWAY_URL` pointing at the real
|
|
175
|
-
gateway, and diff the two reports. This is the shadow comparison the pilot needs.
|
|
176
|
-
|
|
177
|
-
**What the operator must have in hand** (this repo does not vendor OpenClaw):
|
|
178
|
-
|
|
179
|
-
- The OpenClaw checkout containing `scripts/loadtest-gateway.mjs` and Node installed.
|
|
180
|
-
- A **bearer token accepted by both** sides. For the engine that is
|
|
181
|
-
`OPENCLAW_GATEWAY_TOKEN` (see DEPLOY.md); point the gateway run at its own token.
|
|
182
|
-
- **The same agent id provisioned on both** sides (e.g. `demo`) so `model:
|
|
183
|
-
openclaw:<agent>` resolves on each. On the engine, provision via
|
|
184
|
-
`scripts/import_pack.rb`.
|
|
169
|
+
- A bearer token accepted by each side — `INSIKA_GATEWAY_TOKEN` for the engine
|
|
170
|
+
(see DEPLOY.md), the gateway's own token for the gateway run.
|
|
171
|
+
- **The same agent id provisioned on both** sides (e.g. `demo`). On the engine,
|
|
172
|
+
provision via `scripts/import_pack.rb`.
|
|
185
173
|
- The **same provider** (or an equivalent-latency one) behind each, otherwise you
|
|
186
174
|
are comparing providers, not engines.
|
|
187
175
|
- Both endpoints reachable from where you run the client, warmed up (hit `/up` on
|
data/docs/MEDIA.md
CHANGED
|
@@ -87,7 +87,7 @@ receive it (`channel.capabilities` on the request):
|
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
89
|
curl -X POST /v1/responses -H "Authorization: Bearer $TOKEN" -d '{
|
|
90
|
-
"model": "
|
|
90
|
+
"model": "insika:store-support", "user": "chat-7",
|
|
91
91
|
"input": "manda a foto do sofá da promoção",
|
|
92
92
|
"channel": { "capabilities": ["image_output", "audio_output"] }
|
|
93
93
|
}'
|