Skip to content

Commit 80998af

Browse files
docs: adopt the fleet agent-context standard (#6) (#7)
Add a root AGENTS.md carrying the handbook#68 behavioural rules, the ownership boundary against core, the CLI and Orchestration, and the build/test commands derived from .github/workflows/ci.yml. Move the release walk into .claude/skills/cut-release so the root file stays inside the length budget. Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 207c50f commit 80998af

2 files changed

Lines changed: 189 additions & 0 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
name: cut-release
3+
description: Publish a runnable-python version — bump the agent manifest and every declaration pinning it, tag, and let GoReleaser build. Use when asked to cut, publish or bump a release, when a change must reach the CLI as an installed agent, or when a downloaded agent's asset name or version looks wrong.
4+
---
5+
6+
# Cutting a runnable-python release
7+
8+
A release is a tag. `.github/workflows/releaser.yml` fires on `v*`, re-runs both
9+
suites, then runs GoReleaser; nothing is built by hand, and the tag is the only
10+
trigger.
11+
12+
## The version lives in more than one file
13+
14+
`agent.codefly.yaml` is the source — `main.go` embeds it, and the agent
15+
advertises that identity. But a declaration names the agent it wants, so every
16+
test declaration pins the same version. Bump them together:
17+
18+
| File | Form |
19+
| --- | --- |
20+
| `agent.codefly.yaml` | `version: X.Y.Z` |
21+
| `qualification_test.go` | `agent:` block of the declaration |
22+
| `pkg/pack/pack_test.go` | same |
23+
| `pkg/generate/generate_test.go` | same |
24+
| `builder_grpc_test.go` | `codefly.dev/python:X.Y.Z` passed to `ParseAgent` |
25+
| `README.md` | the status line naming the agent version |
26+
27+
Bumping the manifest alone is not a silent drift: `Load` rejects it with
28+
`declaration pins a different Runnable agent`, and the gRPC builder test fails.
29+
That is the check — run `go test ./...` after the bump, before tagging.
30+
31+
## Steps
32+
33+
1. Bump every file above, in one commit, on a branch. `go test ./...` and
34+
`cd pkg/harness && uv run --with pytest --python 3.12 pytest -q` both pass.
35+
2. Merge the PR.
36+
3. Tag the merged commit `vX.Y.Z` — the same `X.Y.Z` as the manifest — and push
37+
the tag. GoReleaser derives the asset version from the tag, so a tag that
38+
disagrees with the manifest publishes assets the CLI will not find.
39+
4. Watch the `GoReleaser` workflow. Its `test` job gates `release`; a red suite
40+
means no assets, not a partial release.
41+
42+
## What the asset names have to be
43+
44+
Core's downloader asks for `runnable-{name}_{version}_{os}_{arch}.tar.gz`
45+
holding a binary named `runnable-{name}`. `.goreleaser.yaml` satisfies this
46+
through `project_name: runnable-python` plus the archive `name_template`. Do not
47+
rename the project, the binary or the template to something tidier — that
48+
contract is the reason an installed agent resolves at all.
49+
50+
Builds are `CGO_ENABLED=0` for darwin/linux × amd64/arm64: this agent generates
51+
and packages Python and never inspects source with core's tree-sitter grammars,
52+
so it cross-compiles. If something here starts needing cgo, that is a design
53+
question, not a flag to flip.
54+
55+
## If a released agent misbehaves in the CLI
56+
57+
Check identity before behaviour: the version the CLI resolved, the asset it
58+
downloaded, and the manifest inside it. A mismatch between manifest and tag
59+
looks exactly like a stale agent.

‎AGENTS.md‎

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Working in codefly-dev/runnable-python
2+
3+
`github.com/codefly-dev/runnable-python` (Go 1.27) is the Codefly Runnable agent
4+
for Python. It owns everything Python about a runnable: the handler scaffold and
5+
the typed bindings of a contract, the `codefly.runnable/v1` harness generated
6+
into every package, uv-locked dependency and interpreter preparation, the native
7+
package and the build evidence identifying it, and the Linux image recipe.
8+
9+
It does **not** own the invocation and result framing, `RunnableLocation`,
10+
`RunnableBuild` or `PackageArtifact.command` — those belong to
11+
[`codefly-dev/core`](https://github.com/codefly-dev/core), and `pkg/contract`
12+
here is agent-*private* generated configuration, not wire framing. It does not
13+
own process supervision, the command surface, or image building and publishing
14+
— those are the CLI's; nor installed releases, recorded invocations and recovery
15+
— those are Orchestration's. It advertises Builder only: nothing here supervises
16+
a running invocation. Generated handlers belong to their owner's workspace.
17+
18+
The package-by-package map is in the [README](README.md); read it there rather
19+
than a second copy here.
20+
21+
## How to behave
22+
23+
Fleet standard — [handbook#68](https://github.com/obin-ai/handbook/issues/68).
24+
They land hard here: this agent writes code and environment that run later, on
25+
someone else's machine, from bytes nobody re-reads. A value hand-placed here is
26+
baked into a package and shipped.
27+
28+
- **A gap in the tooling is a bug in the tooling — never a reason to reach
29+
around it.** When a step `codefly`, `uv` or core does not perform is needed,
30+
the answer is a capability fixed in whichever of them owns it, named in the
31+
PR. Never a hand-assembled substitute — not as a "workaround", not "just this
32+
once", not "until the capability lands".
33+
- **Never hack. Provide the best fix, even when it spans repos.** The fix living
34+
in `codefly-dev/core` or `codefly-dev/cli` is not a reason to work around it
35+
here — open the PR there and consume the reviewed result. When it genuinely
36+
cannot be fixed now, the deliverable is a precise issue against that owner
37+
plus an explicitly labelled stopgap, never an unlabelled one.
38+
- **Classify every change that makes something work**, in the PR body: a *fix*
39+
at the place that owns the behaviour, or a *hack*. A hack does not become a
40+
fix by working, by being small, by being local, or by the real fix belonging
41+
elsewhere.
42+
- **Never hardcode what the system resolves** — injected environment, derived
43+
ports, service addresses, credentials copied out of another component. Typing
44+
one encodes something true only on one machine for ten minutes, and it fails
45+
quietly: a runtime missing a credential can skip registration *silently*, so
46+
the thing boots, serves, and is simply absent. Here the same shape appears as
47+
a pinned interpreter path, a guessed `uv` location or an inlined digest —
48+
every one of them is resolved, measured or declared by code that already
49+
exists.
50+
- **Diagnose, do not pattern-match.** "It started working when I set X" is not a
51+
diagnosis — set X back and confirm it breaks. Do not trust an error message
52+
before checking its claim: in the session behind this standard, a runtime
53+
reporting *"the provisioned secret does not match its digest"* actually meant
54+
a missing internal token, and the digest was correct all along.
55+
- **Say what you did not verify.** Unverified is not the same as working. The Go
56+
suite is not the harness suite, and neither is a real invocation on a prepared
57+
package; if you could not exercise something, the PR says so.
58+
59+
## Build and test
60+
61+
Derived from [`.github/workflows/ci.yml`](.github/workflows/ci.yml), which runs
62+
two jobs. Both are runnable locally, and both need `uv` on `PATH`: `prepare`
63+
shells out to it and the agent advertises the local backend by probing for it.
64+
65+
```bash
66+
go build ./...
67+
go vet ./...
68+
go test ./... # ~20s: the suite really locks, downloads
69+
# an interpreter, packages and invokes
70+
cd pkg/harness && uv run --with pytest --python 3.12 pytest -q
71+
```
72+
73+
No test skips itself when a prerequisite is missing — without `uv` the Go suite
74+
fails rather than passing hollow. Keep it that way: a prepared tree that was
75+
never prepared is not evidence.
76+
77+
`builder_grpc_test.go` is the end-to-end path. It compiles the binary, installs
78+
it where `manager.Load` looks, and drives `Load`, `Create`,
79+
`RunnableBuildInputs` and `Package` over real gRPC — exercise the lifecycle
80+
through it rather than hand-running the binary, which is a plugin server and not
81+
a CLI.
82+
83+
`docs/protocol.md` is the live contract. `docs/qualification.md` and the
84+
milestone documents are dated records of older commits, explicitly superseded;
85+
do not run commands out of them or trust their digests.
86+
87+
## Rules that bite
88+
89+
- **`.codefly/` is the agent's, the handler is the author's.** `Scaffold` never
90+
overwrites a handler — regenerating must not silently discard an
91+
implementation. Nothing an author edits may move under the generated
92+
directory.
93+
- **The bounded schema profile is stated twice**, and the two must agree:
94+
`pkg/generate/types.go` emits the TypedDicts an author's type checker sees,
95+
and `codefly_runnable/schema.py` enforces the same profile at invocation time.
96+
A change to one is a change to both, with tests in both suites.
97+
- **Package bytes are the identity.** The harness digest is sha256 over the
98+
harness `.py` sources; the generated contract is encoded with stable key
99+
order; the archive is deterministic across machines. Anything that makes the
100+
same declaration produce different bytes — a timestamp, a map iteration, a
101+
newly-embedded non-`.py` file — breaks the promise a launcher relies on.
102+
- **Measure, never assume, what identifies a build.** The toolchain is read out
103+
of the interpreter that was prepared, not from the requested version; the
104+
harness digest is measured from the tree that was generated, not from the
105+
agent version. Keep both readings at the bytes.
106+
- **Builder state is per-process.** The lock serializes calls but cannot tell
107+
two callers apart, and `Load` replaces what it finds, so one connection drives
108+
one Runnable at a time. Concurrent builds get one agent process each — do not
109+
add a session map to paper over this.
110+
- **The agent version is pinned in more than one place.** `agent.codefly.yaml`
111+
is the source, and every test declaration pins the same agent; bumping one
112+
alone fails `Load` with `declaration pins a different Runnable agent`. See the
113+
release skill before touching it.
114+
115+
## Procedures
116+
117+
Step-by-step procedures live in `.claude/skills/`, loaded on demand rather than
118+
carried here:
119+
120+
- `cut-release` — publishing a version, what the tag triggers, and the asset
121+
names core's downloader requires.
122+
123+
## Workflow
124+
125+
- Branch and PR; never commit to `main`. Conventional Commits for the title.
126+
- Keep this file under ~150 lines (hard cap 200). Push depth into a nested
127+
`AGENTS.md` beside what it describes, into `.claude/skills/`, or into `docs/`.
128+
- If a `CLAUDE.md` is ever added, it is a one-line `@AGENTS.md` pointer. One
129+
canonical source.
130+
- Treat this file as code: the PR that changes a process updates it.

0 commit comments

Comments
 (0)