Any service that runs the lifecycle answers the same API.
A person may build agents on more than one service, and should be able to follow every build in one place. So a service that implements the lifecycle reports each build stage by stage, in one shape, and says what the person may do next. A client shows what the service says and holds no lifecycle logic of its own.
The schema: lifecycle-api.schema.json · The check: lifecycle-api-check.mjs · Version 0.1
Every service answers these paths.
They sit under /v1/lifecycle on the service. Every request and answer is JSON, and every time is ISO 8601 with a zone.
| Call | What it answers |
|---|---|
GET /me | who the key belongs to, { account: { email, name } }, and the service's name, { service: { name } } |
GET /builds | the person's builds, each with its id, name, what it does, its version, what it makes (kind: agent or process), the stage it is at and its state there, where it ended in words, and when it last changed; archived builds only when the request adds ?archived=1 |
POST /builds | a new build, started from the person's words: does, and kind when it is a process, and if they like refuses, more, name and a prototype repository |
GET /builds/{id} | one build, stage by stage, with its log and the actions it offers now |
GET /builds/{id}/timeline | how long each step took: one bar per step per round, with who held it, whether an agent was working or it waited on a person or on something outside, its start, end and duration, and the totals |
GET /builds/{id}/files?path= | the bytes of a file a stage handed back, at the link the build gives |
POST /builds/{id}/actions/{name} | the build after the person takes one of the actions it offered |
POST /builds/{id}/try | the answer to one input, sent to the pull request or to the installed agent |
GET /builds/{id}/tries | the tries made on the build, newest first |
POST /builds/{id}/tries/{tid}/keep | the try, now kept as a new test in the agent's suite |
POST /builds/{id}/tries/{tid}/wrong | the try, now sent back to whoever holds the code |
The person's own key signs every call.
Each call carries Authorization: Bearer <key>. The key is one the person makes for their own account, and the service says how to make one and what it must allow. A client asks for the key once and never sees a password. It sends the key only to the service, and asks for a new one when the service answers 401 or 403.
A build lists all twenty-four stages.
The answer to GET /builds/{id} lists every stage of the lifecycle once, by the ids in stages.json. A stage the service does not run is listed as not_offered, so a client always draws the whole lifecycle and the person can see what this service leaves to someone else.
{
"stage": "code",
"state": "waiting_on_you",
"holder": {
"kind": "your_agent",
"name": "your coding agent, on you/word-tally"
},
"runs": 2,
"rounds": [
{ "n": 2, "at": "2026-10-31T09:15:00Z",
"label": "Push 2", "verdict": "passed",
"back": false, "words": "Every test passed." },
{ "n": 1, "at": "2026-10-31T08:52:00Z",
"label": "Push 1", "verdict": "failed",
"back": true, "words": "Two tests failed." }
],
"given": "the build folder, with the suite and AGENTS.md",
"handed_back": {
"words": ["the scripts"],
"files": [
{ "path": "scripts/count.mjs", "size": 3500,
"link": "/v1/lifecycle/builds/b1/files?path=scripts%2Fcount.mjs" }
]
},
"done_when_met": null,
"log": [
{ "at": "2026-10-31T09:15:00Z", "said": "The gate passed on 772a7e4." }
],
"where": {
"kind": "pull_request",
"url": "https://github.com/you/word-tally/pull/1",
"head": "772a7e4",
"check": {
"title": "The gate passed", "words": "Every test passed.",
"items": ["fence", "suite"], "passed": true
}
}
}
| Field | What it says |
|---|---|
state | not_started, working, waiting_on_you (the person's act is next), done, refused, stopped or not_offered |
holder | who holds the stage: a reference_agent, your_agent (one the person brought) or a person, with a name; null for a stage that is not offered |
rounds | each time the stage ran, newest first; back is true when that round sent the work back |
handed_back | what the stage handed back, in words and as files, each with its size in bytes and a link read with the same key |
done_when_met | whether the stage's "done when" was met: true, false, or null while it is not known |
where | where the work is when the person can open it, such as a pull request with its newest commit and its check; otherwise null |
Beside the stages, a build carries its id, name, what it does, its version, the handle it answers at once installed, its own log, its actions, and can_try, which says whether a try may be sent now to the pull request and to the installed agent. It also carries kind, agent or process, absent meaning agent, and a service that does not build the kind asked for refuses the request and says so; summary, where it ended or where it is in the words a person reads first, such as "Installed · conforms" or "Stopped at Requirements"; paused, since when and why the installed agent is paused, for example after a conformance check it failed, or null; archived, true once the person archived it; and timeline, the same bars the timeline call answers.
A client draws one button for each action, and nothing else.
Every build carries the actions the person may take now. A client shows each one as a button with its label, asks for the inputs it needs, and posts them to /builds/{id}/actions/{name}. It never adds a button the service did not offer and never decides on its own what comes next, so every judgment stays with the service and its agents. When the service offers nothing, the person has nothing to do.
{
"stage": "requirements",
"name": "approve",
"label": "Approve the spec",
"needs": [
{ "name": "repository",
"label": "Your coding agent's repository, as owner/name",
"kind": "repository",
"optional": true }
]
}
A need's kind is text for a line, words for a paragraph, or repository for owner/name. A need marked optional may be left empty, and is then not sent. An action names the stage it acts at, and that is always a stage the build offers. The answer is the build as it now stands.
Try it, keep what it got right, and send back what it got wrong.
Before the person accepts the code, and after the agent is installed, they can send it one input and read the answer.
- Try.
POST /builds/{id}/trywith{ say, against }, whereagainstispull_requestorinstalled. The answer is{ try: { id, say, against, answer, ms, at } }. Against the pull request, the service runs the newest commit on that one input, through the same checks its gate uses. Against the installed agent, it sends the agent a message as the person and waits for the reply. - Keep.
POST /builds/{id}/tries/{tid}/keepadds the try to the agent's suite as a new test file, so the answer it gave becomes one it must keep giving. - Wrong.
POST /builds/{id}/tries/{tid}/wrongwith an optional{ why }sends the try back to whoever holds the code, as a comment on the pull request.
GET /builds/{id}/tries lists every try, newest first, each saying whether it was kept and whether it was sent_back. A service may cap how many tries a build has in a day.
An error is words a person can read.
Any refusal is a 4xx or 5xx status with { error } in words, which a client shows as they are. A refused action also carries the build as it now stands. A 401 or 403 means the key is missing, wrong, or does not allow the call. A 429 means the service was asked too often, and a client waits before asking again.
Version 0.1 grows only by adding fields.
This is version 0.1, served on the /v1/lifecycle path. It changes only by adding fields. A service never removes a field or changes what one means, and a client ignores any field it does not know. That way an older client keeps working against a newer service, and a service can say more than this page does without breaking anyone.
The schema and a check are open, beside the stages.
The schema, lifecycle-api.schema.json, holds every request and answer above as JSON Schema. It sits in CommonAgent/adlc beside stages.json, under Apache-2.0.
The check, lifecycle-api-check.mjs, reads a running service with a key and says whether its answers keep the contract. It only reads. It checks every answer against the schema, and the rules a schema cannot say: every stage listed once, actions only at stages the build offers, and rounds newest first.
$ node standards/lifecycle-api-check.mjs https://your-service.example/v1/lifecycle/ --key-file ./key
It needs Node 22 and nothing else. It takes the key from a file or from ADLC_API_KEY, never from the command line, and never prints it. Exit 0 means the service keeps the contract.
AgentMesh is the first implementation.
AgentMesh answers this API for the agents a person builds there. A person makes a key for their own account on its Keys page and gives it to the client. Any other service that runs the lifecycle can answer the same paths, and the same client will follow its builds too. See Implementations for what holds each stage today.