docs/agent-resource-safety.md
Rama main · réplica de lectura del repositorio original.
# Agent rules for concurrency and resource safety
These rules apply when changing request ingestion, HTTP/SSE transport, protocol conversion, asynchronous usage/billing work, notifications, or in-memory caches. They supplement the repository protocol translation and billing rules.
- Never capture `*gin.Context`, a response writer, or a pooled/mutable request object in work that can outlive the handler. Compute immutable billing, routing, model, and logging snapshots before enqueueing. `c.Copy()` is not a substitute for explicit ownership of referenced data.
- Request buffering must use the shared ingress memory budget before allocating, including unknown-length uploads, decompression, normalization, and contiguous copies. Per-user concurrency acquired after reading a body does not protect ingress. Distinguish temporary capacity exhaustion (503 with retry guidance) from a per-request size violation (413); never turn either into a successful or truncated request.
- Bound decompressor window/memory and internal concurrency as well as decoded output. Read one byte beyond an output limit to detect overflow. Do not assume `io.LimitReader` outside a decoder bounds its internal allocations.
- Bound streaming data at the line, accumulated event, and queued-byte levels. A channel capacity limits item count, not memory. Document any single-item exception and its hard size cap. A sequence of short lines, comments, or heartbeats must not bypass event bounds or cancellation/drain budgets.
- Use per-operation upload/read and downstream write/flush deadlines for long-lived HTTP streams. Do not impose a short deadline on the entire healthy stream. After client cancellation, any upstream drain for late billing usage must have a finite budget and release its body, timers, goroutines, and concurrency slots. Resource limits and truncated streams must never manufacture a successful terminal event.
- No unbounded per-request background goroutines or queues for persistence, notifications, or retries. Reuse a bounded executor, define overload behavior, give I/O an explicit deadline, and integrate shutdown. `context.WithoutCancel` alone is not an I/O timeout. Financial work must not be silently dropped; notification work may be best effort with observable drops.
- In-memory caches need active expiry and explicit entry/byte bounds. A timestamp checked only on lookup does not reclaim abandoned keys. Avoid one timer/goroutine per key and unbounded scans or network I/O under shared locks; tie background cleanup to service shutdown.
- Keep work proportional to input/output size. Avoid growing-string concatenation per delta, repeated whole-prefix copies/hashes, and repeated full JSON decoding on hot paths. Preserve protocol compatibility through RelayKit under the rules above; performance work does not authorize a local translator or changing billing semantics.
- For an already-active legacy converter, storage-only maintenance must preserve the same mappings, emitted events, state transitions, signatures, usage and public snapshots. Retain all compatibility regressions and verify size-scaling allocations. This does not authorize new translation behavior, restored fallbacks, or expanded legacy coverage; those changes still require RelayKit first.
- Treat zero-copy strings/slices, pooled buffers, and cached objects as immutable while borrowed. Do not return storage to a pool, mutate it, or retain a small slice of a large buffer across asynchronous boundaries without an explicit ownership/copy policy.
- For changes to these boundaries, add focused regression coverage for overload, cancellation, expiry, and shutdown as applicable; run affected tests with `-race`. Use representative size-scaling benchmarks for algorithm changes and report cumulative allocations separately from retained heap/RSS. Keep configuration defaults, validation, and deployment examples consistent, and regenerate Wire after lifecycle/DI changes.