Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
955b1d3
Preserve Python class namespace semantics
sunmodza Sep 27, 2026
d3ba9b3
Align entry metadata and scope visibility with CPython
sunmodza Sep 27, 2026
abb1d50
Hide generator type scopes from Python locals
sunmodza Sep 27, 2026
e4a016a
Compile docstring-only classes and check stdlib syntax
sunmodza Sep 27, 2026
8b84cfb
Preserve entry script state through process shutdown
sunmodza Sep 27, 2026
787f999
Match Python original arguments and safe import path
sunmodza Sep 27, 2026
7da0b8d
Run Python modules with CPython-compatible startup
sunmodza Sep 27, 2026
ac6fd22
Support Python command stdin and archive entry modes
sunmodza Sep 27, 2026
853d8ca
Compile AI source during module startup
sunmodza Sep 27, 2026
8fec650
Resolve modules before loading project configuration
sunmodza Sep 27, 2026
6b51a11
Preserve Python generator delegation and shutdown behavior
sunmodza Sep 27, 2026
6a64173
Avoid user attribute hooks during type inspection
sunmodza Sep 27, 2026
bb487ec
Allow tracing callbacks to reenter type checks
sunmodza Sep 27, 2026
1596c80
Avoid collisions with user Python bindings
sunmodza Sep 27, 2026
4dcafc6
Hide runtime bindings from Python globals
sunmodza Sep 27, 2026
9eb9179
Preserve Python bytecode cache on source imports
sunmodza Sep 27, 2026
e630a5d
Explain modules beneath packages with AI syntax
sunmodza Sep 27, 2026
609e654
Support variadic generic parameter and tuple annotations
sunmodza Sep 27, 2026
4ca7bd3
Allow callable annotations at runtime boundaries
sunmodza Sep 27, 2026
a3ee0ca
Cover Python 3.14 syntax in differential tests
sunmodza Sep 27, 2026
9f34539
Expand variadic generic type aliases in contracts
sunmodza Sep 27, 2026
04a8d29
Resolve defaulted generic alias parameters
sunmodza Sep 27, 2026
e58bdce
Bind variadic and defaulted generic class parameters
sunmodza Sep 27, 2026
ef5fed9
Resolve inherited generic field types
sunmodza Sep 27, 2026
c867115
Expand inherited variadic generic arguments
sunmodza Sep 27, 2026
aed6dd3
Resolve inherited generic defaults by parameter identity
sunmodza Sep 27, 2026
d654b3b
Support TypeGuard and TypeIs return contracts
sunmodza Sep 27, 2026
ec8c56b
Accept TypeAlias declaration markers
sunmodza Sep 27, 2026
05750d3
Validate standard generic collection annotations
sunmodza Sep 27, 2026
561a278
Validate typed regular expression objects
sunmodza Sep 27, 2026
4a54907
Accept standard typed IO streams
sunmodza Sep 27, 2026
e817db4
Bind AnyStr through typed IO streams
sunmodza Sep 27, 2026
0a1246e
Check concrete implementations of mapping annotations
sunmodza Sep 27, 2026
90a2bcf
Support abstract concrete collections and bare typing aliases
sunmodza Sep 27, 2026
5fa6aa0
Validate Hashable Sized and ChainMap annotations
sunmodza Sep 27, 2026
a20891a
Validate typed mapping views
sunmodza Sep 27, 2026
05dc829
Validate Container and Reversible annotations
sunmodza Sep 27, 2026
912091e
Accept bare lazy and context manager annotations
sunmodza Sep 27, 2026
721455a
Validate union and TypeVar class annotations
sunmodza Sep 27, 2026
3d1e0af
Validate class objects for generic annotations
sunmodza Sep 27, 2026
362fed7
Validate unpacked TypedDict keyword arguments
sunmodza Sep 27, 2026
38ad82f
Resolve Self in inherited class fields
sunmodza Sep 27, 2026
d7668dc
Resolve Self for renamed method receivers
sunmodza Sep 27, 2026
cb2a355
Resolve Self in dynamically assigned methods
sunmodza Sep 27, 2026
509dc50
Run piped stdin without explicit dash
sunmodza Sep 27, 2026
e607c12
Add persistent interactive Aiython console
sunmodza Sep 27, 2026
1004ea7
Apply CLI options to interactive and piped execution
sunmodza Sep 27, 2026
ef37ddf
Support dataclass InitVar keyword markers and field factories
sunmodza Sep 27, 2026
a7041a0
Run PYTHONSTARTUP before interactive Aiython sessions
sunmodza Sep 27, 2026
604a380
Support interactive sessions after script execution
sunmodza Sep 27, 2026
6b67ad2
Support Pydantic and attrs class field descriptors
sunmodza Sep 27, 2026
3cc9b25
Accept arbitrary Annotated metadata without reevaluation
sunmodza Sep 27, 2026
049bce9
Validate annotated Enum members after auto resolution
sunmodza Sep 27, 2026
de908d9
Defer descriptor field checks until instance access
sunmodza Sep 27, 2026
253c450
Restore embedded console exception and display state
sunmodza Sep 27, 2026
4658b8f
Accept attrs Factory defaults in annotated classes
sunmodza Sep 27, 2026
420daba
Preserve evaluation order for non-name annotations
sunmodza Sep 27, 2026
0f6b93d
Release generator frames promptly and preserve function name updates
sunmodza Sep 27, 2026
7403eb2
Respect descriptor-backed dataclass fields during type checks
sunmodza Sep 27, 2026
bb8fff2
Preserve dataclass construction and decorated class source lines
sunmodza Sep 27, 2026
2104133
Resolve type(None) safely in annotations
sunmodza Sep 27, 2026
a749a77
Resolve class local annotation aliases and bare InitVar
sunmodza Sep 27, 2026
549fd43
Accept whitespace in string type annotations
sunmodza Sep 27, 2026
3b10fb6
Resolve lexical class annotations and validate completed dataclasses
sunmodza Sep 27, 2026
5ee1aae
Resolve generic parameter scopes and skip empty class scans
sunmodza Sep 27, 2026
558e0c9
Handle methods invoked before metaclass MRO is ready
sunmodza Sep 27, 2026
112fcd3
Preserve private attribute mangling and safe instance scans
sunmodza Sep 27, 2026
bdb3358
Route execution through native bridge and preserve recovery and yield…
sunmodza Sep 28, 2026
54dabec
Make offline CI independent of local credentials and launcher alias
sunmodza Sep 28, 2026
6aa6f12
Expose recovery and yield through managed bridge API
sunmodza Sep 28, 2026
cb9778e
Validate native generator yields with Python monitoring
sunmodza Sep 28, 2026
9f0bbea
Use VM type callbacks with AI recovery and restore full coverage
sunmodza Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ jobs:
persist-credentials: false
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.11"
python-version: "3.11.16"
version: latest-known
enable-cache: false
- run: uv run --locked zensical build --clean --strict
- run: uv run --locked python scripts/check_docs_site.py
- run: uv run --locked --python 3.11.16 zensical build --clean --strict
- run: uv run --locked --python 3.11.16 python scripts/check_docs_site.py
16 changes: 11 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]
python-version: ["3.11.16", "3.12.14", "3.13.15", "3.14.4"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -25,6 +25,12 @@ jobs:
python-version: ${{ matrix.python-version }}
version: latest-known
enable-cache: false
- name: Prepare offline project credentials
run: |
mkdir -p .aiython
printf 'OPENROUTER_API_KEY=offline-test\n' > .aiython/credentials.env
- name: Check CPython frontend code objects
run: uv run --locked --python ${{ matrix.python-version }} python benchmarks/stdlib_syntax.py
- name: Test with line and branch coverage
env:
COVERAGE_PROCESS_START: ${{ github.workspace }}/.coveragerc
Expand All @@ -45,7 +51,7 @@ jobs:
persist-credentials: false
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.11"
python-version: "3.11.16"
version: latest-known
enable-cache: false
- name: Check tag matches package version
Expand Down Expand Up @@ -84,11 +90,11 @@ jobs:
persist-credentials: false
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.11"
python-version: "3.11.16"
version: latest-known
enable-cache: false
- run: uv run --locked zensical build --clean --strict
- run: uv run --locked python scripts/check_docs_site.py
- run: uv run --locked --python 3.11.16 zensical build --clean --strict
- run: uv run --locked --python 3.11.16 python scripts/check_docs_site.py
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: docs-site
Expand Down
37 changes: 33 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.11"
python-version: "3.11.16"
version: latest-known
- run: uv build --no-sources
- run: uv venv --python 3.11 /tmp/aiython-wheel-smoke
- run: uv venv --python 3.11.16 /tmp/aiython-wheel-smoke
- run: uv pip install --python /tmp/aiython-wheel-smoke/bin/python dist/*.whl
- run: /tmp/aiython-wheel-smoke/bin/aiython --version
- run: |
Expand All @@ -29,14 +29,20 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]
python-version: ["3.11.16", "3.12.14", "3.13.15", "3.14.4"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: ${{ matrix.python-version }}
version: latest-known
- run: uv python install ${{ matrix.python-version }}
- name: Prepare offline project credentials
run: |
mkdir -p .aiython
printf 'OPENROUTER_API_KEY=offline-test\n' > .aiython/credentials.env
- name: Check CPython frontend code objects
run: uv run --locked --python ${{ matrix.python-version }} python benchmarks/stdlib_syntax.py
- name: Test with line and branch coverage
env:
COVERAGE_PROCESS_START: ${{ github.workspace }}/.coveragerc
Expand All @@ -48,7 +54,30 @@ jobs:
uv run --locked --python ${{ matrix.python-version }} --extra a2a python -m coverage json -o coverage.json
uv run --locked --python ${{ matrix.python-version }} python scripts/coverage_summary.py
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: coverage-${{ matrix.python-version }}
path: coverage.json
if-no-files-found: error
if-no-files-found: warn

native-hooks:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11.16", "3.12.14", "3.13.15", "3.14.4"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: ${{ matrix.python-version }}
version: latest-known
- name: Install CPython build headers
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq build-essential pkg-config libbz2-dev libffi-dev \
libgdbm-dev libgdbm-compat-dev liblzma-dev libncurses-dev \
libreadline-dev libsqlite3-dev libssl-dev libzstd-dev tk-dev \
uuid-dev zlib1g-dev libnsl-dev libtirpc-dev
- name: Build and test patched CPython
run: uv run --locked --python ${{ matrix.python-version }} python scripts/build_cpython_hooks.py ${{ matrix.python-version }}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ aiython tickets.py

Use a project environment when your script imports other project dependencies; a uv tool has its own isolated environment. [Setup and configuration](docs/configuration.md) covers profiles, custom endpoints, and capability routes.

Run an importable module or package with `aiython -m package.module` or `aiython -m package`; pass its arguments after the module name.
The CLI also accepts `aiython -c 'print(1)'`, `aiython -` for stdin, and a directory or zipapp with `__main__.py`.

Ordinary Python runs without loading LiteLLM or contacting a provider. Reasoning calls use the [LiteLLM Python SDK](https://docs.litellm.ai/docs/) in process; no proxy service is needed.

## The execution boundary
Expand Down
3 changes: 3 additions & 0 deletions README.pypi.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ Python owns statement order, loops, assignments, and side effects. AI works at t

Use `aiython --help` to see the CLI, `aiython config show` to inspect configuration, and `aiython --stats tickets.py` to see model calls and timings. Additional routes for vision, documents, embeddings, reranking, speech, images, and video are configured only when needed.

Run an importable module or package with `aiython -m package.module` or `aiython -m package`; pass its arguments after the module name.
The CLI also accepts `aiython -c 'print(1)'`, `aiython -` for stdin, and a directory or zipapp with `__main__.py`.

Aiython is not a sandbox. Frame tools can use `eval` and `exec` with your process permissions, and relevant source or object data may be sent to your configured provider. Use trusted code and review provider data handling.

Licensed under MIT.
9 changes: 9 additions & 0 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,12 @@ For live task comparisons against agent baselines, keep acceptance tests, input
snapshots, model settings, tool permissions and budgets fixed; count failures as
well as successes. Follow the [evaluation guide](../docs/performance-evaluation.md)
and collect run wall time, human intervention, token usage and cost separately.

## Standard library syntax check

Run `uv run --locked --python 3.13 python benchmarks/stdlib_syntax.py` from the
repository root, changing `--python` to test another supported version. The
checker compares CPython compilation with Aiython's source transformation for
standard library modules. It processes files in batches to bound memory use.
Passing this check establishes syntax transformation only; it does not execute
the modules or establish behavioral compatibility.
72 changes: 72 additions & 0 deletions benchmarks/stdlib_syntax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Check the frontend against CPython and compile the standard library.

Run: uv run python benchmarks/stdlib_syntax.py
This checks compilation only; it does not execute the modules.
"""

import argparse
import gc
from pathlib import Path
import sysconfig
import tokenize

from aiython.frontend import parse
from aiython.models import ProfileConfig, ResolvedConfig
from aiython.runtime import Runtime


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--stdlib', type=Path, default=Path(sysconfig.get_path('stdlib')))
parser.add_argument('--batch-size', type=int, default=40)
args = parser.parse_args()
if args.batch_size < 1:
parser.error('--batch-size must be positive')

root = args.stdlib.resolve()
paths = sorted(path for path in root.rglob('*.py')
if not {'site-packages', 'test', 'tests'} & set(path.parts))
profile = ProfileConfig('offline', 'fake', 'offline')
config = ResolvedConfig(None, root, 'offline', {'offline': profile})
failures = []
runtime = None
for index, path in enumerate(paths):
if index % args.batch_size == 0:
if runtime is not None:
runtime.capabilities.close()
del runtime
gc.collect()
runtime = Runtime(config)
try:
with tokenize.open(path) as file:
source = file.read()
native = compile(source, str(path), 'exec', dont_inherit=True)
unit = parse(source, str(path))
if unit.blocks:
raise AssertionError('valid Python source was classified as AI syntax')
frontend_code = compile(unit.tree, str(path), 'exec', dont_inherit=True)
# CPython compares code contents, including nested code objects.
# marshal byte streams can differ in reference encoding even when
# the code objects are equal; both compiles use the same filename.
if frontend_code != native:
raise AssertionError('frontend changed the native CPython code object')
runtime.compile_source(source, str(path))
source_tree = runtime.units[str(path)].tree
if compile(source_tree, str(path), 'exec', dont_inherit=True) != native:
raise AssertionError('runtime preparation changed the native source tree')
except Exception as error:
failures.append((path.relative_to(root), error))
if (index + 1) % 100 == 0:
print(f'Checked {index + 1}/{len(paths)}; failures: {len(failures)}', flush=True)
if runtime is not None:
runtime.capabilities.close()
print(f'Checked {len(paths)} standard library files; failures: {len(failures)}')
for path, error in failures[:30]:
print(f'{path}: {type(error).__name__}: {error}')
if len(failures) > 30:
print(f'... and {len(failures) - 30} more failures')
return bool(failures)


if __name__ == '__main__':
raise SystemExit(main())
4 changes: 4 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ Model IDs use LiteLLM syntax. A route can also be `{ model = "...", api_base = "

Run `aiython config show --script PATH` to inspect resolved settings. `--config` chooses an exact file. `--profile` selects a default named profile; `--force-profile` overrides source directives. CLI options for script execution go before the script path, while later arguments go to the program.

Run an importable module or package with `aiython -m package.module` or `aiython -m package`. Put Aiython options before `-m`; arguments after the module name go to that module. Packages need a `__main__.py`, as with `python -m`. Source files get Aiython's source transformation; modules available only as bytecode run as Python bytecode. During execution, project parent packages with AI statements are transformed too. `--explain -m` resolves parent packages with AI syntax without executing them.

The CLI also accepts `aiython -c 'print(1)'`, `aiython -` for source from stdin, and a directory or zipapp containing `__main__.py`. Arguments after the command, `-`, or path go to the program. For `-c` and stdin, Aiython searches for `aiython.toml` from the current directory. A direct `.pyc` file and source-less `__main__.pyc` run as Python bytecode without source transformation or `--explain` output.

Version 1 and 2 configs are rejected with migration guidance and never overwritten. Move the old reasoning model to top-level `model`, put other routes under `[capabilities]`, and replace direct keys with `api_key_env` plus a private env file. Runtime state starts fresh in `.aiython/runtime-v3.sqlite`; older databases remain untouched.

## Deadlines and jobs
Expand Down
4 changes: 4 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ aiython tickets.py

`--explain` shows the AI boundary without executing the script or contacting a provider. Running the script uses your chosen provider and may incur charges. For a uv project, prefix both commands with `uv run`.

Run `aiython` without a script in a terminal for an interactive console. It keeps variables, type annotations, and future imports between commands, and accepts AI expressions. Pass `--profile NAME` or `--config PATH` to select its model configuration. Piped input runs as a Python script, with or without an explicit `-` argument.

Use `aiython -i tickets.py` to enter the console after the script finishes. Its variables and type annotations remain available there.

## Next steps

- Try the [small examples](examples.md) to see one behavior at a time.
Expand Down
Loading
Loading