Token-usage wiring (closes the 0-tokens gap in memory_dream_usage):
- internal/harness/k8sjob/k8sjob.go: after extractResultJSON, parse
tokens_in/tokens_out/tokens_cached/cost_usd from the final JSON
envelope and stash into ExecResult.Usage so the dream worker's
UsageGate circuit breaker actually counts consumption.
- dream-agent/dream_runner.py: Max20 OAuth sessions don't surface
per-call tokens through the SDK's ResultMessage.usage. Falls back
to a turn-based estimate so the gate has SOME signal:
tokens_in_est = turns * 5000 + tool_calls * 2000
tokens_out_est = turns * 300
Calibrated against observed reflection runs.
Watchdog (deploy/kubic/watchdog/):
- watchdog.yaml: in-cluster CronJob runs every hour at :05 past UTC,
with a dedicated ServiceAccount + Role granting (get/list/exec on
pods, patch+update on deployments/scale) inside the synapbus
namespace only.
- Health checks: pod readiness + restart count; last-1h job
succ/fail/in_flight counts; today's jobs_started + tokens_in +
circuit_broken.
- Red flags that auto-stop synapbus (scale to 0):
* pod restart count > 3
* failed dream jobs in last 1h > 20
* jobs_started today > 200 OR tokens_in > 30M
* circuit broke AND still firing (started >> completed)
- Dockerfile: slim alpine + kubectl v1.30.5 binary (synapbus-watchdog:v1).
Built locally and imported into kubic's containerd because the
public docker.io/bitnami/kubectl manifest was returning text/html
from kubic's network egress.
Replaces the schedule-skill remote-agent approach because Anthropic
cloud agents can't reach kubic.home.arpa (LAN-only) and can't call
kubectl scale. The k8s CronJob is the right primitive for an
in-cluster safety watchdog.
Live evidence: first manual run on kubic reported
pod=synapbus-... ready=true restarts=0
last_1h jobs total=18 succ=18 fail=0 in_flight=0
today: jobs_started=189 tokens_in=0 succeeded=169 failed=15
HEALTHY — no action
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
synapbus-dream-agent
A slim Python container that performs memory consolidation for
SynapBus, dispatched on demand by the in-server ConsolidatorWorker
via the k8sjob harness backend.
What it does
- Reads its job context from env vars (dispatch token, job id, job type, owner id, prompt).
- Connects to SynapBus's MCP endpoint over streamable-http, passing
the agent's API key (
Authorization: Bearer ...) and the dispatch token (X-Synapbus-Dispatch-Token: ...) on every request. - Runs Claude Code (via
claude-agent-sdk) constrained to the sixmemory_*MCP tools defined inspecs/020-proactive-memory-dream-worker/contracts/mcp-memory-tools.md. - Streams structured JSON logs to stdout (Loki-friendly) and emits a
final
{"final": true, ...}envelope so the harness can parse Usage.
How the worker invokes it
internal/messaging/consolidator.go builds an HarnessExecRequest
with:
| Env var | Set by |
|---|---|
SYNAPBUS_DISPATCH_TOKEN |
ConsolidatorWorker |
SYNAPBUS_CONSOLIDATION_JOB_ID |
ConsolidatorWorker |
SYNAPBUS_JOB_TYPE |
ConsolidatorWorker |
SYNAPBUS_OWNER_ID |
ConsolidatorWorker |
SYNAPBUS_DREAM_PROMPT |
ConsolidatorWorker |
SYNAPBUS_RUN_ID |
k8sjob harness |
SYNAPBUS_URL, SYNAPBUS_API_KEY, ANTHROPIC_API_KEY |
Pod spec / Secret |
Build
docker buildx build --platform=linux/amd64 \
-t kubic.home.arpa:32000/synapbus-dream-agent:v0.1.0 \
--load /Users/user/repos/synapbus/dream-agent/
Push:
docker push kubic.home.arpa:32000/synapbus-dream-agent:v0.1.0
Local smoke test
The --mock flag validates the env contract and exits without
invoking the SDK or hitting the network:
SYNAPBUS_URL=http://localhost:8080 \
SYNAPBUS_API_KEY=fake \
SYNAPBUS_DISPATCH_TOKEN=fake \
SYNAPBUS_CONSOLIDATION_JOB_ID=1 \
SYNAPBUS_JOB_TYPE=reflection \
SYNAPBUS_OWNER_ID=algis \
SYNAPBUS_DREAM_PROMPT="test" \
SYNAPBUS_RUN_ID=r-test \
python3 dream_runner.py --mock
Deploy
See k8s-job-template.yaml. The harness clones the template and
overlays req.Env into containers[0].env.