client io architecture

This commit is contained in:
Ilia Denisov
2026-03-12 18:45:46 +02:00
committed by GitHub
parent 2dafa69b93
commit 079b9facb0
36 changed files with 1810 additions and 460 deletions
+922
View File
@@ -0,0 +1,922 @@
# AGENTS.md
> This file defines how Codex and other coding agents should operate in this repository.
> It is intentionally strict, verbose, and easy to trim down.
> If any instruction here conflicts with an explicit user request in the chat, the user request wins.
> If any instruction here conflicts with a deeper repository-local instruction in a subdirectory `AGENTS.md`, the deeper file wins for files inside that subtree.
---
## 1. Purpose
This repository is developed primarily in Go.
The agent must optimize for:
- correctness before speed,
- readability before cleverness,
- explicit behavior before hidden magic,
- small, reviewable changes,
- reproducible builds and tests,
- clear written reasoning for non-obvious decisions.
The agent should behave like a careful senior Go engineer working in an existing codebase with real maintenance costs.
---
## 2. Core operating rules
### 2.1 Main priorities
When making changes, follow this order of priority:
1. Preserve correctness.
2. Preserve or improve clarity.
3. Preserve compatibility unless the task explicitly allows breaking changes.
4. Keep the diff minimal.
5. Keep the implementation idiomatic for modern Go.
6. Keep performance reasonable, but do not micro-optimize without evidence.
### 2.2 What the agent must not do
The agent must not:
- rewrite large areas of code without clear need,
- introduce speculative abstractions,
- rename many symbols “for cleanliness” unless required,
- mix unrelated refactors with the requested task,
- silently change public behavior,
- silently change wire formats, database semantics, or API contracts,
- add dependencies unless necessary,
- invent requirements not stated by the user or codebase,
- leave TODOs instead of implementing the requested behavior, unless explicitly asked,
- claim code was tested if it was not actually tested,
- claim a root cause without evidence,
- fix extra bugs opportunistically unless they are tightly adjacent and clearly explained.
### 2.3 Expected default behavior
Unless the user asks otherwise, the agent should:
- inspect the relevant code path before editing,
- understand current behavior before proposing changes,
- prefer the smallest correct patch,
- update or add tests for every functional change,
- keep public interfaces stable,
- preserve log/event/metric semantics unless a change is needed,
- explain assumptions,
- mention trade-offs when they matter.
### 2.3 Expected documentation behavior
Unless the user asks otherwise, the agent should:
- supply added packages, types, funcs, consts and vars with a commentaries explaining its purpose and behavior,
- supply public functions with a more comprehensive commentary and supplemental funcs with more concise comments,
- provide comments respecting the Go Doc Comments syntax,
- provide comments only in English language,
- translate any non-English commetraries met in existing code,
- correct obvious grammatical and style errors in existing commentaries.
---
## 3. Repository familiarization workflow
Before making non-trivial changes, the agent should quickly map the local conventions.
### 3.1 Files to inspect first
Prefer inspecting, when present:
- `go.mod`
- `go.sum`
- `README.md`
- `Makefile`
- `Taskfile.yml` / `Taskfile.yaml`
- `.golangci.yml` / `.golangci.yaml`
- `.editorconfig`
- `buf.yaml`
- `buf.gen.yaml`
- `Dockerfile*`
- `compose*.yml`
- CI files under `.github/workflows/`, `.gitlab-ci.yml`, etc.
- migration directories
- existing `AGENTS.md` files in subdirectories
- representative files in the affected package
- representative tests in the affected package
### 3.2 Conventions to infer
The agent should infer and follow:
- package layout style,
- naming conventions,
- error handling conventions,
- logging conventions,
- context usage conventions,
- test style,
- benchmark style,
- dependency injection pattern,
- API versioning conventions,
- DTO/model separation style,
- storage and transaction conventions,
- lint and formatting requirements.
If conventions are inconsistent, prefer the one used in the closest affected code.
---
## 4. Scope control
### 4.1 Stay within scope
The agent must solve the users request directly and avoid unrelated cleanup.
Allowed adjacent changes:
- fixing a test broken by the main change,
- adding a missing helper required by the main change,
- small refactors necessary to make the change safe,
- updating documentation directly affected by the change.
Not allowed without explicit justification:
- formatting unrelated files,
- reorganizing package structure,
- replacing libraries,
- changing error taxonomy globally,
- changing logging framework,
- broad “modernization” passes,
- large dependency bumps.
### 4.2 When the requested change is underspecified
If details are missing, the agent should:
1. infer the most conservative behavior from existing code,
2. avoid breaking current behavior,
3. document the chosen assumption in the final response.
Do not block on avoidable clarification if a reasonable implementation path exists.
---
## 5. Go version and language guidance
### 5.1 Target version
Target the Go version declared in `go.mod`.
If the repository does not make this obvious, assume modern stable Go and avoid experimental features unless already present.
### 5.2 Idiomatic Go requirements
The agent should prefer:
- simple package APIs,
- concrete types when interfaces are not needed,
- small interfaces defined by consumers,
- explicit error handling,
- early returns,
- table-driven tests where appropriate,
- `context.Context` as the first parameter for request-scoped operations,
- `errors.AsType` first, `errors.Is` / `errors.As` last,
- standard library first.
The agent should avoid:
- unnecessary generics,
- unnecessary reflection,
- hidden global state,
- panics for expected errors,
- overuse of empty interfaces or `any`,
- deeply nested control flow,
- concurrency without clear benefit,
- channel-based designs where a simple call flow is better.
### 5.3 Style details
Prefer:
- short, focused functions,
- package-level cohesion,
- exported identifiers only when needed,
- comments for exported symbols,
- comments explaining “why”, not narrating trivial code,
- stable and unsurprising zero values where appropriate.
Avoid:
- single-letter names except tight local scopes,
- clever helper layers that obscure flow,
- Boolean parameter lists that are hard to read,
- hidden side effects,
- magic constants without names.
---
## 6. Editing rules for Go code
### 6.1 Function and type changes
When modifying a function or method, the agent should:
- preserve signature compatibility unless the task explicitly requires change,
- preserve context and cancellation behavior,
- preserve caller expectations,
- update all call sites,
- update tests that express expected behavior.
When adding new exported API:
- keep it minimal,
- document it,
- justify why export is needed,
- prefer package-private helpers if external use is not required.
### 6.2 Error handling
The agent must:
- return errors, not swallow them,
- wrap errors when adding useful context,
- avoid duplicative wrapping,
- preserve sentinel errors or typed errors already used in the codebase,
- use `%w` correctly,
- not log and return the same error at multiple layers unless the codebase explicitly does that.
If the codebase distinguishes user-facing, domain, transport, and storage errors, preserve that separation.
### 6.3 Context usage
The agent must:
- pass context through relevant call chains,
- not store contexts in structs,
- not use `context.Background()` in request flows unless clearly appropriate,
- respect cancellation and deadlines when existing code expects that,
- avoid creating child contexts unnecessarily.
### 6.4 Concurrency
Only introduce concurrency if it clearly improves the requested behavior and does not degrade maintainability.
If adding concurrency, the agent must consider:
- cancellation,
- data races,
- goroutine lifetime,
- bounded parallelism,
- error propagation,
- testability,
- deterministic shutdown.
Avoid spawning goroutines without a clear ownership model.
### 6.5 Logging and observability
Follow existing repository conventions.
The agent should:
- keep logs structured if the codebase uses structured logging,
- avoid logging sensitive values,
- avoid noisy logs in hot paths,
- preserve stable field names when logs are used operationally,
- update metrics/traces only when directly relevant.
Do not add logs as a substitute for error handling.
---
## 7. Testing requirements
### 7.1 General rule
Every behavior change should be covered by tests unless the repository clearly does not test that layer.
A functional code change without tests requires a clear reason in the final response.
### 7.2 Preferred testing style
Prefer:
- table-driven tests,
- focused tests per behavior,
- `testify` for assertions and requirements if the repository already uses it or if new tests are added and no conflicting convention exists,
- deterministic tests,
- subtests with meaningful names,
- minimal fixtures,
- clear failure messages.
### 7.3 What tests should verify
Tests should verify:
- externally observable behavior,
- error cases,
- edge cases,
- nil / empty / zero-value behavior where relevant,
- backward compatibility where relevant,
- concurrency behavior if changed,
- serialization/deserialization boundaries if relevant.
### 7.4 What tests should avoid
Avoid tests that are:
- tightly coupled to private implementation details without need,
- flaky,
- timing-sensitive without control,
- dependent on wall clock when fake time can be used,
- dependent on random behavior without fixed seed,
- dependent on external services unless the repository already uses integration test infrastructure.
### 7.5 Test commands
Prefer repository-native commands first.
Common examples:
```bash
go test ./...
go test ./... -race
go test ./... -cover
```
If a narrower command is sufficient, use the smallest command that provides confidence.
---
## 8. Dependency policy
### 8.1 Default rule
Prefer the Go standard library and existing repository dependencies.
Do not add a new dependency unless it provides clear value that is difficult to replicate safely with existing tools.
### 8.2 If adding a dependency is necessary
The agent must:
- choose a well-maintained package,
- minimize dependency surface,
- avoid dependency overlap,
- explain why the new dependency is needed,
- update tests and usage accordingly.
Avoid adding heavy frameworks into lightweight packages.
---
## 9. Performance policy
### 9.1 Default stance
Do not optimize speculatively.
Prefer clear code first, then optimize only if:
- the task is explicitly performance-related,
- the affected path is obviously hot,
- profiling evidence is available,
- the repository already treats this path as performance-sensitive.
### 9.2 When performance matters
The agent should consider:
- allocations,
- copies,
- unnecessary conversions,
- lock contention,
- query count,
- I/O amplification,
- algorithmic complexity.
If making a performance optimization, document the trade-off and preserve readability as much as possible.
---
## 10. API, wire format, and compatibility rules
### 10.1 Backward compatibility
Assume compatibility matters unless the task says otherwise.
The agent must not casually change:
- JSON field names,
- protobuf field numbers,
- SQL schema semantics,
- HTTP status codes,
- error codes,
- event payloads,
- config keys,
- environment variable names,
- CLI flags,
- file formats.
### 10.2 If a breaking change is necessary
The agent should:
- keep the change localized,
- update affected tests,
- update docs and examples,
- explicitly call out the break in the final response.
---
## 11. Database and persistence guidance
If the repository interacts with a database, the agent should preserve data safety first.
### 11.1 Queries and mutations
The agent must:
- understand existing transaction boundaries,
- avoid introducing N+1 query patterns,
- preserve idempotency where relevant,
- preserve isolation expectations,
- handle `sql.ErrNoRows` or equivalent consistently.
### 11.2 Migrations
If adding or changing migrations:
- make them forward-safe,
- avoid destructive changes unless explicitly requested,
- preserve rollback strategy if the repository uses one,
- avoid combining schema and risky data backfills blindly,
- update related models, queries, and tests.
### 11.3 Data correctness
The agent must be conservative with:
- nullability,
- defaults,
- unique constraints,
- indexes,
- timestamp semantics,
- timezone handling,
- soft-delete semantics.
---
## 12. HTTP / RPC / messaging guidance
### 12.1 Handlers and transport code
When editing transport-layer code, preserve:
- status code semantics,
- request validation behavior,
- response shape,
- middleware expectations,
- authn/authz boundaries,
- timeout and cancellation behavior.
### 12.2 Serialization
The agent must:
- keep wire compatibility,
- avoid changing omitempty behavior casually,
- handle unknown fields according to existing patterns,
- preserve canonical formats if already established.
### 12.3 Messaging / events
For queues, streams, or pub/sub:
- preserve event contract stability,
- preserve delivery assumptions,
- preserve idempotency handling,
- avoid changing partitioning or keys without reason.
---
## 13. CLI and developer-experience guidance
If the repository includes CLI commands or tooling, the agent should preserve UX consistency.
Do not casually change:
- command names,
- flag names,
- exit code semantics,
- help text style,
- config resolution order.
When adding a flag or command:
- keep naming consistent,
- document defaults,
- handle invalid input cleanly,
- add tests where feasible.
---
## 14. Security and secrets handling
The agent must treat security as a default concern.
### 14.1 Must avoid
Never:
- commit secrets,
- log tokens, passwords, cookies, private keys, or connection strings,
- weaken auth checks casually,
- disable TLS verification without explicit reason,
- interpolate untrusted input into shell/SQL/HTML/paths unsafely,
- introduce path traversal risks,
- trust user input without validation.
### 14.2 Must consider
Consider:
- input validation,
- output encoding,
- least privilege,
- SSRF risk,
- command injection,
- SQL injection,
- deserialization safety,
- sensitive data redaction,
- constant-time comparisons where relevant,
- secure defaults.
### 14.3 Authentication and authorization
Preserve existing auth boundaries.
If a task touches auth logic, the agent must be especially conservative and update tests for both allowed and denied cases.
---
## 15. Configuration guidance
The agent should preserve current configuration patterns.
Do not casually change:
- env var names,
- precedence rules,
- default values,
- required/optional behavior,
- config file schema.
When adding configuration:
- prefer clear names,
- define sane defaults,
- validate values,
- document behavior,
- update examples if present.
---
## 16. Documentation update policy
Update documentation when the user-visible or developer-visible behavior changes.
Potential files to update:
- `README.md`
- package docs
- API docs
- CLI help
- examples
- migration notes
- deployment docs
Do not rewrite large docs unless necessary.
---
## 17. Commenting policy
### 17.1 Code comments
Use comments sparingly but effectively.
Add comments when:
- exporting a symbol,
- explaining why a non-obvious approach is used,
- documenting invariants,
- clarifying ownership/lifecycle/concurrency rules.
Do not add comments that merely restate obvious code.
### 17.2 Commit-style explanations in response
In the final response, the agent should explain:
- what changed,
- why it changed,
- what assumptions were made,
- what was tested,
- any notable trade-offs.
---
## 18. How to present work in chat
When the agent responds with implementation details, it should be concise but complete.
### 18.1 Final response should usually include
- a short summary of the change,
- the key files modified,
- important reasoning or assumptions,
- test commands executed,
- any remaining risks or follow-ups if relevant.
### 18.2 The agent must not
- dump huge irrelevant code blocks if files were already edited,
- exaggerate confidence,
- claim tests passed if they were not run,
- omit important caveats.
---
## 19. Patch construction guidance
### 19.1 Preferred change shape
Prefer a sequence like:
1. smallest safe production change,
2. tests that capture behavior,
3. minimal docs update if needed.
### 19.2 Refactoring threshold
Refactor only when necessary to support the requested change.
Good reasons:
- current structure prevents a safe fix,
- testability is too poor to validate behavior,
- the bug stems from tangled responsibilities,
- a small extraction materially reduces risk.
Bad reasons:
- personal style preference,
- “cleaner architecture” ambitions,
- speculative future use cases.
---
## 20. Large or risky changes
For changes with broad blast radius, the agent should be more conservative.
Examples:
- auth,
- billing,
- persistence,
- migrations,
- concurrency,
- public APIs,
- shared libraries,
- critical hot paths.
In such cases, the agent should:
- minimize the changed surface area,
- add focused regression coverage,
- call out risk explicitly,
- avoid mixing in refactors.
---
## 21. When the agent should stop and report limits
The agent should explicitly say so if:
- the repository is missing files needed to implement safely,
- tests cannot be run in the environment,
- behavior depends on unknown external systems,
- a breaking design choice is required but unspecified,
- the requested change would be unsafe without broader context.
In those cases, still provide the best grounded partial result possible.
---
## 22. Preferred workflow for bug fixes
When fixing a bug, the agent should generally follow this order:
1. identify the failing behavior,
2. inspect the smallest relevant code path,
3. preserve existing public contract,
4. implement the minimal fix,
5. add or update regression tests,
6. verify no adjacent behavior was unintentionally changed.
If the root cause is uncertain, state that clearly and avoid overstating certainty.
---
## 23. Preferred workflow for new features
When implementing a feature, the agent should generally:
1. inspect similar existing features,
2. match established architecture,
3. add the smallest useful surface area,
4. keep compatibility where possible,
5. add tests for success and failure paths,
6. update minimal necessary docs.
---
## 24. Preferred workflow for refactoring
For refactors, the agent must preserve behavior.
The agent should:
- keep refactors mechanical and reviewable,
- avoid semantic drift,
- maintain test coverage,
- separate pure refactor from behavior change whenever practical.
If both are unavoidable in one patch, explain that clearly.
---
## 25. Monorepo / multi-package guidance
If this repository contains multiple services or packages, the agent should:
- change only the relevant module/package unless broader edits are required,
- respect local conventions of the touched area,
- check for local `AGENTS.md` files,
- avoid introducing cross-package coupling casually.
---
## 26. File and package organization guidance
When adding new files:
- place them near the owning package,
- use existing naming conventions,
- avoid generic names like `common.go`, `helpers.go`, `utils.go` unless that pattern already exists,
- keep package boundaries clear.
When adding helpers, prefer names tied to the domain or behavior.
---
## 27. Example Go-specific preferences
These are defaults unless the repository already uses a different style.
### 27.1 Error examples
Preferred:
```go
func ParsePort(s string) (int, error) {
port, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("parse port %q: %w", s, err)
}
if port < 1 || port > 65535 {
return 0, fmt.Errorf("parse port %q: out of range", s)
}
return port, nil
}
```
Avoid:
```go
func ParsePort(s string) (int, error) {
i, _ := strconv.Atoi(s)
return i, nil
}
```
### 27.2 Context examples
Preferred:
```go
func (s *Service) Fetch(ctx context.Context, id string) (*Item, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
return s.repo.Fetch(ctx, id)
}
```
Avoid:
```go
func (s *Service) Fetch(id string) (*Item, error) {
return s.repo.Fetch(context.Background(), id)
}
```
### 27.3 Table-driven tests
Preferred:
```go
func TestParsePort(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
want int
wantErr bool
}{
{name: "valid", input: "8080", want: 8080},
{name: "non-numeric", input: "abc", wantErr: true},
{name: "out of range", input: "70000", wantErr: true},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := ParsePort(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
```
---
## 28. Suggested command checklist
Before concluding, the agent should use the smallest relevant subset of these commands when available and appropriate:
```bash
go test ./...
go test ./... -race
go test ./... -cover
go vet ./...
golangci-lint run
staticcheck ./...
go test ./path/to/pkg -run TestName -v
```
Use repository-native wrappers first if they exist, for example:
```bash
make test
make lint
task test
task lint
```
---
## 29. Suggested final response template
Use this shape unless the user asked for something else:
1. What changed.
2. Why it changed.
3. Files touched.
4. Tests run.
5. Assumptions or caveats.
Be direct. Do not pad the response.
---
## 30. Bottom-line instruction
When in doubt, the agent should choose the safest change that:
- solves the actual user request,
- matches existing repository conventions,
- preserves compatibility,
- adds or updates tests,
- keeps the diff small and reviewable.