Every serious coding-agent team accumulates eval traces: prompt version, model id, tool sequence, pass/fail, latency, token counts, dollar cost, tenant, suite name. Dumping them into Postgres until it melts, or waiting for a full warehouse, are both failure modes. S3 + Athena (with Glue catalog or CTAS) gets you SQL over JSONL this week — pass rates by prompt version, cost outliers, latency regressions — without standing up Redshift. When joins and concurrency outgrow Athena, graduate; until then, ship visibility.
⚡ TL;DR: Write one JSONL line per eval case to
s3://…/evals/dt=YYYY-MM-DD/. Glue crawler or explicit table DDL → Athena. SQL for pass rate, p95 latency, cost byprompt_version. Pair with EMF per-tenant tokens for live metrics and Bedrock ApplyGuardrail for safety filters on tool I/O. Related: tool schemas.
Land traces as partition-friendly JSONL
# ✅ One immutable line per eval case — easy for Athena SerDe
import json, datetime
from pathlib import Path
def emit_eval_trace(row: dict, out_dir: Path):
dt = datetime.date.today().isoformat()
path = out_dir / f"dt={dt}" / "traces.jsonl"
path.parent.mkdir(parents=True, exist_ok=True)
required = {
"eval_id", "suite", "prompt_version", "model_id",
"pass", "latency_ms", "tokens_in", "tokens_out", "cost_usd",
"tenant_id", "ts",
}
missing = required - row.keys()
if missing:
raise ValueError(f"missing {missing}")
with path.open("a") as f:
f.write(json.dumps(row, separators=(",", ":")) + "\n")
# Sync nightly (or from the Batch eval job) — hive-style partitions
aws s3 sync ./evals/out/ s3://acme-agent-evals/evals/ \
--exclude "*" --include "dt=*/*"
❌ Nested giant JSON blobs per run with random keys; ✅ flat columns Athena can GROUP BY. Keep tool call detail in a nested array only if you need it — prefer a separate tool_events prefix if volume is huge.
Glue crawler vs explicit DDL vs CTAS
| Approach | When | Tradeoff |
|---|---|---|
| Glue crawler | Schema still evolving | Easy; watch type guesses on pass/cost_usd |
| Explicit Athena DDL | You own the contract | Best for CI-enforced schemas |
| CTAS to Parquet | Query volume grows | Faster/cheaper scans; add a compact job |
-- Explicit external table over JSONL
CREATE EXTERNAL TABLE agent_evals (
eval_id string,
suite string,
prompt_version string,
model_id string,
pass boolean,
latency_ms int,
tokens_in int,
tokens_out int,
cost_usd double,
tenant_id string,
ts string
)
PARTITIONED BY (dt string)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://acme-agent-evals/evals/'
TBLPROPERTIES ('has_encrypted_data'='false');
MSCK REPAIR TABLE agent_evals;
-- or ALTER TABLE agent_evals ADD PARTITION (dt='2026-09-21')
-- LOCATION 's3://acme-agent-evals/evals/dt=2026-09-21/';
-- Compact hot partitions to Parquet for repeated dashboards
CREATE TABLE agent_evals_parquet
WITH (
format = 'PARQUET',
parquet_compression = 'SNAPPY',
external_location = 's3://acme-agent-evals/evals_parquet/',
partitioned_by = ARRAY['dt']
) AS
SELECT * FROM agent_evals WHERE dt >= '2026-09-01';
SQL that product and platform both care about
-- Pass rate and cost by prompt version (last 7 days)
SELECT prompt_version,
count(*) AS n,
avg(CASE WHEN pass THEN 1.0 ELSE 0.0 END) AS pass_rate,
approx_percentile(latency_ms, 0.95) AS p95_ms,
sum(cost_usd) AS spend_usd
FROM agent_evals
WHERE dt BETWEEN '2026-09-14' AND '2026-09-21'
GROUP BY 1
ORDER BY pass_rate ASC, spend_usd DESC;
-- Regressions: versions that got slower at same suite
WITH base AS (
SELECT suite, prompt_version,
approx_percentile(latency_ms, 0.95) AS p95
FROM agent_evals
WHERE dt = '2026-09-21'
GROUP BY 1, 2
)
SELECT * FROM base ORDER BY p95 DESC LIMIT 20;
-- Tenant cost outliers (eval farm abuse / misconfig)
SELECT tenant_id, sum(cost_usd) AS spend, count(*) AS cases
FROM agent_evals
WHERE dt = '2026-09-21'
GROUP BY 1
HAVING sum(cost_usd) > 50
ORDER BY spend DESC;
Live per-request token burn still belongs in CloudWatch EMF; Athena is for suite-level history and prompt A/B, not second-by-second paging.
Wire evals into the agent release train
- Eval Batch/CodeBuild job writes JSONL → S3.
- Partition repair (Lambda on
s3:ObjectCreatedor nightlyMSCK). - Athena saved queries + optional QuickSight/Grafana Athena plugin.
- Gate deploys: fail the pipeline if
pass_rateforsuite=coredrops >2pp vs previousprompt_version.
Strict tool JSON contracts reduce flaky evals that look like model regressions. Guardrails on tool I/O (ApplyGuardrail) should be recorded as columns too (guardrail_intervened boolean) so you do not confuse blocked unsafe output with model failure.
// Minimal TypeScript emitter (same contract)
type EvalTrace = {
eval_id: string;
suite: string;
prompt_version: string;
model_id: string;
pass: boolean;
latency_ms: number;
tokens_in: number;
tokens_out: number;
cost_usd: number;
tenant_id: string;
ts: string;
guardrail_intervened?: boolean;
};
export function line(t: EvalTrace): string {
return JSON.stringify(t) + "\n";
}
When to leave Athena for a warehouse
Stay on Athena while: scans stay under a few GB/day compressed, concurrency is analyst-scale, and schema is wide-but-flat. Move (or add) a warehouse when: you need sub-second BI for dozens of editors, heavy joins across product DBs, or fine-grained row-level security beyond S3/Lake Formation. Lake Formation + IAM still cover many multi-tenant read patterns before you pay warehouse tax.
Checklist: eval lake in a day
- [ ] Freeze JSONL schema; enforce in emitter CI
- [ ] Hive partitions
dt=on S3; repair partitions automatically - [ ] Athena workgroup with result location + bytes-scanned cutoff
- [ ] Three saved queries: pass rate, p95, tenant spend
- [ ] Deploy gate on core suite pass_rate delta
- [ ] Columns for guardrail intervention and model id
- [ ] EMF for live cost; Athena for historical eval SQL
You do not need a warehouse to know which prompt version got worse overnight. You need honest traces, S3 partitions, and Athena.
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.