Skip to main content
Without governance, AI agents default to reading entire files. This wastes tokens, risks exposing sensitive code regions, and creates unpredictable context costs. SDL-MCP’s policy engine enforces a “prove you need it” model for raw code access.

How It Works

Every request to sdl.code.needWindow (Rung 4 — raw code) passes through the policy engine before code is returned:
Agent Request
"I need to read validateToken()"


┌──────────────────────────────┐
│       Policy Engine          │
│                              │
│  ✓ Identifiers provided?     │──── deny if empty
│  ✓ Within line limit?        │──── deny if > maxWindowLines
│  ✓ Within token limit?       │──── deny if > maxWindowTokens
│  ✓ Identifiers exist?        │──── deny if not in range
│  ✓ Symbol in current slice?  │──── more likely to approve
│  ✓ Scorer utility check      │──── is the code worth reading?
│                              │
└──────────┬───────────────────┘

    ┌──────┴──────┐
    │             │
 APPROVE       DENY
    │             │
    ▼             ▼
Return code   Return guidance
+ audit log   + nextBestAction

Policy Configuration

Configure governance in the policy block of your sdlmcp.config.json:
{
  "policy": {
    "maxWindowLines": 180,
    "maxWindowTokens": 1400,
    "requireIdentifiers": true,
    "allowBreakGlass": false,
    "defaultDenyRaw": true
  }
}
SettingDefaultDescription
maxWindowLines180Maximum lines per raw code request. Requests above this are clamped, not rejected.
maxWindowTokens1400Maximum tokens per raw code request. Requests above this are clamped.
requireIdentifierstrueAgent must specify non-empty identifiersToFind — what it expects to find in the code.
allowBreakGlassfalseAllow emergency override with full audit logging.
defaultDenyRawtrueDefault deny for raw code windows — requires proof-of-need (symbol in slice, identifiers provided, reason given).
budgetCapsnullOptional server-side defaults: { "maxCards": 60, "maxEstimatedTokens": 12000 }.
maxWindowLines and maxWindowTokens are clamped, not rejected. An agent requesting 500 lines gets 180 lines, not an error. This prevents agents from failing on policy limits while still bounding token spend.

What Agents Must Provide for Rung 4 Access

A well-formed sdl.code.needWindow request that is likely to be approved:
{
  "repoId": "my-repo",
  "symbolId": "sha256:abc...",
  "reason": "Need to inspect the exact error handling branch for expired tokens",
  "identifiersToFind": ["TokenExpiredError", "expiresAt"],
  "expectedLines": 60,
  "maxTokens": 800,
  "sliceContext": {
    "taskText": "debug JWT expiration handling",
    "stackTrace": "Error: invalid signature\n  at validateToken (auth/jwt.ts:47)"
  }
}
Pass sliceContext to give the gating engine task context — this significantly improves approval likelihood for justified requests. Include taskText, stackTrace, failingTestPath, or editedFiles as available.
Required fields when requireIdentifiers: true (the default):
  • identifiersToFind — non-empty list of identifiers the agent expects to find
  • expectedLines — expected line count (must be ≤ maxWindowLines)
  • reason — plain-English justification for needing raw code

Denied Request Responses

When a request is denied, the response includes actionable guidance rather than just a rejection:
{
  "approved": false,
  "whyDenied": ["No identifiers matched in the requested range"],
  "nextBestAction": {
    "tool": "sdl.code.getHotPath",
    "args": {
      "symbolId": "sha256:abc...",
      "identifiersToFind": ["errorCode", "retryCount"]
    },
    "rationale": "Hot-path can locate these identifiers without full code access"
  }
}
Never ignore nextBestAction, fallbackTools, or fallbackRationale in denied responses. These fields tell you exactly what to try instead — agents that skip them waste tokens re-attempting denied requests.
Common denial reasons and the recommended next action:
Denial ReasonNext Action
No identifiers providedAdd identifiersToFind and retry
Identifiers not found in rangeUse sdl.code.getHotPath with those identifiers
Exceeds line/token limitLower expectedLines / maxTokens to within policy limits
Symbol not in current sliceBuild a slice with this symbol as an entry point first
Scorer utility below thresholdUse sdl.code.getSkeleton instead; the skeleton may be sufficient

Audit Logging

Every raw code access and every denial is logged with:
  • Audit hash — unique identifier for the decision
  • Request details — who asked, for what symbol, with what justification
  • Decision — approve, deny, or downgrade (to skeleton/hot-path)
  • Evidence used — what factors influenced the decision
Audit logs are written to the SDL-MCP server logs and are retained for compliance review. Every allowBreakGlass override is logged with the requesting agent’s stated reason.

Break-Glass Override

Break-glass allows emergency bypass of policy denials. It is disabled by default and should remain disabled in production.
{
  "policy": {
    "allowBreakGlass": true
  }
}
Every break-glass override is logged in the audit trail with the agent’s reason. Enable only in development or when a justified emergency requires it. Do not enable in production or CI environments.
When allowBreakGlass: true, an agent can include "breakGlass": true in its needWindow request to bypass the scorer utility check. Identifier and limit checks still apply.

Policy Tools

Policy can be read and updated at runtime without restarting the server.

sdl.policy.get

Read current policy settings:
{
  "repoId": "my-repo"
}
Response includes all current policy values and their sources (config file vs runtime override).

sdl.policy.set

Update policy at runtime using a merge patch — only supplied fields change:
{
  "repoId": "my-repo",
  "policyPatch": {
    "maxWindowLines": 300,
    "requireIdentifiers": true
  }
}
Useful policy adjustments:
ScenarioPatch
Large functions in codebase{ "maxWindowLines": 300, "maxWindowTokens": 2500 }
Strict production gating{ "allowBreakGlass": false, "requireIdentifiers": true }
CI tighter limits{ "maxWindowLines": 120, "maxWindowTokens": 1000 }
Disable identifier requirement{ "requireIdentifiers": false } (not recommended)
Runtime policy changes via sdl.policy.set are not persisted to the config file. They reset on server restart. To make changes permanent, update sdlmcp.config.json.

Runtime Execution Governance

sdl.runtime.execute has its own separate governance layer:
  • Disabled by default — must be explicitly enabled with runtime.enabled: true in config
  • Executable validation — only allowed runtimes and executables can run (configurable allowlist)
  • CWD jailing — subprocesses cannot escape the repo root
  • Environment scrubbing — only PATH and explicitly allowlisted env vars are passed through
  • Concurrency limits — prevents resource exhaustion (maxConcurrentJobs: 2 by default)
  • Timeout enforcement — hard kill on timeout (maxDurationMs: 30000 by default)
  • Output truncation — stdout and stderr are capped at configurable byte limits
To enable runtime execution:
{
  "runtime": {
    "enabled": true,
    "allowedRuntimes": ["node", "python"],
    "maxDurationMs": 30000
  }
}

Build docs developers (and LLMs) love