Class: Insika::DSL::System

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

Overview

The result of Insika.system { … }: N agents sharing ONE runtime graph.

It is the multi-agent counterpart of Definition, and deliberately the same shape — reply for a turn, serve for the server, to_packs for the data. Nothing here is a new engine path: every agent is still an ordinary Pack imported by the standard PackImporter, so a system of three agents is indistinguishable from three hand-written packs provisioned into the same deployment.

Why it exists: delegation (subagents), fan-out/fan-in and routing all require the child agents to be resolvable in the SAME ProfileSource. A Definition owns exactly one pack, so those patterns had no home in the DSL.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(definitions:, workflows: [], mcp_instances: [], runtime: {}, backend: nil) ⇒ System

backend: the store this system's graph owns. nil = the historic path (INSIKA_DB, or memory when unset). Set by Insika.embed.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/insika/dsl/system.rb', line 22

def initialize(definitions:, workflows: [], mcp_instances: [], runtime: {}, backend: nil)
  @definitions = definitions.freeze
  @workflows = workflows.freeze
  # MCP instances are global to the graph (one McpStore, not per-agent),
  # so a system-level `mcp` declaration and one nested inside a member
  # `agent { … }` block land in the same set. A name declared both places
  # is NOT an error here (unlike within one collection) — the later one
  # (system-level, applied last) simply wins; Insika::DSL::Runtime upserts
  # this combined list once at boot.
  @mcp_instances = (definitions.flat_map(&:mcp_instances) + mcp_instances)
                    .each_with_object({}) { |m, acc| acc[m[:name]] = m }.values.freeze
  @backend = backend
  # Agent-level runtime knobs merged in declaration order, then the
  # system-level ones on top (an explicit `provider`/`api_key` in the
  # system block is the shared default and wins the tie).
  @runtime_options = definitions.map(&:runtime_options)
                                .reduce({}) { |acc, opts| acc.merge(opts) }
                                .merge(runtime)
                                .freeze
end

Instance Attribute Details

#definitionsObject (readonly)

Returns the value of attribute definitions.



18
19
20
# File 'lib/insika/dsl/system.rb', line 18

def definitions
  @definitions
end

#mcp_instancesObject (readonly)

Returns the value of attribute mcp_instances.



18
19
20
# File 'lib/insika/dsl/system.rb', line 18

def mcp_instances
  @mcp_instances
end

#runtime_optionsObject (readonly)

Returns the value of attribute runtime_options.



18
19
20
# File 'lib/insika/dsl/system.rb', line 18

def runtime_options
  @runtime_options
end

#workflowsObject (readonly)

Returns the value of attribute workflows.



18
19
20
# File 'lib/insika/dsl/system.rb', line 18

def workflows
  @workflows
end

Instance Method Details

#find(agent_id) ⇒ Object



58
59
60
61
# File 'lib/insika/dsl/system.rb', line 58

def find(agent_id)
  definitions.find { |d| d.id == agent_id.to_s } ||
    (raise Insika::NotFoundError, "agent '#{agent_id}' is not in this system (have: #{ids.join(', ')})")
end

#idObject

The primary agent: the first declared. Only used where a single id is structurally required (the default model seed, the banner) — never to guess the target of a turn, which is always explicit.



48
# File 'lib/insika/dsl/system.rb', line 48

def id = definitions.first.id

#idsObject



43
# File 'lib/insika/dsl/system.rb', line 43

def ids = definitions.map(&:id)

#packObject

Runtime reads it for the provider/default-model seed.



56
# File 'lib/insika/dsl/system.rb', line 56

def pack = definitions.first.to_pack

#packsObject



53
# File 'lib/insika/dsl/system.rb', line 53

def packs = to_packs

#profile(agent_id) ⇒ Object

The AgentProfile the engine runs for one agent, read back from the store.



64
# File 'lib/insika/dsl/system.rb', line 64

def profile(agent_id) = runtime.profile(find(agent_id).id)

#reply(agent_id, message, session: nil, timeout: nil) ⇒ Object Also known as: chat

One turn against ONE agent of the system, in-process. The agent is always explicit: with several agents in the graph, inferring the target would be a guess, and a wrong guess is a silently wrong conversation.



69
70
71
# File 'lib/insika/dsl/system.rb', line 69

def reply(agent_id, message, session: nil, timeout: nil)
  runtime.chat(message, agent: find(agent_id).id, session_id: session, timeout: timeout)
end

#run(workflow_name, input: {}, agent: nil, timeout: nil) ⇒ Object

Runs a declared workflow and returns its output. agent: is the profile the run executes under (policy, limits, context) — it defaults to the primary agent, since a workflow's own steps pick their agents explicitly. A bad input raises before any run is created (schema is enforced at the edge, exactly as it is over HTTP).



79
80
81
82
# File 'lib/insika/dsl/system.rb', line 79

def run(workflow_name, input: {}, agent: nil, timeout: nil)
  runtime.run_workflow(workflow_name.to_s, input: input,
                                           agent: (agent || id).to_s, timeout: timeout)
end

#runtimeObject

The live runtime graph with every pack imported. Memoized.



93
94
95
96
97
98
# File 'lib/insika/dsl/system.rb', line 93

def runtime
  @runtime ||= begin
    require_relative "runtime"
    Runtime.new(self, backend: @backend)
  end
end

#serve(port: 9292, host: "localhost", token: nil, **opts) ⇒ Object

Boot the control UI (/studio) + the drop-in API (/v1) with EVERY agent of the system served — each agent's id is a model on /v1/responses. With workflows declared, GET /v1/workflows + POST /v1/workflows/:name are exposed too.



88
89
90
# File 'lib/insika/dsl/system.rb', line 88

def serve(port: 9292, host: "localhost", token: nil, **opts)
  runtime.serve(port: port, host: host, token: token, **opts)
end

#to_packsObject

The portable artifacts — one Pack per agent. Hand them to any PackImporter and you get this same system.



52
# File 'lib/insika/dsl/system.rb', line 52

def to_packs = definitions.map(&:to_pack)