Skip to content

feat: dream cost emails, and a general API task runner for other scheduled prompts - #46

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

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

Conversation

@stevepridemore

@stevepridemore stevepridemore commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Requested by Steve · project thread

What

This follows #45 and has two parts.

1. A cost summary after every dream run

  • Nightly, completed: a summary with the subject Graph dream — <date>: N sessions, E entities, D edges (~$cost). The body has the counts update_manifest recorded, what discovery found, and the model's closing message.
  • Nightly, nothing pending: a one-line nothing new (~$0.00).
  • Weekly: the cost appears in the subject and at the top of the report.
  • Every email, alerts included, opens with Cost: ~$X (N turns, model) and API spend this month: ~$Y over K runs. The month figure is summed from logs/dream-runner/runs.jsonl.

2. task.js: a general API task runner

It runs one scheduled prompt (a Claude Code SKILL.md, say) on the API key, with only the capabilities its JSON task file grants. There is no shell.

  • Files: list_dir and read_file work anywhere under workdir. write_file and edit_file work only under write_paths. Symlinks out of the folder are refused.
  • Commands: each command is its own tool. The executable and leading args are fixed. The model supplies one argument, which must match a regex pattern, plus flags from a fixed list. Commands run with no shell and with a timeout.
  • HTTP: http_request reaches only the listed prefix and method pairs. The runner injects bearer tokens and {{VAR}} values read from files, so the model never sees them. Redirects aren't followed, and URLs with userinfo are refused.
  • MCP servers: stdio MCP servers, limited to an allowlist of their tools.
  • Email: send_email goes through a gws-mcp notify profile, which fixes the sender and recipient. Sends are capped per run.
  • Logging: runs log to the dream's runs.jsonl, so the month-to-date spend covers them. A run that doesn't complete emails an alert.
  • Scheduling: scripts/register-api-task.ps1 sets up a Task Scheduler job. Tasks run on the host so they can use local tools like python or an MCP server's .exe.

Task files and their prompts live on the host, outside this repo.

The branch also carries -s ours merges of its pre-squash #45 history, so it could be reused without a force push. Those merges change no files.

Testing

  • tsc is clean.
  • src/dream-runner: 27/27 tests pass.
    • Mail: the nightly summary, the quiet-night message, the weekly cost header, and the month-to-date sum (last month excluded).
    • Task runner: read/write confinement, including a symlink escape; the command pattern and flag allowlist; URL prefix and method checks; frontmatter stripping; a full loop that runs a command, writes a file, refuses a read-only write and sends an HTML email; and the failure alert.

🤖 Generated with Claude Code

https://claude.com/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
…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
Nightly runs now mail a summary (sessions, entities, edges, the model's
closing note) with the run's cost; quiet nights send a one-liner. Every
mail, the weekly report included, opens with the run's cost and the API
spend so far this month from runs.jsonl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo1icT3VB2uwkk8m5RAaWH
task.js runs one scheduled prompt on the API key with only what its task
file grants: confined file tools, allowlisted commands (fixed exe and
args, one pattern-checked argument), allowlisted URLs with credentials
injected by the runner, allowlisted stdio MCP tools, and email through a
gws-mcp notify profile. Runs share the dream's runs.jsonl so month-to-date
spend covers them. Adds register-api-task.ps1 for Task Scheduler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo1icT3VB2uwkk8m5RAaWH
@stevepridemore stevepridemore changed the title feat: email a cost summary after every dream run feat: dream cost emails, and a general API task runner for other scheduled prompts Oct 27, 2026
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 9cc4476 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