Latency Budgets skill for Codex

Split p99 across every hop, then set timeouts. Codex sets an end-to-end target from the user experience, maps the critical path from a real trace, and allocates a p99 budget to each sequential and parallel hop with headroom. It accounts for fan-out tail amplification and queueing, derives timeouts, deadlines and retry limits from the allocations, measures with open-model load, and checks percentiles from raw samples with a bundled script.

  • Performance
  • $latency-budgets
  • 134 lines
  • 3 files
  • ~1,650 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- latency-budgets

Installs to ~/.agents/skills/latency-budgets/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $latency-budgets, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Builds end-to-end latency budgets for a user-facing request and splits them across hops - edge, gateway, services, databases, third-party APIs - at p50, p99 and p99.9, accounting for fan-out tail amplification, retries, queueing and coordinated omission, with timeouts and deadlines that fit inside the budget. Includes a stdlib script that computes percentiles from raw samples, checks them against a budget and shows fan-out risk. Use when defining latency targets for a new API or page, when p99 is far worse than p50, when a call graph fans out to many services, or when timeouts and retries are set without a plan.

Use it when

  • Defining latency targets for a new API, service or page
  • p99 is far worse than p50, especially with fan-out to many services
  • Timeouts and retries were set by default rather than from a budget

Use something else when

How to check the result

An open-model load test at target throughput keeps end-to-end p99 within target with each hop inside its allocation, backed by at least 1,000 samples per endpoint.

The mistake it prevents

Averaging p99 values across instances or adding per-hop p99s as if percentiles were additive, which produces budgets that real tails never meet.

~/.agents/skills/latency-budgets/SKILL.md

--- name: latency-budgets description: Builds end-to-end latency budgets for a user-facing request and splits them across hops - edge, gateway, services, databases, third-party APIs - at… license: MIT ---

Done well means: the end-to-end target comes from the user experience, every hop on the critical path has an allocation at the percentile that matters (usually p99), the allocations add up with headroom, timeouts and retries are derived from the budget rather than defaults, and measurements use correct percentiles from raw data, not averages.

Principles

  • Budget at tail percentiles. p50 describes the typical request; users and SLOs feel p99.
  • Averages of percentiles are meaningless; percentiles do not add. Budget per hop, then validate end to end with measured distributions.
  • Fan-out amplifies tails: if a request waits on N parallel calls, each with a 1% chance of being slow, the chance the request is slow is 1 - 0.99^N (N = 10 -> ~10%, N = 100 -> ~63%). A per-call p99 becomes the request’s p90 or worse.
  • Queueing explodes near saturation: latency rises sharply above ~70–80% utilisation. Budgets assume a utilisation target; capacity planning keeps you there ($capacity-planning).
  • Load generators that wait for responses before sending the next request hide stalls (coordinated omission). Use open-model load (k6 constant-arrival-rate, wrk2, Gatling open injection) when measuring tails.

Workflow

- [ ] 1. Set the end-to-end target from UX/SLO (e.g. API p99 ≤ 400 ms; page LCP ≤ 2.5 s)
- [ ] 2. Draw the critical path: sequential hops vs parallel fan-out, from a real trace
- [ ] 3. Measure each hop's current p50/p99 (traces, histograms, raw samples)
- [ ] 4. Allocate the budget per hop with 15-25% headroom; find hops that cannot fit
- [ ] 5. Derive timeouts, retry policy and deadlines from the allocations
- [ ] 6. Fix or redesign the hops that do not fit (parallelise, cache, hedge, drop from critical path)
- [ ] 7. Verify end to end under open-model load at target throughput; wire alerts to SLOs

Allocation example

Target: GET /checkout/summary p99 ≤ 400 ms at the edge.

HopTypeMeasured p99Budget p99Timeout
CDN/edge + TLS to originsequential25 ms30 ms-
Gateway + authsequential18 ms25 ms50 ms
Checkout service own worksequential40 ms50 ms-
Cart, pricing, inventoryparallel fan-out (3)60 / 140 / 90 ms150 ms (slowest of three)180 ms each
Postgres (2 queries, sequential)sequential2 x 30 ms70 ms100 ms statement timeout
Serialisation + responsesequential10 ms15 ms-
Total340 ms + 60 ms headroomclient deadline 400 ms

Rules applied: sequential hops add; parallel hops cost the slowest member (at the request percentile, which is worse than any single member’s p99); headroom absorbs variance and growth.

Measuring

Raw samples beat pre-aggregated averages. Export durations from access logs, traces or load-test output and run:

python3 scripts/latency_report.py samples.txt --budget p95=250 --budget p99=400
python3 scripts/latency_report.py access.csv --csv-column duration_ms --budget p99=400 --fanout 3
k6 run --summary-export=summary.json script.js   # k6 thresholds can also enforce p(99)

The script prints count, mean, p50/p90/p95/p99/p99.9/max (nearest-rank), exits 1 when a budget is exceeded, warns when there are too few samples for tail percentiles (under 1,000), and with --fanout N shows what share of requests would hit at least one call slower than each per-call percentile.

  • Prometheus histograms: histogram_quantile(0.99, sum by (le) (rate(http_server_request_duration_seconds_bucket[5m]))). Accuracy depends on bucket boundaries; put boundaries near your budget values.
  • Never average p99s across instances or time windows; aggregate the histograms or raw data, then compute the percentile.

Turning budgets into timeouts and retries

  • Each hop’s timeout ≥ its p99 budget (or p99.9) so normal slow calls succeed, but well below the caller’s remaining budget so a retry still fits.
  • Propagate deadlines, not fixed timeouts: gRPC deadlines, or a request-scoped deadline passed downstream (remaining = deadline - now - local overhead). Stop work when the deadline passes.
  • Retries: at most one, only for idempotent calls, only if the remaining budget allows it, with jitter and a retry budget (e.g. ≤ 10% extra load). Retries at every layer multiply (3 layers x 3 attempts = 27 calls).
  • Hedged requests (send a second request after the p95 delay, take the first answer) cut tails for idempotent reads at a few percent extra load.
  • Timeout and retry mechanics (circuit breakers, bulkheads) belong to $resilience-patterns.

Fixing hops that do not fit

ProblemOptions
Sequential calls that are independentParallelise
Wide fan-out with bad tailReduce N (batch endpoints), hedge, tolerate partial results, cache
One slow dependency on the critical pathAsync it (queue, precompute), cache with TTL, serve stale
DB hop over budget$database-performance-triage
GC or runtime pauses in tailRuntime tuning ($jvm-tuning, $go-pprof, $node-profiling)
Cross-region hopCo-locate, or move the call off the request path
High utilisation queueingScale out, shed load, rate-limit low-priority traffic

Hard rules

  • Measure at the same point the budget is defined (edge vs service) and say which.
  • Report sample counts with tail percentiles; p99.9 of 500 samples is a single request.
  • Budget changes that alter production timeouts or retry counts are behavioural changes; roll out gradually and get the owning team’s agreement.
  • Load tests against shared or production environments need explicit approval.

Verification

  • Open-model load test at target throughput: end-to-end p99 within target, each hop within allocation (from traces), error rate unchanged.
  • The script’s budget checks pass on samples from the test, with ≥ 1,000 samples per endpoint for p99 claims.
  • SLO alerts ($slo-error-budgets) reference the same percentile and threshold.

Report template

## Latency budget: <request> (target p99 <= 400 ms at edge, 600 rps)
Critical path (from trace): edge -> gateway -> checkout -> [cart | pricing | inventory] -> postgres
| Hop | Budget p99 | Measured p99 | Timeout | Status |
|---|---|---|---|---|
| pricing | 150 ms | 210 ms | 180 ms | over: cache price lists (TTL 60 s) |
End to end (k6 constant-arrival-rate 600 rps, 10 min, 360k samples): p50 120, p99 470 ms -> after fixes p99 360 ms
Fan-out risk: 3 parallel calls, per-call p99 => ~3% of requests hit a slow call
Retries: one retry for cart only (idempotent), only if > 150 ms of budget remains

What the zip contains

Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Latency Budgets"
  short_description: "Split p99 across every hop, then set timeouts"
  default_prompt: "$latency-budgets Our checkout API p50 is 80 ms but p99 is 1.4 s, build a latency budget and find the hop that blows it"
scripts/latency_report.py102 lines, 3.7 KB
#!/usr/bin/env python3
"""Percentiles from raw latency samples, checked against a latency budget.

Standard library only. Reads one number per line (milliseconds) from a file or stdin,
or one column of a CSV with --csv-column. Prints count, mean, p50/p90/p95/p99/p99.9/max,
and optionally:

  --budget p95=300 --budget p99=800   fail (exit 1) if a percentile exceeds its budget
  --fanout 20                          show how per-call tail latency compounds when one
                                       request waits for N parallel calls

Usage:
  python3 latency_report.py samples.txt --budget p99=500
  python3 latency_report.py access.csv --csv-column duration_ms --budget p95=250 --fanout 10
  some_command | python3 latency_report.py -

Exit: 0 within budget, 1 budget exceeded, 2 bad input.
"""
from __future__ import annotations

import argparse
import csv
import math
import statistics
import sys

PCTS = [50, 90, 95, 99, 99.9]


def percentile(sorted_vals: list[float], p: float) -> float:
    """Nearest-rank percentile: the smallest value with at least p% of samples at or below it."""
    if not sorted_vals:
        raise ValueError("no samples")
    rank = max(1, math.ceil(p / 100 * len(sorted_vals)))
    return sorted_vals[rank - 1]


def read_samples(path: str, column: str | None) -> list[float]:
    fh = sys.stdin if path == "-" else open(path, newline="", encoding="utf-8")
    vals: list[float] = []
    with fh:
        if column:
            for row in csv.DictReader(fh):
                try:
                    vals.append(float(row[column]))
                except (KeyError, TypeError, ValueError):
                    continue
        else:
            for line in fh:
                line = line.strip()
                if not line or line.startswith("#"):
                    continue
                try:
                    vals.append(float(line))
                except ValueError:
                    continue
    return vals


def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    ap.add_argument("path", help="file with samples in ms, or - for stdin")
    ap.add_argument("--csv-column", help="read this column from a CSV with a header row")
    ap.add_argument("--budget", action="append", default=[], help="e.g. p95=300 (ms); repeatable")
    ap.add_argument("--fanout", type=int, help="number of parallel calls one request waits for")
    a = ap.parse_args()

    vals = sorted(read_samples(a.path, a.csv_column))
    if len(vals) < 2:
        print("need at least 2 numeric samples", file=sys.stderr)
        return 2

    res = {f"p{p:g}": percentile(vals, p) for p in PCTS}
    res["max"] = vals[-1]
    print(f"samples={len(vals)} mean={statistics.fmean(vals):.1f} ms")
    for k, v in res.items():
        print(f"  {k:>6}: {v:10.1f} ms")
    if len(vals) < 1000:
        print("  note: fewer than 1,000 samples; p99 and above are unreliable")

    failed = 0
    for b in a.budget:
        key, _, limit = b.partition("=")
        key = key.strip().lower()
        if key not in res or not limit:
            print(f"bad budget '{b}'; use one of {', '.join(res)}=<ms>", file=sys.stderr)
            return 2
        ok = res[key] <= float(limit)
        failed += not ok
        print(f"budget {key} <= {float(limit):g} ms: {'ok' if ok else 'EXCEEDED'} ({res[key]:.1f} ms)")

    if a.fanout:
        n = a.fanout
        print(f"\nfan-out {n}: chance a request waits on at least one call slower than ...")
        for p in (95, 99, 99.9):
            q = 1 - (p / 100) ** n
            print(f"  per-call p{p:g} ({res[f'p{p:g}']:.1f} ms): {q * 100:5.1f}% of requests")
    return 1 if failed else 0


if __name__ == "__main__":
    sys.exit(main())

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem