Class: Insika::SkillStore
- Inherits:
-
Object
- Object
- Insika::SkillStore
- Defined in:
- lib/insika/skill_store.rb
Overview
AUTHORED skills, in two scopes. Holds the complete SKILL.md (frontmatter + body) in the durable Store. The SkillCatalog overlays these skills on top of the on-disk ones (seed), with the Store winning — so editing/creating a skill in the Studio takes effect without a restart (via reload).
SHARED scope (agent: omitted) — one record per skill in the ConfigStore
(scope "skills"), keyed by the skill name:
{ "content" => "<entire SKILL.md>",
"updated_at" => iso8601,
"history" => [ { "content" =>, "at" => }, ... ] }
AGENT scope (agent: given) — one record per AGENT (scope "agent_skills"), the
skills nested under it, exactly the AgentFileStore shape:
{ "skills" => { "<name>" => { "content" =>, "updated_at" =>, "history" => [] } } }
The agent dimension is a SECOND ARGUMENT, never part of the key. A composite
"agent/name" key would put a / inside what the Studio serves as a single path
segment (GET /skills/:name, the editor, the versions list) — the class of route
bug that ships green and 404s in production. Two arguments become two route
segments (/agents/:id/skills/:name) and nothing needs encoding.
Two scopes and not one: the shared records are untouched by the arrival of the agent dimension, so there is no migration and a live deployment keeps serving exactly what it served.
THE STORE POSITION IS THE IDENTITY. Which scope a record sits in — and under which
key — is what decides which skill it is; the frontmatter name: inside an override
stays the bare shared name. See SkillCatalog#find.
Constant Summary collapse
- SCOPE =
"skills"- AGENT_SCOPE =
"agent_skills"- HISTORY_MAX =
20
Instance Method Summary collapse
-
#agents ⇒ Object
-> [String] every agent that has specialized at least one skill.
-
#all(agent: nil) ⇒ Object
-> { name => content } of the scope's authored skills.
-
#delete(name, agent: nil) ⇒ Object
-> bool (did it exist?).
-
#get(name, agent: nil) ⇒ Object
-> String | nil (complete SKILL.md).
-
#initialize(config_store:) ⇒ SkillStore
constructor
A new instance of SkillStore.
-
#names(agent: nil) ⇒ Object
-> [String] names in the scope, lexicographic order.
-
#restore(name, index, agent: nil) ⇒ Object
Restores version
indexas the current content (a new write). -
#versions(name, agent: nil) ⇒ Object
-> [ { "content" =>, "at" => } ] most recent first.
-
#write(name, content, agent: nil, create_only: false) ⇒ Object
Writes (upsert).
Constructor Details
#initialize(config_store:) ⇒ SkillStore
Returns a new instance of SkillStore.
39 40 41 |
# File 'lib/insika/skill_store.rb', line 39 def initialize(config_store:) @cs = config_store end |
Instance Method Details
#agents ⇒ Object
-> [String] every agent that has specialized at least one skill. What the
catalog overlays and doctor sweeps.
60 |
# File 'lib/insika/skill_store.rb', line 60 def agents = @cs.keys(AGENT_SCOPE).sort |
#all(agent: nil) ⇒ Object
-> { name => content } of the scope's authored skills.
54 55 56 |
# File 'lib/insika/skill_store.rb', line 54 def all(agent: nil) names(agent: agent).each_with_object({}) { |n, acc| acc[n] = get(n, agent: agent) } end |
#delete(name, agent: nil) ⇒ Object
-> bool (did it exist?).
74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/insika/skill_store.rb', line 74 def delete(name, agent: nil) key = name.to_s return @cs.delete(SCOPE, key) if agent.nil? wrapper = @cs.get(AGENT_SCOPE, agent.to_s) return false unless wrapper&.dig("skills", key) wrapper["skills"].delete(key) @cs.put(AGENT_SCOPE, agent.to_s, wrapper) true end |
#get(name, agent: nil) ⇒ Object
-> String | nil (complete SKILL.md).
44 45 46 |
# File 'lib/insika/skill_store.rb', line 44 def get(name, agent: nil) record(name, agent)&.fetch("content", nil) end |
#names(agent: nil) ⇒ Object
-> [String] names in the scope, lexicographic order.
49 50 51 |
# File 'lib/insika/skill_store.rb', line 49 def names(agent: nil) agent.nil? ? @cs.keys(SCOPE) : agent_skills(agent).keys.sort end |
#restore(name, index, agent: nil) ⇒ Object
Restores version index as the current content (a new write). -> Hash.
90 91 92 93 94 95 96 97 |
# File 'lib/insika/skill_store.rb', line 90 def restore(name, index, agent: nil) hist = versions(name, agent: agent) i = Integer(index) raise Insika::NotFoundError, "skill '#{name}' not found" unless record(name.to_s, agent) raise Insika::ValidationError, "version #{index} does not exist" if i.negative? || i >= hist.length write(name, hist[i]["content"], agent: agent) end |
#versions(name, agent: nil) ⇒ Object
-> [ { "content" =>, "at" => } ] most recent first.
87 |
# File 'lib/insika/skill_store.rb', line 87 def versions(name, agent: nil) = record(name, agent)&.fetch("history", []) || [] |
#write(name, content, agent: nil, create_only: false) ⇒ Object
Writes (upsert). create_only refuses to overwrite. -> Hash (the stored record).
63 64 65 66 67 68 69 70 71 |
# File 'lib/insika/skill_store.rb', line 63 def write(name, content, agent: nil, create_only: false) key = name.to_s current = record(key, agent) raise Insika::ValidationError, "skill '#{key}' already exists#{" for agent '#{agent}'" if agent}" if create_only && current rec = build_record(content.to_s, current) put(key, rec, agent) rec end |