v0.0.0 - The scaffold
Released earlier in development (pre-release).
When you type python at a terminal, an extraordinary amount of
work is hidden behind the prompt. The interpreter parses your
source, builds an AST, lowers that AST through a symbol table
into bytecode, allocates a frame, sets up an evaluation stack,
and then walks an instruction loop that has been incrementally
refined for thirty years. Around that loop sits a garbage
collector, a thread state, an import machinery, and a standard
library that runs to several million lines. Reading the CPython
source for the first time is humbling. Porting it is a
multi-year project.
v0.0.0 is where we start that project. There is no parser yet. There is no compiler. There is no VM. What ships here is a Go module, a CLI entry point that prints a version banner, a build metadata package, and the surrounding infrastructure (CI, license, contributing guide, issue templates) that lets the rest of the work happen in the open. Every later release fills in the inside of the box this release puts on the table.
This page is a little shorter than the changelog entries that follow it, because there is genuinely less code in v0.0.0 than in any later cut. But the choices we made here (which Go version, which CPython version, what to name the binary, how to structure the module path) propagate through everything that comes next. So we want to walk through them before they vanish into the background.
Highlights
Two pieces of work define this release.
A Go module that compiles
The module is github.com/tamnd/gopy. It targets Go 1.26 or
newer. The CLI entry point lives at cmd/gopy. You can build it
and ask it to identify itself.
$ go install github.com/tamnd/gopy/cmd/gopy@v0.0.0
$ gopy --version
gopy 0.0.0 (compatible with CPython 3.14.0+) [darwin/arm64]
$ gopy --copyright
Copyright (c) 2026 Tam Nguyen. All rights reserved.
[...full Apache 2.0 notice and CPython attribution...]
That is the entirety of what the binary does at this stage. Pass
anything else and it prints usage and exits non-zero. There is
no REPL, no -c flag, no script execution. We are very
deliberately not pretending the binary is a Python yet.
The reason the banner exists at all is that it lets us put CI in place and prove the toolchain works end to end before any interesting code lands. The first hundred contributors to a project spend a startling amount of time fighting build infrastructure; we wanted that fight resolved before there was a single line of port to argue about.
A version package that mirrors CPython's
CPython has four small C files that together identify a build:
Python/getversion.c, Python/getplatform.c,
Python/getcompiler.c, Python/getcopyright.c. Each exposes a
single function. Together they produce the banner Python prints
when it starts up.
We ported those four files to one Go package, build/. The API
mirrors the upstream surface:
| Symbol | CPython counterpart |
|---|---|
build.Version | PY_VERSION constant |
build.PythonCompatVersion | PY_VERSION we target |
build.Platform | Py_GetPlatform() |
build.Compiler | Py_GetCompiler() |
build.VersionString | Py_GetVersion() |
build.Copyright | Py_GetCopyright() |
build.Platform returns the host OS / arch the way Go's
runtime reports it (darwin/arm64, linux/amd64,
windows/amd64). build.Compiler reports the Go toolchain that
built the binary, not the C compiler CPython would report,
because the host language is Go.
// build/build.go
package build
const (
Version = "0.0.0"
PythonCompatVersion = "3.14.0"
)
func Platform() string { /* runtime.GOOS/GOARCH */ }
func Compiler() string { /* runtime.Version() */ }
func VersionString() string { /* gopy X.Y.Z (compatible with ...) [plat] */ }
func Copyright() string { /* Apache 2.0 + CPython attribution */ }
This package is the first one any future subsystem can import without circular dependency risk, so it is also where the project's import graph is anchored.
What's new
The full feature breakdown, grouped by area.
Module and entry point
- Go module path
github.com/tamnd/gopy. We picked the shortgopyname early. It is unambiguous (the only other project with the same name was archived years ago) and types cleanly at a terminal. The CLI binary is alsogopy. The symmetry matters: every future shell example in the docs starts with the same five letters. go.modtargeting Go 1.26+. Go 1.26 ships with generic type aliases and the iterator improvements that CPython's port leans on heavily (every Python iterator becomes a Go iterator, and the type aliases let us shareObjectshapes across packages without verbose conversions). Older toolchains would force workarounds that we do not want to maintain.cmd/gopywith--versionand--copyright. The two flags are intentionally the only ones at this stage. Adding--helpand-cand-mand the rest happens in v0.6 when the interpreter can actually do something with them.
Build metadata package
build/build.go. Constants forVersionandPythonCompatVersion. The version constant is bumped on every release;PythonCompatVersionmoves only when we move the CPython target.build/platform.go. Platform string assembled fromruntime.GOOSandruntime.GOARCH. Matches the convention Go'sruntime/debug.BuildInfouses, which makes downstream tooling (like crash reporters) trivially compatible.build/compiler.go. Reports the Go toolchain version throughruntime.Version(). We deliberately do not pretend to report a C compiler here, because the binary is a pure Go binary.build/version.go. Assembles the banner CPython prints. Format:gopy X.Y.Z (compatible with CPython A.B.C+) [plat].build/copyright.go. The Apache 2.0 notice plus a paragraph attributing the design and reference behavior to the CPython project under the PSF License.
License
- Apache License 2.0. We chose Apache over MIT because the explicit patent grant matters for a runtime that may end up embedded in commercial products. We chose Apache over LGPL because we do not want to impose link-time obligations on users; this is a tool, not a library that recipients must preserve as a separable component.
Project policy documents
README.md. Project overview, install instructions, current status, link to the roadmap.CONTRIBUTING.md. How to file an issue, how to write a PR description, the porting style we follow (1:1 from CPython with citations), the commit-message convention.SECURITY.md. How to report a vulnerability privately. Because the long-term goal is to be a drop-in for CPython, any security advisory in CPython has to be evaluated against our port. The policy document points to the issue tracker and to a private email channel for embargoed reports.CODE_OF_CONDUCT.md. The standard Contributor Covenant text adapted with project-specific contacts.
Continuous integration
- CI workflow. Runs on Linux, macOS, and Windows under Go
1.26. Each platform runs
go build ./...,go vet ./...,golangci-lint run, andgo test ./.... Fast and quiet at this stage because there is nothing to test yet beyond the CLI flag parsing. The point is that the pipeline is green before any interesting code lands, so the first regression we see in any later cut is unambiguously a real regression. - Release workflow. Triggered by
v*tags. Uses GoReleaser to cross-compile thegopybinary for the platform matrix, produces SHA256 checksums, and attaches GitHub attestations (build provenance) per the SLSA framework. Reproducible builds are not yet a goal but the workflow is structured so we can opt in without rewriting it. - Dependabot. Configured for both
gomodandgithub-actionsecosystems. Weekly cadence. Keeping CI dependencies fresh is annoying to do manually and easy to automate.
Repository hygiene
.golangci.yml. Enables the strict-ish set:govet,staticcheck,errcheck,gosimple,ineffassign,unused,gofmt,goimports,revive. Plus a small project-local rule set: nointerface{}(useany), nopanicoutsideinit(raise a Python exception instead), notime.Sleepin tests (use synchronization)..editorconfig. Tabs for Go, spaces for everything else, LF line endings, trim trailing whitespace..gitattributes. Marks Go files and text files explicitly, marks binary fixtures as binary, sets the merge driver forgo.sumtounion..gitignore. Standard Go ignores plus the build cache and the editor swap files we expect to see.- Issue and PR templates. Bug report, feature request, port request (for "please port this CPython subsystem next"), and a PR template with the citation-and-test boxes the project requires.
Why we built it this way
A few of the choices in this release deserve their own callouts, because they affect everything downstream.
Why port CPython 3.14 rather than 3.12 or 3.13
The honest answer is that 3.14 is the first CPython release where the things we want to port are stable. Specifically:
- The Tier 1 / Tier 2 interpreter split. 3.13 introduced the Tier 2 micro-op interpreter as an experiment. 3.14 is where the boundary firmed up enough that a port can target it without chasing every PR. Porting against 3.12 would mean porting against a single-tier loop and then re-porting on top of it; porting against 3.13 would mean chasing the micro-op set every month. 3.14 is the cut where we can plant a flag.
- The free-threading work (PEP 703). 3.13 introduced the
--disable-gilbuild as an experimental flag. 3.14 ships it as a supported configuration. The lock primitives, parking lot, and critical-section machinery we port in v0.1.0 are the 3.14 versions of those subsystems. Doing this against 3.12 would mean back-porting the design. - The JIT prep work. 3.13 and 3.14 reshape the bytecode-to-IR pipeline in preparation for a real JIT. We do not promise a JIT in gopy, but the bytecode we emit is the 3.14 bytecode, which means a future JIT effort starts from a sensible baseline rather than from an old one we would have to rewrite.
The cost of picking the newest stable release is that we sometimes hit bugs in upstream that have been fixed in master. We mirror those fixes back manually. It is less work than chasing two-version-behind compatibility would have been.
Why Go rather than Rust or C++
We considered all three. The choice came down to ergonomics and runtime fit.
Go's garbage collector matches Python's semantic model.
Python objects can have cycles, can be reachable through dicts
and lists, and need a tracing collector. Go gives us that
collector for free. CPython itself uses reference counting
plus a cycle collector; we can lean on Go's tracing GC to
cover both jobs, modulo the cases where Python's semantics
require deterministic destruction (file handles, locks). Those
cases get explicit __del__ plumbing.
Go's goroutines match Python's threading model under free-threading. Each Python thread becomes a goroutine. The Go scheduler handles OS-thread mapping. We do not have to write the kind of scheduler glue CPython needs.
Go's standard library covers a lot of what Python's stdlib
needs. encoding/json, crypto/*, net, os/exec,
time, regexp (which we eventually replace; see v0.12.2)
all give us a starting point. Rust would have given us many of
the same building blocks at a lower level but more glue.
Go compiles fast and produces a single static binary. This matters more than it sounds. Iteration speed on a project this size is the rate-limiting factor for everything else. A Rust build that takes two minutes per change adds a day per week of waiting; a Go build that takes ten seconds gives that day back.
The downside of Go is that some CPython idioms (macros, struct layouts shared across compilation units, the inline cache machinery) do not translate directly. We handle each case as it comes up. The general rule: faithful port first, then profile, then optimize where it matters.
Why scaffold first, code second
A common pattern in new projects is to dump a thousand lines of code into the first commit and figure out the surrounding infrastructure later. We did the opposite. v0.0.0 has no interesting code at all. What it has is the infrastructure that lets later releases land without procedural friction: CI is green, the license is in place, the contributing guide is written, the issue templates exist.
The cost of doing this in v0.0.0 is one release that does nothing. The benefit is that every subsequent release lands against a working pipeline. Every code review references the contributing guide. Every CVE has a security policy to point at. Every PR has a template to follow. None of these are emergencies; all of them are friction we paid once.
Where it lives
The repository layout at v0.0.0 is intentionally small:
cmd/gopy/. The CLI entry point.main.goonly.build/. The metadata package. Four files mirroring the four CPython sources..github/workflows/. The CI and release pipelines..github/ISSUE_TEMPLATE/,.github/pull_request_template.md. The templates.- Top-level dotfiles.
.golangci.yml,.editorconfig,.gitattributes,.gitignore,.goreleaser.yaml. - Top-level docs.
README.md,CONTRIBUTING.md,SECURITY.md,CODE_OF_CONDUCT.md,LICENSE.
The whole tree is under two thousand lines. The interesting code starts in v0.1.0.
Compatibility
Nothing user-visible runs at this stage, so there is nothing to break. But two policy choices set expectations for later releases.
- Go 1.26 or newer. We will move the floor forward as Go releases land, but only when we actually use a feature from the newer version. No drive-by floor bumps.
- CPython 3.14.0+ behavior. Every observable behavior we ship should match CPython 3.14 unless the changelog calls out a divergence and explains why. If you write Python that runs against CPython 3.14 and it does not run against gopy, that is a bug.
What's next
v0.1.0 is the first release with executable code. It ports the compiler-side memory arena, the threading primitives, the synchronization machinery, and the per-process hash secret. The interpreter still does not run Python code, but it has the underpinnings every later subsystem rests on.
Roughly:
- v0.1. Foundation. Arena, threads, locks, hash secret.
- v0.2. Object model. The core types up through dict.
- v0.3. Exceptions.
- v0.4. Strings, bytes, sets, the real hash machinery.
- v0.5. Parser and AST.
- v0.6. Compiler and VM. First Python actually runs.
- v0.7. User-defined classes and the descriptor protocol.
- v0.8. Generators, coroutines, async.
- v0.9. Frame and code refinements, full VM coverage.
- v0.10. Garbage collector and weak references.
- v0.11. Import machinery and the first stdlib modules.
- v0.12. Tier 2 interpreter, vendored stdlib, real regex.
- v0.13. JIT scaffolding.
- v0.14. Free-threading.
That roadmap is aspirational and shifts as we learn. The order holds; the individual cut dates do not.
Acknowledgments
The CPython project under the Python Software Foundation. Every
file in build/ is a port from a file under CPython's
Python/ directory, and the rest of the project is built the
same way. The license attribution in build/copyright.go makes
this explicit. We owe the CPython authors every architectural
idea this project uses.