Repository navigation
Conversation
…ng it is visible
`60d471bc` added `UpcallBootstrap.publishToGlobals()` and converted `ksp-fixtures/artifact`'s four
tests to it. The other 21 -- 19 in `compose`, 2 in `app` -- kept hand-assembling
`ctypes.CFUNCTYPE`s over `UpcallStub`'s raw addresses, which meant they passed whether or not the
public entry point worked. The item was reported closed on the strength of the four.
Each `@BeforeTest` block collapses to `check(UpcallBootstrap.publishToGlobals()) { ... }`.
`UpcallStub` and `ctypes` no longer appear anywhere under `ksp-fixtures/{app,compose}`.
The check that this is worth anything: emptying `publishToGlobals()` to `return true` now fails
all 99 compose tests with "the raw upcall entry points are not bound". Before the sweep the same
tamper changed nothing.
compose 99/0, app 70/0, artifact 36/0 -- unchanged, as a call-site swap should leave them.
`09388516` committed `UpcallBootstrap.desktop.kt` reduced to `actual fun publishToGlobals():
Boolean { return true }` -- the state an agent left it in after proving the sweep was load-bearing,
and never restored. Every consumer of the public bootstrap therefore bound nothing, which is what
`ksp-fixtures:artifact` was reporting as "the raw upcall entry points are not bound" for its 24
failures.
Two things let it through. The agent reported restoring the file and reported a clean
`git diff --stat` for it; neither was true. And the commit was staged with `git add -A` after
`git status --short` printed 22 lines against a reported 21 changed files -- the extra line was
this, and the discrepancy was not questioned.
The verification missed it because the three fixture modules were run in one Gradle invocation:
`compose` and `app` passed, `artifact` passed behind them, and running `artifact` alone fails.
Each fixture module is now run as its own invocation.
…be injected
The rule: nothing in this repository may translate `androidx` into `pythonx`. It owns the language
boundary, and a boundary that knows a UI library by name is not one.
`PythonxAdapter` carried four such facts. `register_package('pythonx.compose', 'androidx.compose')`
and its `pythonx.kotlin` sibling are gone; `_VALUE_CLASS_ALLOWLIST`'s seeded `Dp` is gone; and the
`dp(value)` function -- the only one of the four with no general seam beside it, so that another
library's value class needed a core edit -- is now `value_of(kotlin_type_name, raw)`. The seams
that were already there (`register_package`, `allow_raw_primitive`) are the whole mechanism, and
`install(modules, rawPrimitiveValueClasses)` is how an embedder fills them. Nothing is registered
by default: with no map installed `pythonx.*` resolves nothing, which is the honest answer rather
than a guess.
`kotlin_package_for` had to change with it. A prefix substitution cannot express a rename in the
middle of a name, and the map is full of exactly that -- `pythonx.compose.layout` for
`androidx.compose.foundation.layout`, dropping `foundation.` -- which is why the runtime and the
generated `.pyi` stubs disagreed about module names. An exact match wins; failing that, the longest
mapped ancestor with the tail appended, because `Arrangement` is a submodule under a mapped package
rather than a mapped name of its own and no map would list every nested object. `_Finder` also
recognises intermediate steps (`pythonx.compose.foundation` is nobody's Kotlin package but leads to
one) as containers carrying nothing of their own.
`PythonxTableSourceTest` asserted the *opposite* of this rule -- that the rendered source contains
`register_package('pythonx.compose', 'androidx.compose')` and names `Dp`. It was pinning the binder
knowing a library. Inverted: the seams must exist, and no library name may.
Callers supply the map instead, and each set of fixtures now carries the file a consumer's
`pythonx-map.toml` stands in for. What remains of `androidx` in `commonMain` is one line of javap
output quoted in a KDoc.
Run one module per Gradle invocation: desktop 529/0 skipped=1, wasmJsNode 471 + browser 10,
plugin 175/0, app 70/0, compose 99/0, artifact 36/0, androidNative compiles clean.
…cks rather than regrets Each step was locally defensible and the aggregate was not what was asked for. Seven ways that happened, each written as something to check rather than something to feel bad about: - The specification was `UI.ipynb`, and it was classified as a user file not to touch -- agents were told the same. ROADMAP and `docs/` were treated as requirements when they are output. - "pythonx must be a real package that restructures androidx" was answered with an adapter that synthesises `pythonx.*` and puts `__path__ = []` on every module it makes, which is the one thing that stops a real package's files from ever loading. Then that package's code was deleted as redundant. - Test counts stood in for progress. `pythonx-compose`'s 71 tests all assert an *absence*; no capability was added. Implementations, defect fixes, doc corrections and deletions were reported in one column. - An audit prompt written here fixed its own answer: "the identifier must appear in the other repo" cannot detect a reimplementation in another language, so it returned "no transfer" for a transfer, and that was relayed as fact. - Three verifications could not fail: three fixture modules in one Gradle invocation hid an ordering dependency; `git add -A` swept a tampered production file in against a `git status` line count that did not match the report; and 21 fixtures used raw addresses, so gutting the public API they were meant to prove changed nothing. - Told the binder may not rename `androidx`, an agent was instructed to keep the hardcoded default "so existing callers don't break" -- the default being the forbidden thing. - 159 GB deleted, three-day-old processes killed, a cross-repo API change dispatched: all decided and then reported.
…nto one module Lands with known failures: `ksp-fixtures:compose` 51 of 99 and `ksp-fixtures:artifact` 3 of 36. `python-multiplatform` desktop 529/0, wasmJs, and `ksp-fixtures:app` 70/0 all pass. The remaining failures are one unresolved decision, described at the end. **The rule.** The binder may not export a Kotlin namespace under a different name. `_ROOT`, `_PACKAGES`, `register_package` and `kotlin_package_for` are gone -- with them the whole notion that `androidx.compose.foundation.layout` could be reached as `pythonx.compose.layout`. A Kotlin package is importable under its own name and no other. `install()` no longer takes a module map; it takes only the set of value classes writable as a raw primitive, which is a fact about the library that declares them rather than a rename. This also frees `pythonx.*` for what it is supposed to be: a real distribution whose modules sit on `sys.path` and whose code restructures the Kotlin surface into something Pythonic. The synthetic `pythonx.*` modules carried `__path__ = []`, which took those names *and* guaranteed no real package's files could ever be found under them. **The merge.** Removing the second namespace exposed that two layers had been building separate module objects for the same package. `PythonProxySource` puts proxy classes into `sys.modules` under Kotlin names; this layer served top-level declarations and extensions under `pythonx.*`. CPython answers from `sys.modules` before consulting any finder, so once both wanted the same name the first one there won and the other was simply absent. `_adapt_module` attaches this layer's `__getattr__` to whatever module object exists, creating one only if none does, and it runs from `_register_table` rather than at the end of the source -- `_PACKAGES_SEEN` is empty until the table is registered, so a module created before that point would never reach the finder. It re-attaches rather than skipping an already-adapted module: a module outlives an `install()`, and one adapted by an earlier table closes over dictionaries the current one has replaced. That was invisible while every such module hung off a single `pythonx` root that could be dropped wholesale. **What is left, and why it is not decided here.** Two kinds of name now meet inside one module, and a name in a module's `__dict__` wins over its `__getattr__`, so a proxy class shadows a declaration of the same name -- `Column() got an unexpected keyword argument 'content'`. Which of the two owns a contested name is a design decision: `docs/kotlin-extensions-in-python.md` §4.2 proposed a hybrid that is class and callable at once, and its metaclass form was measured and rejected there. Picking a replacement is not a call to make while landing a refactor.
…visible
Sent a documentation-sync task to agy without it. The worktree was
untouched and the whole output was:
jetski: no output produced — a tool required the "command" permission
that headless mode cannot prompt for, so it was auto-denied.
The existing note in this section says to check the head of the log
because an argument error exits immediately and looks like success in a
background run. That is not enough here: the wrapper ended with
`echo "AGY_EXIT=$?"` and **that line never appeared**. So the failure is
invisible to the exit code as well as to a quick glance -- a background
run reports completion and simply has no artefacts.
The check that does work is looking at the worktree's own
`git status --short`. Recorded with the invocation, including
`--add-dir`, which is also needed for agy to read the tree at all.
…wo entries this proved wrong Three corrections, all from measurements taken 2026-09-13. 1. `agy -p "/usage"` reports the actual remaining quota, and nothing else in this document said so. It is a slash command, not a subcommand -- `agy usage` is rejected outright. The output shows TWO windows, weekly and five-hour, per model family. The five-hour window is usually what blocks, and reading only the weekly one misleads: measured here, weekly stood at 65% and 33% while both five-hour windows were at 0% and nothing would run. This matters because the same `Individual quota reached` message was misdiagnosed twice in one day -- first as total exhaustion, then as a concurrency limit -- and both were wrong. The cost of guessing is real in both directions: a live account gets reported dead and hours of throughput are thrown away, or rounds keep being dispatched at one that is actually blocked. Query first, then judge. 2. The second account is alive. This document said it was dead of an expired subscription and told the next reader not to try reviving it. What actually appeared was `Failed to authenticate: OAuth session expired and could not be refreshed` -- a session, not a subscription -- and it works after logging in again. Both wrappers are recorded now, `claude-sub` and `agy-sub`, with the point that their quotas are counted separately from the main account's. 3. A fresh worktree has no vendored tree, so its gate is meaningless until one is assembled: every suite fails with `torch._C has no _aten_implemented`. Two rounds today judged their own work on such a gate. One reported "0 new failures" while 119 were already failing and hid its own; the other explained the failures away as build ordering. The coordinating session must run vendor_torch.sh and install_shim.sh before handing a worktree out -- not the agent, because the permission classifier blocks it and then the rule-following agent stops while the rule-breaking one works around it.
…inating session 1. An agent's completion notification says its turn ended, not that its worktree is quiet. On 2026-09-17 a notification was read as "finished" and the coordinating session then ran `git stash`, `merge`, `pop`, a rebuild and its own gate in that same worktree. The agent's gate was still running. It died at `cat: test_shim.py.log: No such file` with **944 ok** on the board -- a number that reads like a partial pass rather than a corrupted run. The cause was shared state: both gates derived one `$stage` from the repository path, and the second one's `rm -rf "$suite_logs"` deleted the first one's logs mid-run. The tooling is fixed now -- the gate takes a stage lock and refuses a second run, and log directories are per-run -- but stashing an agent's uncommitted work is dangerous on its own, so the check before touching a worktree stays. 2. The stage lock only covers one worktree. Gates in *different* worktrees hold different locks, run concurrently, and the CPU contention makes the CoreML `MLComputePlan` tests fail in batches. At load 10-12 a branch that changed only documentation came out with 4 FAIL, every one of them CoreML, every one of them contamination. Another round's vendoring -- a cargo build -- counts as load too. Both entries exist because the same mistake was made more than once in one day, and each time the failure looked like a regression in the branch under test.
Both were created by a coordinating session in earlier work and left at `/Volumes/macMini/`, outside any repository, where nothing referenced them and nothing would have noticed them going stale. - `tools/agent_rules.md` — the common rules a subagent reads before starting. - `tools/loop-verify.sh` — the verification gate for the paseo loop. Still live: its target worktree `/Volumes/macMini/worktrees/loop` exists. `loop-verify.sh` keeps its hard-coded worktree path. Relocating the file is tidying; changing where it looks is a behaviour change and belongs to whoever next works on that loop.
89 worktrees were living at /Volumes/macMini/worktrees, outside any repository. They are now at .worktrees/ and gitignored, and their git registrations are repaired -- `git worktree list` reports 0 prunable where it previously reported 89, because the metadata still pointed at the old absolute path. The repository is on the external volume already, so the constraint that put them outside in the first place -- do not fill the internal SSD -- is satisfied without scattering working trees across the disk.
Without a rule saying so, this session put its worktrees (86 of them, 267 GB) in /Volumes/macMini/worktrees, its agent prompts in /Volumes/macMini/prompts, the RN benchmark and a wasm build tree beside them, and dozens of logs in /tmp. Nobody managed those locations, they ended up shared with other projects' sessions, and the user moved all of it back by hand. §6 makes the repository root the boundary: worktrees under .worktrees/, benchmarks/, experiments/, tools/, and the session scratch directory for anything temporary. Writing to another repository is not an exception -- only when told to work there. Re-pointing the shared build caches is called out separately because it reaches other projects. Two stale paths in the same file pointed the other way and are corrected: the worktree location and the build-dir cleanup command both named /Volumes/macMini/worktrees, and the cache line named /Volumes/macMini/caches, which no longer exists.
This reverts 0308cfd. That commit made the adapter win a contested name in a Kotlin-named module by deleting the proxy's positional function. The original design gives each namespace one owner: Kotlin names (androidx.*, kotlin.*) belong to PythonProxySource, and the keyword/default/value-class surface belongs to pythonx, which is the pythonx-compose package's job, not the binder's. Letting the adapter take over Kotlin names rebuilt a Pythonic layer inside the binder. Also drops the stray empty a.o that commit added at the repository root.
On every push to develop, tools/release/sync-release.sh writes one commit to release whose tree is develop minus the Markdown that must not reach main: every root *.md except README.md, and every *.md directly under docs/. A workflow then opens or updates the release -> main pull request. main-source-guard fails any PR into main whose head is not release, and protect-main.sh prints (or, with --apply, applies) the branch protection that makes that check required. pages.yml publishes docs/guide from main. Tests: tools/release/test-sync-release.sh (31 checks, scratch repos under .tmp/).
CLAUDE.md becomes AGENTS.md, rewritten in English as the shared core plus this repository's rules; CLAUDE.md is now only `@AGENTS.md`. The torchnative-only sections move out (they go to torchnative). tools/agent_rules.md is folded in. docs/ keeps no Markdown at its root any more: design/, investigations/, platforms/ and roadmap/ (ROADMAP.md moves there). Every reference to a moved path is updated, including comments and generator-emitted strings and the tests that assert them. New: docs/INTENT.md, docs/SPEC.md, docs/locale/README_ko.md, and docs/guide, a static en/ko site checked by tools/check_guide.py. README.md and PROJECT.md are rewritten.
release is not a long-lived branch: CI builds it from each develop push. Each run now makes exactly one commit on top of the source commit (tree = source minus the Markdown main must not carry) and force-pushes it with a lease on the remote sha it read. Stale release branches are ignored and overwritten instead of becoming the base. The PR step uses the optional RELEASE_PR_TOKEN secret: a PR opened with GITHUB_TOKEN triggers no workflows, so main-source-guard would never report.
The 3.15t claim (2026-08-10) was corrected by ROADMAP section 9 two days later: 3.14t passes on desktop with -PpythonFreeThreaded=true, the default stays off, and Android/iOS have no free-threaded prebuilts. The restructured docs had copied the stale claim; threading-and-abi.md keeps its reasoning as history. Also: release is CI-generated from each develop push, not a standing branch.
The wasm experiment demonstrated little the library did not already have; two things were missing and are now in place: - tools/wasm/build-cpython.sh builds, verifies and stages the CPython Emscripten interpreter inside the repo (.caches/), replacing a script and a default path outside it. On develop the wasm suite silently skipped because that path no longer existed; buildWasmPython wires the script into Gradle. - The staged stdlib zip predated its own ABI patches (PYEMSCRIPTEN_PLATFORM_ VERSION read as None). WasmInterpreterAbiTest pins it; WasmSharedMemory GrowthTest pins pointer validity across memory growth, which only the experiment proved before. wasm-experiment/ is removed. Root: build_log.txt, claude-code-sessions.zip (unrelated transcripts), the assertion-less test_c_api probes and the already-applied update_*.py migration scripts are removed. benchmarks/rn-benchmark brings in the React Native comparison source only (heavy dirs ignored, out-of-repo paths made relative). tools/loop-verify.sh runs in its own checkout and runs suites separately. .gitignore drops the wasm-experiment lines and ignores .caches/ and .tmp/.
# Conflicts: # python-multiplatform/build.gradle.kts # python-multiplatform/src/wasmJsMain/README.md # python-multiplatform/src/wasmJsMain/kotlin/python/native/ffi/EmbedAPI.wasmJs.kt # python-multiplatform/src/wasmJsMain/kotlin/python/native/ffi/Wasm.kt
The rest of experiments/ becomes code and tests: - build-cpython.sh gains `wheels` (pydantic_core and typing_extensions by pinned sha256, fetched into .caches) and `stock` (the unpatched PEP 776 build as a negative control). `build`/`all` now build the native prerequisites a fresh checkout lacks, which only worked before on a reused tree. - WasmCompiledWheelTest imports a real compiled extension (pydantic_core). - WasmCallTrampolineTest pins the arity CPython's wasm trampoline accepts. - WasmCrossingOverheadTest and WasmUpcallRouteOverheadTest turn the experiment's measurements into tests that assert only orderings. - WasmKotlinLeavesLinearMemoryAloneTest pins that Kotlin-only work leaves CPython's memory untouched. The wasm CI job fetches the wheels. Benchmark.run returns its ns/op figure. Not integrated: patch-memory-max and legacy Test B (Kotlin now imports its memory), and the METH_O upcall variant (a calling-convention change).
Compose 1.6.11 -> 1.11.1 (material3 1.9.0), lifecycle 2.10.0, activity-compose 1.12.4. compileSdk goes to 36 because material3-android, navigationevent and activity 1.12 require it; targetSdk stays 34. The generator's $composer/$changed/$default slot assumptions hold on 1.11 (2727 slots pair; SecureTextField's new Char slot is declined by design). One generator defect surfaced: material3 1.9's 326 entries overflowed the 64 KB method limit in ArtifactFragment.entries(), so large fragments now split into ordered helper methods. Tests follow the API changes: AnchoredDraggableState lost its threshold and animation arguments, dragAndDropSource lost its PointerInputScope overload, lightColorScheme takes 48 colours, Icon gains a ColorProducer tint, and the hidden clickable overload is replaced. sample drops iosX64, which 1.11 does not publish.
The binder must never re-export a Kotlin namespace under another name. It did, in three places, and the Compose render tests had been written against that: - the binding layer renamed members and parameters to snake_case on androidx.* and answered there only when it happened to install last; - the stub generator turned androidx.* into pythonx.* by default; - the adapter occupied sys.modules["pythonx"] with __path__ = [], so the real pythonx-compose package could never load. Now every function on a Kotlin-named module takes keyword arguments by Kotlin parameter name and honours Kotlin defaults (KotlinSurface.kt), whichever layer installed it: one owner per name, decided by the upcall-table epoch at call time. The adapter installs as python_multiplatform.binding, with no name conversion. Stubs are emitted under Kotlin names only; the Pythonic stub product and its manifest belong to pythonx-compose. Functions carry public metadata for pythonx-compose to re-expose them by rule: inspect.signature (Kotlin names, KOTLIN_DEFAULT for defaults, no synthetic slots) and python_multiplatform.describe(fn) (overloads, Kotlin types, value classes, the trailing composable content parameter). Tests move from snake_case to Kotlin parameter names. This reverts nothing of the GIL, thread-state or upcall calling convention.
# Conflicts: # ksp-fixtures/compose/src/desktopTest/kotlin/fixture/compose/ComposableRenderTest.kt # ksp-fixtures/compose/src/desktopTest/kotlin/fixture/compose/IconRenderTest.kt
Fix: A KSP result of a proxied Kotlin class is that class's proxy
github-actions
Bot
force-pushed
the
release
branch
from
October 3, 2026 10:36
49d2dc7 to
5505efd
Compare
Chore: Wasm npm lock at gradle/wasm-yarn.lock, no root kotlin-js-store
github-actions
Bot
force-pushed
the
release
branch
from
October 3, 2026 10:40
5505efd to
39f8ebb
Compare
Docs: No new top-level folders without approval
github-actions
Bot
force-pushed
the
release
branch
from
October 3, 2026 10:42
39f8ebb to
3a4287f
Compare
…ged and the JVM alive (#98)
…ng PyObjects (#98) The traverse proof built PyObject(0xAAAA, borrowed=false); the first GC after it ran Py_DecRef(0xAAAA) on the Cleaner thread and killed the JVM (hs_err: si_addr 0xaaaa, Py_XDECREF <- topLevelDecRefAction). The upcall path's ownership is correct: it now uses two real object() instances (new references, borrowed=false).
Fix: GeneratedTableTest wrapped fabricated addresses in owning PyObjects (JVM crash on GC)
github-actions
Bot
force-pushed
the
release
branch
from
October 3, 2026 10:50
3a4287f to
a40a167
Compare
Chore: main protection by settings only (option B)
Test: Always run the TypedPython leak test now that #98 is fixed
CI: Run the TypedPython app fixture and fail when its tests skip
github-actions
Bot
force-pushed
the
release
branch
2 times, most recently
from
October 3, 2026 11:07
3461702 to
93c4cb9
Compare
Chore: Remove protect-main.sh
github-actions
Bot
force-pushed
the
release
branch
from
October 3, 2026 11:31
93c4cb9 to
c7feffd
Compare
CI: Cancel superseded runs per workflow and ref
github-actions
Bot
force-pushed
the
release
branch
from
October 3, 2026 11:41
c7feffd to
00141fa
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated sync of
develop(e888e7f) intorelease. Markdown outside README.md and docs/ subfolders is removed by tools/release/sync-release.sh.