Class: Clacky::AgentProfile
- Inherits:
-
Object
- Object
- Clacky::AgentProfile
- Defined in:
- lib/clacky/agent_profile.rb
Overview
Loads and represents an agent profile (system prompt + skill whitelist).
Lookup order for a profile named "coding":
1. ~/.clacky/agents/coding/ (user override, physical dir)
2. extension agent unit with id == "coding" (ext.yml contributes.agents)
Each user profile directory (opt-in override) contains:
- profile.yml — name, description, skills whitelist
- system_prompt.md — agent-specific system prompt content
Global files (shared across all agents) are user-only overrides:
- ~/.clacky/agents/SOUL.md — agent personality/values (else DEFAULT_SOUL)
- ~/.clacky/agents/USER.md — user profile info (else DEFAULT_USER)
The universal behavioural rules (todo manager, tool usage, response style, etc.) live in a bundled resource file at lib/clacky/prompts/base.md.
Constant Summary collapse
- USER_AGENTS_DIR =
File.("~/.clacky/agents").freeze
- BASE_PROMPT_PATH =
File.("../prompts/base.md", __FILE__).freeze
- DEFAULT_SOUL =
<<~MD.freeze You are calm, precise, and helpful. You communicate clearly and concisely. You are honest about uncertainty and ask for clarification when needed. You take initiative but respect the user's preferences and decisions. MD
- DEFAULT_USER =
"(No user profile configured yet. To personalize, create ~/.clacky/agents/USER.md)"
Instance Attribute Summary collapse
-
#description ⇒ Object
readonly
Returns the value of attribute description.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Class Method Summary collapse
-
.all ⇒ Array<Hash>
List all available agent profiles across user + extension layers.
- .load(name) ⇒ AgentProfile
Instance Method Summary collapse
-
#base_prompt ⇒ String
Base prompt shared by all agents (bundled resource).
-
#initialize(name) ⇒ AgentProfile
constructor
A new instance of AgentProfile.
-
#soul ⇒ String
Soul content (user override, else default).
-
#system_prompt ⇒ String
Agent-specific system prompt content.
-
#user_profile ⇒ String
User profile content (user override, else default).
Constructor Details
#initialize(name) ⇒ AgentProfile
Returns a new instance of AgentProfile.
35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/clacky/agent_profile.rb', line 35 def initialize(name) @name = name.to_s result = ExtensionLoader.last_result @ext_unit = result&.agents&.find { |u| u.id == @name } if @ext_unit.nil? result = ExtensionLoader.load_all(force: true) @ext_unit = result&.agents&.find { |u| u.id == @name } end profile_data = load_profile_yml @description = profile_data["description"] || "" @system_prompt_content = load_agent_file("system_prompt.md") end |
Instance Attribute Details
#description ⇒ Object (readonly)
Returns the value of attribute description.
33 34 35 |
# File 'lib/clacky/agent_profile.rb', line 33 def description @description end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
33 34 35 |
# File 'lib/clacky/agent_profile.rb', line 33 def name @name end |
Class Method Details
.all ⇒ Array<Hash>
List all available agent profiles across user + extension layers. Precedence on id collision: user override → extension unit.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/clacky/agent_profile.rb', line 57 def self.all out = {} add = lambda do |id, title, title_zh, description, description_zh, source, order, layer, | next if id.nil? || id.empty? out[id] = { id: id, title: title, title_zh: title_zh, description: description, description_zh: description_zh, source: source, order: order, layer: layer, author: , } end ext_result = ExtensionLoader.last_result || ExtensionLoader.load_all ext_result&.agents&.each do |unit| spec = unit.spec || {} title = spec["title"].to_s title = unit.id if title.empty? add.call( unit.id, title, spec["title_zh"].to_s, spec["description"].to_s, spec["description_zh"].to_s, "extension", spec["order"], unit.layer.to_s, spec["author"].to_s ) end Dir.glob(File.join(USER_AGENTS_DIR, "*")).sort.each do |path| next unless File.directory?(path) id = File.basename(path) next if id.start_with?("_") next unless File.file?(File.join(path, "profile.yml")) = read_profile_yml(File.join(path, "profile.yml")) add.call( id, ["title"] || ["name"] || id, ["title_zh"].to_s, ["description"].to_s, ["description_zh"].to_s, "user", ["order"], "user", ["author"].to_s.empty? ? "You" : ["author"].to_s ) end source_rank = { "user" => 0, "extension" => 1 } out.values.sort_by { |a| [source_rank[a[:source]] || 9, a[:order] || 999, a[:id]] } end |
.load(name) ⇒ AgentProfile
50 51 52 |
# File 'lib/clacky/agent_profile.rb', line 50 def self.load(name) new(name) end |
Instance Method Details
#base_prompt ⇒ String
Returns base prompt shared by all agents (bundled resource).
119 120 121 122 |
# File 'lib/clacky/agent_profile.rb', line 119 def base_prompt return "" unless File.file?(BASE_PROMPT_PATH) File.read(BASE_PROMPT_PATH).strip end |
#soul ⇒ String
Returns soul content (user override, else default).
125 126 127 128 129 130 131 132 |
# File 'lib/clacky/agent_profile.rb', line 125 def soul user_path = File.join(USER_AGENTS_DIR, "SOUL.md") if File.exist?(user_path) && !File.zero?(user_path) File.read(user_path).strip else DEFAULT_SOUL.strip end end |
#system_prompt ⇒ String
Returns agent-specific system prompt content.
114 115 116 |
# File 'lib/clacky/agent_profile.rb', line 114 def system_prompt @system_prompt_content end |
#user_profile ⇒ String
Returns user profile content (user override, else default).
135 136 137 138 139 140 141 142 |
# File 'lib/clacky/agent_profile.rb', line 135 def user_profile user_path = File.join(USER_AGENTS_DIR, "USER.md") if File.exist?(user_path) && !File.zero?(user_path) File.read(user_path).strip else DEFAULT_USER end end |