AI Context Engineering untuk Arsitektur Multi-Service
13 min read

AI Context Engineering untuk Arsitektur Multi-Service

Ketika kamu develop satu service dengan bantuan AI coding agent, masalah baru muncul begitu service itu bergantung pada service lain: agent tidak tahu apa-apa soal API, payload, atau event dari service tetangga. Solusi paling gampang — clone semua repo ke satu workspace, atau paste seluruh dokumentasi API ke context — cepat jadi tidak scalable begitu jumlah service bertambah. Artikel ini membahas cara memberi agent kesadaran terhadap komunikasi antar service secara efisien: agent tahu ke mana harus mencari kontrak, tapi hanya membaca detailnya saat benar-benar dibutuhkan. Pendekatan ini berlaku untuk tim manapun yang develop arsitektur multi-service dengan bantuan AI agent, apapun tool atau stack yang dipakai.

Masalah: Kenapa “Buka Semua Repo” Tidak Scalable

Pendekatan paling naif untuk memberi agent awareness lintas service adalah membuka semua repo terkait ke satu workspace, lalu berharap agent membaca semuanya sebelum bekerja. Ini punya beberapa masalah mendasar.

Pertama, context window jadi penuh dengan kode yang tidak relevan dengan task. Agent yang seharusnya fokus mengubah satu endpoint di checkout-service malah harus memproses ribuan baris kode dari payment-service, inventory-service, dan notification-service sekaligus — sebagian besar tidak relevan sama sekali dengan perubahan yang sedang dikerjakan.

Kedua, ini melanggar batas kepemilikan tim. Di organisasi dengan banyak tim yang masing-masing memiliki service sendiri, membuka seluruh source code service tim lain ke workspace developer lain bukan cuma soal noise — ini juga soal akses dan tanggung jawab. Tim payment mungkin tidak ingin source code internal mereka jadi bagian dari context yang di-generate oleh agent tim lain.

Ketiga, kontrak yang di-copy manual ke dalam dokumentasi atau context cepat menjadi usang. Begitu service lain berubah, siapa yang bertanggung jawab mengupdate salinan itu? Biasanya tidak ada, sampai suatu hari agent membuat asumsi yang salah berdasarkan kontrak yang sudah tidak berlaku.

flowchart TD
    A[Agent mengerjakan checkout-service] --> B{Butuh tahu API payment-service}
    B -- Pendekatan naif --> C[Clone seluruh repo payment-service]
    C --> D[Context penuh kode tidak relevan]
    D --> E[Token mahal, noise tinggi, akses tidak terkontrol]
    B -- Pendekatan pointer --> F[Baca pointer file: lokasi kontrak]
    F --> G[Fetch kontrak spesifik saja, saat dibutuhkan]
    G --> H[Context ringkas, akses terbatas, selalu relevan]

Masalah ini bukan hal baru — ini versi lain dari masalah dependency awareness yang sudah lama ada di software engineering, hanya sekarang konsekuensinya langsung memengaruhi kualitas kode yang di-generate oleh agent.


Prinsip Dasar: Pointer, Bukan Payload

Solusi untuk masalah di atas sebenarnya sederhana secara konsep: agent tidak perlu membawa seluruh isi kontrak service lain ke dalam context-nya sejak awal. Yang dia butuhkan hanyalah tahu di mana kontrak itu berada, dan instruksi kapan harus membacanya. Ini disebut pola pointer — agent menyimpan referensi ringan, lalu melakukan lazy load isi lengkapnya hanya ketika task yang sedang dikerjakan benar-benar membutuhkannya.

Analoginya mirip dengan #include di C atau import di bahasa pemrograman modern. Header file tidak membawa seluruh implementasi — dia cuma mendeklarasikan apa yang tersedia. Compiler (atau dalam kasus ini, agent) baru mengambil detail lengkap saat benar-benar dipanggil. Bandingkan dengan pendekatan yang salah: menempelkan seluruh isi function ke setiap file yang memanggilnya.

// ANTI-PATTERN: paste seluruh OpenAPI spec payment-service ke dalam
// system prompt / instruksi agent, meskipun task tidak menyentuh payment
[500 baris YAML OpenAPI di-paste langsung]

// BENAR: referensi lokasi kontrak, agent fetch sendiri saat dibutuhkan
Dependency: payment-service
Kontrak: docs/contracts/payment-openapi.yaml
Baca file ini HANYA jika task menyentuh integrasi payment.

Prinsip ini juga selaras dengan bagaimana agent modern bekerja — mereka punya tool untuk membaca file atau memanggil resource on-demand. Yang perlu kamu sediakan bukan payload lengkap di awal, tapi jalur yang jelas supaya agent tahu kapan dan di mana harus mengambil informasi tambahan itu.

  • Pola pointer ini bukan cuma soal hemat token — ini juga soal keamanan akses. Agent hanya perlu tahu lokasi kontrak publik (API spec), bukan source code penuh service lain.
  • Semakin sedikit informasi yang dipaksa masuk ke context di awal, semakin agent bisa fokus pada task yang sedang dikerjakan.

Anatomi Pointer File

Pointer file adalah dokumen kecil, biasanya diletakkan di root tiap service, yang mendaftar service lain yang berinteraksi dengannya beserta lokasi kontraknya. Formatnya bisa YAML, JSON, atau markdown terstruktur — yang penting konsisten di seluruh organisasi.

# dependencies.yaml — diletakkan di root checkout-service

consumes:
  - service: payment-service
    type: rest
    contract: https://git.internal/payment-service/blob/main/openapi.yaml
    endpoints_used:
      - POST /v1/charge
      - GET /v1/charge/{id}
    last_verified: "2026-07-10"

  - service: inventory-service
    type: event
    contract: https://git.internal/inventory-service/blob/main/schemas/order-events.avsc
    events_consumed:
      - order.reserved
      - order.reservation_failed

produces:
  - event: order.created
    schema: schemas/order-created.json
    known_consumers:
      - inventory-service
      - notification-service

Field yang penting untuk selalu ada:

FieldFungsi
serviceNama service yang jadi dependency
typeJenis komunikasi — REST, gRPC, event, message queue
contractLokasi kontrak lengkap — path lokal, URL git, atau MCP endpoint
endpoints_used / events_consumedBagian spesifik dari kontrak yang benar-benar dipakai, bukan seluruh API
last_verifiedKapan terakhir kali kontrak ini dicek masih valid

Field endpoints_used penting karena mempersempit apa yang perlu dibaca agent — kamu tidak perlu seluruh OpenAPI spec payment-service, cukup dua endpoint yang memang dipakai checkout-service.


Mekanisme “Baca Hanya Jika Perlu”

Setelah pointer file ada, pertanyaan berikutnya adalah bagaimana secara teknis agent membaca isi kontrak itu hanya ketika dibutuhkan, bukan otomatis di setiap sesi. Ada beberapa mekanisme, dari yang paling sederhana sampai yang paling dinamis.

File Lokal dengan Instruksi Eksplisit

Cara paling sederhana: kontrak disimpan sebagai file di repo (misalnya docs/contracts/), dan instruksi ke agent (AGENTS.md, system prompt, atau task description) secara eksplisit bilang “baca file X jika kamu mengubah kode yang berkaitan dengan Y”. Agent modern seperti Claude Code, Cursor, atau Codex CLI punya tool baca file bawaan, jadi mereka bisa fetch file ini sendiri ketika instruksinya jelas.

## Service Dependencies
This service integrates with payment-service (see dependencies.yaml).
Before modifying any payment integration code, read the referenced
contract file first. Do not assume payload structure from memory.

Kelemahan pendekatan ini: kontrak harus di-commit ke repo yang sama (atau accessible lewat path lokal), yang berarti butuh proses sinkronisasi manual kalau kontrak aslinya ada di repo service lain.

Git Sparse-Checkout atau Submodule Khusus Kontrak

Kalau kontrak (misalnya file OpenAPI atau proto) disimpan di repo aslinya masing-masing service, kamu bisa memakai git sparse-checkout atau submodule yang hanya menarik folder kontrak — bukan seluruh source code service tersebut.

# Sparse-checkout hanya folder contracts/ dari payment-service
git clone --filter=blob:none --sparse https://git.internal/payment-service.git
cd payment-service
git sparse-checkout set contracts/

Dengan ini, agent (atau pipeline yang menyiapkan context untuk agent) bisa punya akses ke kontrak terbaru tanpa meng-clone implementasi penuh service lain. Ini jalan tengah yang baik antara “tidak ada akses sama sekali” dan “akses penuh ke semua kode”.

MCP Resource Server

Untuk kebutuhan yang lebih dinamis — di mana kontrak sering berubah dan kamu ingin agent selalu dapat versi terbaru tanpa proses clone atau sync manual — tiap service bisa expose MCP (Model Context Protocol) server kecil dengan resource seperti get_contract, get_endpoints, atau get_recent_changes.

sequenceDiagram
    participant Agent
    participant MCP as MCP Server payment-service
    participant Repo as payment-service repo

    Agent->>MCP: get_contract("payment-service")
    MCP->>Repo: baca openapi.yaml terbaru
    Repo-->>MCP: kontrak terkini
    MCP-->>Agent: kontrak + metadata versi

Dengan pola ini, agent tidak pernah menyimpan salinan kontrak yang bisa basi — setiap kali dia butuh, dia query langsung ke sumbernya. Ini pendekatan paling scalable untuk organisasi dengan banyak service dan tim yang sering merilis perubahan.

RAG atas Registry Kontrak

Kalau jumlah service sudah sangat banyak (puluhan hingga ratusan), pendekatan pointer manual per-service mulai sulit dikelola satu per satu. Di skala ini, index seluruh kontrak ke vector store (dari OpenAPI spec, AsyncAPI, atau proto file), lalu agent melakukan retrieval berdasarkan relevansi task — bukan predefined pointer list yang harus dijaga manual.

Pendekatan ini lebih kompleks untuk disiapkan, tapi mengurangi beban maintenance pointer file satu-satu ketika skala organisasi sudah besar.


Perbandingan Pendekatan

PendekatanKompleksitas SetupReal-time-nessCocok untuk SkalaOverhead Token
File lokal + instruksiRendahRendah (perlu sync manual)1–10 serviceRendah
Git sparse-checkoutSedangSedang10–30 serviceRendah–Sedang
MCP resource serverSedang–TinggiTinggi (selalu live)10–100+ serviceRendah (fetch on-demand)
RAG atas registryTinggiSedang–Tinggi50+ serviceSedang (tergantung retrieval)

Decision Tree — Pilih Mekanisme yang Tepat

flowchart TD
    A{Berapa banyak service?} -- 1-10 --> B{Kontrak sering berubah?}
    A -- 10-50 --> C{Butuh real-time?}
    A -- 50+ --> D[Pertimbangkan RAG atas registry kontrak]

    B -- Jarang --> E[File lokal + instruksi eksplisit]
    B -- Sering --> F[MCP resource server]

    C -- Ya --> F
    C -- Tidak --> G[Git sparse-checkout / submodule kontrak]

Contoh Implementasi End-to-End

Bayangkan struktur direktori sederhana untuk checkout-service yang bergantung pada payment-service dan inventory-service:

checkout-service/
  ├── AGENTS.md
  ├── dependencies.yaml
  ├── docs/
  │   └── contracts/
  │       ├── payment-openapi.yaml      # hasil sparse-checkout, auto-updated
  │       └── order-events.avsc
  ├── src/
  │   └── ...
  └── ...

Isi AGENTS.md yang mereferensikan pointer file, bukan menempelkan isinya:

## Service Dependencies

This service depends on payment-service and inventory-service.
See dependencies.yaml for the full list and contract locations.

Rules:
- Before touching any code under src/integrations/payment/, read
  docs/contracts/payment-openapi.yaml first.
- Do not assume payload shapes from memory or from old code comments.
- If your change adds a new dependency or modifies an existing
  integration, update dependencies.yaml as part of the task (see
  "Menjaga Pointer File Tetap Sinkron" below).

Alurnya jadi seperti ini: agent mulai task, baca AGENTS.md, melihat instruksi soal dependency. Kalau task memang menyentuh integrasi payment, agent baru membuka docs/contracts/payment-openapi.yaml — bukan seluruh source code payment-service. Kalau task tidak menyentuh integrasi apapun, pointer file ini bahkan tidak pernah dibaca, sehingga tidak memakan context sama sekali.

Tips Khusus: Membuat Claude Code Membaca AGENTS.md

Satu hal teknis yang perlu diperhatikan kalau kamu pakai Claude Code: secara native, Claude Code membaca file CLAUDE.md, bukan AGENTS.md. Kalau tim kamu memakai beberapa AI coding agent sekaligus (Claude Code, Cursor, Codex CLI) dan ingin satu source of truth yang sama, ada dua cara membuat Claude Code tetap membaca AGENTS.md:

// CLAUDE.md — baris pertama meng-import AGENTS.md
@AGENTS.md

// Instruksi tambahan khusus Claude Code bisa ditambahkan di bawah ini

Alternatifnya, jalankan perintah /init di repo yang sudah punya AGENTS.md — Claude Code akan otomatis membaca dan menggabungkan isinya (termasuk .cursorrules dan .windsurfrules kalau ada), tanpa kamu perlu menulis CLAUDE.md manual.

  • Kalau tim hanya pakai satu tool, pakai format native tool itu langsung (CLAUDE.md untuk Claude Code saja).
  • Kalau tim pakai lebih dari satu AI coding agent, taruh instruksi bersama di AGENTS.md, lalu buat file native tiap tool (CLAUDE.md, .cursorrules) yang meng-import darinya — supaya tidak ada duplikasi yang harus dijaga manual.

Menjaga Pointer File Tetap Sinkron — Update Otomatis oleh Agent

Pointer file yang stale sama berbahayanya dengan tidak punya pointer file sama sekali — bahkan lebih buruk, karena menciptakan rasa percaya diri yang salah. Agent yang membaca kontrak usang akan menghasilkan kode yang terlihat benar tapi sebenarnya salah asumsi. Bagian ini membahas bagaimana memastikan pointer file dan kontrak tetap sinkron begitu ada perubahan dependency.

Trigger: Kapan Update Perlu Terjadi

Beberapa kondisi yang harus memicu update pada pointer file atau kontrak:

  • Endpoint baru ditambahkan atau dihapus
  • Payload/schema berubah — field baru, tipe data berubah, field dihapus
  • Path atau route berubah
  • Dependency baru muncul — service A mulai memanggil service C yang sebelumnya tidak pernah dipanggil
  • Event baru dipublish atau di-consume, untuk arsitektur event-driven

Pendekatan 1 — Self-Update sebagai Bagian dari Task Checklist Agent

Cara paling langsung: instruksikan secara eksplisit di AGENTS.md bahwa setiap perubahan yang berdampak pada kontrak komunikasi harus disertai update pada pointer file, sebagai bagian dari definisi “task selesai” — bukan langkah opsional.

// ANTI-PATTERN: agent ubah endpoint, tapi dependencies.yaml
// tidak pernah disentuh — kontrak jadi usang tanpa disadari

// BENAR: instruksi eksplisit di AGENTS.md
Jika perubahanmu menambah, menghapus, atau mengubah endpoint/payload/
event yang dipakai service lain, update dependencies.yaml dan file
kontrak terkait SEBELUM menandai task selesai. Ini bagian dari
Definition of Done, bukan langkah tambahan opsional.

Instruksi semacam ini bersifat advisory, bukan enforced secara mekanis — agent bisa saja tetap lupa. Karena itu pendekatan ini idealnya dikombinasikan dengan safety net di Pendekatan 3.

Pendekatan 2 — Generate Kontrak dari Kode, Bukan Ditulis Manual

Daripada mengandalkan agent (atau manusia) mengingat untuk mengupdate dokumen kontrak terpisah, kontrak bisa di-generate otomatis dari kode itu sendiri — misalnya OpenAPI spec dari annotation route, atau proto file yang jadi single source of truth untuk gRPC.

// ANTI-PATTERN
openapi.yaml ditulis manual, terpisah dari kode route
  -> gampang out of sync begitu route berubah

// BENAR
openapi.yaml di-generate dari annotation/decorator di kode
  -> agent cukup ubah kode, generator yang menjaga kontrak tetap akurat

Dengan pola ini, “update kontrak” bukan lagi langkah manual terpisah yang bisa terlupa — dia otomatis mengikuti perubahan kode. Trade-off-nya adalah butuh setup tooling di awal, dan tidak semua stack punya generator yang matang untuk semua jenis kontrak (REST relatif mudah, event schema kadang lebih rumit).

Pendekatan 3 — CI/Git Hook sebagai Safety Net

Untuk kasus di mana Pendekatan 1 gagal (agent atau manusia lupa update manual), CI pipeline bisa mendeteksi perubahan pada file route/schema dan memvalidasi apakah kontrak terkait sudah diperbarui.

flowchart TD
    A[Agent commit perubahan kode] --> B{CI deteksi diff pada route/schema?}
    B -- Tidak --> C[Lanjut merge seperti biasa]
    B -- Ya --> D{Kontrak terkait ikut ter-update?}
    D -- Ya --> C
    D -- Tidak --> E[Block merge / auto-generate ulang kontrak]
    E --> F[Notifikasi ke PR: update kontrak sebelum merge]

Safety net ini penting karena instruksi ke agent sifatnya advisory — CI check yang benar-benar memblokir merge adalah jaminan yang lebih kuat dibanding sekadar berharap agent patuh pada instruksi.

Pendekatan 4 — Notifikasi ke Consumer Service

Satu hal yang sering terlewat: setelah kontrak producer berubah, bagaimana service consumer tahu? Update pointer file di sisi producer saja tidak cukup kalau consumer tidak pernah diberi tahu ada perubahan.

Beberapa opsi:

  • Changelog per kontrak — file CHANGELOG.md di folder contracts yang mencatat setiap perubahan beserta tanggal
  • Versioning skema — semantic versioning pada schema, sehingga breaking change terlihat jelas dari perubahan major version
  • Notifikasi aktif — webhook atau pesan ke channel tim consumer setiap kali kontrak producer berubah
  • MCP tool get_recent_changes — kalau memakai MCP resource server, sediakan tool yang mengembalikan diff kontrak sejak timestamp tertentu, sehingga agent di sesi berikutnya bisa cek “ada yang berubah sejak terakhir saya baca?”

Perbandingan Pendekatan Sinkronisasi

PendekatanButuh Disiplin InstruksiRisiko TerlewatKompleksitas Setup
Self-update manualTinggiTinggi (bergantung kepatuhan agent)Rendah
Generate dari kodeRendahRendahSedang–Tinggi
CI safety netRendah (otomatis)RendahSedang
Notifikasi consumerSedangSedangRendah–Sedang
  • Agent yang “lupa” update kontrak lebih berbahaya daripada tidak punya kontrak sama sekali — kontrak yang salah menciptakan false confidence di sesi agent berikutnya, alih-alih membuat agent bertanya atau mengecek ulang.
  • Jangan hanya mengandalkan instruksi di AGENTS.md untuk sinkronisasi kontrak. Kombinasikan dengan CI check (Pendekatan 3) sebagai jaminan mekanis, bukan cuma advisory.

Kapan Tidak Perlu Pola Ini

Tidak semua tim butuh seluruh mekanisme di atas. Pola pointer dan sinkronisasi otomatis punya overhead setup yang harus sebanding dengan manfaatnya.

BUTUH pola ini jika:
  ✓ Lebih dari 3-4 service yang saling bergantung
  ✓ Tim/kepemilikan service terpisah (bukan satu tim yang sama)
  ✓ Kontrak antar service cukup sering berubah
  ✓ Sudah mulai terjadi insiden karena agent salah asumsi kontrak

TIDAK BUTUH jika:
  ✗ Monorepo kecil dengan 1-2 service, satu tim yang sama
  ✗ Kontrak nyaris tidak pernah berubah
  ✗ Semua developer (dan agent) sudah punya akses penuh ke semua kode tanpa masalah kepemilikan

Untuk tim kecil dengan monorepo, kadang solusi paling efisien justru tetap membuka semua kode ke satu workspace — overhead membangun pointer file dan MCP server tidak sebanding dengan manfaatnya di skala itu.


Ringkasan

  • Masalah inti: membuka semua repo service lain ke satu workspace tidak scalable — bikin context bloat, melanggar batas kepemilikan tim, dan kontrak yang di-copy manual cepat basi.
  • Prinsip dasar: pakai pola pointer — agent tahu di mana kontrak berada, bukan membawa seluruh isinya sejak awal. Baca hanya saat task benar-benar membutuhkan.
  • Pointer file cukup berisi nama service, tipe komunikasi, lokasi kontrak, dan bagian spesifik yang dipakai — bukan seluruh spesifikasi API.
  • Empat mekanisme dari sederhana ke kompleks: file lokal + instruksi eksplisit, git sparse-checkout/submodule, MCP resource server untuk real-time, dan RAG atas registry kontrak untuk skala besar.
  • Claude Code membaca CLAUDE.md, bukan AGENTS.md secara native — gunakan @AGENTS.md sebagai baris import di CLAUDE.md, atau jalankan /init di repo yang sudah punya AGENTS.md.
  • Sinkronisasi kontrak tidak boleh mengandalkan instruksi manual saja — kombinasikan self-update oleh agent, generate kontrak dari kode, CI safety net, dan notifikasi ke consumer service.
  • Kontrak yang stale lebih berbahaya daripada tidak ada kontrak sama sekali, karena menciptakan false confidence pada agent di sesi berikutnya.
  • Pola ini tidak selalu diperlukan — untuk monorepo kecil dengan satu tim, overhead setup mungkin tidak sebanding dengan manfaatnya.

Portofolio