Class: Insika::DSL::SystemBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/dsl.rb

Overview

Collects several agents into ONE runtime. A single agent is a Definition; more than one needs a container, because delegation (subagents) and any multi-agent pattern only mean something when the children live in the same graph. It adds no new engine path: each agent is still its own Pack, imported through the standard PackImporter.

Instance Method Summary collapse

Constructor Details

#initializeSystemBuilder

Returns a new instance of SystemBuilder.



47
48
49
50
51
52
# File 'lib/insika/dsl.rb', line 47

def initialize
  @definitions = []
  @workflows = []
  @mcp_instances = []
  @runtime = {}
end

Instance Method Details

#agent(id, &block) ⇒ Object

Declares one agent — the SAME block the standalone Insika.agent takes. Returns its Definition, so a script can keep a handle if it wants one.



64
65
66
67
68
69
70
71
72
# File 'lib/insika/dsl.rb', line 64

def agent(id, &block)
  definition = Builder.new(id).build(&block)
  if @definitions.any? { |d| d.id == definition.id }
    raise ArgumentError, "duplicate agent id in system: #{definition.id}"
  end

  @definitions << definition
  definition
end

#api_base(value) ⇒ Object



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

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

#api_key(value) ⇒ Object



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

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

#build(backend: nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


54
55
56
57
58
59
60
# File 'lib/insika/dsl.rb', line 54

def build(backend: nil, &block)
  instance_eval(&block) if block
  raise ArgumentError, "Insika.system needs at least one agent" if @definitions.empty?

  System.new(definitions: @definitions, workflows: @workflows,
             mcp_instances: @mcp_instances, runtime: @runtime, backend: backend)
end

#mcp(name, transport: nil, command: nil, args: nil, url: nil, headers: nil, env: nil, description: nil, enabled: true) ⇒ Object

Declares an MCP server instance: global to the graph, not any one agent — gated per agent through tools_allow_groups on the group mcp:<name>, same as any other tool group. Insika::DSL::Runtime upserts it into the McpStore at boot; the MOTOR-VS-FORJA rule applies — code is the TEMPLATE (transport/command/args/url/description always follow the DSL), but an operator's own enabled/env/headers edit via Studio/CLI/API, once the instance exists, is never clobbered back. mcp "tavily", transport: :http, url: "https://mcp.tavily.com/mcp&quot;, headers: { "Authorization" => "Bearer #ENV["TAVILY_KEY"]" } mcp "filesystem", transport: :stdio, command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]

A SYSTEM-level declaration (as opposed to inside one member agent { } block) has no single agent's config to auto-grant — it does NOT by itself give any agent access. Declare the mcp inside the specific agent { } block that needs it instead, where Builder#mcp auto-adds "mcp:" to THAT agent's tools_allow_groups (below).

Raises:

  • (ArgumentError)


124
125
126
127
128
129
130
131
132
133
# File 'lib/insika/dsl.rb', line 124

def mcp(name, transport: nil, command: nil, args: nil, url: nil,
        headers: nil, env: nil, description: nil, enabled: true)
  n = name.to_s
  raise ArgumentError, "duplicate mcp instance in system: #{n}" if @mcp_instances.any? { |m| m[:name] == n }

  @mcp_instances << { name: n, transport: transport&.to_s, command: command, args: args,
                       url: url, headers: headers, env: env, description: description,
                       enabled: enabled }
  n
end

#provider(name) ⇒ Object

System-wide runtime knobs (NOT part of any pack): they configure the LLM clients for every agent. A per-agent provider still wins for that agent; this is the default and the place to put a shared key.



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

def provider(name) = @runtime[:provider] = name.to_s

#workflow(name, description: nil, input: nil, output: nil, &block) ⇒ Object

Declares a WORKFLOW: deterministic Ruby orchestrating agent turns, for the shapes a single tool-loop should not decide on its own — chaining, routing, evaluate-and-retry. It is registered in the same WorkflowRegistry a deployment uses, so it gets a durable run (the run id IS a Task), :workflow_started/:workflow_completed on the event stream, and — when served — GET /v1/workflows + POST /v1/workflows/:name.

workflow "draft", input: { type: "object", required: ["topic"], … } do |input, ctx|
draft = ctx.ask("writer", "Write about #{input['topic']}")
ctx.ask("editor", "Tighten this:\n#{draft}")
end

input:/output: take a JSON Schema Hash (validated by the engine's zero-dep validator) or any dry-schema-compatible #call-able. A bad input is refused synchronously, with NO run created.

Raises:

  • (ArgumentError)


89
90
91
92
93
94
95
96
97
98
# File 'lib/insika/dsl.rb', line 89

def workflow(name, description: nil, input: nil, output: nil, &block)
  raise ArgumentError, "workflow '#{name}' needs a block" if block.nil?

  name = name.to_s
  raise ArgumentError, "duplicate workflow in system: #{name}" if @workflows.any? { |w| w[:name] == name }

  @workflows << { name: name, description: description,
                  input_schema: input, output_schema: output, block: block }
  name
end