|
| 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