Weekly Metrics Narrative
MIT↓ 0 downloadsA single Claude Code agent reads a metrics.csv, computes week-over-week deltas, and writes an executive narrative — what moved, why it likely moved, what to watch, and one recommended action — alongside a compact metrics table, to reports/weekly.md.
Topology
Disclosures
Everything below runs on your machine or inside the sandbox when you use this workflow. Mismatches between these declarations and the actual code block publishing.
Host hooks
Commands executed on YOUR host machine by Sandcastle lifecycle hooks.
None declared.
Sandbox hooks
Commands executed inside the sandbox container.
None declared.
Network access
None. The agent operates only on the local repository inside the sandbox.
Shell expansion
No shell-expansion blocks in prompt files.
Files
Diff vs the stock Sandcastle 0.12.0 template Dockerfile — green lines were added by the author, red lines were removed from stock.
+# Sandbox image for the Weekly Metrics Narrative workflow.+# Node 22, git, and the Claude Code CLI, running as a non-root `agent` user.FROM node:22-bookworm# System dependencies.RUN apt-get update && apt-get install -y --no-install-recommends \git \curl \jq \ca-certificates \&& rm -rf /var/lib/apt/lists/*# Claude Code CLI (the agent runtime).RUN npm install -g @anthropic-ai/claude-code# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to# the host user via --build-arg to avoid permission errors on bind mounts.+# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it+# (the stock Sandcastle template pattern) — groupadd/useradd would collide with+# the existing IDs on a default build.ARG AGENT_UID=1000ARG AGENT_GID=1000-RUN groupadd --gid ${AGENT_GID} agent \- && useradd --uid ${AGENT_UID} --gid ${AGENT_GID} --create-home --shell /bin/bash agent+RUN groupmod -o -g ${AGENT_GID} node \+ && usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node-USER agent-WORKDIR /workspace+USER ${AGENT_UID}:${AGENT_GID}+WORKDIR /home/agent++# Sandcastle bind-mounts the worktree and sets the working directory at+# container start; the container just needs to stay alive until then.+ENTRYPOINT ["sleep", "infinity"]
Show full Dockerfile (highlighted)
# Sandbox image for the Weekly Metrics Narrative workflow.
# Node 22, git, and the Claude Code CLI, running as a non-root `agent` user.
FROM node:22-bookworm
# System dependencies.
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
jq \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Claude Code CLI (the agent runtime).
RUN npm install -g @anthropic-ai/claude-code
# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to
# the host user via --build-arg to avoid permission errors on bind mounts.
# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it
# (the stock Sandcastle template pattern) — groupadd/useradd would collide with
# the existing IDs on a default build.
ARG AGENT_UID=1000
ARG AGENT_GID=1000
RUN groupmod -o -g ${AGENT_GID} node \
&& usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node
USER ${AGENT_UID}:${AGENT_GID}
WORKDIR /home/agent
# Sandcastle bind-mounts the worktree and sets the working directory at
# container start; the container just needs to stay alive until then.
ENTRYPOINT ["sleep", "infinity"]
# Auth for the Claude Code reporter agent.
# Run `claude setup-token` on your host to generate a token, then paste it here
# in your local .sandcastle/.env (never commit the real .env).
CLAUDE_CODE_OAUTH_TOKEN=
import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
// A single-pass metrics reporter. One Sonnet agent reads metrics.csv, computes
// week-over-week deltas, and writes an executive narrative plus a compact table.
// maxIterations is 1 because this is a one-shot report, not a loop. Default
// `head` branch strategy on docker means the commit lands on the current branch.
const result = await run({
name: "weekly-metrics-narrative",
agent: claudeCode("claude-sonnet-4-6"),
sandbox: docker(),
promptFile: ".sandcastle/prompt.md",
maxIterations: 1,
});
console.log(`Weekly report written in ${result.commits.length} commit(s) on ${result.branch}.`);
Weekly metrics narrative
You are a data-literate operator writing the weekly metrics update that a busy executive will actually read. Lead with meaning, not numbers.
Input
Read metrics.csv in the repository root. It has one row per week (a date or
week-label column) and one column per metric (e.g. signups, active users,
revenue, churn). If the file is missing or has fewer than two weeks of data,
say so and do the best you can with what is there.
Your task
Compute the week-over-week change for the two most recent rows: absolute
delta and percentage change for every metric. Then write reports/weekly.md
with these sections:
- Headline — one or two sentences capturing the single most important thing that happened this week.
- What moved — the metrics that changed most, each with its old value, new value, absolute delta, and percent change. Call out direction (up/down) and whether that is good or bad for this metric.
- Why it likely moved — your best hypotheses for the notable changes, clearly labelled as hypotheses. Distinguish signal from noise; a 2% wobble on a small base is not a story.
- What to watch — leading indicators or risks that could shape next week.
- Recommended action — exactly one concrete, specific action worth taking now, and why.
- Metrics table — a compact markdown table: metric | prior week | this week | delta | % change, one row per metric.
Do the arithmetic carefully and show correct signs and units. Do not fabricate
causes you cannot support from the data; when you are guessing, say so. Commit
the file with a message like ops: weekly metrics narrative.
README
Weekly Metrics Narrative
Turn a raw metrics.csv into the weekly update your leadership will actually
read — meaning first, numbers in support. One Claude Code agent, one Docker
sandbox, no network, no hooks.
What it does
The agent reads a metrics.csv from your repository root (one row per week, one
column per metric), computes week-over-week deltas — absolute and percentage
— for the two most recent weeks, and writes an executive narrative to
reports/weekly.md. The report leads with a headline, explains what moved and
whether that is good or bad for each metric, offers labelled hypotheses for
why it moved, flags what to watch next, and lands on exactly one
recommended action. A compact metrics table (prior week, this week, delta, %
change) sits at the bottom for anyone who wants the raw figures.
It is disciplined about signal versus noise — a 2% wobble on a small base is not treated as a story — and it never fabricates a cause it cannot support from the data.
How it works
main.ts calls run() once with maxIterations: 1 against a Sonnet agent — a
one-shot report, not a loop. Commits land directly on your current branch
through the Docker bind mount, so there is no worktree or merge step, and no
network access or shell expansion is used.
Requirements
Place a metrics.csv at your repo root (a date/week column plus one column per
metric, at least two weeks of rows), set CLAUDE_CODE_OAUTH_TOKEN in
.sandcastle/.env (run claude setup-token on your host), then build the image
once with npx @ai-hero/sandcastle docker build-image and run it with
npx tsx .sandcastle/main.ts. Read the result at reports/weekly.md.