---
title: Where to put your code
description: Find the right home for functions, tools, agents, events, jobs, databases, storage, schemas, routes, and shared code.
pagination: false
sidebar:
  icon: folder-tree
---

Keep files with the service that owns their behavior, regardless of how callers reach them. For example, an order lookup belongs to
Orders whether it is reached from HTTP, another function, or an AI model. Its implementation stays in
`src/services/orders/functions/`; the entry points reference it.

## The placement map

The paths below are patterns, not files you must create in every domain. Replace `orders` with the owner.

| What you are adding                | Where it belongs                                                                                                           | Continue learning                                                                                         |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Public domain boundary             | `src/services/orders/service.ts`                                                                                           | [Define a service](/docs/modules/service/define)                                                          |
| Business operation                 | `src/services/orders/functions/create-order.function.ts`                                                                   | [Functions](/docs/learn/concepts/functions)                                                               |
| Event contract                     | `src/services/orders/events/order-created.event.ts`                                                                        | [Events: getting started](/docs/modules/events)                                                           |
| Event subscriber                   | `functions/*.function.ts` in the **receiving** domain, such as `src/services/receipts/functions/order-receipt.function.ts` | [Consume an event](/docs/modules/events/consume)                                                          |
| Task and its job                   | `src/services/receipts/tasks/send-receipt.task.ts` and `src/services/receipts/jobs/send-receipt.job.ts`                    | [Jobs: getting started](/docs/modules/jobs) and [schedules](/docs/modules/jobs/schedules)                 |
| AI tool                            | `src/services/orders/tools/lookup-order.tool.ts`                                                                           | [AI: getting started](/docs/modules/ai), then [tools](/docs/modules/ai/tools)                             |
| AI agent                           | `src/services/orders/agents/order-support.agent.ts`                                                                        | [AI: getting started](/docs/modules/ai), then [your first agent](/guides/ai/build-an-offline-agent)       |
| Cache                              | `src/services/orders/cache/prices.cache.ts`                                                                                | [Caching: getting started](/docs/modules/caching)                                                         |
| Object bucket                      | `src/services/assets/buckets/assets.bucket.ts` in the domain that owns the files                                           | [Storage: getting started](/docs/modules/storage)                                                         |
| Database service                   | `src/services/database/service.ts` using `defineDrizzleService`                                                            | [Database: getting started](/docs/modules/database)                                                       |
| Database table                     | `src/services/database/schema/`, exported through its `index.ts`                                                           | [Define the schema](/docs/modules/database/schema)                                                        |
| Custom data-model method           | `src/services/database/models/*.model.ts`, registered in the database service                                              | [Custom models](/docs/modules/database/models)                                                            |
| Database migration                 | Tracked SQL/metadata under `src/services/database/migrations`; runner owned by installed tooling                           | [Migrations](/docs/modules/database/migrations)                                                           |
| Domain schema                      | Beside its sole consumer, or a domain module such as `src/services/orders/schemas.ts` when shared locally                  | [Schemas](/docs/learn/concepts/schemas)                                                                   |
| Declared domain error              | `src/services/orders/errors/order-not-found.error.ts`                                                                      | [Errors](/docs/learn/concepts/errors)                                                                     |
| Prompt, constant, or helper        | Beside its consumer or in a focused module within the same domain                                                          | [Agent instructions](/docs/modules/ai/agents), [domain boundaries](/docs/modules/service/define)          |
| URL and HTTP method                | `src/routes/orders/route.ts`                                                                                               | [Services and routes](/docs/modules/service/routes)                                                       |
| HTTP middleware or input transform | `src/routes/middleware/*.middleware.ts` or `src/routes/transforms/`                                                        | [Middleware](/docs/modules/http/middleware), [request mapping](/docs/modules/http/requests)               |
| Environment contract               | `src/platform/env.ts`                                                                                                      | [Environment](/docs/learn/concepts/environment)                                                           |
| Provider and server configuration  | `relkit.config.ts`, importing infrastructure support from Platform                                                         | [Integrations](/docs/modules/integrations), [application configuration](/docs/learn/concepts/application) |
| Function and application tests     | `tests/unit/` and `tests/integration/`                                                                                     | [Testing](/docs/modules/operations/testing)                                                               |

Default-export each ordinary descriptor from its own file, following its capability's guide. Route files
are different: they export named HTTP methods. Plain helper modules are ordinary TypeScript; they do not
need a RELKIT descriptor or their own service.

## One selected service root

New applications place boundaries at `src/services/<name>/service.ts`. Existing flat
applications keep `src/<name>/service.ts`, and `relkit add` preserves the detected layout.
Choose one root for the whole application, including Database and Auth. A directory alone,
an empty `services/` directory, or a folder containing only helpers does not select a layout.
Discovery uses authored service and descriptor evidence.

Each service is an immediate child of the selected root. Every descriptor belongs beneath
exactly one boundary. Nested boundaries, mixed flat/grouped services, missing boundaries,
and an aggregate `src/services/service.ts` are structural errors. A source-only move keeps
logical IDs, URLs, exposure, resource ownership and policies stable. Use the
[optional migration guide](/docs/modules/service/migration) for coordinated moves and rollback.

## Imports follow the boundary

Service implementation files can import their own files, Platform, and another service's
public `service.ts` boundary. Routes can import service boundaries and Platform. Platform
can import only Platform within application source; `relkit.config.ts` can import Platform
fragments. Application code cannot depend on tests, migrations, scripts or other tooling.
Tooling can import the application for testing and operational work.

The compiler follows helper modules, re-exports, type-only edges and literal dynamic imports
using the project's effective TypeScript aliases. A barrel cannot make a private cross-service
import public. Computed dynamic imports and unresolved dynamic targets produce analysis-limit
warnings; they are not proof that the target respects a boundary.

Keep prompt text, JSON assets, schemas and local helper modules beside their consumer.
Assets move with their service but do not become independently discovered descriptors.

## Functions own behavior; dependencies grant clients

A function is where validated business input becomes a result. Keep HTTP parsing in routes and model
instructions in agents. The function declares the provider-backed resources it uses; putting a cache or bucket
in the same folder does not grant access automatically.

```ts title="examples/commerce/src/services/orders/functions/create-order.function.ts"
/** Prices validated order input through the declared cache, records request tracing and derives its receipt key. */
import { defineFunction } from "@relkit/app/functions";
import prices from "../cache/prices.cache.js";
import { receiptObjectName } from "../../../platform/receipt-object.js";
import { createOrderOutput, orderInput } from "../../../platform/schemas.js";

const createOrder = defineFunction({
  // RELKIT validates these schemas before the handler runs.
  input: orderInput,
  output: createOrderOutput,
  dependencies: {
    cache: { prices },
  },
  timeoutMs: 10_000,
  concurrency: 100,
  handler: async (input, context) => {
    // Dependencies are accessed through the checked execution context.
    const unitPrice = await context.trace.span(
      "pricing.calculate",
      { attributes: { "pricing.strategy": "catalog" } },
      () => context.cache.prices.getOrSet({ sku: input.sku }, async () => 1_000),
    );
    context.trace.event("pricing.calculated", { "pricing.unit_cents": unitPrice });
    context.trace.setAttributes({ "pricing.quantity": input.quantity });
    const totalCents = unitPrice * input.quantity;
    return {
      orderId: input.orderId,
      receiptKey: receiptObjectName(input.orderId),
      totalCents,
    };
  },
});

export default createOrder;
```

Here, `dependencies.cache` supplies `context.cache.prices`. Event publishers separately declare
`publishes` before using `context.events`; see [publish an event](/docs/modules/events/publish).
Neither permission is configured in `service.ts`. The price cache is an
Orders implementation detail, while `createOrder` can be a public operation.

Use [functions](/docs/learn/concepts/functions) for the handler and validation model,
[context](/docs/learn/concepts/context) for execution resources, and
[your first cache](/guides/caching/cache-product-prices) for a complete resource-backed example.

## Tools and agents stay with their business domain

Put an order lookup tool in Orders, not in a separate application-wide AI domain just because a model
calls it. A tool points at a function with `defineTool({ target: ... })` or adapts that function with
`.asTool()`. The schemas and business behavior stay with the function. Start with
[defining a tool](/docs/modules/ai/tools) or [turning a function into a tool](/docs/modules/ai/function-tools).

An agent's `agents/` file owns its instructions, schemas, model, tool allowlist, and execution limits.
Store long instructions in a local prompt module if that makes them easier to maintain. Plain text does
not need a new service. See [defining an agent](/docs/modules/ai/agents).

Native execution requires [installing `@relkit/langchain` and its selected SDKs](/guides/ai/build-an-offline-agent)
and configuring the required runtime profiles. A folder or generic agent contract does not select that engine.

To let a browser run an agent, expose its `client` contract and select it with the generated
`useAgent` hook. Every run, observation, control, and continuation supplies the application's stable
business thread ID. Agents and tools remain compiler-discovered domain files; do not add an `agents` or
`tools` map to `defineService`. A tool in another domain can target a public function from the owning
service; it should not import that domain's private tool file.

“Internal to the domain” does not mean “hidden from MCP.” When the endpoint is explicitly enabled,
tools are eligible by default unless disabled. Complete the separate [MCP SDK/setup](/docs/modules/ai/mcp)
and review [tool approvals](/docs/modules/ai/approvals) before exposing them.

## Events and jobs belong to the work's owner

Put the fact in the producing domain's `events/` directory and the reaction in the receiving domain's
`functions/` directory. For example, Orders defines `orders.created`, while Receipts defines the
`defineEventFunction` that reacts to it. The subscriber stays out of `service.functions`: it runs through
event delivery or replay, not direct invocation.

A task and job belong to the domain responsible for completing the durable work. The task stays in that
domain's `tasks/` directory and the job owns its public name, admission, and schedules. A schedule is an
option on the job, not a separate service or a reason to move the task. Follow [your first event](/guides/events/react-to-order-events),
[jobs](/docs/modules/jobs), and [schedules](/docs/modules/jobs/schedules) for the complete workflows.

## Resources do not go in the service member list

Caches and buckets live under their owning domains. Functions declare `dependencies.cache` or
`dependencies.buckets`, and `relkit.config.ts` supplies provider bindings for the selected profiles.
Changing the provider does not require moving the domain's business code.

If another domain needs access, expose an operation on the owner—such as uploading or reading a file—
instead of publishing the raw cache or bucket as a service member. See the
[storage tutorial](/guides/storage/upload-product-images) for a bucket, function, service, and route working together.

Database and authentication integrations have specialized boundaries: `src/services/database/service.ts` uses
`defineDrizzleService`, with table schemas beneath that domain; `src/services/auth/service.ts` uses
`defineBetterAuthService`. They are not `database` or `auth` options on `defineService`. Start with
[Database](/docs/modules/database) for setup, schemas, queries, and model customization. The compiler supplies
`context.database`; functions do not add a `dependencies.database` entry. Ordinary business domains
still expose operations through `defineService`. See the
[Database](/docs/modules/database) and [Auth](/docs/modules/auth) guidance when replacing older adapters, or
[Integrate Better Auth](/docs/modules/database/better-auth)
to share the current database with authentication. Continue with [Auth](/docs/modules/auth) for service setup,
sessions, route protection, clients, and authentication customization.

## Shared code is not a shortcut around ownership

Keep domain-specific schemas, constants, errors, and helpers with their owner. Other domains consume
public service operations instead of importing those private modules. A public function's declared
errors are part of its checked contract; you do not add an `errors` member map to the service.

Use `src/platform/` for genuinely shared, domain-independent support, such as the environment contract
or common schema primitives. Platform cannot import domains or routes. If a “shared” helper knows how
Orders calculates a total, it belongs to Orders.

Tests live outside `src/` in the generated layout. Unit tests can import the specific function they
exercise; integration tests verify the application's routes and runtime behavior. Do not add a domain
service just to hold tests.

## Let the compiler check the organization

Run these commands from your generated application's root after adding or moving artifacts:

```sh
bun run check
bun run typecheck
```

Expect a successful graph check and type check. Fix missing services, invalid IDs, unresolved targets,
and boundary errors before continuing. Do not register every file manually in `relkit.config.ts` or edit
`.relkit/generated`; the compiler derives the model from source.

Naming and folder-placement suggestions remain nonfatal convention warnings. They are
distinct from ownership, missing-boundary, mixed-layout and forbidden-import errors.

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

**Reference and source**

- [`@relkit/services` API](/docs/api/services)
- [`@relkit/functions` API](/docs/api/functions)
- [`@relkit/tools` API](/docs/api/tools)
- [`@relkit/agents` API](/docs/api/agents)
- [`@relkit/jobs` API](/docs/api/jobs)
- [`@relkit/events` API](/docs/api/events)
- [`@relkit/cache` API](/docs/api/cache)
- [`@relkit/buckets` API](/docs/api/buckets)

- [`examples/commerce/src/services/orders/functions/create-order.function.ts`](https://github.com/rel-kit/relkit/blob/main/examples/commerce/src/services/orders/functions/create-order.function.ts)
- [`templates/default/v1/agent/src/services/hello/agents/assistant.agent.ts`](https://github.com/rel-kit/relkit/blob/main/templates/default/v1/agent/src/services/hello/agents/assistant.agent.ts)
