<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jeremy Rajan</title>
    <description>The latest articles on DEV Community by Jeremy Rajan (@jeremyrajan).</description>
    <link>https://dev.to/jeremyrajan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F285398%2Fad6316aa-ae4d-4559-8df6-bc6d81c009fa.png</url>
      <title>DEV Community: Jeremy Rajan</title>
      <link>https://dev.to/jeremyrajan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeremyrajan"/>
    <language>en</language>
    <item>
      <title>What I've Learned Scaling Engineering Organisations</title>
      <dc:creator>Jeremy Rajan</dc:creator>
      <pubDate>Mon, 06 Apr 2026 04:47:46 +0000</pubDate>
      <link>https://dev.to/jeremyrajan/what-ive-learned-scaling-engineering-organisations-1i8l</link>
      <guid>https://dev.to/jeremyrajan/what-ive-learned-scaling-engineering-organisations-1i8l</guid>
      <description>&lt;p&gt;Scaling an engineering organisation isn't primarily a technical problem. It's a systems problem — and the system includes people, processes, and culture just as much as it includes code.&lt;/p&gt;

&lt;p&gt;After 15 years of building and leading engineering teams across fintech, iGaming, and logistics, here's what I keep coming back to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hire for the problem, not the stack
&lt;/h2&gt;

&lt;p&gt;The best engineers I've worked with weren't defined by the languages they knew. They were defined by how they thought about problems. When you're scaling fast, you need people who can reason about trade-offs, not people who happen to know your ORM.&lt;/p&gt;

&lt;p&gt;This doesn't mean technical skills don't matter — they do. But the hierarchy is: &lt;strong&gt;problem-solving first, system thinking second, specific tech third.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Process should reduce friction, not create it
&lt;/h2&gt;

&lt;p&gt;Every process you introduce has a cost. The question isn't "is this best practice?" — it's "does this make us faster or slower right now?"&lt;/p&gt;

&lt;p&gt;I've seen teams drown in ceremony that made sense at a previous scale. Stand-ups that run 45 minutes. Sprint planning that takes half a day. Retrospectives that produce action items nobody tracks.&lt;/p&gt;

&lt;p&gt;Good process at scale looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Short feedback loops&lt;/strong&gt; — deploy often, measure everything, fix fast&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear ownership&lt;/strong&gt; — every service, every domain has a name attached&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal coordination overhead&lt;/strong&gt; — if two teams need a meeting to ship a feature, your architecture is wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture follows team structure
&lt;/h2&gt;

&lt;p&gt;Conway's Law isn't just an observation — it's a design tool. If you want independent, fast-moving teams, your architecture needs to support that. Shared databases, shared deployment pipelines, shared anything becomes a bottleneck.&lt;/p&gt;

&lt;p&gt;The most effective replatforming work I've done started with the org chart, not the system diagram.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability is not optional
&lt;/h2&gt;

&lt;p&gt;You cannot scale what you cannot see. Before optimising anything — performance, cost, reliability — you need to know what's actually happening in production.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structured logging from day one&lt;/li&gt;
&lt;li&gt;Distributed tracing across service boundaries&lt;/li&gt;
&lt;li&gt;Dashboards that answer questions, not just display metrics&lt;/li&gt;
&lt;li&gt;Alerts that are actionable, not noisy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've walked into organisations running 100+ services with no centralised logging. The first conversation is always the same: "We need to see before we can fix."&lt;/p&gt;

&lt;h2&gt;
  
  
  The work is never purely technical
&lt;/h2&gt;

&lt;p&gt;The hardest part of scaling engineering isn't the code. It's getting alignment between engineering, product, and the business on what matters. It's making trade-offs visible. It's saying "no" to the right things so you can say "yes" to the things that actually move the needle.&lt;/p&gt;

&lt;p&gt;That's the work I find most interesting — and it's why I do what I do.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're dealing with scaling challenges and want to talk through your situation, &lt;a href="mailto:jeremyrajan@gmail.com"&gt;get in touch&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The views expressed in this article are my own and do not reflect the opinions or positions of my employer.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://jeremyrajan.com/blog/scaling-engineering-orgs/" rel="noopener noreferrer"&gt;https://jeremyrajan.com/blog/scaling-engineering-orgs/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>leadership</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Writing an AI Skill Definition for Go Backend API Engineers</title>
      <dc:creator>Jeremy Rajan</dc:creator>
      <pubDate>Mon, 06 Apr 2026 04:47:41 +0000</pubDate>
      <link>https://dev.to/jeremyrajan/writing-an-ai-skill-definition-for-go-backend-api-engineers-31m1</link>
      <guid>https://dev.to/jeremyrajan/writing-an-ai-skill-definition-for-go-backend-api-engineers-31m1</guid>
      <description>&lt;p&gt;In a &lt;a href="https://jeremyrajan.com/blog/ai-adoption-strategy/" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;, I argued that centralised agent skill definitions are the key to scaling AI across an engineering org. In &lt;a href="https://jeremyrajan.com/blog/ai-engineering-context-layer/" rel="noopener noreferrer"&gt;the follow-up&lt;/a&gt;, I walked through how to build a context layer that connects your knowledge base to AI tooling.&lt;/p&gt;

&lt;p&gt;This post is the next step: what does an actual skill definition look like, why is it opinionated, and how do you enforce it across your entire engineering org?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a skill definition matters
&lt;/h2&gt;

&lt;p&gt;Give an AI coding assistant a Go codebase with no context and ask it to add a new endpoint. You'll get something that compiles. It might even work. But it won't match your architecture. It won't follow your error handling patterns. It won't use your shared libraries. It won't write the tests the way your team expects.&lt;/p&gt;

&lt;p&gt;The AI doesn't know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That your transport layer is deliberately dumb — just request mapping, no business logic&lt;/li&gt;
&lt;li&gt;That you use ULIDs, not UUIDs&lt;/li&gt;
&lt;li&gt;That all dependencies must be passed explicitly — no globals, no init()&lt;/li&gt;
&lt;li&gt;That new libraries need approval before being added to &lt;code&gt;go.mod&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;That integration tests must use testcontainers against a real database, not mocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without these rules, every AI-generated PR becomes a code review battle. The reviewer catches the violations, requests changes, the engineer fixes them — and the AI makes the same mistakes next time. You're paying for AI tooling but not getting the leverage.&lt;/p&gt;

&lt;p&gt;A skill definition fixes this. It's a configuration file that tells the AI &lt;strong&gt;how your team works&lt;/strong&gt; — the patterns, the conventions, the guardrails. Once it's in place, AI output matches your standards from the first generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built
&lt;/h2&gt;

&lt;p&gt;We maintain a Go backend service template — a golden path that every backend &lt;strong&gt;API service&lt;/strong&gt; in the organisation is built from. It's opinionated by design. Every API service follows the same layered architecture, the same testing patterns, the same deployment pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note on scope:&lt;/strong&gt; This skill definition covers API services — request/response workloads served over REST, gRPC, or GraphQL. Background workers, event consumers, cron jobs, and data pipelines have different concerns (concurrency patterns, retry semantics, idempotency, backpressure) and deserve their own skill definitions. Don't force a worker into an API template.&lt;/p&gt;

&lt;p&gt;The template already encodes our standards in code. But code shows &lt;em&gt;what&lt;/em&gt; — it doesn't explain &lt;em&gt;why&lt;/em&gt;, and it doesn't tell the AI what's off-limits. That's what the skill definition does.&lt;/p&gt;

&lt;p&gt;Here's what we considered when writing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture section: encoding the "why"
&lt;/h2&gt;

&lt;p&gt;The first thing the skill definition establishes is the architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transport (REST/gRPC/GraphQL) → usecase → storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it doesn't just state the layers — it explains the rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transport layer is dumb.&lt;/strong&gt; It maps requests to DTOs, calls the usecase, maps the response back. No business logic here. Ever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business logic lives in &lt;code&gt;internal/usecase/&lt;/code&gt;.&lt;/strong&gt; This is transport-agnostic. It uses plain Go structs and &lt;code&gt;context.Context&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage is behind interfaces.&lt;/strong&gt; Never use GORM directly in handlers or usecases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies are explicit.&lt;/strong&gt; All handler dependencies are passed as function arguments or struct fields. No globals. No init().&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why so explicit? Because without this, an AI will happily put business logic in a handler, call GORM directly from a resolver, or create a package-level database variable. It doesn't know these are violations unless you tell it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a new entity: the golden workflow
&lt;/h2&gt;

&lt;p&gt;One of the most common tasks is adding a new domain entity. Without guidance, an AI might start with the endpoint and work backwards. That's wrong in our architecture. The skill definition specifies the exact sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define the entity and storage interface in &lt;code&gt;internal/storage/database/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Define the usecase (interfaces, DTOs, implementations) in &lt;code&gt;internal/usecase/&amp;lt;entity&amp;gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Wire the transport handlers in &lt;code&gt;internal/transport/{rest,grpc,graphql}/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Register everything in &lt;code&gt;internal/bootstrap/bootstrap.go&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Write migrations via &lt;code&gt;make scaffold name=&amp;lt;entity&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Model → usecase → transport → bootstrap. Not the other way around. This is the kind of workflow knowledge that lives in senior engineers' heads. Encoding it means every engineer — and every AI tool — follows the same path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The library gate: governing dependencies
&lt;/h2&gt;

&lt;p&gt;This is one of the most important sections, and one that most AI configurations miss entirely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before reaching for an external library, check if the company shared libs already provide the functionality. This is a hard gate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We've seen what happens without this rule. Teams independently adopt three different HTTP client libraries, two logging frameworks, and four ways to handle configuration. Each one is individually reasonable. Collectively, they're a maintenance nightmare.&lt;/p&gt;

&lt;p&gt;The skill definition makes the approved dependency set explicit — Chi for HTTP routing, GORM for database access, testify for assertions, testcontainers for integration tests. If a library isn't in the template's &lt;code&gt;go.mod&lt;/code&gt;, it needs a discussion before it gets added.&lt;/p&gt;

&lt;p&gt;This is the kind of organisational guardrail that AI tools will never infer from the code alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing: non-negotiable
&lt;/h2&gt;

&lt;p&gt;The skill definition doesn't suggest tests. It mandates them:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tests are mandatory. No exceptions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And it's specific about what "tested" means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests&lt;/strong&gt; with gomock and testify, colocated with source files, run in parallel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration tests&lt;/strong&gt; with testcontainers against real Postgres — never mock the database in integration tests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage&lt;/strong&gt; enforced by CI via SonarQube&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The distinction between unit and integration tests matters. We got burned in the past when mocked database tests passed but production migrations failed. The skill definition encodes that lesson so no one has to learn it again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "never do this" list
&lt;/h2&gt;

&lt;p&gt;Every opinionated system needs explicit anti-patterns. Ours has eight:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Never overhaul the template — the structure is the standard&lt;/li&gt;
&lt;li&gt;Never modify &lt;code&gt;.github/&lt;/code&gt; — workflows are synced from the template&lt;/li&gt;
&lt;li&gt;Never copy-paste without understanding the structure&lt;/li&gt;
&lt;li&gt;Never commit generated code&lt;/li&gt;
&lt;li&gt;Never bypass the shared library gate&lt;/li&gt;
&lt;li&gt;Never write a handler without tests&lt;/li&gt;
&lt;li&gt;Never use raw GORM outside the storage layer&lt;/li&gt;
&lt;li&gt;Never hardcode secrets, URLs, or environment-specific values&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These aren't aspirational guidelines. They're hard rules. And they're the rules most likely to be violated by AI tools without explicit instruction — because an AI optimises for "working code," not "code that belongs in your system."&lt;/p&gt;

&lt;h2&gt;
  
  
  PR process: contract first, stack small
&lt;/h2&gt;

&lt;p&gt;The skill definition also encodes how work gets reviewed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contract first.&lt;/strong&gt; Before writing code, agree on the API contract — protobuf definition, GraphQL schema, or OpenAPI spec. The contract is the handshake between teams. Code comes after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack PRs for large features.&lt;/strong&gt; One concern per PR. If a PR touches usecase, storage, transport, and config simultaneously, it's too big. Break it down: model → usecase → transport → wiring. Each one is a reviewable, mergeable unit.&lt;/p&gt;

&lt;p&gt;This is the kind of process knowledge that usually lives in onboarding docs that nobody reads. Putting it in the skill definition means the AI actively follows it — suggesting stacked PRs when the scope gets large, asking about contracts before generating code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping it centralised: engineers can't change it
&lt;/h2&gt;

&lt;p&gt;The skill definition is only useful if it's consistent across every service. If individual teams can modify their copy, you end up with drift — and drift is worse than no standard at all.&lt;/p&gt;

&lt;p&gt;We solve this the same way we handle CI workflows: &lt;strong&gt;template sync.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our service template repository contains the &lt;code&gt;CLAUDE.md&lt;/code&gt; file alongside the &lt;code&gt;.github/&lt;/code&gt; workflows. When changes are pushed to the template, they automatically sync to all downstream service repositories. Engineers can't modify the file in their repos — it gets overwritten on the next sync.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;engineering-standards/         # Template repo (write access: platform team only)
  ├── .github/workflows/       # CI/CD pipelines
  ├── CLAUDE.md                # AI skill definition (backend-go.md)
  └── ...                      # Template code

payment-service/               # Downstream service repo
  ├── .github/workflows/       # ← synced from template
  ├── CLAUDE.md                # ← synced from template
  └── src/                     # Team's business logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The platform team owns the skill definition. They iterate on it based on code review patterns — if reviewers keep catching the same AI-generated mistakes, the fix goes into the definition, not into individual PRs.&lt;/p&gt;

&lt;p&gt;This creates a feedback loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI generates code → reviewer catches a pattern violation →
platform team updates CLAUDE.md → AI stops making that mistake →
across every service, immediately
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the difference between fixing a problem once and fixing it everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full skill definition
&lt;/h2&gt;

&lt;p&gt;Here's the complete &lt;code&gt;CLAUDE.md&lt;/code&gt; we use for Go backend services. It's opinionated, specific, and it works.&lt;/p&gt;

&lt;p&gt;&lt;a href="/downloads/backend-go.md"&gt; Download backend-go.md&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;This codebase follows a clean layered architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transport (REST/gRPC/GraphQL) → usecase → storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transport layer is dumb.&lt;/strong&gt; It maps requests to DTOs, calls the usecase, maps the response back. No business logic here. Ever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business logic lives in &lt;code&gt;internal/usecase/&lt;/code&gt;.&lt;/strong&gt; This is transport-agnostic. It uses plain Go structs and &lt;code&gt;context.Context&lt;/code&gt;. It never imports transport-specific packages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage is behind interfaces.&lt;/strong&gt; Never use GORM directly in handlers or usecases. Always go through the repository interface in &lt;code&gt;internal/storage/database/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies are explicit.&lt;/strong&gt; All handler dependencies are passed as function arguments (REST) or struct fields (gRPC/GraphQL). Looking at a handler must tell you exactly what it depends on. No globals. No init().&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Adding a new entity/domain
&lt;/h3&gt;

&lt;p&gt;Think in usecases first, not endpoints.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define the entity&lt;/strong&gt; in &lt;code&gt;internal/storage/database/&lt;/code&gt; — model struct with the base Model embed (ULID generation), plus the storage interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define the usecase&lt;/strong&gt; in &lt;code&gt;internal/usecase/&amp;lt;entity&amp;gt;/&lt;/code&gt; — interfaces (Creator, Fetcher, Manager), DTOs, and implementations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wire the transport&lt;/strong&gt; in &lt;code&gt;internal/transport/{rest,grpc,graphql}/&lt;/code&gt; — handlers that call the usecase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Register in bootstrap&lt;/strong&gt; — add the new storage and usecase manager to &lt;code&gt;internal/bootstrap/bootstrap.go&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write migrations&lt;/strong&gt; — use &lt;code&gt;make scaffold name=&amp;lt;entity&amp;gt;&lt;/code&gt; to generate migration files.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The flow is always: model → usecase → transport → bootstrap. Not the other way around.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code conventions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Structure:&lt;/strong&gt; Follow the existing package structure exactly. Do not create new top-level packages. Use &lt;code&gt;internal/&lt;/code&gt; for all application code. Only &lt;code&gt;pkg/&lt;/code&gt; for truly reusable utilities. Generated code (mocks, swagger, GraphQL) is never committed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naming:&lt;/strong&gt; Handlers use &lt;code&gt;&amp;lt;Entity&amp;gt;&amp;lt;Action&amp;gt;&lt;/code&gt; (e.g., CreateTodo). Interfaces use role-based names (Creator, Fetcher, Manager) — not ITodoService. Errors are descriptive (ErrNotFound, ErrInvalidArgument).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error handling:&lt;/strong&gt; Errors are transport-specific. Usecases return plain Go errors. Transport layers map them to the appropriate format (HTTP status codes, gRPC AppError, GraphQL gqlerr). Never swallow errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration:&lt;/strong&gt; All config via environment variables with struct tags and sensible defaults. Never hardcode values. Config structs live in &lt;code&gt;internal/config/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IDs:&lt;/strong&gt; Use ULIDs, not UUIDs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependencies and libraries
&lt;/h3&gt;

&lt;p&gt;Use shared company libraries first. Before reaching for an external library, check if the company shared libs already provide the functionality. This is a hard gate.&lt;/p&gt;

&lt;p&gt;Do not add new libraries without approval. The template defines the approved dependency set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;Tests are mandatory. No exceptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit tests:&lt;/strong&gt; Colocated with source files. Use gomock for mocking, testify for assertions. Run with t.Parallel() where possible. Test behaviour, not implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration tests:&lt;/strong&gt; Use build tag &lt;code&gt;//go:build integration&lt;/code&gt;. Use testcontainers for real Postgres and Redis — never mock the database in integration tests. Containers auto-cleanup via tb.Cleanup().&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;p&gt;Observability is wired into the template from the start. Structured logging with context and correlation IDs. Prometheus metrics pre-configured. Health checks at &lt;code&gt;/internal/health&lt;/code&gt; and &lt;code&gt;/internal/metrics&lt;/code&gt;. Panic recovery built into all transport layers.&lt;/p&gt;

&lt;p&gt;Do not add custom observability infrastructure. Use what the template provides.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;Performance is baked in, not an afterthought. But also: KISS. No N+1 queries. Use DataLoaders for GraphQL. Profile before optimising. Keep handlers thin.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you must never do
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Never overhaul the template&lt;/li&gt;
&lt;li&gt;Never modify &lt;code&gt;.github/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Never copy-paste without understanding the structure&lt;/li&gt;
&lt;li&gt;Never commit generated code&lt;/li&gt;
&lt;li&gt;Never bypass the shared library gate&lt;/li&gt;
&lt;li&gt;Never write a handler without tests&lt;/li&gt;
&lt;li&gt;Never use raw GORM outside the storage layer&lt;/li&gt;
&lt;li&gt;Never hardcode secrets, URLs, or environment-specific values&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  PR process
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Contract first.&lt;/strong&gt; Agree on the API contract before writing code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep PRs small.&lt;/strong&gt; One concern per PR. Stack PRs for large features: model → usecase → transport → wiring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI is the gatekeeper.&lt;/strong&gt; All tests, linting, secret scanning, and coverage must pass. If CI fails, fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed after we shipped it
&lt;/h2&gt;

&lt;p&gt;The most immediate impact was on code review. Reviewers stopped catching the same structural violations. AI-generated PRs started following the architecture — usecases in the right place, dependencies explicit, tests included.&lt;/p&gt;

&lt;p&gt;The subtler impact was on junior engineers. A junior with this skill definition gets AI output shaped by 15 years of backend engineering experience. They don't need to know &lt;em&gt;why&lt;/em&gt; we use ULIDs or &lt;em&gt;why&lt;/em&gt; the transport layer is dumb — the AI already knows, and the code it generates reflects that.&lt;/p&gt;

&lt;p&gt;That's the real leverage of centralised skill definitions: &lt;strong&gt;you encode your best thinking once, and every engineer benefits from it every day.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of a series on AI adoption for engineering teams. Start with &lt;a href="https://jeremyrajan.com/blog/ai-adoption-strategy/" rel="noopener noreferrer"&gt;the strategy&lt;/a&gt;, then read about &lt;a href="https://jeremyrajan.com/blog/ai-engineering-context-layer/" rel="noopener noreferrer"&gt;building the context layer&lt;/a&gt;. If you're building this for your org and want help, &lt;a href="mailto:jeremyrajan@gmail.com"&gt;get in touch&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The views expressed in this article are my own and do not reflect the opinions or positions of my employer.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://jeremyrajan.com/blog/ai-skill-definitions-go-backend-api/" rel="noopener noreferrer"&gt;https://jeremyrajan.com/blog/ai-skill-definitions-go-backend-api/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>backend</category>
      <category>machinelearning</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Building an AI Context Layer for Engineering Teams</title>
      <dc:creator>Jeremy Rajan</dc:creator>
      <pubDate>Mon, 06 Apr 2026 04:44:42 +0000</pubDate>
      <link>https://dev.to/jeremyrajan/building-an-ai-context-layer-for-engineering-teams-2ngi</link>
      <guid>https://dev.to/jeremyrajan/building-an-ai-context-layer-for-engineering-teams-2ngi</guid>
      <description>&lt;p&gt;In a &lt;a href="https://jeremyrajan.com/blog/ai-adoption-strategy/" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;, I outlined a four-step AI adoption strategy for engineering teams. The first step — building a knowledge layer — is the one most teams skip, and the one that matters most.&lt;/p&gt;

&lt;p&gt;This post is the practical follow-up. How do you actually build that knowledge layer when you have 50+ engineers, hundreds of Confluence pages, thousands of Jira tickets, and dozens of GitHub repos?&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: context fragmentation
&lt;/h2&gt;

&lt;p&gt;In any mid-to-large engineering org, knowledge lives in at least three places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Confluence&lt;/strong&gt; — architecture decisions, runbooks, domain models, onboarding docs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jira&lt;/strong&gt; — what's being built, why, by whom, and what's blocked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; — the code itself, PRs, reviews, comments, API contracts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An engineer working on Service A needs to understand how Service B's API behaves, what the Confluence architecture doc says about the integration, and whether there's a Jira ticket in flight that changes the contract.&lt;/p&gt;

&lt;p&gt;Without that cross-service context, AI generates code that compiles but breaks at integration. It's technically correct and practically useless.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The setup has three layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Knowledge index (Onyx)&lt;/strong&gt; — crawls and indexes your Confluence, Jira, and GitHub data into a searchable vector store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Context bridge (MCP)&lt;/strong&gt; — a lightweight server that lets Claude Code query the knowledge index on demand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Local guardrails (CLAUDE.md)&lt;/strong&gt; — per-repo configuration files that define conventions, patterns, and boundaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Engineer asks Claude Code to implement a feature
     │
     ├──▶ MCP → Onyx: fetches Jira ticket details
     ├──▶ MCP → Onyx: searches Confluence for relevant docs
     ├──▶ MCP → Onyx: finds related code across all repos
     ├──▶ Local: reads current codebase directly
     └──▶ Local: reads CLAUDE.md for conventions
     │
     ▼
Code generated with full organisational context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through each layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: The knowledge index
&lt;/h2&gt;

&lt;p&gt;You need something that crawls your tools, chunks the content, generates embeddings, and keeps everything in sync. You could build this yourself — the &lt;a href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation" rel="noopener noreferrer"&gt;RAG pattern&lt;/a&gt; isn't complicated — but at scale, you want something purpose-built.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/onyx-dot-app/onyx" rel="noopener noreferrer"&gt;Onyx&lt;/a&gt; (formerly Danswer) is open source, self-hosted, and has pre-built connectors for Confluence, Jira, GitHub, Slack, and Google Drive. You deploy it with Docker, point it at your Atlassian and GitHub credentials, and it handles crawling, chunking, embedding, and incremental sync.&lt;/p&gt;

&lt;p&gt;What matters here isn't the tool — it's the principle: &lt;strong&gt;centralise your organisation's knowledge into something AI can search.&lt;/strong&gt; Whether that's Onyx, Glean, or something you build internally, the outcome is the same: a single retrieval layer over everything your team knows.&lt;/p&gt;

&lt;p&gt;A few practical notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Incremental sync is non-negotiable.&lt;/strong&gt; You'll have thousands of documents. Full re-indexing on every change doesn't scale. The sync layer needs to track what's changed since the last run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata matters as much as content.&lt;/strong&gt; Every chunk needs to be tagged with its source, project, author, date, and type. This lets you filter queries — "only search Confluence" or "only this Jira project" — which dramatically improves relevance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale docs are a real problem.&lt;/strong&gt; If your Confluence has outdated architecture pages, the AI will confidently use wrong context. Build in a relevance scoring layer, or flag pages that haven't been updated in 6+ months.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Layer 2: The context bridge
&lt;/h2&gt;

&lt;p&gt;Once your knowledge is indexed, you need a way for Claude Code to query it. This is where the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;MCP is a standard that lets AI tools connect to external data sources through lightweight servers. You build a small MCP server that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accepts search queries from Claude Code&lt;/li&gt;
&lt;li&gt;Hits your Onyx API&lt;/li&gt;
&lt;li&gt;Returns relevant chunks with metadata&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The MCP server is configured in your Claude Code settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"org-knowledge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mcp-onyx-bridge/index.js"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ONYX_API_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://onyx.internal.yourcompany.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ONYX_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in place, when an engineer says "implement PROJ-456", Claude Code can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch the Jira ticket details, acceptance criteria, and comments&lt;/li&gt;
&lt;li&gt;Search Confluence for related architecture docs and domain context&lt;/li&gt;
&lt;li&gt;Find how other services handle similar patterns across your GitHub org&lt;/li&gt;
&lt;li&gt;Then generate code grounded in all of that context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bridge is intentionally thin. It's a translator between Claude Code and your knowledge index — not a place for business logic. Keep it simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: Local guardrails
&lt;/h2&gt;

&lt;p&gt;The knowledge index gives Claude &lt;strong&gt;what to know.&lt;/strong&gt; The guardrails tell it &lt;strong&gt;how to behave.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every repo gets a &lt;code&gt;CLAUDE.md&lt;/code&gt; file at its root. This is a plain markdown file that Claude Code reads automatically. It defines:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this service is and who owns it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;This is the payment-service, owned by the Payments squad.
It processes transactions via Stripe and publishes events to Kafka.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key dependencies and integration points:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Dependencies&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; webhook-service: receives payment status callbacks (see PROJ-234 for contract)
&lt;span class="p"&gt;-&lt;/span&gt; notification-service: triggered via Kafka events on payment.completed
&lt;span class="p"&gt;-&lt;/span&gt; Stripe API: all payment processing goes through our Stripe wrapper in lib/stripe/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conventions and patterns to follow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Conventions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; All API endpoints use the handler pattern in src/handlers/
&lt;span class="p"&gt;-&lt;/span&gt; Errors must use the AppError class from lib/errors.ts
&lt;span class="p"&gt;-&lt;/span&gt; Database queries go through the repository pattern, never raw SQL
&lt;span class="p"&gt;-&lt;/span&gt; All new endpoints need integration tests, not just unit tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What to never do:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Guardrails&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Never commit API keys or secrets — use environment variables
&lt;span class="p"&gt;-&lt;/span&gt; Never bypass the rate limiter middleware
&lt;span class="p"&gt;-&lt;/span&gt; Never write directly to the payments database from outside this service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the &lt;strong&gt;centralised agent skills definition&lt;/strong&gt; from the &lt;a href="https://jeremyrajan.com/blog/ai-adoption-strategy/" rel="noopener noreferrer"&gt;adoption strategy&lt;/a&gt;. When you standardise these files across your org, you get consistent AI behaviour everywhere — same patterns, same guardrails, same quality bar.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it comes together: a real example
&lt;/h2&gt;

&lt;p&gt;An engineer on the Payments squad picks up PROJ-456: "Add retry logic to payment webhook handler."&lt;/p&gt;

&lt;p&gt;They open Claude Code in the payment-service repo and type: "Implement PROJ-456."&lt;/p&gt;

&lt;p&gt;Here's what happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Claude reads &lt;code&gt;CLAUDE.md&lt;/code&gt; — knows this is the payment-service, knows the handler pattern, knows the error conventions&lt;/li&gt;
&lt;li&gt;Claude queries Onyx via MCP — fetches PROJ-456 from Jira, gets the description, acceptance criteria, and a comment from the tech lead saying "use exponential backoff, max 3 retries"&lt;/li&gt;
&lt;li&gt;Claude queries Onyx for "webhook retry" in Confluence — finds the architecture doc that specifies retry policy and SLA requirements&lt;/li&gt;
&lt;li&gt;Claude queries Onyx for related code across GitHub — finds that notification-service already implements retry logic with the same pattern, sees the exact backoff parameters&lt;/li&gt;
&lt;li&gt;Claude reads the local codebase — finds the existing webhook handler, the test patterns, the Kafka publisher&lt;/li&gt;
&lt;li&gt;Claude generates the implementation — with exponential backoff (not just simple retry), matching the existing notification-service pattern, following the handler convention, with integration tests&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the difference between "AI that writes code" and "AI that writes code that belongs in your system."&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollout
&lt;/h2&gt;

&lt;p&gt;Don't try to set up all three layers at once. The sequence matters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 1–2: Deploy Onyx.&lt;/strong&gt; Connect Confluence, Jira, and GitHub. Let it index. Validate that search results are relevant. This is your foundation — if the knowledge layer is bad, everything built on top of it will be bad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3: Build the MCP bridge.&lt;/strong&gt; A thin server that queries Onyx. Test it with Claude Code on one repo, with one senior engineer. Iterate on the query patterns — you'll learn what context Claude actually needs vs. what's noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 4–5: Write CLAUDE.md files for key repos.&lt;/strong&gt; Start with 3–5 high-traffic repos. Have the senior engineers who own those repos write them. These files encode years of tribal knowledge into something AI can use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 6+: Expand.&lt;/strong&gt; More repos, more engineers, more refined guardrails. The senior engineers who piloted the setup become the advocates.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unglamorous truth
&lt;/h2&gt;

&lt;p&gt;None of this is technically hard. Docker, a small MCP server, and some markdown files. The hard part is organisational:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Getting Confluence clean enough that AI doesn't hallucinate from outdated docs&lt;/li&gt;
&lt;li&gt;Getting engineers to write and maintain CLAUDE.md files&lt;/li&gt;
&lt;li&gt;Getting leadership to invest in the indexing infrastructure before they see results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the teams that do this work end up with something genuinely powerful: AI that doesn't just write code — it writes code that understands why the code exists, how it fits into the system, and what constraints it needs to respect.&lt;/p&gt;

&lt;p&gt;That's the context layer. And it's the difference between AI as a toy and AI as infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next up:&lt;/strong&gt; Want to see what a real skill definition looks like? Read &lt;a href="https://jeremyrajan.com/blog/ai-skill-definitions-go-backend-api/" rel="noopener noreferrer"&gt;Writing an AI Skill Definition for Go Backend Engineers&lt;/a&gt; — the full file, the reasoning behind it, and how to enforce it across your org.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the practical companion to &lt;a href="https://jeremyrajan.com/blog/ai-adoption-strategy/" rel="noopener noreferrer"&gt;A Practical AI Adoption Strategy for Engineering Teams&lt;/a&gt;. If you're building this for your org and want help with the setup, &lt;a href="mailto:jeremyrajan@gmail.com"&gt;get in touch&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The views expressed in this article are my own and do not reflect the opinions or positions of my employer.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://jeremyrajan.com/blog/ai-engineering-context-layer/" rel="noopener noreferrer"&gt;https://jeremyrajan.com/blog/ai-engineering-context-layer/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>devtools</category>
      <category>engineering</category>
    </item>
    <item>
      <title>A Practical AI Adoption Strategy for Engineering Teams</title>
      <dc:creator>Jeremy Rajan</dc:creator>
      <pubDate>Mon, 06 Apr 2026 04:44:40 +0000</pubDate>
      <link>https://dev.to/jeremyrajan/a-practical-ai-adoption-strategy-for-engineering-teams-2o27</link>
      <guid>https://dev.to/jeremyrajan/a-practical-ai-adoption-strategy-for-engineering-teams-2o27</guid>
      <description>&lt;p&gt;Most organisations start their AI adoption journey by buying a tool and hoping engineers figure it out. That's backwards. The tool is the easy part — the hard part is giving AI enough context to be genuinely useful, and enough guardrails to be safe.&lt;/p&gt;

&lt;p&gt;After helping engineering teams integrate AI into their workflows, here's the approach I keep coming back to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Build a knowledge layer over your codebase and docs
&lt;/h2&gt;

&lt;p&gt;Before you can get meaningful output from any AI system, it needs to understand your world — your architecture, your conventions, your domain language. Without that context, you get generic answers that sound plausible but miss the point.&lt;/p&gt;

&lt;p&gt;This means building a retrieval layer over your existing knowledge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt; — architecture decisions, runbooks, onboarding guides, API specs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code&lt;/strong&gt; — repository structure, naming conventions, shared libraries, deployment configs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Institutional knowledge&lt;/strong&gt; — the stuff that lives in Slack threads and senior engineers' heads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, this looks like setting up RAG (retrieval-augmented generation) pipelines, embedding your docs and code into searchable vector stores, or curating structured context files that AI tools can reference. Some teams go further and build proper knowledge graphs with explicit relationships between systems, teams, and domains — but you don't need that on day one.&lt;/p&gt;

&lt;p&gt;The point is: &lt;strong&gt;context before capability.&lt;/strong&gt; An AI tool with deep context about your specific codebase will outperform a more powerful model that knows nothing about how you work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Start small — solve specific problems, not everything
&lt;/h2&gt;

&lt;p&gt;Once your knowledge layer exists, resist the temptation to go big. Pick narrow, well-defined problems where AI can deliver value quickly and where failure is cheap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating boilerplate code that follows your existing patterns&lt;/li&gt;
&lt;li&gt;Writing and updating tests for existing functionality&lt;/li&gt;
&lt;li&gt;Summarising pull requests or flagging potential issues in code review&lt;/li&gt;
&lt;li&gt;Answering developer questions about internal systems using your docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is small enough to ship in a week, visible enough to build confidence, and — critically — scoped enough that you can evaluate whether the output is actually good.&lt;/p&gt;

&lt;p&gt;This phase is really about &lt;strong&gt;iterating on how you define what AI can do.&lt;/strong&gt; Every time you solve a small problem, you learn something about how to describe tasks, what context the model needs, and where it falls short. Those learnings compound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Centralise your agent skill definitions
&lt;/h2&gt;

&lt;p&gt;This is where most teams skip ahead, and it's where the real organisational leverage lives.&lt;/p&gt;

&lt;p&gt;As you solve more problems with AI, you'll accumulate a set of instructions, prompts, tool permissions, and behavioural constraints — what I call &lt;strong&gt;skill definitions.&lt;/strong&gt; These define what an AI agent can do, how it should do it, and what it should never do.&lt;/p&gt;

&lt;p&gt;Left decentralised, every team invents their own. You end up with inconsistent quality, no shared guardrails, and no way to audit what AI is actually doing across the organisation.&lt;/p&gt;

&lt;p&gt;Centralising these definitions gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt; — every team's AI tooling follows the same conventions and standards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guardrails&lt;/strong&gt; — you control what actions AI can take (read-only vs. write, internal tools vs. external APIs, which repos it can modify)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditability&lt;/strong&gt; — a single place to review and update what AI agents are permitted to do&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusability&lt;/strong&gt; — a skill definition that works well for one team can be shared across the organisation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, this looks like maintained configuration files, shared prompt libraries, or a platform team that owns the AI tooling layer. The specifics depend on your stack — but the principle is the same: &lt;strong&gt;treat AI agent behaviour as infrastructure, not individual preference.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Pilot with senior engineers before going wide
&lt;/h2&gt;

&lt;p&gt;When you're ready to expand, don't roll out to everyone at once. Start with your most experienced engineers.&lt;/p&gt;

&lt;p&gt;This isn't about gatekeeping — it's about feedback quality. Senior engineers can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spot hallucinations&lt;/strong&gt; — they know the codebase well enough to catch when AI confidently generates something wrong&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluate trade-offs&lt;/strong&gt; — they can judge whether AI-suggested code is merely correct or actually good&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refine skill definitions&lt;/strong&gt; — their feedback directly improves the guardrails and instructions for everyone else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build trust&lt;/strong&gt; — when senior engineers vouch for a tool, adoption across the team follows naturally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run the pilot long enough to iterate on your skill definitions at least two or three times. The goal isn't just to validate that the tool works — it's to harden the configuration so that when less experienced engineers use it, the guardrails are already in place.&lt;/p&gt;

&lt;p&gt;Only then do you open it up to the wider team — with documentation, training, and clear expectations about what AI is good at and where human judgement is still required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta-lesson
&lt;/h2&gt;

&lt;p&gt;The pattern here is deliberately boring: build context, start small, centralise control, expand carefully. It's the same playbook you'd use for any significant infrastructure change — because that's what AI adoption is. It's not a product launch. It's a change to how your engineering organisation operates.&lt;/p&gt;

&lt;p&gt;The teams I've seen succeed with AI aren't the ones that adopted the fanciest tools first. They're the ones that did the unglamorous work of building context and defining boundaries before scaling up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next up:&lt;/strong&gt; If you want the practical, hands-on version of this — how to actually connect Jira, Confluence, and GitHub to Claude for context-aware code generation — read &lt;a href="https://jeremyrajan.com/blog/ai-engineering-context-layer/" rel="noopener noreferrer"&gt;Building an AI Context Layer for Engineering Teams&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're thinking through AI adoption for your engineering team and want to pressure-test your approach, &lt;a href="mailto:jeremyrajan@gmail.com"&gt;get in touch&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The views expressed in this article are my own and do not reflect the opinions or positions of my employer.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://jeremyrajan.com/blog/ai-adoption-strategy/" rel="noopener noreferrer"&gt;https://jeremyrajan.com/blog/ai-adoption-strategy/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>ai</category>
      <category>machinelearning</category>
      <category>engineering</category>
    </item>
  </channel>
</rss>
