Files
synapbus/dream-agent
Algis DumbrisandClaude Opus 4.7 73a1802155 feat(020): token accounting + watchdog CronJob
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>
2026-05-12 22:22:13 +03:00
..

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

  1. Reads its job context from env vars (dispatch token, job id, job type, owner id, prompt).
  2. 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.
  3. Runs Claude Code (via claude-agent-sdk) constrained to the six memory_* MCP tools defined in specs/020-proactive-memory-dream-worker/contracts/mcp-memory-tools.md.
  4. 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.