insika 0.7.0 → 0.9.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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +191 -0
  3. data/README.md +9 -6
  4. data/bin/insika +44 -3
  5. data/docs/AGENTS.md +74 -14
  6. data/docs/API.md +73 -0
  7. data/docs/ARCHITECTURE.md +45 -44
  8. data/docs/ARTIFACTS.md +42 -0
  9. data/docs/CHANNELS.md +19 -2
  10. data/docs/CONTEXT.md +86 -38
  11. data/docs/DEPLOY.md +27 -7
  12. data/docs/EVALS.md +98 -8
  13. data/docs/FACTS.md +4 -0
  14. data/docs/KNOWLEDGE.md +7 -0
  15. data/docs/LOADTEST.md +15 -27
  16. data/docs/MEDIA.md +1 -1
  17. data/docs/OBSERVABILITY.md +48 -4
  18. data/docs/POLICY.md +14 -5
  19. data/docs/RELEASING.md +4 -0
  20. data/docs/RUNNING-LOCAL.md +2 -2
  21. data/docs/SECURITY.md +28 -2
  22. data/docs/SOAK.md +1 -1
  23. data/docs/TOOLS.md +150 -32
  24. data/docs/prompts/ADD-TOOL.md +12 -2
  25. data/docs/prompts/DIAGNOSE-TURN.md +3 -0
  26. data/docs/prompts/GO-LIVE.md +6 -4
  27. data/lib/insika/agent_profile.rb +47 -10
  28. data/lib/insika/channels/web/widget.js +33 -0
  29. data/lib/insika/channels/web.rb +5 -2
  30. data/lib/insika/chat_builder.rb +90 -37
  31. data/lib/insika/commands/agent_payload.rb +1 -1
  32. data/lib/insika/commands/run_distillation.rb +5 -8
  33. data/lib/insika/commands/seed_session.rb +118 -0
  34. data/lib/insika/compaction.rb +196 -0
  35. data/lib/insika/context/builder.rb +35 -11
  36. data/lib/insika/context/fragment.rb +4 -1
  37. data/lib/insika/context/priority.rb +8 -0
  38. data/lib/insika/context/provider.rb +5 -0
  39. data/lib/insika/context/providers/briefing.rb +61 -29
  40. data/lib/insika/context/providers/fence_notice.rb +27 -0
  41. data/lib/insika/context/providers/knowledge.rb +7 -4
  42. data/lib/insika/context/providers/memory.rb +8 -4
  43. data/lib/insika/context/providers/session.rb +50 -10
  44. data/lib/insika/context_trace_store.rb +11 -1
  45. data/lib/insika/doctor.rb +213 -10
  46. data/lib/insika/dsl/runtime.rb +5 -0
  47. data/lib/insika/dsl.rb +6 -0
  48. data/lib/insika/edge_limiter.rb +4 -1
  49. data/lib/insika/env_schema.rb +5 -6
  50. data/lib/insika/errors.rb +1 -0
  51. data/lib/insika/evals/assertions.rb +92 -6
  52. data/lib/insika/evals/golden.rb +91 -2
  53. data/lib/insika/evals/runner.rb +20 -0
  54. data/lib/insika/evals/simulator.rb +11 -2
  55. data/lib/insika/evals/transport.rb +118 -16
  56. data/lib/insika/evidence.rb +79 -12
  57. data/lib/insika/executor.rb +94 -23
  58. data/lib/insika/fence.rb +96 -0
  59. data/lib/insika/golden_store.rb +3 -0
  60. data/lib/insika/loop_detector.rb +5 -34
  61. data/lib/insika/mcp_store.rb +5 -2
  62. data/lib/insika/mcp_tool_registry.rb +8 -1
  63. data/lib/insika/memory_store.rb +12 -0
  64. data/lib/insika/overlay_tool_registry.rb +5 -0
  65. data/lib/insika/prefix_fingerprint.rb +32 -27
  66. data/lib/insika/profile_source.rb +8 -0
  67. data/lib/insika/server/app.rb +43 -1
  68. data/lib/insika/server/rack_app.rb +2 -0
  69. data/lib/insika/server/responses.rb +35 -8
  70. data/lib/insika/session_store.rb +38 -5
  71. data/lib/insika/settings_store.rb +18 -2
  72. data/lib/insika/soak/runner.rb +4 -4
  73. data/lib/insika/spoken_transcript.rb +31 -0
  74. data/lib/insika/studio/app.rb +34 -8
  75. data/lib/insika/studio/forms.rb +29 -3
  76. data/lib/insika/studio/views/_agent_tab_config.erb +5 -1
  77. data/lib/insika/studio/views/session.erb +1 -1
  78. data/lib/insika/studio/views/settings.erb +11 -0
  79. data/lib/insika/studio/views/tool_edit.erb +6 -2
  80. data/lib/insika/telemetry/recorder.rb +61 -1
  81. data/lib/insika/templates/daily-digest/README.md +9 -0
  82. data/lib/insika/templates/research-analyst/agent.rb +10 -0
  83. data/lib/insika/tool_assembly.rb +21 -13
  84. data/lib/insika/tool_batch.rb +67 -0
  85. data/lib/insika/tool_definition.rb +73 -10
  86. data/lib/insika/tool_envelope.rb +102 -2
  87. data/lib/insika/tool_store.rb +9 -4
  88. data/lib/insika/tool_trace_store.rb +1 -1
  89. data/lib/insika/tool_usage_report.rb +172 -0
  90. data/lib/insika/tools/data_defined_tool.rb +1 -0
  91. data/lib/insika/tools/present.rb +122 -0
  92. data/lib/insika/tools/run_persona_eval.rb +6 -1
  93. data/lib/insika/tools/tool_search.rb +4 -2
  94. data/lib/insika/turn_budget.rb +91 -0
  95. data/lib/insika/turn_state.rb +13 -1
  96. data/lib/insika/version.rb +1 -1
  97. data/lib/insika/wiring/graph.rb +7 -0
  98. data/lib/insika/wiring/graph_chat.rb +4 -0
  99. data/lib/insika.rb +11 -0
  100. metadata +10 -1
@@ -21,7 +21,7 @@ module Insika
21
21
  def name = "tool_search"
22
22
 
23
23
  def initialize(catalog, deferred_allowed, chat, tool_registry:, event_stream:,
24
- checkpoint_store:, state:)
24
+ checkpoint_store:, state:, trace_recorder: nil)
25
25
  @catalog = catalog
26
26
  @deferred_allowed = Array(deferred_allowed).map(&:to_s)
27
27
  @chat = chat
@@ -29,6 +29,7 @@ module Insika
29
29
  @event_stream = event_stream
30
30
  @checkpoint_store = checkpoint_store
31
31
  @state = state
32
+ @trace_recorder = trace_recorder
32
33
  @promoted = [] # names already promoted IN THIS chat — idempotency
33
34
  super()
34
35
  end
@@ -59,7 +60,8 @@ module Insika
59
60
  @promoted << entry.name
60
61
  ToolEnvelope.new(tool, state: @state, checkpoint_store: @checkpoint_store,
61
62
  tool_registry: @tool_registry, timeout: timeout,
62
- skip_side_effects: Array(@state.skip_side_effects))
63
+ skip_side_effects: Array(@state.skip_side_effects),
64
+ event_stream: @event_stream, trace_recorder: @trace_recorder)
63
65
  rescue Insika::NotFoundError
64
66
  nil
65
67
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Insika
4
+ # The turn's tool-call counter — one object, three jobs: count, warn, abort.
5
+ #
6
+ # `max_tool_calls` was enforced but never announced: the model learned of the
7
+ # ceiling only when the turn DIED with `stage: :tool_limit`. A real run in
8
+ # `round1.db` (task d0421891) burned all 50 calls summing a 40-number list one
9
+ # `calc` at a time and delivered nothing — the model had no way to know it was
10
+ # spending a budget at all.
11
+ #
12
+ # So the budget speaks before it kills: at 10 / 5 / 2 calls remaining the
13
+ # engine appends a short notice and the model gets to converge on an answer.
14
+ #
15
+ # · **User turn, never system.** The system prefix stays byte-stable, so the
16
+ # Anthropic cache breakpoint (`chat_builder.rb`'s `apply_instructions`) keeps
17
+ # hitting. A notice in the prompt would bill a cache WRITE every turn.
18
+ # · **Batch boundary only** (ToolBatch), and **a halted batch receives
19
+ # nothing** — the two rules SteerInjector established for mid-turn appends.
20
+ # · **Off when there is no limit.** `max: nil` = count nothing, warn nothing,
21
+ # abort never.
22
+ #
23
+ # KNOWN REACH (measured against DeepSeek, 2026-08-30): a model that announces
24
+ # ONE batch bigger than the whole budget — 30 independent tool calls in a single
25
+ # assistant message — never reaches a boundary before the abort, so it gets no
26
+ # notice. Nothing can be appended mid-batch, so that shape is out of reach by
27
+ # construction; it is also the shape where the model already knows what it asked
28
+ # for. What this catches is the SEQUENTIAL burn, which is the one `round1.db`
29
+ # actually shows: 46 `calc` calls, one per step.
30
+ class TurnBudget
31
+ # The three notices, verbatim and escalating — one constant, so a transcript
32
+ # reader can recognize an engine sentence without an origin stamp (chat
33
+ # messages carry none). Keyed by calls REMAINING after the call being made.
34
+ NOTICES = {
35
+ 10 => "Tool budget: 10 of your %<max>d tool calls for this turn are left. " \
36
+ "Start converging — prefer one call that answers the question over several that circle it.",
37
+ 5 => "Tool budget: 5 tool calls left in this turn. " \
38
+ "Drop anything optional and gather only what the answer actually needs.",
39
+ 2 => "Tool budget: 2 tool calls left in this turn. " \
40
+ "Consolidate what you already have and answer now — do not start new work."
41
+ }.freeze
42
+
43
+ def self.notice(remaining, max) = format(NOTICES.fetch(remaining), max: max)
44
+
45
+ # chat: the turn's chat — must answer #add_message (the boundary append).
46
+ # max: the profile's max_tool_calls. nil = no budget: no notice, no abort.
47
+ # emit: ->(type, data) — the Executor's emitter, bound to the task.
48
+ def initialize(chat:, max:, emit:)
49
+ @chat = chat
50
+ @max = max
51
+ @emit = emit
52
+ @calls = 0
53
+ @batch = ToolBatch.new
54
+ @pending = nil # a threshold was crossed; waiting for the batch boundary
55
+ @warned = [] # thresholds already spent (each fires at most once a turn)
56
+ end
57
+
58
+ # From ChatBuilder's before_tool_call, FIRST thing: counts the call about to
59
+ # run and raises when it is past the ceiling. The raise is the pre-existing
60
+ # guard-rail, moved here so the count has exactly one owner.
61
+ def tool_call
62
+ return if @max.nil?
63
+
64
+ @calls += 1
65
+ if @calls > @max
66
+ raise Insika::TimeoutError.new("tool call limit exceeded (#{@max})", stage: :tool_limit)
67
+ end
68
+
69
+ remaining = @max - @calls
70
+ @pending = remaining if NOTICES.key?(remaining) && !@warned.include?(remaining)
71
+ end
72
+
73
+ # From ChatBuilder's after_tool_result, with the RAW result.
74
+ def tool_result(result) = @batch.halt!(result)
75
+
76
+ # RubyLLM after_message: delivers the armed notice the moment the batch of
77
+ # tool results closes.
78
+ def message_ended(message)
79
+ return unless @batch.closed?(message)
80
+ return if @pending.nil?
81
+
82
+ remaining = @pending
83
+ @pending = nil
84
+ return if @batch.halted? # nothing will read it (halt_when): drop, never deliver
85
+
86
+ @warned << remaining
87
+ @chat.add_message(role: :user, content: self.class.notice(remaining, @max))
88
+ @emit.call(:tool_budget_warned, { remaining: remaining, max: @max })
89
+ end
90
+ end
91
+ end
@@ -61,9 +61,16 @@ module Insika
61
61
  # context via an evidence-declared tool (the envelope
62
62
  # appends; the validator/enforcer read it). Built per
63
63
  # turn by the Executor; nil = no session/no evidence.
64
- :evidence_attachments, # [ {type, url, caption} ] hoarded by the
64
+ :evidence_attachments, # [ {type, url, caption, id} ] hoarded by the
65
65
  # envelope this turn; read by the Executor at stage 8
66
66
  # for the channel delivery. Reset per turn.
67
+ :presentations, # [ {component, title, items: [{id,url,caption}]} ] —
68
+ # what a presentation tool SELECTED this turn, in call
69
+ # order. Non-empty = the channel delivery sends these
70
+ # instead of every hoarded card. Reset per turn.
71
+ :fence_max_chars, # per-leaf cap the ToolEnvelope applies when the
72
+ # agent has `fencing` on — the platform's
73
+ # `fencing.max_chars`, set per turn by the Executor.
67
74
  :context_trace_entry # the sanitized trace entry parked at
68
75
  # prepare_turn (fingerprints + invalidation_reason);
69
76
  # the stage-8 stamp merges the cache-hit fields into
@@ -167,6 +174,10 @@ module Insika
167
174
  # nil = concurrency off: no gate, no overhead, serial execution unchanged.
168
175
  attr_accessor :tool_gate
169
176
 
177
+ # Turns of one session already run serially through SessionActor. This gate
178
+ # prevents overlapping writes within a parallel batch of the current turn.
179
+ attr_accessor :side_effect_gate
180
+
170
181
  # parallel tool calls, resolved PER TURN and read by ChatBuilder
171
182
  # (whether to hand the gem `concurrency:`) and ToolAssembly (the gate's size).
172
183
  #
@@ -199,6 +210,7 @@ module Insika
199
210
  @output_parts = []
200
211
  @channel_capabilities = []
201
212
  @evidence_attachments = []
213
+ @presentations = []
202
214
  # Fiber storage is INHERITED by fibers created later, so a turn spawned from
203
215
  # inside a tool call (a subagent child) would start out carrying its
204
216
  # parent's correlation. Clearing at turn start keeps a child from keying its
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Insika
4
- VERSION = "0.7.0"
4
+ VERSION = "0.9.0"
5
5
  end
@@ -422,6 +422,13 @@ module Insika
422
422
  bus = Insika::CommandBus.new
423
423
  bus.register(:create_session,
424
424
  Insika::Commands::CreateSession.new(session_store: spine.session_store, event_stream: spine.event_stream))
425
+ # A snapshot loaded into a conversation before its first turn — what an eval
426
+ # case declares as `state:`. The HTTP route and the in-process eval transport
427
+ # both dispatch this one command.
428
+ bus.register(:seed_session,
429
+ Insika::Commands::SeedSession.new(session_store: spine.session_store,
430
+ memory_store: spine.memory_store,
431
+ event_stream: spine.event_stream))
425
432
  bus.register(:cancel_task,
426
433
  Insika::Commands::CancelTask.new(task_store: spine.task_store, executor: executor))
427
434
  bus.register(:pause_task,
@@ -18,6 +18,10 @@ module Insika
18
18
  @graph = graph
19
19
  end
20
20
 
21
+ # The graph this seam speaks for — an eval transport reaches its bus and
22
+ # event stream through here.
23
+ attr_reader :graph
24
+
21
25
  # One turn, in-process -> the assistant's text. `agent:` is REQUIRED —
22
26
  # this seam has no notion of "the default agent" (that is a DSL::Runtime
23
27
  # concept, filled in by its own caller before delegating here). Raises
data/lib/insika.rb CHANGED
@@ -99,6 +99,7 @@ require_relative "insika/context/catalog_provider"
99
99
  require_relative "insika/context/builder"
100
100
  require_relative "insika/context/providers/request"
101
101
  require_relative "insika/context/providers/prompt"
102
+ require_relative "insika/context/providers/fence_notice"
102
103
  require_relative "insika/context/providers/skill"
103
104
  require_relative "insika/context/providers/skill_trigger"
104
105
  require_relative "insika/context/providers/tool_search"
@@ -146,6 +147,7 @@ require_relative "insika/skill_store"
146
147
  require_relative "insika/secret_masking"
147
148
  require_relative "insika/tool_store"
148
149
  require_relative "insika/tool_trace_store"
150
+ require_relative "insika/tool_usage_report"
149
151
  require_relative "insika/context_trace_store"
150
152
  # the model-visible payload of one ask + its durable trace —
151
153
  # "model-visible means logged" (the conformance suite's reconstruction half).
@@ -229,6 +231,7 @@ require_relative "insika/system_file_store"
229
231
  require_relative "insika/recovery"
230
232
  require_relative "insika/command_bus"
231
233
  require_relative "insika/commands/create_session"
234
+ require_relative "insika/commands/seed_session"
232
235
  require_relative "insika/commands/cancel_task"
233
236
  require_relative "insika/commands/pause_task"
234
237
  require_relative "insika/commands/approve_action"
@@ -275,6 +278,10 @@ require_relative "insika/commands/revoke_contact"
275
278
  # the report destination's Studio mutation — delete one artifact
276
279
  # (a bus command, like every Studio mutation).
277
280
  require_relative "insika/commands/delete_artifact"
281
+ # in-session compaction (RFC-0044) — the summarizer (pure, injected
282
+ # ask) and the boundary math; the trigger lives in the Executor, the
283
+ # persistence in SessionStore, the read path in the Session provider.
284
+ require_relative "insika/compaction"
278
285
  # session distillation — the distiller (pure, injected ask)
279
286
  # and the proposal store (proposals + the latched dedup ledger + the
280
287
  # per-session distilled marker).
@@ -332,12 +339,16 @@ require_relative "insika/session_actor"
332
339
  require_relative "insika/skill_catalog"
333
340
  require_relative "insika/tool_catalog"
334
341
  require_relative "insika/steer_injector"
342
+ require_relative "insika/tool_batch"
335
343
  require_relative "insika/loop_detector"
344
+ require_relative "insika/turn_budget"
336
345
  require_relative "insika/balloon_splitter"
337
346
  require_relative "insika/turn_output"
338
347
  require_relative "insika/turn_state"
339
348
  require_relative "insika/turn_timing"
340
349
  require_relative "insika/capability/resolved_tool"
350
+ require_relative "insika/fence"
351
+ require_relative "insika/spoken_transcript"
341
352
  require_relative "insika/tool_envelope"
342
353
  require_relative "insika/tool_assembly"
343
354
  require_relative "insika/chat_builder"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: insika
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guilherme Lages Santos
@@ -296,6 +296,7 @@ files:
296
296
  - lib/insika/commands/run_harvest.rb
297
297
  - lib/insika/commands/run_refinement.rb
298
298
  - lib/insika/commands/seed_demo_data.rb
299
+ - lib/insika/commands/seed_session.rb
299
300
  - lib/insika/commands/send_message.rb
300
301
  - lib/insika/commands/session_purge.rb
301
302
  - lib/insika/commands/set_agent_tools.rb
@@ -311,6 +312,7 @@ files:
311
312
  - lib/insika/commands/write_golden.rb
312
313
  - lib/insika/commands/write_skill.rb
313
314
  - lib/insika/commands/write_system_file.rb
315
+ - lib/insika/compaction.rb
314
316
  - lib/insika/config_store.rb
315
317
  - lib/insika/contact_store.rb
316
318
  - lib/insika/context/builder.rb
@@ -319,6 +321,7 @@ files:
319
321
  - lib/insika/context/priority.rb
320
322
  - lib/insika/context/provider.rb
321
323
  - lib/insika/context/providers/briefing.rb
324
+ - lib/insika/context/providers/fence_notice.rb
322
325
  - lib/insika/context/providers/knowledge.rb
323
326
  - lib/insika/context/providers/memory.rb
324
327
  - lib/insika/context/providers/prompt.rb
@@ -360,6 +363,7 @@ files:
360
363
  - lib/insika/event_stream.rb
361
364
  - lib/insika/evidence.rb
362
365
  - lib/insika/executor.rb
366
+ - lib/insika/fence.rb
363
367
  - lib/insika/followup_engine.rb
364
368
  - lib/insika/followup_policy.rb
365
369
  - lib/insika/followup_store.rb
@@ -486,6 +490,7 @@ files:
486
490
  - lib/insika/soak/envelope.rb
487
491
  - lib/insika/soak/report.rb
488
492
  - lib/insika/soak/runner.rb
493
+ - lib/insika/spoken_transcript.rb
489
494
  - lib/insika/steer_injector.rb
490
495
  - lib/insika/store.rb
491
496
  - lib/insika/stores/memory.rb
@@ -562,6 +567,7 @@ files:
562
567
  - lib/insika/token_estimator.rb
563
568
  - lib/insika/token_store.rb
564
569
  - lib/insika/tool_assembly.rb
570
+ - lib/insika/tool_batch.rb
565
571
  - lib/insika/tool_catalog.rb
566
572
  - lib/insika/tool_definition.rb
567
573
  - lib/insika/tool_envelope.rb
@@ -570,6 +576,7 @@ files:
570
576
  - lib/insika/tool_registry.rb
571
577
  - lib/insika/tool_store.rb
572
578
  - lib/insika/tool_trace_store.rb
579
+ - lib/insika/tool_usage_report.rb
573
580
  - lib/insika/tools/a2a_remote.rb
574
581
  - lib/insika/tools/agent_enum.rb
575
582
  - lib/insika/tools/concurrency.rb
@@ -577,6 +584,7 @@ files:
577
584
  - lib/insika/tools/generate_image.rb
578
585
  - lib/insika/tools/load_knowledge.rb
579
586
  - lib/insika/tools/load_skill.rb
587
+ - lib/insika/tools/present.rb
580
588
  - lib/insika/tools/remember.rb
581
589
  - lib/insika/tools/run_persona_eval.rb
582
590
  - lib/insika/tools/save_artifact.rb
@@ -587,6 +595,7 @@ files:
587
595
  - lib/insika/tools/tool_search.rb
588
596
  - lib/insika/tools/tts.rb
589
597
  - lib/insika/tools/update_briefing.rb
598
+ - lib/insika/turn_budget.rb
590
599
  - lib/insika/turn_output.rb
591
600
  - lib/insika/turn_state.rb
592
601
  - lib/insika/turn_timing.rb