insika 0.2.0 → 0.3.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 +145 -17
- data/README.md +7 -1
- data/bin/insika +211 -8
- data/docs/AGENTS.md +377 -2
- data/docs/API.md +56 -0
- data/docs/CHANNELS.md +99 -9
- data/docs/CONTEXT.md +127 -16
- data/docs/DEPLOY.md +5 -2
- data/docs/EVALS.md +19 -0
- data/docs/FACTS.md +135 -0
- data/docs/HARVEST.md +117 -0
- data/docs/LOADTEST.md +14 -8
- data/docs/OBSERVABILITY.md +54 -0
- data/docs/REFINEMENT.md +3 -3
- data/docs/RELEASING.md +27 -0
- data/docs/RUNNING-LOCAL.md +3 -3
- data/docs/SECURITY.md +61 -0
- data/docs/SKILLS.md +2 -2
- data/docs/SOAK.md +127 -0
- data/docs/TOOLS.md +70 -2
- data/docs/domain.md +115 -0
- data/docs/index.md +1 -1
- data/lib/insika/agent_profile.rb +145 -10
- 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 +118 -13
- data/lib/insika/channels/relay.rb +77 -3
- data/lib/insika/channels/web.rb +2 -2
- data/lib/insika/chat_builder.rb +84 -1
- data/lib/insika/checkpoint_store.rb +16 -0
- data/lib/insika/commands/agent_payload.rb +3 -3
- data/lib/insika/commands/cancel_followup.rb +49 -0
- data/lib/insika/commands/delete_tenant_data.rb +95 -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/reject_harvest.rb +38 -0
- data/lib/insika/commands/resolve_proposal.rb +108 -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/send_message.rb +103 -6
- data/lib/insika/commands/session_purge.rb +67 -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 +3 -0
- data/lib/insika/context/provider.rb +17 -3
- data/lib/insika/context/providers/briefing.rb +96 -0
- data/lib/insika/context/providers/memory.rb +16 -7
- data/lib/insika/context/providers/prompt.rb +30 -2
- 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/distill.rb +224 -0
- data/lib/insika/distill_engine.rb +169 -0
- data/lib/insika/doctor.rb +716 -6
- data/lib/insika/dsl/runtime.rb +9 -3
- data/lib/insika/dsl/server_boot.rb +70 -1
- data/lib/insika/dsl.rb +83 -3
- data/lib/insika/edge_limiter.rb +28 -7
- data/lib/insika/env_schema.rb +9 -2
- data/lib/insika/errors.rb +11 -0
- data/lib/insika/evals/assertions.rb +3 -2
- data/lib/insika/evals/pairwise.rb +18 -6
- data/lib/insika/evidence.rb +183 -0
- data/lib/insika/executor.rb +687 -70
- data/lib/insika/followup_engine.rb +207 -0
- data/lib/insika/followup_policy.rb +221 -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 +1 -1
- 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/media.rb +298 -0
- data/lib/insika/memory_audit_store.rb +85 -0
- data/lib/insika/memory_store.rb +264 -23
- data/lib/insika/message_origin.rb +6 -1
- data/lib/insika/model_visible.rb +87 -0
- data/lib/insika/model_visible_trace_store.rb +66 -0
- data/lib/insika/onboarding.rb +6 -1
- data/lib/insika/outbox_store.rb +42 -4
- data/lib/insika/outcome_store.rb +147 -0
- 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 +22 -1
- 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 +281 -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/schema_guard.rb +35 -0
- data/lib/insika/server/app.rb +144 -4
- data/lib/insika/server/rack_app.rb +16 -0
- 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 +4 -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/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 +875 -14
- data/lib/insika/studio/assets/dist/application.css +1 -1
- data/lib/insika/studio/assets/dist/application.js +22 -22
- data/lib/insika/studio/assets/dist/favicon.svg +6 -0
- data/lib/insika/studio/forms.rb +262 -5
- data/lib/insika/studio/nav_icons.rb +6 -1
- data/lib/insika/studio/views/agent_detail.erb +627 -84
- data/lib/insika/studio/views/agents.erb +10 -6
- data/lib/insika/studio/views/approvals.erb +4 -1
- 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 +4 -1
- 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 +2 -1
- data/lib/insika/studio/views/layout.erb +1 -0
- 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 +56 -1
- data/lib/insika/studio/views/settings.erb +2 -0
- data/lib/insika/studio/views/skills.erb +0 -1
- 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 +0 -1
- data/lib/insika/task_store.rb +21 -1
- data/lib/insika/testing/store_contract.rb +27 -6
- data/lib/insika/tick.rb +25 -1
- 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 +44 -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_state.rb +38 -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 +300 -13
- data/lib/insika.rb +92 -3
- metadata +77 -1
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module Insika
|
|
6
|
+
# the trigger — finds due sessions and mines them. It
|
|
7
|
+
# spawns a worker fiber (supervisor child, `Tick#start` shape) whose loop
|
|
8
|
+
# claims a window and runs ONE due session's harvest ON the worker fiber —
|
|
9
|
+
# off the tick's critical path and off every customer turn's path (D2: the
|
|
10
|
+
# mining model call never blocks a customer turn).
|
|
11
|
+
#
|
|
12
|
+
# The scan uses the engine default `idle_hours` (24) as the LOWER BOUND; the
|
|
13
|
+
# per-agent value is re-checked inside RunHarvest. The loop-stop (the
|
|
14
|
+
# H-harvest kill) is the profile's `enabled: false` — the engine only
|
|
15
|
+
# respects data (D4).
|
|
16
|
+
class HarvestEngine
|
|
17
|
+
SCOPE = "harvest_engine"
|
|
18
|
+
KEY = "claim"
|
|
19
|
+
DEFAULT_WINDOW = 300 # seconds — the O(n) scan never rides the 60 s loop
|
|
20
|
+
DEFAULT_IDLE_HOURS = 24
|
|
21
|
+
DEFAULT_MIN_MESSAGES = 3
|
|
22
|
+
|
|
23
|
+
def initialize(store:, harvest_store:, session_store:, runner:,
|
|
24
|
+
profiles: nil, logger: nil, window: DEFAULT_WINDOW,
|
|
25
|
+
idle_hours: DEFAULT_IDLE_HOURS, sleeper: nil)
|
|
26
|
+
@store = store
|
|
27
|
+
@harvest_store = harvest_store
|
|
28
|
+
@session_store = session_store
|
|
29
|
+
@runner = runner
|
|
30
|
+
@profiles = profiles # profile source; nil/empty = nothing mines (parity)
|
|
31
|
+
@logger = logger
|
|
32
|
+
@window = window.to_i
|
|
33
|
+
@idle_hours = idle_hours.to_i
|
|
34
|
+
@sleeper = sleeper || method(:default_sleep)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The deployment root swaps in its own runner (the bus instance carrying
|
|
38
|
+
# the negative list + the settings-backed miner factory); the base graph's
|
|
39
|
+
# inert one keeps the loop honest until then.
|
|
40
|
+
attr_accessor :runner
|
|
41
|
+
|
|
42
|
+
# The supervisor child (tick.rb:69-83's shape). Returns false when
|
|
43
|
+
# idle_hours <= 0 (the engine default OFF switch — parity) OR when no
|
|
44
|
+
# profile declares an enabled harvest (a scan with nothing to mine would
|
|
45
|
+
# re-read every session record every window forever).
|
|
46
|
+
def start(parent:)
|
|
47
|
+
return false unless harvestable?
|
|
48
|
+
|
|
49
|
+
@task = parent.async do |t|
|
|
50
|
+
t.annotate("insika-harvest")
|
|
51
|
+
loop do
|
|
52
|
+
@sleeper.call(@window)
|
|
53
|
+
run_once
|
|
54
|
+
rescue StandardError => e
|
|
55
|
+
log(:warn, "harvest pass failed: #{e.class}: #{e.message}")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# One pass. -> { claimed: false } | { claimed: true, mined: N,
|
|
62
|
+
# skipped: N, errors: N }
|
|
63
|
+
def run_once
|
|
64
|
+
return { claimed: false } unless harvestable?
|
|
65
|
+
return { claimed: false } unless claim_window
|
|
66
|
+
|
|
67
|
+
session = due_sessions.first # one model call per pass; the window paces
|
|
68
|
+
return { claimed: true, mined: 0, skipped: 0, errors: 0 } if session.nil?
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
outcome = @runner.call(Insika::Command.build(:run_harvest,
|
|
72
|
+
{ agent: session[:agent],
|
|
73
|
+
session_ids: [session[:id]] }))
|
|
74
|
+
if outcome[:mined]
|
|
75
|
+
{ claimed: true, mined: 1, skipped: 0, errors: 0 }
|
|
76
|
+
else
|
|
77
|
+
{ claimed: true, mined: 0, skipped: 1, errors: 0 }
|
|
78
|
+
end
|
|
79
|
+
rescue StandardError
|
|
80
|
+
# a broken session must not hold the pass; the marker discipline
|
|
81
|
+
# keeps it re-runnable.
|
|
82
|
+
{ claimed: true, mined: 0, skipped: 0, errors: 1 }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
# The engine is inert unless the switch is on (idle_hours > 0) AND at
|
|
89
|
+
# least one profile declares an enabled harvest (the loop-stop: the
|
|
90
|
+
# H-harvest kill is `enabled: false`). The grounding matcher is a
|
|
91
|
+
# per-session pre-filter (D3), never an engine-wide switch.
|
|
92
|
+
def harvestable?
|
|
93
|
+
return false if @idle_hours <= 0
|
|
94
|
+
return false if @profiles.nil?
|
|
95
|
+
|
|
96
|
+
profiles_list.any? do |p|
|
|
97
|
+
config = p.respond_to?(:harvest) ? p.harvest : nil
|
|
98
|
+
config && Coercion.truthy?(config["enabled"])
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def matcher_configured?(profile)
|
|
103
|
+
grounding = Grounding.parse(profile.grounding)
|
|
104
|
+
grounding && grounding.matcher.sku?
|
|
105
|
+
rescue StandardError
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def profiles_list
|
|
110
|
+
return @profiles.values if @profiles.respond_to?(:values)
|
|
111
|
+
|
|
112
|
+
@profiles.respond_to?(:all) ? @profiles.all : []
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Sessions with `vars["agent"]`, idle past the PROFILE's own bounds, with
|
|
116
|
+
# enough messages, not yet mined, whose agent declares harvest.enabled + a
|
|
117
|
+
# matcher. Oldest first. One session per pass is enough (each is a model
|
|
118
|
+
# call).
|
|
119
|
+
#
|
|
120
|
+
# The scan reads the per-agent idle_hours/min_messages (the engine
|
|
121
|
+
# defaults only when the pack does not declare them): a session the pack's
|
|
122
|
+
# bounds reject is NOT elected, so it can never sit at the head of the
|
|
123
|
+
# queue — RunHarvest would skip it, write no marker, and it would be
|
|
124
|
+
# "the oldest" forever, starving every later session (the review fix).
|
|
125
|
+
def due_sessions
|
|
126
|
+
due = []
|
|
127
|
+
@session_store.each_id do |id|
|
|
128
|
+
session = @session_store.find(id)
|
|
129
|
+
next unless session
|
|
130
|
+
next if @harvest_store.mined?(id)
|
|
131
|
+
next if Coercion.blank?(session.vars["agent"])
|
|
132
|
+
|
|
133
|
+
profile = @profiles[session.vars["agent"].to_s]
|
|
134
|
+
next unless profile
|
|
135
|
+
next unless harvest_declared?(profile)
|
|
136
|
+
|
|
137
|
+
config = profile.respond_to?(:harvest) ? profile.harvest : nil
|
|
138
|
+
idle_hours = config&.key?("idle_hours") ? config["idle_hours"].to_i : @idle_hours
|
|
139
|
+
min_messages = config&.key?("min_messages") ? config["min_messages"].to_i : DEFAULT_MIN_MESSAGES
|
|
140
|
+
next unless aged?(session.updated_at, Time.now.utc - idle_hours * 3600)
|
|
141
|
+
next if session.messages.size < min_messages
|
|
142
|
+
|
|
143
|
+
due << { id: id, agent: session.vars["agent"].to_s, updated_at: session.updated_at }
|
|
144
|
+
end
|
|
145
|
+
due.sort_by { |s| s[:updated_at].to_s }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def harvest_declared?(profile)
|
|
149
|
+
config = profile.respond_to?(:harvest) ? profile.harvest : nil
|
|
150
|
+
return false unless config && Coercion.truthy?(config["enabled"])
|
|
151
|
+
|
|
152
|
+
matcher_configured?(profile)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def aged?(updated_at, cutoff)
|
|
156
|
+
return false if Coercion.blank?(updated_at)
|
|
157
|
+
|
|
158
|
+
Time.iso8601(updated_at.to_s) <= cutoff
|
|
159
|
+
rescue ArgumentError
|
|
160
|
+
false
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# One worker per window across N workers — the tick's claim idiom.
|
|
164
|
+
def claim_window
|
|
165
|
+
now = Time.now.utc
|
|
166
|
+
@store.transaction do
|
|
167
|
+
current = @store.get(SCOPE, KEY)
|
|
168
|
+
last = current && begin
|
|
169
|
+
Time.iso8601(current["claimed_at"].to_s)
|
|
170
|
+
rescue ArgumentError
|
|
171
|
+
nil # a corrupted claim is not a claim — take the window
|
|
172
|
+
end
|
|
173
|
+
if last.nil? || (now - last) >= @window
|
|
174
|
+
@store.set(SCOPE, KEY, { "claimed_at" => now.iso8601 })
|
|
175
|
+
true
|
|
176
|
+
else
|
|
177
|
+
false
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def default_sleep(seconds)
|
|
183
|
+
task = defined?(Async::Task) ? Async::Task.current? : nil
|
|
184
|
+
task ? task.sleep(seconds) : sleep(seconds)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def log(level, message)
|
|
188
|
+
@logger&.public_send(level, "[harvest] #{message}")
|
|
189
|
+
rescue StandardError
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|