Precise enough for humans. Constrained enough for agents.
Carrier™’s advantage isn’t maximal language power. It’s that the backend surface is small, explicit, and compiler-checked. That is exactly the shape that lets code generators stay consistent across a project — and across drafts.
Six properties that matter
service. A tight scalar set plus Vector(N). Fewer ways to be wrong..carrier/manifest.json: models, routes, policies, jobs, schedules, clients, workflows, queues, tenants, flags, llm_clients. Tools read it instead of scraping.carrier check runs the full semantic pass — types, CRUD validity, auth/policy rules, idempotency scope, workflow dependency graphs, LLM tool schemas.00_models, 10_types, 20_actions, 30_routes) keeps generation order stable.carrier agent-docs writes AGENTS.md and CLAUDE.md into the project so Claude Code and Codex start with grounded, project-specific guidance.LLM clients belong inside the compiler contract
When a Carrier service calls an LLM, the tool surface is typed. Tool parameter schemas come from Carrier types. Tool dispatch reapplies auth and policy context. Every tool call is audited. Transcripts persist in carrier_llm_conversations.
fn search_help_docs(term: String) -> String[] { return [term, "billing", "password reset"]} action get_support_profile() -> MeResponse { let actor = auth.current_user() return { id: actor.id, email: actor.email, name: actor.name }} llm client SupportAgent { provider: env("LLM_PROVIDER", "openai") wire_format: env("LLM_WIRE_FORMAT", "openai") model: env("LLM_MODEL", "gpt-4.1-mini") api_key: env("LLM_API_KEY", "replace-me") max_turns: env_int("LLM_MAX_TURNS", 8) temperature: 0.2 system_prompt: "You are a concise support assistant." tool search_help_docs(term: String) -> String[] = search_help_docs tool get_support_profile() -> MeResponse = get_support_profile} route POST "/support/draft" protect Auth -> SupportTicketDraft { input: SupportReplyRequest handler { return SupportAgent.respond_as(SupportTicketDraft, { user_prompt: input.message conversation_id: input.conversation_id }) }}respond_as(TypeName, ...) returns a value matching the named Carrier type directly — no loose JSON, no manual schema glue.fn or action declarations. Tools run inside the same auth/policy session as the triggering route.audit.record("llm_tool_call", ...) and emits OTLP spans with carrier.client_name and token attributes.carrier agent-docs — the one command to run first
Before an agent writes a line of Carrier, run carrier agent-docs inside the project. Carrier writes AGENTS.md and CLAUDE.md so Codex and Claude Code start from project-specific, grounded guidance — not generic web-scraped patterns.
$ cd my-service$ carrier agent-docs # AGENTS.md + CLAUDE.md$ # now Claude Code / Codex have grounded project context$ claude 'add an idempotent POST /orders route'agent-docs pins the Carrier vocabulary, the multi-file layout, the CLI, and the compile/check loop inside the repo itself.The generation recipe, verbatim from docs/ai-authoring.md
This is the order the AI authoring guide recommends. It matches what the compiler expects in practice.
1. write carrier.toml2. declare one service (with telemetry if OTLP is available)3. add auth jwt if any route is protected4. create enum used by models or query defaults5. create model declarations (include Vector(N) when retrieval is in scope)6. create type declarations for I/O and pagination7. add crud for resource-like models8. add policy for role or tenant visibility9. add action for real business writes10. add custom route11. add workflow when a flow has durable steps / compensation / parallel branches12. add job / event / schedule / queue only when needed13. add tenant when tenant lifecycle matters14. add flag for percentage / tenant-scoped rollouts15. add stream / subscription / watch for realtime16. add client for outbound JSON integrations17. add llm client for typed LLM tools18. add pure fn helpers last19. add test blocks for the public route contractservice · optional auth jwt · type · route · optional model + crud · pure fn helpersaction with transactions · idempotent write routes · policy where role visibility matters · test blockscache · jobs · schedules · queues · events · audit · raw SQL · DB functions · workflow · llm client · Vector(N) · tenant RLS · OTLP telemetryWhat NOT to invent
Carrier is grounded, not aspirational. The AI authoring guide is explicit about what does not exist yet.
native fn or accept Vector(N) from upstream.after StepName for dependencies.return inside transaction { } — do the work in the block, return after.respond_as(TypeName, ...) instead of parameterised type arguments.