Repository navigation
Releases: ets-public/brevi
Release list
Brevi v0.25.0
The launch release, out ahead of the weekly Monday release. Most of it is correctness: Brevi now measures itself against CPython's own test suite (tools/conformance.py), and the fixes it led to cut built programs that give a wrong answer there from 79 to 45 while the share of tests that match rose from 49.2% to 57.9%; every other silent wrong output found in the release runs is fixed too, including d.copy() and list(d) on a dict in typed builds. 567 of 813 saved agent programs now take the fast typed build, with none differing from CPython. Python files exit 1 on an uncaught exception, Brevi-only syntax in a .py file is refused as CPython refuses it, install.sh checks its download against SHA256SUMS, and the dynamic build's re beats CPython on search loops. Release test, two runs on the release tree: Sonnet 1.09x and Haiku 1.01x the cost of CPython, every task passed, no check failed because of Brevi, 95 of 96 final programs native (two earlier runs on the tree just before #207: 1.04x and 1.05x, 96 of 96).
- Composio example.
integrations/composio_agent.pygives a LangChain agent Composio's hosted MCP server for app tools (GitHub issues here) next tobrevi mcp. Composio holds the credentials and makes the calls; the data reaches Brevi'srunasstdin, so the agent's program runs sandboxed with nothing granted. - Silent wrong output fixed: list items that start with
\x01. On the typed pathprint(["\x01a", "b"])printed['b'], because regex matches kept their group positions as a hidden last string and printing a list dropped any such string. A match is now its own type holding the positions apart, so a list shows every item, printing a match shows<re.Match object; ...>, andm.group(9),m[-1],len(m)andfor x in mfail as in CPython instead of exposing the hidden text. (#200, thanks @timoconnellaus) - CPython conformance measure.
tools/conformance.pycuts each test of CPython 3.13.13'stest_grammar,test_scope,test_int,test_listandtest_dictinto a standalone script and comparesbrevi buildoutput with CPython's: at first 49.2% of 311 scored tests matched (153), 59 were refused and 79 built programs gave a wrong answer; after the fixes below from that run, 57.9% match (180), 67 are refused and 45 give a wrong answer (57.6% and 45 withBREVI_DYN=1). Report;--checkfails only on a wrong result not incompat/conformance_known_wrong.txt. Not in CI. (#199, thanks @timoconnellaus) install.shverifies its download. The script now checks the release archive against the release'sSHA256SUMSbefore unpacking it, and stops with nothing installed on a mismatch or when the check cannot be made (BREVI_FROM_SOURCE=1still builds from source). (#198, thanks @timoconnellaus)time.gmtime,time.strftimeanddatetime.timezonebuild natively (#194). The dynamic build refused them, so a shared module formatting a UTC timestamp sent 23 of the 27 files of the pipeline in #175 to CPython. Nowtime.gmtime()gives atime.struct_time(fields, indexing,tm_zone),time.strftime(fmt, t)formats it, andtime.time_ns()is there. Datetimes can be aware:datetime.timezone.utc,datetime.UTC,timezone(timedelta(...)),tzinfo=indatetime(...),combine(d, t, tz),replace(tzinfo=...),now(tz),fromtimestamp(ts, tz),utcfromtimestamp,astimezone(tz),timestamp(),utcoffset(),tzname(),%z/%Z, offsets inisoformat(),str()andrepr(), comparison and subtraction as instants, and CPython's error for mixing naive and aware datetimes.fromisoformat("...Z")and"...+02:00"now give aware datetimes; before, theZwas dropped and the result was naive (silent wrong output). Local time (time.localtime,strftimewithout a tuple, a naivetimestamp(),fromtimestampwithout tz) still runs in CPython. Aware datetimes passed to a Python library keep their offset.- A dynamic-build blocker in an imported module names that module's file (#194).
brevi check main.pysaidmain.py line 4: time.gmtime is not supportedwhen line 4 was in_m.py; it now says_m.py line 4, in check, in run's path line and in--explain. (x or "").lower()on a JSON value builds typed (#195).x or ""wherexcame fromjson.loadswas typed as a bool, so the typed build's Rust did not compile and the file took the slower dynamic build. A JSON valueora string or int is now a JSON value. Also:re.search,re.sub,re.findalland friends on a JSON string build typed (they stopped the typed build withno method named re_find found for enum Json).json.load(open(path))builds typed (#195). It stopped check withunknown function __open_r(), which read like an internal error; it now reads the file aswith open(path) as f: json.load(f)does. Other uses of anopen()file the typed build cannot take (passing it to a function, for example) get a plain message instead.- The dynamic build's
reis faster than CPython on search loops (#195). A search now skips straight to the characters a match can start with (also after\b,^or\A, and withre.I), a one-character pattern such as[^a-z ]scans without backtracking, and searching one text with many patterns converts the text once. On a stand-in for the issue's bench (re.search(rf"\b{w}", scene, re.I)over 147 topics and 79 scenes, plusre.sub) the dynamic build went from 0.86x to about 1.8x CPython's speed. - Silent wrong output fixed:
\N{NAME}in.pyfiles.print("\N{BULLET} item")printed the escape as text on the typed path and sent the program to CPython from the dynamic build. Both builds now resolve names (any case, aliases) from a table in the compiler, 400 KB of binary size; a bad name, or a computedCJK UNIFIED IDEOGRAPH-/HANGUL SYLLABLEone, is refused and left to CPython. (#185, thanks @timoconnellaus) - Silent wrong output fixed: float
sum(). Typed builds added floats one by one, sosum([0.1] * 10)printed0.9999999999999999andsum([1e100, 1.0, -1e100])printed0.0. Every typed float sum (lists, generators, JSON numbers, Python library values,sum(xs, start)) is now compensated as in CPython 3.12+ and the dynamic build, andsum([-0.0])prints0.0. (#190, thanks @timoconnellaus) - Uncaught exceptions in Python files exit 1, as in CPython. Native typed builds exited 101, Rust's panic status, and
bench/compare/compare.pyhid it by only comparing zero with non-zero; it now compares exact exit codes..bvprograms still exit 101 after a runtime error. (#187, thanks @timoconnellaus) - A
.pyfile with Brevi-only syntax or methods is refused, as CPython refuses it.if a && b:,x => x + 1,fn f(x):,0..n,true,fail("x")andxs.push(1)on a list used to build and print an answer; nowcheckandbuildreport one error with the Python spelling andbrevi runfalls back to CPython, which gives the real error. A class's ownpushstill works, and.bvfiles keep every Python spelling. (#189, thanks @timoconnellaus) - Silent wrong output fixed: print arguments and tuple items changed by a later one.
print(xs, grow(xs)), wheregrowchanges the list (or a global, orself), printed the list from before the change in typed builds, andr = (q, q.pop())kept a copy ofq; both now go to the dynamic build and print what python3 prints. None of the 580 saved solutions moved off the typed path. (#188, thanks @timoconnellaus) - Silent wrong output fixed: a Python file the guard could not read. When the dynamic parser could not read a file (or a module it imports) that the typed build accepted, such as one using
except*, every guard check was skipped, soprint({3, -1, 5})printed the set sorted. Such a file is no longer built typed:brevi runruns it with python3, andbuildandcheckreport an error. Files with a bareallowline now get the guard's checks too. (#191, thanks @timoconnellaus) - Silent wrong output fixed:
d.copy()andlist(d)on a dict in typed builds.d.copy()gave the dict's keys, soprint(d.copy())showed{'a'}for{'a': 1}, andlist(d)printed as a set ({'a'}for['a']).d.copy()is now a copy of the dict (also with tuple keys) andlist(d)a list of its keys. Found from the conformance run. - Silent wrong output fixed: comparing dict views in typed builds.
d.items() <= e.items()andd.keys() == {1, 2}follow set rules in CPython; the typed build compared them as lists. Programs that comparekeys(),items()orvalues()with<,<=,>,>=,==or!=now take the dynamic build, which now compares key and item views with sets and with each other as CPython does (values()views compare by identity, sod.values() == d.values()is False). Found from the conformance run. - Dict subclasses in dynamic builds:
__missing__anddict(mapping).d[k]on a dict subclass with__missing__now calls it for a missing key (aCounter-stylec[k] += 1works), anddict(sub),dict(obj)andd.update(obj)copy a dict subclass, or any object withkeys()and__getitem__, as CPython does instead of gi...
Brevi v0.24.6
Breaking: sandbox. Under
--sandbox, and in the MCP tools, which sandbox by default, a program's own# allow read,# allow writeor# allow nativeline no longer grants anything. Only the caller grants capabilities:--allow read,write,native,--allow-py requests,pydantic(CPython for just those Python libraries), or the MCP tools'allowandallow_pyarguments. A sandboxed program whose allow line asks for more than the caller granted is refused. If your setup relied on an allow line in the file, pass the flag or MCP argument instead. Without--sandboxnothing changes: a program with an allow line is still held to it.
A security fix release, out ahead of the weekly Monday release: from v0.24.1 to v0.24.5 code written by an AI (or anyone) could free itself from the sandbox by adding an allow line, for example to reach the network through requests. It also brings dynamic builds that call Python libraries instead of falling back to CPython, and native argparse, textwrap, bytes, hashlib, base64 and a faster native random. Release test over two runs on the release commit: Sonnet 1.03x of Python's cost (95% range 0.97x to 1.09x), Haiku 1.02x (0.97x to 1.08x), every task passed on both sides, no check failed because of Brevi, and 47 of 48 and 48 of 48 final programs native (one Sonnet toc program ran in CPython).
- Dynamic builds call Python libraries instead of falling back to CPython. A Python file that the typed build could not take and that imported a library Brevi lacks (requests, pydantic, yaml, sqlite3, logging, ...) used to run entirely in python3. Now the dynamic build hands just those imports to CPython, which it loads into the program as the typed build's pybridge does, and the rest of the program stays native. Classes built on a library's classes (
class Post(BaseModel)) are run by CPython as a whole, andopen()is CPython's. The path line says what CPython runs:{"path":"dynamic",...,"py":["pydantic","requests"]}. Library values behave as they do in CPython: attributes, calls with keyword arguments, items, operators, iteration (lazy, so generators print in order),with, formatting,isinstance, and exceptions you can catch by the library's class or a builtin base, with their attributes. Functions of the program can be passed as callbacks, and a list or dict a library changes is copied back. A multi-file pipeline modelled on #175 (argparse CLIs, a requests client, pydantic models, JSON files) prints and writes exactly what python3 does.--sandboxstill refuses these imports unless the caller grants them (see the next entry).brevi checkand--nativestay strict. - Under
--sandboxonly the caller grants capabilities. A# allow read,# allow writeor# allow nativeline in the program used to unlock that capability under--sandbox, so code written by an AI (or anyone) could free itself, for example to reach the network through requests. Now the sandboxed program's own allow lines grant nothing: one that asks for more than the caller granted is refused with`# allow native` grants nothing under --sandbox: only the caller grants native. The caller grants with--allow read,write,native, or--allow-py requests,pydanticfor CPython on the named Python libraries only (not rust fn, crates, or other libraries); in the MCP tools these are theallowandallow_pyarguments. Either flag implies--sandbox. Error hints now name the flag to pass, not a line to add. Without--sandbox, a program with an allow line is still held to it. Breaking: a sandboxed run that relied on an allow line in the file needs the flag instead. - Bytes,
hashlibandbase64build natively.b"..."literals were refused by the dynamic build andhashlib/base64ran in CPython; they now build natively (dynamic build), also under--sandbox. Bytes values havebytes(...),str.encode/bytes.decode(UTF-8, ASCII, Latin-1 withstrict/replace/ignore, CPython's error messages), indexing, slicing, iteration,in,+,*, comparisons, hashing and the common methods.hashlibhas MD5, SHA-1, SHA-224/256/384/512 andnew(pure Rust, no crates);base64has b64, urlsafe and b16 encoding and decoding with CPython 3.13's padding rules andbinascii.Errormessages. Fuzzed against python3: 102 hashes over lengths around the block edges, 900 UTF-8 decodes of random byte soup, every base64 edge case tried. Also:repr()of a str in dynamic builds now escapes non-breaking and other separator spaces as CPython does ('\xa0'). argparsebuilds natively. Command-line programs that parse their arguments with argparse (21 of the 27 files in the pipeline of #175) ran in CPython; they now take the dynamic build, also under--sandbox. Brevi builds CPython 3.13's own argparse with the program (tools/mkargparse.py, PSF license), so usage lines,-hhelp, error messages and exit codes are CPython's: 54 runs of three test CLIs (subcommands, groups, choices,nargs,BooleanOptionalAction, custom types,parse_known_args) print exactly what python3 prints. A run takes about 3 ms against 23 ms in CPython.argparse.FileTypeandfromfile_prefix_chars, which open files, still run in CPython. Also:brevi run x.pygives the programsys.argv[0] == "x.py"in dynamic builds, as python3 does;hasattr(value, "__name__")and similar dunder data were True for strings, numbers and None (argparse tests it);raise SystemExit(2)had no.code.textwrapbuilds natively.wrap,fill,shorten,dedent,indentandTextWrapper(all options butfix_sentence_endings) now run in the dynamic build, also under--sandbox, breaking lines exactly where CPython does (hyphenated words, em-dashes, long words,max_linesand placeholders); 1,000 random texts wrapped at random widths print what python3 prints.randombuilds natively outside the sandbox too. Brevi's own copy of CPython's generator now has its core (seeding, draws,getrandbits,shuffle) in native code: a loop of 300,000randintcalls and 100 shuffles takes 0.15 s, against 0.62 s with the old copy and 0.42 s through CPython (CPython itself: 0.11 s). So a program usingseed,random,randint,randrange,choice,choices,shuffle,sample,uniform,gauss,getrandbitsorRandomwith int seeds now takes the dynamic build instead of calling CPython, and draws the same numbers as CPython for the same seed. Other random functions, or a seed that is not an int literal, still use CPython's random.brevi checknames what stopped the dynamic build too. When the typed build could not take a file and the dynamic build refused it as well, check reported only the typed reason (except KeyError: ... and this file cannot be built over dynamic values), so fixing that line still left the file unbuilt and the real blocker (import argparse, a bytes literal) was shown only byrun --explain. The message now ends with it, as(file line N: reason), andbrevi runputs it in its{"path":"cpython"}line. The generator hint no longer suggests moving a yield out of a try block when the yield is not in one. Found running check over a 27-file production pipeline, where every file stopped on one shared line in an imported module.
Prebuilt brevi compiler for Linux, macOS and Windows. brevi still needs rustc on the PATH to build programs. On Linux and macOS, sh install.sh installs rustc if it is missing and picks the right download; on Windows, install Rust with rustup and unzip the .zip. Details: https://brevi-lang.org/docs/install
Brevi v0.24.5
A fix release for four silent wrong outputs in typed builds, the ones the v0.24.4 release test found in its trains and poker checks: a list name set to None by a nested function, changes through nested setdefault, joining tuples, and comparing JSON values. On the 813-program corpus 565 programs build typed, up from 550. Release test over two runs on the release commit: Sonnet 1.06x of Python's cost (95% range 1.01x to 1.12x), Haiku 1.01x (0.95x to 1.07x), every task passed on both sides, no check failed because of Brevi (5 did in v0.24.4), and 48 of 48 and 47 of 48 final programs native (the one Haiku cron_next program ran in CPython).
- Silent wrong output fixed: comparing JSON values. In typed builds
j == [](andj == {}) was true for a JSON null, 0, False or"", andFalse == 0on a JSON value was false; both now answer as Python does.v is True/v is Falseon a JSON value is now an identity test (a JSON 1 equals True but is not True), so template renderers that use it build typed. - Silent wrong output fixed: a list name set to None by a nested function.
players = Nonein a nesteddefwithnonlocal playerswas not seen by the outer function'splayers is None, so a typed poker program printed an extra empty round and read later hands as duplicates. The outer test now sees it. - Silent wrong output fixed: nested
setdefaultchanges lost.d = labels.setdefault(s, {}),lst = d.setdefault(k, []), then changinglstchanged only copies in typed builds, so every trip of a typed trains program printed "no route". The change now reacheslabels. - Fixed: joining tuples.
(tm,) + lab[k]gave a list in typed builds (a wrong result, or a TypeError where CPython ran fine); it is now one tuple, also when the right side is held as a JSON value.
Prebuilt brevi compiler for Linux, macOS and Windows. brevi still needs rustc on the PATH to build programs. On Linux and macOS, sh install.sh installs rustc if it is missing and picks the right download; on Windows, install Rust with rustup and unzip the .zip. Details: https://brevi-lang.org/docs/install
Brevi v0.24.4
Every one of the 48 final release-test programs from v0.24.3's runs now builds natively, with no CPython: enum, io.StringIO, json.JSONDecoder().raw_decode and path text from os and pathlib run natively, and exact fractions.Fraction, bin/hex/oct, startswith with a start index re.S/re.M and csv.writer(sys.stdout) are on the typed path. On the 813-program corpus 550 programs build typed, up from 496, and eight silent wrong outputs are fixed (re groups and tuples, rows changed in a loop, typed $ and re.split, dict keys printed as None). Release test over five runs (four on main before #171 and #172, one on the release commit): Sonnet 1.06x of Python's cost (95% range 1.03x to 1.10x), Haiku 1.03x (0.98x to 1.10x), every task passed on both sides, 120 of 120 and 117 of 120 final programs native. The run on the release commit alone gave 1.02x and 1.03x. Five checks along the way (of 2 tasks, trains and poker) failed under Brevi where CPython passed the same files; the models fixed their code and passed, and those typed-build wrong outputs are being fixed for the next release. The 3 programs that ran in CPython were split into several files, where a module Brevi could not build (a with other than open, except IndexError) sends the whole program to CPython.
- Typed path round 3. On the 813-program corpus 550 programs build typed, up from 522, none moved off it, and the CPython shadow check finds no wrong output.
j.get(k),j.get(k, [])andj.get(k, {})on JSON values,json.load(sys.stdin),any(f(x, seen) for ...)wherefchangesseen,deps or [], names set inside a loop (or just before itsbreak) and read after it, andreturn Nonefrom a function returning JSON now build typed. AMISSING = object()sentinel is read as None when every test of it also tests None. A loop over a set builds typed when its passes cannot depend on order (adding to sets, appending to the element's own entry, counting, setting a flag). - Silent wrong output fixed: dicts with different keys. When a list held dicts with different key sets (
{"id": 1, "error": "bad"}next to{"id": 2, "total": "4"}), the typed build printed and dumped every key on every dict, so a missing key showed asNone/null. Keys only some dicts have are now left out while unset, as in Python, andk in randr.get(k)see whether the key is set. csv.writer(sys.stdout)on the typed path.w = csv.writer(sys.stdout, lineterminator='\n')(also throughout = sys.stdoutused only for the writer) withw.writerow(row)andw.writerows(rows)now builds typed, quoting fields as CPython's csv module does (a field holding the delimiter, a quote or a line break is quoted, quotes doubled;delimiter=andlineterminator=literals). Of the 48 final release-suite programs, haiku_csv_join moved to the typed path and prints what python3 prints on the task's tests.re.S/re.DOTALLandre.M/re.MULTILINEon the typed path.re.compile(r'\{\{(.*?)\}\}', re.S)andre.split(p, s, flags=re.S), common in template renderers, sent the program to the dynamic build; the flags (and(?s)/(?m)written in the pattern) now build typed. Silent wrong output fixed: in the typed build$matched only at the very end, while Python's also matches before a final newline (re.search(r'z$', 'z\n'));re.splitskipped empty matches (re.split(r'\b', 'a b'), Python 3.7 on splits there) and gave''instead ofNonefor a group that took no part.io.StringIObuilds natively. An in-memory text file (write,getvalue,read,readline, iteration,seek,tell,truncate,print(..., file=buf),with), and socsv.writer(io.StringIO())andcsv.DictReader(io.StringIO(text)), now take the dynamic build, also under--sandboxwithout# allow native. The rest ofio(BytesIO,open) still runs in CPython. Both saved csv_join fallback programs print what python3 prints. Also: under--sandboxthe{"path":"dynamic"}line for such programs namedallow nativethough none was needed; it now says which modules the typed build lacks.bin,hex,octandstartswith/endswithwith a start index on the typed path.bin(x).count('1')and tokenizers that tests.startswith('<->', i)built over dynamic values; they now build typed (negative numbers and negative starts as in Python). Of the 48 final release-suite programs, haiku_sudoku moved to the typed path and prints what python3 prints on the task's tests.json.JSONDecoder().raw_decodeandjson.decoder.scanstringbuild natively. Tokenizers that read a JSON value or string out of a longer text (v, end = json.JSONDecoder().raw_decode(s, i)) ran in CPython; they now take the dynamic build, with CPython's indexes andJSONDecodeErrormessages. These were the last 2 of the 48 final release-suite programs that still needed CPython (bothjson_query); both now print what python3 prints on the task's tests.enumbuilds natively.class Color(Enum)with plain values orauto(), aliases, methods, properties and an__init__taking tuple values, plusColor(1),Color['RED'], iteration,len,in,__members__,@unique, andstr/intmixins (class Level(str, Enum)), now runs in the dynamic build; it used to run in CPython.repr,str, f-strings, hashing and errors (ValueError: 7 is not a valid Color,TypeErroron<) print what CPython prints.IntEnum,StrEnumandFlagstill run in CPython. Also:vars(SomeClass)works, and a class's attributes keep the order its body defines them in.- Exact fractions on the typed path.
fractions.Fractionin a typed build held only denominators that divide lcm(1..32)*10, so every Python file importing fractions went to the dynamic build. A Fraction is now a reduced numerator over a denominator (64 bits each, worked in 128-bit arithmetic), so unit tables, matrix elimination and exact ratios such as 1/86400 build typed and print what CPython prints, includingx.numerator,x.denominator,Fraction("3/4"),//,%,round,absandsum(xs, Fraction(0)). A value that needs more than 64 bits stops with an error naming the limit rather than rounding. - More plain Python stays typed. On the 813-program corpus (bench/typed_corpus.py: saved solutions and repros), 522 build typed, up from 496 on main (386 of the 580 solutions, up from 371); 37 moved onto the typed path and every one with tests prints what python3 prints, and 11 moved off it on purpose (the silent wrong outputs below).
~x,yield [x for ...],(only,) = xs,d.setdefault(k, {})[j] = v,re.sub(pattern, repl, s)withrepla function of the program, a nested function with default arguments, and calls inside a generator inside another generator (" ".join(fmt(x) for x in r) for r in m) now build typed. A re group that took no part in the match is None, as in CPython, som.group(2) is Nonestays typed. - Silent wrong output fixed: re groups and tuples. In typed builds
print(m.group(2))for a group that did not match printed an empty line instead ofNone, andprint(m.groups())showed['a', '', 'c']instead of('a', None, 'c'). A list filled withtuple(cur)by a helper function (out.append(tuple(cur))on a list passed in, or returned) printed its items as lists. All now match CPython (printingm.groups()and such lists goes to the dynamic build). - Silent wrong output fixed: rows changed in a loop.
for r in t[2]: r[1] = von rows held in a tuple (a table kept as(cols, order, rows)) changed copies of the rows in typed builds, so the update was lost; it now changes the rows themselves, and a loop that changes each row while also reading the rows' owner (if test(t, r): r[i] = v) builds over dynamic values. A program whose mixed lists ([1, 'a']) have the same item types as one of its real tuples printed those lists as tuples and copied them where Python shares them; it now builds over dynamic values too. Regex patterns also read\x00,\u00e9,\f,\vand\aescapes, which the typed build matched as letters. - Path text from
osandpathlibbuilds natively everywhere. A program whose only uses ofosandpathlibare path text (os.path.join,basename,dirname,splitext,normpath,os.sep,pathlib.PurePath) used Brevi's own copies of those functions only under--sandbox; outside it the program ran in CPython. It now builds natively in both. A program that also reaches files, processes or the environment (os.environ,os.listdir,pathlib.Path,os.path.exists) still runs those in CPython, as before. - Docs: the site has a Changelog page built from this file, and every diagram is a sharp SVG that opens full size on click; the architecture diagram shows the current build paths.
Prebuilt brevi compiler for Linux, macOS and Windows. brevi still needs rustc on the PATH to build programs. On Linux and macOS, sh install.sh installs rustc if it is missing and picks the right download; on Windows, install Rust with rustup and unzip the .zip. Details: https://brevi-lang.org/docs/install
Brevi v0.24.3
brevi check gives its verdict in well under a second, and checking or running an unchanged program again replays the cached result in about 10 ms: the median check of a new file went from 559 ms to 144 ms (60 of 65 programs under a second, up from 40), and a re-check from 60 ms to 8 ms median. Standard-library calls now run natively instead of in CPython (of 47 probe programs, 19 needed CPython before and 5 now), math and time are on the fast typed path (a math.sqrt/math.log loop over 20 million numbers takes 0.09 s, CPython 1.9 s), and two silent wrong outputs are fixed (re.findall with groups, reading a missing defaultdict key). No new release-test run; the v0.24.2 figures stand.
- Faster
check, and repeated calls are near-instant. Agents callbrevi checkandbrevi runover and over, so every call's wait counts. Three changes, measured on 65 programs (the release suite's saved repros and saved solutions; table in the pull request): (1)checkandrunmade all eight versions of the generated program up front, each a whole parse and type-inference pass, even when the first one built or was cached; now the others are made only when the ones before fail. (2) A new result cache:check,buildandrunof files that did not change replay the last result (the same output,{"path":...}line and binary) without parsing, inference or rustc, keyed on a hash of the program's files, the command and flags, theBREVI_*and Python settings, the Brevi binary and the toolchain. A re-check of an unchanged file went from 344 ms on average (up to 3.9 s) to 11 ms, a re-run from 379 ms (up to 4.7 s) to 18 ms. A refusal is replayed only for programs that import nothing an install could change. The MCP server gets this with no change. (3)checkof a file on the dynamic path runs rustc's checks instead of a full build (2.8 s to 0.5 s on one program). Type inference also copies its name tables less.--no-cache(orBREVI_NO_CACHE=1) builds from scratch,BREVI_CACHE_MBcaps the cache (default 1024 MB, least recently used out first; it was 400 entries), andBREVI_TIME=1prints how long each build stage took. Also fixed: a build into a folder that did not exist left its error in the build cache, so building again after creating the folder still failed. - Standard-library calls run natively, not in CPython. A Python file that the typed build could only finish by handing some standard-library calls to CPython (
math.gcd,itertools.product,statistics.mean,copy.deepcopy,datetime.datetime.strptime,functools.reduce,time.perf_counter) now takes the dynamic build, which has those modules natively: no libpython at run time, and it works under--sandbox. Libraries Brevi lacks (numpy, sqlite3) still run in CPython as before. Of 47 small standard-library probe programs, 19 needed CPython before and 5 now (argparse, textwrap, io, os and random; random and textwrap come next); in the 765-program corpus 11 programs moved from CPython to native, and every moved program with tests prints what python3 prints. Also fixed: one missing call such asmath.gcdsent everymath.call on its line to CPython,math.sqrtincluded. - Silent wrong output fixed:
re.findallwith groups. With two or more groupsre.findallreturns tuples ([('a', '1'), ('b', '2')]), but the typed build gave whole matches (['a1', 'b2']). Such calls now build over dynamic values; with no group or one group they stay typed. - Silent wrong output fixed: reading a missing defaultdict key. In Python
d[k]on a defaultdict puts the key in, solen(d), loops and printing see it; the typed build left the dict unchanged. A program that reads a defaultdict with a key that may be missing and also looks at its keys now builds over dynamic values (a key taken from a loop over the dict's keys, or frommin/maxof them, counts as found, so counting and grouping programs stay typed: 1 of the 765 corpus programs moved). mathandtimeon the fast typed path.math.gcd,lcm,comb,perm,factorial(big results grow past 64 bits as in Python),isclose,log(with or without a base),log2,log10,log1p,exp,expm1,sin,cos,tan,asin,acos,atan,atan2, the hyperbolic functions,hypot,fabs,trunc,degrees,radians,copysign,fmod,isnan,isinf,isfinite, the constantspi,e,tau,infandnan, andtime.time,perf_counterandmonotonicnow build typed, whether imported withimport mathorfrom math import gcd, pi. Domain errors are aValueErroranexceptcatches, as in CPython, andmath.sqrt(-1)raises instead of giving nan. A loop ofmath.sqrtandmath.logover 20 million numbers runs in 0.09 s (CPython: 1.9 s). Of the 47 probes,mathandtimeprograms moved from the dynamic build to the typed one; no program in the corpus left the typed path.- Docs: a What runs native table lists, module by module, which functions the typed build has, which run in the dynamic build, and which still need CPython.
Prebuilt brevi compiler for Linux, macOS and Windows. brevi still needs rustc on the PATH to build programs. On Linux and macOS, sh install.sh installs rustc if it is missing and picks the right download; on Windows, install Rust with rustup and unzip the .zip. Details: https://brevi-lang.org/docs/install
Brevi v0.24.2
Every build off the fast typed path now says which path it took and which function to rewrite, in one JSON line an agent can act on. More plain Python stays on the typed path: of 765 agent-written programs, 477 build typed, up from 429. random and path functions work in the sandbox, Python files can use # allow read as a comment, fallback errors are shorter (--traceback for the whole one), and printing tuple(xs) no longer shows a list. Release test on this build (one run): 1.09x Python's cost for Sonnet and 1.06x for Haiku, every task passed, 23 of 24 final programs native for each model.
- Every build says which path it took. For a Python file,
brevi run,buildandcheckprint one JSON line on stderr whenever the file is off the typed fast path:{"path":"dynamic","line":3,"fn":"show","why":"print() of a set shows CPython's hash order, ..."},{"path":"pybridge","why":"numpy run in CPython","py":["numpy"]}or{"path":"cpython","line":2,"fn":"g","why":"Brevi could not build it: unknown function eval()","by":"python3"}.line,fn(andfilein a module) name the code to rewrite to get the typed path back. The typed path stays silent;--explainprints it too and adds the fix, up to five more reasons and why the dynamic build refused a CPython run;BREVI_PATH=1prints the line for every build,BREVI_PATH=0never. The CPython fallback'snote: ran with python3; ...line is replaced by its{"path":"cpython"}line. bench/e2e.py logs each pyb task's final path and adds atypcolumn (tasks on the typed path). - More plain Python stays on the typed fast path. On a corpus of 765 agent-written programs (the saved benchmark solutions and past fallback repros), 429 built typed before and 477 now (372 of the 580 solutions, up from 326), none moved off the typed path, and every moved program with tests prints what python3 prints on them. Loops, comprehensions and prints of a set of strings (or of tuples led by one) build typed: CPython draws string hashes afresh each run, so no order is CPython's own (rustc checks the element type; sets of numbers still go to the dynamic build, which follows CPython's order). A comprehension over a set handed straight to
max,min,sorted,any,all,lenorsetno longer counts as an order-dependent use. A sentinelMISSING = object()used only to mark "no value" (d.get(k, MISSING),if old is MISSING:) is read as None when the program cannot tell the two apart (no other None value reaches it, no truth test on it). Also typed now:[[int(x) for x in row] for row in rows]and other comprehensions inside comprehensions that can fail,d.get(k, {})andd.get(k, [])on JSON objects,t[:2]andt[1:]on tuples, a lambda set inside a loop or if body, anddatetime.date(...)in a function with a local nameddate. - Silent wrong output fixed:
print(tuple(xs)). A tuple made withtuple(...)from a list or iterator is held as a list by the typed build, so printing it (or a list of them) showed[1, 2]instead of(1, 2). Such programs now build over dynamic values. - random, path text and
# allowwork in the sandbox. Under--sandboxand in the MCP tools (sandboxed by default),import random; random.seed(1)gave two confusing errors (unknown method .seed()andunknown name 'random'). random now runs natively there from Brevi's own copy of CPython's generator, so a seeded program draws the same numbers as CPython (seed,random,randint,randrange,choice,choices,shuffle,sample,uniform,gauss,getrandbits,Random); any other random function gives one error naming it and saying it needsallow native. Path text no longer needsallow nativeeither:os.path.join,basename,dirname,split,splitext,normpath,isabs,os.sepandpathlib.PurePath(POSIX rules) run without CPython, while every call that reaches files, processes or the environment (os.listdir,os.environ,os.system,pathlib.Path,os.path.exists,ospassed as a value) is still refused, now by name. New tests in tests/sandbox show each of those stays blocked, and that Brevi's own PurePath has no file access at all. In Python files the allow line can be a comment,# allow read, so the file stays valid Python (the bareallow readstill works and stays the form for.bvfiles), and fixes suggest the comment form. - Shorter fallback output. When
brevi runfalls back to CPython on a sure mistake, it printed the note and then CPython's whole traceback, so the error was read twice. It now prints the note and only the traceback's last line, with where it was raised (NameError: name 'totl' is not defined (main.py line 3)).--tracebackorBREVI_TRACEBACK=1shows the whole traceback; other fallbacks are unchanged.
Prebuilt brevi compiler for Linux, macOS and Windows. brevi still needs rustc on the PATH to build programs. On Linux and macOS, sh install.sh installs rustc if it is missing and picks the right download; on Windows, install Rust with rustup and unzip the .zip. Details: https://brevi-lang.org/docs/install
Brevi v0.24.1
A hardening release before the public preview. brevi run --sandbox can no longer be bypassed, and the MCP server survives programs that never end or flood their output. Plain Python comes first everywhere: the MCP server builds code as main.py, and brevi check, build and run report sure Python mistakes (an undefined name, a method no type has) instead of building them. Dataclasses and string escapes print as in CPython, programs killed by a signal say so, and the npm wrapper is now named brevi, as on crates.io and PyPI. The README and docs were rewritten for first-time visitors, and brevi-lang.org has a documentation section.
- npm wrapper renamed to
brevi. The npm package is nowbrevi(npx brevi mcp,npm install -g brevi), the same name as on crates.io and PyPI; it wasbrevi-lang. Nothing is published yet. - Newcomer pass before the public preview. README rewritten for a first-time visitor: install, a quick start in plain Python, agents, how it works and the v0.24 status, with the v0.8 to v0.23 result tables moved to bench/HISTORY.md. The site and docs now describe the v0.24 behaviour: sure mistakes in Python files (unknown names and methods, text plus a number) are reported by check and build and named in run's fallback note, a new small program builds in well under a second after a one-time runtime build, and the release-test figures match the v0.24.0 release notes. The skill allows installed packages, release notes give the Windows install step, and examples/top.py is a first Python example.
- Sandbox fixes (security).
brevi run --sandbox x.pycould be bypassed in three ways. A program refused for an undeclaredread()was then run with python3, which read the file anyway (the inner build was also started without--sandbox). Python libraries that CPython runs inside the program (import os,import subprocess, and evensys.modules["os"]through Brevi's ownsys) were not checked at all. And files that go to the dynamic build skipped the check. Now the capability check runs first and its refusal is final on every path. Python libraries needallow native(a Python value reaches everything CPython can, so there is no safe subset), and a program held to its allow lines (--sandboxor anallowline) never falls back to python3. Dynamic builds have no file, network or library access, so a program that passes the check may still build that way; a unit test keeps it so. - Dataclasses print as in Python. In typed builds
print(P(1))for@dataclass class P: x: int; y: int = 0printedP_D { x: 1, y: 0 }instead ofP(x=1, y=0), also inside lists, dicts and tuples. Dataclasses now print CPython's generated repr (fields in order,field(repr=False)left out), a class with__str__still shows its__repr__(or the dataclass one) inside containers and forrepr()and!r, and a class with neither prints<__main__.C object at 0x...>. Dynamic builds also leavefield(repr=False)fields out. - Python first everywhere. The MCP server builds code as plain Python by default (kept as
main.py, built likebrevi run main.py); before, it kept every program asmain.bv, so plain Python failed there (type(it).__name__gaveunknown function ax_type_of(), a class method call gaveunknown method .speak(), andfile: "main.py"on a fresh server said "no program yet"). Brevi code is still recognised by itsfnitems or a.bvfile name.patchreplaces Pythondefs and classes by name (decorators included) and adds new ones after the file's last def. The server's instructions now say to write plain Python and the plain solution, instead of reading the guide first.brevi --helplistsFILE.pyfirst. - Python mistakes are reported, not built.
brevi check,buildandrunon a.pyfile exited 0 on mistakes such asprint(totl(nums))(a name defined nowhere),x.uppr()(a method no Python type has) or"n=" + len(nums): the typed build refused them, the dynamic build accepted them, and the error only showed when that line ran.checkandbuildnow stop with the error and a hint (name 'totl' is not defined,did you mean total?);runruns the file with python3 and names the mistake in its note, so a mistake in a branch that never runs costs nothing. Only mistakes CPython would also stop on are reported this way: a name bound anywhere in the file, a method any built-in type or common module has, a file that catches the error itself, and lines that may never run (under asys.version_info,sys.platformorTYPE_CHECKINGtest, anif False:orx if False else y, or an except block) still build as before (0 of 1,152 saved Python files are refused). No message names Brevi's internal helpers (ax_...) any more. - The MCP server is hardened for the public preview. A program that never ends used to hang the whole server for good: the 20 s limit stopped
brevi, but the program it started kept running and holding the output pipe. Every call now runs in its own process group, and a timeout, a cancel or the client leaving stops everything it started (on Linux children also die with the server). Output is read while the program runs, so a program printing more than 64 KB no longer always timed out; long output is cut in the middle (first 16 KB and last 4 KB kept). The JSON reader is now strict and complete: emoji and other characters sent as\ud83d\ude00pairs (Python's defaultjson.dumps) were dropped, deeply nested input crashed the server, and a line that was not UTF-8 ended it. Batches, non-objects and bad ids get JSON-RPC errors, an unknown tool is a -32602 error, a misspelt or mistyped argument (stdn,timeout: "abc") is reported to the model instead of ignored, and a bad file name (../x.py) no longer breaks every later call. New:notifications/cancelledstops the named call, ping and tools/list are answered while a call runs, protocol versions are negotiated (newest 2025-11-25), tool titles and read-only hints,timeoutonrunandtest,brevi mcp --timeout S --mem-mb MB(Linux: a program gets at most half the machine's memory by default),runwithcasesfalls back to CPython likerundoes (sandbox off), and the work folder and its build folders are removed when the client disconnects (folders left by crashed servers are removed at start). The first call after an install or upgrade timed out while Brevi built its runtime libraries (about 20 s), and kept timing out, since each try was stopped before the build finished; the server now builds them in the background as soon as it starts. When the client's PATH leaves out~/.cargo/bin(Claude Desktop on macOS), the server finds rustc there; when rustc is missing the reply says how to install it. Tested with the official MCP Python SDK and with Claude Code, and tests/mcp_test.py covers each case. - Python string escapes in native builds.
"\x41","\u00e9","\U0001F600", octal"\101"and\a \b \f \vprinted their backslash text instead of the character, andrepr()of a text with control characters showed them raw instead of as\x07; both now match CPython. - Killed programs say so. A program stopped by a signal (out of memory, a crash) exited
brevi runwith 1 and no message; it now prints one JSON line ({"e":"signal",...}) on stderr and exits 128 + the signal.BREVI_MEM_MBlimits a program's memory on Linux. - Docs and new websites. brevi-lang.org has a documentation section (
/docs/): install, using Brevi (commands, flags, errors, CPython fallback, sandbox, GUI test mode), for AI agents (MCP, skill, plugin, llms.txt, the plain-solution advice), architecture with its diagrams, what works and limits, benchmarks, the Brevi language guide, FAQ, roadmap and contributing. Every page is also served as Markdown (/docs/NAME.md). Both sites share one stylesheet, work on phones, and switch their GitHub links and install command on once the repository is public.site/gen.pybuilds the pages fromsite/docs/*.mdand the repo's own docs;bash site/deploy.sh --previewdeploys to workers.dev for review.
Prebuilt brevi compiler for Linux, macOS and Windows. brevi still needs rustc on the PATH to build programs. On Linux and macOS, sh install.sh installs rustc if it is missing and picks the right download; on Windows, install Rust with rustup and unzip the .zip. Details: https://brevi-lang.org/docs/install
Brevi v0.24.0
Agents now pay less with Brevi than with Python on heavy work. Told that their Python is compiled to native code, models write the plain solution instead of hand-optimising it; on four tasks with a 10 s limit the whole job cost 0.58x Python's with Sonnet and 0.53x with Haiku, every program built natively and no check failed because of Brevi. On the 24-task release suite, ordinary work, the bar is still met (1.07x Sonnet, 1.00x Haiku). Plain generators and string indexing now build typed and fast, first builds take about 35% less time, and seven bugs found by the new shadow check (each pyb check also runs CPython) are fixed.
- Write the plain version. The skill, the editor rules, the MCP server's text and llms.txt now tell agents that their Python is compiled to native code, which runs plain loops 10 to 50 times faster than CPython, so they should write the plain, direct solution and not hand-optimise it. In a test on the four
speedtasks, this one line cut output tokens by 50% for Sonnet and 28% for Haiku, and cost by 44% and 25%. Every plain program then ran natively within the 10 s limit, where CPython needs 29 to 59 s. bench/e2e.py adds the line to the pyb prompt (--no-hintleaves it out). - Plain idioms build typed and fast. An endless generator read only with
next()(a seeded random number source:def rng(seed): x = seed; while True: ...; yield x) used to send the whole program to the dynamic build. It is now rewritten into a small class, so the program builds typed. The speed set's plain reference programs go from 24 to 29 s to 1.8 to 2.5 s for knapsack, and from over 60 s to 2.3 s for Game of Life (CPython: 25 s and 30 s). Indexing a long text (a[i],b[j - 1]) scanned the whole text each time to check for ASCII; the answer is now kept until the text is freed, so a 10,000-letter edit distance takes 1.8 s instead of 15 s. - Faster cold builds. A build without
--release(whatbrevi runuses) of a program it has not seen takes about 35% less time: the 12 speed programs inbench/comparewent from 405 ms to 257 ms each on average on a 4-core machine (a hello world from 186 ms to 148 ms), and they run as fast as before (all 12 still beat CPython; all 591 saved solutions still match it). Brevi now calls rustc directly instead of through the rustup proxy (the proxy's choice of toolchain is remembered until PATH, rustup's settings or a rust-toolchain file change), uses one codegen unit per core (4 to 16) instead of rustc's 16, looks up rustc while the front end works, and no longer compiles a program twice when the build cache is off (BREVI_NO_CACHE=1, whichbench/compareuses, so its build times there were doubled). - Speed and correctness on fast-Python idioms (found by the new time-limited bench set, where models write optimised Python first). Typed builds: a row taken from a grid (
nrow = new[r]) and changed cell by cell was copied back into the grid after every cell, so a 600x600 Game of Life took 16 s; it is now written back once after the loop when nothing in the loop reads the grid (2.6 s; CPython 30 s). Small runtime helpers (indexing,%,//,+) are inlined into default builds.min(a, b, c)andmaxof three or more values, and built-ins passed tomap(map(sum, rows),map(abs, xs)), build typed instead of falling to the slower dynamic build. Programs that pack many fields into one int (2 ** (F * K),x << (F * w)) build over dynamic values instead of overflowing 64-bit ints. Dynamic builds:&,|and^on ints past 128 bits work (they raised NotImplementedError);bin,oct,hexandint(s, 2)/int(s, 16)on big ints are linear (a 360,000-bitbin()took 11 s);2 ** nis a shift (a bit-packed knapsack went from 20 s to 3 s). Big-int arithmetic borrows its operands instead of copying them (another bit-packed knapsack: 10.5 s to 7.1 s; CPython 6.2 s). - Silent wrong answers fixed.
out.extend(xs)orout += xson a list parameter did not change the caller's list (found by the e2e shadow check in a Haiku poker solution, where it cost 7 checks and the task). A list parameter changed and also returned (xs.append(x); return xs) was not changed for the caller. In dynamic builds,x >> nwith n over about a million bits shifted by a million instead. From the first release-suite runs with the shadow check (19 checks that failed only because of Brevi): a function givingNoneon one path and a list or dict on another (parse(tokens)returning None for bad input) read an empty list as None, so empty input was reported as invalid (such results are now dynamic values; the "may be None or empty" build error is gone).isinstance(r, tuple)was false for a tuple held as a dynamic value.all(c in digits for c in s)andany(...)over a text held as a dynamic value walked nothing, and[c for c in s]over a text gave the text instead of its characters. A list slot set by a nested function, in a list that held an int and None (found = [0, None]), was set on a copy (programs like this now build over dynamic values). Programs usingfractionsbuild over dynamic values, where a Fraction takes any denominator (unit tables stopped with "can't be held exactly"). Dynamic builds adddatetime.time(dt.time(),time(9, 30),datetime.combine(d, t)) andDecimal.scaleb. - Bench. e2e.py's
pybcheck also runs CPython on the same files and logs the pair per check (shadow); the summary'sbugcolumn counts checks that failed only because of Brevi. New setspeed(bench/ref/gen_speed.py): Game of Life, knapsack, edit distance and Collatz chains, small inputs and tens of millions of steps under the 10 s test limit. bench/replay.py replays saved conversations from their first failed check with different feedback (shorter diff feedback made retries 14% cheaper, about 2% of a whole run).
Prebuilt brevi compiler for Linux, macOS and Windows. brevi still needs rustc on the PATH to build programs; sh install.sh fetches it and picks the right download.
Brevi v0.23.0
Closes the last big-int gaps from v0.22.0: // and % now work on big integers, and growing numbers shared between functions (closures, nonlocal, global) grow as in Python. bench/compare still matches CPython on all 591 saved agent programs, 13 of 13 traps and 18 of 18 large programs. brevi build x.py --standalone now puts CPython inside the binary, so a program that uses Python libraries runs on a machine without Python. A new architecture doc explains how a file becomes a native program.
- Standalone builds of programs that use Python libraries.
brevi build x.py --standaloneputs CPython inside the binary: libpython, the shared libraries it and the extension modules need (the C library aside), the standard library, and the installed packages the program imports with everything they require. The binary runs on a Linux machine with no Python installed. It unpacks once into~/.cache/brevi/py(or$BREVI_PY_CACHE), about 1 to 3 seconds on first run, then starts as fast as before. Results stay CPython's own: numpy, pandas, sqlite3/hashlib/zlib, BeautifulSoup with lxml, Pillow, Jinja2 and openpyxl programs, plus one reading argv and stdin, print exactly what python3 prints when run in an empty root holding only the C library. Sizes: about 13 MB with only the standard library, 31 MB with numpy, 42 MB with pandas. Linux only for now. from PIL import Image(a from-import of a submodule the package does not import itself) failed with "module 'PIL' has no attribute 'Image'" in every build that calls Python libraries; it now imports the submodule, as Python does.- The last big-int gaps closed.
//and%work on big integers with Python's floor and sign rules. Examples:fact(50) % MOD, a digit-sum loops += x % 10; x //= 10over a 40-digit number, and-h // 7. Top-level int constants such asMOD = 1000000007join big arithmetic. A growing number shared between functions builds over dynamic values, where it grows as in Python. This covers a closure reading it,nonlocal acc; acc *= v,global total; total = total * 7 + k, and a top-level hash that a function prints. Before, these stopped with an overflow error. - Architecture doc. docs/ARCHITECTURE.md explains, with diagrams, what Brevi does between a
.bvor.pyfile and a native program: front end, typed build, the guard that sends Python to the dynamic build,src/dynpy, library calls through pybridge, rustc and the build cache, and the CPython fallback.
Prebuilt brevi compiler for Linux, macOS and Windows. brevi still needs rustc on the PATH to build programs; sh install.sh fetches it and picks the right download.
Brevi v0.22.0
A bug hunt and a head-to-head with Codon and Nuitka. Four rounds of bug hunting fixed the silent wrong answers they turned up (slices with negative steps, list aliasing on the first build attempt, catchable errors, copy.copy, and more), and the comparison found six more bugs, which are also fixed. On the same unchanged Python files, Brevi builds all 591 saved agent programs and all 18 large programs, and every one prints exactly what CPython prints; Codon builds 206 of the 591 and gets 36 of those wrong. Builds are faster by default: brevi run and brevi build without --release beat CPython on all 12 speed programs (1.7x to 40x). With --release, Brevi is level with Codon where Codon builds, and it also runs the object-heavy programs Codon refuses. Sets and deques in typed builds now give constant-time lookups and popleft(). Ints that grow past 64 bits, including values returned from functions (factorials, Fibonacci, recursion), become big ints as in Python. A growing value captured by a closure or nonlocal still stops with an overflow error.
-
Bug hunt, part 1: silent wrong answers fixed (each printed something other than CPython, with no error). Typed build: negative-step slices (
xs[::-2],xs[-1:-3:-1],s[4:0:-1]gave the wrong items or none; anyxs[a:b:k]now picks as Python does, also on text);zs = xsfollowed byxs.sort(),xs.extend(...),xs += [...]ordel xs[a:b]left zs unchanged (now built over dynamic values, where both names share the list); format specs with a fill character or theb,o,g,e,#,_,=and nested{x:{w}.2f}forms ('{:-^5}'.format('a')printeda); f-strings{x!r},{x=},{ x }and{1e16:g}; a plain (not f-) string such as"{x}"was filled in from a variable named x; a list mixing ints and floats printed1000.0as1000. Python files that print or walk a set (the typed build keeps a set in sorted order, CPython walks its hash table:{-1, 5}prints{5, -1}, and a set inside a dict printed as a list), or that change a value inside the print call showing it (print(q, q.popleft())), are now built over dynamic values. Dynamic build:raise ValueError("msg")(any built-in exception) lost its message and args;str.centerpadded odd widths on the wrong side; a set literal of 3 or more constants printed in a different order than CPython. 11 new parity probes cover these. -
Bug hunt, part 2. Silent wrong answers: the checks that make Python names share a list (
zs = xs; xs.sort(); print(zs)) were skipped on the first build attempt of every Python file, so most such programs printed an unchanged copy (they now build over dynamic values, where both names share the list); a change through one name followed by setting it anew (xs.sort(); xs = [...]; print(zs)) was also missed.'-'.join("xyz")printed-(nowx-y-z).-0.0 % 5.0printed-0.0(Python's zero remainder takes the divisor's sign), and float or JSON-number%and//by zero gave nan instead of ZeroDivisionError. In a Python file, an unknown method was silently renamed to a near one (s.rpartitionran ass.partition); Python files are never auto-fixed now. Printing arematch showed its groups (['12'],[]) instead of<re.Match object; ...>or None (such files now build over dynamic values). Speed: in dynamic builds everys.add(x),len(s)ands |= tcopied the whole set, so a 40,000-item BFS took 34 seconds; it now takes 0.09 s, as CPython does, and-=,&=and issuperset no longer slow down with the set's size. -
Bug hunt, part 3 (exceptions, input, numbers). Python files with
except ZeroDivisionError,IndexError,KeyError,LookupError,ArithmeticError,AssertionError,EOFError,OverflowErrororRecursionErrorbuild over dynamic values; the typed build stopped at those errors instead of letting the except catch them. Silent wrong answers fixed: a dict or set changed in a loop over it went on (CPython raises RuntimeError; such files build over dynamic values);2.0 ** 5000printed inf (OverflowError),0.0 ** -1and(-8) ** 0.5printed inf/nan (ZeroDivisionError, and a refusal since Python gives a complex number); a huge int power wrapped its exponent.input()at the end of the input raises EOFError (it returned "").int("1_000")andfloat("1_0.5")read as in Python.copy.copy(rows)shares the inner lists, as in Python (such files build over dynamic values; the typed build copied them). Dynamic builds: unbounded recursion raises RecursionError (it crashed with a stack overflow), andsys.setrecursionlimitstill allows deep recursion. -
Fast sets in typed builds. Typed builds kept a Python set as a plain list, so every
x in sscanned it: a 40,000-node BFS took several seconds and a 400x400 grid BFS with a walls set ran for over two minutes (CPython: 0.03 s and 0.4 s). A list or set that is searched withinand otherwise only added to, popped, counted, sorted or walked (a function's ownseen, or a parameter used that way) is now kept with a hash index, sointakes constant time; the items stay in the same order, so output does not change. The grid BFS now takes 0.8 s and the 40,000-node BFS 0.2 s (what remains isdeque.popleft(), still a list shift in typed builds). Set&and-sort one side once instead of scanning it for every item (15,000-item sets: 2.8 s to under 0.1 s), andwhile q:no longer copies the whole list on every test. tests/typed checks these against CPython with a time limit. -
Fast deques and object sets in typed builds. A
deque(or a list taken from the front withpop(0)) that is otherwise only added to, popped, read by index or walked is kept as a ring buffer, sopopleft()takes constant time instead of moving every item (40,000-node BFS: 0.20 s to 0.13 s; the 400x400 grid BFS takes 0.21 s with--release, CPython 0.43 s). Sets stored on objects (self.seen = set(), thenx in self.seen) get the same hash index as local sets when every use of that field is a set use. -
Big ints returned from functions. A function whose result outgrows 64 bits returns a big integer, as in Python. This covers a loop that multiplies (
f *= i ... return f), Fibonacci and Tribonacci sums, and recursion such asreturn n * fact(n - 1)orfib(n - 1) + fib(n - 2). These calls stopped with an overflow error before. Where such a call meets other big values or is printed, it stays big. Where an int is needed (an index, a count,//or%), it becomes an int, and raises OverflowError only if it does not fit. A function's locals no longer have to stay small ints just because another function uses the same name. -
Still open: a growing value captured by a nested function (a closure or
nonlocal) stops with an overflow error. -
Fast by default, and the Codon/Nuitka gaps closed.
brevi runandbrevi buildwithout--releasenow compile the program at opt-level 1 against a fully optimised runtime, so the default build beats CPython on all 12 speed programs inbench/compare(it was slower on 5): trees 9.8 s to 0.71 s (CPython 5.2 s), strings 5.6 s to 1.13 s (2.4 s), records 2.5 s to 0.38 s (0.71 s), nbody 1.4 s to 0.12 s; a cold build takes about 0.3 s longer. Typed programs get a small-object allocator (per-thread free lists for blocks up to 256 bytes, as CPython's pymalloc does), about 25% faster on string and dict work, plus quicker one-charactersplitand{i:06d}formatting. List slice assignment (xs[a:b] = ys,perm[:k+1] = perm[k::-1]) builds typed, so fannkuch runs at 3.5x CPython instead of 0.4x. Ints that grow past 64 bits in Python (a factorialf *= i, a rolling hashh = h * 31 + ord(c), Fibonaccia, b = b, a + b, big literals) become big ints,3 ** 200 % mis worked out exactly, and a literal power past 64 bits builds over dynamic values; these stopped with an overflow error before (a value returned from a function still does). Generators work in dynamic builds and run lazily, step by step with their caller as in CPython (endless generators,send(),yield fromwith a return value,StopIteration.value, generators in classes and closures); the typed build, which runs a generator to the end at its first use, keeps only generators where that cannot show (no prints, no reads of outside state, no while loops), and a yield inside try/finally or with is refused at build time.csv.DictReaderis native (rows are dicts of str, sod[r["dept"]]no longer guesses int keys),__file__is the file's absolute path, and thesys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))line needs no CPython.csv.reader(f, delimiter=";")is no longer read with commas. The chess huge-set program with generators and both__file__programs now build natively and match CPython. -
Brevi vs Codon vs Nuitka (
bench/compare/): the same unchanged Python files built by Brevi, Codon 0.20.6 and Nuitka 4.3.4, checked against CPython's exact output and exit status, with build and run times. Of 591 saved agent solutions, Brevi builds 589 and matches CPython on 588, Nuitka 591 and 591, Codon 206 and 170 (16 of its builds print a different answer with no error). On typed code Brevi--releaseruns 2.7x to 47x faster than CPython on 9 of 12 speed programs, level with Codon; Nuitka stays near CPython. It found four silent wrong answers in Brevi, now fixed:book = asks if buy else bidsfollowed bybook.remove(o)changed a copy (such files now build over dynamic values);m.group(3) is Nonewas False for a group that did not match (likewise); an f-string value starting with a number (f"{1/3:.3f}",f"{2*n}") printed as literal text;raise SystemExit(3)exited with 0 (dynamic build) or 1 (typed build) instead of 3. Also fixed two wrong refusals:d.setdefault(k, set()).add(x)stopped with KeyError, and10 ** -2stopped with an error instead of giving 0.01.
Prebuilt brevi compiler for Linux, macOS and Windows. brevi still needs rus...