insika 0.1.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 +199 -5
- data/README.md +8 -2
- data/bin/insika +231 -13
- data/docs/AGENTS.md +505 -6
- data/docs/API.md +56 -0
- data/docs/CHANNELS.md +100 -10
- data/docs/CONTEXT.md +147 -19
- data/docs/DEPLOY.md +34 -11
- data/docs/EMBEDDING.md +11 -7
- data/docs/EVALS.md +20 -1
- data/docs/FACTS.md +135 -0
- data/docs/HARVEST.md +117 -0
- data/docs/LOADTEST.md +17 -10
- data/docs/OBSERVABILITY.md +65 -2
- data/docs/REFINEMENT.md +9 -9
- data/docs/RELEASING.md +34 -7
- data/docs/RUNNING-LOCAL.md +4 -4
- data/docs/SECURITY.md +85 -11
- data/docs/SKILLS.md +189 -3
- data/docs/SOAK.md +127 -0
- data/docs/TOOLS.md +70 -2
- data/docs/WHY.md +1 -1
- data/docs/WORKFLOWS.md +2 -2
- data/docs/domain.md +115 -0
- data/docs/index.md +2 -2
- data/docs/onboarding/start.md +1 -1
- data/lib/insika/agent_profile.rb +228 -26
- data/lib/insika/alert_dispatcher.rb +139 -0
- data/lib/insika/balloon_splitter.rb +102 -0
- data/lib/insika/baseline_store.rb +2 -2
- data/lib/insika/budget_ledger.rb +166 -0
- data/lib/insika/cache_series_store.rb +49 -0
- data/lib/insika/channel_delivery.rb +132 -24
- data/lib/insika/channel_registry.rb +1 -1
- data/lib/insika/channels/relay.rb +80 -6
- data/lib/insika/channels/web/widget.js +2 -2
- data/lib/insika/channels/web.rb +9 -9
- data/lib/insika/channels/webhook.rb +58 -0
- data/lib/insika/chat_builder.rb +145 -13
- data/lib/insika/checkpoint_store.rb +16 -0
- data/lib/insika/circuit_state.rb +114 -0
- data/lib/insika/coercion.rb +8 -0
- data/lib/insika/commands/agent_payload.rb +6 -4
- data/lib/insika/commands/cancel_followup.rb +49 -0
- data/lib/insika/commands/create_agent.rb +2 -2
- data/lib/insika/commands/create_session.rb +1 -1
- data/lib/insika/commands/delete_llm_provider.rb +1 -1
- data/lib/insika/commands/delete_skill.rb +43 -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 +12 -12
- data/lib/insika/commands/import_mcp_tools.rb +1 -1
- data/lib/insika/commands/import_tools.rb +4 -4
- data/lib/insika/commands/issue_tenant_token.rb +41 -0
- 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/resolve_refinement.rb +1 -1
- data/lib/insika/commands/revoke_contact.rb +49 -0
- data/lib/insika/commands/revoke_token.rb +39 -0
- data/lib/insika/commands/rollback_harvest.rb +86 -0
- data/lib/insika/commands/rotate_tenant_token.rb +43 -0
- data/lib/insika/commands/run_distillation.rb +186 -0
- data/lib/insika/commands/run_harvest.rb +393 -0
- data/lib/insika/commands/run_refinement.rb +5 -5
- data/lib/insika/commands/send_message.rb +112 -15
- data/lib/insika/commands/session_purge.rb +67 -0
- data/lib/insika/commands/set_agent_tools.rb +1 -1
- data/lib/insika/commands/set_skill_agents.rb +60 -19
- data/lib/insika/commands/trigger_workflow.rb +1 -1
- data/lib/insika/commands/update_agent.rb +1 -1
- data/lib/insika/commands/write_data_tool.rb +1 -1
- data/lib/insika/commands/write_golden.rb +1 -1
- data/lib/insika/commands/write_skill.rb +19 -9
- data/lib/insika/config_store.rb +8 -4
- data/lib/insika/contact_store.rb +183 -0
- data/lib/insika/context/builder.rb +23 -5
- data/lib/insika/context/fragment.rb +31 -3
- data/lib/insika/context/priority.rb +6 -2
- 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/request.rb +1 -1
- data/lib/insika/context/providers/session.rb +17 -2
- data/lib/insika/context/providers/skill.rb +7 -1
- data/lib/insika/context/providers/skill_trigger.rb +128 -0
- data/lib/insika/context/providers/tool_search.rb +2 -0
- data/lib/insika/context_trace_store.rb +128 -0
- data/lib/insika/delegation_store.rb +2 -2
- data/lib/insika/distill.rb +224 -0
- data/lib/insika/distill_engine.rb +169 -0
- data/lib/insika/doctor.rb +962 -7
- data/lib/insika/dsl/runtime.rb +20 -11
- data/lib/insika/dsl/server_boot.rb +74 -4
- data/lib/insika/dsl/system.rb +1 -1
- data/lib/insika/dsl.rb +152 -15
- data/lib/insika/edge_limiter.rb +167 -8
- data/lib/insika/egress_guard.rb +3 -3
- data/lib/insika/env_schema.rb +22 -12
- data/lib/insika/errors.rb +72 -5
- data/lib/insika/evals/assertions.rb +15 -14
- data/lib/insika/evals/baseline.rb +3 -3
- data/lib/insika/evals/golden.rb +8 -8
- data/lib/insika/evals/judge.rb +7 -7
- data/lib/insika/evals/pairwise.rb +21 -9
- data/lib/insika/evals/report.rb +2 -2
- data/lib/insika/evals/runner.rb +6 -6
- data/lib/insika/evals/transport.rb +2 -2
- data/lib/insika/event_stream.rb +23 -5
- data/lib/insika/evidence.rb +183 -0
- data/lib/insika/executor.rb +1092 -160
- 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/frontmatter.rb +1 -1
- 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 +3 -3
- 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/http_client.rb +3 -3
- data/lib/insika/inbound_log.rb +1 -1
- data/lib/insika/llm_configurator.rb +3 -3
- data/lib/insika/loop_detector.rb +143 -0
- data/lib/insika/mcp_http_client.rb +4 -4
- data/lib/insika/mcp_tool_ingestor.rb +6 -6
- 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 +8 -3
- data/lib/insika/model_resolver.rb +1 -1
- data/lib/insika/model_selection.rb +5 -4
- data/lib/insika/model_visible.rb +87 -0
- data/lib/insika/model_visible_trace_store.rb +66 -0
- data/lib/insika/onboarding.rb +8 -3
- data/lib/insika/outbox_store.rb +44 -6
- data/lib/insika/outcome_store.rb +147 -0
- data/lib/insika/overlay_tool_registry.rb +3 -4
- data/lib/insika/pack.rb +3 -3
- data/lib/insika/pack_importer.rb +17 -15
- 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/pending_action_store.rb +1 -1
- data/lib/insika/plugin/loader.rb +2 -2
- data/lib/insika/policy/policy.rb +1 -1
- data/lib/insika/prefix_fingerprint.rb +58 -0
- data/lib/insika/profile_source.rb +34 -7
- data/lib/insika/proposal_store.rb +271 -0
- data/lib/insika/provider_error_classifier.rb +160 -0
- data/lib/insika/queue_policy.rb +6 -3
- data/lib/insika/recovery.rb +47 -6
- data/lib/insika/refinement/candidate.rb +4 -4
- data/lib/insika/refinement/evidence_collector.rb +6 -6
- data/lib/insika/refinement/gate.rb +7 -7
- data/lib/insika/refinement/panel.rb +7 -7
- data/lib/insika/refinement/proposer.rb +10 -10
- data/lib/insika/refinement_store.rb +12 -12
- data/lib/insika/reliability.rb +211 -0
- data/lib/insika/retention.rb +281 -0
- data/lib/insika/routing.rb +101 -0
- data/lib/insika/safety/config.rb +46 -6
- data/lib/insika/safety/corpus.rb +255 -0
- data/lib/insika/safety/detectors.rb +34 -115
- data/lib/insika/safety/factory.rb +18 -5
- 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 +20 -5
- data/lib/insika/safety/moderator.rb +19 -11
- data/lib/insika/safety/output_filter.rb +10 -6
- data/lib/insika/safety/output_validator.rb +13 -7
- data/lib/insika/safety/safe_responses.rb +1 -1
- data/lib/insika/sandbox/boundary.rb +2 -2
- data/lib/insika/sandbox.rb +1 -1
- data/lib/insika/schema_guard.rb +35 -0
- data/lib/insika/server/app.rb +366 -54
- data/lib/insika/server/boot.rb +4 -4
- data/lib/insika/server/rack_app.rb +31 -7
- data/lib/insika/server/responses.rb +58 -9
- data/lib/insika/server/tenant_auth.rb +61 -0
- data/lib/insika/session_actor.rb +11 -7
- data/lib/insika/session_store.rb +66 -3
- data/lib/insika/settings_store.rb +15 -5
- data/lib/insika/shadow_pair_store.rb +258 -0
- data/lib/insika/shutdown.rb +4 -4
- data/lib/insika/skill_catalog.rb +131 -20
- data/lib/insika/skill_store.rb +70 -22
- 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 +1 -1
- data/lib/insika/store.rb +11 -2
- data/lib/insika/stores/memory.rb +6 -0
- data/lib/insika/stores/sqlite.rb +8 -0
- data/lib/insika/studio/app.rb +1058 -75
- data/lib/insika/studio/assets/dist/application.css +1 -1
- data/lib/insika/studio/assets/dist/application.js +27 -26
- data/lib/insika/studio/assets/dist/favicon.svg +6 -0
- data/lib/insika/studio/forms.rb +274 -22
- data/lib/insika/studio/nav_icons.rb +7 -2
- data/lib/insika/studio/views/_message.erb +2 -2
- data/lib/insika/studio/views/agent_detail.erb +629 -86
- data/lib/insika/studio/views/agents.erb +11 -7
- 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/refinement.erb +4 -4
- data/lib/insika/studio/views/session.erb +133 -3
- data/lib/insika/studio/views/settings.erb +9 -12
- data/lib/insika/studio/views/skills.erb +66 -12
- 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/subagent_graph.rb +3 -3
- data/lib/insika/task_actor.rb +3 -3
- data/lib/insika/task_store.rb +22 -2
- data/lib/insika/telemetry/pricing.rb +3 -3
- data/lib/insika/telemetry/recorder.rb +1 -1
- data/lib/insika/telemetry.rb +2 -2
- data/lib/insika/testing/store_contract.rb +54 -33
- data/lib/insika/tick.rb +146 -0
- data/lib/insika/token_store.rb +168 -0
- data/lib/insika/tool_assembly.rb +5 -5
- data/lib/insika/tool_definition.rb +25 -15
- data/lib/insika/tool_envelope.rb +70 -1
- data/lib/insika/tool_manifest.rb +11 -7
- data/lib/insika/tool_output_compressor.rb +100 -0
- data/lib/insika/tool_store.rb +1 -1
- data/lib/insika/tool_trace_store.rb +1 -1
- data/lib/insika/tools/concurrency.rb +2 -2
- data/lib/insika/tools/data_defined_tool.rb +14 -5
- data/lib/insika/tools/generate_image.rb +44 -0
- data/lib/insika/tools/load_skill.rb +61 -3
- data/lib/insika/tools/schedule_followup.rb +164 -0
- data/lib/insika/tools/stuck_signal.rb +44 -0
- data/lib/insika/tools/subagent.rb +4 -4
- data/lib/insika/tools/subagents.rb +1 -1
- data/lib/insika/tools/tts.rb +47 -0
- data/lib/insika/tools/update_briefing.rb +126 -0
- data/lib/insika/turn_output.rb +2 -2
- data/lib/insika/turn_state.rb +54 -13
- data/lib/insika/turn_timing.rb +24 -4
- data/lib/insika/usage_ledger.rb +1 -1
- data/lib/insika/version.rb +1 -1
- data/lib/insika/vitals.rb +84 -0
- data/lib/insika/wiring/graph.rb +372 -34
- data/lib/insika/workflow.rb +1 -1
- data/lib/insika/workflow_registry.rb +1 -1
- data/lib/insika.rb +122 -16
- metadata +95 -2
- data/lib/insika/server/admin_auth.rb +0 -29
data/lib/insika/dsl/runtime.rb
CHANGED
|
@@ -26,16 +26,16 @@ module Insika
|
|
|
26
26
|
# the runtime does not care how many agents it hosts, only that each one
|
|
27
27
|
# arrives as an ordinary Pack.
|
|
28
28
|
#
|
|
29
|
-
# `backend`: the store this graph owns
|
|
29
|
+
# `backend`: the store this graph owns. nil = the historic
|
|
30
30
|
# path, `INSIKA_DB` or memory — ENV is a default, never a requirement (the
|
|
31
|
-
# embed contract
|
|
31
|
+
# embed contract). An injected backend wins and is never widened
|
|
32
32
|
# back to the environment.
|
|
33
33
|
def initialize(definition, backend: nil)
|
|
34
34
|
@definition = definition
|
|
35
35
|
@injected_backend = backend
|
|
36
36
|
@packs = (definition.respond_to?(:packs) ? Array(definition.packs) : [definition.pack]).freeze
|
|
37
37
|
@pack = @packs.first
|
|
38
|
-
#
|
|
38
|
+
# the graph's OWN RubyLLM config, built before the graph so
|
|
39
39
|
# every collaborator that talks to a provider is handed it at wiring time
|
|
40
40
|
# rather than reading a process-wide singleton at call time.
|
|
41
41
|
@llm = build_llm_context
|
|
@@ -44,7 +44,7 @@ module Insika
|
|
|
44
44
|
import_packs
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
# The graph's RubyLLM context
|
|
47
|
+
# The graph's RubyLLM context — an isolated config dup that
|
|
48
48
|
# answers #chat. nil only under the test stub, where the fallback is the
|
|
49
49
|
# RubyLLM constant itself. Read by the specs and by anything a host wires
|
|
50
50
|
# alongside this graph.
|
|
@@ -178,7 +178,8 @@ module Insika
|
|
|
178
178
|
guardrails: c[:guardrails], context_providers: context_providers(spine, c),
|
|
179
179
|
edge_limiter: c[:edge_limiter],
|
|
180
180
|
executor_extra: { settings_store: c[:settings_store], tool_trace_store: c[:tool_trace_store],
|
|
181
|
-
|
|
181
|
+
context_trace_store: c[:context_trace_store],
|
|
182
|
+
cache_series_store: c[:cache_series_store], llm: @llm }
|
|
182
183
|
)
|
|
183
184
|
register_authoring_commands(graph, c)
|
|
184
185
|
register_workflows(graph)
|
|
@@ -228,17 +229,21 @@ module Insika
|
|
|
228
229
|
system_file_store: Insika::SystemFileStore.new(config_store: config_store),
|
|
229
230
|
mcp_store: Insika::McpStore.new(config_store: config_store),
|
|
230
231
|
tool_trace_store: Insika::ToolTraceStore.new(store: backend),
|
|
232
|
+
context_trace_store: Insika::ContextTraceStore.new(store: backend),
|
|
233
|
+
# the per-AGENT cache-hit series (the Studio agent-detail card).
|
|
234
|
+
cache_series_store: Insika::CacheSeriesStore.new(store: backend),
|
|
231
235
|
tool_registry: tool_registry,
|
|
232
236
|
tool_catalog: Insika::ToolCatalog.new(tool_registry: tool_registry),
|
|
233
237
|
skill_catalog: Insika::SkillCatalog.new([], store: skill_store),
|
|
234
238
|
prompt_catalog: Insika::PromptCatalog.new([]),
|
|
235
239
|
profile_source: Insika::StoredProfileSource.new(config_store: config_store),
|
|
236
|
-
#
|
|
240
|
+
# the moderator/validator tiers ask a model too. A graph reading its
|
|
237
241
|
# own key for the turn but the global one for a guardrail would be a
|
|
238
242
|
# credential leak wearing the look of isolation.
|
|
239
243
|
guardrails: Insika::Safety::Factory.new(settings_store: settings_store, llm: @llm),
|
|
240
244
|
edge_limiter: Insika::EdgeLimiter.new(
|
|
241
|
-
ledger: Insika::UsageLedger.new(store: backend), settings_store: settings_store
|
|
245
|
+
ledger: Insika::UsageLedger.new(store: backend), settings_store: settings_store,
|
|
246
|
+
budget_ledger: spine.budget_ledger, event_stream: spine.event_stream
|
|
242
247
|
)
|
|
243
248
|
}
|
|
244
249
|
end
|
|
@@ -251,6 +256,7 @@ module Insika
|
|
|
251
256
|
agent_files: c[:agent_file_store], system_files: c[:system_file_store]
|
|
252
257
|
),
|
|
253
258
|
Insika::Context::Providers::Skill.new(catalog: c[:skill_catalog]),
|
|
259
|
+
Insika::Context::Providers::SkillTrigger.new(catalog: c[:skill_catalog]),
|
|
254
260
|
Insika::Context::Providers::ToolSearch.new(catalog: c[:tool_catalog]),
|
|
255
261
|
Insika::Context::Providers::Memory.new(store: spine.memory_store),
|
|
256
262
|
Insika::Context::Providers::Session.new(session_store: spine.session_store)
|
|
@@ -264,6 +270,7 @@ module Insika
|
|
|
264
270
|
bus = graph.bus
|
|
265
271
|
es = graph.event_stream
|
|
266
272
|
mem = graph.memory_store
|
|
273
|
+
audit = graph.memory_audit_store
|
|
267
274
|
bus.register(:create_agent, Insika::Commands::CreateAgent.new(profile_source: c[:profile_source], event_stream: es))
|
|
268
275
|
bus.register(:update_agent, Insika::Commands::UpdateAgent.new(profile_source: c[:profile_source], event_stream: es))
|
|
269
276
|
bus.register(:delete_agent, Insika::Commands::DeleteAgent.new(profile_source: c[:profile_source], event_stream: es))
|
|
@@ -273,9 +280,11 @@ module Insika
|
|
|
273
280
|
bus.register(:restore_agent_file, Insika::Commands::RestoreAgentFile.new(profile_source: c[:profile_source], agent_file_store: c[:agent_file_store], event_stream: es))
|
|
274
281
|
bus.register(:write_skill, Insika::Commands::WriteSkill.new(skill_store: c[:skill_store], skill_catalog: c[:skill_catalog], event_stream: es))
|
|
275
282
|
bus.register(:set_skill_agents, Insika::Commands::SetSkillAgents.new(profile_source: c[:profile_source], event_stream: es))
|
|
276
|
-
bus.register(:memory_put_fact, Insika::Commands::MemoryPutFact.new(memory_store: mem, event_stream: es))
|
|
277
|
-
bus.register(:memory_forget_fact, Insika::Commands::MemoryForgetFact.new(memory_store: mem, event_stream: es))
|
|
283
|
+
bus.register(:memory_put_fact, Insika::Commands::MemoryPutFact.new(memory_store: mem, event_stream: es, audit_store: audit))
|
|
284
|
+
bus.register(:memory_forget_fact, Insika::Commands::MemoryForgetFact.new(memory_store: mem, event_stream: es, audit_store: audit))
|
|
278
285
|
bus.register(:memory_add_note, Insika::Commands::MemoryAddNote.new(memory_store: mem, event_stream: es))
|
|
286
|
+
# the LGPD access right — the Studio Customers drill exports.
|
|
287
|
+
bus.register(:export_customer_memory, Insika::Commands::ExportCustomerMemory.new(memory_store: mem, event_stream: es))
|
|
279
288
|
bus.register(:update_settings, Insika::Commands::UpdateSettings.new(settings_store: c[:settings_store], event_stream: es))
|
|
280
289
|
bus.register(:upsert_llm_provider, Insika::Commands::UpsertLLMProvider.new(provider_store: c[:provider_store], configurator: c[:configurator], event_stream: es))
|
|
281
290
|
bus.register(:delete_llm_provider, Insika::Commands::DeleteLLMProvider.new(provider_store: c[:provider_store], configurator: c[:configurator], event_stream: es))
|
|
@@ -303,7 +312,7 @@ module Insika
|
|
|
303
312
|
([provider_name] + @packs.filter_map { |p| Insika::Coercion.presence(p.config[:provider])&.to_s }).uniq
|
|
304
313
|
end
|
|
305
314
|
|
|
306
|
-
#
|
|
315
|
+
# the graph's credentials belong to the graph. `RubyLLM.context`
|
|
307
316
|
# is an isolated dup of the config that answers #chat, so two graphs in one
|
|
308
317
|
# process no longer overwrite each other's keys: the global
|
|
309
318
|
# `RubyLLM.configure` is a single slot PER PROVIDER, and the loser of that
|
|
@@ -332,7 +341,7 @@ module Insika
|
|
|
332
341
|
|
|
333
342
|
# The LLMConfigurator's target (Studio: edit a provider key, no restart).
|
|
334
343
|
# Scoped to THIS graph's context — a `RubyLLM.configure` here would reach
|
|
335
|
-
# into every other graph in the process, which is the leak
|
|
344
|
+
# into every other graph in the process, which is the leak exists to
|
|
336
345
|
# close. nil = no context (test stub) -> the configurator's own global
|
|
337
346
|
# default, unchanged.
|
|
338
347
|
def llm_configure
|
|
@@ -25,23 +25,28 @@ module Insika
|
|
|
25
25
|
# Fixed local token: logs into the Studio (cookie) and gates /v1 (Bearer).
|
|
26
26
|
# Never a real secret — override with `token:` or ADMIN_TOKEN.
|
|
27
27
|
@token = token || ENV.fetch("ADMIN_TOKEN", "local-demo")
|
|
28
|
-
#
|
|
28
|
+
# the /v1 app is a value now — the same one a host mounts.
|
|
29
29
|
# This boot adds the Studio and a reactor around it, nothing else.
|
|
30
30
|
@builder = Insika::Server::AppBuilder.new(runtime, token: @token)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def run
|
|
34
|
+
# The boot refusal (shadow + no criterion) lives in the AppBuilder; run it
|
|
35
|
+
# BEFORE the Studio is configured so the criterion the Studio folds is the
|
|
36
|
+
# same object the delivery path stamps pairs with.
|
|
37
|
+
@builder.channels?
|
|
38
|
+
register_parity_commands
|
|
34
39
|
configure_studio
|
|
35
40
|
dispatch = Rack::URLMap.new("/studio" => Studio::App, "/" => @builder.app)
|
|
36
41
|
endpoint = Async::HTTP::Endpoint.parse("http://#{@host}:#{@port}")
|
|
37
42
|
middleware = Protocol::Rack::Adapter.new(dispatch)
|
|
38
|
-
#
|
|
43
|
+
# the OTEL bridge is opt-in but must be reachable from the DSL
|
|
39
44
|
# front door too — the convention is worthless if only config.ru can export.
|
|
40
45
|
# nil (the default) -> attach is a no-op and no gem is loaded.
|
|
41
46
|
telemetry = Insika::Telemetry.setup(service_name: ENV.fetch("OTEL_SERVICE_NAME", "insika"))
|
|
42
47
|
|
|
43
48
|
banner(telemetry)
|
|
44
|
-
#
|
|
49
|
+
# first Ctrl-C/SIGTERM closes the intake and drains in-flight
|
|
45
50
|
# turns (INSIKA_DRAIN_TIMEOUT, default 20s); a second signal skips the wait.
|
|
46
51
|
Insika::Shutdown.install(executor: @graph.executor)
|
|
47
52
|
Async do
|
|
@@ -56,6 +61,23 @@ module Insika
|
|
|
56
61
|
def workflows? = @builder.workflows?
|
|
57
62
|
def channels? = @builder.channels?
|
|
58
63
|
|
|
64
|
+
# The parity page renders whenever a shadow channel exists, so the judge
|
|
65
|
+
# button must reach a registered command — the deployment bus registers the
|
|
66
|
+
# same one (config/deployment.rb); a page that answers "unknown command"
|
|
67
|
+
# would make this root a second-class citizen. Registered only when the
|
|
68
|
+
# criterion loaded (a command without one would be a fake judge).
|
|
69
|
+
def register_parity_commands
|
|
70
|
+
criterion = @builder.criterion
|
|
71
|
+
return unless criterion
|
|
72
|
+
|
|
73
|
+
@graph.bus.register(:judge_shadow_pairs,
|
|
74
|
+
Insika::Commands::JudgeShadowPairs.new(
|
|
75
|
+
shadow_pairs: @graph.shadow_pair_store,
|
|
76
|
+
settings_store: @rt.component(:settings_store),
|
|
77
|
+
criterion: criterion, event_stream: @graph.event_stream
|
|
78
|
+
))
|
|
79
|
+
end
|
|
80
|
+
|
|
59
81
|
def configure_studio
|
|
60
82
|
Studio::App.configure(
|
|
61
83
|
command_bus: @graph.bus, profile_source: @graph.profiles,
|
|
@@ -64,15 +86,63 @@ module Insika
|
|
|
64
86
|
agent_file_store: @rt.component(:agent_file_store), skill_store: @rt.component(:skill_store),
|
|
65
87
|
skill_catalog: @graph.skill_catalog, tool_catalog: @graph.tool_catalog,
|
|
66
88
|
tool_store: @rt.component(:tool_store), memory_store: @graph.memory_store,
|
|
89
|
+
# the Customers drill renders the audit lines.
|
|
90
|
+
memory_audit_store: @graph.memory_audit_store,
|
|
67
91
|
session_store: @graph.session_store,
|
|
68
92
|
settings_store: @rt.component(:settings_store), llm_provider_store: @rt.component(:provider_store),
|
|
69
93
|
mcp_store: @rt.component(:mcp_store), system_file_store: @rt.component(:system_file_store),
|
|
70
94
|
tool_trace_store: @rt.component(:tool_trace_store),
|
|
95
|
+
context_trace_store: @rt.component(:context_trace_store),
|
|
96
|
+
# the per-agent cache-hit series (agent-detail card).
|
|
97
|
+
cache_series_store: @rt.component(:cache_series_store),
|
|
71
98
|
task_store: @graph.task_store, checkpoint_store: @graph.checkpoint_store,
|
|
72
|
-
pending_action_store: @graph.pending_action_store
|
|
99
|
+
pending_action_store: @graph.pending_action_store,
|
|
100
|
+
# WS7: the agents-grid scorecard reads the outcome store.
|
|
101
|
+
outcome_store: @graph.outcome_store,
|
|
102
|
+
# the outcome-funnel page reads the fold's cells and the
|
|
103
|
+
# BudgetLedger's current counters (D6); the only mutation — the
|
|
104
|
+
# baseline freeze — dispatches :freeze_funnel_baseline on the bus.
|
|
105
|
+
funnel_store: @graph.funnel_store,
|
|
106
|
+
budget_ledger: @graph.budget_ledger,
|
|
107
|
+
# the parity page (only rendered when a shadow channel exists —
|
|
108
|
+
# the nav row keys off the registry).
|
|
109
|
+
shadow_pair_store: @graph.shadow_pair_store,
|
|
110
|
+
parity_criterion: @builder.criterion,
|
|
111
|
+
channel_registry: @graph.channel_registry,
|
|
112
|
+
# the Follow-ups page reads the stores directly;
|
|
113
|
+
# its only mutations (cancel, force-revoke) dispatch bus commands.
|
|
114
|
+
followup_store: @graph.followup_store,
|
|
115
|
+
contact_store: @graph.contact_store,
|
|
116
|
+
# the Facts (wiki) page reads the proposal store
|
|
117
|
+
# directly; its mutations dispatch :resolve_proposal on the bus.
|
|
118
|
+
proposal_store: @graph.proposal_store,
|
|
119
|
+
# the Harvest page reads the harvest store + the two
|
|
120
|
+
# pre-registered artifacts directly; its mutations dispatch the
|
|
121
|
+
# harvest bus commands. The criterion/negative list load best-effort
|
|
122
|
+
# (nil = the page renders the refusal/empty states).
|
|
123
|
+
harvest_store: @graph.harvest_store,
|
|
124
|
+
harvest_criterion: harvest_criterion,
|
|
125
|
+
negative_list: harvest_negative_list
|
|
73
126
|
)
|
|
74
127
|
end
|
|
75
128
|
|
|
129
|
+
# The frozen criterion — best-effort at boot: a bare install
|
|
130
|
+
# without the env var renders the ruler's hole instead of
|
|
131
|
+
# crashing the Studio.
|
|
132
|
+
def harvest_criterion
|
|
133
|
+
path = Insika::EnvSchema.read("INSIKA_HARVEST_CRITERION")
|
|
134
|
+
path && Insika::Harvest::Criterion.load(path)
|
|
135
|
+
rescue Insika::ConfigError, Insika::ValidationError
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def harvest_negative_list
|
|
140
|
+
path = Insika::EnvSchema.read("INSIKA_HARVEST_NEGATIVE")
|
|
141
|
+
path && Insika::Harvest::NegativeList.parse(File.read(path))
|
|
142
|
+
rescue Errno::ENOENT
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
|
|
76
146
|
def persistence
|
|
77
147
|
@graph.durable? ? "durable (sqlite)" : "ephemeral (memory)"
|
|
78
148
|
end
|
data/lib/insika/dsl/system.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Insika
|
|
|
17
17
|
class System
|
|
18
18
|
attr_reader :definitions, :workflows, :runtime_options
|
|
19
19
|
|
|
20
|
-
# `backend`: the store this system's graph owns
|
|
20
|
+
# `backend`: the store this system's graph owns. nil = the
|
|
21
21
|
# historic path (INSIKA_DB, or memory when unset). Set by `Insika.embed`.
|
|
22
22
|
def initialize(definitions:, workflows: [], runtime: {}, backend: nil)
|
|
23
23
|
@definitions = definitions.freeze
|
data/lib/insika/dsl.rb
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
require_relative "pack"
|
|
4
4
|
|
|
5
5
|
module Insika
|
|
6
|
-
# Public Ruby DSL
|
|
6
|
+
# Public Ruby DSL — the OSS "business card":
|
|
7
7
|
#
|
|
8
8
|
# agent = Insika.agent("assistant") do
|
|
9
|
-
# model "deepseek-
|
|
9
|
+
# model "deepseek-v4-flash"
|
|
10
10
|
# instructions "You are a concise, friendly assistant."
|
|
11
11
|
# end
|
|
12
12
|
# puts agent.reply("hi, what can you do?") # one turn, in-process
|
|
13
13
|
# agent.serve # control UI + /v1 on :9292
|
|
14
14
|
#
|
|
15
15
|
# It is THIN SUGAR that GENERATES the data (a Insika::Pack), never a bypass of
|
|
16
|
-
# config-over-code (COMPETITIVE-ANALYSIS
|
|
16
|
+
# config-over-code (COMPETITIVE-ANALYSIS). `Insika.agent { … }.to_pack`
|
|
17
17
|
# is the same portable artifact the PackImporter consumes at runtime — the DSL
|
|
18
18
|
# and a hand-written pack produce the SAME profile (the parity spec proves it),
|
|
19
19
|
# because BOTH go through the standard import → StoredProfileSource round-trip.
|
|
@@ -153,7 +153,7 @@ module Insika
|
|
|
153
153
|
|
|
154
154
|
# A DATA-DEFINED (declarative HTTP) tool — pure config-over-code. `defn` is a
|
|
155
155
|
# ToolDefinition hash (name/description/parameters/binding…). Its name is
|
|
156
|
-
# auto-added to the allowlist so the agent can call its own tool
|
|
156
|
+
# auto-added to the allowlist so the agent can call its own tool.
|
|
157
157
|
def data_tool(defn)
|
|
158
158
|
h = defn.transform_keys(&:to_s)
|
|
159
159
|
@tools << h
|
|
@@ -173,9 +173,31 @@ module Insika
|
|
|
173
173
|
n
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
+
# skills_eager — turns progressive disclosure off for THIS agent, wholly or in
|
|
177
|
+
# part. The body of an eager skill is in the prompt on every turn, so its
|
|
178
|
+
# activation is not a decision and cannot be missed; it is paid for on every
|
|
179
|
+
# turn, so measure the bodies against `context_budget` first (a stable position
|
|
180
|
+
# makes them a cacheable prefix).
|
|
181
|
+
#
|
|
182
|
+
# skills_eager # every allowed skill
|
|
183
|
+
# skills_eager "formato", "markers" # exactly these
|
|
184
|
+
# skills_eager false # none (the default)
|
|
185
|
+
#
|
|
186
|
+
# A LIST and not a per-skill flag because skills are shared: `escalation-to-human`
|
|
187
|
+
# sits in several allowlists, and one flag on the skill would force one decision
|
|
188
|
+
# onto every agent holding it.
|
|
189
|
+
def skills_eager(*names)
|
|
190
|
+
flat = names.flatten
|
|
191
|
+
@config[:skills_eager] =
|
|
192
|
+
if flat.empty? then true
|
|
193
|
+
elsif flat == [true] || flat == [false] then flat.first
|
|
194
|
+
else flat.map(&:to_s)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
176
198
|
# --- delegation ------------------------------------------------------
|
|
177
199
|
# subagents "security", "performance" → the child agents this one MAY
|
|
178
|
-
# spawn
|
|
200
|
+
# spawn. CAPACITY field: opt-in, never inherited, and the ids
|
|
179
201
|
# must be agents of the same system (`Insika.system { … }`) or already in
|
|
180
202
|
# the store. Present ⇒ the engine wires `spawn_subagent`/`spawn_subagents`.
|
|
181
203
|
def subagents(*ids)
|
|
@@ -185,23 +207,138 @@ module Insika
|
|
|
185
207
|
# --- knobs -----------------------------------------------------------
|
|
186
208
|
def memory(on = true) = @config[:memory] = on
|
|
187
209
|
|
|
188
|
-
#
|
|
210
|
+
# Spend caps per calendar window (WS2): daily/monthly token budgets for
|
|
211
|
+
# this agent, per (tenant, agent) when multi-tenant. HARD is the default:
|
|
212
|
+
# absent `soft:` (or `soft: false`) turns the cap into a hard wall (the
|
|
213
|
+
# turn fails with budget_exceeded + retry_after); `soft: true` warns once
|
|
214
|
+
# per window and keeps running.
|
|
215
|
+
# budget daily: 100_000, monthly: 2_000_000, soft: false
|
|
216
|
+
def budget(hash) = (@config[:budget] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
217
|
+
|
|
218
|
+
# The outcome funnel declaration: the store's stage vocabulary
|
|
219
|
+
# as pack data — the engine folds WS7 outcome kinds into the DECLARED
|
|
220
|
+
# stages, and never hard-codes one itself. Merges, so repeated calls
|
|
221
|
+
# accumulate (like budget).
|
|
222
|
+
# funnel stages: %w[greeted qualified cart paid],
|
|
223
|
+
# advance_on: { "pix_paid" => "paid", "abandoned_cart" => "cart" },
|
|
224
|
+
# primary: "paid", attribution_window: "72h"
|
|
225
|
+
def funnel(hash) = (@config[:funnel] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
226
|
+
|
|
227
|
+
# The follow-up declaration: the agent may book a follow-up
|
|
228
|
+
# with a customer at a future time (`schedule` tool); the engine fires the
|
|
229
|
+
# synthetic turn and enforces the policy (quiet hours, max frequency,
|
|
230
|
+
# cancellation keywords, silence detection) at fire time. Pack data —
|
|
231
|
+
# merges, so repeated calls accumulate (like budget).
|
|
232
|
+
# followup arm: "schedule",
|
|
233
|
+
# policy: { quiet_hours: { timezone: "America/Sao_Paulo",
|
|
234
|
+
# start: "21:30", end: "09:00" },
|
|
235
|
+
# max_frequency: "2/24h",
|
|
236
|
+
# cancel_keywords: ["não quero mais contato"],
|
|
237
|
+
# silence_after_sends: 3 }
|
|
238
|
+
def followup(hash) = (@config[:followup] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
239
|
+
|
|
240
|
+
# The session-distillation declaration: what counts as a fact
|
|
241
|
+
# for THIS store, distilled from finished customer conversations. Pack
|
|
242
|
+
# data — merges, so repeated calls accumulate (like budget). `prompt` and
|
|
243
|
+
# `model` are pack-authored keys the DSL passes through.
|
|
244
|
+
# distill enabled: true, idle_hours: 6, max_proposals: 10
|
|
245
|
+
def distill(hash) = (@config[:distill] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
246
|
+
|
|
247
|
+
# The gated-harvest declaration: the engine may read this
|
|
248
|
+
# agent's finished traffic and propose SKILLS for the store's playbook —
|
|
249
|
+
# through the negative list, the grounding filter and the double gate,
|
|
250
|
+
# never applied automatically. Pack data — merges, so repeated calls
|
|
251
|
+
# accumulate (like budget). `prompt`/`model` are pack-authored keys the
|
|
252
|
+
# DSL passes through.
|
|
253
|
+
# harvest enabled: true,
|
|
254
|
+
# negative_list: [ { rule: "no-competitor-prices", pattern: "concorrente" } ],
|
|
255
|
+
# miner: { model: "deepseek-v4-flash", window: { last_sessions: 200 } }
|
|
256
|
+
def harvest(hash) = (@config[:harvest] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
257
|
+
|
|
258
|
+
# Provider-interaction reliability, as DATA (WS3): retries + exponential
|
|
259
|
+
# backoff on transient failures, a fallback model chain (mid-turn
|
|
260
|
+
# rotation), and a circuit breaker per (tenant, provider/model) that
|
|
261
|
+
# fail-fasts once the window trips. `fallback`/`circuit_breaker` entries
|
|
262
|
+
# are "provider/model" refs or plain model ids.
|
|
263
|
+
# reliability retries: 3, backoff: "exponential",
|
|
264
|
+
# fallback: ["gpt-4o-mini"], circuit_breaker: { after: 10, within: 60, cooldown: 300 }
|
|
265
|
+
def reliability(hash)
|
|
266
|
+
(@config[:reliability] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Operator alert delivery (WS6): POST this agent's budget_warning /
|
|
270
|
+
# breaker_open / delivery_failed events to the webhook as JSON.
|
|
271
|
+
def alerts(hash) = (@config[:alerts] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
272
|
+
|
|
273
|
+
# Intent routing (WS4): classify each turn's message into one route with a
|
|
274
|
+
# cheap model BEFORE the ask. A Hash: route name -> description (or a Hash
|
|
275
|
+
# with description/delegate/stuck/message), plus the reserved keys
|
|
276
|
+
# "default" (the deterministic fallback) and "model"/"provider" (the cheap
|
|
277
|
+
# classifier). The classifier prompt is generated — data only.
|
|
278
|
+
# routes "shopping" => "the customer wants to browse products",
|
|
279
|
+
# "order" => { "description" => "asks about an existing order",
|
|
280
|
+
# "delegate" => "order-agent" },
|
|
281
|
+
# "human" => { "description" => "the customer asks for a person",
|
|
282
|
+
# "stuck" => true },
|
|
283
|
+
# "default" => "shopping", "model" => "deepseek-v4-flash"
|
|
284
|
+
def routes(hash) = (@config[:routes] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
285
|
+
|
|
286
|
+
# The agent may signal it cannot proceed (WS5): when on, the model
|
|
287
|
+
# can call `signal_stuck`, which ends the turn with `outcome: :stuck` + a final
|
|
288
|
+
# message + a `:turn_stuck` event. What "stuck" means is the consumer's call.
|
|
289
|
+
# stuck_signal true
|
|
290
|
+
def stuck_signal(on = true) = @config[:stuck_signal] = on
|
|
291
|
+
|
|
292
|
+
# The per-session working-state schema this agent keeps and asks for
|
|
293
|
+
# a flat list of field names. [] = off (no provider output,
|
|
294
|
+
# no update_briefing/set_next_step tools).
|
|
295
|
+
# briefing_fields "size", "budget", "delivery_day"
|
|
296
|
+
def briefing_fields(*names)
|
|
297
|
+
@config[:briefing_fields] = names.flatten.map(&:to_s)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Generated-media output policy (WS9, saída): the media kinds this
|
|
301
|
+
# agent MAY generate as turn outputs, with per-kind config. The other
|
|
302
|
+
# half of the gate is the CHANNEL's: the request must declare the
|
|
303
|
+
# matching capability for the tools to exist at all.
|
|
304
|
+
# outputs image: { model: "gpt-image-1", size: "1024x1024" },
|
|
305
|
+
# tts: { model: "tts-1", voice: "alloy" }
|
|
306
|
+
def outputs(hash) = (@config[:outputs] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
307
|
+
|
|
308
|
+
# The engine's "Tool discipline" block in the system prompt (retry a
|
|
309
|
+
# weak/empty tool result with a different approach before giving up).
|
|
310
|
+
# ON by default — this setter exists to turn it OFF:
|
|
311
|
+
# tool_persistence false
|
|
312
|
+
def tool_persistence(on = true) = @config[:tool_persistence] = on
|
|
313
|
+
|
|
314
|
+
# Mechanical tool-result dedupe in the replayed history
|
|
315
|
+
# (no-LLM compaction, apt for bloated transcripts). CHANGES WHAT THE MODEL
|
|
316
|
+
# SEES: repeated identical tool results collapse to a back-reference.
|
|
317
|
+
def tool_output_compression(on = true) = @config[:tool_output_compression] = on
|
|
318
|
+
|
|
319
|
+
# Content-safety guardrails — opt-in and configurable per agent.
|
|
189
320
|
# Pure config-over-code: the hash is stored on the profile and consumed by
|
|
190
321
|
# Safety::Config.from_profile. Merges, so repeated calls accumulate.
|
|
191
322
|
# guardrails input: true, output: true, strictness: "medium",
|
|
192
|
-
# moderator: "deepseek/deepseek-
|
|
323
|
+
# moderator: "deepseek/deepseek-v4-flash",
|
|
193
324
|
# responses: { "injection" => "I can't help with that." }
|
|
194
325
|
def guardrails(hash) = (@config[:guardrails] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
195
326
|
|
|
196
|
-
#
|
|
327
|
+
# Evidence-grounding policy: the pack declares how the engine
|
|
328
|
+
# polices product claims against the evidence ledger. Same
|
|
329
|
+
# config-over-code shape as `guardrails`; absent = off (parity).
|
|
330
|
+
# grounding mode: :flag, matcher: { sku: '\b[A-Z]{2,4}\d{4,8}\b' }
|
|
331
|
+
def grounding(hash = {}) = (@config[:grounding] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
332
|
+
|
|
333
|
+
# Refinement — how the agent's own instruction files may be
|
|
197
334
|
# improved from real traffic. Same config-over-code shape as `guardrails`;
|
|
198
|
-
# omitting it entirely leaves the agent report-only (
|
|
335
|
+
# omitting it entirely leaves the agent report-only (writes nothing).
|
|
199
336
|
# refine mode: "propose", window: { last_sessions: 200 }, files: %w[TOOLS.md],
|
|
200
|
-
# proposers: ["deepseek/deepseek-
|
|
337
|
+
# proposers: ["deepseek/deepseek-v4-flash", "gpt-5-mini"],
|
|
201
338
|
# budget: { tokens: 200_000 }
|
|
202
339
|
def refine(hash) = (@config[:refinement] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
203
340
|
|
|
204
|
-
# Facts about THIS deployment that are not tools
|
|
341
|
+
# Facts about THIS deployment that are not tools, so an eval
|
|
205
342
|
# case can declare what it needs and be skipped where it is absent instead of
|
|
206
343
|
# failing for the wrong reason.
|
|
207
344
|
# declares "promotions", "human_handoff"
|
|
@@ -217,7 +354,7 @@ module Insika
|
|
|
217
354
|
# edge_stream thinking: true, intermediate: false
|
|
218
355
|
def edge_stream(hash) = (@config[:edge_stream] ||= {}).merge!(hash.transform_keys(&:to_s))
|
|
219
356
|
|
|
220
|
-
# LLM generation params
|
|
357
|
+
# LLM generation params. `param:temperature, 0.2` or `params(...)`.
|
|
221
358
|
def param(key, value) = (@config[:params] ||= {})[key.to_sym] = value
|
|
222
359
|
def params(hash) = (@config[:params] ||= {}).merge!(hash.transform_keys(&:to_sym))
|
|
223
360
|
def temperature(value) = param(:temperature, value)
|
|
@@ -281,14 +418,14 @@ module Insika
|
|
|
281
418
|
DSL.system(&block)
|
|
282
419
|
end
|
|
283
420
|
|
|
284
|
-
#
|
|
421
|
+
# the front door for MOUNTING Insika into an app you already
|
|
285
422
|
# have. Same block as `Insika.system`, one added obligation: the caller names
|
|
286
423
|
# the store, so the graph stops discovering it from `INSIKA_DB` and two graphs
|
|
287
424
|
# in one process can no longer read each other's sessions.
|
|
288
425
|
#
|
|
289
426
|
# INSIKA = Insika.embed(backend: Insika::Stores::SQLite.new(path: "storage/insika.sqlite3")) do
|
|
290
427
|
# agent "support" do
|
|
291
|
-
# model "deepseek-
|
|
428
|
+
# model "deepseek-v4-flash"
|
|
292
429
|
# instructions "…"
|
|
293
430
|
# end
|
|
294
431
|
# end
|
|
@@ -296,7 +433,7 @@ module Insika
|
|
|
296
433
|
# mount Insika::Server.rack_app(INSIKA, token: ENV.fetch("INSIKA_TOKEN")), at: "/ai"
|
|
297
434
|
#
|
|
298
435
|
# It is a thin front door over the SAME assembly `Insika.system` uses — there is
|
|
299
|
-
# one pipeline
|
|
436
|
+
# one pipeline, and the parity spec holds it to that. What an embedded
|
|
300
437
|
# graph owns, and what it still shares with the process, is docs/EMBEDDING.md.
|
|
301
438
|
def embed(backend:, &block)
|
|
302
439
|
DSL.embed(backend: backend, &block)
|