insika 0.8.0 → 1.0.1
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 +312 -2
- data/README.md +10 -6
- data/bin/insika +4 -1
- data/bin/insika-router +0 -1
- data/docs/AGENTS.md +142 -18
- data/docs/API.md +73 -0
- data/docs/ARCHITECTURE.md +56 -50
- data/docs/ARTIFACTS.md +33 -3
- data/docs/BENCHMARK.md +50 -7
- data/docs/CHANNELS.md +19 -2
- data/docs/CONTEXT.md +96 -27
- data/docs/CROSS-HARNESS-BENCH.md +157 -0
- data/docs/DEPLOY.md +70 -13
- data/docs/EMBEDDING.md +10 -0
- data/docs/EVALS.md +129 -10
- data/docs/FACTS.md +12 -6
- data/docs/HARVEST.md +8 -3
- data/docs/KNOWLEDGE.md +14 -0
- data/docs/MEDIA.md +6 -2
- data/docs/OBSERVABILITY.md +164 -28
- data/docs/PERFORMANCE.md +110 -0
- data/docs/PLUGINS.md +4 -4
- data/docs/POLICY.md +26 -14
- data/docs/RELEASING.md +9 -0
- data/docs/ROUTER.md +7 -0
- data/docs/RUBYLLM_2_MIGRATION.md +307 -0
- data/docs/SECURITY.md +34 -1
- data/docs/SHARED-CONVERSATIONS.md +40 -0
- data/docs/SKILLS.md +5 -3
- data/docs/STUDIO.md +122 -0
- data/docs/TOOLS.md +238 -42
- data/docs/operate.md +3 -0
- data/docs/prompts/ADD-TOOL.md +12 -2
- data/docs/prompts/DIAGNOSE-TURN.md +3 -0
- data/docs/prompts/GO-LIVE.md +3 -1
- data/lib/insika/agent_defaults.rb +140 -0
- data/lib/insika/agent_file_store.rb +12 -0
- data/lib/insika/agent_profile.rb +99 -17
- data/lib/insika/artifact_signing.rb +11 -0
- data/lib/insika/channels/web/widget.js +33 -0
- data/lib/insika/channels/web.rb +5 -2
- data/lib/insika/chat_builder.rb +124 -28
- data/lib/insika/checkpoint.rb +6 -1
- data/lib/insika/checkpoint_store.rb +59 -7
- data/lib/insika/commands/agent_payload.rb +3 -3
- data/lib/insika/commands/delete_kit.rb +26 -0
- data/lib/insika/commands/gate_harvest.rb +3 -0
- data/lib/insika/commands/review_guardrail_flag.rb +38 -0
- data/lib/insika/commands/run_distillation.rb +5 -8
- data/lib/insika/commands/run_harvest.rb +17 -1
- data/lib/insika/commands/seed_session.rb +128 -0
- data/lib/insika/commands/send_message.rb +9 -3
- data/lib/insika/commands/update_settings.rb +23 -1
- data/lib/insika/commands/write_kit.rb +27 -0
- data/lib/insika/compaction.rb +33 -7
- data/lib/insika/config_store.rb +87 -7
- data/lib/insika/context/builder.rb +29 -9
- data/lib/insika/context/priority.rb +5 -0
- data/lib/insika/context/provider.rb +7 -2
- data/lib/insika/context/providers/briefing.rb +11 -8
- data/lib/insika/context/providers/fence_notice.rb +27 -0
- data/lib/insika/context/providers/knowledge.rb +32 -6
- data/lib/insika/context/providers/memory.rb +61 -8
- data/lib/insika/context/providers/pending_confirmation.rb +52 -0
- data/lib/insika/context/providers/prompt.rb +17 -4
- data/lib/insika/context/providers/session.rb +46 -6
- data/lib/insika/context/reranker.rb +75 -0
- data/lib/insika/distill.rb +22 -13
- data/lib/insika/doctor.rb +142 -4
- data/lib/insika/dsl/runtime.rb +37 -4
- data/lib/insika/dsl/server_boot.rb +4 -12
- data/lib/insika/dsl.rb +19 -5
- data/lib/insika/edge_limiter.rb +27 -16
- data/lib/insika/egress_guard.rb +11 -3
- data/lib/insika/env_schema.rb +33 -73
- data/lib/insika/errors.rb +1 -0
- data/lib/insika/evals/assertions.rb +259 -18
- data/lib/insika/evals/golden.rb +230 -4
- data/lib/insika/evals/judge.rb +19 -10
- data/lib/insika/evals/moderator_compare.rb +47 -0
- data/lib/insika/evals/report.rb +61 -9
- data/lib/insika/evals/runner.rb +64 -4
- data/lib/insika/evals/simulator.rb +21 -3
- data/lib/insika/evals/transport.rb +317 -15
- data/lib/insika/event_stream.rb +63 -7
- data/lib/insika/evidence.rb +105 -17
- data/lib/insika/executor.rb +698 -164
- data/lib/insika/fence.rb +96 -0
- data/lib/insika/golden_store.rb +3 -0
- data/lib/insika/harvest.rb +26 -14
- data/lib/insika/harvest_engine.rb +1 -0
- data/lib/insika/harvest_store.rb +12 -0
- data/lib/insika/http_client.rb +87 -4
- data/lib/insika/kits.rb +54 -0
- data/lib/insika/knowledge.rb +34 -20
- data/lib/insika/llm_configurator.rb +46 -4
- data/lib/insika/llm_provider_store.rb +1 -3
- data/lib/insika/llm_trace_store.rb +54 -0
- data/lib/insika/mcp_client.rb +27 -63
- data/lib/insika/mcp_live_tool.rb +55 -9
- data/lib/insika/mcp_store.rb +37 -3
- data/lib/insika/mcp_tool_registry.rb +76 -26
- data/lib/insika/media.rb +38 -25
- data/lib/insika/memory_store.rb +16 -3
- data/lib/insika/memory_trim.rb +60 -0
- data/lib/insika/middleware.rb +12 -3
- data/lib/insika/model_metrics_store.rb +285 -0
- data/lib/insika/model_registry.rb +110 -0
- data/lib/insika/model_resolver.rb +1 -1
- data/lib/insika/model_selection.rb +14 -21
- data/lib/insika/model_visible.rb +17 -6
- data/lib/insika/onboarding.rb +1 -0
- data/lib/insika/outcome_store.rb +1 -1
- data/lib/insika/overlay_tool_registry.rb +30 -2
- data/lib/insika/pack_importer.rb +54 -9
- data/lib/insika/packaging.rb +14 -5
- data/lib/insika/pending_action_store.rb +45 -18
- data/lib/insika/plugin/loader.rb +4 -2
- data/lib/insika/prefix_fingerprint.rb +32 -27
- data/lib/insika/profile_source.rb +5 -0
- data/lib/insika/provider_error_classifier.rb +18 -3
- data/lib/insika/queue_policy.rb +7 -2
- data/lib/insika/refinement/proposer.rb +42 -17
- data/lib/insika/reliability.rb +12 -3
- data/lib/insika/router/app.rb +14 -5
- data/lib/insika/router/session_key.rb +35 -5
- data/lib/insika/safety/config.rb +20 -3
- data/lib/insika/safety/corpus.rb +10 -3
- data/lib/insika/safety/factory.rb +39 -1
- data/lib/insika/safety/input_guardrail.rb +21 -1
- data/lib/insika/safety/judge_moderator.rb +70 -0
- data/lib/insika/safety/output_validator.rb +29 -5
- data/lib/insika/safety/prompt_echo.rb +89 -0
- data/lib/insika/schema_guard.rb +53 -76
- data/lib/insika/server/app.rb +58 -7
- data/lib/insika/server/rack_app.rb +2 -0
- data/lib/insika/server/responses.rb +50 -5
- data/lib/insika/session_actor.rb +58 -28
- data/lib/insika/session_store.rb +163 -11
- data/lib/insika/settings_store.rb +57 -2
- data/lib/insika/shared_conversations.rb +305 -0
- data/lib/insika/skill_catalog.rb +28 -2
- data/lib/insika/skill_store.rb +6 -0
- data/lib/insika/soak/runner.rb +1 -1
- data/lib/insika/spoken_transcript.rb +31 -0
- data/lib/insika/steer_injector.rb +7 -48
- data/lib/insika/store.rb +29 -1
- data/lib/insika/stores/json_value.rb +37 -0
- data/lib/insika/stores/memory.rb +22 -35
- data/lib/insika/stores/postgres.rb +295 -0
- data/lib/insika/stores/sqlite.rb +257 -43
- data/lib/insika/stores/turn_counter.rb +25 -0
- data/lib/insika/studio/app.rb +1459 -163
- data/lib/insika/studio/assets/dist/application.css +1 -1
- data/lib/insika/studio/assets/dist/application.js +25 -25
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-sans-latin-wght-normal.woff2 +0 -0
- data/lib/insika/studio/forms.rb +189 -12
- data/lib/insika/studio/nav_icons.rb +3 -0
- data/lib/insika/studio/views/_agent_config_retrieval.erb +73 -0
- data/lib/insika/studio/views/_agent_picker.erb +15 -0
- data/lib/insika/studio/views/_agent_tab_config.erb +83 -243
- data/lib/insika/studio/views/_agent_tab_history.erb +56 -1
- data/lib/insika/studio/views/_agent_tab_performance.erb +34 -0
- data/lib/insika/studio/views/_agent_tab_prompts.erb +7 -9
- data/lib/insika/studio/views/_agent_tab_skills.erb +16 -7
- data/lib/insika/studio/views/_agent_tab_tools.erb +90 -0
- data/lib/insika/studio/views/_agents_master.erb +8 -12
- data/lib/insika/studio/views/_cfg_alerts.erb +4 -0
- data/lib/insika/studio/views/_cfg_budget.erb +20 -0
- data/lib/insika/studio/views/_cfg_distill.erb +26 -0
- data/lib/insika/studio/views/_cfg_followup.erb +30 -0
- data/lib/insika/studio/views/_cfg_grounding.erb +21 -0
- data/lib/insika/studio/views/_cfg_guardrails.erb +47 -0
- data/lib/insika/studio/views/_cfg_harvest.erb +35 -0
- data/lib/insika/studio/views/_cfg_reliability.erb +32 -0
- data/lib/insika/studio/views/_chats_master.erb +66 -0
- data/lib/insika/studio/views/_customers_master.erb +37 -0
- data/lib/insika/studio/views/_empty.erb +15 -0
- data/lib/insika/studio/views/_facts_master.erb +42 -0
- data/lib/insika/studio/views/_harvest_candidate_detail.erb +14 -0
- data/lib/insika/studio/views/_identity.erb +16 -0
- data/lib/insika/studio/views/_kpi.erb +17 -0
- data/lib/insika/studio/views/_message.erb +40 -2
- data/lib/insika/studio/views/_model_chart.erb +82 -0
- data/lib/insika/studio/views/_refinement_master.erb +38 -0
- data/lib/insika/studio/views/{tool_edit.erb → _tool_edit.erb} +38 -11
- data/lib/insika/studio/views/_tool_users.erb +17 -0
- data/lib/insika/studio/views/_why.erb +53 -0
- data/lib/insika/studio/views/agent_detail.erb +17 -10
- data/lib/insika/studio/views/agents.erb +28 -16
- data/lib/insika/studio/views/approvals.erb +16 -11
- data/lib/insika/studio/views/artifact.erb +20 -7
- data/lib/insika/studio/views/artifacts.erb +19 -14
- data/lib/insika/studio/views/chats.erb +22 -29
- data/lib/insika/studio/views/customer.erb +88 -43
- data/lib/insika/studio/views/customers.erb +16 -26
- data/lib/insika/studio/views/evals.erb +49 -31
- data/lib/insika/studio/views/fact_detail.erb +114 -0
- data/lib/insika/studio/views/facts.erb +15 -116
- data/lib/insika/studio/views/followups.erb +63 -52
- data/lib/insika/studio/views/funnel.erb +104 -43
- data/lib/insika/studio/views/harvest.erb +85 -59
- data/lib/insika/studio/views/home.erb +138 -23
- data/lib/insika/studio/views/inbox.erb +36 -0
- data/lib/insika/studio/views/kits.erb +176 -0
- data/lib/insika/studio/views/knowledge.erb +92 -80
- data/lib/insika/studio/views/layout.erb +42 -11
- data/lib/insika/studio/views/live.erb +45 -0
- data/lib/insika/studio/views/mcp.erb +43 -13
- data/lib/insika/studio/views/models.erb +187 -0
- data/lib/insika/studio/views/parity.erb +38 -32
- data/lib/insika/studio/views/playground.erb +13 -9
- data/lib/insika/studio/views/refinement.erb +15 -224
- data/lib/insika/studio/views/refinement_detail.erb +243 -0
- data/lib/insika/studio/views/session.erb +153 -118
- data/lib/insika/studio/views/settings.erb +86 -22
- data/lib/insika/studio/views/skills.erb +53 -19
- data/lib/insika/studio/views/system_files.erb +7 -7
- data/lib/insika/studio/views/task.erb +45 -15
- data/lib/insika/studio/views/tasks.erb +35 -18
- data/lib/insika/studio/views/tools.erb +62 -73
- data/lib/insika/task_actor.rb +4 -1
- data/lib/insika/task_store.rb +67 -1
- data/lib/insika/telemetry/pricing.rb +16 -14
- data/lib/insika/telemetry/recorder.rb +115 -9
- data/lib/insika/telemetry/ruby_llm_instrumenter.rb +110 -0
- data/lib/insika/telemetry.rb +12 -2
- data/lib/insika/templates/daily-digest/README.md +13 -0
- data/lib/insika/templates/daily-digest/agent.rb +22 -11
- data/lib/insika/testing/store_contract.rb +65 -0
- data/lib/insika/text_tool_call.rb +66 -0
- data/lib/insika/tick.rb +7 -1
- data/lib/insika/token_store.rb +5 -0
- data/lib/insika/tool_assembly.rb +21 -13
- data/lib/insika/tool_batch.rb +1 -1
- data/lib/insika/tool_catalog.rb +12 -2
- data/lib/insika/tool_definition.rb +82 -13
- data/lib/insika/tool_envelope.rb +266 -29
- data/lib/insika/tool_manifest.rb +1 -0
- data/lib/insika/tool_store.rb +12 -4
- data/lib/insika/tool_trace_store.rb +15 -8
- data/lib/insika/tool_usage_report.rb +14 -4
- data/lib/insika/tools/a2a_remote.rb +1 -1
- data/lib/insika/tools/agent_enum.rb +1 -1
- data/lib/insika/tools/confirm_pending.rb +112 -0
- data/lib/insika/tools/data_defined_tool.rb +24 -15
- data/lib/insika/tools/generate_image.rb +2 -2
- data/lib/insika/tools/load_knowledge.rb +7 -3
- data/lib/insika/tools/load_skill.rb +3 -3
- data/lib/insika/tools/present.rb +115 -0
- data/lib/insika/tools/remember.rb +7 -2
- data/lib/insika/tools/run_persona_eval.rb +9 -6
- data/lib/insika/tools/save_artifact.rb +16 -6
- data/lib/insika/tools/schedule_followup.rb +3 -3
- data/lib/insika/tools/stuck_signal.rb +3 -3
- data/lib/insika/tools/subagent.rb +5 -5
- data/lib/insika/tools/subagents.rb +3 -3
- data/lib/insika/tools/tool_search.rb +11 -7
- data/lib/insika/tools/tts.rb +2 -2
- data/lib/insika/tools/update_briefing.rb +4 -4
- data/lib/insika/turn_output.rb +2 -2
- data/lib/insika/turn_state.rb +31 -1
- data/lib/insika/turn_timing.rb +51 -14
- data/lib/insika/version.rb +1 -1
- data/lib/insika/wiring/graph.rb +88 -16
- data/lib/insika/wiring/graph_chat.rb +4 -0
- data/lib/insika/workflow.rb +25 -64
- data/lib/insika.rb +23 -3
- metadata +74 -13
- data/lib/insika/commands/import_mcp_tools.rb +0 -48
- data/lib/insika/mcp_http_client.rb +0 -67
- data/lib/insika/mcp_tool_ingestor.rb +0 -165
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
<!-- ── Config / model ────────────────────────────────────────────────────── -->
|
|
3
3
|
<section class="section" id="config" data-tabs-target="panel">
|
|
4
4
|
<% config_groups = [
|
|
5
|
-
["model", "Model & limits", "model,
|
|
5
|
+
["model", "Model & limits", "model, generation and runtime limits"],
|
|
6
|
+
["retrieval", "Memory & knowledge", "remember, learn, retrieve and rerank"],
|
|
6
7
|
["guardrails", "Guardrails", "input/output checks, strictness, safe replies"],
|
|
7
8
|
["grounding", "Grounding", "evidence behind product claims"],
|
|
8
9
|
["funnel", "Outcome funnel", "stage vocabulary"],
|
|
@@ -17,10 +18,13 @@
|
|
|
17
18
|
]
|
|
18
19
|
active = config_groups.find { |k, _| k == @config_group } || config_groups.first
|
|
19
20
|
@config_group = active[0] %>
|
|
20
|
-
|
|
21
|
+
<%# The group nav is its own master column (like Prompts' file list) — not a
|
|
22
|
+
"Configuration" card wrapping a duplicate "Configuration" card. Its own
|
|
23
|
+
drill-pane-head carries the title; the detail pane's head carries the
|
|
24
|
+
active group's name + description, same as every other drill-detail. %>
|
|
21
25
|
<div class="drill config-drill">
|
|
22
26
|
<aside class="drill-pane drill-master">
|
|
23
|
-
<div class="drill-pane-head"><div class="title"><strong>Configuration
|
|
27
|
+
<div class="drill-pane-head"><div class="title"><strong>Configuration <span class="count"><%= config_groups.size %></span></strong></div></div>
|
|
24
28
|
<nav class="drill-pane-body drill-list" aria-label="config groups">
|
|
25
29
|
<% config_groups.each do |key, label, sub| %>
|
|
26
30
|
<a class="drill-item<%== ' active' if @config_group == key %>"
|
|
@@ -34,21 +38,41 @@
|
|
|
34
38
|
</aside>
|
|
35
39
|
<section class="drill-pane drill-detail">
|
|
36
40
|
<div class="drill-pane-head">
|
|
37
|
-
<div class="title"><strong><%= active[1] %></strong></div>
|
|
41
|
+
<div class="title"><strong><%= active[1] %></strong><span class="count"><%= active[2] %></span></div>
|
|
38
42
|
<button type="submit" form="config-form" class="btn primary" data-turbo-submits-with="Saving…">Save config</button>
|
|
39
43
|
</div>
|
|
40
44
|
<div class="drill-pane-body">
|
|
41
|
-
<form id="config-form" method="post" action="/studio/agents/<%= Rack::Utils.escape(@agent.id) %>/config">
|
|
45
|
+
<form id="config-form" method="post" action="/studio/agents/<%= Rack::Utils.escape(@agent.id) %>/config" data-controller="dirty-guard">
|
|
42
46
|
<%== csrf_tag %>
|
|
43
47
|
<input type="hidden" name="cfg" value="<%= @config_group %>">
|
|
48
|
+
<p class="muted">Save config applies changes to <strong><%= @agent.id %></strong>. Changes take effect on future turns.</p>
|
|
44
49
|
<div class="config-group" data-config-group="model"<%== " hidden" unless @config_group == "model" %>>
|
|
45
|
-
<
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<label>provider
|
|
50
|
-
<input type="text" name="provider" value="<%= @agent.provider %>" placeholder="deepseek">
|
|
50
|
+
<fieldset class="subgroup">
|
|
51
|
+
<legend>Model</legend>
|
|
52
|
+
<label>model <span class="muted">(blank = platform <code>default_model</code>; the list is Settings › LLM providers)</span>
|
|
53
|
+
<%== model_select("model_ref", model_ref_of(@agent.model, @agent.provider), "inherit platform default") %>
|
|
51
54
|
</label>
|
|
55
|
+
</fieldset>
|
|
56
|
+
|
|
57
|
+
<fieldset class="subgroup">
|
|
58
|
+
<legend>Response generation <span class="muted">— blank = provider default</span></legend>
|
|
59
|
+
<div class="grid-3">
|
|
60
|
+
<label>temperature
|
|
61
|
+
<input type="text" name="temperature" value="<%= agent_param(@params, :temperature) %>" inputmode="decimal" placeholder="0.7">
|
|
62
|
+
</label>
|
|
63
|
+
<label>max_tokens
|
|
64
|
+
<input type="text" name="max_tokens" value="<%= agent_param(@params, :max_tokens) %>" inputmode="numeric" placeholder="4096">
|
|
65
|
+
</label>
|
|
66
|
+
<label>reasoning <span class="muted">(off/on/effort — blank = inherit model/platform)</span>
|
|
67
|
+
<%== thinking_select("thinking", agent_param(@params, :thinking), "inherit") %>
|
|
68
|
+
</label>
|
|
69
|
+
</div>
|
|
70
|
+
</fieldset>
|
|
71
|
+
|
|
72
|
+
<details class="newfile">
|
|
73
|
+
<summary>Runtime limits & allowed models</summary>
|
|
74
|
+
<p class="muted">Control turn duration, tool concurrency and usage. Blank limits inherit platform defaults where indicated.</p>
|
|
75
|
+
<div class="grid-2">
|
|
52
76
|
<label>turn_timeout (s)
|
|
53
77
|
<input type="text" name="turn_timeout" value="<%= @agent.limits[:turn_timeout] %>" inputmode="numeric">
|
|
54
78
|
</label>
|
|
@@ -64,6 +88,16 @@
|
|
|
64
88
|
<label>agent_token_ceiling <span class="muted">(tokens/window; blank = platform, 0 = off)</span>
|
|
65
89
|
<input type="text" name="agent_token_ceiling" value="<%= @agent.limits[:agent_token_ceiling] %>" inputmode="numeric" placeholder="inherit platform edge">
|
|
66
90
|
</label>
|
|
91
|
+
</div>
|
|
92
|
+
<label>model fence <span class="muted">(<code>model_policy.allow</code> — one ref per line: <code>provider/model</code>, <code>provider/*</code> or <code>model</code>; blank = no fence)</span>
|
|
93
|
+
<textarea name="model_policy_allow" rows="3" placeholder="deepseek/* openai/gpt-4o-mini"><%= @model_policy_allow %></textarea>
|
|
94
|
+
</label>
|
|
95
|
+
</details>
|
|
96
|
+
|
|
97
|
+
<details class="newfile">
|
|
98
|
+
<summary>Messages received while busy</summary>
|
|
99
|
+
<p class="muted">Choose whether new messages wait, combine, steer or interrupt a running turn.</p>
|
|
100
|
+
<div class="grid-2">
|
|
67
101
|
<label>queue_mode <span class="muted">(burst policy: followup | collect | steer | interrupt; blank = platform/default followup)</span>
|
|
68
102
|
<select name="queue_mode">
|
|
69
103
|
<option value=""<%== " selected" if @agent.limits[:queue_mode].nil? %>>— inherit</option>
|
|
@@ -75,99 +109,35 @@
|
|
|
75
109
|
<label>steer_max_messages <span class="muted">(messages ONE running turn may absorb; blank = default 5, 0 = never steer)</span>
|
|
76
110
|
<input type="text" name="steer_max_messages" value="<%= @agent.limits[:steer_max_messages] %>" inputmode="numeric" placeholder="5">
|
|
77
111
|
</label>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<label>temperature
|
|
88
|
-
<input type="text" name="temperature" value="<%= agent_param(@params, :temperature) %>" inputmode="decimal" placeholder="0.7">
|
|
89
|
-
</label>
|
|
90
|
-
<label>max_tokens
|
|
91
|
-
<input type="text" name="max_tokens" value="<%= agent_param(@params, :max_tokens) %>" inputmode="numeric" placeholder="4096">
|
|
92
|
-
</label>
|
|
93
|
-
<label>reasoning <span class="muted">(off/on/effort — blank = inherit model/platform)</span>
|
|
94
|
-
<%== thinking_select("thinking", agent_param(@params, :thinking), "inherit") %>
|
|
95
|
-
</label>
|
|
112
|
+
<label>debounce_ms <span class="muted">(extra quiet window at the door; blank/0 = none — collect/steer still merge into a turn that is queued behind another)</span>
|
|
113
|
+
<input type="text" name="debounce_ms" value="<%= @agent.limits[:debounce_ms] %>" inputmode="numeric" placeholder="0">
|
|
114
|
+
</label>
|
|
115
|
+
<label>debounce_max_ms <span class="muted">(ceiling on the TOTAL wait; blank = default 10000)</span>
|
|
116
|
+
<input type="text" name="debounce_max_ms" value="<%= @agent.limits[:debounce_max_ms] %>" inputmode="numeric" placeholder="10000">
|
|
117
|
+
</label>
|
|
118
|
+
<label>steer_join <span class="muted">(template framing a mid-run message; must carry <%= Insika::QueuePolicy::JOIN_PLACEHOLDER %>; blank = the raw text)</span>
|
|
119
|
+
<input type="text" name="steer_join" value="<%= @agent.limits[:steer_join] %>" placeholder="the customer just added: <%= Insika::QueuePolicy::JOIN_PLACEHOLDER %>">
|
|
120
|
+
</label>
|
|
96
121
|
</div>
|
|
97
|
-
</
|
|
122
|
+
</details>
|
|
98
123
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<div class="config-group" data-config-group="retrieval"<%== " hidden" unless @config_group == "retrieval" %>>
|
|
127
|
+
<%== render("_agent_config_retrieval") %>
|
|
102
128
|
</div>
|
|
103
129
|
|
|
104
130
|
<div class="config-group" data-config-group="guardrails"<%== " hidden" unless @config_group == "guardrails" %>>
|
|
105
|
-
|
|
106
|
-
<legend>guardrails <span class="muted">— content safety</span></legend>
|
|
107
|
-
<label class="check">
|
|
108
|
-
<input type="checkbox" name="guardrail_input" value="1"<%== " checked" if guardrail_field(@guardrails, :input, true) %>>
|
|
109
|
-
input guardrail <span class="muted">— block injection / abuse / inappropriate before the LLM</span>
|
|
110
|
-
</label>
|
|
111
|
-
<label class="check">
|
|
112
|
-
<input type="checkbox" name="guardrail_output" value="1"<%== " checked" if guardrail_field(@guardrails, :output, true) %>>
|
|
113
|
-
output guardrail <span class="muted">— redact PII/secret in the stream + flag policy slips</span>
|
|
114
|
-
</label>
|
|
115
|
-
<div class="grid-2">
|
|
116
|
-
<label>strictness
|
|
117
|
-
<% strictness = guardrail_field(@guardrails, :strictness, "medium").to_s %>
|
|
118
|
-
<select name="guardrail_strictness">
|
|
119
|
-
<% %w[low medium high].each do |lvl| %>
|
|
120
|
-
<option value="<%= lvl %>"<%== " selected" if lvl == strictness %>><%= lvl %></option>
|
|
121
|
-
<% end %>
|
|
122
|
-
</select>
|
|
123
|
-
</label>
|
|
124
|
-
<label>LLM moderator <span class="muted">(model ref, or <code>on</code> for the platform utility_model; blank = deterministic only)</span>
|
|
125
|
-
<input type="text" name="guardrail_moderator" value="<%= guardrail_field(@guardrails, :moderator, "") %>" placeholder="deepseek/deepseek-v4-flash">
|
|
126
|
-
</label>
|
|
127
|
-
</div>
|
|
128
|
-
<details class="newfile">
|
|
129
|
-
<summary>safe replies — override per category <span class="muted">(blank = engine default; this agent's voice/language)</span></summary>
|
|
130
|
-
<% gr_resp = (@guardrails["responses"] || @guardrails[:responses] || {}) %>
|
|
131
|
-
<label>default <span class="muted">(catch-all for any blocked category)</span>
|
|
132
|
-
<textarea name="guardrail_response_default" rows="2" placeholder="I can't help with that particular request…"><%= guardrail_field(gr_resp, :default, "") %></textarea>
|
|
133
|
-
</label>
|
|
134
|
-
<div class="grid-2">
|
|
135
|
-
<% %w[injection sexual abuse escalate].each do |cat| %>
|
|
136
|
-
<label><%= cat %>
|
|
137
|
-
<textarea name="guardrail_response_<%= cat %>" rows="2"><%= guardrail_field(gr_resp, cat, "") %></textarea>
|
|
138
|
-
</label>
|
|
139
|
-
<% end %>
|
|
140
|
-
</div>
|
|
141
|
-
</details>
|
|
142
|
-
</fieldset>
|
|
131
|
+
<%== render("_cfg_guardrails") %>
|
|
143
132
|
</div>
|
|
144
133
|
|
|
145
134
|
<div class="config-group" data-config-group="grounding"<%== " hidden" unless @config_group == "grounding" %>>
|
|
146
|
-
|
|
147
|
-
<legend>grounding <span class="muted">— product claims must point at evidence the session actually saw</span></legend>
|
|
148
|
-
<% gr = @agent.grounding || {} %>
|
|
149
|
-
<div class="grid-2">
|
|
150
|
-
<label>mode <span class="muted">(blank = off)</span>
|
|
151
|
-
<select name="grounding_mode">
|
|
152
|
-
<option value="">off</option>
|
|
153
|
-
<% %w[flag enforce].each do |m| %>
|
|
154
|
-
<option value="<%= m %>"<%== ' selected' if gr["mode"] == m %>><%= m %></option>
|
|
155
|
-
<% end %>
|
|
156
|
-
</select>
|
|
157
|
-
</label>
|
|
158
|
-
<label>matcher SKU <span class="muted">(evidence id prefix, e.g. <code>sku:</code>)</span>
|
|
159
|
-
<input type="text" name="grounding_matcher_sku" value="<%= gr.dig("matcher", "sku") %>" placeholder="sku:">
|
|
160
|
-
</label>
|
|
161
|
-
<label>name keys <span class="muted">(comma-separated metadata keys with product names)</span>
|
|
162
|
-
<input type="text" name="grounding_name_keys" value="<%= agent_list(gr.dig("matcher", "name_keys")) %>" placeholder="product, name">
|
|
163
|
-
</label>
|
|
164
|
-
</div>
|
|
165
|
-
</fieldset>
|
|
135
|
+
<%== render("_cfg_grounding") %>
|
|
166
136
|
</div>
|
|
167
137
|
|
|
168
138
|
<div class="config-group" data-config-group="funnel"<%== " hidden" unless @config_group == "funnel" %>>
|
|
169
139
|
<fieldset class="subgroup">
|
|
170
|
-
<legend>outcome funnel <span class="muted">— the
|
|
140
|
+
<legend>outcome funnel <span class="muted">— the stages used to track recorded outcomes</span></legend>
|
|
171
141
|
<% fun = @agent.funnel || {} %>
|
|
172
142
|
<div class="grid-2">
|
|
173
143
|
<label>stages <span class="muted">(one per line, in order; blank = no funnel)</span>
|
|
@@ -190,35 +160,7 @@
|
|
|
190
160
|
</div>
|
|
191
161
|
|
|
192
162
|
<div class="config-group" data-config-group="followups"<%== " hidden" unless @config_group == "followups" %>>
|
|
193
|
-
|
|
194
|
-
<legend>follow-ups <span class="muted">— the seller who comes back (blank arm = off)</span></legend>
|
|
195
|
-
<% fu = @agent.followup || {}; fup = fu["policy"] || {}; fqh = fup["quiet_hours"] || {} %>
|
|
196
|
-
<div class="grid-2">
|
|
197
|
-
<label>arm
|
|
198
|
-
<input type="text" name="followup_arm" value="<%= fu["arm"] %>" placeholder="schedule">
|
|
199
|
-
</label>
|
|
200
|
-
<label>max frequency <span class="muted">(e.g. <code>2/24h</code>)</span>
|
|
201
|
-
<input type="text" name="followup_max_frequency" value="<%= fup["max_frequency"] %>" placeholder="2/24h">
|
|
202
|
-
</label>
|
|
203
|
-
<label>quiet hours timezone <span class="muted">(blank = no quiet hours)</span>
|
|
204
|
-
<input type="text" name="followup_tz" value="<%= fqh["timezone"] %>" placeholder="America/Sao_Paulo">
|
|
205
|
-
</label>
|
|
206
|
-
<div class="grid-2">
|
|
207
|
-
<label>quiet start
|
|
208
|
-
<input type="text" name="followup_quiet_start" value="<%= fqh["start"] %>" placeholder="21:30">
|
|
209
|
-
</label>
|
|
210
|
-
<label>quiet end
|
|
211
|
-
<input type="text" name="followup_quiet_end" value="<%= fqh["end"] %>" placeholder="09:00">
|
|
212
|
-
</label>
|
|
213
|
-
</div>
|
|
214
|
-
<label>cancel keywords <span class="muted">(comma-separated)</span>
|
|
215
|
-
<input type="text" name="followup_cancel_keywords" value="<%= agent_list(fup["cancel_keywords"]) %>" placeholder="não quero mais contato">
|
|
216
|
-
</label>
|
|
217
|
-
<label>silence after N sends
|
|
218
|
-
<input type="text" name="followup_silence_after_sends" value="<%= fup["silence_after_sends"] %>" placeholder="3" inputmode="numeric">
|
|
219
|
-
</label>
|
|
220
|
-
</div>
|
|
221
|
-
</fieldset>
|
|
163
|
+
<%== render("_cfg_followup") %>
|
|
222
164
|
</div>
|
|
223
165
|
|
|
224
166
|
<div class="config-group" data-config-group="schedules"<%== " hidden" unless @config_group == "schedules" %>>
|
|
@@ -251,68 +193,11 @@
|
|
|
251
193
|
</div>
|
|
252
194
|
|
|
253
195
|
<div class="config-group" data-config-group="distill"<%== " hidden" unless @config_group == "distill" %>>
|
|
254
|
-
|
|
255
|
-
<legend>distillation <span class="muted">— finished conversations → proposed customer facts (Facts page)</span></legend>
|
|
256
|
-
<% dist = @agent.distill || {} %>
|
|
257
|
-
<label class="check">
|
|
258
|
-
<input type="checkbox" name="distill_enabled" value="1"<%== " checked" if Insika::Coercion.truthy?(dist["enabled"]) %>>
|
|
259
|
-
distillation on — idle sessions are read back as fact proposals; a human always approves
|
|
260
|
-
</label>
|
|
261
|
-
<div class="grid-3">
|
|
262
|
-
<label>idle hours <span class="muted">(blank = 6)</span>
|
|
263
|
-
<input type="text" name="distill_idle_hours" value="<%= dist["idle_hours"] %>" placeholder="6" inputmode="numeric">
|
|
264
|
-
</label>
|
|
265
|
-
<label>min messages <span class="muted">(blank = 3)</span>
|
|
266
|
-
<input type="text" name="distill_min_messages" value="<%= dist["min_messages"] %>" placeholder="3" inputmode="numeric">
|
|
267
|
-
</label>
|
|
268
|
-
<label>max proposals <span class="muted">(blank = 10)</span>
|
|
269
|
-
<input type="text" name="distill_max_proposals" value="<%= dist["max_proposals"] %>" placeholder="10" inputmode="numeric">
|
|
270
|
-
</label>
|
|
271
|
-
</div>
|
|
272
|
-
<label>model <span class="muted">(blank = the platform utility_model)</span>
|
|
273
|
-
<input type="text" name="distill_model" value="<%= dist["model"] %>" placeholder="deepseek-v4-flash">
|
|
274
|
-
</label>
|
|
275
|
-
<label>prompt <span class="muted">— what counts as a fact for THIS store (blank = the engine default)</span>
|
|
276
|
-
<textarea name="distill_prompt" rows="3" placeholder="what counts as a customer fact for this store"><%= dist["prompt"] %></textarea>
|
|
277
|
-
</label>
|
|
278
|
-
</fieldset>
|
|
196
|
+
<%== render("_cfg_distill") %>
|
|
279
197
|
</div>
|
|
280
198
|
|
|
281
199
|
<div class="config-group" data-config-group="harvest"<%== " hidden" unless @config_group == "harvest" %>>
|
|
282
|
-
|
|
283
|
-
<legend>harvest <span class="muted">— finished traffic → proposed SKILLs, double-gated, human-promoted (Harvest page)</span></legend>
|
|
284
|
-
<% hv = @agent.harvest || {}; hvm = hv["miner"] || {} %>
|
|
285
|
-
<label class="check">
|
|
286
|
-
<input type="checkbox" name="harvest_enabled" value="1"<%== " checked" if Insika::Coercion.truthy?(hv["enabled"]) %>>
|
|
287
|
-
harvest on — idle conversations are mined for skills; a human promotes every one
|
|
288
|
-
</label>
|
|
289
|
-
<div class="grid-3">
|
|
290
|
-
<label>idle hours <span class="muted">(blank = 24)</span>
|
|
291
|
-
<input type="text" name="harvest_idle_hours" value="<%= hv["idle_hours"] %>" placeholder="24" inputmode="numeric">
|
|
292
|
-
</label>
|
|
293
|
-
<label>min messages <span class="muted">(blank = 3)</span>
|
|
294
|
-
<input type="text" name="harvest_min_messages" value="<%= hv["min_messages"] %>" placeholder="3" inputmode="numeric">
|
|
295
|
-
</label>
|
|
296
|
-
<label>miner model <span class="muted">(blank = the platform utility_model)</span>
|
|
297
|
-
<input type="text" name="harvest_miner_model" value="<%= hvm["model"] %>" placeholder="deepseek-v4-flash">
|
|
298
|
-
</label>
|
|
299
|
-
<label>miner window <span class="muted">(last N conversations)</span>
|
|
300
|
-
<input type="text" name="harvest_miner_window" value="<%= hvm.dig("window", "last_sessions") %>" placeholder="200" inputmode="numeric">
|
|
301
|
-
</label>
|
|
302
|
-
<label>miner max proposals
|
|
303
|
-
<input type="text" name="harvest_miner_max_proposals" value="<%= hvm["max_proposals"] %>" placeholder="10" inputmode="numeric">
|
|
304
|
-
</label>
|
|
305
|
-
<label>miner budget (tokens)
|
|
306
|
-
<input type="text" name="harvest_miner_budget_tokens" value="<%= hvm.dig("budget", "tokens") %>" placeholder="20000" inputmode="numeric">
|
|
307
|
-
</label>
|
|
308
|
-
</div>
|
|
309
|
-
<label>negative list <span class="muted">— JSON: <code>[{ "rule": "…", "pattern": "…" }]</code> (blank = the deployment seed)</span>
|
|
310
|
-
<textarea name="harvest_negative_list" rows="3" spellcheck="false" class="mono" placeholder='[ { "rule": "no-competitor-prices", "pattern": "concorrente" } ]'><%= agent_json(hv["negative_list"]) %></textarea>
|
|
311
|
-
</label>
|
|
312
|
-
<label>prompt <span class="muted">— what a harvestable skill is for THIS store (blank = the engine default)</span>
|
|
313
|
-
<textarea name="harvest_prompt" rows="3" placeholder="what makes a skill harvestable for this store"><%= hv["prompt"] %></textarea>
|
|
314
|
-
</label>
|
|
315
|
-
</fieldset>
|
|
200
|
+
<%== render("_cfg_harvest") %>
|
|
316
201
|
</div>
|
|
317
202
|
|
|
318
203
|
<div class="config-group" data-config-group="refinement"<%== " hidden" unless @config_group == "refinement" %>>
|
|
@@ -340,9 +225,9 @@
|
|
|
340
225
|
<label>files <span class="muted">(allowlist, comma-separated)</span>
|
|
341
226
|
<input type="text" name="refinement_files" value="<%= agent_list(rf["files"]) %>" placeholder="IDENTITY.md">
|
|
342
227
|
</label>
|
|
343
|
-
<
|
|
344
|
-
|
|
345
|
-
</
|
|
228
|
+
<div>proposers <span class="muted">(model refs)</span>
|
|
229
|
+
<%== model_slots("refinement_proposers_refs", rf["proposers"], min: 2) %>
|
|
230
|
+
</div>
|
|
346
231
|
</div>
|
|
347
232
|
<label>budget (tokens)
|
|
348
233
|
<input type="text" name="refinement_budget_tokens" value="<%= rfb["tokens"] %>" inputmode="numeric">
|
|
@@ -351,79 +236,27 @@
|
|
|
351
236
|
</div>
|
|
352
237
|
|
|
353
238
|
<div class="config-group" data-config-group="budget_rel"<%== " hidden" unless @config_group == "budget_rel" %>>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
<% bd = @agent.budget || {} %>
|
|
357
|
-
<div class="grid-3">
|
|
358
|
-
<label>daily (tokens)
|
|
359
|
-
<input type="text" name="budget_daily" value="<%= bd["daily"] %>" inputmode="numeric">
|
|
360
|
-
</label>
|
|
361
|
-
<label>monthly (tokens)
|
|
362
|
-
<input type="text" name="budget_monthly" value="<%= bd["monthly"] %>" inputmode="numeric">
|
|
363
|
-
</label>
|
|
364
|
-
<label>alert at <span class="muted">(0–1 of the cap)</span>
|
|
365
|
-
<input type="text" name="budget_alert_at" value="<%= bd["alert_at"] %>" placeholder="0.8" inputmode="decimal">
|
|
366
|
-
</label>
|
|
367
|
-
</div>
|
|
368
|
-
<label class="check">
|
|
369
|
-
<input type="checkbox" name="budget_soft" value="1"<%== " checked" if bd["soft"] %>>
|
|
370
|
-
soft cap <span class="muted">— crossing it warns and runs; hard (default) fails the turn</span>
|
|
371
|
-
</label>
|
|
372
|
-
</fieldset>
|
|
373
|
-
|
|
374
|
-
<fieldset class="subgroup">
|
|
375
|
-
<legend>reliability <span class="muted">— WS3: retries, fallback chain, circuit breaker</span></legend>
|
|
376
|
-
<% rel = @agent.reliability || {}; brk = rel["circuit_breaker"] || {} %>
|
|
377
|
-
<div class="grid-3">
|
|
378
|
-
<label>retries
|
|
379
|
-
<input type="text" name="reliability_retries" value="<%= rel["retries"] %>" inputmode="numeric">
|
|
380
|
-
</label>
|
|
381
|
-
<label>backoff
|
|
382
|
-
<select name="reliability_backoff">
|
|
383
|
-
<option value="">none</option>
|
|
384
|
-
<% %w[constant exponential].each do |m| %>
|
|
385
|
-
<option value="<%= m %>"<%== ' selected' if rel["backoff"] == m %>><%= m %></option>
|
|
386
|
-
<% end %>
|
|
387
|
-
</select>
|
|
388
|
-
</label>
|
|
389
|
-
<label>timeout (s)
|
|
390
|
-
<input type="text" name="reliability_timeout" value="<%= rel["timeout"] %>" placeholder="30" inputmode="numeric">
|
|
391
|
-
</label>
|
|
392
|
-
<label>breaker after <span class="muted">(failures)</span>
|
|
393
|
-
<input type="text" name="reliability_breaker_after" value="<%= brk["after"] %>" inputmode="numeric">
|
|
394
|
-
</label>
|
|
395
|
-
<label>breaker within (s)
|
|
396
|
-
<input type="text" name="reliability_breaker_within" value="<%= brk["within"] %>" inputmode="numeric">
|
|
397
|
-
</label>
|
|
398
|
-
<label>breaker cooldown (s)
|
|
399
|
-
<input type="text" name="reliability_breaker_cooldown" value="<%= brk["cooldown"] %>" inputmode="numeric">
|
|
400
|
-
</label>
|
|
401
|
-
</div>
|
|
402
|
-
<label>fallback models <span class="muted">(comma-separated refs, tried after retries)</span>
|
|
403
|
-
<input type="text" name="reliability_fallback" value="<%= agent_list(rel["fallback"]) %>" placeholder="openai/gpt-4o-mini">
|
|
404
|
-
</label>
|
|
405
|
-
</fieldset>
|
|
239
|
+
<%== render("_cfg_budget") %>
|
|
240
|
+
<%== render("_cfg_reliability") %>
|
|
406
241
|
</div>
|
|
407
242
|
|
|
408
243
|
<div class="config-group" data-config-group="routing"<%== " hidden" unless @config_group == "routing" %>>
|
|
409
244
|
<fieldset class="subgroup">
|
|
410
245
|
<legend>routing, media, alerts & the edge</legend>
|
|
411
|
-
<label>routes <span class="muted">—
|
|
246
|
+
<label>routes <span class="muted">— JSON: intent → description / delegate / stuck (blank = no routing)</span>
|
|
412
247
|
<textarea name="routes" rows="4" spellcheck="false" class="mono" placeholder='{ "shopping": "the customer wants to browse", "default": "shopping" }'><%= agent_json(@agent.routes) %></textarea>
|
|
413
248
|
</label>
|
|
414
|
-
<label>outputs <span class="muted">—
|
|
249
|
+
<label>outputs <span class="muted">— JSON: generated media the agent may produce (blank = none)</span>
|
|
415
250
|
<textarea name="outputs" rows="3" spellcheck="false" class="mono" placeholder='{ "image": { "model": "gpt-image-1" } }'><%= agent_json(@agent.outputs) %></textarea>
|
|
416
251
|
</label>
|
|
417
252
|
<label>STT vocabulary prompt <span class="muted">— domain words (products, brands) expected in this agent's voice notes (blank = deployment default)</span>
|
|
418
|
-
<input type="text" name="stt_prompt" value="<%= @agent.stt_prompt %>" placeholder="
|
|
253
|
+
<input type="text" name="stt_prompt" value="<%= @agent.stt_prompt %>" placeholder="Acme Surf, tênis, boné trucker, chinelo">
|
|
419
254
|
</label>
|
|
420
255
|
<div class="grid-2">
|
|
421
|
-
|
|
422
|
-
<input type="text" name="alerts_webhook" value="<%= @agent.alerts && @agent.alerts["webhook"] %>" placeholder="https://ops.example.com/insika-alerts">
|
|
423
|
-
</label>
|
|
256
|
+
<%== render("_cfg_alerts") %>
|
|
424
257
|
<label class="check">
|
|
425
258
|
<input type="checkbox" name="stuck_signal" value="1"<%== " checked" if @agent.stuck_signal %>>
|
|
426
|
-
stuck signal <span class="muted">— the model may end a turn <code>stuck</code
|
|
259
|
+
stuck signal <span class="muted">— the model may end a turn <code>stuck</code></span>
|
|
427
260
|
</label>
|
|
428
261
|
</div>
|
|
429
262
|
<div class="grid-2">
|
|
@@ -471,6 +304,9 @@
|
|
|
471
304
|
<label>approvals required <span class="muted">(tools whose call suspends for a human)</span>
|
|
472
305
|
<input type="text" name="approvals_required" value="<%= agent_list(@agent.approvals_required) %>" placeholder="send_discount_coupon">
|
|
473
306
|
</label>
|
|
307
|
+
<label>customer confirm <span class="muted">(tools held until the customer says yes on the next message)</span>
|
|
308
|
+
<input type="text" name="customer_confirm" value="<%= agent_list(@agent.customer_confirm) %>" placeholder="create_order">
|
|
309
|
+
</label>
|
|
474
310
|
<label>policies <span class="muted">(registry names)</span>
|
|
475
311
|
<input type="text" name="policies" value="<%= agent_list(@agent.policies) %>" placeholder="tool_allowlist, skill_allowlist">
|
|
476
312
|
</label>
|
|
@@ -490,12 +326,16 @@
|
|
|
490
326
|
</label>
|
|
491
327
|
<label class="check">
|
|
492
328
|
<input type="checkbox" name="prompt_caching" value="1"<%== " checked" if @agent.prompt_caching %>>
|
|
493
|
-
prompt caching <span class="muted">— Anthropic breakpoint
|
|
329
|
+
prompt caching <span class="muted">— Anthropic breakpoint at the identity layer; memory/knowledge render below it</span>
|
|
494
330
|
</label>
|
|
495
331
|
<label class="check">
|
|
496
332
|
<input type="checkbox" name="tool_output_compression" value="1"<%== " checked" if @agent.tool_output_compression %>>
|
|
497
333
|
tool output compression <span class="muted">— mechanical dedupe of repeated tool results</span>
|
|
498
334
|
</label>
|
|
335
|
+
<label class="check">
|
|
336
|
+
<input type="checkbox" name="fencing" value="1"<%== " checked" if @agent.fencing %>>
|
|
337
|
+
fencing <span class="muted">— sanitize tool results, memory and knowledge before the model reads them (invisible characters, forged turn markers, transcript-shaped tags) and add the "data, not instructions" notice</span>
|
|
338
|
+
</label>
|
|
499
339
|
<label class="check">
|
|
500
340
|
<input type="checkbox" name="tool_persistence" value="1"<%== " checked" unless @agent.tool_persistence == false %>>
|
|
501
341
|
tool persistence <span class="muted">— the engine's "Tool discipline" prompt block (retry weak/empty tool results differently); ON by default</span>
|
|
@@ -1,8 +1,63 @@
|
|
|
1
1
|
<!-- ── History ───────────────────────────────────────────────────────────── -->
|
|
2
2
|
<section class="section" id="history" data-tabs-target="panel">
|
|
3
|
+
<div class="section-head">
|
|
4
|
+
<h2>What changed, and what it did</h2>
|
|
5
|
+
<p class="muted"><%= @change_primary ? "Primary outcome · #{@change_primary}" : "All outcomes" %> per day, last <%= @change_days.size %> days. A dashed line marks each change to a prompt file or to one of this agent's skills.</p>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="card">
|
|
8
|
+
<% max = [@change_days.values.max || 0, 1].max; span = @change_days.size * 86_400.0 %>
|
|
9
|
+
<svg class="barchart" viewBox="0 0 700 176" preserveAspectRatio="none" role="img" aria-label="outcomes per day, last <%= @change_days.size %> days, with prompt-file changes">
|
|
10
|
+
<% slot = 700.0 / @change_days.size; bw = slot * 0.58 %>
|
|
11
|
+
<% @change_days.each_with_index do |(d, c), i| %>
|
|
12
|
+
<% h = (c.to_f / max) * 150.0; x = (i * slot) + ((slot - bw) / 2.0) %>
|
|
13
|
+
<rect class="bar<%== ' bar-empty' if c.zero? %>" x="<%= format('%.1f', x) %>" y="<%= format('%.1f', c.zero? ? 157.0 : 160.0 - h) %>"
|
|
14
|
+
width="<%= format('%.1f', bw) %>" height="<%= format('%.1f', c.zero? ? 3.0 : [h, 3.0].max) %>" rx="2">
|
|
15
|
+
<title><%= d.strftime('%b %-d') %> · <%= c %> outcome<%= 's' unless c == 1 %></title>
|
|
16
|
+
</rect>
|
|
17
|
+
<% end %>
|
|
18
|
+
<% @changes.each do |ch| %>
|
|
19
|
+
<% x = format('%.1f', (ch[:at] - @change_start) / span * 700.0) %>
|
|
20
|
+
<line class="change-mark" x1="<%= x %>" y1="0" x2="<%= x %>" y2="160" vector-effect="non-scaling-stroke"><title><%= ch[:kind] == :skill ? "skill " : "" %><%= ch[:file] %> · <%= ch[:at].strftime('%b %-d %H:%M UTC') %></title></line>
|
|
21
|
+
<% end %>
|
|
22
|
+
<line class="barchart-axis-line" x1="0" y1="160" x2="700" y2="160" />
|
|
23
|
+
</svg>
|
|
24
|
+
<div class="barchart-axis"><span><%= @change_days.keys.first.strftime('%b %-d') %></span><span>today</span></div>
|
|
25
|
+
</div>
|
|
26
|
+
<% if @changes.empty? %>
|
|
27
|
+
<div class="empty">No prompt or skill changes in the last <%= @change_days.size %> days.</div>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% @changes.each do |ch| %>
|
|
30
|
+
<div class="card">
|
|
31
|
+
<div class="drill-section-head">
|
|
32
|
+
<h3><% if ch[:kind] == :skill %><span class="muted">skill</span> <% end %><%= ch[:file] %></h3>
|
|
33
|
+
<span class="muted mono"><%= ch[:at].strftime('%Y-%m-%d %H:%M UTC') %></span>
|
|
34
|
+
</div>
|
|
35
|
+
<% unless ch[:removed].empty? && ch[:added].empty? %>
|
|
36
|
+
<pre class="diff"><% ch[:removed].first(6).each do |l| %><span class="del">- <%= l %></span><% end %><% ch[:added].first(6).each do |l| %><span class="ins">+ <%= l %></span><% end %></pre>
|
|
37
|
+
<% end %>
|
|
38
|
+
<p>
|
|
39
|
+
<% if ch[:before] && ch[:after] %>
|
|
40
|
+
Outcomes: <strong class="tnum"><%= ch[:after] %></strong> in the 7 days after vs <strong class="tnum"><%= ch[:before] %></strong> in the 7 days before.
|
|
41
|
+
<% else %>
|
|
42
|
+
<span class="muted">Outcomes 7 days after vs 7 days before: not enough data.</span>
|
|
43
|
+
<% end %>
|
|
44
|
+
</p>
|
|
45
|
+
<% if ch[:kind] == :skill %>
|
|
46
|
+
<a href="/studio/agents/<%= Rack::Utils.escape(@agent.id) %>/skills/<%= Rack::Utils.escape(ch[:file]) %>" data-turbo-frame="_top">Open the skill</a>
|
|
47
|
+
<% else %>
|
|
48
|
+
<form method="post" action="/studio/agents/<%= Rack::Utils.escape(@agent.id) %>/prompts/restore">
|
|
49
|
+
<%== csrf_tag %>
|
|
50
|
+
<input type="hidden" name="file" value="<%= ch[:file] %>">
|
|
51
|
+
<input type="hidden" name="version" value="<%= ch[:version] %>">
|
|
52
|
+
<button type="submit" class="ghost">Restore the version before</button>
|
|
53
|
+
</form>
|
|
54
|
+
<% end %>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|
|
57
|
+
|
|
3
58
|
<div class="section-head">
|
|
4
59
|
<h2>History</h2>
|
|
5
|
-
<p class="muted">recent conversations
|
|
60
|
+
<p class="muted">This agent's most recent conversations. Every conversation is in <a href="/studio/chats?agent=<%= Rack::Utils.escape(@agent.id) %>" data-turbo-frame="_top">Chats</a>.</p>
|
|
6
61
|
</div>
|
|
7
62
|
<% if @recent_sessions.empty? %>
|
|
8
63
|
<div class="empty">No conversations yet. Start one in the <a href="/studio/playground?agent=<%= Rack::Utils.escape(@agent.id) %>" data-turbo-frame="_top">playground</a>.</div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!-- ── Performance ───────────────────────────────────────────────────────── -->
|
|
2
|
+
<% periods = { "24h" => "24h", "7d" => "7 days", "30d" => "30 days" } %>
|
|
3
|
+
<% q = Rack::Utils.escape(@agent.id) %>
|
|
4
|
+
<section class="section" id="performance" data-tabs-target="panel">
|
|
5
|
+
<div class="section-head">
|
|
6
|
+
<h2>Performance</h2>
|
|
7
|
+
<p class="muted">The same numbers as the <a href="/studio/home?agent=<%= q %>" data-turbo-frame="_top">home</a> and <a href="/studio/models?agent=<%= q %>" data-turbo-frame="_top">Models</a>, for this agent. Latency is the whole turn; first balloon is what a channel customer waits.</p>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="card">
|
|
10
|
+
<table class="grid">
|
|
11
|
+
<thead><tr><th></th><% periods.each_value do |label| %><th class="num"><%= label %></th><% end %></tr></thead>
|
|
12
|
+
<tbody>
|
|
13
|
+
<% pct = ->(ms) { ms.nil? ? "—" : format("%.1fs", ms / 1000.0) } %>
|
|
14
|
+
<% rows = [
|
|
15
|
+
["Conversations", ->(p) { @perf_activity[p][:conversations] }],
|
|
16
|
+
["Messages", ->(p) { @perf_activity[p][:messages] }],
|
|
17
|
+
["Outcomes", ->(p) { @perf_activity[p][:outcomes] }],
|
|
18
|
+
["Cost", ->(p) { usd(@perf.dig(p, "totals", "cost")) }],
|
|
19
|
+
["Model requests", ->(p) { @perf.dig(p, "totals", "requests") || 0 }],
|
|
20
|
+
["Tokens in / out", ->(p) { t = @perf.dig(p, "totals") || {}; "#{t['input_tokens'] || 0} / #{t['output_tokens'] || 0}" }],
|
|
21
|
+
["Turns", ->(p) { @perf.dig(p, "turns", "count") || 0 }],
|
|
22
|
+
["Failed turns", ->(p) { t = @perf.dig(p, "turns"); t && t["count"].positive? ? format("%d (%.1f%%)", t["failures"], t["failures"] * 100.0 / t["count"]) : "—" }],
|
|
23
|
+
["ttft p50 / p95", ->(p) { w = @perf.dig(p, "turns", "windows", "ttft_ms") || {}; "#{pct.(w['p50'])} / #{pct.(w['p95'])}" }],
|
|
24
|
+
["Turn p50 / p95", ->(p) { w = @perf.dig(p, "turns", "windows", "total_ms") || {}; "#{pct.(w['p50'])} / #{pct.(w['p95'])}" }],
|
|
25
|
+
["First balloon p95", ->(p) { pct.(@perf.dig(p, "turns", "windows", "first_balloon_ms", "p95")) }],
|
|
26
|
+
["Tool calls ok", ->(p) { tools = Array(@perf.dig(p, "turns", "tools")); calls = tools.sum { |t| t["calls"] }; calls.zero? ? "—" : format("%.1f%% of %d", (calls - tools.sum { |t| t["failures"] }) * 100.0 / calls, calls) }]
|
|
27
|
+
] %>
|
|
28
|
+
<% rows.each do |label, value| %>
|
|
29
|
+
<tr><td><%= label %></td><% periods.each_key do |p| %><td class="num"><%= value.(p) %></td><% end %></tr>
|
|
30
|
+
<% end %>
|
|
31
|
+
</tbody>
|
|
32
|
+
</table>
|
|
33
|
+
</div>
|
|
34
|
+
</section>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<section class="section" id="prompts" data-tabs-target="panel">
|
|
3
3
|
<div class="section-head">
|
|
4
4
|
<h2>Prompts <span class="pill info plain"><%= @prompt_files.size %></span></h2>
|
|
5
|
-
<p class="muted">
|
|
5
|
+
<p class="muted">Define this agent’s identity and voice. Save each prompt separately; no files means the deployment default is used.</p>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
<div class="drill" data-controller="list-filter">
|
|
@@ -60,19 +60,17 @@
|
|
|
60
60
|
<% else %>
|
|
61
61
|
<input type="hidden" name="file" value="<%= @prompt_selected %>">
|
|
62
62
|
<% end %>
|
|
63
|
-
<label>content <span class="muted">(markdown — the agent's identity/voice)</span></label>
|
|
63
|
+
<label for="prompt-content">content <span class="muted">(markdown — the agent's identity/voice)</span></label>
|
|
64
64
|
<div data-controller="code-editor" data-code-editor-language-value="markdown" data-code-editor-preview-value="true">
|
|
65
|
-
<textarea name="content" data-code-editor-target="source" rows="18"><%= new ? "" : (pf ? pf[:content] : "") %></textarea>
|
|
65
|
+
<textarea id="prompt-content" name="content" data-code-editor-target="source" rows="18"><%= new ? "" : (pf ? pf[:content] : "") %></textarea>
|
|
66
66
|
</div>
|
|
67
67
|
</form>
|
|
68
68
|
|
|
69
69
|
<% if !new && pf %>
|
|
70
70
|
<% unless pf[:versions].empty? %>
|
|
71
|
-
<
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
<span class="muted">store-backed history — restore replaces the current content.</span>
|
|
75
|
-
</div>
|
|
71
|
+
<details class="drill-section newfile">
|
|
72
|
+
<summary>Previous versions <span class="count"><%= pf[:versions].size %></span></summary>
|
|
73
|
+
<p class="muted">Restore replaces the current content.</p>
|
|
76
74
|
<ul class="versions-list">
|
|
77
75
|
<% pf[:versions].each_with_index do |v, i| %>
|
|
78
76
|
<li>
|
|
@@ -86,7 +84,7 @@
|
|
|
86
84
|
</li>
|
|
87
85
|
<% end %>
|
|
88
86
|
</ul>
|
|
89
|
-
</
|
|
87
|
+
</details>
|
|
90
88
|
<% end %>
|
|
91
89
|
|
|
92
90
|
<div class="drill-section">
|