Why agree on a communication convention
Have you encountered backend projects with inconsistent formats? Recently, while refactoring a Go backend project, a painful factor was the inconsistent request and response formats. This is a straightforward and simple issue, but it can cause a lot of trouble in practice.
Frontend and backend should have some sort of contract — it could be a “data format document” like OpenAPI, tRPC, or rules like RESTful — to reduce friction in communication.
If even the communication method isn’t standardized, or if some weird custom approach is used, it becomes hard for any developer or AI to interpret. For example, I encountered a project that shoved the response status into a custom msg field; people unfamiliar with that would easily implement it as “successful response but actually failed”.
- You need to understand a custom abstraction to modify code
- Code depends on unique context
- Hard to maintain
const { msg } = await updateUserPassword();if (msg === 'ok') {}Best to follow conventions
By “convention” I mean industry practice. This kind of “unspoken understanding that requires no explanation” is the real key to reducing development cost, because AI and developers already have built-in knowledge to handle it quickly and reliably. For example, the issue above can be clearly resolved using HTTP status codes; below we use the Response.ok API to confirm the response code is in the 200–299 range.
const res = await updateUserPassword();if (res.ok) { // body}How to do it in the AI era?
The above is a description of common best practices — everyone understands it, but the hard part is to keep it. In the AI-assisted era, the first thing to do is to write the team’s consensus and specifications as documentation, such as the Agent Skills standard:
---name: response-helperdescription: Provide a standardized HTTP response patterncompatibility: opencode---
## What I do
I provide a standardized HTTP response pattern with generic type support. I help you:
- Build consistent API responses with a standard envelope structure- Use generics to preserve type information in response data- Handle success and error responses uniformly- Include pagination metadata when needed
## When to use me
Use this skill when:- Need consistent response format across endpoints- Want type-safe response data with generics- Implementing pagination responses
## Response Structure
```gotype Response[T any] struct { Success bool `json:"success"` Msg string `json:"msg,omitempty"` Data T `json:"data,omitempty"` Error *ErrorInfo `json:"error,omitempty"` Meta *PaginationMeta `json:"meta,omitempty"`}
type ErrorInfo struct { Code int `json:"code"` Message string `json:"message"`}
type PaginationMeta struct { Page int `json:"page,omitempty"` PerPage int `json:"per_page,omitempty"` TotalCount int64 `json:"total_count,omitempty"`}## Usage
### Basic Success Response
```gofunc GetUser(c *gin.Context) { var user User // ... fetch user
response.Success(c, user)}// Response: {"success": true, "msg": "ok", "data": {...}}### Success with Type (Generic)
```goresponse.Success(c, MyStruct{Field: "value"})// Response: {"success": true, "msg": "ok", "data": {"field": "value"}}### Success with Pagination
```govar users []Usermeta := &response.PaginationMeta{ Page: 1, PerPage: 10, TotalCount: 100,}// ... fetch users
response.SuccessWithMeta(c, users, meta)// Response: {"success": true, "msg": "ok", "data": [...], "meta": {"page": 1, "per_page": 10, "total_count": 100}}### Error Response
```goresponse.Error(c, http.StatusBadRequest, http.StatusBadRequest, "missing id")// Response: {"success": false, "error": {"code": 400, "message": "missing id"}}
response.BadRequest(c, "INVALID_PARAM", "missing id")response.NotFound(c, "user not found")response.Unauthorized(c, "token expired")response.InternalError(c, "database error")## Helper Functions
| Function | Status Code | Default Code | Description ||----------|-------------|--------------|--------------|| `Success(c, data)` | 200 | - | Success with data || `SuccessWithMeta(c, data, meta)` | 200 | - | Success with pagination || `Error(c, status, code, msg)` | custom | custom | Custom error || `BadRequest(c, code, msg)` | 400 | code | Bad request error || `Unauthorized(c, msg)` | 401 | UNAUTHORIZED | Unauthorized || `Forbidden(c, msg)` | 403 | FORBIDDEN | Forbidden || `NotFound(c, msg)` | 404 | NOT_FOUND | Not found || `InternalError(c, msg)` | 500 | INTERNAL_ERROR | Internal server error |
## Benefits
1. **Type Safety**: Generics preserve the type of response data instead of using `interface{}`2. **Consistency**: Single response envelope format across all endpoints3. **Developer Experience**: Clear success (`msg: "ok"`) and error structure4. **Pagination**: Built-in meta for paginated responsesAI is least capable of handling missing context it doesn’t know how to fill in. Clear documentation planning is more important than ever. With a clear blueprint, you can design a Loop to let an AI Agent achieve a goal with a concrete outcome — for example the case above: “migrate existing responses to the format described by the Skill”.
Designing an Agent Loop for acceptance and execution
Use Codex’s built-in tool for running Agent loops: goal as an example. The official documentation mentions that a Goal typically defines six things:
- Outcome (the state that should be true upon completion)
- Verification surface (tests, benchmarks, reports, or output that can prove success)
- Constraints (things that must not regress during the work)
- Boundaries (files, tools, and data scope Codex may use)
- Iteration (conditions for continuing work)
For the task “unify existing API response formats”, you can design a prompt like this:
/goal Migrate all HTTP handlers in internal/handler/ to use the response helper defined in .agents/skills/response-helper/SKILL.md
Outcome:Every handler in internal/handler/ must return responses exclusively through the response helper. No handler may call c.JSON, c.String, or any raw response method directly.
Verification surface:1. Run `grep -rn "c\.JSON\|c\.String\|c\.Data" internal/handler/` — result must be empty.2. Run `go build ./...` — must exit 0.3. Run `go test ./internal/handler/...` — must exit 0 with no failures.
Constraints:- Do not change any business logic, only the response layer.- Do not modify files outside internal/handler/ and internal/helper/.- Do not introduce new dependencies.- HTTP status codes must follow the standard defined in SKILL.md (2xx success, 4xx client error, 5xx server error). Do not preserve any custom msg or status field from the old implementation.
Boundaries:- Read .agents/skills/response-helper/SKILL.md before making any changes.- Work only on files under internal/handler/.- You may create or modify internal/helper/response.go if the helper does not yet exist.
Iteration:After each handler file is migrated, run the verification commands before moving to the next file. If any check fails, fix it before continuing. Do not batch multiple files into one turn.Corresponding to the official six elements:
- Outcome — Use grep to allow machine verification; it isn’t “it feels done”, it only passes when the result is empty.
- Verification surface — Three checkpoints, all required: static scan (no direct calls), compile (no type breaks), tests (no behavior regressions). These three are executable commands; the exit code is the answer.
- Constraints — Explicitly tell Codex what it must not do to prevent it from “helpfully” changing business logic or adding a new dependency. The most important constraint is to forbid preserving the old msg field, since that’s the core problem this article aims to solve.
- Boundaries — Limit the scope of work to avoid Codex causing side effects in unrelated areas.
- Iteration — Require changing one file at a time, verify after each change; this way, if it fails midway, the damage is contained and you won’t break all handlers at once.