For Agents
Reverse-chronological session log. Newest entries at top, grouped by date (
## YYYY-MM-DD). Each bullet: one piece of work, short summary, wikilinks to docs touched. Updated byobsidian-documenteron every project doc write. Read byhistorianat bootstrap.
2026-07-08
@crm(Levandor CRM repo) enrolled on babylon via the HTTP/provisionflow —POST /provision {"handle":"crm"}authorized by Tailscale identity (no pre-existing token) →{handle, token}, token inlined into the repo’s gitignored 0600.mcp.jsonmerged with its existingsupabaseserver. This is the canonical enrollment path now and supersedes the crib sheet’smint-token→GH-secret→secrets/babylon.envrecipe (annotated in babylon-operator-crib-sheet). Confirms the per-repo inlined-token pattern: the repo-local.mcp.jsonshadows the plugin’s globalplugin:babylon:babylonserver, which 401s because there’s noBABYLON_TOKENOS env var (by design — one global var would collapse every repo to one identity). Tools don’t hot-load → available only after a Claude Code restart + approving the server.@crm’s first babyloncatch_upwill surface any messages sent to it since the fleet’s mid-June cutover (CRM was still on the file-basedCRM_AGENT_HANDOFF.mduntil now). — crm-babylon-onboarding-2026-07-08
2026-06-23
- Issue tracker — Task 4
Hub::update_issueshipped at pincbc9233(crates/babylon-core/src/ops/issues.rs): one entrypoint for every edit to an existing issue — close/reopen (status), reassign, reparent, title/body. DRY authz: extractedpub(crate) assert_can_resolve(by, id)out ofHub::resolveintoops/lifecycle.rs(author OR mentioned/assigned ORkind='operator');resolvenow calls it after its idempotent early-return, the close path ofupdate_issuereusesself.resolve, the reopen path reusesassert_can_resolvedirectly — no duplicated authz. Status semantics:closed→resolve(setsresolved_at/by); any non-closed status →assert_can_resolve→clear_resolved(NULLs bothresolved_atANDresolved_by) →set_issue_status(reopen re-checks authz + clears resolution). Reassign replaces (not appends):reassign_issueDELETEs allmessage_mentionsfor the msg, INSERTs the one new assignee, upsertssubscriptions(ON CONFLICT(handle,channel_id) DO UPDATE SET active=1,last_acked_id=msg_id-1so they see it on next catch-up), thenself.waiters.wake(&assignee). Cycle guard:assert_no_cyclewalks parent chain up viaSELECT parent_id FROM issues WHERE message_id=?→Error::IssueCycleif it reaches the issue being reparented. Empty update (all fields None) →Error::TooLarge("update needs at least one field"). Clippy gotcha: brief’s verbatim_numwas then USED informat!→used_underscore_binding+uninlined_format_argsfired; fix = rename_num→num+ inlineformat!("#{prefix}-{num}")(underscore prefix = intentionally-unused; if you use it, drop the underscore — don’t#[allow]); the orphanedauthorbinding left by the authz move was renamed_author. Known wart:assert_can_resolvere-queries the message row, duplicating the kind/dm-can_seecheckresolvealready did → one redundant read on the resolve path, kept self-contained so reopen can call it standalone (behavior-preserving). Setter helpers (set_issue_status/clear_resolved/reassign_issue/set_issue_parent/set_message_field) are private, each onewith_writertxn. 4 newops::issuestests (close→reopen clears, close authz rejects outsider, reassign replaces + reparent blocks cycle, no-fields errors); full suite 54 passing, clippy-clean — babylon-issue-tracker-update-issue
2026-06-12
- Auto-notify refinements (plugin pin
b2a4934): documented the token cost split — the Stop/SessionStart turn-boundary hook is token-cheap (runs as a subprocess; only spends LLM tokens when there’s a real item →decision:block→ auto-act) and is the ambient default, whereas/babylon:watchis costly (model-drivenwait_forloop, each poll = a full model turn) so it’s for short active waits only, NOT a standing daemon; watchwait_fortimeout bumped 25s→50s and watch.md/SKILL.md now say “use sparingly; the Stop hook is the ambient default.” Captured the idle-agent gap: neither path notifies a session that is idle (no turns → Stop hook never fires) AND not running a live watch — real hit was @deploy missing server pin2960f2a(watch not live + idle session). Corrected a common misconception:dm(to=X)DOES register amessage_mention(messages.rs inserts the recipient), so a DM does wake a liveonly_mentionswatcher / show in the hook’scatch_up(only_mentions:true)— the miss was liveness, not mentions; only zero-idle-cost fix is external push (server→webhook), still deferred. Ratified the redeploy-signaling fleet convention via a#generaldecision: on shipping a deployable pin, post a durabletask@mentioning@deploy(durable + wakes live watcher); CI repos should prefer push-to-deploy (git push → GH Action → bounce — the push IS the signal, no need to ping idle @deploy) — babylon-auto-notify - Dashboard chat / message-history feature shipped in server pin
2960f2a: god-view conversations + DM threads + send as@operator+ 4s poll; @deploy is deploying it (same pin that surfaced the idle-agent gap) — babylon-owner-dashboard - Auto-notify SHIPPED (client-side plugin pin
c97a330, no server redeploy — agents get it via/plugin update babylon+ restart): each agent auto-learns of + auto-handles babylon items addressed to it, no manualcatch_up. Two paths: (1) turn-boundary hook —plugin/hooks/hooks.jsonwiresSessionStart+Stop(timeout 15000ms) tohooks/babylon_notify.sh, which reads hook JSON from stdin → ifstop_hook_activeexits 0 (loop guard), else runsscripts/babylon_unread.pyand on unread emits{"decision":"block","reason":"🔔 …auto-act…"}onStop(continues the agent to handle it) or{"hookSpecificOutput":{additionalContext}}onSessionStart; loop-safe because the auto-act sweepacks → server cursor advances → next check empty, plus thestop_hook_activeguard. (2)/babylon:watch— foregroundwait_for({only_mentions:true,timeout_secs:25})long-poll → read+auto-act+ack, repeat until interrupted (near-real-time). Query scriptplugin/scripts/babylon_unread.py= self-contained Python3 stdlib-only MCP-over-Streamable-HTTP client (hooks run as separate processes w/ no access to the session’s MCP connection); token/URL from$BABYLON_TOKENor repo.mcp.json(.mcpServers.babylon.{url,headers.Authorization}); does initialize → notifications/initialized →catch_up({only_mentions:true}), parses SSEdata:frames, prints<ch> · <kind> · @<from> · <sum>(cap 10) or nothing,--jsonmode too; fail-safe — any error/timeout prints nothing + exits 0, never breaks the turn, 10s socket timeout. Auto-act protocol (SKILL.md, coordination ONLY):question→answer-from-context viapost(kind:answer,reply_to)(auto-resolves) or surface;task→post “on it”, do work only via normal visible flow, thenresolve;dm/note/decision/status→read+ack; ALWAYS ack (also clears the hook); NEVER touch code/files/infra or send outbound autonomously. Corrected the “parked” status in babylon-owner-dashboard — babylon-auto-notify
2026-06-11
- Owner dashboard (
/dashboard) shipped in server pin240e544: owner-only web console served bybabylon-serverover the tailnet (https://babylon.taild4189d.ts.net/dashboard), gated byTailscale-User-Login == BABYLON_OWNER_LOGIN. Endpoints (all owner-gated at a fail-closed router-layer middleware):GET /dashboard+/dashboard/app.{js,css};GET /api/overview(agents+presence, channels w/ member+message counts + last-activity, open questions/tasks, global stats, DB size, git pin, health);POST /api/tokens/{mint,rotate,revoke};POST /api/channels+/api/channels/{name}/archive;POST /api/messages— channel/message writes run server-side as a special@operatorhandle. Security (ambient browser auth ⇒ needs CSRF + rebind defenses): double-submit CSRF (babylon_csrfcookieSameSite=Strict;Secure+<meta>token, echoed inX-Babylon-CSRF, constant-time compare), Origin check, Host allowlist (reusesBABYLON_ALLOWED_HOSTS); XSS-hardened (alltextContent/createElement, neverinnerHTML(test-asserted), strict CSPdefault-src 'none',X-Frame-Options: DENY); tokens shown once + never logged; DB errors generic 500;Cache-Control: no-storeon/api;dm:excluded at SQL layer + rejected as write target. Security-reviewed → SHIP, 15 integration tests. babylon-core:ensure_operator()(idempotent unusable-token operator agent),admin_channels()+global_stats()(dm:-excluded),revoke_token→UnknownHandle/404. Addedbabylon-server/build.rsemittingGIT_SHA(surfaced in/api/overview). Theme: pure-CSS ancient-Babylon/parchment (Ishtar lapis/gold/terracotta, CSP-clean). No new env; needstailscale serveto inject the login header; handed to @deploy via AGENT_HANDOFF Round 101 — babylon-owner-dashboard
2026-06-09
- Per-project agent identity & the plugin-cache token clash: on one machine every Claude Code session authed to babylon as the SAME handle (
deploy) regardless of repo —registerreturned{"handle":"deploy"}in a session meant to bebabylon/code. Root cause: the installed plugin cache~/.claude/plugins/cache/babylon/babylon/<version>/.mcp.jsonhad a hardcodedBearer bbln_<deploy-token>instead ofBearer ${BABYLON_TOKEN}(baked in during a deploy-side test); the marketplace SOURCE copy was correct. A hardcoded cache token = one global identity for the whole box. Captured the Claude Code mechanics:${VAR}in.mcp.jsonreads ONLY the OS/shell env ofclaudeat launch (settingsenvdoes NOT feed it ⇒ no per-project token via settings); MCP precedence local > project.mcp.json> user~/.claude.json> plugin > connectors (highest wins entirely, no field merge); plugins de-duped by endpoint/URL so a project.mcp.jsonat the same URL shadows the plugin server; cache hand-edits survive/reload-plugins+ restart but are OVERWRITTEN on/plugin update/reinstall. Recommended pattern: one gitignored 0600 per-repo.mcp.jsonwith the repo’s token inlined (shadows the plugin’s global server → deterministic per-agent identity, no env vars/direnv). Gotcha: such a token file must be gitignored —polymarket_fetchIS a git repo (session banner wrongly said it wasn’t) and the file was briefly un-ignored; added.mcp.json+.babylon.envto.gitignore. Security follow-up: rotate any cache/transcript-exposed token viababylon-server rotate-token --handle <h>. Improvement:/babylon:initshould emit a per-project gitignored.mcp.jsoninstead of the fragile.babylon.envenv-var approach — babylon-per-project-identity-plugin-cache-clash - Fixed
/mcp403 behindtailscale serve: rmcp 1.7StreamableHttpServiceruns a DNS-rebindingHost-header check first (validate_dns_rebinding_headers), andallowed_hostsdefaults to loopback-only — so the tailnet name was rejected before auth (health/readyz/provision are plain axum routes, hence only/mcpgated). AddedBABYLON_ALLOWED_HOSTS(CSV/TOML viade_csv_vec) wired inserve.rs:*⇒disable_allowed_hosts(), non-empty ⇒ extend loopback defaults viawith_allowed_hosts, empty ⇒ unchanged. Regression-tested viareqwest ...builder().resolve(tailnet_name, 127.0.0.1:PORT)(Host = tailnet name, socket = loopback): positiveregisterhandshake + negative 403. Pin164b49d, 50/50 tests, clippy/fmt clean. Handed to @deploy (Round 98) to redeploy + setBABYLON_ALLOWED_HOSTS=babylon.taild4189d.ts.net; unblocks Phase 1/babylon:init code— babylon-403-host-check - Migration coordination — Phase 0 done, Phase 1 in progress: documented the 4-phase cutover from
polymarket_fetch/AGENT_HANDOFF.md(~4400 lines / ~80K tokens) to babylon. Phase 1 pilot =deploy+operator; Phase 2 =code+hormuz; Phase 3 =weather+cryptoshort+bscvuln; Phase 4 = archive handoff + watcher off. Captured @babylon’s Q1-Q6 answers: per-repo.mcp.json(not user-scope — would collapse all sessions to one handle), channel scheme (handoffauto-seeded;main; per-streamdeploy/pm/weather/crypto/hormuz/bsc;dm:reserved), fresh-slate history import (carry-over items refiled astasks — weather telemetry redeployce4109d, crypto rustls pin), and belt-and-suspenders cutover signal (decisionto#main+## CUTOVERannotation in handoff). Protocol summary pulled fromwowjeeez/babylonREADME @2990d5b+skill/SKILL.md@0fd9cea(16 tools, 6 message types, SHA-256-hashed bearer tokens, per-agent cursors,wait_forlong-poll, limits: 256 global concurrency / 512 KB body / 2 concurrentwait_forper handle / ≤50s timeout,/healthz+/readyzunauth and/readyzexercises the writer). Per-agent install steps (per-repo.mcp.json,secrets/babylon.env0600, skill install pinned to0fd9cea, macOS MagicDNS workaround via/etc/hosts, smoke check,register/catch_up/open_questions/open_tasks) split into a separate crib sheet for non-infra agents. Added cross-link in babylon-deploy-notes — babylon-migration, babylon-operator-crib-sheet - Babylon DEPLOYED to
polymarket-infraGCP VM (pin4fe5cdb): newbabylonAnsible role +deploy-babylon.ymlworkflow in the terraform repo, binary at/opt/babylon/repo/target/release/babylon-server, SQLite at/var/lib/babylon/babylon.db, exposed on the tailnet viatailscale serve --bg http://127.0.0.1:8787, continuous WAL backup to GCS bucketpolymarket-infra-babylon-backupvia Litestream (GCE-native ADC, no SA JSON key needed). 7 agent tokens minted (code/weather/hormuz/cryptoshort/bscvuln/deploy/operator) and distributed via per-repo GH Actions secrets. Operational/host-layout details and the reusable patterns (Litestream + GCE ADC, idempotenttailscale serve, babylon-server CLI surface) live in the levandor-infra project — babylon-deploy-notes - Code audit + hardening: 4-lens adversarial audit verified core SOUND; fixed 3 CRITICAL + 9 HIGH + cheap MEDIUM (each with a regression test), 43 tests green; captured 6 generalizable design lessons + deferred backlog (M4/M5/M6 + ~7 LOW); local commits
ee81e76+4fe5cdb, not pushed — Code audit + hardening (2026-06-09) - v1 implementation COMPLETE & green: 4 crates (core/mcp/server/cli), 16 tools, 29 tests, fmt/clippy/build clean — every audit finding encoded as a passing test — v1 Build Complete (2026-06-09)
- Captured the hard-won rmcp 1.7 gotchas (object-root outputSchema, rustc ≥ 1.88 floor, schemars 1.0, server/client wiring, figment bool coercion, sqlx in-memory PID temp file) — v1 Build Complete (2026-06-09)
- Updated design-decisions §9 to note the 4th crate (
babylon-cli) + 1.88 toolchain bump — babylon-design-decisions
2026-06-08
- Initialized activity log.
- Documented the new babylon project: purpose, architecture, crate layout, tech stack — babylon
- Captured all 10 design decisions + rationale and the
tailscale-rsembedded-node gotcha — babylon-design-decisions
2026-06-15
Newest-at-top convention degraded for these entries
These were appended (not inserted at top) because in-place edit tooling was unavailable in this session. Chronologically this is the newest date — read it as the latest activity despite its position at the end of the file.
- KB reconcile sweep to current state (HEAD origin/main
2f6a6eb,wowjeeez/babylon). Documented the dashboard timestamp fix (pin2f6a6eb):fmtAge()incrates/babylon-server/assets/app.jscomparednowin seconds against server timestamps in ms (created_at/last_seen/last_activity_ts=now_ms), so theif(d<0)d=0guard clamped every age to “0s ago”; fix =var d = now - Math.floor(ts/1000)(the!ts → "never"early-return already handled never-seen). Deployed + verified live. Recorded the timestamp unit convention (all server ts are epoch ms) — babylon-owner-dashboard - Corrected babylon-owner-dashboard: the chat / message-history feature (pin
2960f2a) is no longer “@deploy is deploying it” — it is LIVE & verified end-to-end from the tailnet (/dashboard200,/api/conversations200,/api/history200; god-view lists all DM threads). Confirmed server surface against the repo:god.rschannel_history(channel,before,limit)(oldest→newest, body+mentions,beforecursor, god-view readsdm:w/o membership) +conversations()(no membership filter); HTTPGET /api/conversations,GET /api/history?channel=&before=&limit=,POST /api/messagesextended to allowdm:targets (operator auto-subscribes). Gotcha: DM?channel=value must beencodeURIComponent-encoded (dm:a+b→dm:a%2Bb) — babylon-owner-dashboard - Added a current-state snapshot to the babylon index (the page a future session reads first): supersedes the stale build-era “not pushed / not deployed” status; carries the full shipped-pin history (
2f6a6eb→164b49d), the fleet identity map (babylon←~/coding/babylon, deploy←~/levandor/terraform, polymarket←~/coding/polymarket_fetch, hormuz_bet←~/coding/hormuz_bet; per-repo gitignored 0600.mcp.jsonw/ inlined token), the live URLhttps://babylon.taild4189d.ts.net/dashboard, thedeploytoken rotation (rotate-token --handle deploy), and the milestone that coordination has moved onto the hub itself (agents now register/catch_up/dm/post/wait_for via babylon tools instead ofAGENT_HANDOFF.md) — babylon