Repository navigation
feat: run the nightly dream and weekly maintenance on an Anthropic API key - #45
Merged
Merged
Conversation
Adds src/dream-runner/, a standalone runner that executes prompts/dream-nightly.md through the Claude Messages API with an Anthropic API key, instead of as a Claude Code scheduled task on a claude.ai plan. - Spawns the MCP server over stdio for the graph tools, adds file tools confined to ~/graph-memory (rw) and ~/.claude/projects (ro); no shell. - Reads the key at run time from ANTHROPIC_API_KEY or ANTHROPIC_API_KEY_FILE (file or one-file folder); never logged or passed to the MCP child. - Skips the API entirely when nothing is pending; holds dream.lock itself; prompt caching, context editing, per-run cost cap with a wrap-up turn. - Opt-in graph-dream compose service (profile "dream", daily at DREAM_SCHEDULE) and scripts/register-dream-task.ps1 for Task Scheduler. - Docs in docs/DREAM_PROCESS.md "API-key runner"; tests for the loop, path confinement, key parsing, pending check and schedule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jo1icT3VB2uwkk8m5RAaWH
…e manifest entries The first live run spent its whole $3 budget listing ~/.claude/projects and paging through the 500 KB manifest, and extracted nothing. - The runner now passes the exact pending list (session id, path, new or delta, previous processed_at) in the first message; the notes tell the model not to list the projects folder or read the manifest. - read_transcript pages through a session with the canonical parser and applies the delta filter before anything reaches the model; it replaces graph_read_transcript, which is now excluded by default. - update_manifest merges one entry on disk with the watermark captured at read time, instead of the model rewriting the whole manifest. - Sessions modified in the last 30 minutes are left for the next run. - Wrap-up starts at 80% of the budget; hard stop at 1.5x. - Smaller default result and read sizes (40k / 30k chars). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jo1icT3VB2uwkk8m5RAaWH
- `--job weekly` runs prompts/weekly-maintenance.md with its own tool list (graph_export, graph_cypher and graph_prune allowed; graph_merge hidden), its own budget (DREAM_WEEKLY_MAX_COST_USD, default $1.50) and runner notes. No pending check; lock source api-runner-weekly. - Step 12's email needs a shell and gws, so the runner skips it and the report says so. Off by default (DREAM_WEEKLY_SCHEDULE=off) so it doesn't double up with the Claude Code weekly task. - Daemon schedules both jobs, each with its own next slot; schedules accept "HH:MM" or "Sun HH:MM". - register-dream-task.ps1 takes -Job weekly -DayOfWeek. - Log the ids of sessions held back as still active. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jo1icT3VB2uwkk8m5RAaWH
The runner, not the model, sends mail after a run: the weekly report when the weekly job completes, and a short alert (outcome, cost, log tail) when any run errors, stops at its budget or turn limit, is refused, or finds the lock held. Normal nightly runs send nothing. Plain SMTP via nodemailer (Gmail app password by default), with the password read from a mounted file like the API key. Off unless DREAM_ALERT_TO, DREAM_SMTP_USER and the password file are set. A failed send is logged and doesn't fail the run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jo1icT3VB2uwkk8m5RAaWH
Replace SMTP with a POST to gws-mcp's send-only /notify endpoint over the shared mcp-notify network, authenticated by a token file. The runner no longer holds a Gmail credential; gws-mcp fixes the sender and recipient. Drops nodemailer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jo1icT3VB2uwkk8m5RAaWH
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jo1icT3VB2uwkk8m5RAaWH
stevepridemore
pushed a commit
that referenced
this pull request
Oct 27, 2026
stevepridemore
pushed a commit
that referenced
this pull request
Oct 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Steve
What
An opt-in runner that executes the dream prompts through the Messages API with an Anthropic API key, so scheduled graph work bills the key instead of a claude.ai plan. The Claude Code scheduled tasks stay the default.
src/dream-runner/index.ts: manual tool-use loop onclient.beta.messages.create. Spawns the MCP server over stdio (same pattern as.mcp.json.example) for the graph tools, so it runs asLOCAL_TENANT_IDwith no network auth. Two jobs:nightly(default) and--job weekly.pending.ts: the runner does the nightly prompt's discovery itself and passes the exact pending list (session id, path, new/delta, previousprocessed_at) in the first message. No pending work means no API call. Sessions modified in the last 30 minutes are left for the next run.transcript-tools.ts:read_transcriptpages through a session with the canonical parser and applies the delta filter before anything reaches the model (replacesgraph_read_transcript, which returns a whole session).update_manifestmerges one manifest entry on disk with the watermark captured at read time.file-tools.ts:list_dir,stat_file,read_file,write_file,move_file,delete_file, confined to~/graph-memory(rw) and~/.claude/projects(ro), symlink-aware. No shell.dream.lockcan't be touched through them.api-key.ts: key fromANTHROPIC_API_KEYorANTHROPIC_API_KEY_FILE(bare key,KEY=valueline, or a folder with one file). Never logged, stripped from the MCP child's env.dream.lockitself (api-runner/api-runner-weekly) and releases it infinally.clear_tool_usescontext editing, per-run budget (wrap-up at 80%, hard stop at 1.5x),DREAM_MAX_TURNS, results over 40k chars spilled to a file with a pointer.graph_cypher,graph_prune,graph_export,graph_reembed,graph_ingest,graph_unmerge,graph_read_transcript; the weekly gets export/cypher/prune and hidesgraph_merge.notify.ts: email from the runner after a run (never a model tool). Weekly runs that complete send the report; any run that errors, stops on budget or turns, is refused, or finds the lock held sends an alert with the log tail; a normal nightly sends nothing. Sent through gws-mcp's send-only notify listener (stevepridemore/gws-mcp#1) with a token file, so the runner holds no Gmail credential. Off unlessDREAM_NOTIFY_TOKEN_HOST_FILEis set.claude-opus-5-5, effortmedium, server-sidefallbacks: "default".Scheduling
graph-dreamcompose service under thedreamprofile (off unlessCOMPOSE_PROFILES=dream): nightly atDREAM_SCHEDULE, weekly atDREAM_WEEKLY_SCHEDULE(offby default), inDREAM_TZ. Key file mounted read-only fromANTHROPIC_API_KEY_HOST_FILE. Joins the sharedmcp-notifynetwork to reach gws-mcp.scripts/register-dream-task.ps1 [-Job weekly]for Task Scheduler.Notes
:latestimage won't have the runner until a newv*tag; until then use the dev override (docker-compose.dev.ymlcoversgraph-dream).@anthropic-ai/sdk.Testing
src/dream-runner/dream-runner.test.ts(20 tests): nightly loop against a fake model and MCP server, weekly job tool list and prompt, no-API-call when nothing is pending, pending list and active-session hold-back, transcript paging, delta filter and manifest merge, path confinement (.., symlink), key parsing, daily and weekly schedule math, cost estimate, which mail goes out per outcome, and the notify POST.completed, 18 turns, ~$0.79, 6 transcripts processed (5 entities, 15 edges), no tool errors, allgraph_validatecalls clean, the 180 MB transcript handled in one empty-delta page.🤖 Generated with Claude Code