agentf 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/agentf +8 -0
- data/lib/agentf/agent_policy.rb +54 -0
- data/lib/agentf/agents/architect.rb +67 -0
- data/lib/agentf/agents/base.rb +53 -0
- data/lib/agentf/agents/debugger.rb +75 -0
- data/lib/agentf/agents/designer.rb +69 -0
- data/lib/agentf/agents/documenter.rb +58 -0
- data/lib/agentf/agents/explorer.rb +65 -0
- data/lib/agentf/agents/reviewer.rb +64 -0
- data/lib/agentf/agents/security.rb +84 -0
- data/lib/agentf/agents/specialist.rb +68 -0
- data/lib/agentf/agents/tester.rb +79 -0
- data/lib/agentf/agents.rb +19 -0
- data/lib/agentf/cli/architecture.rb +83 -0
- data/lib/agentf/cli/arg_parser.rb +50 -0
- data/lib/agentf/cli/code.rb +165 -0
- data/lib/agentf/cli/install.rb +112 -0
- data/lib/agentf/cli/memory.rb +393 -0
- data/lib/agentf/cli/metrics.rb +103 -0
- data/lib/agentf/cli/router.rb +111 -0
- data/lib/agentf/cli/update.rb +204 -0
- data/lib/agentf/commands/architecture.rb +183 -0
- data/lib/agentf/commands/debugger.rb +238 -0
- data/lib/agentf/commands/designer.rb +179 -0
- data/lib/agentf/commands/explorer.rb +208 -0
- data/lib/agentf/commands/memory_reviewer.rb +186 -0
- data/lib/agentf/commands/metrics.rb +272 -0
- data/lib/agentf/commands/security_scanner.rb +98 -0
- data/lib/agentf/commands/tester.rb +232 -0
- data/lib/agentf/commands.rb +17 -0
- data/lib/agentf/context_builder.rb +35 -0
- data/lib/agentf/installer.rb +580 -0
- data/lib/agentf/mcp/server.rb +310 -0
- data/lib/agentf/memory.rb +530 -0
- data/lib/agentf/packs.rb +74 -0
- data/lib/agentf/service/providers.rb +158 -0
- data/lib/agentf/tools/component_spec.rb +28 -0
- data/lib/agentf/tools/error_analysis.rb +19 -0
- data/lib/agentf/tools/file_match.rb +21 -0
- data/lib/agentf/tools/test_template.rb +17 -0
- data/lib/agentf/tools.rb +12 -0
- data/lib/agentf/version.rb +5 -0
- data/lib/agentf/workflow_contract.rb +158 -0
- data/lib/agentf/workflow_engine.rb +424 -0
- data/lib/agentf.rb +87 -0
- metadata +164 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: dc86ebb30ccffc8b338dd6e728947f87886c302046e81c01e10d739aac78095f
|
|
4
|
+
data.tar.gz: dda17a3643259253dac2b299e8b45c4f016f4e01f381e6b81956f54633eebcd3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ff8dd589ee31f74de1a35b0fc8122292b7b78747eb3d45338cc4f9fdcff4f8956f69ad0935aa0cc9b3d7bc3395b607b98d5143e1563c786f83eae44902153bb3
|
|
7
|
+
data.tar.gz: 4334dabaf36eabfdfbfeac1b7ff4bda60f1f2b17361df549ef641a85479c2b80b82806a5c18ffacfcba1ee0f7d618c13d55e0ffef8d7e66d0164aade58298a86
|
data/bin/agentf
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
class AgentPolicy
|
|
5
|
+
REQUIRED_KEYS = %w[always ask_first never].freeze
|
|
6
|
+
|
|
7
|
+
def validate(agent_name:, boundaries:, context: {}, result: nil)
|
|
8
|
+
errors = []
|
|
9
|
+
boundaries = normalize(boundaries)
|
|
10
|
+
|
|
11
|
+
required_inputs = Array(boundaries["required_inputs"])
|
|
12
|
+
missing_inputs = required_inputs.reject { |key| context.key?(key) }
|
|
13
|
+
unless missing_inputs.empty?
|
|
14
|
+
errors << violation(
|
|
15
|
+
code: "missing_required_inputs",
|
|
16
|
+
severity: "error",
|
|
17
|
+
message: "#{agent_name} missing required inputs: #{missing_inputs.join(', ')}",
|
|
18
|
+
agent: agent_name
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if result.is_a?(Hash) && result["error"]
|
|
23
|
+
errors << violation(
|
|
24
|
+
code: "agent_result_error",
|
|
25
|
+
severity: "warn",
|
|
26
|
+
message: "#{agent_name} returned an error: #{result['error']}",
|
|
27
|
+
agent: agent_name
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
errors
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def normalize(boundaries)
|
|
35
|
+
payload = (boundaries || {}).transform_keys(&:to_s)
|
|
36
|
+
REQUIRED_KEYS.each { |key| payload[key] = Array(payload[key]) }
|
|
37
|
+
payload["required_inputs"] = Array(payload["required_inputs"])
|
|
38
|
+
payload["required_outputs"] = Array(payload["required_outputs"])
|
|
39
|
+
payload
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def violation(code:, severity:, message:, agent:)
|
|
45
|
+
{
|
|
46
|
+
"code" => code,
|
|
47
|
+
"severity" => severity,
|
|
48
|
+
"message" => message,
|
|
49
|
+
"agent" => agent,
|
|
50
|
+
"type" => "agent_policy"
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Agentf
|
|
6
|
+
module Agents
|
|
7
|
+
# Architect Agent - Strategy, task decomposition
|
|
8
|
+
class Architect < Base
|
|
9
|
+
DESCRIPTION = "Strategy, task decomposition, and memory retrieval."
|
|
10
|
+
COMMANDS = %w[glob read_file memory].freeze
|
|
11
|
+
MEMORY_CONCEPTS = {
|
|
12
|
+
"reads" => ["get_recent_memories", "get_pitfalls"],
|
|
13
|
+
"writes" => [],
|
|
14
|
+
"policy" => "Retrieve relevant memories before planning; do not duplicate runtime memory into static markdown."
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def self.description
|
|
18
|
+
DESCRIPTION
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.commands
|
|
22
|
+
COMMANDS
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.memory_concepts
|
|
26
|
+
MEMORY_CONCEPTS
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.policy_boundaries
|
|
30
|
+
{
|
|
31
|
+
"always" => ["Capture constraints before decomposition", "Use recent memories and pitfalls in planning"],
|
|
32
|
+
"ask_first" => ["Changing architectural style from project defaults"],
|
|
33
|
+
"never" => ["Skip task decomposition for non-trivial workflows"],
|
|
34
|
+
"required_inputs" => [],
|
|
35
|
+
"required_outputs" => ["subtasks", "context"]
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def plan_task(task)
|
|
40
|
+
log "Planning: #{task}"
|
|
41
|
+
|
|
42
|
+
# Retrieve relevant memories before planning
|
|
43
|
+
recent = memory.get_recent_memories(limit: 5)
|
|
44
|
+
pitfalls = memory.get_pitfalls(limit: 3)
|
|
45
|
+
|
|
46
|
+
context = {
|
|
47
|
+
"task" => task,
|
|
48
|
+
"relevant_memories" => recent,
|
|
49
|
+
"pitfalls_to_avoid" => pitfalls
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Decompose into subtasks
|
|
53
|
+
subtasks = [
|
|
54
|
+
{ "id" => 1, "description" => "Research and gather requirements" },
|
|
55
|
+
{ "id" => 2, "description" => "Implement the solution" },
|
|
56
|
+
{ "id" => 3, "description" => "Review and test" }
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
log "Found #{recent.size} relevant memories"
|
|
60
|
+
log "Avoiding #{pitfalls.size} known pitfalls"
|
|
61
|
+
log "Created #{subtasks.size} subtasks"
|
|
62
|
+
|
|
63
|
+
{ "subtasks" => subtasks, "context" => context }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module Agents
|
|
5
|
+
# Base agent class
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :memory, :name
|
|
8
|
+
|
|
9
|
+
def self.typed_name
|
|
10
|
+
name.split("::").last.upcase
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.description
|
|
14
|
+
"Agent for #{typed_name.downcase}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.commands
|
|
18
|
+
[]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.memory_concepts
|
|
22
|
+
{
|
|
23
|
+
"reads" => ["RedisMemory#get_recent_memories", "RedisMemory#get_pitfalls"],
|
|
24
|
+
"writes" => ["RedisMemory#store_lesson", "RedisMemory#store_success", "RedisMemory#store_pitfall"],
|
|
25
|
+
"policy" => "Memory is runtime state in Redis and should not be embedded as raw data in manifest markdown."
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.prompt
|
|
30
|
+
"You are the #{typed_name} agent."
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.policy_boundaries
|
|
34
|
+
{
|
|
35
|
+
"always" => [],
|
|
36
|
+
"ask_first" => [],
|
|
37
|
+
"never" => [],
|
|
38
|
+
"required_inputs" => [],
|
|
39
|
+
"required_outputs" => []
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize(memory)
|
|
44
|
+
@memory = memory
|
|
45
|
+
@name = self.class.typed_name
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def log(message)
|
|
49
|
+
puts "\n[#{@name}] #{message}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../commands"
|
|
5
|
+
|
|
6
|
+
module Agentf
|
|
7
|
+
module Agents
|
|
8
|
+
# Debugger Agent - Error analysis and diagnosis
|
|
9
|
+
class Debugger < Base
|
|
10
|
+
DESCRIPTION = "Error analysis, diagnosis, and remediation guidance."
|
|
11
|
+
COMMANDS = %w[parse_error analyze_logs suggest_fix].freeze
|
|
12
|
+
MEMORY_CONCEPTS = {
|
|
13
|
+
"reads" => [],
|
|
14
|
+
"writes" => ["store_episode"],
|
|
15
|
+
"policy" => "Persist debugging lessons with root cause and proposed fixes."
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def self.description
|
|
19
|
+
DESCRIPTION
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.commands
|
|
23
|
+
COMMANDS
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.memory_concepts
|
|
27
|
+
MEMORY_CONCEPTS
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.policy_boundaries
|
|
31
|
+
{
|
|
32
|
+
"always" => ["Return analysis with root causes and suggested fix", "Persist debugging lesson"],
|
|
33
|
+
"ask_first" => ["Applying speculative fixes without reproducible error"],
|
|
34
|
+
"never" => ["Discard stack trace context when available"],
|
|
35
|
+
"required_inputs" => [],
|
|
36
|
+
"required_outputs" => ["analysis"]
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize(memory, commands: nil)
|
|
41
|
+
super(memory)
|
|
42
|
+
@commands = commands || Agentf::Commands::Debugger.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def diagnose(error, context: nil)
|
|
46
|
+
log "Diagnosing error"
|
|
47
|
+
log " Error: #{error[0..100]}..."
|
|
48
|
+
|
|
49
|
+
analysis = @commands.parse_error(error)
|
|
50
|
+
|
|
51
|
+
memory.store_episode(
|
|
52
|
+
type: "lesson",
|
|
53
|
+
title: "Debugged: #{error[0..50]}...",
|
|
54
|
+
description: "Root cause: #{analysis.possible_causes.first}. Fix: #{analysis.suggested_fix}",
|
|
55
|
+
context: context.to_s,
|
|
56
|
+
tags: ["debugging", "error", "fix"],
|
|
57
|
+
agent: name
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
log "Root cause: #{analysis.possible_causes.first}"
|
|
61
|
+
log "Suggested fix: #{analysis.suggested_fix}"
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
"error" => error,
|
|
65
|
+
"analysis" => {
|
|
66
|
+
"error_type" => analysis.error_type,
|
|
67
|
+
"possible_causes" => analysis.possible_causes,
|
|
68
|
+
"suggested_fix" => analysis.suggested_fix,
|
|
69
|
+
"stack_trace" => analysis.stack_trace
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../commands"
|
|
5
|
+
|
|
6
|
+
module Agentf
|
|
7
|
+
module Agents
|
|
8
|
+
# Designer Agent - Design specs to implementation
|
|
9
|
+
class Designer < Base
|
|
10
|
+
DESCRIPTION = "UI/UX implementation from design specs."
|
|
11
|
+
COMMANDS = %w[generate_component validate_design_system].freeze
|
|
12
|
+
MEMORY_CONCEPTS = {
|
|
13
|
+
"reads" => [],
|
|
14
|
+
"writes" => ["store_success"],
|
|
15
|
+
"policy" => "Capture successful design implementation patterns."
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def self.description
|
|
19
|
+
DESCRIPTION
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.commands
|
|
23
|
+
COMMANDS
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.memory_concepts
|
|
27
|
+
MEMORY_CONCEPTS
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.policy_boundaries
|
|
31
|
+
{
|
|
32
|
+
"always" => ["Return generated component details", "Persist successful implementation pattern"],
|
|
33
|
+
"ask_first" => ["Changing primary UI framework"],
|
|
34
|
+
"never" => ["Return empty generated code for successful design task"],
|
|
35
|
+
"required_inputs" => [],
|
|
36
|
+
"required_outputs" => ["component", "generated_code"]
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize(memory, commands: nil)
|
|
41
|
+
super(memory)
|
|
42
|
+
@commands = commands || Agentf::Commands::Designer.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def implement_design(design_spec, framework: "react")
|
|
46
|
+
log "Implementing design: #{design_spec}"
|
|
47
|
+
|
|
48
|
+
spec = @commands.generate_component("GeneratedComponent", design_spec)
|
|
49
|
+
|
|
50
|
+
memory.store_success(
|
|
51
|
+
title: "Implemented design: #{design_spec}",
|
|
52
|
+
description: "Created #{spec.name} in #{spec.framework}",
|
|
53
|
+
context: "Framework: #{framework}",
|
|
54
|
+
tags: ["design", "ui", framework],
|
|
55
|
+
agent: name
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
log "Created component: #{spec.name}"
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
"design_spec" => design_spec,
|
|
62
|
+
"component" => spec.name,
|
|
63
|
+
"framework" => framework,
|
|
64
|
+
"generated_code" => spec.code
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Agentf
|
|
6
|
+
module Agents
|
|
7
|
+
# Documenter Agent - Sync Redis memory with local Markdown docs
|
|
8
|
+
class Documenter < Base
|
|
9
|
+
DESCRIPTION = "Syncs Redis memory with local Markdown summaries."
|
|
10
|
+
COMMANDS = %w[read_file write_file memory].freeze
|
|
11
|
+
MEMORY_CONCEPTS = {
|
|
12
|
+
"reads" => ["get_recent_memories"],
|
|
13
|
+
"writes" => [],
|
|
14
|
+
"policy" => "Summarize memory trends into docs without storing raw secrets."
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def self.description
|
|
18
|
+
DESCRIPTION
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.commands
|
|
22
|
+
COMMANDS
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.memory_concepts
|
|
26
|
+
MEMORY_CONCEPTS
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.policy_boundaries
|
|
30
|
+
{
|
|
31
|
+
"always" => ["Summarize recent successes and pitfalls"],
|
|
32
|
+
"ask_first" => ["Publishing docs to external destinations"],
|
|
33
|
+
"never" => ["Leak sensitive context in summaries"],
|
|
34
|
+
"required_inputs" => [],
|
|
35
|
+
"required_outputs" => ["successes", "pitfalls", "total_memories"]
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def sync_docs(project_name)
|
|
40
|
+
log "Syncing documentation"
|
|
41
|
+
|
|
42
|
+
memories = memory.get_recent_memories(limit: 20)
|
|
43
|
+
|
|
44
|
+
successes = memories.select { |m| m["type"] == "success" }
|
|
45
|
+
pitfalls = memories.select { |m| m["type"] == "pitfall" }
|
|
46
|
+
|
|
47
|
+
log "Found #{successes.size} successes"
|
|
48
|
+
log "Found #{pitfalls.size} pitfalls"
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
"successes" => successes,
|
|
52
|
+
"pitfalls" => pitfalls,
|
|
53
|
+
"total_memories" => memories.size
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../commands"
|
|
5
|
+
|
|
6
|
+
module Agentf
|
|
7
|
+
module Agents
|
|
8
|
+
# Explorer Agent - Codebase exploration
|
|
9
|
+
class Explorer < Base
|
|
10
|
+
DESCRIPTION = "Rapid codebase exploration and context gathering."
|
|
11
|
+
COMMANDS = %w[glob grep read_file].freeze
|
|
12
|
+
MEMORY_CONCEPTS = {
|
|
13
|
+
"reads" => [],
|
|
14
|
+
"writes" => ["store_episode"],
|
|
15
|
+
"policy" => "Store exploration breadcrumbs as episodic memories."
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def self.description
|
|
19
|
+
DESCRIPTION
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.commands
|
|
23
|
+
COMMANDS
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.memory_concepts
|
|
27
|
+
MEMORY_CONCEPTS
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.policy_boundaries
|
|
31
|
+
{
|
|
32
|
+
"always" => ["Return concrete file evidence", "Persist exploration breadcrumbs"],
|
|
33
|
+
"ask_first" => ["Scanning outside configured base path"],
|
|
34
|
+
"never" => ["Mutate project files during exploration"],
|
|
35
|
+
"required_inputs" => [],
|
|
36
|
+
"required_outputs" => ["files", "context_gathered"]
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize(memory, commands: nil)
|
|
41
|
+
super(memory)
|
|
42
|
+
@commands = commands || Agentf::Commands::Explorer.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def explore(query, file_pattern: nil)
|
|
46
|
+
log "Exploring: #{query}"
|
|
47
|
+
|
|
48
|
+
files = @commands.glob(query, file_types: nil)
|
|
49
|
+
|
|
50
|
+
memory.store_episode(
|
|
51
|
+
type: "exploration",
|
|
52
|
+
title: "Explored: #{query}",
|
|
53
|
+
description: "Found #{files.size} relevant files",
|
|
54
|
+
context: "Search pattern: #{file_pattern || 'all files'}",
|
|
55
|
+
tags: ["exploration", "context"],
|
|
56
|
+
agent: name
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
log "Found #{files.size} files"
|
|
60
|
+
|
|
61
|
+
{ "query" => query, "files" => files, "context_gathered" => true }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Agentf
|
|
6
|
+
module Agents
|
|
7
|
+
# Reviewer Agent - Quality assurance
|
|
8
|
+
class Reviewer < Base
|
|
9
|
+
DESCRIPTION = "Quality assurance and regression checking against memory."
|
|
10
|
+
COMMANDS = %w[read_file memory].freeze
|
|
11
|
+
MEMORY_CONCEPTS = {
|
|
12
|
+
"reads" => ["get_pitfalls", "get_recent_memories"],
|
|
13
|
+
"writes" => [],
|
|
14
|
+
"policy" => "Validate outputs against known pitfalls before approval."
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def self.description
|
|
18
|
+
DESCRIPTION
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.commands
|
|
22
|
+
COMMANDS
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.memory_concepts
|
|
26
|
+
MEMORY_CONCEPTS
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.policy_boundaries
|
|
30
|
+
{
|
|
31
|
+
"always" => ["Report approval decision", "Highlight known pitfalls in review findings"],
|
|
32
|
+
"ask_first" => ["Approving with unresolved critical security issues"],
|
|
33
|
+
"never" => ["Approve without any review evidence"],
|
|
34
|
+
"required_inputs" => [],
|
|
35
|
+
"required_outputs" => ["approved", "issues"]
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def review(subtask_result)
|
|
40
|
+
log "Reviewing subtask #{subtask_result['subtask_id']}"
|
|
41
|
+
|
|
42
|
+
pitfalls = memory.get_pitfalls(limit: 5)
|
|
43
|
+
memories = memory.get_recent_memories(limit: 5)
|
|
44
|
+
|
|
45
|
+
issues = []
|
|
46
|
+
|
|
47
|
+
pitfalls.each do |pitfall|
|
|
48
|
+
issues << "Warning: Known pitfall - #{pitfall['title']}" if pitfall["type"] == "pitfall"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
approved = issues.empty?
|
|
52
|
+
|
|
53
|
+
if approved
|
|
54
|
+
log "Approved (no issues found)"
|
|
55
|
+
else
|
|
56
|
+
log "Issues found: #{issues.size}"
|
|
57
|
+
issues.each { |issue| log " - #{issue}" }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
{ "approved" => approved, "issues" => issues }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../commands"
|
|
5
|
+
|
|
6
|
+
module Agentf
|
|
7
|
+
module Agents
|
|
8
|
+
# Security Agent - Performs lightweight security assessments during workflows
|
|
9
|
+
class Security < Base
|
|
10
|
+
DESCRIPTION = "Security scanning for secret leaks and prompt injection."
|
|
11
|
+
COMMANDS = %w[scan best_practices].freeze
|
|
12
|
+
MEMORY_CONCEPTS = {
|
|
13
|
+
"reads" => [],
|
|
14
|
+
"writes" => ["store_success", "store_pitfall"],
|
|
15
|
+
"policy" => "Record findings while redacting sensitive values."
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def self.description
|
|
19
|
+
DESCRIPTION
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.commands
|
|
23
|
+
COMMANDS
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.memory_concepts
|
|
27
|
+
MEMORY_CONCEPTS
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.policy_boundaries
|
|
31
|
+
{
|
|
32
|
+
"always" => ["Return issue list and best practices", "Persist outcome as success or pitfall"],
|
|
33
|
+
"ask_first" => ["Allowing known secret patterns in context"],
|
|
34
|
+
"never" => ["Echo raw secrets in output"],
|
|
35
|
+
"required_inputs" => [],
|
|
36
|
+
"required_outputs" => ["issues", "best_practices"]
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize(memory, commands: nil)
|
|
41
|
+
super(memory)
|
|
42
|
+
@commands = commands || Agentf::Commands::SecurityScanner.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def assess(task:, context: {})
|
|
46
|
+
log "Running security assessment"
|
|
47
|
+
|
|
48
|
+
findings = @commands.scan(task: task, context: context)
|
|
49
|
+
summary = summarize_findings(findings)
|
|
50
|
+
|
|
51
|
+
if findings["issues"].empty?
|
|
52
|
+
memory.store_success(
|
|
53
|
+
title: "Security review passed",
|
|
54
|
+
description: summary,
|
|
55
|
+
context: task,
|
|
56
|
+
tags: ["security", "pass"],
|
|
57
|
+
agent: name
|
|
58
|
+
)
|
|
59
|
+
else
|
|
60
|
+
memory.store_pitfall(
|
|
61
|
+
title: "Security findings detected",
|
|
62
|
+
description: summary,
|
|
63
|
+
context: task,
|
|
64
|
+
tags: ["security", "warning"],
|
|
65
|
+
agent: name
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
findings.merge("best_practices" => @commands.best_practices)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def summarize_findings(findings)
|
|
75
|
+
if findings["issues"].empty?
|
|
76
|
+
"No potential secrets or prompt-injection attempts detected."
|
|
77
|
+
else
|
|
78
|
+
issues = findings["issues"].map { |issue| "- #{issue['issue']}: #{issue['detail']}" }
|
|
79
|
+
"Identified #{findings['issues'].size} potential issue(s):\n#{issues.join('\n')}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Agentf
|
|
6
|
+
module Agents
|
|
7
|
+
# Specialist Agent - Code execution
|
|
8
|
+
class Specialist < Base
|
|
9
|
+
DESCRIPTION = "Code execution and lesson-learning persistence."
|
|
10
|
+
COMMANDS = %w[read_file write_file run_command].freeze
|
|
11
|
+
MEMORY_CONCEPTS = {
|
|
12
|
+
"reads" => [],
|
|
13
|
+
"writes" => ["store_success", "store_pitfall"],
|
|
14
|
+
"policy" => "Persist execution outcomes as lessons for downstream agents."
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def self.description
|
|
18
|
+
DESCRIPTION
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.commands
|
|
22
|
+
COMMANDS
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.memory_concepts
|
|
26
|
+
MEMORY_CONCEPTS
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.policy_boundaries
|
|
30
|
+
{
|
|
31
|
+
"always" => ["Persist execution outcome", "Return deterministic success boolean"],
|
|
32
|
+
"ask_first" => ["Applying architecture style changes across unrelated modules"],
|
|
33
|
+
"never" => ["Claim implementation complete without execution result"],
|
|
34
|
+
"required_inputs" => [],
|
|
35
|
+
"required_outputs" => ["subtask_id", "success"]
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def execute(subtask)
|
|
40
|
+
log "Executing: #{subtask['description']}"
|
|
41
|
+
|
|
42
|
+
success = subtask.fetch("success", true)
|
|
43
|
+
|
|
44
|
+
if success
|
|
45
|
+
memory.store_success(
|
|
46
|
+
title: "Completed: #{subtask['description']}",
|
|
47
|
+
description: "Successfully executed subtask #{subtask['id']}",
|
|
48
|
+
context: "Working on #{subtask.fetch('task', 'unknown task')}",
|
|
49
|
+
tags: ["implementation", subtask.fetch("language", "general")],
|
|
50
|
+
agent: name
|
|
51
|
+
)
|
|
52
|
+
log "Stored success memory"
|
|
53
|
+
else
|
|
54
|
+
memory.store_pitfall(
|
|
55
|
+
title: "Failed: #{subtask['description']}",
|
|
56
|
+
description: "Subtask #{subtask['id']} failed",
|
|
57
|
+
context: "Working on #{subtask.fetch('task', 'unknown task')}",
|
|
58
|
+
tags: ["failure", "implementation"],
|
|
59
|
+
agent: name
|
|
60
|
+
)
|
|
61
|
+
log "Stored pitfall memory"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
{ "subtask_id" => subtask["id"], "success" => success, "result" => "Code executed" }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|