Class: Insika::DSL::Builder
- Inherits:
-
Object
- Object
- Insika::DSL::Builder
- Defined in:
- lib/insika/dsl.rb
Overview
Collects the declarations and emits a Insika::Pack. Declarations map 1:1 to the pack manifest (AgentProfile.build attrs) + the pack's files/skills/tools — so what you write is exactly the data the engine stores.
Instance Method Summary collapse
-
#alerts(hash) ⇒ Object
breaker_open / delivery_failed events to the webhook as JSON.
- #api_base(value) ⇒ Object
-
#api_key(value) ⇒ Object
--- runtime (LLM provider) config — NOT part of the pack ------------ Configures RubyLLM at chat/serve time.
-
#briefing_fields(*names) ⇒ Object
The per-session working-state schema this agent keeps and asks for a flat list of field names.
-
#budget(hash) ⇒ Object
Spend caps per calendar window (WS2): daily/monthly token budgets for this agent, per (tenant, agent) when multi-tenant.
- #build(&block) ⇒ Object
-
#data_tool(defn) ⇒ Object
A DATA-DEFINED (declarative HTTP) tool — pure config-over-code.
-
#declares(*names) ⇒ Object
Facts about THIS deployment that are not tools, so an eval case can declare what it needs and be skipped where it is absent instead of failing for the wrong reason.
- #deny_tools(*names) ⇒ Object
-
#distill(hash) ⇒ Object
The session-distillation declaration: what counts as a fact for THIS store, distilled from finished customer conversations.
-
#edge_stream(hash) ⇒ Object
Which internal channels may cross to the CUSTOMER.
-
#followup(hash) ⇒ Object
The follow-up declaration: the agent may book a follow-up with a customer at a future time (
scheduletool); the engine fires the synthetic turn and enforces the policy (quiet hours, max frequency, cancellation keywords, silence detection) at fire time. -
#funnel(hash) ⇒ Object
The outcome funnel declaration: the store's stage vocabulary as pack data — the engine folds WS7 outcome kinds into the DECLARED stages, and never hard-codes one itself.
-
#grounding(hash = {}) ⇒ Object
Evidence-grounding policy: the pack declares how the engine polices product claims against the evidence ledger.
-
#guardrails(hash) ⇒ Object
Content-safety guardrails — opt-in and configurable per agent.
-
#harvest(hash) ⇒ Object
The gated-harvest declaration: the engine may read this agent's finished traffic and propose SKILLS for the store's playbook — through the negative list, the grounding filter and the double gate, never applied automatically.
-
#initialize(id) ⇒ Builder
constructor
A new instance of Builder.
- #instructions(text) ⇒ Object (also: #prompt)
-
#limit(key, value) ⇒ Object
Per-agent limits (timeouts/budgets).
- #limits(hash) ⇒ Object
- #max_tokens(value) ⇒ Object
-
#memory(on = true) ⇒ Object
--- knobs -----------------------------------------------------------.
- #metadata(hash) ⇒ Object
-
#model(name) ⇒ Object
--- identity & model ------------------------------------------------.
-
#outputs(hash) ⇒ Object
Generated-media output policy (WS9, saída): the media kinds this agent MAY generate as turn outputs, with per-kind config.
-
#param(key, value) ⇒ Object
LLM generation params.
- #params(hash) ⇒ Object
- #policies(*names) ⇒ Object
-
#prompt_file(name, content) ⇒ Object
An extra prompt FILE (identity fragment).
-
#provider(name) ⇒ Object
Provider for both the profile AND the RubyLLM configuration at run time.
-
#refine(hash) ⇒ Object
Refinement — how the agent's own instruction files may be improved from real traffic.
-
#reliability(hash) ⇒ Object
Provider-interaction reliability, as DATA (WS3): retries + exponential backoff on transient failures, a fallback model chain (mid-turn rotation), and a circuit breaker per (tenant, provider/model) that fail-fasts once the window trips.
-
#routes(hash) ⇒ Object
Intent routing (WS4): classify each turn's message into one route with a cheap model BEFORE the ask.
-
#skill(name, content = nil, description: nil, instructions: nil) ⇒ Object
--- skills ---------------------------------------------------------- skill "escalate", "
" — or — skill "escalate", description: "…", instructions: "…" The name is auto-added to the agent's skill allowlist. -
#skills_eager(*names) ⇒ Object
skills_eager — turns progressive disclosure off for THIS agent, wholly or in part.
-
#stuck_signal(on = true) ⇒ Object
The agent may signal it cannot proceed (WS5): when on, the model can call
signal_stuck, which ends the turn withoutcome: :stuck+ a final message + a:turn_stuckevent. -
#subagents(*ids) ⇒ Object
--- delegation ------------------------------------------------------ subagents "security", "performance" → the child agents this one MAY spawn.
- #temperature(value) ⇒ Object
-
#to_pack ⇒ Object
The generated portable artifact — the heart of "generates the data".
-
#tool_output_compression(on = true) ⇒ Object
Mechanical tool-result dedupe in the replayed history (no-LLM compaction, apt for bloated transcripts).
-
#tool_persistence(on = true) ⇒ Object
The engine's "Tool discipline" block in the system prompt (retry a weak/empty tool result with a different approach before giving up).
-
#tools(*names) ⇒ Object
--- tools ----------------------------------------------------------- tools "a", "b" → allowlist [names].
Constructor Details
#initialize(id) ⇒ Builder
Returns a new instance of Builder.
110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/insika/dsl.rb', line 110 def initialize(id) @id = id.to_s @config = {} @files = {} @skills = {} @tools = [] # Auto-enable the allowlist policies: harmless when the allowlist is nil=all, # correct once you restrict tools/skills. Visible in #to_pack — no hidden magic. @config[:policies] = %i[tool_allowlist skill_allowlist] @runtime = {} # non-pack knobs (llm provider/key/base) consumed by the runtime end |
Instance Method Details
#alerts(hash) ⇒ Object
breaker_open / delivery_failed events to the webhook as JSON.
271 |
# File 'lib/insika/dsl.rb', line 271 def alerts(hash) = (@config[:alerts] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#api_base(value) ⇒ Object
374 |
# File 'lib/insika/dsl.rb', line 374 def api_base(value) = @runtime[:api_base] = value.to_s |
#api_key(value) ⇒ Object
--- runtime (LLM provider) config — NOT part of the pack ------------ Configures RubyLLM at chat/serve time. Defaults: provider = the agent's provider; key = ENV.
373 |
# File 'lib/insika/dsl.rb', line 373 def api_key(value) = @runtime[:api_key] = value.to_s |
#briefing_fields(*names) ⇒ Object
The per-session working-state schema this agent keeps and asks for a flat list of field names. [] = off (no provider output, no update_briefing/set_next_step tools). briefing_fields "size", "budget", "delivery_day"
296 297 298 |
# File 'lib/insika/dsl.rb', line 296 def briefing_fields(*names) @config[:briefing_fields] = names.flatten.map(&:to_s) end |
#budget(hash) ⇒ Object
Spend caps per calendar window (WS2): daily/monthly token budgets for
this agent, per (tenant, agent) when multi-tenant. HARD is the default:
absent soft: (or soft: false) turns the cap into a hard wall (the
turn fails with budget_exceeded + retry_after); soft: true warns once
per window and keeps running.
budget daily: 100_000, monthly: 2_000_000, soft: false
216 |
# File 'lib/insika/dsl.rb', line 216 def budget(hash) = (@config[:budget] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#build(&block) ⇒ Object
122 123 124 125 |
# File 'lib/insika/dsl.rb', line 122 def build(&block) instance_eval(&block) if block Definition.new(pack: to_pack, runtime: @runtime) end |
#data_tool(defn) ⇒ Object
A DATA-DEFINED (declarative HTTP) tool — pure config-over-code. defn is a
ToolDefinition hash (name/description/parameters/binding…). Its name is
auto-added to the allowlist so the agent can call its own tool.
157 158 159 160 161 162 163 |
# File 'lib/insika/dsl.rb', line 157 def data_tool(defn) h = defn.transform_keys(&:to_s) @tools << h name = h["name"].to_s (@config[:tools_allow] ||= []) << name unless name.empty? || Array(@config[:tools_allow]).include?(name) h end |
#declares(*names) ⇒ Object
Facts about THIS deployment that are not tools, so an eval case can declare what it needs and be skipped where it is absent instead of failing for the wrong reason. declares "promotions", "human_handoff"
345 346 347 |
# File 'lib/insika/dsl.rb', line 345 def declares(*names) (@config[:capabilities_declared] ||= []).concat(names.flatten.map(&:to_s)) end |
#deny_tools(*names) ⇒ Object
150 151 152 |
# File 'lib/insika/dsl.rb', line 150 def deny_tools(*names) @config[:tools_deny] = names.flatten.map(&:to_s) end |
#distill(hash) ⇒ Object
The session-distillation declaration: what counts as a fact
for THIS store, distilled from finished customer conversations. Pack
data — merges, so repeated calls accumulate (like budget). prompt and
model are pack-authored keys the DSL passes through.
distill enabled: true, idle_hours: 6, max_proposals: 10
245 |
# File 'lib/insika/dsl.rb', line 245 def distill(hash) = (@config[:distill] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#edge_stream(hash) ⇒ Object
Which internal channels may cross to the CUSTOMER. Both off by default: the
answer is the answer, and the provider's reasoning (thinking) or the model
narrating its tool loop (intermediate) is for the Studio and the trace.
Each opted-in channel gets its OWN frame type at /v1/responses — never the
answer's — so a consumer that only reads the answer is unaffected either way.
edge_stream thinking: true, intermediate: false
355 |
# File 'lib/insika/dsl.rb', line 355 def edge_stream(hash) = (@config[:edge_stream] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#followup(hash) ⇒ Object
The follow-up declaration: the agent may book a follow-up
with a customer at a future time (schedule tool); the engine fires the
synthetic turn and enforces the policy (quiet hours, max frequency,
cancellation keywords, silence detection) at fire time. Pack data —
merges, so repeated calls accumulate (like budget).
followup arm: "schedule",
policy: { quiet_hours: { timezone: "America/Sao_Paulo",
start: "21:30", end: "09:00" },
max_frequency: "2/24h",
cancel_keywords: ["não quero mais contato"],
silence_after_sends: 3 }
238 |
# File 'lib/insika/dsl.rb', line 238 def followup(hash) = (@config[:followup] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#funnel(hash) ⇒ Object
The outcome funnel declaration: the store's stage vocabulary as pack data — the engine folds WS7 outcome kinds into the DECLARED stages, and never hard-codes one itself. Merges, so repeated calls accumulate (like budget). funnel stages: %w[greeted qualified cart paid], advance_on: { "pix_paid" => "paid", "abandoned_cart" => "cart" }, primary: "paid", attribution_window: "72h"
225 |
# File 'lib/insika/dsl.rb', line 225 def funnel(hash) = (@config[:funnel] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#grounding(hash = {}) ⇒ Object
Evidence-grounding policy: the pack declares how the engine
polices product claims against the evidence ledger. Same
config-over-code shape as guardrails; absent = off (parity).
grounding mode: :flag, matcher: { sku: '\b[A-Z]2,4\d4,8\b' }
331 |
# File 'lib/insika/dsl.rb', line 331 def grounding(hash = {}) = (@config[:grounding] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#guardrails(hash) ⇒ Object
Content-safety guardrails — opt-in and configurable per agent. Pure config-over-code: the hash is stored on the profile and consumed by Safety::Config.from_profile. Merges, so repeated calls accumulate. guardrails input: true, output: true, strictness: "medium", moderator: "deepseek/deepseek-v4-flash", responses: { "injection" => "I can't help with that." }
325 |
# File 'lib/insika/dsl.rb', line 325 def guardrails(hash) = (@config[:guardrails] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#harvest(hash) ⇒ Object
The gated-harvest declaration: the engine may read this
agent's finished traffic and propose SKILLS for the store's playbook —
through the negative list, the grounding filter and the double gate,
never applied automatically. Pack data — merges, so repeated calls
accumulate (like budget). prompt/model are pack-authored keys the
DSL passes through.
harvest enabled: true,
negative_list: [ { rule: "no-competitor-prices", pattern: "concorrente" } ],
miner: { model: "deepseek-v4-flash", window: { last_sessions: 200 } }
256 |
# File 'lib/insika/dsl.rb', line 256 def harvest(hash) = (@config[:harvest] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#instructions(text) ⇒ Object Also known as: prompt
136 |
# File 'lib/insika/dsl.rb', line 136 def instructions(text) = @config[:base_prompt] = text.to_s |
#limit(key, value) ⇒ Object
Per-agent limits (timeouts/budgets). limit :turn_timeout, 120 or limits(...).
364 |
# File 'lib/insika/dsl.rb', line 364 def limit(key, value) = (@config[:limits] ||= {})[key.to_sym] = value |
#limits(hash) ⇒ Object
365 |
# File 'lib/insika/dsl.rb', line 365 def limits(hash) = (@config[:limits] ||= {}).merge!(hash.transform_keys(&:to_sym)) |
#max_tokens(value) ⇒ Object
361 |
# File 'lib/insika/dsl.rb', line 361 def max_tokens(value) = param(:max_tokens, value) |
#memory(on = true) ⇒ Object
--- knobs -----------------------------------------------------------
208 |
# File 'lib/insika/dsl.rb', line 208 def memory(on = true) = @config[:memory] = on |
#metadata(hash) ⇒ Object
368 |
# File 'lib/insika/dsl.rb', line 368 def (hash) = (@config[:metadata] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#model(name) ⇒ Object
--- identity & model ------------------------------------------------
128 |
# File 'lib/insika/dsl.rb', line 128 def model(name) = @config[:model] = name.to_s |
#outputs(hash) ⇒ Object
Generated-media output policy (WS9, saída): the media kinds this agent MAY generate as turn outputs, with per-kind config. The other half of the gate is the CHANNEL's: the request must declare the matching capability for the tools to exist at all. outputs image: { model: "gpt-image-1", size: "1024x1024" }, tts: { model: "tts-1", voice: "alloy" }
306 |
# File 'lib/insika/dsl.rb', line 306 def outputs(hash) = (@config[:outputs] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#param(key, value) ⇒ Object
LLM generation params. param:temperature, 0.2 or params(...).
358 |
# File 'lib/insika/dsl.rb', line 358 def param(key, value) = (@config[:params] ||= {})[key.to_sym] = value |
#params(hash) ⇒ Object
359 |
# File 'lib/insika/dsl.rb', line 359 def params(hash) = (@config[:params] ||= {}).merge!(hash.transform_keys(&:to_sym)) |
#policies(*names) ⇒ Object
367 |
# File 'lib/insika/dsl.rb', line 367 def policies(*names) = @config[:policies] = names.flatten.map(&:to_sym) |
#prompt_file(name, content) ⇒ Object
An extra prompt FILE (identity fragment). Name = the file name (e.g. "SOUL.md").
140 141 142 |
# File 'lib/insika/dsl.rb', line 140 def prompt_file(name, content) @files[name.to_s] = content.to_s end |
#provider(name) ⇒ Object
Provider for both the profile AND the RubyLLM configuration at run time.
131 132 133 134 |
# File 'lib/insika/dsl.rb', line 131 def provider(name) @config[:provider] = name.to_s @runtime[:provider] ||= name.to_s end |
#refine(hash) ⇒ Object
Refinement — how the agent's own instruction files may be
improved from real traffic. Same config-over-code shape as guardrails;
omitting it entirely leaves the agent report-only (writes nothing).
refine mode: "propose", window: { last_sessions: 200 }, files: %w,
proposers: ["deepseek/deepseek-v4-flash", "gpt-5-mini"],
budget: { tokens: 200_000 }
339 |
# File 'lib/insika/dsl.rb', line 339 def refine(hash) = (@config[:refinement] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#reliability(hash) ⇒ Object
Provider-interaction reliability, as DATA (WS3): retries + exponential
backoff on transient failures, a fallback model chain (mid-turn
rotation), and a circuit breaker per (tenant, provider/model) that
fail-fasts once the window trips. fallback/circuit_breaker entries
are "provider/model" refs or plain model ids.
reliability retries: 3, backoff: "exponential",
fallback: ["gpt-4o-mini"], circuit_breaker: { after: 10, within: 60, cooldown: 300 }
265 266 267 |
# File 'lib/insika/dsl.rb', line 265 def reliability(hash) (@config[:reliability] ||= {}).merge!(hash.transform_keys(&:to_s)) end |
#routes(hash) ⇒ Object
Intent routing (WS4): classify each turn's message into one route with a cheap model BEFORE the ask. A Hash: route name -> description (or a Hash with description/delegate/stuck/message), plus the reserved keys "default" (the deterministic fallback) and "model"/"provider" (the cheap classifier). The classifier prompt is generated — data only. routes "shopping" => "the customer wants to browse products", "order" => { "description" => "asks about an existing order", "delegate" => "order-agent" }, "human" => { "description" => "the customer asks for a person", "stuck" => true }, "default" => "shopping", "model" => "deepseek-v4-flash"
284 |
# File 'lib/insika/dsl.rb', line 284 def routes(hash) = (@config[:routes] ||= {}).merge!(hash.transform_keys(&:to_s)) |
#skill(name, content = nil, description: nil, instructions: nil) ⇒ Object
--- skills ----------------------------------------------------------
skill "escalate", "
169 170 171 172 173 174 |
# File 'lib/insika/dsl.rb', line 169 def skill(name, content = nil, description: nil, instructions: nil) n = name.to_s @skills[n] = normalize_skill(n, content, description, instructions) (@config[:skills] ||= []) << n unless @config.fetch(:skills, []).include?(n) n end |
#skills_eager(*names) ⇒ Object
skills_eager — turns progressive disclosure off for THIS agent, wholly or in
part. The body of an eager skill is in the prompt on every turn, so its
activation is not a decision and cannot be missed; it is paid for on every
turn, so measure the bodies against context_budget first (a stable position
makes them a cacheable prefix).
skills_eager # every allowed skill
skills_eager "formato", "markers" # exactly these
skills_eager false # none (the default)
A LIST and not a per-skill flag because skills are shared: escalation-to-human
sits in several allowlists, and one flag on the skill would force one decision
onto every agent holding it.
189 190 191 192 193 194 195 196 |
# File 'lib/insika/dsl.rb', line 189 def skills_eager(*names) flat = names.flatten @config[:skills_eager] = if flat.empty? then true elsif flat == [true] || flat == [false] then flat.first else flat.map(&:to_s) end end |
#stuck_signal(on = true) ⇒ Object
The agent may signal it cannot proceed (WS5): when on, the model
can call signal_stuck, which ends the turn with outcome: :stuck + a final
message + a :turn_stuck event. What "stuck" means is the consumer's call.
stuck_signal true
290 |
# File 'lib/insika/dsl.rb', line 290 def stuck_signal(on = true) = @config[:stuck_signal] = on |
#subagents(*ids) ⇒ Object
--- delegation ------------------------------------------------------
subagents "security", "performance" → the child agents this one MAY
spawn. CAPACITY field: opt-in, never inherited, and the ids
must be agents of the same system (Insika.system { … }) or already in
the store. Present ⇒ the engine wires spawn_subagent/spawn_subagents.
203 204 205 |
# File 'lib/insika/dsl.rb', line 203 def subagents(*ids) @config[:subagents] = ids.flatten.map(&:to_s) end |
#temperature(value) ⇒ Object
360 |
# File 'lib/insika/dsl.rb', line 360 def temperature(value) = param(:temperature, value) |
#to_pack ⇒ Object
The generated portable artifact — the heart of "generates the data".
377 378 379 380 381 382 |
# File 'lib/insika/dsl.rb', line 377 def to_pack Insika::Pack.from_h( config: @config.merge(id: @id), files: @files, skills: @skills, tools: @tools ) end |
#tool_output_compression(on = true) ⇒ Object
Mechanical tool-result dedupe in the replayed history (no-LLM compaction, apt for bloated transcripts). CHANGES WHAT THE MODEL SEES: repeated identical tool results collapse to a back-reference.
317 |
# File 'lib/insika/dsl.rb', line 317 def tool_output_compression(on = true) = @config[:tool_output_compression] = on |
#tool_persistence(on = true) ⇒ Object
The engine's "Tool discipline" block in the system prompt (retry a weak/empty tool result with a different approach before giving up). ON by default — this setter exists to turn it OFF:
tool_persistence false
312 |
# File 'lib/insika/dsl.rb', line 312 def tool_persistence(on = true) = @config[:tool_persistence] = on |
#tools(*names) ⇒ Object
--- tools ----------------------------------------------------------- tools "a", "b" → allowlist [names]. Not called → nil = all (parity).
146 147 148 |
# File 'lib/insika/dsl.rb', line 146 def tools(*names) @config[:tools_allow] = names.flatten.map(&:to_s) end |