Skip to content

Add the mngr_donate plugin (spend spare Claude capacity on a donation skill)#2417

Open
jean-imbue wants to merge 9 commits into
mainfrom
add-mngr-donate
Open

Add the mngr_donate plugin (spend spare Claude capacity on a donation skill)#2417
jean-imbue wants to merge 9 commits into
mainfrom
add-mngr-donate

Conversation

@jean-imbue

@jean-imbue jean-imbue commented Jul 10, 2026

Copy link
Copy Markdown

What

Adds the standalone imbue-mngr-donate plugin — 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/--stop install/remove a launchd LaunchAgent (macOS) so idle quota drains on a schedule.

Supersedes the earlier stacked pair (#2414 skill + #2415 plugin). We moved the skill out of this repo entirely — see below — so it's now a single PR with mngr_usage untouched.

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:

  1. checks it out into a host-dir cache (<host_dir>/donate-skills/<skill>/), and
  2. points the headless agent at it to run.

--skill-ref controls what's checked out:

  • a branch to track — each tick adopts the lab's latest (good for a fast-moving experiment), or
  • a pinned commit for a reviewed, reproducible version — imbue bumps the ref to adopt updates.

So the lab iterates on the skill without an mngr release, and there's no vendored copy in this repo to drift.

⚠️ Security note: the agent runs the checked-out skill with --dangerously-skip-permissions, so whatever code is at --skill-ref executes on the host. The default is therefore pinned to a reviewed commit (c2e9bbe…), not main — so unattended/scheduled runs execute an audited version. Bump the SHA in a reviewed PR to adopt the lab's updates; pass --skill-ref main to track the branch instead.

Why a separate plugin

Measuring usage (mngr usage) and donating spare quota (mngr donate) are orthogonal. This plugin depends on imbue-mngr-usage (installing donate pulls usage in) and is registered in the plugin catalog as a default-installable DEPENDENT gated on imbue-mngr-usage. mngr_usage is unchanged.

Notes for review

  • launchd, not cron. A cron job runs outside the login session and can't reach the macOS keychain where Claude's subscription token lives (every tick fails auth); a LaunchAgent runs in the login session and authenticates, and catches up after sleep. --start/--stop are 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.
  • Glue-heavy, modest coverage floor. The pure decision logic (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 (launchd time.sleep, stdout-tee, the deliberate subprocess shell-outs).

Verified

mngr donate --help/--dry-run load from the new plugin; mngr plugin list shows both donate and usage; prepare_skill_dir cloned the lab's repo and checked out the skill (code + prompts); plugin-catalog, dependency-pin, changelog, and wheel/readme gates pass locally; mngr usage unaffected.

🤖 Generated with Claude Code

… 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>
@jean-imbue

Copy link
Copy Markdown
Author

CI note: test-offload red only from a pre-existing minds ratchet — not from this PR

After the latest push (bdfe65ea8), every donate-related failure is fixed:

  • ✅ ruff format (donate.py, donate_test.py)
  • Commands missing generated docs: ['donate']donate registered in SECONDARY_COMMANDS, docs/commands/secondary/donate.md generated
  • ✅ donate package tests + its own ratchets (70 passed)
  • ✅ check-changelog, test-docker, test-offload-acceptance, Acceptance Tests

The only remaining red is test-offload → test_prevent_inline_functions, which scans minds, not this plugin:

RATCHET TEST FAILURE: inline functions in non-test code have increased!
apps/minds/imbue/minds/desktop_client/app.py:1072   def _on_change() -> None:
apps/minds/imbue/minds/desktop_client/api_v1.py:433

This is pre-existing on main and unrelated to donate:

  • This branch touches zero minds files (git diff --name-only origin/main...HEAD | grep minds → none).
  • The branch base is exactly origin/main HEAD (0 behind).
  • The offending inline _on_change was introduced in c15f7bad5 (Gabriel Guralnick, 2026-06-23, "minds: discovery-pipeline health watchdog"), long before this branch existed. The ratchet snapshot for minds was never updated for it, so main is already red on this test.

Nothing in this PR can turn that check green — it needs a separate minds-owned fix (extract the inline callbacks or bump the minds ratchet snapshot). Flagging so this can be merged with that context.

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 jean-imbue left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@jean-imbue jean-imbue requested a review from joshalbrecht July 10, 2026 17:24
package_name="imbue-mngr-donate",
description="Spend spare Claude capacity on a donation skill (`mngr donate`)",
tier=PluginTier.DEPENDENT,
is_recommended=True,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +19 to +20
capacity over time, ``mngr donate --start`` installs a launchd LaunchAgent
(macOS) that re-runs the tick on an interval (``--stop`` removes it) -- the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do any imbumans use linux?
also proposing to punt this to the next PR...

Comment on lines +98 to +103
# 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we'll definitely want these to be configurable, and we'll have to think about how the initial setup works properly here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, 100% agree for the final version, i'm hoping to just get this in as a v0 so we can start unblocking Nasa

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, I'll need to think a little bit more about this. This has billing implications in the near future potentially

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@boweiliu

Copy link
Copy Markdown
Collaborator

#2457

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants