A monorepo Knowledge Base without metadata filters will happily retrieve Python ETL snippets while you’re editing a TypeScript Lambda. Attach language, repository, service, and owner metadata at ingest, then pass retrieval filters from the IDE context so Bedrock Retrieve only searches the slice that matters.
⚡ TL;DR: Emit sidecar metadata JSON (or inline attributes) per chunk at sync time; map filterable fields in the KB; call
Retrieve/RetrieveAndGeneratewithvectorSearchConfiguration.filter; derive filters from open files / git root in the client. See Bedrock Retrieval Filters (tenants) and KB Sync webhooks.
Metadata at ingest
// s3://code-kb/payments/src/getOrder.ts.metadata.json
{
"metadataAttributes": {
"repo": "acme/backend",
"service": "payments",
"language": "typescript",
"owner": "payments-platform",
"path": "services/payments/src/getOrder.ts",
"visibility": "internal"
}
}
# sync_chunk.py — write chunk + metadata for Bedrock KB S3 data source
def write_chunk(bucket: str, key: str, text: str, meta: dict):
s3.put_object(Bucket=bucket, Key=key, Body=text.encode("utf-8"))
s3.put_object(
Bucket=bucket,
Key=f"{key}.metadata.json",
Body=json.dumps({"metadataAttributes": meta}).encode("utf-8"),
ContentType="application/json",
)
Derive language from extension + package.json / pyproject.toml presence; derive service from path conventions (services/<name>/). Fail the sync job if required keys are missing—unfiltered docs are a production bug.
Filter at Retrieve time
import boto3
bedrock_agent = boto3.client("bedrock-agent-runtime")
def retrieve_for_ide(query: str, *, repo: str, language: str | None, service: str | None):
and_all = [{"equals": {"key": "repo", "value": repo}}]
if language:
and_all.append({"equals": {"key": "language", "value": language}})
if service:
and_all.append({"equals": {"key": "service", "value": service}})
return bedrock_agent.retrieve(
knowledgeBaseId=KB_ID,
retrievalQuery={"text": query},
retrievalConfiguration={
"vectorSearchConfiguration": {
"numberOfResults": 8,
"filter": {"andAll": and_all},
}
},
)
Default filters from the active editor (languageId, nearest service root). Do not use global search as the default for “where is X implemented?” inside a 40-service monorepo.
Client context wiring
function filtersFromEditor(ctx: IdeContext) {
return {
repo: ctx.gitRemoteSlug, // acme/backend
language: mapVscodeLang(ctx.languageId),
service: detectService(ctx.filePath), // services/payments/...
};
}
Offer an explicit “search all services” toggle for cross-cutting questions (auth, platform libs). Log which filter mode was used for eval slices.
Pitfalls
| Pitfall | Result | Fix |
|---|---|---|
| Metadata only on parent folder | Chunks inherit wrong service | Per-chunk sidecars |
Stringly-typed language (ts vs typescript) |
Empty results | Canonical enum at sync |
| Over-filtering (repo+service+path prefix) | Zero hits | Relax stepwise; never silently drop filters without UX |
No visibility attribute |
Secrets/docs leak across teams | Filter visibility=internal + IAM |
Closing checklist
Dos
– Require repo, language, service metadata on every chunk
– Pass andAll filters from IDE context by default
– Canonicalize enum values at sync time
– Provide an explicit broad-search mode
– Include filter mode in RAG eval dimensions
Donts
– Do not ingest without sidecars “to move faster”
– Do not rely on path substrings in the query text instead of filters
– Do not mix tenant/customer data without tenant isolation filters
– Do not fail open (ignore filter errors)
– Do not forget to re-sync metadata after service renames
Related reading
- Bedrock Retrieval Filters: Tenant Isolation
- Bedrock Knowledge Base Sync
- Multi-Index RAG Design
- Query Rewriting for Code RAG
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
