DECISIONS — Aquila POC

Living log. Every locked decision, branch, and reversal is appended here with date + rationale. Never delete a decision; supersede it with a new entry that references the old one.

Convention: D### — Title · Status: locked | pending | superseded by D### · Date: YYYY-MM-DD


D001 — Project name is Aquila

Status: locked · Date: 2026-09-16

Named after the Aquila constellation (the eagle; carries Zeus's thunderbolt in myth). Chosen for: NASA-style celestial naming, two syllables, easy to pronounce across India, new-age without being generic. Earlier candidate "TARA" (Task & Reminder Agent / तारा = star) was retired in favour of Aquila.

Rationale: Bhavya owns exaflair.com; we get a subdomain delegation without touching existing Vercel CNAMEs, MX records, or other services.


D002 — POC runtime environment

Status: locked · Date: 2026-09-16


D003 — Bot runtime: build our own minimal runtime (core/)

Status: locked · Date: 2026-09-16

Options compared (full comparison at docs/comparisons/runtime.html):

Option Verdict
Build minimal own runtime CHOSEN. ~600-line FastAPI service: persona, chat loop, SQLite memory, APScheduler routines, LiteLLM client, tiny tool layer. ~100–150MB idle RAM. Full control, zero licensing surprises, clean metering hooks.
Port OpenMausBot Rejected: Mac-first days-old app, rides desktop CLI logins (can't ship to customers), Linux build "coming". Kept as UX reference only.
Adopt nanobot (HKUDS) Kept as reference codebase + fallback if our build slips.
LimeBot-OS / Daemora / OpenClaw Rejected: RAM footprint (Chromium/LanceDB/Electron-class), unofficial WhatsApp bridges, or AGPL constraints kill the ₹250 economics.

The user (Bhavya) reviewed the comparison HTML and chose the build path.


D004 — Channels: Telegram + web microsites now; WhatsApp later

Status: locked · Date: 2026-09-16

One Aquila Telegram bot multiplexes all personas (persona-attributed messages). Per-bot Telegram identities need manual BotFather work per bot — deferred; Vestra does the same with one "Bash" bot.


D005 — Inference gateway: LiteLLM proxy, per-bot virtual keys

Status: locked · Date: 2026-09-16


D006 — Identity: phone number = account; Telegram OTP

Status: locked · Date: 2026-09-16


D007 — Static site + CI: mirrored from ~/exa/design-context

Status: locked · Date: 2026-09-16


D008 — POC personas (and the wider catalog)

Status: locked · Date: 2026-09-16

Ship 3 personas + baseline companion:

  1. Inbox Munshi — IMAP sweep → Hinglish digest on Telegram → drafted replies to copy. (Founding call: "the killer app is email".) Tool: IMAP read.
  2. Udaan — daily 7:30am one-idea briefing for the owner's trade/city. Tool: web search (optional).
  3. DaamDekho — watches named product pages 2×/day, alerts on price drops. Tool: web fetch.

Full 18-bot catalog with ranking, packaging and trap list: docs/killer-apps.md (from the killer-apps research subagent, 2026-09-16).

Safety posture (locked): detect → summarize in Hinglish → deliver on Telegram → draft the reply for the human to send. The bot never sends email, never touches money, never files anything. Traps explicitly out: WhatsApp auto-reply, GST filing, auto-payments, government-portal automation, bank scraping. See docs/killer-apps.md §4.


D009 — Success metrics for the POC

Status: locked · Date: 2026-09-16

Bhavya: "use your recommended baseline, adjust as we go."


D010 — Build process: docs-first, orchestrator, reviewed deliveries

Status: locked · Date: 2026-09-16


D011 — Repo layout (monorepo)

Status: locked · Date: 2026-09-16

core/            # WS1 — per-bot runtime container
gateway/         # WS2 — LiteLLM proxy config + metering API
manager/         # WS3 — FastAPI control plane + microsites (Jinja, no JS build)
channels/telegram/  # WS4 — Aquila Telegram bot: OTP, chat multiplex, delivery
deploy/          # orchestrator — compose stack, relay/Caddy, k8s mapping notes
site/ brand/ docs/ scripts/  # WS5 — static homepage + docs (build → public/)
e2e/             # orchestrator — smoke tests + density harness
opencode.json, .opencode/agent/  # agent config (DeepSeek Flash, max effort)

D012 — Open decisions (pending, tracked)

Status: pending · Date: 2026-09-16 · Updated: 2026-09-16 evening


Day-1 workstream deliveries (renumbered from agent proposals — numbering is orchestrator-owned)

D013 — Bot memory: SQLite WAL + FTS5 recall + rule-based facts

Status: locked · Date: 2026-09-16 · Source: WS1

No embeddings, no extra deps. Facts extracted by deterministic heuristics ("yaad rakho:" / name / business / city patterns). Recall + facts injected into the system prompt char-budgeted at 8k chars (≈2k tokens). imap_last_uid watermark lives in a state kv table so read-only IMAP behaves as "since last run". Rationale: zero marginal cost, works offline, and the POC's economics depend on prompt size discipline.

D014 — Tool layer: exactly four tools via OpenAI function-calling loop

Status: locked · Date: 2026-09-16 · Source: WS1

web_search (SearXNG), web_fetch (stdlib HTML strip + SSRF guard: http/https, ports 80/443, no private/loopback), imap_read (imapclient, read-only, BODY.PEEK[], UID watermark — never sends, never marks read), calculator (AST sandbox, no eval). Max 4 tool rounds; results truncated to 4k chars. Tool calls are logged and folded into text (not emitted as SSE events — stream shape stays frozen {delta}/{done}).

D015 — Bot admin surface requires the shared internal token

Status: locked · Date: 2026-09-16 · Source: WS1

PUT /persona and POST /routine/{name}/run require X-Internal-Token (or bearer) when AQUILA_INTERNAL_TOKEN is set. /chat stays open only on the isolated compose network. Manager and telegram send the header.

D016 — Image-size metric convention

Status: locked · Date: 2026-09-16 · Source: WS1

Report both on-disk and registry-compressed sizes plus idle RAM. python:3.11-slim baseline (~200MB disk / ~49MB compressed) is the floor for all workstreams; the earlier ≤180MB-disk target is dropped as unreachable. Idle RAM targets stand (bot ≤250MB; achieved ~39–55MB).

D017 — Routine delivery contract

Status: locked · Date: 2026-09-16 · Source: WS1

Core POSTs {"bot_id","routine","text"} to $DELIVERY_WEBHOOK with optional X-Internal-Token; unset webhook = log-only (dev mode). Manager exposes the receiver and forwards to telegram /internal/send.

D018 — Gateway metering: LiteLLM custom_auth + CustomLogger, not native virtual keys

Status: locked · Date: 2026-09-16 · Source: WS2

LiteLLM's native virtual keys/budgets require Postgres (absent in the POC). LiteLLM stays the routing engine; general_settings.custom_auth validates BOT_LLM_KEY against gateway.db, and a custom logger parses standard_logging_object into gateway.db + spend.jsonl. Native keys can be adopted when Postgres arrives (k8s phase).

D019 — Upstream selection is rendered at container start

Status: locked · Date: 2026-09-16 · Source: WS2

config.yaml carries env refs; deployments whose aquila_requires env is absent are pruned and OpenRouter free models are promoted into aquila-default / aquila-cheap — absence of credentials means zero failed attempts, no wasted latency. Effective config inspectable at gateway/data/effective_config.yaml.

D020 — Budgets are calendar-month IST with community FX (USD_INR, default 88)

Status: locked · Date: 2026-09-16 · Source: WS2

Exhaustion → 429 + Retry-After (until next IST month start) + X-Aquila-Budget-* headers; core surfaces the Hinglish message. DB failure = fail-closed 503. Spend rows carry precomputed IST month (no counter races).

D021 — Manager owns bot LLM key lifecycle

Status: locked · Date: 2026-09-16 · Source: WS3

POST /api/bots mints the gateway virtual key (BOT_BUDGET_INR), stores it on the bot row; delete revokes it. Gateway admin endpoints are internal-token-only, never exposed to users.

D022 — Infra failure semantics for bot CRUD

Status: locked · Date: 2026-09-16 · Source: WS3

Never 5xx for infra hiccups: persist status:"error" + Hinglish message, retryable via Start. Delete = soft-delete row (audit) + container/volume/key teardown.

D023 — Persona seeding contract

Status: locked · Date: 2026-09-16 · Source: WS3

Manager passes PERSONA_KEY + PERSONA_FILE=/data/persona.md; core seeds from its bundled catalog on first boot; persona read/write proxied through manager (PUT admin-only per D015). PERSONA_KEY is an additive env beyond the frozen §2.2 list.

D024 — Manager ships a mirrored brand token file

Status: locked · Date: 2026-09-16 · Source: WS3

manager/app/static/tokens.css mirrors brand/tokens.css because the image build context is manager/. brand/tokens.css remains source of truth; drift risk accepted for POC (revisit with a build step).

D025 — Session cookie: per-subdomain, HMAC-hashed at rest, DB-revocable

Status: locked · Date: 2026-09-16 · Source: WS3

Random token, HMAC(session_secret) stored; HttpOnly SameSite=Lax; COOKIE_SECURE flips Secure behind the relay. Sessions revocable server-side.

D026 — Telegram via raw Bot API over httpx (no bot framework)

Status: locked · Date: 2026-09-16 · Source: WS4

~10 API calls needed; smaller image (225MB), uvicorn-native, offline-testable with fakes.

D027 — Signed login nonces minted by telegram /internal/deeplink

Status: locked · Date: 2026-09-16 · Source: WS4

v1-<phone>-<exp>-<hmac16> (fits Telegram's 64-char start payload); contact verification offline, opaque nonces resolved via manager GET /internal/otp/challenge/{nonce} as fallback.

D028 — Chat reply protocol: SSE stream → single-message edit with throttle

Status: locked · Date: 2026-09-16 · Source: WS4

Manager streams SSE {delta}{done}; telegram edits one placeholder message (1.5s throttle), splits finals at 4000 chars; Hinglish fallbacks for 429/404/errors.

D029 — Telegram webhook secret derivation

Status: locked · Date: 2026-09-16 · Source: WS4

TELEGRAM_WEBHOOK_SECRET defaults to sha256(AQUILA_INTERNAL_TOKEN)[:32]; set_webhook.sh derives it identically. Webhook endpoint always 200s (background dispatch) to avoid Telegram retries storms.

D030 — Homepage v2

Status: locked · Date: 2026-09-16 · Source: WS5

Single-file, mobile-first, honesty-first landing: hero / how / personas / day / pricing (Shuruaat 250, Dhanda 299, Ustaad 499) / safety never-always / FAQ / CTA; one CTA intent "Find my star"; chat preview labelled as sample; no raster assets; reduced-motion + no-JS fallbacks; verified in headless Chrome at 1440 and 390.

D031 — Brand token v2

Status: locked · Date: 2026-09-16 · Source: WS5

Gold = action accent only, constellation blue = links only; em-dash ban in copy; new semantic, focus, elevation and motion tokens; light-theme AA contrast fixes (#8A6008 gold, #5B6580 faint).

D032 — Relay runs as an additive guest on bengaluru-svc

Status: locked · Date: 2026-09-16 · Source: orchestrator recon

The relay VPS is a live k3s-agent node of the deklo cluster. Rules: static Caddy binary + systemd unit (no apt repo changes), only additive ufw rules if required (allow 80,443 — never touching existing SSH/mesh rules), no k3s workloads touched, resource ceiling ~200MB. Any change that could affect the cluster stops and goes back to the team first.

D033 — OpenRouter free-tier reality + the tool-support constraint

Status: locked · Date: 2026-09-16 · Source: orchestrator live probe

Discovered while bringing the stack up (all verified against the live catalog, 2026-09-16):

Proof (live, 2026-09-16): bot container aquila-bot-day1check (persona=nova) → gateway (auth + ₹ budget + spend attribution) → OpenRouter → streamed Hinglish reply over SSE; spend rows attributed per model (cohere/north-mini-code:free, nemotron-3-super... transient 502 from Nvidia during probing), tokens metered correctly.

D034 — Persona naming v2: celestial backronyms (supersedes D008 display names)

Status: locked · Date: 2026-09-16 · Source: Bhavya

The founding-call register (munshi/udaad/saathi) reads dated to Bhavya; product personas are now celestial + backronyms, matching the Aquila brand (stars that work while you sleep):

Key Name Backronym Job
vega VEGA Virtual Email Guardian Assistant Inbox digest + reply drafts (was Inbox Munshi)
nova NOVA Notifications Of Valuable Advice Daily 7:30am business idea (was Udaan)
pulsar PULSAR Price Updates & Low-price Signals, Alert Radar Price watch 2×/day (was DaamDekho)
sirius SIRIUS Smart Intelligent Reminder & Insights Utility System Baseline companion (was Saathi)

Applied across core/personas/* (files renamed), manager/app/personas.py, site cards (each card shows the backronym), tests, briefs and docs. The wider killer-apps catalog keeps its Hindi archetype names until promoted to products; POC personas are the four above. Follow-up naming pattern for new bots: constellation/star + backronym (e.g., ORION, LYRA, ATLAS).

D035 — Voice: English only, classy. No Hinglish anywhere user-visible

Status: locked · Date: 2026-09-16 · Source: Bhavya

"Indian doesn't mean Hinglish and clumsy." All user-facing copy — personas, Telegram messages, manager microcopy and errors, site, docs — is English, plain and warm. No Hinglish, no em/en dashes (brand rule), no decorative emoji beyond the brand star. Persona bodies carry language: english. The "yaad rakho" pattern is tolerated as input recognition only, never as output. Tests no longer pin copy strings verbatim where avoidable (L04).

Scope applied: 4 personas rewritten, Telegram copy module rewritten, 30+ manager strings and templates translated, core budget/error messages translated; all suites re-run green (the six suites that pinned old strings were updated as part of the pass).

D036 — Agentic setup v2: expert skills, reviewer/manager agents, learning loop

Status: locked · Date: 2026-09-16 · Source: Bhavya ("install reputed skills… keep improving autonomously")

D037 — Per-user credentials live in an encrypted connections table, never in .env

Status: locked · Date: 2026-09-16 · Source: Bhavya's scale-alignment question

.env holds platform secrets (Telegram token, LLM provider keys) and, for the POC, ONE demo tenant's mailbox so the VEGA flow is demonstrable immediately. The product pattern is per-user: connections(user_id, provider, secret_encrypted, meta) with AES-GCM at rest under AQUILA_ENCRYPTION_KEY (KMS envelope encryption in production). Production migrates Gmail to OAuth (gmail.readonly, refresh tokens encrypted the same way); app passwords remain the fallback for non-Gmail IMAP. Full architecture: docs/multiuser.md.

D038 — Bot credential injection: env at spawn (POC) → short-lived fetch at boot (target)

Status: locked (POC shortcut recorded) · Date: 2026-09-16

POC: manager decrypts the owner's connection and passes IMAP_* into the bot container env at spawn (visible to docker inspect; acceptable inside the isolated compose network for the POC). Target: bot authenticates to the manager with its own token at boot and fetches a short-lived credential; secrets never land in container config. Recorded so the reviewer can flag regressions.

D039 — Control-plane DB path: SQLite now, Postgres (CNPG) next — same schema

Status: locked · Date: 2026-09-16

POC runs SQLite WAL (adequate to ~100 bots/box, zero ops). Scale target is the platform's existing CNPG Postgres, with Redis for ephemeral state (OTP/rate limits/session cache) and Garage S3 for artifacts. Schema is written SQL-first with a versioned migration runner so the driver swap is not a redesign.

D040 — Onboarding is phone-first, then connect-services, then pack selection

Status: locked · Date: 2026-09-16

  1. <phone> microsite → 2) Telegram OTP sign-in → 3) connect inbox (OAuth, skippable) →
  2. pick a pack (Dukaan/Vyapari/Service/Firm) → 5) bots provisioned with the owner's connections →
  3. billing via UPI (post-POC; gateway budgets already enforced). Two minutes, no app, no password.

D041 — Manager UI v2 + favicon; verification harness rules

Status: locked · Date: 2026-09-16 · Source: ui-polish v2 delivery

Manager microsite UI v2: fluid type scale, locked card/input/button shapes, budget bars, empty and error states, tabular numerics — DOM contracts with app.js unchanged, all copy English, tests green. Favicon: night-sky star mark at site/favicon.svg, linked from the homepage, docs template, and both comparison pages. Verification harness rules (L10): restart servers before screenshots, pin themes, kill by PID never by pkill -f pattern.

D042 — Live launch path: tunnel from dev machine first, node-b2 next

Status: locked · Date: 2026-09-16 · Source: orchestrator

Wildcard DNS is live (*.aquila.exaflair.com → relay VPS). The relay's Caddy reaches the manager through a reverse SSH tunnel opened by the host running the stack. Sequence: (1) live today via a tunnel from the dev machine using Bhavya's exaflair key (no new keys); (2) moves to node-b2 (spare laptop) when its SSH key is authorized there; the tunnel becomes an autossh systemd unit on the laptop with restricted permitlisten. Public TLS is on-demand per hostname via Caddy → manager /internal/tls-ask (digits-only subdomain allowlist).