Amazon Athena: Query Coding-Agent Tool Audit Logs Without a Warehouse

2 views

Your coding agents already write tool-audit JSON to S3 — session id, tool name, latency, token counts, tenant. Dumping that into a warehouse “someday” means you still cannot answer “which tenant burned $400 of Bedrock last night?” today. Amazon Athena runs SQL against the S3 objects (via Glue Data Catalog) so forensics and cost queries stay serverless. Distinct from CloudTrail Lake (organization CloudTrail / IAM abuse), CloudWatch Logs Insights (live log groups), and Detective (entity investigation graphs): here we query your application-level tool audit lake on S3.

⚡ TL;DR: Emit newline-delimited JSON tool audits to partitioned S3 prefixes, crawl/register with Glue, query with Athena for abuse/cost/quality joins, and keep CloudTrail Lake for IAM. Related: CloudTrail Lake IAM abuses, GuardDuty compromised credentials, ADOT multi-hop traces, Budgets + Cost Anomaly.

What belongs in the tool-audit lake

Emit one JSON object per tool invocation (or per turn summary). Keep it boring and columnar-friendly:

json
{
  "ts": "2026-10-04T20:15:03.441Z",
  "account_id": "111122223333",
  "tenant_id": "acme",
  "session_id": "sess_9f3a",
  "agent_role": "patcher",
  "prompt_version": "3",
  "tool": "apply_patch",
  "ok": true,
  "latency_ms": 842,
  "input_bytes": 12044,
  "output_bytes": 220,
  "bedrock_input_tokens": 4100,
  "bedrock_output_tokens": 900,
  "model_id": "anthropic.claude-sonnet-4-20250514-v1:0",
  "repo": "acme/payments",
  "pr_number": 1882,
  "error_code": null,
  "actor_arn": "arn:aws:sts::111122223333:assumed-role/batch-agent-job/job-1"
}
Store Use Not for
S3 + Athena Ad-hoc SQL, cost rollups, forensics Sub-second product UI queries
CloudWatch Logs Insights Live debugging last hours/days Cheap year-long analytics at scale
CloudTrail Lake Who called which AWS API Your custom tool names
Detective Credential compromise graphs Token-cost by prompt_version
OpenSearch Full-text / semantic scratch Pennies-per-TB SQL scans

❌ Only relying on CloudTrail: it will not show tool=apply_patch or prompt versions.

Land data so Athena does not hate you

Partition by date (and tenant if huge). Use JSON-SerDe or — better — convert to Parquet with a Firehose/ETL job once volume grows.

s3://coding-agent-audit-prod/
  tool_invocations/
    dt=2026-10-04/
      tenant=acme/
        part-000.gz
python
# ✅ agent host: append NDJSON to dated prefix (or Kinesis Firehose)
import json, gzip, io, boto3
from datetime import datetime, timezone

s3 = boto3.client("s3")

def emit_tool_audit(record: dict):
    dt = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    tenant = record["tenant_id"]
    key = f"tool_invocations/dt={dt}/tenant={tenant}/{record['session_id']}-{record['ts']}.json.gz"
    buf = io.BytesIO()
    with gzip.GzipFile(fileobj=buf, mode="wb") as gz:
        gz.write((json.dumps(record) + "\n").encode())
    s3.put_object(
        Bucket="coding-agent-audit-prod",
        Key=key,
        Body=buf.getvalue(),
        ServerSideEncryption="aws:kms",
        ContentType="application/json",
    )

Prefer Firehose → S3 with GZIP and period partitioning when QPS rises. Tag the bucket; block public access; Macie-scan if agents might leak secrets into fields (Macie on artifact buckets).

Glue table + Athena workgroup

sql
-- ✅ external table over JSON (simplify types as needed)
CREATE EXTERNAL TABLE IF NOT EXISTS coding_agent.tool_invocations (
  ts string,
  account_id string,
  tenant_id string,
  session_id string,
  agent_role string,
  prompt_version string,
  tool string,
  ok boolean,
  latency_ms int,
  input_bytes bigint,
  output_bytes bigint,
  bedrock_input_tokens int,
  bedrock_output_tokens int,
  model_id string,
  repo string,
  pr_number int,
  error_code string,
  actor_arn string
)
PARTITIONED BY (dt string, tenant string)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://coding-agent-audit-prod/tool_invocations/'
TBLPROPERTIES ('has_encrypted_data'='true');

MSCK REPAIR TABLE coding_agent.tool_invocations;
-- or ALTER TABLE ... ADD PARTITION for tighter control
bash
# ✅ Athena workgroup with enforce output location + bytes scanned cutoff
aws athena create-work-group \
  --name coding-agent-forensics \
  --configuration ResultConfigurationUpdates={OutputLocation=s3://athena-results-prod/coding-agent/},EnforceWorkGroupConfiguration=true,BytesScannedCutoffPerQuery=10737418240

Queries that earn their keep

sql
-- ✅ Bedrock token burn by tenant + prompt_version (yesterday IST window ≈ UTC day)
SELECT tenant_id,
       prompt_version,
       COUNT(*) AS tool_calls,
       SUM(bedrock_input_tokens + bedrock_output_tokens) AS tokens,
       SUM(CASE WHEN ok THEN 0 ELSE 1 END) AS failures
FROM coding_agent.tool_invocations
WHERE dt = '2026-10-03'
GROUP BY 1, 2
ORDER BY tokens DESC
LIMIT 50;
sql
-- ✅ suspicious: apply_patch spikes for one actor
SELECT actor_arn, COUNT(*) AS patches, COUNT(DISTINCT repo) AS repos
FROM coding_agent.tool_invocations
WHERE dt = '2026-10-04'
  AND tool = 'apply_patch'
GROUP BY 1
HAVING COUNT(*) > 200
ORDER BY patches DESC;
sql
-- ✅ join-quality: tool error rate by agent_role
SELECT agent_role, tool,
       AVG(latency_ms) AS p_avg_ms,
       AVG(CASE WHEN ok THEN 0.0 ELSE 1.0 END) AS err_rate
FROM coding_agent.tool_invocations
WHERE dt BETWEEN '2026-10-01' AND '2026-10-04'
GROUP BY 1, 2
ORDER BY err_rate DESC;

Correlate IAM oddities with CloudTrail Lake and escalate credential compromise with Detective / GuardDuty. Athena answers “what did the agent tool layer do?” — CloudTrail answers “which AWS APIs did the role call?”

Cost control for the queries themselves

Mistake Fix
SELECT * over months Always filter dt; project columns
No workgroup cutoff Set bytes scanned cutoff
Tiny gzip files Firehose buffer or compact to Parquet
Results bucket open Private + lifecycle expire 7–30d
Glue crawler chaos Explicit partitions for high-churn tenants

Convert hot partitions to Parquet/ORC when monthly scans hurt. Keep ADOT traces for hop-level latency; use Athena for fleet rollups — not as a substitute for Application Signals SLOs.

Production checklist

  • [ ] NDJSON (or Parquet) tool audits to partitioned S3; KMS encrypted
  • [ ] Schema stable; versions additive; document field meanings
  • [ ] Glue table + repair/add partitions automated
  • [ ] Athena workgroup: enforced output location + scan cutoff
  • [ ] Saved queries for cost-by-tenant, patch spikes, error rates
  • [ ] IAM: analysts can athena:StartQueryExecution only on that workgroup
  • [ ] Clear split: Athena (app audits) vs CloudTrail Lake (AWS APIs) vs Detective
  • [ ] Lifecycle rules on audit + results buckets
  • [ ] Alert when emit pipeline lag > N minutes

FAQ

Q: Athena or OpenSearch for tool logs?
A: OpenSearch when you need full-text search and dashboards with sub-second filters. Athena when you want SQL + cheap scans over months of JSON/Parquet without running a cluster.

Q: Should every token stream go to S3?
A: No — store counts and metadata, not raw prompts/completions with secrets. If you must retain payloads, encrypt, redact, and bucket-policy deny public + cross-account by default.

Q: How does this relate to Cost Explorer?
A: Cost Explorer shows AWS service bills. Athena shows which tenant/prompt/tool drove Bedrock tokens inside your app. You need both to attribute spend.

Related reading

Park the JSON in S3. Ask SQL questions. Skip the warehouse until Athena’s bills annoy you — then go columnar, not theatrical.

Last updated on October 4, 2026

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.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.