This repository builds apps where the AI agent and UI are equal partners:
everything the UI can do, the agent can do through the same SQL data and action
surface. Keep this file small: the rules below are the invariants, and
.agents/skills/* carries the workflow. Read the relevant skill before changing
that area.
.agents/skills/ holds detailed area guidance; each description says when to
read it. Read the matching skill before changing that area. Skill rules govern;
this file records only invariants.
A few entry points:
adding-a-feature— four-area feature checklist.decisions— when using capability-aware classification and scoring.content-product-development— Content's product contract and shared changes.writing-agent-instructions— before editing instructions, skills, or tool/action descriptions.verifying-changes— exercise the broken path before calling a fix done.adding-tests-and-ci— before adding tests, CI jobs, or workflow triggers.test-audit— test changes.reporting-progress— during long runs and before asking for status.concurrent-agents— before shared-checkout work.ship— guarded shipping; beta/docs deploy automatically, other production promotion is manual.ship-and-monitor— requested post-merge checks or beta risks local proof cannot cover.ship-now— fast admin merge with post-merge monitoring.
Use /sidecar <task> for read-only investigations.
- Scale effort: for small changes, read briefly, edit, and run existing checks; skip surveys, unrequested tests, and browser automation. Explore deeply only for ambiguous or cross-cutting work.
- Before finishing or handoff, close opened browser tabs/app sessions and stop started processes. Honor keep-open requests; stop only owned resources; report failures.
- A user's task-scoped authorization persists across replies, heartbeats, and
compaction until complete or revoked. An affirmative answer resolves the
pending action; do not ask again. Revalidate live ownership and state before
writing; verification is not renewed approval. Ask only for scope beyond the
authorization, a user-reserved boundary such as production promotion or
contacting another person, a missing credential, a user-only decision, or a
destructive data change. See
shipandnew-branchfor covered git/PR work. - Never add
Co-Authored-Byor other agent attribution to commits. - Use the current suitable branch. PRs are ready for review unless drafts are requested.
- Deployment split:
.github/workflows/deploy-beta-sites-prebuilt.ymlis the sole beta publisher; Actions uploads prebuilt artifacts to independentbeta.*.agent-native.comsites. Netlify Git builds are disabled; do not wait on their queues or previews. If beta deployment status is in scope, check the Actions run and built-in site smoke. Normal/shipdoes not monitor beta or claim beta health. Add an independent beta behavior check only when requested or a concrete beta risk needs proof local checks cannot cover; use/ship-and-monitor. The docs exception is.github/workflows/deploy-docs-production.yml: it publishesfw/www.agent-native.comfrom matchingmainchanges and disables Git builds; there is no beta docs site. Other production promotion is manual through.github/workflows/deploy-production-sites-prebuilt.ymlorpromote-netlify-deploy.yml. Let workflows manage Netlify locks; clearing one does not promote production. - When shipping from a worktree, commit, push, and open or update the PR there; leave the shared checkout untouched.
- Never push to another's PR without exact-PR authorization; merge authorization
is separate unless the user explicitly authorized that exact merge. See
ship/babysit-prfor author/head checks. - On your own PRs, replies to every review thread you fix or disposition are always authorized and expected; add a concise top-level recap for review-body-only feedback. On others' PRs, comment only with explicit authorization for that exact PR; avoid unrelated comments, tags, and assignments.
- Use root
.tmp/for repo-local temp files; it is gitignored. - Never use
[codex],codex, or similar agent labels in user-visible GitHub metadata unless explicitly requested. - Keep the chat title accurate.
- Follow
reporting-progressfor updates and mid-task stops. - When adding package dependencies or framework integrations, verify the current
latest version first with
npm view/pnpm viewor current docs. Do not rely on remembered versions. - Never use
pnpm patchorpnpm patch-commit, addpnpm.patchedDependencies, or commit dependency patch artifacts underpatches/. Upgrade the dependency or fix app-owned code instead. - A
catch, default, or coercion that returns a value callers cannot distinguish from success is a bug, not a guard. "Absent" and "unreadable" must be different values; a truncated run is not a completed one; a dropped payload is not an empty one. This is the single habit behind the longest-running class of repeat user reports here: each layer coerces a failure into a clean value, so every layer above it reports something confidently wrong and nobody can see it. Prefer a loud, typed failure over a plausible-looking normal state. - Before adding a condition to a function that already stacks several special cases, stop: that shape is how the same bug ships twice. Fix the boundary that made the special case necessary, and delete the ones it subsumes.
- Write code that reads like the surrounding code: match its comment density, naming, and idiom. When you do comment, comment a constraint the code cannot show — a non-obvious trap a future change would otherwise reintroduce — not what the next line does, why your change is correct, or where it came from.
- When changing docs under
packages/core/docs/content, update the matching localized docs underpackages/core/docs/content/locales/*when the source meaning changes. If translations cannot be updated in the same change, call out the specific locales that need follow-up; reviewers should flag docs changes that only update one language. - During review, treat any user-facing copy change as a localization change:
UI labels, buttons, tooltips, placeholders, errors, empty states,
accessibility text, prompts, and documentation prose all need their English
source and configured locale translations updated together. Run
pnpm guard:i18n-catalogsandpnpm guard:i18n-changed-copy; do not approve an unexplainedi18n-copy-ignoremarker or localization baseline update. - Docs-only commits start with
docs:in the present tense, e.g.docs: fix broken link in provider API guide, notdocs: fixed broken link.
Every final response must end with a three-line status block:
---
⠀
🟢 Actual concise status sentenceThe words after the icon are a short, task-specific status written for this
response; never use the placeholder text Brief status literally. Use 🟢
when the requested coding/work unit is finished on the current branch, even if
routine commit/PR/deploy/CI remains. Use 🟡 when non-routine work or a manual
step is still pending. Use 🔴 only when blocked on user input. When you use
🟡, the pending item must be legible from that one line alone — the user
should never have to reply asking what is not done.
Rules here are carried by skills, not by blocking your tools. Two exceptions exist, and both are narrow on purpose.
Guards (pnpm guards, and CI on every PR — these apply to Codex, Claude
Code, and a human equally). pnpm guards --list prints the current set;
no-silent-coercion, no-raw-colors, no-boot-data-work,
no-heavy-dashboard-list-reads, no-unbounded-table-reads,
no-bare-error-in-actions, external-result-contract,
no-source-reading-tests, no-oracle-loosening,
no-raw-sql-interpolation, action-output-schema,
strict-lint, and compiler-strictness check only lines this branch added,
so the
pre-existing backlog stays a separate cleanup. Each guard has a documented
opt-out pragma, and every opt-out is a decision a reviewer should see.
no-new-legacy-mcp-hints, no-new-escape-hatches, and compiler-strictness
have none: the MCP destructiveHint baseline, each file's count of any,
!, and suppression comments, and the list of tsconfig strictness opt-outs
only shrink.
A guard reports three outcomes, not two: exit 0 passed, exit 1 failed, exit 2
could not run. A diff-scoped guard that cannot resolve a base ref exits 2 via
requireAddedLines, and pnpm guards renders it SKIPPED and refuses to print
"All checks passed" — in CI it fails the run. Never reintroduce an exit(0)
for a check that inspected nothing; that is the flagship rule above, violated
inside the thing that enforces it.
Shared edits are visible in Git; read concurrent-agents. Batch fixes to avoid
CI churn. Update from origin/main only for GitHub CONFLICTING PRs; merge
shared branches.
One hook (scripts/hooks/file-lease.mjs, registered in the tracked
.claude/settings.json): denies a write when another live session holds the
file, or when it changed on disk under you. It exists because this is the only
rule you cannot follow by reading instructions — no amount of guidance tells you
that a peer session is mid-edit in the same file right now. Re-read and build on
their change; never force past it. It is a Claude Code mechanism only: it gives
Codex sessions and plain human edits nothing, so it is a backstop, not a
guarantee. Read concurrent-agents before working in a shared checkout.
guard:hooks-registered keeps this section and that file from drifting apart.
guard:i18n-catalogs checks catalog shape, placeholders, raw UI literals,
English-value debt, and localized docs coverage. guard:i18n-changed-copy
checks the changed-source side of the contract: a changed English catalog or
source doc must have the corresponding configured locale files/sections changed
in the same diff. Use i18n-copy-ignore only for reviewed non-translatable or
source-only edits, with the reason visible in the diff.
Everything else is guidance — but guidance nobody measures is guidance nobody
can tell is working. node scripts/agent-friction-report.mjs --weeks 2 counts
how often the user has had to repeat each correction, and names the skill that
was supposed to close it.
Before adding a rule to this file or to a skill, run that report. Then:
- Name the pattern key the rule is supposed to move. No key, no rule — add one
to
PATTERNSfirst, so the rule is falsifiable. - If the pattern is climbing and already has owning guidance, do not restate
the guidance. Rewriting a rule that has already failed twice is how this repo
grew four copies of "push your work" across two skills while the worktree
stayed unpushed. Replace it with a mechanism, or with one command the agent
runs instead of remembering a procedure —
pnpm ship:push -mis what that looks like. - Delete the prose the mechanism replaces, in the same change.
The earlier version of this section claimed unasked branch creation "went to zero" and used that to argue guidance beats mechanism. Measured on 2026-08-12 it was 16 in two weeks. Keep the claim and the number in the same place, or the argument rots into exactly the patchwork it warns about.
- Data lives in PostgreSQL via Drizzle by default. Explicit Local File Mode artifacts
declared through
agent-native.jsonmay use repo files as the source of truth, but app state, auth, settings, and hosted/collaborative mode still use SQL. Keep schemas PostgreSQL-specific. - Keep app and template database code PostgreSQL-specific. Use Drizzle's
PostgreSQL query builder for normal reads and writes and
getDbExec().execute()for reviewed PostgreSQL raw SQL. Never call adapter-specificrun(),all(), orget()methods. - Actions are the single source of truth. Define app operations in
actions/withdefineAction; the agent calls them as tools and the frontend calls the shared action surface throughuseActionQuery/useActionMutation. - Client code imports named helpers, hooks, or client modules instead of
hand-writing REST calls to framework routes or template
/api/*routes. If a browser workflow needs a route and no helper exists, add the helper first and teach that method in docs/skills instead of teaching rawfetch. - Before adding any custom API or Nitro route for app data, inspect existing actions first. Reuse or extend the action surface instead of creating REST wrappers, pass-through endpoints, or duplicate CRUD routes that re-export actions.
- Before adding settings, setup, credential, OAuth, or connection UI for an external service, inspect the shared toolkit, settings, vault, OAuth, workspace-connection, onboarding, and provider API primitives. Use the strongest existing primitive by default; keep custom UI only for provider-specific prerequisites, sequencing, status, or health checks.
- Normal app data must flow through actions. If you are about to write a handler
under
server/routes/api/, or middleware to guard one, stop and write an action instead. The only exceptions are uploads, streaming, inbound webhooks, OAuth callbacks, public unauthenticated URLs, and non-JSON responses. Existing template/api/*CRUD is a grandfathered baseline being migrated, not a pattern to copy;guard:no-action-twin-routesfails on new ones. - For provider integrations used in ad hoc analysis, querying, reporting, or
cross-source research, prefer the shared
provider-api-catalog,provider-api-docs, andprovider-api-requestaction pattern from@agent-native/core/provider-apiinstead of hardcoding one action per provider endpoint/filter. First-class actions are ergonomic shortcuts, not capability limits: when the upstream API can express an endpoint, filter, pagination mode, or payload, agents should have a safe way to call it directly through the provider API substrate. If an app stores provider credentials on resource/share rows, add a scoped resolver that preserves those access checks before exposing raw provider requests. - For customer or third-party provider data, never read API keys or tokens from
process.env. Inspect the workspace connection catalog first, use the granted connection's vault-backed credential refs, and only use scoped local credentials when no reusable connection exists. Deployment environment variables are for deploy-level configuration, not user/workspace data access. - Treat Clay as a credentialed GTM provider API, not as a messaging channel.
Hosted access uses
CLAY_PUBLIC_API_KEYthrough the provider API substrate; the optional local Clay CLI/MCP plugin has a separate browser-login session and must not be required, auto-installed, or vendored by default. Its public repository currently declares no license. Use n8n and Zapier as automation/workflow or remote MCP connections rather than provider presets. - Keep apps focused, but default to local actions. Use A2A only on request or
for required peer-owned data, credentials, or capabilities. Availability or
enrichment alone doesn't qualify; artifacts stay in their owning app. Pass
bounded ids, URLs, and summaries across app boundaries. Read
composable-mini-appsbefore designing cross-app workflows. - All AI work goes through the agent chat. UIs do not call LLMs directly.
- Application state belongs in SQL
application_stateso the agent can know the current navigation, selection, and focused object. - Chat-run tools refresh UI data; opt in to
useDbSync({ realtime: { reason } })only when external changes matter. Readreal-time-sync. - Server configuration is one zod schema. Add a field under
packages/core/src/app-config/and read it withgetAppConfig(); an environment variable is a declared.meta({ env })alias into that field, not a parallel namespace. Consumer code never readsprocess.env— four resolvers do, andconfigurationnames them. - Never do heavy work at serverless cold start — migrations, backfills,
aggregation, index builds, provider handshakes, or warmup probes in module
load or plugin init run on every cold Lambda and surface as sitewide slowness,
not as a slow startup. Do that work in a
recurring-jobstask, an automation, or lazily behind the first request that needs it. Readperformancebefore adding anything to a startup path. - The agent can modify app code; design UI and data flows with that in mind.
Every feature must touch the four areas when applicable: UI, actions, skills or instructions, and application state.
- Schema changes must be additive. Never drop, rename, truncate, or destructively alter tables or columns in migrations or startup code. New columns must default or allow null.
- Core framework tables belong to
defineStore()migration lists: append, never edit; readframework-stores. - SQL stores structured app state, metadata, references, and searchable text. Do
not store large raw payloads — files, images, videos, audio, PDFs, ZIPs,
screenshots, session replay chunks, thumbnails,
data:URLs, or base64 file bodies — in SQL tables,application_state,settings, orresources. Use configured file/blob storage (uploadFile,putPrivateBlob, provider object storage) and persist only URLs, ids, or opaque handles. Seestoring-data. - Never use
drizzle-kit pushagainst production databases. - Tables with
ownableColumns()require scoped reads and writes throughaccessFilter,resolveAccess, orassertAccess. Custom Nitro routes must establish request context before querying ownable data. - Never hardcode API keys, tokens, webhook URLs, signing secrets, private Builder/internal data, customer data, or credential-looking literals in source, docs, tests, fixtures, screenshots, prompts, or generated extension/app content. Use obviously fake placeholders in examples.
- Do not copy provider tokens into apps when a workspace integration grant can be used. Vault/secrets own secret values; apps own app-specific readers and interpretation.
- One resolver per credential key, and every runtime path goes through it. Grep
the key name before reading
process.env, and pass the caller's email into shared helpers rather than resolving identity inside them.resolveCredentialsearches exactly one organization, so it is not a fix for a caller with no org (cron, CLI) or a key synced under a different org. Runnpx agent-native doctor --only no-env-credentialsfrom the app directory before finishing a credential change. See thesecretsskill. - Never create an organization, repoint a user's
active-org-id, or migrate a roster/identity list into a new org on your own initiative — vault credentials are per-organization, so a second org orphans every key synced under the first. One org per workspace is the intended pattern: add members to the existing org, and stop and get an explicit yes before doing otherwise. Seeauthentication. - Use the
security,storing-data,sharing,portability, andintegration-webhooksskills for implementation details.
- TypeScript everywhere. Do not add
.jsor.mjssource files. - Run oxfmt on modified source files.
- Use shadcn/ui primitives for standard controls and dialogs. Do not build custom dropdowns/popovers/modals with absolute positioning.
- Use Tabler Icons for UI icons. Do not use emojis as first-party icons.
- No browser
alert,confirm, orprompt; use shadcn dialogs. - Agent prompt inputs must use the shared composer stack:
AgentComposerFrame,PromptComposer, andTiptapComposer. - Background agents must use the core run-manager / agent-teams infrastructure unless working on the existing local Code exception.
- Logged-in app pages can be CSR. Public/SEO pages must SSR real content.
- Every SSR HTML and React Router
.dataresponse is one impersonal, public shell, hard-cached at the CDN for every visitor. Never addprivate,no-store,Vary: Cookie, session/cookie reads, or auth branches to the SSR path — personalization is client-side after load. Enforced byguard:ssr-cache-shellandssr-handler.spec.ts; do not weaken either. - UIs should update optimistically, roll back errors, and avoid blocking spinners except for destructive work.
- UI feedback: target 100 ms, never exceed 400 ms; acknowledge before network work.
- Data loads use layout-matching
Skeletongeometry, not a generic "Loading..." label; reserveSpinnerfor brief mutations, uploads, and progress actions. - For any user-facing UI change — including screenshot feedback, copy or density
cleanup, settings, and control placement — read
frontend-design. - Default surface density is a hard default, not a judgment call. Do not add a
page title that repeats the nav item or route, a description or subtitle under
any title, an eyebrow, a breadcrumb beside a back arrow, a count/stat strip
over content already on screen, or an About section in settings. A card, panel,
tab, settings group, or row gets a title or a description, never both;
explanation goes in a tooltip, a
Managepopover, or a menu. Rendering a user's own storeddescriptionis content, not chrome — keep it, and render nothing when it is empty. Prose props are always optional; neverdescription: string.guard:no-default-chromechecks lines this branch adds, andtemplates/forms/is the reference implementation. This is the repo's most-repeated correction (text-heavy-ui), so it is stated as a default you apply rather than a tradeoff you weigh: if the user wants one of these, they will ask. - Use the
frontend-design,shadcn-ui,client-side-routing,native-navigation,real-time-sync, anddelegate-to-agentskills for details.
- Publishable package source changes in
packages/core,packages/dispatch,packages/scheduling, orpackages/pinpointneed a.changeset/*.md. - Do not manually bump package versions; changesets handle versions on merge.
- The public template allow-list is controlled by
packages/shared-app-config/templates.tsplus mirrored CLI/docs surfaces. Hidden templates must not appear in public catalogs unless they are explicitly unhidden first. - Ship a user-facing change to a template app (new capability, visible
improvement, behavior-affecting fix)? Record it from that app with
agent-native changelog add "<one sentence>" --type <added|improved|fixed>. Skip refactors, tooling, and tests. See thechangelogskill. - The Builder Code starter (
packages/core/src/templates/builder-code-starter) is patches overtemplates/chat. After editing Chat, runpnpm guard:template-layers. If a patch fails, runpnpm template-layer rebase builder-code-starter --out .tmp/fs, resolve conflicts keeping the starter's intent, thenpnpm template-layer diff builder-code-starter --from .tmp/fs. Never delete a failing patch.
Extensions are sandboxed Alpine.js mini-apps stored in SQL. When the user asks
to create or edit an extension/widget/dashboard/calculator/mini-app, use the
extension actions and extensionData instead of source changes. Use the
extensions skill for the full rules.