Class: Insika::Commands::SetSkillAgents

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

Overview

Control command: enables/disables a skill on N agents at once, adjusting each one's profile.skills allowlist — and, with eager_ids, which of them keep the body in the prompt on EVERY turn (profile.skills_eager). Takes effect on the next dispatch (hot via ProfileSource). -> { name, enabled_for, eager_for, skipped_all, skipped_eager_all }.

Both are per-agent decisions about the same skill, which is why they are one command: eagerness lives on the agent precisely BECAUSE the skill is shared, so the screen that says "which agents can load this" is the screen that says "and which of them always have it".

Allowlist semantics (AgentProfile): nil = ALL, [] = none, [names] = subset. Important and deliberate consequence:

  • enabling on an agent with skills=nil: no-op (already has all).
  • DISABLING on an agent with skills=nil: NOT done here — removing one from "all" would require enumerating the catalog and materializing an explicit allowlist (destructive/surprising). These agents are left intact and go into skipped_all. To restrict, use an explicit :set_agent_tools/allowlist first.

skills_eager is NOT allowlist semantics (nil = NONE, see SkillCatalog#eager_for), so the two fields are adjusted by different rules: nil there is an empty set that can simply be added to, and only the BLANKET true has the same "cannot remove one name from all" problem (-> skipped_eager_all).

Instance Method Summary collapse

Constructor Details

#initialize(profile_source:, event_stream:) ⇒ SetSkillAgents

Returns a new instance of SetSkillAgents.



32
33
34
35
# File 'lib/insika/commands/set_skill_agents.rb', line 32

def initialize(profile_source:, event_stream:)
  @profile_source = profile_source
  @event_stream = event_stream
end

Instance Method Details

#call(command) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/insika/commands/set_skill_agents.rb', line 37

def call(command)
  p = AgentPayload.symbolize(command.payload)
  name = AgentPayload.presence(p[:name])
  raise Insika::ValidationError, "name is required" if name.nil?
  raise Insika::ValidationError, "agent_ids must be a list" unless p[:agent_ids].nil? || p[:agent_ids].is_a?(Array)
  raise Insika::ValidationError, "eager_ids must be a list" unless p[:eager_ids].nil? || p[:eager_ids].is_a?(Array)

  wanted = Array(p[:agent_ids]).map(&:to_s)
  # eager_ids ABSENT means "this form does not manage eagerness" — leave every
  # profile's setting alone. An empty ARRAY means "none of them", which is a real
  # instruction and must be applied. `nil` and `[]` are not the same request.
  eager_wanted = p[:eager_ids].nil? ? nil : Array(p[:eager_ids]).map(&:to_s)
  result = { enabled_for: [], eager_for: [], skipped_all: [], skipped_eager_all: [] }

  @profile_source.all.each { |profile| apply(profile, name, wanted, eager_wanted, result) }

  @event_stream.emit(Insika::Event.new(
                       type: :skill_agents_set,
                       data: { name: name, agent_ids: wanted, eager_ids: eager_wanted,
                               skipped_all: result[:skipped_all] }.compact,
                       meta: { at: Time.now.utc.iso8601 }
                     ))
  { name: name }.merge(result)
end