Skip to content

feat: run the nightly dream and weekly maintenance on an Anthropic API key - #45

Merged
stevepridemore merged 6 commits into
mainfrom
claude/project-thread-wjzqyt
Oct 27, 2026
Merged

stevepridemore merged 6 commits into
mainfrom
claude/project-thread-wjzqyt

Conversation

@stevepridemore

@stevepridemore stevepridemore commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

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 on client.beta.messages.create. Spawns the MCP server over stdio (same pattern as .mcp.json.example) for the graph tools, so it runs as LOCAL_TENANT_ID with 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, previous processed_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_transcript pages through a session with the canonical parser and applies the delta filter before anything reaches the model (replaces graph_read_transcript, which returns a whole session). update_manifest merges 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.lock can't be touched through them.
  • api-key.ts: key from ANTHROPIC_API_KEY or ANTHROPIC_API_KEY_FILE (bare key, KEY=value line, or a folder with one file). Never logged, stripped from the MCP child's env.
  • The runner holds dream.lock itself (api-runner / api-runner-weekly) and releases it in finally.
  • Cost controls: prompt caching, clear_tool_uses context 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.
  • Per-job tool lists: the nightly hides graph_cypher, graph_prune, graph_export, graph_reembed, graph_ingest, graph_unmerge, graph_read_transcript; the weekly gets export/cypher/prune and hides graph_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 unless DREAM_NOTIFY_TOKEN_HOST_FILE is set.
  • Defaults: claude-opus-5-5, effort medium, server-side fallbacks: "default".

Scheduling

  • graph-dream compose service under the dream profile (off unless COMPOSE_PROFILES=dream): nightly at DREAM_SCHEDULE, weekly at DREAM_WEEKLY_SCHEDULE (off by default), in DREAM_TZ. Key file mounted read-only from ANTHROPIC_API_KEY_HOST_FILE. Joins the shared mcp-notify network to reach gws-mcp.
  • Or scripts/register-dream-task.ps1 [-Job weekly] for Task Scheduler.

Notes

  • The published :latest image won't have the runner until a new v* tag; until then use the dev override (docker-compose.dev.yml covers graph-dream).
  • The cloud-session collector still runs in a Projects thread on the subscription; it only queues text.
  • New dependency: @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.
  • Live nightly runs on the home machine (dev image from this branch):
    • First commit: worked end to end but spent the whole $3 budget on discovery (listing the projects folder, paging the 500 KB manifest), with nothing extracted. Fixed in a194029.
    • a194029: completed, 18 turns, ~$0.79, 6 transcripts processed (5 entities, 15 edges), no tool errors, all graph_validate calls clean, the 180 MB transcript handled in one empty-delta page.
  • Weekly job: unit-tested only; not run live yet (it prunes).

🤖 Generated with Claude Code

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
@stevepridemore stevepridemore self-assigned this Oct 27, 2026
…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
@stevepridemore stevepridemore changed the title feat: run the nightly dream on an Anthropic API key feat: run the nightly dream and weekly maintenance on an Anthropic API key Oct 27, 2026
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
stevepridemore merged commit 232033b into main Oct 27, 2026
2 checks passed
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.

2 participants