Class: Insika::DSL::Builder

Inherits:
Object
  • Object
show all
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

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

Operator alert delivery (WS6): POST this agent's budget_warning / breaker_open / delivery_failed events to the webhook, as JSON. alerts webhook: "https://ops.example.com/insika-alerts"



232
# File 'lib/insika/dsl.rb', line 232

def alerts(hash) = (@config[:alerts] ||= {}).merge!(hash.transform_keys(&:to_s))

#api_base(value) ⇒ Object



294
# File 'lib/insika/dsl.rb', line 294

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.



293
# File 'lib/insika/dsl.rb', line 293

def api_key(value) = @runtime[:api_key] = value.to_s

#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"



265
266
267
# File 'lib/insika/dsl.rb', line 265

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

#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



275
# File 'lib/insika/dsl.rb', line 275

def edge_stream(hash) = (@config[:edge_stream] ||= {}).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." }



251
# File 'lib/insika/dsl.rb', line 251

def guardrails(hash) = (@config[:guardrails] ||= {}).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(...).



284
# File 'lib/insika/dsl.rb', line 284

def limit(key, value) = (@config[:limits] ||= {})[key.to_sym] = value

#limits(hash) ⇒ Object



285
# File 'lib/insika/dsl.rb', line 285

def limits(hash) = (@config[:limits] ||= {}).merge!(hash.transform_keys(&:to_sym))

#max_tokens(value) ⇒ Object



281
# File 'lib/insika/dsl.rb', line 281

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



288
# File 'lib/insika/dsl.rb', line 288

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

#param(key, value) ⇒ Object

LLM generation params. param:temperature, 0.2 or params(...).



278
# File 'lib/insika/dsl.rb', line 278

def param(key, value) = (@config[:params] ||= {})[key.to_sym] = value

#params(hash) ⇒ Object



279
# File 'lib/insika/dsl.rb', line 279

def params(hash) = (@config[:params] ||= {}).merge!(hash.transform_keys(&:to_sym))

#policies(*names) ⇒ Object



287
# File 'lib/insika/dsl.rb', line 287

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 }



259
# File 'lib/insika/dsl.rb', line 259

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 }



225
226
227
# File 'lib/insika/dsl.rb', line 225

def reliability(hash)
  (@config[:reliability] ||= {}).merge!(hash.transform_keys(&:to_s))
end

#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.



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



238
# File 'lib/insika/dsl.rb', line 238

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



280
# File 'lib/insika/dsl.rb', line 280

def temperature(value) = param(:temperature, value)

#to_packObject

The generated portable artifact — the heart of "generates the data".



297
298
299
300
301
302
# File 'lib/insika/dsl.rb', line 297

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.



243
# File 'lib/insika/dsl.rb', line 243

def tool_output_compression(on = true) = @config[:tool_output_compression] = 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