From eed1239496c8d318643200eaa8ce7d51d83b2624 Mon Sep 17 00:00:00 2001 From: makseq Date: Mon, 10 Aug 2026 17:03:52 +0300 Subject: [PATCH] The ordered path from a task description to a connected node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This repository already has everything an agent needs to build somebody's node — the contract, the recipe, a correct reference, a black-box harness and the operational steps — and no ordered path through them. The "Create external node" button in the orchestrator generates a prompt telling the user's own coding agent to clone this repository and finish at an image digest, and an agent arriving that way had to infer the sequence from five documents that each assume you already know why you are reading them. So: one section in CLAUDE.md, which Claude Code reads on its own, with a one-line pointer from README.md. Six steps, each pointing at the document that owns the detail. Nothing is restated — a rule written down twice goes stale in one of the two places, and the stale copy is the one somebody is reading. The step that carries the weight is the third, verification, because after the node has been edited the harness is judging the author's container rather than ours. It states the bar honestly, which means saying three things the section would be easier without: that --red-for-real can only differ from the plain run while some test carries expected_red_until_fixed, and none does; that only the conforms_today group has node.py as its subject, and most of it rests on reference-quality or our-policy bases the contract permits a node to fail; and that several of those tests assert this node COPIES its inputs to its outputs, so a node that transforms them goes red honestly. A red test there is a question, not an instruction. CONFORMANCE-BASELINE.md is named for what it is — a measurement of the reference node, not a prediction about anybody else's. One stale statement is corrected in passing. OPERATIONS.md told the reader that a denied pull of the agent image meant the package had not been made public yet, and to ask an admin to publish it. The package IS public now — an anonymous token against ghcr.io returns the manifest with a 200 — so no docker login is needed and the paragraph said the opposite of the truth. A second stale statement is NOT fixed here, because the token pushing this branch has no workflow scope: the conformance workflow's header still explains why CI is green by saying twenty tests are known to fail, which was true before node.py was repaired and is false now. It needs the same treatment, from a push that is allowed to touch .github/workflows/. Verified: the full harness passes on this tree (128 passed, 1 skipped in 138s — the skip is the verbatim-citation check, which needs a checkout of the orchestrator); collection with --print-labels exits 0, so every test still declares a group and a basis; and every relative link and heading anchor added here resolves. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++ README.md | 8 +++++ docs/OPERATIONS.md | 7 ++-- 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 59024ca..ad95f47 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,88 @@ below exists to serve both without lying to either. | `conformance/` | A black-box harness: it builds this image, runs it, judges it from the outside only | | `tests/` | The tests that harness runs | | `CONFORMANCE-BASELINE.md` | The measured record of what the harness found | +| `CONTRIBUTING.md` | The two-minute version of making this repository your own node | + +## Building your own node from this template + +**Background**, about this repository and the order to work in. You have somebody's task +description and a clone of this repository, and you have to finish at a node the +orchestrator can run. Every step below points at the document that owns the detail rather +than repeating it, because a rule written down twice goes stale in one of the two places +and the stale copy is the one somebody is reading. + +1. **Read before you write.** + [docs/README.md](docs/README.md#how-to-read-this-three-kinds-of-statement) first — it + says how to read the three labels, and it routes everything else — then + [docs/AUTHORING.md](docs/AUTHORING.md#the-recipe) end to end: the recipe, the + [skeleton](docs/AUTHORING.md#the-skeleton), and the + [checklist](docs/AUTHORING.md#the-checklist) you come back to before you ship. Where the + shorter documents leave you guessing, [docs/PROTOCOL.md](docs/PROTOCOL.md) is the + authority; the [shipped-capability matrix](docs/README.md#shipped-capability-matrix) is + what keeps you from building against something the contract models and the orchestrator + does not send. + +2. **Implement the task by editing `node.py`.** Copy and edit it; do not rewrite it from + memory. Every one of the [invariants](#invariants-for-nodepy) below is something a + from-scratch version leaves out and a real run then punishes, and + [docs/AUTHORING.md](docs/AUTHORING.md#nodepy-and-this-skeleton) says which of its + choices are choices rather than rules. Another language is fine — nothing here imports + anything from the orchestrator — but then `node.py` is your behavioural reference rather + than your starting point, and the `Dockerfile`, which copies `node.py` and nothing else, + becomes yours to change too. + +3. **Verify with the harness. This is the step that makes the difference**, because after + step 2 it is judging **your** container: it builds the image from this repository's own + `Dockerfile` and never patches it. The commands are in + [Running the harness](#running-the-harness) — run them from the repository root, as + written. The bar, honestly: + + * `python -m pytest` must be green. Exactly one skip is expected and is not about your + node: the verbatim-citation check, which needs a checkout of the orchestrator. + * `--red-for-real` differs from the plain run only when some test carries + `expected_red_until_fixed`, and none does today — see + [the expected-red mechanism](#the-expected-red-mechanism). Run both anyway: the day + they disagree, a known defect is being absorbed and you want to know whose. + * **A red test is a question, not an instruction.** Only tests in the `conforms_today` + group have `node.py` as their subject today, so nothing you write moves a + `subject_is_platform` or `harness_self_test` result; and most of that group rests on + `basis_reference_quality` or `basis_our_policy`, which by + [the definitions below](#the-three-labels-and-why-they-are-load-bearing) are things + the contract permits a node to do differently. Several assert that this node + **copies** its inputs to its outputs — a node that transforms them fails those + honestly, as `tests/test_inputs.py` says in its own opening docstring. Get the split + for your run from `--print-labels`, read the check, and change your node only if what + you broke is the contract + ([docs/CONFORMANCE.md](docs/CONFORMANCE.md#if-a-conformance-directory-exists-in-this-repository)). + * [CONFORMANCE-BASELINE.md](CONFORMANCE-BASELINE.md) measures the **reference** node, + before and after its repair. It is the most useful thing here to read before writing + your own, and it describes nothing about yours: its counts predict nothing about your + run, and it is not a document to rewrite in your node's image. + +4. **Build the image and take its digest.** `docker build` first, then whichever of the two + commands in [docs/OPERATIONS.md](docs/OPERATIONS.md#the-image-reference) matches how the + image will travel — the repo digest of a pushed image, or the bare id of one built + locally, which that section explains only resolves on a machine that already holds it. + Use them as they are written rather than a variant of your own. + +5. **Write the README the human gets.** What the node does; the input and output + assumptions you made, port names first, since + [docs/AUTHORING.md](docs/AUTHORING.md#1-decide-what-your-node-consumes-and-produces) + explains what those names decide downstream; the two commands from step 4; and the + connect steps from step 6. [CONTRIBUTING.md](CONTRIBUTING.md) is a fair model for length. + +6. **Hand back the digest, and where to paste it.** Point the human at the registration + steps instead of summarising them, and hand over the start line the registration reply + prints instead of one you wrote yourself. **BEHAVIOUR.** A node is registered from + Settings, the **External Nodes** tab, **Connect node**, where that digest is pasted; the + reply creates the pool, the deployment and the revision, and prints the agent's + `docker run` line already filled in with the orchestrator's address, the pool name and — + only when that registration minted one — the pool token + ([docs/OPERATIONS.md](docs/OPERATIONS.md#registering-a-node), and + [starting the agent](docs/OPERATIONS.md#starting-the-agent) for the general form). That + line is run on the machine that will run your container. Registering is not running: + nothing executes until a pipeline node points at the deployment + ([docs/OPERATIONS.md](docs/OPERATIONS.md#pointing-a-pipeline-node-at-your-deployment)). ## The three labels, and why they are load-bearing diff --git a/README.md b/README.md index aad0997..72dfb59 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,13 @@ the contract happily permits a real node to skip. So: **start with [docs/](docs/), then copy `node.py` and edit it.** +If you would rather follow a numbered list — and if you are a coding agent working from +somebody's task description, this is the one to follow — the ordered path from that +description to a node the orchestrator can run is +[Building your own node from this template](CLAUDE.md#building-your-own-node-from-this-template) +in `CLAUDE.md`: read the documents, edit `node.py`, prove it with the harness, take the +digest, hand it over. + ## What is here | Path | What it is | @@ -53,6 +60,7 @@ So: **start with [docs/](docs/), then copy `node.py` and edit it.** | `tests/` | The tests that harness runs, and `pytest.ini` names the label on each one | | `CONFORMANCE-BASELINE.md` | What the harness measured against `node.py`, before and after the repair | | `CONTRIBUTING.md` | Notes on making this repository your own | +| `CLAUDE.md` | The ordered path from a task description to a connected node, and the invariants `node.py` exists to demonstrate | ## Build and run diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index 0636715..f890fd3 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -139,11 +139,10 @@ that talks to the orchestrator: it enrols once, polls for work, starts your cont streams its logs back and reports the result. It listens on no port; every connection is outbound. -**BEHAVIOUR.** The agent image is published at **`ghcr.io/humansignal/lspo-agent:latest`**. +**BEHAVIOUR.** The agent image is published at **`ghcr.io/humansignal/lspo-agent:latest`**, +and the package is **public**: an anonymous pull works and no `docker login` is needed. `docker run` pulls it for you; `docker pull ghcr.io/humansignal/lspo-agent:latest` fetches -it on its own if you would rather do that first. **If the pull is denied there is nothing -to retry and nothing wrong with the machine**: the package's visibility has not been made -public yet. Ask the workspace admin to make it public, or to grant that account access. +it on its own if you would rather do that first. Both the Connect reply and the setup command print the start line already filled in — prefer either of those, because they carry the real pool name and, when that registration