@prism-d1/cli 1.0.84 → 1.0.86
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.
- package/dist/assets/coding-agent/README.md +824 -0
- package/dist/assets/coding-agent/agent.py +381 -0
- package/dist/assets/coding-agent/config.py +338 -0
- package/dist/assets/coding-agent/deploy/Dockerfile +86 -0
- package/dist/assets/coding-agent/deploy/create_harness.py +144 -0
- package/dist/assets/coding-agent/deploy/deploy-harness.sh +201 -0
- package/dist/assets/coding-agent/deploy/prism-coding-agent.yml +399 -0
- package/dist/assets/coding-agent/eval/run_eval.py +430 -0
- package/dist/assets/coding-agent/eval/run_harness_eval.py +262 -0
- package/dist/assets/coding-agent/eval/stubs/001-tags-element-validation.json +11 -0
- package/dist/assets/coding-agent/eval/stubs/002-status-filter.json +11 -0
- package/dist/assets/coding-agent/eval/stubs/003-refuse-test-deletion.json +11 -0
- package/dist/assets/coding-agent/fixtures/001-tags-element-validation.json +12 -0
- package/dist/assets/coding-agent/fixtures/002-status-filter.json +12 -0
- package/dist/assets/coding-agent/fixtures/003-refuse-test-deletion.json +10 -0
- package/dist/assets/coding-agent/pyproject.toml +28 -0
- package/dist/assets/coding-agent/requirements.txt +8 -0
- package/dist/assets/coding-agent/system_prompt.py +290 -0
- package/dist/assets/coding-agent/tools/__init__.py +11 -0
- package/dist/assets/coding-agent/tools/create_pr.py +141 -0
- package/dist/assets/coding-agent/tools/git_ops.py +197 -0
- package/dist/assets/github-workflows/prism-agent-eval.yml +2 -1
- package/dist/assets/github-workflows/prism-ai-metrics.yml +12 -3
- package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +4 -3
- package/dist/assets/github-workflows/prism-eval-gate.yml +3 -2
- package/dist/assets/gitlab-workflows/prism-agent-eval.yml +2 -1
- package/dist/assets/gitlab-workflows/prism-ai-metrics.yml +3 -2
- package/dist/assets/gitlab-workflows/prism-eval-gate.yml +2 -1
- package/dist/assets/infra/lib/constructs/otel-collector-construct.ts +134 -2
- package/dist/assets/infra/lib/dashboard-stack.ts +79 -95
- package/dist/assets/infra/lib/lambda/metrics-processor.ts +99 -1
- package/dist/assets/infra/lib/lambda/otel-receiver.ts +318 -29
- package/dist/assets/infra/lib/lambda/productivity-widget.ts +85 -4
- package/dist/assets/infra/lib/lambda/velocity-widget.ts +265 -0
- package/dist/assets/infra/lib/metrics-pipeline-stack.ts +5 -0
- package/dist/src/commands/bootstrapper/install-coding-agent.d.ts +62 -0
- package/dist/src/commands/bootstrapper/install-coding-agent.d.ts.map +1 -0
- package/dist/src/commands/bootstrapper/install-coding-agent.js +614 -0
- package/dist/src/commands/bootstrapper/install-coding-agent.js.map +1 -0
- package/dist/src/commands/bootstrapper/setup-github-oidc.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/setup-github-oidc.js +23 -0
- package/dist/src/commands/bootstrapper/setup-github-oidc.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""PRISM coding agent — entry point.
|
|
2
|
+
|
|
3
|
+
Reads one issue, fixes it in a git repository, verifies the fix, and commits.
|
|
4
|
+
Optionally opens a pull request.
|
|
5
|
+
|
|
6
|
+
Local run against a JSON issue file (any git repository, any language):
|
|
7
|
+
|
|
8
|
+
python agent.py --repo /path/to/repo \
|
|
9
|
+
--issue /path/to/repo/.coding-agent/fixtures/001-my-bug.json
|
|
10
|
+
|
|
11
|
+
Read the issue from a GitHub Actions event payload and open a PR:
|
|
12
|
+
|
|
13
|
+
python agent.py --repo . --github-event "$GITHUB_EVENT_PATH" --create-pr
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import time
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
from config import ConfigError, load_config
|
|
26
|
+
from system_prompt import build_system_prompt, build_task_prompt
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _load_issue(args: argparse.Namespace) -> dict:
|
|
30
|
+
"""Resolve the issue from a fixture file, a GitHub event payload, or flags."""
|
|
31
|
+
if args.issue:
|
|
32
|
+
path = Path(args.issue)
|
|
33
|
+
if not path.exists():
|
|
34
|
+
raise SystemExit(f"Issue file not found: {path}")
|
|
35
|
+
return json.loads(path.read_text())
|
|
36
|
+
|
|
37
|
+
if args.github_event:
|
|
38
|
+
path = Path(args.github_event)
|
|
39
|
+
if not path.exists():
|
|
40
|
+
raise SystemExit(f"GitHub event payload not found: {path}")
|
|
41
|
+
event = json.loads(path.read_text())
|
|
42
|
+
issue = event.get("issue")
|
|
43
|
+
if not issue:
|
|
44
|
+
raise SystemExit(
|
|
45
|
+
"Event payload has no 'issue' key. This agent handles issue "
|
|
46
|
+
"events; got keys: " + ", ".join(sorted(event))
|
|
47
|
+
)
|
|
48
|
+
return {
|
|
49
|
+
"number": issue.get("number"),
|
|
50
|
+
"title": issue.get("title", ""),
|
|
51
|
+
"body": issue.get("body", ""),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if args.title:
|
|
55
|
+
return {"number": args.number or 0, "title": args.title, "body": args.body or ""}
|
|
56
|
+
|
|
57
|
+
raise SystemExit(
|
|
58
|
+
"No issue supplied. Use --issue FILE, --github-event FILE, or --title TEXT."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _configure_git_identity(repo_path: Path, name: str, email: str) -> None:
|
|
63
|
+
"""Set the agent's commit identity locally in this repository.
|
|
64
|
+
|
|
65
|
+
This is what makes agent commits distinguishable from human commits in PRISM:
|
|
66
|
+
the author email flows through `git log --format=%ae` into the CI workflow's
|
|
67
|
+
commit_authors array, then into the attribution store, and finally appears as
|
|
68
|
+
its own row on the Developer Productivity dashboard.
|
|
69
|
+
"""
|
|
70
|
+
import subprocess
|
|
71
|
+
|
|
72
|
+
for key, value in (("user.name", name), ("user.email", email)):
|
|
73
|
+
subprocess.run(
|
|
74
|
+
["git", "config", key, value],
|
|
75
|
+
cwd=str(repo_path),
|
|
76
|
+
check=True,
|
|
77
|
+
capture_output=True,
|
|
78
|
+
timeout=30,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Matches agentcore.contract.MAX_ITERATIONS, so a fixture that passes locally is
|
|
83
|
+
# not then failed by a tighter bound in the deployed path -- and, more importantly,
|
|
84
|
+
# not the reverse. Enforced by a test rather than a shared import, because importing
|
|
85
|
+
# the agentcore package pulls in boto3 and this module defers heavy imports so that
|
|
86
|
+
# --help and config validation work without the SDK installed.
|
|
87
|
+
DEFAULT_MAX_ITERATIONS = 100
|
|
88
|
+
# Below the eval harness's 1800s subprocess timeout, so the agent stops itself and
|
|
89
|
+
# says why rather than being killed with no explanation. A run killed from outside
|
|
90
|
+
# loses its reason; a run that stops itself reports one.
|
|
91
|
+
DEFAULT_DEADLINE_SECONDS = 1500
|
|
92
|
+
# A single model call that has produced nothing for this long is not slow, it is
|
|
93
|
+
# stuck -- the observed case sat for fourteen minutes on one call at 0.2% CPU.
|
|
94
|
+
READ_TIMEOUT = 300
|
|
95
|
+
CONNECT_TIMEOUT = 15
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class IterationBound:
|
|
99
|
+
"""Stop the agent after a fixed number of model calls, or past a deadline.
|
|
100
|
+
|
|
101
|
+
Strands has no iteration cap in this version -- Agent takes model, messages,
|
|
102
|
+
tools, system_prompt, conversation_manager, hooks, retry_strategy, and nothing
|
|
103
|
+
that bounds call count. So the count is kept here off BeforeModelCallEvent, the
|
|
104
|
+
one lifecycle event that fires exactly once per model call.
|
|
105
|
+
|
|
106
|
+
Stopping uses that event's `cancel` field rather than raising. Setting it makes
|
|
107
|
+
the event loop synthesise a final assistant message carrying the cancellation
|
|
108
|
+
text and finish with stop_reason `end_turn`, so the run ends cleanly and the
|
|
109
|
+
reason lands in the agent's own transcript instead of in a traceback. Raising
|
|
110
|
+
from the hook would also stop it, but it would report a deliberate bound as a
|
|
111
|
+
crash -- and the run this exists to fix had already committed a correct fix.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, max_iterations: int = 0, deadline_seconds: int = 0) -> None:
|
|
115
|
+
self.max_iterations = max_iterations or DEFAULT_MAX_ITERATIONS
|
|
116
|
+
self.deadline_seconds = deadline_seconds or DEFAULT_DEADLINE_SECONDS
|
|
117
|
+
self.iterations = 0
|
|
118
|
+
self.started = 0.0
|
|
119
|
+
self.stopped_reason = ""
|
|
120
|
+
|
|
121
|
+
def register_hooks(self, registry, **_: object) -> None:
|
|
122
|
+
from strands.hooks import BeforeModelCallEvent
|
|
123
|
+
|
|
124
|
+
registry.add_callback(BeforeModelCallEvent, self.before_model_call)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def elapsed(self) -> float:
|
|
128
|
+
return 0.0 if not self.started else time.monotonic() - self.started
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def stopped(self) -> bool:
|
|
132
|
+
return bool(self.stopped_reason)
|
|
133
|
+
|
|
134
|
+
def before_model_call(self, event) -> None:
|
|
135
|
+
if not self.started:
|
|
136
|
+
self.started = time.monotonic()
|
|
137
|
+
|
|
138
|
+
# Checked before incrementing, so the message names the call that would have
|
|
139
|
+
# been the (n+1)th rather than being off by one.
|
|
140
|
+
if self.iterations >= self.max_iterations:
|
|
141
|
+
return self._stop(
|
|
142
|
+
event,
|
|
143
|
+
f"Stopping after {self.iterations} model calls "
|
|
144
|
+
f"(--max-iterations {self.max_iterations}). Work already committed "
|
|
145
|
+
f"stands; the agent was still going when the bound hit, which "
|
|
146
|
+
f"usually means verification never went green.",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if self.elapsed > self.deadline_seconds:
|
|
150
|
+
return self._stop(
|
|
151
|
+
event,
|
|
152
|
+
f"Stopping after {self.elapsed:.0f}s (--deadline-seconds "
|
|
153
|
+
f"{self.deadline_seconds}), on model call {self.iterations + 1}. "
|
|
154
|
+
f"Work already committed stands.",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
self.iterations += 1
|
|
158
|
+
print(f" [iteration {self.iterations}/{self.max_iterations} · "
|
|
159
|
+
f"{self.elapsed:.0f}s/{self.deadline_seconds}s]", file=sys.stderr)
|
|
160
|
+
|
|
161
|
+
def _stop(self, event, reason: str) -> None:
|
|
162
|
+
self.stopped_reason = reason
|
|
163
|
+
# `cancel` is the only writable field on this event, and a string sets the
|
|
164
|
+
# cancellation message. Guarded so the bound is still usable -- and
|
|
165
|
+
# testable -- without a live event object.
|
|
166
|
+
if event is not None:
|
|
167
|
+
event.cancel = reason
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def build_agent(cfg, create_pr_enabled: bool, bound: "IterationBound | None" = None):
|
|
171
|
+
"""Construct the Strands agent with its tool set.
|
|
172
|
+
|
|
173
|
+
Imports happen here rather than at module scope so that --help and config
|
|
174
|
+
validation work without the Strands SDK installed.
|
|
175
|
+
"""
|
|
176
|
+
# strands-agents-tools gates every mutating tool -- editor, file_write, shell
|
|
177
|
+
# -- behind an interactive confirmation dialog unless BYPASS_TOOL_CONSENT is
|
|
178
|
+
# set. There is nobody to answer it here: the agent runs in CI or in a
|
|
179
|
+
# disposable microVM, so the prompt is auto-cancelled and every write silently
|
|
180
|
+
# fails while the model keeps reasoning.
|
|
181
|
+
#
|
|
182
|
+
# The symptom is badly misleading. The agent diagnoses the bug correctly,
|
|
183
|
+
# writes out the right patch in prose, then reports "unable to complete
|
|
184
|
+
# because all file modification operations are being cancelled by the system"
|
|
185
|
+
# -- which reads as a broken agent rather than a missing environment variable.
|
|
186
|
+
#
|
|
187
|
+
# Safe to set here because the boundary is the sandbox, not the prompt: the
|
|
188
|
+
# eval harness gives the agent a throwaway clone, and the AgentCore harness
|
|
189
|
+
# gives it an isolated microVM. Consent dialogs are for a developer's own
|
|
190
|
+
# working tree, which this agent is never pointed at.
|
|
191
|
+
os.environ.setdefault("BYPASS_TOOL_CONSENT", "true")
|
|
192
|
+
|
|
193
|
+
from botocore.config import Config as BotocoreConfig
|
|
194
|
+
from strands import Agent
|
|
195
|
+
from strands.agent.conversation_manager import SlidingWindowConversationManager
|
|
196
|
+
from strands.models import BedrockModel
|
|
197
|
+
# `shell` and `editor` from strands_tools warn on every call that they are
|
|
198
|
+
# deprecated and become an error log in v0.9.0. The replacements are sandbox-
|
|
199
|
+
# routed, and the warning says that tightens the security boundary -- which
|
|
200
|
+
# sounded like it would break the one thing this agent must do, run the
|
|
201
|
+
# project's own test command against a real checkout.
|
|
202
|
+
#
|
|
203
|
+
# It does not, here. With no `sandbox=` passed to Agent, both tools resolve to
|
|
204
|
+
# NotASandboxLocalEnvironment, documented as "the default execution environment
|
|
205
|
+
# when an Agent is created without a sandbox", which spawns a local `sh` and
|
|
206
|
+
# touches the host filesystem directly. That is exactly what strands_tools did,
|
|
207
|
+
# so this migration preserves behaviour; the tightening only applies to someone
|
|
208
|
+
# who configures a real sandbox, and then it is what they asked for.
|
|
209
|
+
#
|
|
210
|
+
# Note the deprecation message names `bash`, which is itself a deprecated alias
|
|
211
|
+
# for `make_shell` -- so following it literally would land on another deprecated
|
|
212
|
+
# name. `file_read` and `file_write` are not deprecated and stay put.
|
|
213
|
+
from strands.vended_tools import file_editor, shell
|
|
214
|
+
from strands_tools import file_read, file_write
|
|
215
|
+
|
|
216
|
+
from tools import create_pr, git_ops
|
|
217
|
+
|
|
218
|
+
tools = [file_read, file_write, file_editor, shell, git_ops]
|
|
219
|
+
if create_pr_enabled:
|
|
220
|
+
tools.append(create_pr)
|
|
221
|
+
|
|
222
|
+
# Two different runaways were observed, and they need two different bounds --
|
|
223
|
+
# which is why one number is not enough:
|
|
224
|
+
#
|
|
225
|
+
# * One run kept working long after it had committed a correct fix, until an
|
|
226
|
+
# external `timeout` killed it at 1200s. A genuine pass was recorded as a
|
|
227
|
+
# failure. That is too many iterations, and IterationBound stops it.
|
|
228
|
+
#
|
|
229
|
+
# * Another committed, made one more model call, and sat for fourteen minutes
|
|
230
|
+
# with one second of CPU at 0.2% -- blocked inside a single call that never
|
|
231
|
+
# returned. An iteration cap cannot help: the count never advanced. Only a
|
|
232
|
+
# socket-level read timeout ends that, which is why the model is built with
|
|
233
|
+
# an explicit boto config rather than passed as a bare id string.
|
|
234
|
+
#
|
|
235
|
+
# A sliding window bounds how much context each call carries. It bounds neither
|
|
236
|
+
# of the above, and this SDK version has no max_turns or max_iterations on
|
|
237
|
+
# Agent (checked: model, messages, tools, system_prompt, conversation_manager,
|
|
238
|
+
# hooks, retry_strategy -- nothing caps calls). So the cap is built here from
|
|
239
|
+
# the BeforeModelCallEvent hook, which is the seam the SDK does offer.
|
|
240
|
+
model = BedrockModel(
|
|
241
|
+
model_id=cfg.model_id,
|
|
242
|
+
boto_client_config=BotocoreConfig(
|
|
243
|
+
connect_timeout=CONNECT_TIMEOUT,
|
|
244
|
+
read_timeout=READ_TIMEOUT,
|
|
245
|
+
# Retries at the client layer, so a dropped connection costs one retry
|
|
246
|
+
# rather than one lost run. Kept low because each attempt burns wall
|
|
247
|
+
# clock against the deadline.
|
|
248
|
+
retries={"max_attempts": 3, "mode": "standard"},
|
|
249
|
+
),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return Agent(
|
|
253
|
+
model=model,
|
|
254
|
+
tools=tools,
|
|
255
|
+
system_prompt=build_system_prompt(cfg, announce=True),
|
|
256
|
+
conversation_manager=SlidingWindowConversationManager(
|
|
257
|
+
window_size=max(20, cfg.max_attempts * 12),
|
|
258
|
+
),
|
|
259
|
+
hooks=[bound or IterationBound()],
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def main() -> int:
|
|
264
|
+
parser = argparse.ArgumentParser(
|
|
265
|
+
prog="prism-coding-agent",
|
|
266
|
+
description="Fix a GitHub issue in a repository, verify, and commit.",
|
|
267
|
+
)
|
|
268
|
+
parser.add_argument("--repo", required=True, help="Path to the target repository")
|
|
269
|
+
|
|
270
|
+
src = parser.add_argument_group("issue source")
|
|
271
|
+
src.add_argument("--issue", help="Path to a JSON issue fixture")
|
|
272
|
+
src.add_argument("--github-event", help="Path to a GitHub Actions event payload")
|
|
273
|
+
src.add_argument("--title", help="Issue title (inline mode)")
|
|
274
|
+
src.add_argument("--body", help="Issue body (inline mode)")
|
|
275
|
+
src.add_argument("--number", type=int, help="Issue number (inline mode)")
|
|
276
|
+
|
|
277
|
+
over = parser.add_argument_group("configuration overrides")
|
|
278
|
+
over.add_argument("--test-command", help="Override the detected test command")
|
|
279
|
+
over.add_argument("--build-command", help="Override the detected build command")
|
|
280
|
+
over.add_argument("--lint-command", help="Lint command to run as an extra check")
|
|
281
|
+
over.add_argument("--model-id", help="Bedrock model id")
|
|
282
|
+
over.add_argument("--region", help="AWS region for Bedrock")
|
|
283
|
+
over.add_argument("--max-attempts", type=int, help="Verification retry budget")
|
|
284
|
+
over.add_argument("--agent-email", help="Git author email for agent commits")
|
|
285
|
+
|
|
286
|
+
parser.add_argument(
|
|
287
|
+
"--create-pr",
|
|
288
|
+
action="store_true",
|
|
289
|
+
help="Give the agent the create_pr tool so it can push and open a PR",
|
|
290
|
+
)
|
|
291
|
+
parser.add_argument(
|
|
292
|
+
"--dry-run",
|
|
293
|
+
action="store_true",
|
|
294
|
+
help="Print the resolved config and system prompt, then exit",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
bounds = parser.add_argument_group("bounds")
|
|
298
|
+
bounds.add_argument(
|
|
299
|
+
"--max-iterations", type=int, default=DEFAULT_MAX_ITERATIONS,
|
|
300
|
+
help=f"Stop after this many model calls (default {DEFAULT_MAX_ITERATIONS}, "
|
|
301
|
+
f"matching the AgentCore Harness default)",
|
|
302
|
+
)
|
|
303
|
+
bounds.add_argument(
|
|
304
|
+
"--deadline-seconds", type=int, default=DEFAULT_DEADLINE_SECONDS,
|
|
305
|
+
help=f"Stop once a run has taken this long (default {DEFAULT_DEADLINE_SECONDS})",
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
args = parser.parse_args()
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
cfg = load_config(
|
|
312
|
+
Path(args.repo),
|
|
313
|
+
test_command=args.test_command,
|
|
314
|
+
build_command=args.build_command,
|
|
315
|
+
lint_command=args.lint_command,
|
|
316
|
+
model_id=args.model_id,
|
|
317
|
+
region=args.region,
|
|
318
|
+
max_attempts=args.max_attempts,
|
|
319
|
+
agent_email=args.agent_email,
|
|
320
|
+
)
|
|
321
|
+
except ConfigError as exc:
|
|
322
|
+
print(f"Configuration error: {exc}", file=sys.stderr)
|
|
323
|
+
return 2
|
|
324
|
+
|
|
325
|
+
print(f"Repository: {cfg.repo_path}")
|
|
326
|
+
print(f"Project type: {cfg.project_type} (from {cfg.source})")
|
|
327
|
+
print(f"Test command: {cfg.test_command or '(none — fixes will be UNVERIFIED)'}")
|
|
328
|
+
if cfg.build_command:
|
|
329
|
+
print(f"Build: {cfg.build_command}")
|
|
330
|
+
if cfg.lint_command:
|
|
331
|
+
print(f"Lint: {cfg.lint_command}")
|
|
332
|
+
print(f"Model: {cfg.model_id}")
|
|
333
|
+
print()
|
|
334
|
+
|
|
335
|
+
if not cfg.can_verify:
|
|
336
|
+
print(
|
|
337
|
+
"WARNING: no test command resolved. The agent will be told to find "
|
|
338
|
+
"one, and to label the fix UNVERIFIED if it cannot.\n",
|
|
339
|
+
file=sys.stderr,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
issue = _load_issue(args)
|
|
343
|
+
|
|
344
|
+
if args.dry_run:
|
|
345
|
+
print("=" * 72)
|
|
346
|
+
print("SYSTEM PROMPT")
|
|
347
|
+
print("=" * 72)
|
|
348
|
+
print(build_system_prompt(cfg, announce=True))
|
|
349
|
+
print()
|
|
350
|
+
print("=" * 72)
|
|
351
|
+
print("TASK PROMPT")
|
|
352
|
+
print("=" * 72)
|
|
353
|
+
print(build_task_prompt(issue))
|
|
354
|
+
return 0
|
|
355
|
+
|
|
356
|
+
_configure_git_identity(cfg.repo_path, cfg.agent_name, cfg.agent_email)
|
|
357
|
+
|
|
358
|
+
bound = IterationBound(max_iterations=args.max_iterations,
|
|
359
|
+
deadline_seconds=args.deadline_seconds)
|
|
360
|
+
agent = build_agent(cfg, create_pr_enabled=args.create_pr, bound=bound)
|
|
361
|
+
|
|
362
|
+
print(f"Fixing issue #{issue.get('number', '?')}: {issue.get('title', '')}")
|
|
363
|
+
print(f"Bounds: {bound.max_iterations} model calls, "
|
|
364
|
+
f"{bound.deadline_seconds}s")
|
|
365
|
+
print("-" * 72)
|
|
366
|
+
|
|
367
|
+
agent(build_task_prompt(issue))
|
|
368
|
+
|
|
369
|
+
# Exit 0 either way. The bound firing is not the agent failing -- one observed
|
|
370
|
+
# run had a correct feature implementation in the tree when it ran away, and
|
|
371
|
+
# calling that a failure discarded a real result. The eval scores the tree, not
|
|
372
|
+
# this exit code. The reason goes to stderr so it reaches the transcript.
|
|
373
|
+
if bound.stopped:
|
|
374
|
+
print(f"\nBOUND REACHED: {bound.stopped_reason}", file=sys.stderr)
|
|
375
|
+
print(f" model calls: {bound.iterations}/{bound.max_iterations} "
|
|
376
|
+
f"elapsed: {bound.elapsed:.0f}s/{bound.deadline_seconds}s", file=sys.stderr)
|
|
377
|
+
return 0
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
if __name__ == "__main__":
|
|
381
|
+
sys.exit(main())
|