Hardcoding MODEL_ID, TOOL_TIMEOUT_MS, and ALLOWED_REPOS in Lambda environment variables guarantees a redeploy for every tenant tweak. SSM Parameter Store gives you a hierarchical tree — /agents/{env}/{tenant}/tools/* — with IAM path prefixes, standard vs advanced tiers, and SecureString values that can point at Secrets Manager instead of duplicating secrets. AppConfig remains the right hammer for kill switches and progressive flags; Parameter Store is the right hammer for config trees. Do not conflate them.
⚡ TL;DR: Put durable hierarchical config in SSM (
GetParametersByPath). Put secrets in Secrets Manager; store only ARNs/names in SecureString or String params. Use IAMssm:GetParameter*with path conditions per tenant. Use AppConfig for boolean kill switches and timed rollouts. Related: KMS decrypt grants, Verified Permissions.
Design the path taxonomy first
/agents
/prod
/_shared
/model_id = anthropic.claude-...
/max_tool_depth = 8
/tenant-acme
/tools
/run_tests/timeout_ms = 120000
/run_tests/enabled = true
/git_push/allowed_repos = ["acme/api","acme/web"]
/git_push/secret_ref = arn:aws:secretsmanager:...:secret:agents/acme/github
/tenant-globex
/tools/...
/staging/...
Rules of thumb:
_sharedfor env defaults; tenant overrides on read (merge in code).- Tool-specific leaves under
tools/{tool_name}/…. - Never put raw GitHub tokens in String params — secret_ref only.
- Keep kill-switch booleans duplicated in AppConfig if you need timed flags / client-side agents; SSM
enabledis fine for slower config.
Read path: GetParametersByPath with merge
import {
SSMClient,
GetParametersByPathCommand,
} from "@aws-sdk/client-ssm";
const ssm = new SSMClient({});
export async function loadAgentConfig(env: string, tenant: string) {
const prefixes = [
`/agents/${env}/_shared`,
`/agents/${env}/tenant-${tenant}`,
];
const map = new Map<string, string>();
for (const path of prefixes) {
let token: string | undefined;
do {
const page = await ssm.send(
new GetParametersByPathCommand({
Path: path,
Recursive: true,
WithDecryption: true,
NextToken: token,
})
);
for (const p of page.Parameters ?? []) {
const key = p.Name!.split(path)[1] ?? p.Name!;
map.set(key.replace(/^\//, ""), p.Value ?? "");
}
token = page.NextToken;
} while (token);
}
return map; // tenant keys overwrite shared when you apply in order carefully
}
# Python merge — shared first, tenant second
import boto3
ssm = boto3.client("ssm")
def params_under(path: str) -> dict[str, str]:
out, token = {}, None
while True:
kw = dict(Path=path, Recursive=True, WithDecryption=True)
if token:
kw["NextToken"] = token
resp = ssm.get_parameters_by_path(**kw)
for p in resp.get("Parameters", []):
rel = p["Name"][len(path):].lstrip("/")
out[rel] = p["Value"]
token = resp.get("NextToken")
if not token:
return out
def load(env: str, tenant: str) -> dict[str, str]:
cfg = params_under(f"/agents/{env}/_shared")
cfg.update(params_under(f"/agents/{env}/tenant-{tenant}"))
return cfg
Cache in-process with a short TTL (30–60s) or use the SSM freeform/extension patterns on Lambda to avoid hot-path throttling. For high QPS interactive agents, consider a thin Redis cache — but start with path reads + TTL.
IAM prefix policies (tenant isolation)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadOwnTenantConfig",
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath"
],
"Resource": [
"arn:aws:ssm:us-east-1:123456789012:parameter/agents/prod/_shared",
"arn:aws:ssm:us-east-1:123456789012:parameter/agents/prod/_shared/*",
"arn:aws:ssm:us-east-1:123456789012:parameter/agents/prod/tenant-acme",
"arn:aws:ssm:us-east-1:123456789012:parameter/agents/prod/tenant-acme/*"
]
},
{
"Sid": "DenyOtherTenants",
"Effect": "Deny",
"Action": "ssm:GetParameter*",
"Resource": "arn:aws:ssm:us-east-1:123456789012:parameter/agents/prod/tenant-*",
"Condition": {
"StringNotLike": {
"ssm:ResourceTag/tenant": "acme"
}
}
}
]
}
Tag parameters with tenant=acme when you create them, or rely on path-scoped resources alone. Runtime authorization for tool names still belongs in Verified Permissions / Cedar — SSM tells you timeouts and allowlists; Cedar decides if this principal may call git_push.
SecureString vs Secrets Manager
| Store | Use for |
|---|---|
| SSM String | Non-secret config (model id, timeouts, JSON allowlists) |
| SSM SecureString | Mildly sensitive config encrypted by KMS (prefer refs) |
| Secrets Manager | Rotating credentials, GitHub PATs, API keys |
| SSM value = secret ARN | Pointer pattern — fetch secret at use time |
# Pointer — Parameter Store holds the ARN only
aws ssm put-parameter \
--name /agents/prod/tenant-acme/tools/git_push/secret_ref \
--type String \
--value arn:aws:secretsmanager:us-east-1:123456789012:secret:agents/acme/github
# Decrypt path for any SecureString still uses KMS grants —
# see KMS decrypt grants for agent tools
Follow KMS decrypt grants so tools never ship long-lived plaintext keys in env.
AppConfig kill switches vs SSM config trees
| Concern | AppConfig | SSM Parameter Store |
|---|---|---|
| Instant kill switch / flag | ✅ designed for it | Possible but weaker rollout |
| Timed/percentage exposure | ✅ | ❌ DIY |
| Hierarchical tenant config trees | Awkward | ✅ natural paths |
| Large JSON documents | Limited size / better as flags | Standard 4KB / advanced 8KB per param |
| Agent extension / poll | Strong | GetParametersByPath |
✅ AppConfig: tools.run_tests.enabled=false during an incident (kill switches post).
✅ SSM: tools.run_tests.timeout_ms=180000 and allowlists that change weekly.
❌ Putting your entire tenant config blob only in AppConfig flags, or putting kill switches only in SSM with no cache-bust story.
Checklist: ship hierarchical agent config
- [ ] Agree path taxonomy
/agents/{env}/_shared+tenant-{id}/tools/... - [ ] Merge helper with TTL cache in the agent runtime
- [ ] IAM path-scoped read roles per tenant (or per runtime)
- [ ] Secrets as ARNs; Secrets Manager owns rotation
- [ ] AppConfig for kill switches; SSM for config trees — document both
- [ ] Cedar/IAM still authorize tool invocation
- [ ] CloudWatch metric on SSM throttle / cache hit rate
Hierarchical SSM config is how multi-tenant coding agents stay tunable without redeploys — as long as you keep secrets, flags, and authorization in the right systems.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Python asyncio vs Threading: The Benchmark That Changes How You Think About Concurrency
- LLM context windows: Tokens, Context Windows, and Why Models Forget Mid-Task
- Distributed Locks Reality Check: When Redis Redlock Is the Wrong Tool
- SQL Joins Explained: INNER, LEFT, RIGHT, FULL, CROSS, and Self Joins
Newly added
- SSM Parameter Store: Hierarchical Runtime Config for Coding Agents
- Bedrock Prompt Flows: Visual Multi-Step Coding Pipelines You Can Version
- Athena + S3: Query Coding-Agent Eval Traces Without a Warehouse
- AWS Batch: Overnight Long-Running Coding Agent Jobs Without Lambda Timeouts
- CloudWatch Logs Insights: Multi-Tenant Agent Tool Failure Forensics
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.