Add the mngr_donate plugin (spend spare Claude capacity on a donation skill)#2417
Add the mngr_donate plugin (spend spare Claude capacity on a donation skill)#2417jean-imbue wants to merge 9 commits into
Conversation
… skill) New imbue-mngr-donate plugin: 'mngr donate' reads the account usage snapshot (from imbue-mngr-usage, a dependency) and, when the 5h window has budget and the week is under pace, launches a headless Claude agent that runs a donation skill to completion. --start/--stop install/remove a launchd LaunchAgent (macOS) so idle quota drains on a schedule (launchd, not cron, so it runs in the login session where Claude's keychain token lives). The donation skill (code + prompts) is owned by its upstream git repo -- the single source of truth. donate checks it out at --skill-ref (a branch to track, or a pinned commit for a reviewed version) into a host-dir cache and points the headless agent at it, so the lab can revise the skill without an mngr release. Registered in the plugin catalog as a default-installable DEPENDENT gated on imbue-mngr-usage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Default --skill-ref to a specific commit (c2e9bbe) of the lab's skill repo instead of tracking 'main', so unattended/scheduled runs execute an audited version (the agent runs it with --dangerously-skip-permissions). Bump the SHA in a reviewed PR to adopt updates; pass --skill-ref main to track the branch.
- ruff format donate.py + donate_test.py (format-only, check already passed) - add 'donate' to SECONDARY_COMMANDS in make_cli_docs.py + generate docs/commands/secondary/donate.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI note:
|
Headless agents can't refresh Claude's ~8h session token (the desktop app refreshes only its own copy), so scheduled ticks die with 401 once it lapses. If the macOS keychain has an `mngr-donate-oauth` entry (a year-long token from `claude setup-token`), donate now exports it as CLAUDE_CODE_OAUTH_TOKEN to the agent; otherwise the environment is inherited unchanged. Documented the whole failure mode + one-time setup in README "Authentication". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The agent works from a worktree of the host repo, so that repo's stop hooks apply -- a reviewer's "no stopping without a PR" hook goaded a donation agent into opening a junk draft PR. The task changes no repo files, so the message now says so explicitly and tells the agent to refuse. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jean-imbue
left a comment
There was a problem hiding this comment.
Please review! Hoping to get this in so people can install and try at all hands.
It's a prerequisite (skipping it is why scheduled donations die overnight), so it now comes before "Run a donation" instead of after the scheduling docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| package_name="imbue-mngr-donate", | ||
| description="Spend spare Claude capacity on a donation skill (`mngr donate`)", | ||
| tier=PluginTier.DEPENDENT, | ||
| is_recommended=True, |
There was a problem hiding this comment.
Given that this is fairly experimental, I'd probably keep is_recommended False by default for now
Once we're ready to release it (and after we've done some testing, have some initial science partners, etc), then we can flip it to True
| capacity over time, ``mngr donate --start`` installs a launchd LaunchAgent | ||
| (macOS) that re-runs the tick on an interval (``--stop`` removes it) -- the |
There was a problem hiding this comment.
this should work on osx and linux (those are the supported platforms for mngr)
(in practice this generally means it works on WSL too, though we dont test it there)
There was a problem hiding this comment.
do any imbumans use linux?
also proposing to punt this to the next PR...
| # Spare-capacity thresholds, mirroring spare-capacity.sh exactly: | ||
| # spare <=> five_hour.used% < 80 AND weekly.used% < pace_line(weekly.elapsed%) | ||
| # where pace_line starts ~30% under the plain used%==elapsed% line early in the | ||
| # 7-day cycle and tapers up to meet it by the cycle's end. | ||
| FIVE_HOUR_USED_CEILING = 80.0 | ||
| WEEKLY_EARLY_MARGIN = 0.30 |
There was a problem hiding this comment.
we'll definitely want these to be configurable, and we'll have to think about how the initial setup works properly here
There was a problem hiding this comment.
proposing to punt this to the next PR 😅
| # session token -- the desktop app refreshes only its own copy -- so without this, | ||
| # scheduled ticks start failing with `401 Invalid authentication credentials` as | ||
| # soon as the session token lapses. See README "Authentication". | ||
| OAUTH_KEYCHAIN_SERVICE = "mngr-donate-oauth" |
There was a problem hiding this comment.
this is interesting, but we'll need to talk about this more and think about this -- the auth stuff gets fairly complicated, and we'll need a decent amount of testing here
and we'll need to think about how users are notified if / when it stops working too probably
There was a problem hiding this comment.
makes sense, could we enumerate a list of risks we're worried about so we can concretely address them + have an idea of the scope of work?
would it suffice to warn users about this and "use at your own risk" for now?
| # for a reviewed, reproducible version (imbue bumps the ref to adopt updates). | ||
| # Both overridable via --skill-repo / --skill-ref. | ||
| DEFAULT_SKILL_REPO = ( | ||
| "https://gitlab.com/sinnott-armstrong-lab/elsi-checklist/credits-for-science/document-review-skill.git" |
There was a problem hiding this comment.
I think we want one more level of indirection -- ideally it goes to us, and then we supply the list of (currently approved) science projects
we want the projects to be able to update even without people updating this package, for example
There was a problem hiding this comment.
yes, 100% agree for the final version, i'm hoping to just get this in as a v0 so we can start unblocking Nasa
There was a problem hiding this comment.
and then we can also push out the blog post, which would then actually get us other scientists and make this necessary :)
| # behind (a tick that dies mid-way can, which is what the pre-launch cleanup in | ||
| # `donate` handles). ``--dangerously-skip-permissions`` (below) is what lets it | ||
| # actually run the skill's commands unattended. | ||
| DONATE_AGENT_TYPE = "headless_claude" |
There was a problem hiding this comment.
hmmm, I'll need to think a little bit more about this. This has billing implications in the near future potentially
There was a problem hiding this comment.
would this PR be blocked by that? if so what would be the next steps?
psutil's has_cpu_freq() only checks that the IOKit "pmgr" entry exists, so on M4/M5 Macs it reports CPU frequency as available while the actual cpu_freq() read raises (Apple changed the private voltage-state data format psutil parses; see psutil #2642 / #2382). The unguarded call in get_host_resources aborted local host discovery, surfacing as "Error processing host ..." in mngr list and every command built on it (mngr usage, mngr donate -- which then wrongly reported "No Claude usage data"). Read the frequency through read_cpu_freq_ghz, which treats any of the documented psutil failures as "no reading" and returns None, matching how the disk_usage read below already degrades. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Apple Silicon cpu_freq fix touches libs/mngr, whose per-PR changelog entry for this branch is add-mngr-donate.md. Move the note there and drop the separately-named file so the mngr project has a single entry for the PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Adds the standalone
imbue-mngr-donateplugin —mngr donate, which spends spare Claude capacity on a donation skill instead of letting idle quota expire.One tick: read the account-level usage snapshot (from
imbue-mngr-usage, a hard dependency), and if there's spare capacity (5h window under budget and the week under its pace line), launch a headless Claude agent that runs the donation skill to completion, then auto-cleans up.--start/--stopinstall/remove a launchd LaunchAgent (macOS) so idle quota drains on a schedule.Skill lives in the lab's repo, checked out by ref
The donation skill (code + prompts) is owned by its upstream git repo — the single source of truth (for
document-review, the Sinnott-Armstrong lab's GitLab).mngr donate:<host_dir>/donate-skills/<skill>/), and--skill-refcontrols what's checked out:So the lab iterates on the skill without an mngr release, and there's no vendored copy in this repo to drift.
--dangerously-skip-permissions, so whatever code is at--skill-refexecutes on the host. The default is therefore pinned to a reviewed commit (c2e9bbe…), notmain— so unattended/scheduled runs execute an audited version. Bump the SHA in a reviewed PR to adopt the lab's updates; pass--skill-ref mainto track the branch instead.Why a separate plugin
Measuring usage (
mngr usage) and donating spare quota (mngr donate) are orthogonal. This plugin depends onimbue-mngr-usage(installing donate pulls usage in) and is registered in the plugin catalog as a default-installableDEPENDENTgated onimbue-mngr-usage.mngr_usageis unchanged.Notes for review
--start/--stopare macOS-only and error clearly elsewhere. It also forces shared Claude config (-S …isolate_local_config_dir=false) so the agent uses/refreshes the real token instead of a stale isolated copy.evaluate_capacity, pace line, argv/plist builders) is fully unit-tested; the launchd/subprocess/git IO is verified end-to-end (fail_under = 25, noted in the pyproject). A few ratchets are grandfathered for legit patterns (launchdtime.sleep, stdout-tee, the deliberatesubprocessshell-outs).Verified
mngr donate --help/--dry-runload from the new plugin;mngr plugin listshows bothdonateandusage;prepare_skill_dircloned the lab's repo and checked out the skill (code + prompts); plugin-catalog, dependency-pin, changelog, and wheel/readme gates pass locally;mngr usageunaffected.🤖 Generated with Claude Code