There’s a particular kind of developer content that has flooded engineering blogs and conference talks over the past year–someone opens a chat window, types a vague description of what they want to build, and thirty seconds later they have a working application. The demos are impressive. Even fearing. But the implication that “this is how professional software development works now” is reckless and misleading.
This style of working has become known as “vibe coding”. This term was coined by Andrej Karphathy, and it means something real. For hobbyist projects, throwaway scripts, and early-stage prototypes, treating an LLM as a magic autocomplete and iterating until something approximately correct emerges is a true productive tool. The feedback loop is tight, the stakes are low, and the constraint becomes your output description skill.
Enterprise software is a different discipline. A large monorepo serving thousands of users, maintained by dozens of engineers with a live product in production, is not a context where “ship whatever the model suggests” is a viable strategy. Circular import graphs, broken accessibility contracts, leaked environment variables, and reinforced legacy patterns are the kinds of consequences that do not show up in a demo but do show up in midnight incident reports.
The discipline that actually works at scale is something we have been calling, here at Credo AI, “agentic coding”: a workflow in which AI agents autonomously plan, execute, and iterate on code changes, but are guided throughout by explicit rules, rich project context, expert human engineers with oversight at the decisions that matter. The agent is not a ‘magic box’. It is a capable collaborator that needs clear standards, a well-defined working environment, and a review who knows when to push back.
So let’s open this ‘magic box’, and break down what makes it–when applied right by the right people–so magic.
The Rule System
In a large monorepo, an agentic tool like Cursor or Claude Code is only as good as the rules it operates under. It’s never about the environment, it’s always about the constraints. Rules are machine-readable guidelines that govern how the agent makes decisions: which imports are permitted, which components to reach for, how files should be named, which modules are undergoing active migration, and what to do when it encounters legacy patterns.
Think of rules as the equivalent of linting configuration, but for architectural guidance. An ESLint rule can tell the agent never to use “var” in JavaScript. A Cursor rule can tell it never to reach for barrel imports from the design system, or that any file touching a specific domain must import its API client from an internal HTTP module vetted by your engineering team and never construct HTTP calls directly.
Layering Rules by Scope
The most robust rule systems are layered. At the broadest level, workspace-wide rules capture conventions that apply everywhere: import path policies, component selection, test framework usage, TypeScript configuration expectations. These do not have to be written in a file; they are often inferred from the existing files.
However, there are exceptions; for instance, rules that determine how a feature module can or can’t depend on other library categories. At the most specific level, file-level overrides handle edge cases that the broader rules wouldn’t anticipate.
This is often how senior engineers think about a complex codebase. When onboarding a new team member, you don’t recite every decision from first principles every time. You point to the conventions document, the architectural decision records, and the code review checklist. Rules externalise the same knowledge into a form the agent can consume it without extrapolating the context window.
Keeping Rules Evergreen
Rules can’t be set-and-forget. A codebase going through a migration, say, between a raw SQL practice to a developer-friendly ORM tool, or a UI library to a design system (as ours is) will reach a moment where the default assumption should flip. Early in the migration, the rule might prompt the agent to consider migrating a file only if it is touching a large proportion of legacy imports. Later, once adoption is high enough, the rule might instruct the agent to migrate unconditionally. Your rules are like policies created by a lawmaker. As society shifts, your laws need to be changed.
We’ve noticed, during the last year of heavy changes in our product, that it is good practice to give agents a target to act within. For instance, “if we refactor X% of a file using legacy Y, migrate the file to use Z”. Investing a small amount of time to review and update rules as the codebase evolves pays exponential dividends that compound across every subsequent agent-assisted change.
What Well-Structured Rules Look Like
In our monorepo, the .cursor/rules folder contains discrete rule files for each major concern: design system migration, data table conventions, data handling patterns, testing practices, and Next.js-specific constraints. Each file is focused, named descriptively, and linked to the part of the codebase it governs. An agent working on a component in the main application can load only the rules relevant to that context rather than a single monolithic document that tries to cover everything, as an AGENTS.md would look like.
A simple example illustrates why this matters. A rule informing that an agent must “use the Icon component from X, identifying the desired icon by the prop ‘name’ as found in the Lucide icon library”. A human engineer reads this and comprehends the convention. An AI agent reads this and is constrained from ever writing import { Plus } from “lucide-react” ever again.
This is the moment that preciseness matters. It changes how you think about what belongs in a rule. Documentation tells you what exists. Governance constraints what is permitted. The best rules do both: they explain the intent (“Always use the icon wrapper so that icon usage is tracked and themeable”) and enforce a clear boundary (“never reach the underlying library directly”). 1 direction. 2 consumers.
Commands & Plans
Rules are constraints. We’ve addressed them. Commands and plans are the active side of the agentic workflow. It turns a high-level goal into a sequence of concrete, reviewable steps.
Commands as Reusable Recipes
A command is a reusable, parameterised recipe for a common multi-step task. Defining a command to facilitate creating storybook files in your repo is a great example of that scoped work.
Commands shine brightest when the task involves a self-contained feedback loop. Consider a command for adding Storybook stories to a design system component. Instead of giving it a one-shot prompt and hoping for the best, you can write a command that instructs the agent to follow a precise cycle. Instead of jargoning with it, here’s an example:
# Add Stories
Summary: This command is used when the agent is tasked with creating a storybook file or editing an existing storybook file for a component.
## Prompt
The user includes a file or component in their prompt.
If they don't provide you with a file or component, respond with "Please mention the file or component you want me to write the stories for."
Before you start writing the stories:
- Take your time and navigate the other stories (i.e., `*.stories.tsx`) to understand the standards.
- Do NOT assume behaviours based on only the props of the component you are writing the stories for; read through the component file and understand its behaviour.
Write the stories for all possible cases in the mentioned component.
Ensure that all accessibility tests pass.
## Verify your output
Once you finish writing the stories, ensure to double-check them.
Run Storybook and refine the stories if any errors come up. Fix any errors that come up if the output is not successful.
Fix all lint errors. If you need to iterate on the file, ensure to do so and verify the output every time you finish an iteration.
Ensure that the file you wrote abides by the rule described at `.cursor/rules/storybook.mdc`.
To summarise, this command tells the agent, “read the existing stories to understand the conventions, read the component source to understand its actual behaviour (not just its props), write stories for every meaningful variant, run Storybook, check that all accessibility tests pass, and iterate until the output is clean. This all happens before the human in the driver’s seat ever sees it.
Plans as Checkpoints for Complex Tasks
When a task is complex enough to span multiple files and require architectural decisions, migrating a page from MUI to Credo AI’s Cura Design System, say, or implementing a new feature that touches several layers of the stack, the agent should produce a plan before writing a single line of code. The plan articulates what it intends to change, in which order, and why. It surfaces the decisions that need a human eye before they become labouring.
This matters especially in enterprise monorepos because mistakes compound. A single wrong dependency direction, e.g. a shared utility library importing from a domain-specific feature module, can create circular imports that propagate across dozens of dependents. Catching that in a plan review costs nothing. Catching it in a build failure or a production incident costs significantly more.
We’ve faced this so many times. Engineers often describe themselves as “lazy”, and, believe it or not, agents inherit that behaviour.
Documentation as Prompt Engineering
The agent’s effectiveness is bounded by what you put in its context window. And the highest-quality context you can give it is your codebase’s documentation. In a large repo, the discipline is knowing which slice is relevant.
Do not think of Donald Duck.
You did think of Donald Duck, right? Agents do the same. If you tell them not to think of something, they’ll search for that, and stuff the context window with things that are unimportant. Tell your agent what it needs to know. Context that is too sparse leads to hallucinated interfaces; context that is too broad leads to the agent losing track of the constraints that matter.
Take a TypeScript project as an example–This is where JSDoc moves from being a nice-to-have to being essential. Consider what an agent actually sees when it reads a poorly annotated component. A handful of optional props typed as string or boolean, and nothing else. It can only guess at the valid values, then A happens if the boolean is null, and B happens if the boolean is undefined; it can only guess at the default behaviour, at the visual distinctions between variants, and which edge cases to cover. The absence of constraints is an invitation to hallucination.
Let’s roll back in time. Say the component has rich JSDoc, prop-level description that explain visual and behaviour effects, explicit defaults, typed union variants, and behavioural rules documented inline. Now, you know passing loading={true} removes the icons from the button. This sort of documentation serves the engineer reading, and it serves the agent as a constraint system. Again–1 direction. 2 consumers.
The same principle applies at the component level. A JSDoc block that describes the component’s anatomy, usage patterns, slot structure, and DOM identification attributes gives the agent a structural understanding that no amount of type inference can substitute for.
Stories function quite similarly. Every story is an acceptance criterion for the component. If the agent modifies a component and a story breaks visually, that is a failing test. Even without a formal assertion. The stories define what ‘correct’ looks like across every meaningful dimension.
As we know engineers often skim and not read, let’s make it simple:
Better docs = better AI;
Better AI = a reason to write better docs.
Architectural Decisions at Scale
This is the section that separates enterprise agentic coding from demo. The challenges that are trivial at small scale become load-bearing at large scale, the solutions require deliberate investment.
Enforcing Module Boundaries
The frontend and backend monorepo distinguishes between several categories of libraries: shared utilities, service abstraction, reusable components, and domain-specific feature modules. The dependency graph flows one way: domain modules may depend on the reusable components and shared utilities; design system and shared utilities modules must not depend on domain-specific modules. Violating this constraint creates circular imports and makes the codebase progressively harder to reason about.
Nx enforces these boundaries structurally through its integration with ESLint, but the rules layer extends this enforcement to the agent. A rule that says ‘this module lives in [the domain layer] – it must not import from the domain-specific module means the agent cannot accidentally introduce the violation, even when working quickly. It does not wait for the linter to catch it at commit time. The constraint is built in from the get-go.
Migration as First-Class Concern
Enterprise codebases are perpetually mid-migration. Our active migration from Material UI to Cura Kit, our design system, is typical: a large surface area, a long tail of legacy files, and a need to advance the migration incrementally without blocking ongoing feature development.
The temptation is to treat migration as a “background concern”, something the team will get to eventually and recurrently. The agentic approach inverts this. Rules can encode migration state: if the agent is touching a file that has a high proportion of legacy imports, or a high number of lines changed (e.g. >= 30%), the agent is prompted to migrate that file as part of the change. Here’s an example of how you write the rules:
# Design System Migration Rule
> **⚠️ LIVING DOCUMENT:** This rule must be updated whenever components are added, removed, renamed, or when the design system structure changes. See "Maintaining This Rule" section below.
## Context
We are actively migrating from Material UI (MUI) to our custom design system located at `@your-org/design-system`.
## Available Design System Components
Our design system (`@your-org/design-system`) provides the following components:
### Form Components
- `Input` - Text input field
- `Textarea` - Multi-line text input
- `Checkbox` and `CheckboxGroup` - Checkbox inputs
- `RadioGroup` - Radio button groups
- `Select` - Dropdown select
- `DatePicker` - Date selection
- `Calendar` - Calendar component
- `Form` - Form wrapper with validation
- `FormControl` - Form field control wrapper
- `FormSubmitAction` - Submit button wrapper (handles loading/disabled state)
- `FormAction` - Secondary action button wrapper
- `Label` - Form labels
- `HelperText` - Helper/error text
- `Toggle` - Toggle switch
- `SegmentedControl` - Segmented control
### Layout & Navigation
- `Card` - Card container
- `Dialog` - Modal/dialog
- `Sheet` - Slide-over panel
- `Popover` - Popover container
- `DropdownMenu` - Dropdown menu
- `Pagination` - Pagination controls
### Data Display
- `Table` - Basic table
- `DataTable` - Advanced data table with sorting, filtering, pagination
- `Badge` - Badge/pill component
- `Avatar` and `AvatarGroup` - Avatar components
- `Text` - Text/typography component
- `Image` - Image component
- `Skeleton` - Loading skeleton
- `ZeroState` - Empty state component
### Feedback
- `Alert` - Alert/notification messages
- `Banner` - Banner messages
- `Toast` - Toast notifications
- `Spinner` - Loading spinner
- `HoverCard` - Hover card
### Actions
- `Button` - Button component
- `IconButton` - Icon-only button
- `Link` - Link component
### Icons & Branding
- `Icon` - Icon component (with built-in icon set)
- `Logo` - Your organisation’s logo
### Utilities
- `Tooltip` - Tooltip component
- `Theme` - Theme provider
## Rules for Component Selection
### 1. ALWAYS Prefer Design System Components
When writing new code or modifying existing code:
- **ALWAYS** use components from `@your-org/design` instead of MUI components (`@mui/material`, `@mui/lab`, `@mui/icons-material`)
- **ALWAYS** import from `@credo-ai/design` using subpath imports (e.g., `@your-org/design-system/button`), not from MUI packages
- If a component is available in the design system, **NEVER** use the MUI equivalent
### 2. Common MUI to Design System Mappings
When you encounter MUI components, map them to design system equivalents:
| MUI Component | Design System Component |
| ------------------------- | ----------------------------------- |
| `TextField` | `Input` or `Textarea` |
| `Button` | `Button` |
| `IconButton` | `IconButton` |
| `Checkbox` | `Checkbox` |
| `Radio`, `RadioGroup` | `RadioGroup` |
| `Select`, `MenuItem` | `Select` |
| `Dialog`, `Modal` | `Dialog` |
| `Drawer` | `Sheet` |
| `Popover` | `Popover` |
| `Menu` | `DropdownMenu` |
| `Card` | `Card` |
| `Table`, `TableContainer` | `Table` or `DataTable` |
| `Pagination` | `Pagination` |
| `Alert` | `Alert` or `Banner` |
| `CircularProgress` | `Spinner` |
| `Skeleton` | `Skeleton` |
| `Tooltip` | `Tooltip` |
| `Avatar`, `AvatarGroup` | `Avatar`, `AvatarGroup` |
| `Badge` | `Badge` |
| `Switch` | `Toggle` |
| `Typography` | `Text` |
| `Link` | `Link` |
| MUI Icons | `Icon` (check built-in icons first) |
### 3. Migration Prompt Rule
**IMPORTANT:** When modifying a file:
1. **Analyse the file** before making changes:
- Count the total number of lines being modified
- Calculate the percentage of the file being modified
- Check if the file contains MUI imports (`from '@mui/material'`, `from '@mui/lab'`, etc.)
2. **If modifying ≥30% of a file that contains MUI imports:**
- **STOP** before applying changes
- **ASK** the user: "I notice this file is using Material UI components. Since we're modifying a significant portion of this file (X% of lines), would you like me to migrate the MUI components to our design system (`@your-org/design`) equivalents whilst I make these changes? This would include migrating [list specific MUI components found in the file]."
3. **Wait for explicit user consent:**
- **ONLY** proceed with MUI migration if the user explicitly agrees (e.g., "yes", "sure", "go ahead", "please do")
- If the user declines (e.g., "no", "not now", "just make the requested changes"), proceed with ONLY the requested changes without MUI migration
- If unclear, ask for clarification
4. **When migrating with user consent:**
- Replace MUI imports with design system subpath imports (e.g., `@your-org/design-system/button`, not `@your-org/design-system`)
- Update component usage to match design system APIs
- Update props to match design system component interfaces
- Preserve all functionality and behaviour
- Ensure visual consistency where possible
- Add comments if behaviour differs significantly
### 4. New Component Development
When creating new components or features:
- **ALWAYS** start with design system components
- **NEVER** introduce new MUI dependencies
- If a needed component doesn't exist in the design system:
1. First, check if an existing design system component can be composed/extended
2. If not available, inform the user and ask whether to:
- Build a custom component using design system primitives
- Temporarily use a different approach
- Add the component to the design system (if appropriate)
3. **DO NOT** default to MUI without explicit user approval
### 5. Import Patterns
Correct import patterns:
```typescript
// ✅ CORRECT - Use design system with subpath imports
import { Button } from '@your-org/design-system/button';
import { Input } from '@your-org/design-system/input';
import {
Dialog,
DialogHeader,
DialogContent
} from '@your-org/design-system/dialog';
// ❌ INCORRECT - Don't use MUI
import { Button, TextField, Dialog } from '@mui/material';
// ❌ INCORRECT - Don't use barrel imports
import { Button, Input, Dialog } from '@credo-ai/design';
```
### 6. When to Mention Migration
Proactively suggest migration when:
- Creating new features in files that currently use MUI
- Refactoring existing code that uses MUI
- Fixing bugs in MUI-heavy files
- Modifying ≥30% of a file containing MUI imports
### 7. Documentation
When implementing design system components:
- Refer to the design system Storybook at `http://localhost:6006` (when running `npm run storybook`)
- Check component props and APIs in the design system source code
### 8. Maintaining This Rule
**CRITICAL:** This rule document must be kept in sync with the design system.
#### When to Update This Rule
Update `.cursor/rules/{this rule’s filename}.mdc` immediately when:
1. **New components are added** to `@credo-ai/design`
- Add the new component to the appropriate category in "Available Design System Components"
- If it replaces an MUI component, add a mapping entry in the "Common MUI to Design System Mappings" table
- Ensure the subpath import pattern is documented (e.g., `@credo-ai/design/component-name`)
- Update import pattern examples if the new component introduces new patterns
2. **Components are renamed or removed** from `@credo-ai/design`
- Update all references to the old component name
- Update the component listings
- Update the MUI mapping table
- Update any code examples that reference the component
3. **The design system module is renamed or moved**
- Update all import statements (e.g., if `@credo-ai/design` becomes `@credo/design-system`)
- Update all references to the module name throughout the rule
- Update the Storybook URL if it changes
- Update file paths and documentation links
4. **Component categories change**
- Reorganise the "Available Design System Components" section to match the new structure
- Maintain alphabetical order within categories where appropriate
5. **New MUI equivalents are discovered**
- Add them to the "Common MUI to Design System Mappings" table
- Document any notable differences in behaviour
#### Who Should Update This Rule
- **Design system maintainers:** Must update this rule as part of any component addition, removal, or structural change
- **Engineers:** Should proactively update this rule if they notice discrepancies between the rule and the actual design system
- **Cursor AI:** Should inform the user if it detects that a component referenced in code doesn't exist in the rule or vice versa
#### How to Verify Rule Accuracy
Periodically verify this rule against the design system:
1. Check `path-to-your-design-system` directory structure for available components (each component has its own subdirectory)
2. Review component categories in Storybook to ensure alignment
3. Verify that all subpath imports are correct (e.g., `@your-org/design-system/button`, not barrel imports)
4. Test that example code snippets are valid
#### Rule Version Information
- **Design System Module:** `@your-org/design-system`
- **Design System Location:** `packages/design-system/`
- **Last Updated:** XXXX-XX-XX (Added Component Usage Best Practices section)
- **Update Instructions:** When making changes, update the "Last Updated" field with the current date and briefly note what changed
## Component Usage Best Practices
### Text Component for Headings
When using `Text` for page headings, use the `asChild` prop with a semantic HTML heading element for better accessibility.
✅ **Correct** - Semantic heading with Text styling:
```tsx
<Text variant="h2" asChild={true}>
<h2>Page Title</h2>
</Text>
```
❌ **Wrong** - Text renders a span by default, which lacks semantic meaning:
```tsx
<Text variant="h2">Page Title</Text>
```
### Tooltip Component API
Use `Tooltip`, `TooltipTrigger`, and `TooltipContent` for tooltips.
✅ **Correct**:
```tsx
import {
Tooltip,
TooltipTrigger,
TooltipContent
} from '@your-org/design-system/tooltip';
<Tooltip>
<TooltipTrigger asChild={true}>
<IconButton icon="info" />
</TooltipTrigger>
<TooltipContent>Helpful information</TooltipContent>
</Tooltip>
```
❌ **Wrong** - Using non-existent `content` prop:
```tsx
<Tooltip content="Helpful information">
<IconButton icon="info" />
</Tooltip>
```
## Exceptions
The following are acceptable reasons to use non-design-system components:
1. The component genuinely doesn't exist in the design system and there's no reasonable alternative
2. The user explicitly requests using a different library
3. Legacy/third-party integration requirements (document these clearly)
In these cases, always inform the user and document the decision.
## Enforcement Priority
Priority order when making decisions:
1. Use the design system components (HIGHEST PRIORITY)
2. Compose existing design system components
3. Build custom using design system primitives
4. Use alternative libraries (ONLY with user approval)
5. Use MUI (ONLY for maintenance of existing code and with user approval)
### Active Rule Maintenance
When Cursor AI detects any of the following, it should proactively inform the user:
- A component is imported from `@credo-ai/design` that is **not listed** in this rule
- A component listed in this rule **does not exist** in `libs/ui/design/src/lib/`
**Action:** Remind the user to update `.cursor/rules/design-system-migration.mdc` and offer to help make the necessary updates.
By always applying this rule, your coding assistant can help you make the call on whether to migrate a file you’re editing, especially if the changes in the file are measured large (i.e. >= 30% of total lines). Over time, this means the migration advances in proportion to development activity.
Consistency Over Cleverness
Agents can generate novel abstractions that are technically correct but violate team conventions. A competent engineer reviewing such a change might recognise that it works, but also that it looks nothing like the surrounding code; different naming patterns, different state management approaches, different component composition strategies, etc. The cognitive overhead of understanding a codebase increases every time that happens.
Rules act as a guardrail against unnecessary novelty (Fancy word meaning creating a lot of things you didn’t ask for). The goal is not to prevent the agent from being “creative”; it is to make sure that new code looks like it belongs in the codebase, as in the same as you do when your team agrees on what to enforce in your linter and formatter. That’s like a colleague picking it up six months later, they don’t have to wonder why this particular file uses a completely different pattern from everything around it. Consistency is a form of kindness to your future team, which is why team rules + code review matter even more now in the world of AI.
Build System Awareness
In a complex monorepo like ours, where mixed build tools coexist, e.g. Vite, TSC, SWC, the agent needs to understand which build tool applies to which module category. Telling the agent once won’t make it remember it. So, writing commands that specify build tools by module category, and tools like the Nx MCP server that expose the workspace’s build graph directly to the agent, so it reduces the surface area for these kinds of errors.
To avoid extra complexity, I’ll give you a simpler, concrete use case that we use a lot here at Credo AI: unit testing as system awareness. If we look back at the amount of unit tests compared to now, it would scare the nerds who like stats. And we increased our code coverage not only by “prompting agents” to add new tests to the codebase, we’ve built a system around it.
First, we defined what successful tests looked like to us. So, we wrote a rule called “testing-guidelines”. This tells the agent that we want unit tests to avoid “describe” sections, that we want the AAA pattern across the board, that mocking should have a specific pattern so that we know what methods are being mocked.
Then, we write a command (e.g. “add-unit-tests.md”) with specific instructions, so that the agent can produce unit tests. See the example below:
# Add unit tests
Summary: This command is invoked when the agent is tasked with writing unit tests for a file.
## Prompt
The user must include a file in their prompt.
If they don't provide you with a file, respond with "Please mention the file that you want me to help you write the unit tests for."
Write the unit tests for all possible cases in the mentioned file.
Ensure that all test cases respect the rules at `.cursor/rules/testing-guidelines.mdc`.
## Verify your output
Once you finish writing the unit tests, ensure to double-check the unit tests.
Run the unit tests and refine the tests if any errors come up. Fix any errors that come up if the output is not successful.
Ensure to fix all lint errors as well. If you still need to iterate on the file you created, ensure to do so and verify the output every time you finish an iteration.
The rule to determine if something is worth being a command is simple: is it mechanical? Is it a recurring task? Does it expect the same output for the same input? Then it deserves to be a command. We use a similar command for generating Storybook stories, which is often repetitive and labouring to write, and agents rarely get it right
Accessibility and Security in Agentic Workflows
Two areas where AI-generated code most consistently falls short, and where the stakes at enterprise scale are the highest.
Accessibility
Agents are good at generating code that looks correct. They are less reliable at generating code that is correct for users who navigate primarily by keyboard, rely on screen readers, or depend on high-contrast visual modes. The gap between visual correctness and semantic correctness is precisely the sort of thing that slips through in a fast-moving agentic workflow. We’ll talk more about this soon in a dedicated blog post about our design system.
Security
Security concerns in agentic workflows are a superset of the concerns in any fast-moving team or development context, with the added dimension that the agent’s confidence can sometimes mask the absence of caution.
On the server side: rules should enforce input validation, authentication boundary enforcement, and explicit policies around what the agent may and may not expose. We encountered this directly when working on user permissions – it is easy for a model to generate an abstraction for user permissions, but it often allowed for broader access for user roles that should not have such a permission. A rule for this draws a constraint that prevents the generation of the insecure configuration in the first place.
On the client side: XSS prevention patterns, secure handling of tokens and credentials, and CSP-aware code generation are all areas where rules add value. An agent that knows the project’s Content Security Policy will not generate inline event handlers or dynamic script injection that would violate it.
Dependency hygiene is an often-overlooked perspective. Agents will sometimes reach for an npm package to solve a problem that could be solved with a few lines of code, or pull in a package that duplicates something already available in the monorepo. Rules that restrict package introduction – requiring explicit approval, or at minimum prompting the agent to check if a suitable utility already exists in our codebase – reduce both the attack surface and the bundle size.
Empowering Product Managers: Where ‘Vibe Coding’ Actually Shines
Having spent several thousand words cautioning against the looser style of AI-assisted development, it is worth being precise about where it does add genuine value. The answer is: anywhere that the output is a communication tool.
Concept Prototyping
Product managers at Credo AI routinely need to communicate complex AI governance workflows to stakeholders who are not engineers. A working prototype – even a rough one generated in an afternoon with an AI assistant – communicates interaction, flow, and intent in a way that it can validate the product. When a PM can point to a prototype and say, ‘like this, but with proper validation and our design system’, the feedback loop between product and engineering shortens measurably.
This is a legitimate use of the vibe coding style. The stakes are low, the output is disposable, and the goal is communication. The PM is not expected to produce production-quality code. This is not their job. They are expected to produce something that makes a conversation more concrete.
Guardrails for Non-Engineers
The precondition for this to work safely is a sandbox. PM prototypes should live in a dedicated prototype repository or an isolated environment that has no path to the production codebase. The freedom to experiment is predicated on the absence of blast radius. This is not a difficult thing to set up, and it is an investment that pays off quickly.
Where the Handoff Happens
The PM’s prototype is an input to the engineering process, not a deliverable from it. It informs the specification, surfaces edge cases early, and gives engineers a concrete reference point. The engineers then build the real thing using the disciplined agentic workflow described in the rest of this post. The boundary between ‘vibe code as communication’ and ‘agentic code as production delivery’ is the point where the discipline changes, and it is a boundary worth being explicit about.
What We Got Wrong (and What We’re Still Figuring Out)
Engineering blog posts that only describe successes are marketing. The honest version includes the failures.
Subtle Runtime Failures
There have been cases where the agent produced code that passed TypeScript’s type checker and our test suite, but failed at runtime in ways that were genuinely difficult to trace. Type safety is a necessary condition for correctness. The agent’s confidence in its output can sometimes be higher than the output deserves, and reviewers need to approach agent-generated code with the same critical eye they would apply to a junior engineer’s pull request.
The Rule Strictness Tension
Too many rules and the agent becomes rigid, failing to adapt to legitimate edge cases. Too few and it drifts, producing output that is individually reasonable but collectively inconsistent. Finding the right level of specificity for each rule is an ongoing calibration, not a one-time decision. We have iterated on our rule set considerably since we started, and we expect to keep iterating.
Context Window Limitations
Working across many modules in a large monorepo strains the context window. When a task requires the agent to reason about interactions between several distant parts of the codebase, the risk of it losing track of an earlier constraint, or hallucinating a detail it cannot actually see, increases. We haven’t fully solved this. Better tooling for structured context injection, more modular task decomposition, and richer module documentation are the directions we are pursuing.
Measuring Impact
The honest answer to ‘does agentic coding improve velocity?’ is: we believe so, but we have not measured it rigorously. The effort does not disappear; it shifts from writing code to establishing constraints, reviewing plans, and managing context. If that shift represents a net improvement for your team depends on factors specific to your codebase, your review culture, and the sorts of tasks you are doing most frequently. We would be sceptical of anyone who claims a precise velocity multiplier. The evidence is anecdotal, and the comparison baseline is still a bit murky.
The Agent is a Junior Engineer with Perfect Memory
Here is the framing that has proven most useful in practice: treat the agent the way you would treat a capable but inexperienced team member who has read every piece of documentation you have ever written.
A strong junior engineer can implement a well-specified task, follow established patterns, and flag when something doesn’t fit the pattern they were given. They are less reliable when the specification is vague, when the task requires judgment that wasn’t explicitly communicated, or when they need to make architectural decisions that depend on context they don’t have. They improve dramatically when you invest in onboarding, give them clear standards, and review their work with specificity.
The agent is the same, with one significant difference: its memory is perfect, in the sense that it’ll apply a rule every time, without the drift or selective forgetting that affects human team members. This is a superpower if the rules are good. It is a liability if they are wrong.
The investment that makes agentic coding work at enterprise level is the investment in the infrastructure that makes the agent’s output predictable: the rules, the commands, the plans, the module documentation, and the context management discipline. This is engineering work. It doesn’t feel as immediately gratifying as shipping a feature, but it compounds in the same way that any investment in code quality and tooling compounds, and at a much faster rate, because the beneficiary isn’t just the team, but every subsequent agent interaction with the codebase.
DISCLAIMER. The information we provide here is for informational purposes only and is not intended in any way to represent legal advice or a legal opinion that you can rely on. It is your sole responsibility to consult an attorney to resolve any legal issues related to this information.






