Repository navigation
feat: add a minutes-per-run activity goal for Auto-run - #82
OjusWiZard merged 15 commits into
Conversation
The default length of Connect's run window, declared in the service package so a template can override it. A bad value falls back to the default with a warning rather than failing boot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Connect has no staking epoch, so to take part in Auto-run it needs a stopping signal of its own: a run window that stamps last_met_at when its minutes elapse and immediately reopens, since Connect never stops itself. Rolling over at the current time rather than the theoretical boundary turns a suspend into one completed run, not a burst. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agent_performance.json now carries the activity_goal block Pearl reads to decide Connect's Auto-run turn. The user's minutes per run live in that block's target: every write reads it back, adopts a valid value and puts the current one back over anything else, so the session can change it by editing the file and the value survives restarts with no second store. A boot restoring the stored value is not audited as a change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without signer activity nothing wrote the file, so the run window would never roll over for Pearl to see and a session's edit of the target would never be read. The job runs in the app lifespan so TestClient exercises it, and does its blocking write in a worker thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user changes Connect's run window by asking the session, which edits activity_goal.target in agent_performance.json. The brief scopes that edit to the one field, has the session confirm the value it reads back and decline what it cannot map, and keeps it from touching the value unasked. It deliberately says nothing about stopping at the end of a run: Auto-run decides that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed minutes_per_run_changed audit append no longer aborts the agent_performance.json write or gets reported as a failure of the caller's activity entry; it is logged on its own and the change stands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review — OPE-1801
Scope: Agent trading after having earned rewards — Connect minutes-per-run activity goal (OPE-1801/technical/connect.md)
Files reviewed: 15 of 15 changed
Comments: 0
Key findings
None. All seven dimensions returned no findings. The orchestrator also re-read connect/run_goal.py, connect/activity.py, connect/server/app.py, connect/config.py and connect/__main__.py, and found nothing that clears the evidence bar. Lock ordering holds (ActivityLog._lock, then RunGoal's lock), and _append does not re-take the non-reentrant lock. The read-back fails open, and the lifespan task is cancelled and awaited on shutdown.
Dimensions
| Dimension | Critical | Important | Advisory |
|---|---|---|---|
| Security & quality | 0 | 0 | 0 |
| Error handling | 0 | 0 | 0 |
| Test coverage | 0 | 0 | 0 |
| Type design | 0 | 0 | 0 |
| Comments | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 |
| Scope alignment | 0 | 0 | 0 |
| Orchestrator | 0 | 0 | 0 |
Scope coverage
- ✅
RunGoalmodule and D5 run-window rollover semantics (reopen at "now", a target of 0 is always met) - ✅
activity_goalblock inagent_performance.jsonwith the D1 shape - ✅ The session's write of
activity_goal.targetis read back, validated and audited (minutes_per_run_changed) - ✅ The target survives a restart through the boot read-back, which records no audit entry
- ✅
CONNECTION_CONFIGS_CONFIG_MINUTES_PER_RUNenv var, with a fallback to the default of 15 - ✅ 15-second lifespan refresh job: runs off the event loop, tolerates a failing disk, is cancelled on shutdown
- ✅
service.yamloverride andpackages.jsonre-lock - ✅
README.mdandCLAUDE.mdupdated with the code - ✅ "Minutes per run" section in the session brief and a pointer in the skill. Neither mentions a guardrail mode or tells the session to stop
- ✅ Tests follow the scope's Section 9 strategy
- ⏳
olas-operate-appAuto-run rotation, checklist and notification, plus thetraderandoptimusgoals. These are separate repos and PRs per the scope README
Not reviewed
- The tests were not re-run by the reviewer: the sandbox could not build the uv environment (the
safe-pysha3native build failed). CI and the PR's own verification block are the evidence for the gates. - The manual checks in scope Section 9 (a standalone run with the env var set to 1, and live session tests) remain open, as the PR body states.
🤖 Coding Agent [Beta] · Automated review against OPE-1801
Auto-run can now treat Connect like every other agent: it rotates on the false -> true of is_met instead of keying on a Connect-only reading of last_met_at. The window opens at process start and never rolls over; is_met is progress >= target, so a target change is measured against the same window. last_met_at is stamped once, on the first time the goal is seen met. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OjusWiZard
left a comment
There was a problem hiding this comment.
The sticky is_met switch looks right. Requesting changes for the items below.
1. The refresh job can die silently (connect/server/app.py refresh_performance, connect/activity.py _stored_target)
write_performance catches only OSError, _stored_target only OSError/ValueError, and the loop has no try. Reproduced: an agent_performance.json of 100 000 [ makes write_performance() raise RecursionError, and the session can write that file. The task then ends with nothing logged, is_met/progress freeze and target edits are never adopted (Auto-run hands over late or never), and the lifespan finally re-raises the error at shutdown.
Fix: try/except Exception: logger.exception(...) around the loop body, and treat any parse failure in _stored_target as unusable.
2. Test gaps (coverage is 100%, but these regressions would pass)
- The job adopting a session edit is untested:
test_job_writes_the_block_while_serving_and_stops_afteronly checks that the file appears. Edittargetwhile serving and assert it is adopted and audited asminutes_per_run_changed. - Shutdown is checked by deleting the file and sleeping 0.1 s. Count
write_performancecalls across shutdown instead. - A fresh store must not warn: assert no WARNING on the first write with no file, so dropping the
FileNotFoundErrorbranch fails a test. test_unusable_file_keeps_the_current_targetasserts only"agent_performance.json" in caplog.text. Assert the specific warning per case, and that nominutes_per_run_changedentry was written.
3. A bad CONNECTION_CONFIGS_CONFIG_MINUTES_PER_RUN is logged where nobody looks
load_config() runs before setup_logging() in connect/__main__.py, so the "using 15" warning goes only to stderr, not log.txt. Log it after setup_logging, or raise it as a configuration error like the other bad config.
4. Target validation lives in two places, and RunGoal does not enforce it
_parse_minutes_per_run (config.py) and _stored_target (activity.py) each check "int >= 0, not bool"; RunGoal.__init__/set_target accept anything, and a negative target is met at once. One shared check, used by both parsers and raised on in RunGoal, replaces the two.
5. Shorter session instructions, and say what the setting does
The brief's "Minutes per run" section tells the session how to edit a field, but not what the number means for the user. Make it shorter and explain Auto-run. Something like:
Minutes per run
With Pearl's Auto-run, the user's agents take turns. This agent's turn lasts at least N minutes: after that, Pearl hands over to the next agent that can run, or lets this one continue if none can. N is
activity_goal.targetinagent_performance.json; 0 hands over at Pearl's next check. Never stop yourself — Pearl ends the turn.Change N only when the user asks: set
activity_goal.targetto a whole number of minutes, 0 or more (if you can't map the request to one, say so and change nothing), leave the rest of the file as is, then read it back after about 15 seconds and confirm the value.
The agent_performance.json bullet in the "don't hand-edit" list can then just point at this section, and the pearl-connect SKILL.md addition can shrink to one line pointing to it (or go, since the brief is always loaded).
6. Comments and docs
connect/run_goal.pymodule docstring: 7 lines that repeat README "Run window". One line.connect/activity.pymodule docstring ("so the user's minutes per run live in the file and survive restarts"): repeats the README; point to it.- README "Run window", the last paragraph restates the brief's rules for the session (change only when asked, confirm, decline). Link to the brief instead.
- PR description and scope D5 say
last_met_atis stamped once "as for Trader and Optimus". Trader stamps it on every false → true; Connect once per process. Drop the comparison. - PR description: the second MUTATION line still lists "rollover at "now"".
A write that raised anything but OSError ended the task silently, freezing the activity goal Pearl reads. Tests now also cover the job adopting a session edit and stopping at shutdown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
check_target is shared by the env var parser, the read-back of agent_performance.json and RunGoal itself, which now refuses a negative target. A bad CONNECTION_CONFIGS_CONFIG_MINUTES_PER_RUN is a configuration error, so it reaches log.txt instead of a pre-logging stderr warning. Any parse failure of the stored file, RecursionError included, is unusable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Addressed the 6 items in the review from @OjusWiZard:
I also replied to the github-code-quality "Statement has no effect" alert on Checks (pre-push verifier): flake8, pylint, black-check, isort-check, bandit, mypy, check-copyright and gitleaks pass. unit-tests-coverage reaches 100%. Its only failure is the sandbox-only |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bennyjo
left a comment
There was a problem hiding this comment.
No blockers from me; two small questions inline.
|
@valory-coding-agent address the latest 2 review comments |
The first write copies CONNECTION_CONFIGS_CONFIG_MINUTES_PER_RUN into agent_performance.json and the file wins after that, so a later template default never reaches an existing install. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A target the session changed while Connect was stopped was adopted on the next boot as a silent restore, so it never reached the audit log. Audit it as minutes_per_run_restored; a stored value equal to the configured one is still not logged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Addressed the latest 2 review comments, both from @bennyjo. Neither had a category tag. Each one has its own commit:
|
Implements: https://linear.app/valory/issue/OPE-1801
Summary
Connect gets a minutes-per-run activity goal (default 15,
CONNECTION_CONFIGS_CONFIG_MINUTES_PER_RUN). It is published as the sharedactivity_goalblock (scope README D1,unit: "minutes") in theagent_performance.jsonthatActivityLogalready writes, so Pearl's Auto-run rotates on Connect'sis_metlike any other agent's (README D5).connect/run_goal.py(RunGoal) owns the run window: one per process, opened at start and never rolled over.is_metisprogress >= target, so once the minutes have elapsed it stays true for the rest of the process. A target change is measured against the same window: raising it aboveprogressmakes the goal unmet again, lowering it to or belowprogressmeets it. A target of 0 is met at once.last_met_atis stamped once per process, on the first false → true. Connect never stops itself.asyncio.to_thread). A failed write, of any kind, is logged and never ends the job.activity_goal.targetdirectly, and each server write reads it back underActivityLog's lock. A valid new value is adopted and audited (minutes_per_run_changed). An invalid value, or a file that cannot be parsed at all, is logged, and the current target is written back. Onecheck_target(whole number >= 0, not a bool) validates the env var, the read-back andRunGoalitself; a bad env var is a configuration error, like other bad config. The file is inSTORE_PATH, so the value survives restarts.connect/assets/CLAUDE.md) gets a "Minutes per run" section, and thepearl-connectskill points to it. The brief explains what the number means under Auto-run, says to change it only when the user asks, read it back and confirm it, and to decline anything it cannot map. It mentions no guardrail mode and tells the session never to stop itself.service.yamldeclaresminutes_per_run: ${MINUTES_PER_RUN:int:15}, and the packages are re-locked.README.mdandCLAUDE.mdare updated alongside the code.signer.py,guard.py,settings.py, the MCP tools and the routes.Points for reviewers
minutes_per_run_restoredentry, notminutes_per_run_changed. That entry may be the value from before the restart, or an edit the session made while Connect was stopped. A stored value equal to the configured one writes nothing, so a restart with nothing changed adds no entry. Later edits by the session are audited asminutes_per_run_changed, as the scope specifies. Once stored, the file's value wins overCONNECTION_CONFIGS_CONFIG_MINUTES_PER_RUN, so changing the env var later does not reach an existing install.ActivityLog(store_path, run_goal=None)falls back to a 15-minuteRunGoal, so existing call sites and fixtures stay unchanged.__main__always passes the configured one.activity_goalblock logs one warning, as the scope's test list requires. Every existing install will therefore log it once, on its first boot after upgrading. A missing file is treated as a fresh store and is not warned about.is_met): the earlier rolling window, whereis_metstayed false and Auto-run keyed onlast_met_at, was replaced at the reviewer's request on olas-operate-app#2155. The scope (README D5,technical/connect.md) is updated to match.integration-tests(needs an RPC and Docker) andlaunch-tests(needs a TTY).Verification
Technical scope:
OPE-1801/technical/connect.mdon branchiasonrovis/ope-1801-agent-trading-after-having-earned-rewardsof valory-xyz/scopes🤖 Generated with Claude Code