A coding agent makes safe backend changes when it can read the current state of the system, understand the rules that constrain it, and only act through tools that limit the damage of a mistake. In practice that means four things: configuration that is declarative and in one place, schemas that act as contracts, a structured way to expose tools and resources (the Model Context Protocol is one), and guardrails such as dry runs, reviewed migrations and least-privilege credentials. Without them, an agent guesses, and guesses against a production backend are expensive.
Why do agents struggle with backends more than with UI code?
Front-end changes are mostly local and reversible. You change a component, run the app, and see the result. Backend changes have long reach and slow feedback. A renamed column breaks a query three services away. A loosened permission is invisible until someone abuses it. A migration that works on an empty development database can lock a large table.
An agent also lacks the context a human accumulates over months. It does not know that one table is read by a nightly report, that an endpoint is called by an app version from two years ago, or that a certain environment variable is shared. If that knowledge lives in people’s heads, in chat history or in scattered code, the agent cannot use it. The fix is not a smarter prompt. The fix is to move the knowledge into places an agent can read and checks an agent cannot bypass.
Why does declarative configuration beat scattered glue code?
Compare two ways to define “premium users can create up to 50 projects”:
In the scattered version, the rule appears as an if in a route handler, again in a background job, again in a mobile client check, and once more in a SQL view. An agent asked to change the limit has to find every copy. It will miss one, and the code will still compile.
In the declarative version, the rule is data:
entitlements:
premium:
limits:
projects: 50
Everything else reads this value. An agent changes one line, and a validator can check that the file is well formed before anything runs. A reviewer reads a three-line diff instead of a cross-cutting change.
Declarative config helps agents for specific reasons:
- It is searchable and small. The whole state of a feature fits in the agent’s context.
- It is checkable. A schema can reject a typo before it reaches a server.
- It diffs cleanly. Reviewers see intent, not mechanics.
- It separates what from how. The agent states the desired behavior, and tested code applies it.
This does not mean everything should be YAML. Complex logic still belongs in code. The goal is to keep policy (limits, roles, schedules, product IDs, feature switches) as data and keep the code that interprets it small and well tested. When an agent has to write new glue code for common needs such as auth, billing state or reminders, every instance is new surface area that nobody has reviewed.
How do schemas act as contracts?
A schema is a written agreement about shape. Several kinds matter for backends:
- Database schema. Types, nullability, foreign keys and check constraints reject invalid data no matter which code wrote it. If “a subscription must belong to a user” is a foreign key, an agent that forgets it gets an error, not corrupt data.
- API schema. An OpenAPI document or generated types describe requests and responses. Generate client code from it so the mobile app and the server cannot silently drift.
- Config schema. JSON Schema or a typed config describes what the declarative files may contain.
Contracts help agents because they turn vague failures into precise ones. Compare “the app crashed” with “field plan must be one of free, pro; got premium”. An agent can fix the second without human help. When you design validation errors, make them structured and specific: which file, which path, what was expected, what was found, and where possible a suggested fix. Agents read these messages and act on them in a loop, so their quality sets how quickly the agent converges.
Treat generated types as part of the contract. If the server’s response shape changes, regenerating types should break the client build at compile time, not at runtime on a user’s phone.
What is the Model Context Protocol and how does it help?
The Model Context Protocol (MCP) is an open protocol for connecting AI applications to external tools and data. An MCP server exposes capabilities to a compatible client in a standard way, so an agent does not need custom integration code for each system. Servers typically offer:
- Tools: actions the agent can invoke, each with a name, a description and a typed input schema.
- Resources: read-only data the agent can load, such as a schema, a document or a log.
- Prompts: reusable templates that guide a task.
For backend work, an MCP server is a place to put a safe, narrow interface. Instead of giving an agent a shell and a database password, you can expose tools like list_tables, describe_table, validate_config and plan_migration. Each tool does one thing, validates its input, and can enforce policy server-side. Resources can publish the current schema and the config reference, so the agent reads authoritative documentation instead of recalling it from training data, which may be out of date.
Design tools the way you would design a public API:
- Prefer many small, well-named tools over one tool that accepts arbitrary commands.
- Make read tools and write tools distinct, so permissions can differ.
- Return structured results and structured errors.
- Keep descriptions accurate, since the agent chooses tools based on them.
- Treat everything the agent sends as untrusted input and validate it.
The protocol details evolve. Check the Model Context Protocol specification and documentation for the current version before you build against it.
Which guardrails make agent changes safe?
Context tells the agent what is true. Guardrails limit what happens when it is wrong. Four are worth setting up first.
Dry runs. Every command that changes state should have a mode that reports what it would do without doing it. For a deploy, that is a plan listing the resources it would create, change or remove. For a config change, it is a diff of the effective settings. Make the dry run the default, and require an explicit flag to apply. Agents then show you the plan before anything changes.
Reviewed migrations. Database migrations are the highest-risk backend change. Keep them as files in version control, generate or write them as explicit SQL, and require review before they run against anything shared. Check for operations that are risky on large tables, such as adding a column with a volatile default or building an index without the concurrent option on PostgreSQL. A migration should be reviewed as a diff, not discovered at deploy time. Run it first against a copy or a local database with realistic data.
Least-privilege credentials. Give the agent the weakest credentials that let it do its job. A development database user that can only touch the development schema. A read-only role for inspection tools. Short-lived tokens that expire. Never put production credentials in an agent’s environment. If a tool needs to affect production, route it through a gated step that a human approves, and keep that step separate from the agent’s normal workflow. Separate roles for read and write also make it easier to audit what the agent did.
Tests that run locally. The best guardrail is a fast feedback loop. If the agent can start a local backend, load seed data, run scenario tests and read the output in a minute, it will catch its own mistakes. If the only environment is shared staging, it will not, and neither will you.
A short checklist you can adapt:
| Risk | Guardrail |
|---|---|
| Wrong config value | Schema validation before apply |
| Destructive command | Dry run by default, explicit apply |
| Bad migration | Review as SQL, test on realistic local data |
| Leaked or abused credential | Least privilege, short-lived tokens, no production secrets |
| Unreviewed side effects | Audit log of tool calls |
| Hidden client breakage | Generated types and tests against old clients |
How do you start with an existing backend?
You do not need a rewrite. Take small steps in this order:
- Write down the current schema and put it in version control if it is not.
- Gather the scattered rules (limits, roles, schedules) and find the five most important. Move them into one config file with a schema.
- Add a validate command that checks the config and prints structured errors.
- Add a local environment the agent can start and reset in one command.
- Expose read-only inspection tools first. Add write tools later, each with a dry run.
- Remove production credentials from any environment where an agent runs.
Write a short project file for your agent describing the commands, the layout and the rules it must follow, such as “never edit applied migrations; add a new one”. Many agent tools read a repository file for this purpose. The AGENTS.md convention is one open format, and Anthropic documents project memory files for Claude Code. Keep it short and factual, and update it when the rules change.
Key takeaways
- Agents are unsafe on backends mainly because context lives in people’s heads and scattered code. Move it into files and tools.
- Keep policy as declarative data with a schema, and keep the code that interprets it small and well tested.
- Use schemas (database, API, config) as contracts, and make validation errors structured and specific.
- MCP gives agents a standard way to use narrow tools and read authoritative resources. Design those tools like a public API.
- Make dry runs the default, review migrations as SQL, and give agents least-privilege, short-lived credentials.
- A fast local environment is the best guardrail, because the agent can check its own work.
How does this relate to Edict?
Edict is a mobile backend in development. It is being built around these ideas, with declarative configuration, structured validation errors, an MCP server and a local backend for agents to work against. The planned business logic layer is designed to keep policy such as limits and rules as typed configuration, and the planned database module is designed to compile declared entities into PostgreSQL. These are design goals for a product in development, and none of them are available today. How it works describes the planned flow.
For a framework-specific walkthrough, read backend context for an AI coding agent in React Native.