Security-aware AI agent harness built with TypeScript and Bun.
- Agent with file tools, sandboxed execution, and per-user state
- Per-caller memory wiki (notes, skills, log) plus SQL-backed active tasks, with
/new_threadrotation and boundary-based task reconciliation - Persistent conversation state in
DATABASE_URL: full LangGraph history for audit/recovery, plus forced checkpoints that compact runtime context at/new_thread, session-resume, and prompt-budget boundaries and stay in the rebuilt system prompt until replaced - Docker sandbox today, Firecracker path where supported
- CLI and Telegram entrypoints, including Telegram photo, voice, PDF document handling, spreadsheets, scheduled timers, and one-time reminders
Requirements: bun, docker, model API access.
This repository uses Bun workspaces: bot/ for the runtime, web/ for the
embedded authenticated bot browser UI, and landing/ for the public marketing
site. Run workspace commands from the repository root.
Optimize checked-in landing and web image assets with:
bun run images:optimizeThe optimizer uses sharp, skips generated dist/ folders, and only rewrites
an image when the optimized output is meaningfully smaller. Use
bun run images:check in review flows to detect images that still need
optimization.
Database config uses DATABASE_URL only and expects PostgreSQL. Local
development uses Prisma Dev/PGlite when DATABASE_URL is unset: bot startup
starts the local database, uses the generated TCP URL, lazily runs
db:generate if the Prisma client is missing, applies committed migrations with
db:migrate, and then starts normal runtime. To run
those steps manually:
bun run db:dev
export DATABASE_URL='<postgres URL printed by db:dev>'
bun run db:migrateUse bun run db:dev:stop to stop the local PGlite server. Production should set
DATABASE_URL to the real PostgreSQL database and apply committed migrations
with bun run db:migrate. The runtime starts a Prisma client and routes user
state through a model mapped to the existing
harness_users table. sqlite://... runtime URLs are no longer supported.
AI_API_KEY may be empty when you point the app at a local/custom model
endpoint with AI_BASE_URL. AI_TYPE=openrouter still requires a key.
Agent sampling uses AI_TEMPERATURE=1.0 for the main agent and
AI_SUB_AGENT_TEMPERATURE=0.4 for delegated sub-agents by default. The lower
sub-agent default keeps research and web-search synthesis more factual while
still leaving room for query exploration.
Telegram voice messages are enabled by default. Use ENABLE_VOICE_MESSAGES=false
to disable them, TRANSCRIPTION_PROVIDER=openai|openrouter to choose the
OpenAI-compatible transcription backend, TRANSCRIPTION_API_KEY to provide a
dedicated transcription credential when voice cannot reuse AI_API_KEY, and
TRANSCRIPTION_BASE_URL to override the transcription endpoint. If
TRANSCRIPTION_PROVIDER is unset, the app defaults to openrouter when
AI_TYPE=openrouter, otherwise openai. The openai provider uses the
Audio Transcriptions API, while openrouter provider uses OpenRouter's
documented chat-completions audio input flow with the default
openai/gpt-4o-mini-transcribe model. PDF document handling is enabled by
default. Use ENABLE_PDF_DOCUMENTS=false to disable it. Spreadsheet
handling is enabled by default. Use ENABLE_SPREADSHEETS=false to disable it.
Telegram image understanding through MiniMax MCP is opt-in. Set
ENABLE_IMAGE_UNDERSTANDING=true and MINIMAX_API_KEY; optionally override
MINIMAX_API_HOST for the account region. The MCP server is launched with
uvx minimax-coding-plan-mcp -y, so uvx must be installed and available on
PATH.
Large attachment handling uses MAX_CONTEXT_WINDOW_TOKENS=150000,
CONTEXT_RESERVE_SUMMARY_TOKENS=2000,
CONTEXT_RESERVE_RECENT_TURN_TOKENS=2000, and
CONTEXT_RESERVE_NEXT_TURN_TOKENS=2000 by default; override them to tune how
much context is reserved for summaries, recent turns, and the next turn.
Telegram also emits an ephemeral attachment-compaction notice by default;
set ENABLE_ATTACHMENT_COMPACTION_NOTICE=false to disable only that notice,
not the underlying compaction behavior.
Telegram wall-clock and recurring timer requests require an explicit IANA
timezone from the request or from /memory/USER.md. Duration-only one-time
reminders like "in 30 minutes" use the Telegram message timestamp to compute a
UTC runAtUtc without asking for timezone. If a wall-clock or recurring timer
needs a timezone and it is not known, the agent asks for it and saves it to
USER.md. USER.md remains the canonical durable user profile; successful
profile writes mark the agent prompt for rebuild so the next turn sees the
updated timezone, and compaction refreshes the rebuilt prompt with active
checkpoint context. The web UI binds to WEB_HOST=127.0.0.1 by default; set
WEB_HOST explicitly if you need it reachable on another interface.
./dev.shPrisma is used for the PostgreSQL production path. Regenerate the client after schema changes from the bot workspace with:
bun run --filter goodkiddo-bot db:generateApply committed Prisma migrations to a production PostgreSQL database with:
DATABASE_URL='postgresql://...' bun run --filter goodkiddo-bot db:migrateIf config is missing, the app starts an interactive setup wizard.
New Telegram chats are automatically provisioned as free-tier users on first message. Admins can upgrade users to paid with:
bun run admin add-user telegram <chat-id> "Display name"Suspended Telegram users receive the configured blocked-user message.
Ubuntu production provisioning with PostgreSQL and a systemd bot service lives
in ops/ansible/.
That playbook installs Bun, provisions a local PostgreSQL database
and role, builds the landing and embedded bot web UI as static files, serves
the generated landing/dist/ bundle through nginx for the main hostname,
serves the app. file-share UI bundle through nginx behind Cloudflare Flexible
SSL, proxies file-share API and download routes to the Bun service, installs
the local browser/search stack (google-chrome-stable,
agent-browser, SearXNG), and runs the bot as a systemd service bound to
localhost HTTP. It is now structured around separate inventory, non-secret
vars, and Vault-backed secret vars so you do not need to edit production values
inside the playbook. It intentionally leaves Firecracker and execution sandbox
setup out for now; the example production vars keep ENABLE_EXECUTE=false.
Start with ops/README.md.
For release checks, run:
bun run check
bun run lint
bun run test
bun run typecheck
bun run images:check
bun run web:build
bun run landing:buildTo run just the frontend builds with explicit environment values:
POSTHOG_KEY=ci-public-placeholder POSTHOG_HOST=https://us.i.posthog.com bun run landing:build
bun run web:builddocs/features/— high-level feature descriptionsdocs/plans/— execution-ready RALPHEX-aligned implementation plans, consumed one task section at a time
ops/README.mdfor Ubuntu production provisioning with Ansiblebot/src/README.mdfor the code mapweb/for the embedded authenticated bot browser UI workspacebot/src/channels/README.mdfor the CLI and Telegram channels, including Telegram formatting and troubleshootingbot/src/bin/README.mdfor entrypointsbot/src/permissions/README.mdfor user access statebot/src/memory/README.mdfor the memory subsystem