Skip to content

Drift — where AGENTS.md and the code disagree (code wins)·

# AGENTS.md says Code says Consequence for the template
1 Worker router lists ~13 endpoints (upsert/get/retrieve/list/community/profile/share/legal/user/credential) src/index.ts registers 121 endpoints across 46 modules + cron + email + Durable Object Parity ledger is built from index.ts, not the doc
2 Storage: "Vectorize: VECTOR_INDEX \"tiny\"" wrangler.toml: index_name = "tiny-v2" (binding VECTOR_INDEX), plus MEMORY → memory Bootstrap script must create tiny-v2-shaped index, name parametrized
3 KV namespaces: tiny, post, applause, stats toml also binds tiny_old; applause and tiny_old have 0 references in src/ Candidates for removal in template (verify with tests before dropping)
4 Secrets: OPENAI_API_KEY, INTERNAL_API_KEY, CLOUDFLARE_API_TOKEN src also reads DEPOSIT_ADDRESS, VAPID_{PUBLIC,PRIVATE}_KEY, VAPID_SUBJECT, MODEL_CONFIG_ENC_KEY, BASE_RPC_URL, BASE_SEPOLIA_RPC_URL, RECONCILE_ALARM_USER All go in .env.example + wrangler.toml secrets doc
5 "no Node-only APIs in app/api/*" (edge) 8 routes export runtime = 'nodejs': chain/status, devices/ask, devices/endpoint/chat, job-run, run-tool, wallet/faucet, wallet/withdraw, x402/pay (INVENTORY §12) Template keeps the split; docs state which routes need Node (viem signing, new Function sandbox, long deadlines)
6 Directory map omits app/{calls,chain,devices,map,universe,voice,wallet,wearables} pages and components/{Map*,Universe*,Profile*} They exist (20 pages/routes, 44 components) R9 must port them; charter says NO feature cut

Process incidents (builder's own)·

Date What happened Verdict Fix
2026-09-15 iter 3 Commit 23dd774 was pushed although gitleaks printed leaks found: 1 — the check was chained through tail, which swallowed the exit code False positive: .env.example X402_QUOTE_SECRET= (empty) matched with the following variable name as the "secret". No value was ever present. 18 further dir-scan hits were git-ignored .next/ build output scripts/preflight-push.sh scans only tracked files + history and exits non-zero on any finding; .gitleaks.toml allowlists empty-value lines in .env.example only; CI runs the same config
8 lib/tiny-record.ts + app/[slug] send Authentication: Basic base64(tinyai:tinyai) to the worker /get No worker module reads an Authentication header (0 hits in chatgpt-plugin-tinyai/src) — a dead artifact of the pre-platform proxy Dropped in the template; the header carried no secret
9 Worker not-exists sentinel is the literal string 'tiny.technology is not exists' It is a wire constant clients classify on (isTinyNotExists), so it cannot be renamed without a client roll-out Kept byte-identical, lifted into @tiny-vercel/contracts (TINY_NOT_EXISTS) so app and worker share one definition
10 /api/udid isOwnerLogin defaults to the author's GitHub login when OWNER_LOGIN is unset Owner-specific constant (charter rule 4) Template default is no owner (roster unreachable until OWNER_LOGIN is set); the source's isOwnerLogin('cagataycali') === true assertions are inverted in the ported test and say why
11 Worker oauth.ts (per-service OAuth token store, migration 0015, 4 route classes) is documented as the backend for use_github/use_spotify/use_google No router.* line in index.ts mounts it and no app route calls it — the module is dead in production; only tests/oauth-sql.test.ts exercises its SQL Ported verbatim + test green, not mounted (mounting would add a surface the source never exposed). Owner question D-009: wire /oauth* + build the /api/auth/<service> callbacks, or delete the module
12 Worker media.ts is a self-contained module It imported parseByteRange from voice.ts (1159 lines, R8) Extracted the 20-line function verbatim into src/http-range.ts; voice.ts imports from there when it lands (deliberate)
13 Worker ask.ts calls the app's /api/job-run Both call sites hardcoded https://tiny.technology/api/job-run Now ${siteUrl(env)}/api/job-run (APP_URL var) — a self-hosted deployment would otherwise have asked the author's production site to run its jobs
14 DM push says where to reply messages.ts push body hardcoded Reply at https://tiny.technology/… ${siteUrl(env)}/…
15 Web routes read the worker URL from TINY_WORKER_URL 13 device/media/message routes each redeclared WORKER_URL = process.env.TINY_WORKER_URL \|\| '<production host>' Single workerUrlOrPlaceholder() in lib/config; unset → .invalid host + /api/health flag, never the author's worker
16 Worker scheduler.ts runs due jobs "through the app's chat pipeline" Callback hardcoded https://tiny.technology/api/job-run ${siteUrl(env)}/api/job-run; apps/worker/tests/scheduler-callback.test.ts pins that the callback goes to APP_URL and that the source contains no production host
17 job-abandoned.test.ts says "every surface styles job_missed distinctly" (iOS, Android, tiny-tech tray) Those surfaces are separate clients the template does not carry Ported with only the web surface (lib/chat/prompt.ts) — a "split" per docs/TESTS.md
18 model-config.ts / model-providers.ts encrypt BYO API keys "AES-256-GCM at rest" No unit test existed for encrypt/decrypt; wrong-key decrypt silently returns '' (by design — degrades to default provider) Pinned in apps/worker/tests/model-keys.test.ts; MODEL_CONFIG_ENC_KEY documented in docs/ENV.md with the rotation caveat
19 /api/tools, /tools/install, /tools/run call "the sandbox validation hop" at the app itself Hardcoded NEXT_PUBLIC_APP_URL || 'https://tiny.technology' — a fork without the env var would validate user code against the upstream site appOrigin(req) (env or the request's own origin); /api/tools/trust hit plugin.tiny.technology/prefs directly → workerUrlOrPlaceholder()
20 Worker tools.ts "marketplace" (browse/list/upsert/delete) No worker-side test existed; only the web lib/user-tools sandbox was tested apps/worker/tests/tools-marketplace.test.ts on real SQLite: key gate, validation, upsert-in-place, owner-scoped delete, LIKE-escaped browse with author join
21 Worker telegram.ts runs each inbound message "via the app's job pipeline" fetch('https://tiny.technology/api/job-run') hardcoded — a fork's bots would have sent their owners' prompts (and INTERNAL_API_KEY) to the upstream site; the pairing reply also named tiny.technology/<slug> siteUrl(env) / siteHost(env) from APP_URL; pinned by apps/worker/tests/telegram-poll.test.ts (pairing, authorized turn, stranger, offset CAS)
22 Worker firmware.ts FIRMWARE_HOSTS — "where our artifacts are served" Hardcoded ["plugin.tiny.technology", "tiny.technology"]: a fork's /firmware/publish would refuse the fork's own artifact URLs and accept upstream's firmwareHosts(env) = hosts of APP_URL + optional WORKER_URL (new var, docs/ENV.md); empty list refuses every publish; validateBundle(raw, hosts) takes the list explicitly
23 Upstream tests/firmware-channel.test.ts "a second publish REPLACES the channel" RED in tinyai-id itself: fixture version ota-second00000 ranks below ota-deadbeef1234 under versionOrder, so the downgrade guard (Aug 25) turns the second publish into a 409 the test never checks Ported with a genuinely newer version and status assertions on both publishes; the "REPLACES" property is now actually exercised
24 Web /api/voice/session persona: "a living AI at tiny.technology/${name}" Hardcoded host in the spoken system prompt ${siteHostName()}/${name} from lib/config
25 Worker legal.ts — Terms of Use text Operator's brand hardcoded ~12 times in a legal text served to every fork Brand = siteHost(env); header comment marks it TEMPLATE TEXT to replace before going public; tests/legal.test.ts pins both
26 Worker /pay/* (19 routes) + cron reconcilers Always on; a fork with no deposit address/facilitator would expose a half-configured money path Mounted through pay(...) gate: 404 payments disabled + reconcilers skipped unless PAYMENTS_ENABLED="true" (tests/payments-gate.test.ts). Source suite x402-reconcile-status pins the gated registration string.
27 lib/x402/top-up.ts investor/reach copy · lib/deadlines.ts comment Named the upstream operator ("no tiny.technology account needed"; "plugin.tiny.technology straight from the browser") Brand-free copy ("no account on this site needed"); top-up.test.ts pins the new string
28 Web money routes /api/wallet{,/faucet,/withdraw}, /api/x402/{pay,chat/[slug]}, /api/chain/{join,status}, /api/erc8004/registration/[slug] const WORKER = 'https://plugin.tiny.technology'; x402 resource/description, headless fetch('https://tiny.technology/api/chat'), service, erc8004 image/web/x402-chat endpoints all hardcoded tiny.technology; pay-route allowlist + first-party hosts ['tiny.technology','plugin.tiny.technology'] workerUrlOrPlaceholder(); appOrigin(req)/siteHostName(); paymentRequirements(slug, price, payTo, origin = appUrl()); firstPartyHosts() = hosts of NEXT_PUBLIC_APP_URL + TINY_WORKER_URL (no default host); every handler opens with the PAYMENTS_ENABLED 404 gate (tests/payments-gate-web.test.ts)
29 chain/ 1.3 MB operator lane: Besu multinode genesis/bootnodes/artifacts, facilitator server, e2e scripts, backups Only what routes import: apps/web/chain/dev-keys.mjs (Anvil's public dev keys as a DENYLIST — gitleaks allowlisted by path), settle-outcome.mjs, contracts/TinyUSDC.sol, multinode/genesis-8470.json (tinyai-id's genesis as the sample a tiny-network operator replaces). Suites needing the rest (chain-join, chain-join-doc, chain-calldata artifacts, chain-identity/rpc-proxy) not ported.
30 Worker email() entry point tiny@ → hardcoded personal iCloud address; Cloudflare account id 6a90286f… inlined in the destination-registration fallback; cagataycali@… src/email.ts: EMAIL_OWNER_FORWARD (unset = reject), CLOUDFLARE_ACCOUNT_ID+CLOUDFLARE_API_TOKEN (unset = skip registration); postmaster@/hello@ join tiny@; reject sentinel from contracts pkg; 9 tests
31 components/MapBackground.tsx Google Maps browser key AIzaSy… inlined as the fallback for NEXT_PUBLIC_GOOGLE_MAPS_API_KEY env only; unset → loadMapsApi() resolves false, map never loads (no fallback key in the template)
32 app/layout.tsx, robots.ts, sitemap.ts, manifest.ts, error.tsx, global-error.tsx metadataBase https://tiny.technology, @tinyaid player card, say.jpeg/tiny.mp4, publisher "Formaticai.com.", keyword list, https://plugin.tiny.technology/community in the sitemap, unconditional @vercel/analytics, "tiny.technology hit a snag" prose lib/site.ts builds metadata from NEXT_PUBLIC_SITE_*; base URLs from NEXT_PUBLIC_APP_URL; sitemap via TINY_WORKER_URL; Analytics opt-in NEXT_PUBLIC_VERCEL_ANALYTICS=1; prose uses siteName(). SEO keyword list dropped (operator copy).
33 components/chat/{Chat,CommandPalette,UniverseDrawer,Control}.tsx, lib/chat/slash-commands.ts, app/page.tsx browser-side https://plugin.tiny.technology/{community,get,list,tools/browse,openapi.json}; tiny.technology/<slug> prose in the claim CTA/hero/export; A2HS QR pinned to https://tiny.technology/ lib/public-config.ts: publicWorkerUrl() (NEXT_PUBLIC_TINY_WORKER_URL), publicSiteHost() / publicAppUrl() (NEXT_PUBLIC_APP_URL). Behaviour unchanged.
34 R9c pages: app/[slug], about, calls, map, og, universe (+opengraph-image), vcard, wallet, wearables; components/{Community,Profile} https://tiny.technology origins in OG/JSON-LD/vCard/x402 share URLs; @tinyaid twitter site/creator; say.jpeg 1920×1080 OG image; siteName: 'tiny'/'tiny.technology'; plugin.tiny.technology server fetches (Profile fell back to it when TINY_WORKER_URL unset); ${name}@tiny.technology vCard email; tiny.technology is not exists retyped sentinel lib/site.ts +siteOrigin()/siteOgImageAbsolute(), siteTwitter() (absent → no handle), siteName(), siteHostName(); server files workerUrl(), client files publicWorkerUrl()/publicAppUrl()/publicSiteHost(); sentinel imported from @tiny-vercel/contracts. Behaviour unchanged.
35 tests/wearables-web.test.ts pins the owner's Apple team ID / Android cert fingerprint (AASA + assetlinks ↔ ios/*.pbxproj, android gradle) not ported — the template ships no native app or .well-known app-link files; an operator adding them should add the test back with their own identities
36 tiny-tech/src/cli.ts:417 (tray ticker) GET /api/wallet/balance — a route that exists on neither tinyai-id nor the template (/api/wallet GET is the balance route); the probe is wrapped in try {} catch {} so the tray simply never shows a balance ported verbatim in apps/cli (rule 3); the fix (/api/wallet) is a one-line follow-up for the CLI lane's owner review
37 tiny-tech/test/server.test.mjs:213 the unauthenticated MCP smoke test called the LIVE production worker (plugin.tiny.technology/list) — a network dependency on the operator's backend inside npm test apps/cli/test/server.test.mjs starts a loopback fake deployment (/api/health advertising itself as worker, /list, /retrieve) and points the CLI at it with TINY_API_URL; the test now also proves the worker was DERIVED from the app and cached in config.json
38 tiny-tech/src/daemon.ts:22 launchd label technology.tiny.daemon (reverse-DNS of the operator's domain) dev.tiny-vercel.daemon — also means a template daemon and an operator's tiny-tech daemon can coexist on one Mac
39 tiny-tech/src/tray.ts TrayStatus / menubar/main.swift:293 the menu bar opened https://tiny.technology for "the feed" status reply carries webUrl (the configured app origin; public, no token) and the Swift helper opens that; nothing opens when no daemon is running
40 tiny-tech/test/local-tools.test.mjs:93 the "uncreatable path" fixture was /proc/definitely/not/writable/tiny; on Linux mkdirSync(…, {recursive:true}) under procfs never returns, so npm test hung forever on the ubuntu runner (upstream's own CI matrix is ubuntu — the suite only ever passed on a Mac, which has no /proc) fixture is now a path under a regular file (ENOTDIR everywhere); mcp.test.mjs also asserted the macOS-only use_computer mounts in the child — now darwin-only, like the call assertion two lines below already was. Full suite verified on node:22-bookworm (arm64): 1445 tests in 24 s

2026-09-17 — apps/ios and apps/android imported from tinyai-id·

Imported with git subtree add (history preserved: 665 iOS commits, 590 Android commits). Changes made at import so the template stays deployer-neutral:

  • Android: Google Maps key and Meta Wearables DAT creds moved out of AndroidManifest.xml / build.gradle.kts into local.properties / env (MAPS_API_KEY, MWDAT_APP_ID, MWDAT_CLIENT_TOKEN), see local.properties.example. The historical commits still contain the (package-restricted) Maps key; fingerprints are allow-listed in .gitleaksignore.
  • iOS: DEVELOPMENT_TEAM and the MWDAT creds moved from project.yml into Secrets.xcconfig (gitignored, Secrets.example.xcconfig committed); Info.plist now carries $(MWDAT_APP_ID) / $(MWDAT_CLIENT_TOKEN) which Xcode expands at build time. Tiny.xcodeproj regenerated from the example values.

~~Still drifting from charter rule 4: both apps hardcode the production base URL (https://tiny.technology, plugin.tiny.technology) in source.~~ Resolved 2026-09-18: both apps take TINY_BASE_URL + TINY_WORKER_URL at build time (iOS Secrets.xcconfig → Info.plist TinyBaseURL/TinyWorkerURL → Config.baseURL/Config.workerURL; Android local.properties/env → BuildConfig → net/TinyApi.kt BASE_URL/WORKER_URL, wear too). Every URL the apps build — API, sign-in, x402/ERC-8004, OTA manifest, worker media/recordings/ profile/community, and the host named in copy — derives from them; the production values remain only as documented fallbacks (Config.swift, the two build.gradle.kts, the example files) so the reference build is byte-for-byte the same product. Still deployer work, not code: associated domains (applinks:), hosting the OTA artefacts under your TINY_BASE_URL, device firmware media allow-lists. Guide: docs/guides/mobile-apps.md.

Amended 2026-09-18 (same day, apps-origin lane) — the resolution above carried TWO settings, which broke the rule the README states for every client (one setting, discovered once, docs/CLI.md). Now: TINY_BASE_URL is the only required setting; the worker origin and site name are read at launch from GET <TINY_BASE_URL>/api/health (workerUrl, siteName — the JSON apps/web/app/api/health/route.ts publishes and apps/cli/src/config.ts probeDeployment reads), cached per base URL (iOS Deployment in Config.swift, app-group container; Android net/AppConfig.kt, SharedPreferences) and refreshed on every foreground. TINY_WORKER_URL is kept in both example files as an explicit override only (empty by default). Fallback when nothing has been discovered: the reference worker only when the base is the reference deployment, otherwise the base itself — a fork's traffic never reaches the upstream operator's worker. Pinned by apps/ios/Tests/DeploymentTests.swift and apps/android/app/src/test/.../net/AppConfigTest.kt.