-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcoding_agent.rb
More file actions
101 lines (89 loc) · 3.38 KB
/
Copy pathcoding_agent.rb
File metadata and controls
101 lines (89 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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
# frozen_string_literal: true
# A coding agent that edits a real project directory with the built-in tools.
#
# OPENAI_API_KEY=sk-... ruby examples/coding_agent.rb
# # or, on a host with no local Ruby:
# script/rb ruby examples/coding_agent.rb
#
# This is pi's headline surface: an agent that reads, searches, and edits files,
# then runs a command to check its work. Truffle ships the same built-in tools
# pi does, each built by a factory bound to a working directory:
#
# Truffle::Tools.ls / .find / .grep / .read read the project
# Truffle::Tools.write / .edit change it
# Truffle::Tools.bash run commands in it
#
# The example seeds a throwaway project in a temp directory, points every tool
# at it, and asks the model to add a method and prove it works. Nothing here is
# specific to this task: bind the same tools to your own repo and the agent can
# work in it. The directory is temporary so a demo run cannot touch your files.
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "tmpdir"
require "truffle"
# Seed a tiny project so the run has something real to read and edit.
def seed_project(dir)
File.write(File.join(dir, "greeter.rb"), <<~RUBY)
# A greeter with one method. The agent is asked to add a second.
module Greeter
module_function
def hello(name)
"Hello, \#{name}."
end
end
RUBY
end
# Build an agent whose built-in tools are all bound to the project directory.
# Paths the model passes resolve inside it, and bash runs with it as the working
# directory, so a run cannot touch anything outside the project.
def build_agent(project)
tools = [
Truffle::Tools.ls(cwd: project),
Truffle::Tools.find(cwd: project),
Truffle::Tools.grep(cwd: project),
Truffle::Tools.read(cwd: project),
Truffle::Tools.write(cwd: project),
Truffle::Tools.edit(cwd: project),
Truffle::Tools.bash(cwd: project)
]
Truffle.agent(
provider: :openai,
model: ENV.fetch("TRUFFLE_MODEL", "gpt-5.4-mini"),
system_prompt: <<~PROMPT,
You are a coding agent working in a Ruby project.
Read the relevant files before you change anything, make the smallest
edit that satisfies the request, and run the code to prove it works
before you answer. Keep your final reply short.
PROMPT
tools: tools,
max_turns: 12
)
end
# Print each tool call and a short preview of its result, so the run is legible.
def trace(agent)
agent.on(:tool_call) do |event|
args = event[:call].arguments.map { |key, value| "#{key}=#{value.inspect}" }.join(", ")
puts " -> #{event[:call].name}(#{args})"
end
agent.on(:tool_result) do |event|
# Tool output can be long (a file read, a command's stdout); show a preview.
preview = event[:result].to_s.lines.first(4).join.rstrip
preview += "\n ..." if event[:result].to_s.lines.length > 4
puts " <- #{event[:call].name}:\n #{preview.gsub("\n", "\n ")}"
end
end
def task_from_argv
task = ARGV.join(" ")
return task unless task.empty?
"Add a `goodbye(name)` method to Greeter that returns " \
"\"Goodbye, <name>.\", then run greeter.rb to show both methods work."
end
Dir.mktmpdir("truffle-coding-agent") do |project|
seed_project(project)
agent = build_agent(project)
trace(agent)
task = task_from_argv
puts "Project: #{project}"
puts "Task: #{task}"
puts "-" * 72
puts agent.run(task)
end