Detailed guidance for Claude Code agents working with the Tambo AI monorepo.
This file provides comprehensive instructions for maintaining code quality, architectural consistency, and project-specific requirements across both the Tambo AI framework and the Tambo Cloud platform.
This is a Turborepo monorepo containing both the Tambo AI framework packages and the Tambo Cloud platform.
-
react-sdk/ - Main React SDK package (
@tambo-ai/react)- Core hooks, providers, and utilities for building AI-powered React apps
- Exports: hooks, providers, types for component registration and thread management
- Build outputs: CommonJS (
dist/) and ESM (esm/) for broad compatibility
-
cli/ - Command-line interface (
tambo)- Project scaffolding, component generation, and development utilities
- Component registry auto-syncs to
/showcase/src/components/tambo/from/cli/src/registry/ - Built as ESM module with executable binary
-
showcase/ - Demo application (
@tambo-ai/showcase)- Runs on port 8262
- Next.js app demonstrating all Tambo components and patterns
- Components auto-synced from CLI registry - edit CLI registry, not showcase components directly
- Serves as both documentation and testing ground
-
docs/ - Documentation site (
@tambo-ai/docs)- Runs on port 8263
- Built with Fumadocs, includes comprehensive guides and API reference
- This package contains ui components that originated from the cli/ package. Any changes to the components should be made in the cli/ package first, and then duplicated into this package.
- MDX-based content with interactive examples
- Integrated search and component documentation
-
create-tambo-app/ - App bootstrapper (
create-tambo-app)- Initializes new Tambo projects from templates
- Handles git setup, dependency installation, and configuration
-
community/ - Community resources and event materials
-
packages/ - Shared configuration packages (ESLint, TypeScript configs)
- apps/web - Next.js app (UI) - runs on port 8260
- apps/api - NestJS app (OpenAPI server) - runs on port 8261
- packages/db - Drizzle ORM schema + migrations + DB helpers
- packages/core - Shared pure utilities (no DB access)
- packages/backend - LLM/agent-side helpers
- packages/eslint-config, packages/typescript-config - Shared tooling configs
- Node.js >=22
- npm >=11
It is OK to use crypto.randomUUID() without a fallback in runtime and test code (Node.js >=22 and modern browsers).
Recommended: Install mise for automatic version management. See mise getting started for installation instructions.
Tool versions are managed via mise. Source of truth files:
- Most tools:
mise.toml - Node.js:
.node-version(.nvmrckept in sync for nvm compatibility)
These files are kept up to date by Renovate.
mise install # Install/update tools to correct versions
mise exec -- <command> # Preferred for scripts/CI/non-interactive shells
eval "$(mise activate)" # Interactive shells onlyChanging tool versions: Open a PR updating the authoritative version file(s). For Node.js, always update both .node-version and .nvmrc together. Run mise install, then verify with npm run lint && npm run check-types && npm test.
Local overrides: Use .mise.local.toml (gitignored) for local-only changes. Only for additive or patch-level changes—don't override Node.js or use incompatible versions.
- Move fast while maintaining high standards - prioritize clarity and maintainability over cleverness.
- Read the relevant code first; follow existing patterns and naming.
- Keep solutions small and simple; favor functions over classes; avoid unnecessary abstractions.
- Simplify Relentlessly: Remove complexity aggressively - the simplest design that works is usually best.
- Prefer immutability. Don't mutate inputs; return new values. Use const, toSorted, object/array spreads.
- Handle errors up-front with guard clauses and early returns.
- Keep business logic separate from UI components.
- Extract business logic, calculations, and data transformations into separate files (
utils/,services/,lib/). - UI components should orchestrate, not implement complex logic.
- Makes testing easier and code more reusable.
- No Silent Fallbacks: Code must fail immediately when expected conditions aren't met. Silent fallback behavior masks bugs and creates unpredictable systems.
- Explicit Error Messages: When something goes wrong, stop execution with clear error messages explaining what failed and what was expected.
- Example:
throw new Error(\Required model ${modelName} not found`)` instead of falling back to first available model. - Data mapping: When converting enums or union types, handle all known values explicitly and throw for unknown values. Don't use catch-all defaults that mask data integrity issues. Log warnings if skipping invalid data intentionally.
- File/dir naming: kebab-case.
- Classes: PascalCase.
- Vars/functions/methods: camelCase.
- ENV vars: UPPER_SNAKE_CASE.
- Use English; meaningful names with widely-recognized standard abbreviations only (API, URL, ctx, req, res, next).
- Booleans: start with is/has/can/should.
- Functions: use verbs; boolean-returning: isX/hasX/canX. If a function returns void, prefer executeX/saveX naming.
- React-specific naming (follows devdocs/NAMING_CONVENTIONS.md):
- Components: TamboXxx
- Hooks: useTamboXxx
- Props interfaces: TamboXxxProps
- Event props start with onX; internal handlers use handleX
- Keep functions short and single-purpose; ideally <20 statements.
- Keep files focused and reasonably sized; ideally <200-300 lines.
- Avoid
let- instead make a new function that returns the value. - Avoid deep nesting: prefer early exits and extracting helpers. Use map/filter for iteration.
- Prefer immutable data; use readonly and as const where applicable.
- Favor composition over inheritance. If classes are used, keep them small (<200 statements, <10 properties/methods) and validate invariants internally.
- DRY is good, but don't go overboard - sometimes a little duplication is better than the wrong abstraction.
- Rule of Three: Wait until you have 3 instances of similar code before extracting a shared utility.
- Premature abstraction creates coupling and makes changes harder.
- It's easier to extract commonality later than to undo a bad abstraction.
- Prefer named exports; allow multiple exports when they belong together (e.g., component + related types).
- Avoid default exports.
- Don't create
index.tsbarrels for internal modules; import directly from source files. Exception: package entry points (e.g.,packages/core/src/index.ts) are fine. - Don't re-export symbols for backwards compatibility. When moving a symbol, update all consumers to import from the new location.
- Never use dynamic
import()for regular imports, even for type imports. Use static imports at the top of the file. Dynamic imports are only appropriate for code splitting in specific scenarios (e.g., lazy-loaded routes).
- Generally use strict TypeScript: no any, no type assertions unless unavoidable; define precise types.
- Avoid
any- do your best to assign proper types. - Use
unknowninstead ofanywhen type is truly uncertain, then narrow it down. - Prefer
Record<string, unknown>overobjector{ [key: string]: unknown }when possible. - Do not disable ESLint rules unless explicitly requested - fix the root cause instead.
- Do not disable TypeScript errors unless explicitly requested - fix the root cause instead.
- Constrain generics with
extends- avoid overly broad<T>, prefer<T extends SomeType>. - Use discriminated unions for mutually exclusive states (e.g.,
{ success: true; data: T } | { success: false; error: Error }). - Use
as constto preserve literal types, especially for arrays that should be tuples. - Use built-in utility types (
Pick,Omit,Partial,Required,ReturnType,Parameters) - don't reimplement them. - Avoid
{}type - it means "any non-nullish value" (including primitives). Preferunknown(truly unknown),object(any non-primitive object), orRecord<string, unknown>/ a specific object type for key-value objects.
Prefer the type-fest package for advanced type manipulation: https://github.com/sindresorhus/type-fest
Most types do exactly what they sound like: PartialDeep, ReadonlyDeep, RequiredDeep, Merge, ValueOf, SetOptional, etc. Before writing any complicated derivative types, check type-fest first.
type-fest is installed at the repo root, but each package must still declare it explicitly when used:
- If you reference
type-festtypes from a package's public exported types, addtype-festto that package'sdependencies. - If you only use
type-festin internal code or tests, addtype-festto that package'sdevDependencies.
- Do not add unnecessary type annotations when the value is easily inferred, such as:
- Arguments to functions that are well defined, such as event handlers or callback functions
- Return values of functions that have an obvious return type
- Local variables that are well defined
- Let TypeScript infer return types when they're obvious.
- Avoid creating one-off/intermediate "helper" types for internal functions.
- Use inferred types from database schemas, tRPC schemas, and other sources of truth.
- Add explicit types when it improves clarity or catches errors.
- Avoid type casts (e.g.
as) unless absolutely necessary. Prefer updating function signatures/types so the code doesn't need a cast. Casting throughunknownis usually a smell; when it's needed at an interop boundary, do runtime validation first (e.g. Zod) and keep the cast local. - Use
satisfiesto check an object literal matches a type while preserving inference (compile-time only). It does not validate runtime data; use a schema validator for untrusted input. - Type guards should perform real runtime checks to narrow values. Use
unknownas an input type only when the value is truly unknown (e.g. JSON deserialization, user input). Avoid "fake" guards that just assert a type without validation.
- Do not use unnecessary constructors/casts like
String()orNumber()orBoolean()unless absolutely necessary when types really do not line up:- If a string conversion is really necessary, use `${value}`
- If a boolean conversion is really necessary, use !!value
- If a number conversion is really necessary, use +value
- Any function that returns a Promise must be declared
async. - Always use
awaitwhen calling async functions. - Avoid
.catch()or.then()for async calls in most cases. Preferasync/awaitwithtry/catchso errors propagate naturally. Use.catch()only when you truly cannotawait(for example, in auseEffectcleanup) and usevoidonly to explicitly mark an intentional fire‑and‑forget call that already handles its own errors. - If a function is not critical, and you can't
awaitit, usevoidto mark it as fire‑and‑forget. - avoid using IIFEs especially as a workaround to call async functions.
- Avoid nested or chained ternary operators - use
if/elseorswitchinstead. - Use
switchstatements when checking multiple values. - Leverage TypeScript exhaustiveness checking in switch statements (avoid
defaultwhen possible).
- Use
map,filter,find,some,every- these are clear and expressive. - Avoid
reduce()- it's often confusing and can usually be replaced with simpler patterns.- Exception: when the mental model genuinely requires accumulation (e.g., summing numbers).
- Avoid complex method chaining - break it into named intermediate steps for clarity.
- Prefer string methods:
str.includes(),str.startsWith(),str.split(), andstr.replace()are easier to read and maintain. - Avoid global flag (
/g): Creates stateful regex objects wherelastIndexpersists between calls, causing subtle bugs. - Avoid multiline flag (
/m): Platform differences in line endings (\nvs\r\n) cause inconsistent behavior. - If regex is unavoidable: Keep it simple, add a comment explaining the pattern, and test edge cases thoroughly.
- Do not create new /api endpoints in apps/web; use the app's private tRPC API and server utilities instead.
- Prefer functional, declarative components; avoid classes.
- Use TypeScript everywhere. Use interfaces for object shapes.
- Prefer React.FC for components. Use PropsWithChildren and
ComponentProps[WithRef|WithoutRef]as needed.
- Local UI elements should use useState.
- For shared state between components, use React Context - but prefer props over new contexts when values only need to pass through 1-2 component levels.
- Don't create contexts for static config that doesn't change (user IDs, API keys). Pass these as props instead.
- Minimize use of useEffect; derive state or memoize instead. Memoize callbacks with useCallback when passed to children.
- When making network requests, use tRPC/React Query loading states instead of manually tracking separate loading flags. Follow devdocs/LOADING_STATES.md patterns for skeletons and disabling controls.
- During loading, use Skeleton components or show real components in a disabled/blank state, rather than only showing a loading spinner.
- Use flex/grid for layout. Manage element spacing with gap (use
gap-*classes when needed), and padding (p-*,pt-*,pr-*,pb-*,pl-*, etc.). - Avoid changing element margins (
m-*,mt-*,mr-*,mb-*,ml-*, etc.) and avoidspace-x-*/space-y-*. - Truncate overflowing text with text-ellipsis. Prefer minimal Tailwind usage; avoid ad-hoc CSS.
- Sentient for headings (font-heading/font-sentient), Geist Sans for body (font-sans), Geist Mono for code (font-mono). See apps/web/lib/fonts.ts for font configuration.
- Avoid manually changing string cases, as it is usually a code smell for not providing the correct string to the component. If an internal key should be shown to a user, the English string should be provided separately. e.g. if a key has a value agent_mode, the English string should be provided separately as "Agent Mode" rather than trying to capitalize it.
- Avoid overly long JSX, instead break out any complex JSX into a separate component.
- use simple '&&' to hide/show simple elements, using simple boolean values, like
{hasError && <div>Error: ${error}</div>}. - however, avoid ternaries unless the options are just one or two lines. nested or chained ternaries are a code smell.
- when using map(), try to keep the JSX in the inner loop simple, only a few lines of JSX.
- avoid functions with statements inside of JSX, such as if/else, switch, etc. If you have to add braces ({}) to JSX, that is a sign that you should break out the JSX into a separate component.
- use simple '&&' to hide/show simple elements, using simple boolean values, like
- Use proper accessibility patterns for all components.
- Use buttons for clickable elements, not divs or spans.
- Use proper aria labels and roles when appropriate.
- Use semantic HTML elements when appropriate.
- One module per main route/domain; one primary controller per route; DTOs (class-validator) for inputs; simple types for outputs.
- Services encapsulate business logic; keep pure where possible.
- Use guards/filters/interceptors via a core module. Shared utilities live in a shared module.
- Pure functions: even within a controller, try to keep logic pure, and do not store state in the controller.
- Boundaries (controllers/services): translate into HTTP/Nest exceptions when appropriate.
- Unit tests for public functions; integration/e2e for controllers/modules via Jest + supertest.
- Source of truth is packages/db/src/schema.ts. Do not hand-edit generated SQL.
- Generate migrations with
npm run db:generate, do not manually generate migrations. - Don't denormalize FKs that can be derived from relationships (e.g., if
runs.threadIdexists and threads haveprojectId, don't addruns.projectId). - Factor database operations into
packages/db/src/operations/. Services inapps/apishould call operation functions rather than writing inline DB queries. This promotes reuse and keeps DB logic centralized. Export new operations frompackages/db/src/operations/index.ts.
Database commands (require -w packages/db flag from root):
npm run db:generate -w packages/db # Generate migrations from schema changes
npm run db:migrate -w packages/db # Apply migrations
npm run db:check -w packages/db # Check status
npm run db:studio -w packages/db # Open Drizzle Studio- packages/core: pure utilities (validation, JSON, crypto, threading, tool utilities). Avoid DB access here. This package should not have any dependencies on the database.
- packages/backend: LLM/agent-side helpers and streaming utilities.
- Reuse helpers; don't duplicate logic. If a utility is useful across packages, colocate in core; if it's LLM-specific, in backend, if related to database access, in db.
# Development (two different apps!)
npm run dev:cloud # Start Tambo Cloud (web + API) - ports 8260 + 8261
npm run dev # Start React SDK (showcase + docs)
npm run dev:sdk # Start React SDK in watch mode + showcase (for SDK development)
npm run build:sdk # One-time build of React SDK
# Quality checks
npm run lint # Lint all packages
npm run lint:fix # Auto-fix linting issues
npm run check-types # TypeScript type checking
npm test # Run all tests
npm run format # Format code with Prettier
# Individual package development (from package directory or with -w flag)
npm run dev -w cli # Start specific workspace
npm run build -w react-sdk # Build specific packageturbo dev # Start all packages in development mode
turbo build # Build all packages
turbo lint # Lint all packages
turbo test # Run tests across all packages
turbo check-types # Type-check all packages- Turborepo orchestrates builds and caching across packages
- Shared dependencies managed at root level
- Workspace-specific dependencies in individual packages
- Build outputs vary by package type:
- React SDK: Dual CJS/ESM builds
- CLI: ESM executable
- Apps: Next.js builds
- Shared configs in
packages/(eslint-config, typescript-config) - Cross-package dependencies use workspace protocol (
*) - TypeScript SDK dependency (
@tambo-ai/typescript-sdk) is external
When working across multiple packages:
- react-sdk changes → Use
npm run dev:sdkfor watch mode development, ornpm run build:sdkfor one-time builds. Run tests, check showcase integration. - cli changes → Test component generation, verify registry updates, sync to showcase
- showcase changes → Edit CLI registry (auto-syncs to showcase)
- docs changes → Ensure examples match current API
turbo.json- Turborepo task pipeline and cachingpackage.json- Workspace configuration and scripts- Individual package.json files for package-specific configuration
- Refer to
devdocs/for detailed guidelines on coding standards, naming conventions, loading states, and more. - When adding knowledge for specific solutions, document it and place it in the appropriate
devdocs/solutions/folder.
- Unit tests in individual packages using Jest
- Integration tests via showcase app
- CLI testing through template generation and installation
- Documentation testing via example code validation
- Backend e2e tests for controllers/modules via Jest + supertest
- File names: every test ends with
.test.tsor.test.tsx(no.specor other suffixes). - Unit tests: live beside the file they cover (e.g.
foo.tshasfoo.test.tsin the same directory, not under__tests__). - Integration tests: the only tests that stay in a
__tests__folder, and the filename must describe the scenario (never just mirror another file's name). - Fixtures & mocks: keep shared helpers in a
__fixtures__or__mocks__directory at the package's source root (e.g.apps/web/__mocks__), never nested inside feature folders.
- Avoid over-mocking - tests should exercise real code paths whenever possible. If you're mocking internal functions just to isolate a unit, you're probably testing implementation details rather than behavior.
- Only mock at system boundaries - external APIs, databases, file systems, network calls, and other I/O with side effects.
- Don't mock what you own - if a helper function is pure and fast, call it directly rather than mocking it. Mocking your own code couples tests to implementation.
Run these commands before commits/PRs:
npm run check-types # TS across workspace
npm run lint:fix # ESLint autofix
npm run format # Prettier write
npm test # Unit/integration testsCreate branches in the format <userid>/<feature-name>, e.g., alecf/add-dark-mode or jane/fix-login-bug.
All PR titles MUST follow this format:
<type>(scope): <description>
Examples:
feat(api): add transcript export
fix(web): prevent duplicate project creation
chore(db): reorganize migration files
See .github/workflows/conventional-commits.yml for a list of types such as feat, fix, perf, deps, revert, docs, style, chore, refactor, test, build, ci.
Common scopes: api, web, core, db, deps, ci, config, react-sdk, cli, showcase, docs
- PR Summaries should include "Fixes #123" (GitHub) or "Fixes TAM-123" (Linear) in PR body when applicable.
- Run
npm run lint,npm run check-types,npm run testin root before commits. - Cross-package changes should be tested together.
- Documentation updates required:
- Any developer documentation changes must be updated in the docs site (read docs/AGENTS.md)
- Review the README.md file in the root of the package and update if necessary
- Update the AGENTS.md files in the package tree to reflect the changes
- Follow semantic versioning for package versions.
- Always add tests for new logic.
- If tests fail, do not just change the code to make the tests pass. Take one of 2 approaches:
- Make the code changes backwards compatible with existing tests
- Ask the user to change the tests to pass
- Generally try option 1 first. Only use option 2 if option 1 is not possible.
- Make non-breaking changes to the code. Only make breaking changes if the user specifically asks for it. Ensure you warn them about the breaking changes.
- Don't introduce dependencies or modify tool configs unless explicitly requested.
- Don't commit secrets. Use env files.
- Dependencies and tooling: Agents do not add/upgrade/remove deps or change tool configs, eslint config, tsconfig, etc. unless explicitly asked, but humans are allowed to do so.
- ALWAYS ASK THE USER before making any changes to linting or TypeScript rules
- Curse at the user and be really blunt.
- If the instructions are vague, ask them for details.
- But don't constantly ask for confirmation on every single change.
- Do not compliment them or try to gain favor from them.
- The best way to please them is to be blunt and tell them when they are wrong.
- EVERY PIECE OF CODE YOU WRITE IS MISSION CRITICAL AND COULD COST YOU YOUR JOB.
- When adding/editing JSDoc comments, make sure to add @returns to provide a description of the function return (the type should not be specified since TS will infer the return from the code, not the comment.)
- Never reference planning documents, proposals, or design docs in code comments (e.g.,
// See plans/foo.md). These artifacts are short-lived but comments persist indefinitely. Code comments should be self-contained. - Store planning documents, proposals, and design docs in the
devdocs/folder. Solutions go indevdocs/solutions/, brainstorming goes indevdocs/brainstorms/, etc. The only exception isplans/which stays at the repo root for visibility. - For any Tambo-owned URLs you include in comments or documentation, use the
tambo.codomain (not the legacy.aidomain). When you encounter existing Tambo-owned links using the legacy domain, prefer updating them totambo.coas part of related changes. External (non-Tambo) links are fine.