Changelog
October 2026
v0.33.1
Added
- Add
verifytorequests:Falseaccepts any certificate, a path trusts a CA bundle - Add
scriptling.oauthfor OAuth 2.0 sign-in, tokens and token verification (docs)
v0.33.0
This release hardens scriptling against hostile scripts and input; read the breaking changes before upgrading.
Changed
- Breaking: Stop scripts catching timeouts, cancellations and the memory-limit stop, and stop
withblocks (includingcontextlib.suppress) suppressing them,SystemExitor jailPermissionErrors - Breaking: Refuse script-driven plugin loads and stdio MCP servers where the
subprocesslibrary is disabled;--plugin,--plugin-dirand HTTP(S) plugins are unaffected - Breaking: Enforce the network policy on
net.unicast,net.gossip(TCP only),nomad,wait_for.port, script-suppliedcontainerendpoints and every request the Slack, Discord and Telegram libraries make, their platforms’ own hosts included; under a strictallow_hosts, list the platform hosts you use - Breaking: Apply
deny_cidrsand the link-local block toallow_hostsentries, and requireallow_cidrsformulticastgroups under a policy - Breaking: Confine
runtime.http.staticandruntime.kv.opento--allowed-paths, hiding dotfiles and directory listings when restricted - Breaking: Confine
wait_forfile,dirandfile_contentchecks, messagingsend_filelocal files,provision.fetchpaths andcontainer.run()bind mounts to--allowed-paths; jail denials raise uncatchablePermissionErrors,provision.file’s included (they were catchable errors), andnetwork="host"is refused under a network policy - Breaking: Check the
Hostheader on every endpoint of a server bound to loopback (127.0.0.1,[::1],localhost), blocking DNS rebinding against local servers, and answer any name but a loopback one with421. A server bound to any other address (:8000,0.0.0.0) still answers everyHostuntil the new--allowed-hostsis set; once set, it answers loopback names, IP addresses, the machine’s own host name and the listed names, refusing the rest with421(docs) - Breaking: Require
--bearer-tokenfor--mcp-exec-scriptover HTTP, and answer browser requests to/json-rpcfrom other origins with403; allowlist browser apps with--json-rpc-cors-origin - Breaking: Limit JSON-RPC servers, and plugin servers over HTTP, to batches of 1,000 elements and 4 × CPUs handlers at once; change with
--json-rpc-max-batchand--json-rpc-concurrency - Breaking: Refuse
image_urlvalues other thandata:URIs for the Ollama provider instead of fetching them from the host outside the network policy - Breaking: Match Python 3.14 in
html.parser: valueless attributes areNone, CDATA goes tounknown_decl, and handler exceptions,sys.exitand timeouts propagate out offeed(); parsing is about 40% faster - Breaking: Return Go panics from
Evaland every other entry point (CallFunction,CallMethod,CreateInstance,ImportandLoadLibraryIntoEnv) as a*scriptling.PanicErrorreported asscript panic: <value>, with the Go stack trace in itsStackfield;Eval’s message used to include the stack, and the other entry points did not recover panics - Breaking: Raise
RecursionErrorfrommsgpack.packb,yaml.dump,toml.dumpsandtoon.encodefor values nested more than 10,000 levels instead of truncating them, and reject TOML nested more than 100 levels, intoml.loadsand in network policy and secret config files, script metadata, package manifests and MCP tool metadata - Breaking: Raise
RecursionErrorpast the call-depth limit instead of a plainException(except Exceptionstill catches it),TypeErrorfrommin()andmax()for unorderable values, andValueErrorfor a negativecollections.dequemaxlen, as Python does - Breaking: Run the Apple
containerCLI from/usr/local/bin/containerinstead ofPATH, refuse option values that start with-, pass script-supplied names, IDs and images after--, and listruntimes()without starting any process - Treat container elements that are the same object as equal, as Python does, so a NaN held twice is found by
count()andindex() - Enforce memory limits mid-run and count Go-built lists, tuples, itertools and random results and
lru_cachegrowth towards them - Resolve
--allowed-pathsthe way the kernel opens paths so dangling and parent-escaping symlinks cannot leave them, and confine library-loader symlinks to library dirs and allowed paths - Send messaging bot tokens only to their platform’s hosts
- Answer a panic in an HTTP handler with a 500 and log panic stack traces at debug level
Added
- Add
--max-background-tasksto cap an interpreter’s queued or running background tasks at once (envSCRIPTLING_MAX_BACKGROUND_TASKS, configlimits.max_background_tasks); off by default. Go hosts usescriptling.SetMaxBackgroundTasks,WithMaxBackgroundTasksorp.SetMaxBackgroundTasks, andResourceUsage.BackgroundTaskscounts the live tasks - Add
--container-deny-privilegedto refuse privileged containers (env and config forms too); off by default - Complete
collections.dequeas in Python: a real class (isinstance()and subclassing work) withinsert(),remove(),index(),reverse(), item assignment and deletion, comparisons,+and*,reversed()and a read-onlymaxlen - Add reflected and in-place operator methods (
__radd__,__iadd__and the rest), and__reversed__forreversed(); as in Python,str % objformatsobjand never callsobj.__rmod__ - Add
object.Equal,object.Compareandobject.SetCompareHooksso Go-backed classes compare their elements as scripts do - Add
netsecuritypolicy guards (Guard.CheckHost,Guard.Dial,Guard.ResolveIPs,ErrPolicy) andfssecuritypath checks (Config.CheckPath,DeniedError,ErrPathNotAllowed) - Add Go-integration counterparts to the new restrictions:
extlibs.RegisterWaitForLibraryConfigured, messagingRegisterConfigured/NewLibraryConfigured, a network policy argument tounicast.Register,multicast.Register,gossip.Register,nomad.Register,telegram.Register,discord.Registerandslack.Register,scriptlingmcp.RegisterConfigured,plugin.RegisterLibrariesConfiguredandplugin.ProcessesAllowed,container.RegisterWithOptions,container.Options(withDenyPrivilegedandAppleCLI) andcontainer.RegisterConfigured,libloader.WithAllowedPathsandMultiFilesystemLoader.RestrictTo,Scriptling.SetPanicHandler,conversion.ToGoChecked, theServerConfigfields for the new server options, andplugin.Server.SetHTTPLimits(docs)
Fixed
- Fix whole-process crashes from deeply nested or self-referential values: comparing or
repr()of over-deep values, cyclic containers, and generators that resume generators now raiseRecursionError;xml.loads()andxml.dumps()past 1,000 levels raise an error; and over-deep source is a parse error - Fix
html.parserpanics on<!-->,<!--->,<?>and&; - Fix zip web roots serving members with absolute or
..names - Fix
ZipFile/TarFileextract()andextractall()writing members of any size under a value limit; an oversized member now raisesMemoryErrorand leaves nothing on disk - Fix network-policy host matching for trailing-dot hosts, bare domains under suffix rules and non-canonical numeric hosts
- Fix the Gemini provider exposing its API key in
models()andembedding()error messages - Fix
scriptling.airesponses andai.collect_stream()bypassing the interpreter’s value and memory limits, and error messages repeating the provider’s prefix - Fix
runtime.backgroundtasks escaping the memory limit; each live task is now chargedobject.TaskBytes(48 KiB) against it - Fix
collections.dequeignoring the memory and value limits,appendleft()andextendleft()copying the whole deque on every element, andstr()showing string elements unquoted - Fix
setattr()on an instance bypassing property setters, read-only properties and frozen dataclasses - Fix an exception raised inside a
+chain that starts with a variable reaching the caller asTypeErrorinstead (since 0.20.0) - Fix
scriptling helpignoring--no-subprocess, so a module it imported to document could run processes - Fix
scriptling.plugin.load()registering a plugin over a library already registered under the same name, or over a module the library loader supplies from disk, letting a script replace a host library; it now raisesload(): library <name> is already registered, and a--pluginor--plugin-dirplugin under such a name is skipped with a warning - Fix
os.symlink()under--allowed-pathscreating a link whose target lies outside them; the target is resolved from the link’s directory and refused with aPermissionError
v0.32.0
Breaking
- Return Python’s type names from
type(x)andx.type():"int","str","bool","NoneType","list","dict","bytes","function","method","builtin_function_or_method","module","type","FloatArray", and for iterators"range","map","filter","zip","enumerate","list_iterator","count"and so on. The old names ("INTEGER","STRING","NULL","ITERATOR", …) no longer match, sotype(x) == "INTEGER"is nowFalse; compare withint,"int"orisinstance()instead (docs) - Match
isinstance()type-name strings exactly astype()reports them:isinstance(x, "int")andisinstance(None, "NoneType")work, but"INTEGER","Int","None"and a class name in the wrong case no longer match
Fixed
- Fix
type(x).__name__returning scriptling’s upper-case name ("INTEGER") instead of Python’s ("int") - Fix
type()reporting a module as"DICT", a class andtype(int)as"CLASS"/"BUILTIN"instead of"type", and a bound method as"FUNCTION"instead of"method" - Fix
"INTEGER" == intand"STR" == strcomparing equal
v0.31.0
Changed
- Breaking: apply
scriptling.SetMemoryLimitto each interpreter separately instead of the whole process (docs) - Apply the call-depth limit to
CallFunction,CallMethodandCreateInstance - Run
requests.parallelon a fixed pool ofmax_parallelworkers - Stream files into zip and tar archives instead of reading each into memory
- Build
scriptling.grep,scriptling.sed,scriptling.find,os.listdirandcsv.loadsresults under the interpreter’s limits - Refuse
json.dumpsof a value nested more than 10,000 levels deep - Force at most one garbage collection per 100 ms for an interpreter over its memory limit
Added
- Add per-interpreter memory and value limits, off unless set, and the
MemoryErrorexception (docs) - Add
WithMemoryLimitandWithMaxValueBytesoptions toscriptling.New, andp.SetMemoryLimitandp.SetMaxValueBytes - Add
p.ResourceUsage()andp.ShareResourceGuard() - Add
--memory-limitand--max-value-bytesCLI options andlimits.*inscriptling.toml(docs) - Add
objecthelpers for libraries that create values from script input:CheckAlloc,CheckElements,CheckRoom,CheckValue,TrackSlice,TrackMap,ReadAllLimited,ReadFileLimited,BindGuard,CurrentGuardandRunFromContext(docs)
Removed
- Breaking: remove
scriptling.GetMemoryLimitStats,MemoryLimitStatsandDefaultMemoryLimitCheckInterval; usep.ResourceUsage()
Fixed
- Fix comprehensions over a huge
rangeandf(*iterator)ignoring timeouts and allocating their whole result up front - Fix
secrets.randbits(k)for k over 63 returning a truncated value; it now raisesOverflowError - Fix
str.expandtabs(0)dividing by zero - Fix a huge padding or format width, such as
"x".ljust(10**12), attempting the allocation; it now raisesMemoryError - Fix
requests.parallelreturning an error object instead of a status 0Responsefor a request that failed before it was sent - Fix the call-depth counter staying incremented after a call refused for exceeding the limit
- Fix the CLI server starting with its error lost when the setup script fails as the server is signalled to start
- Fix the CLI server resetting its runtime while a previous server’s WebSocket handler was still running
v0.30.4
Changed
- Read 1Password Connect secrets in one request instead of three after an item’s first lookup, by remembering vault and item ids for 10 minutes
Added
- Add 1Password service accounts to the
onepasswordsecret provider through the separate scriptling-onepassword extension: giveservice_account_tokeninstead of a Connectaddressandtoken; the released CLI supports Connect only (docs) - Add the
scriptlingcmdpackage, so a custom CLI with extensions compiled in is amain.gocallingscriptlingcmd.Main()(docs) - Add the
scriptling-onepasswordandscriptling-slim-onepasswordbuilds, with 1Password service accounts compiled in, to the release and Homebrew (docs)
v0.30.3
Breaking
- Pass everything after the script file to the script in
sys.argv, flags included, soscriptling tool.py input.json --verboseworks without--; give scriptling’s own options before the script file. A--straight after the file is still accepted and dropped (docs)
Fixed
- Fix a script argument that matches a subcommand name, such as
scriptling tool.py cacheorscriptling tool.py help, running that subcommand instead of the script
v0.30.2
Changed
- Block reserved address ranges (CGNAT, documentation, benchmarking, NAT64, 6to4 and similar) under a network policy unless
allow_reserved_ips = true(docs)
Added
- Add
allow_reserved_ipsnetwork policy option (AllowReservedIPsin Go) (docs)
Fixed
- Accept TLS 1.2 on connections made under a network policy
v0.30.0
Breaking
- Change
client.response_compact(id)toclient.response_compact(model, previous_response_id=, input=, instructions=). It now compacts a conversation into anoutputto continue from, using OpenAI’s and xAI’s compaction endpoints or, for other providers, a model summary; the old call only stripped reasoning items from a stored response. (docs)
Added
- Add Grok (xAI) as a provider:
ai.Client("", provider=ai.GROK, api_key="xai-..."), using xAI’s native Responses API. (docs) - Add
previous_response_id,instructions,toolsandstoretoclient.response_create()andclient.response_stream(), for multi-turn conversations and tool calling with the Responses API. (docs) - Add
ai.tool_outputs()to send tool results back to the Responses API, and read Responses API output inai.text()andai.tool_calls(). (docs) - Add
client.supports(capability), reporting whether a client uses a native ("responses") or emulated ("responses_emulated") Responses API, and supports"embeddings"and"decision". (docs)
Changed
- Continue emulated Responses API conversations (Claude, Gemini, Ollama, Z AI, Mistral) with
previous_response_id, which was previously ignored, keeping each response’s turn in the process for 15 minutes after last use. Responses are only visible to clients with the same provider, base URL and API key. - Stream tool calls as
function_callitems from emulatedclient.response_stream(), and stream a single response when the client runsremote_serverstools on the native Responses API.
Fixed
- Fix native Responses API streaming returning no events, tool calls through the native Responses API failing, and emulated responses ignoring input messages without a
typefield and theinstructionsof a request. - Fix Ollama tool calls: parallel calls to the same tool sharing an ID, and all but the last of several streamed calls being dropped.
- Fix tool calls without arguments being sent as
"null"instead of{}.
v0.29.0
Breaking
- Evaluate parameter defaults once, at definition, as Python does. A mutable default is now shared between calls.
- Pass
*argsas a tuple instead of a list. Code that modifiesargsmust copy it first withlist(args). - Keep every
;-separated statement of a one-line block inside that block.def f(): return 1; f()no longer callsf; put the call on its own line. - Raise
OverflowErrorwhen integer arithmetic passes 64 bits, instead of wrapping. - Change
json.dumps()output to Python’s format: keys in insertion order (was sorted),", "and": "separators (was compact), non-ASCII escaped. Usesort_keys=True,separators=(",", ":")andensure_ascii=Falsefor the old shape. (docs) - Return
UUIDobjects from theuuidfunctions instead of strings. Usestr(u)for the text form. - Return
bytesfrom thebase64encoders instead of a string. - Return a
timedeltafromdatetimeanddatesubtraction (was a number), and return a struct fromtime.gmtime()andtime.localtime()with Python’s weekday numbering (Monday is 0). - Follow
posixpathinos.path.dirname(),basename()andsplitext()for empty paths, trailing slashes and dotfiles. - Uppercase
ßasSSinstr.upper(). - Remove subclassing of
defaultdict: it is now a function that returns a dict.
Changed
- Iterate dicts in insertion order (was unspecified), including
json.loads(),requests.json(),Counter,popitem()and copies. Dicts built from YAML, TOML, MessagePack and plugin results have sorted keys. (docs) - Make dict-building code 10-30% slower than v0.28.0 in return for ordering. Lookups,
get()and counting are unchanged. - Deliver
**kwargsin alphabetical order (was random per call). Call-site order is not kept. - Rewrite
defaultdictas an ordinary dict with a default factory: every dict method works, lambda and function factories work, keys keep their types, and it is about twice as fast. (docs) - Make named tuples behave like tuples: indexing, slicing,
len(), unpacking, ordering, hashing, immutability, keyword construction anddefaults=. - Raise catchable Python exceptions:
TypeErrorfor unhashable keys, unorderable sorts and wrong argument counts,KeyErrorandIndexErrorfrompop(), theFileNotFoundErrorandOSErrorfamily from file operations, and Python’s types fromassert,str.index,set.removeandmin()of an empty sequence. - Hash plain instances by identity, and raise
AttributeErrorwhen assigning to a property without a setter.
Added
- Add generators:
yieldin a function makes a lazy generator thatfor,list(),next()anditertoolsconsume.yield fromandsend()are not supported. (docs) - Add
@dataclass,enum.EnumandIntEnum,functools.lru_cache,cacheandwraps, user exception classes, andfrom typing import .... - Add
obj.__dict__, a view of an instance’s attributes whose writes go through to the object, and makevars(obj)the same view. (docs) - Add
OrderedDict.move_to_end()andpopitem(last=False), and accept dicts and dict views inreversed(). - Add unpacking forms:
(*a, *b),x = *t, 1, starred targets in nested groups andforheaders,{**d}, andf(**d). (docs) - Add
/positional-only parameters,raise X from e,.5and1.float literals, nested and chained assignment targets, andfrozenset. - Add
copy.deepcopy(),sentinel(),csv.DictReaderandDictWriter,math.fsum,time.monotonic,os.getpid, number methods such asbit_count()andto_bytes(), andint(),float()andstr()without arguments. - Add
client.decide()for Ollama System One decision models. (docs)
Fixed
- Fix a subclass’s class attribute being discarded when a base class defines the same name.
- Fix arguments written after a
*unpack being passed before it. - Fix keyword-only parameters with defaults being unbound when not passed, and a keyword named like a positional-only parameter being rejected when the function takes
**kwargs. - Fix
itertoolsmaterialising infinite generators. - Fix
super().__init__()failing when no base class defines__init__. - Fix
int(True),float(False)andsum()of booleans,%gprecision, and UUID comparison and hashing.
v0.28.0
Changed
Python 3 behaviour. Objects, modules, printing and formatting now behave as Python 3 does, with Python’s error messages,
__getattr__ and __format__ support, and getattr()/hasattr() working exactly like dot access. pathlib.Path supports /. See Python Differences.
Changed
Faster imports. Library files are cached across interpreter instances, so hosts that create an instance per request import 50–77% faster. Edited files are picked up on the next import. See Loader Chain.
Added
Methods as values.
sorted(words, key=str.lower), map(str.strip, lines) and add = items.append work, and dir([]) lists methods. See Built-in Functions.
Added
Extractive text shortening.
similarity.extract(text, max_chars=, max_sentences=, ratio=) keeps the most informative sentences of a long text, in order, using TextRank over hashed word vectors (CPU-only, parallel), and de-duplicates repetitive logs instead of ranking them; similarity.sentences(text) is the splitter behind it. Use it where a long text would otherwise be cut at a fixed length before going to a model. See scriptling.similarity.
Added
Gossip streams.
cluster.open_stream() requests a reply of any size from one node and cluster.handle_stream() serves it, for files and state transfers beyond the packet limit. Closing a stream, or stopping its cluster, ends a read in progress. See scriptling.net.gossip.
Added
Import syntax. Parenthesized lists (
from m import (a, b,)) and PEP 810 lazy import are accepted; lazy imports run immediately. See Imports.
Fixed
F-strings with slices (
f"{items[1:3]}"), !=, lambdas or braces inside string literals now evaluate correctly.
Changed
html.escape(s, quote=True) gains Python’s quote argument and escapes a single quote as ' (previously '), matching CPython.
Fixed
similarity.sentences() and similarity.tokenize() return an empty list for blank text instead of None, so the result can always be iterated or measured with len().
Fixed
Standard library matches Python.
Counter has the full dict API; keyword arguments such as re flags=, int(..., base=), itertools.groupby(key=), random.choices(cum_weights=) and the textwrap options are honoured; bounded deques drop from the opposite end; json.dumps writes tuples as arrays; enumerate() and zip() stop on infinite iterators; isinstance() accepts exception types; dict() accepts mappings and lets keywords win; TOON integers decode as int.
Fixed
format() applies zero padding, fill, sign and grouping like f-strings: format(42, "05d") is "00042". Unknown format codes raise ValueError, and # (0xff), _ grouping, c and n are supported.
Fixed
Iterators, comparisons and errors.
map(), filter(), any() and all() pull from iterators lazily, so they work with itertools.count(), and collecting an endless iterator raises an error instead of exhausting memory. != falls back to __eq__; key= accepts bound methods and callable objects; zip(strict=True) works; except accepts a variable or tuple of types and the LookupError, ArithmeticError and BaseException parents; Counter takes float counts and compares like Python; textwrap splits words exactly as CPython does; random.sample(range(10**9), k) no longer expands the range. itertools.islice() accepts None bounds (islice(it, 2, None)) and stops at the right element when given a step. See Built-in Functions and Error Handling.
Fixed
Gossip HTTP transport.
transport="http" nodes now serve the gossip endpoint on bind_addr; before, they could send but never receive, so HTTP clusters could not form. encryption_key is rejected with this transport (use HTTPS), and a node bound to all interfaces needs advertise_addr.
Fixed
Apple containers work with container CLI 1.1 and later (tested on 1.5): image pulls, image and volume listing, container status and image removal. See scriptling.container.
Fixed
Concurrency. A data race when starting
runtime.background() tasks is fixed, and a server no longer loses its setup script’s error when an earlier server in the same process is still shutting down.
v0.27.3
Fixed
Pipeline concurrency recovers after a rate limit.
client.Pipeline, completion_parallel and ask_parallel halve their concurrency when a request hits a 429, but never restored it, so one early rate limit left a long run at reduced parallelism to the end. Concurrency now grows back by one after each run of clean completions, up to max_parallel, and slot accounting stays exact while the limit changes. See AI Client.
Added
Hosts can cap script fan-out.
ai.SetMaxParallelLimit(n) caps the max_parallel a script may request from client.Pipeline, completion_parallel and ask_parallel, and extlibs.SetRequestsMaxParallelLimit(n) does the same for requests.parallel. A script asking for more silently gets the cap; 0 removes it. One atomic load when the pipeline or batch is created, nothing per request. See Go Integration Basics.
Added
Backslash line continuation. A backslash at the end of a line joins it to the next, as in Python, so a long condition or assignment can be split without wrapping it in parentheses. The continuation line’s indentation is ignored. See Syntax.
Added
Process-wide memory guard for script hosts.
scriptling.SetMemoryLimit(bytes) sets a ceiling on heap memory held by objects. While the heap stays above it after a garbage collection, the most recently started script is cancelled and its evaluation returns a memory limit exceeded error; scriptling.GetMemoryLimitStats() reports the heap, running scripts and cancellations. The guard samples the heap on a timer, so it adds no per-instruction cost to scripts. See Go Integration Basics.
v0.27.2
Changed
Performance. Script execution is 15% to 30% faster depending on workload and calls into scripts are up to 20x faster.
Changed
Comprehensions are about twice as fast. List, dict and set comprehensions bind their variable through a slot and iterate
range() directly.
Changed
Function bodies compile on first call. A module that defines many functions now retains compiled code only for the ones that run, reducing memory for library-heavy hosts.
Added
Program cache budget is configurable. Parsed and compiled scripts are kept in a process-wide cache bounded by 64 MiB by default. The CLI gains
--program-cache-max-bytes (SCRIPTLING_PROGRAM_CACHE_MAX_BYTES, cache.program_max_bytes in scriptling.toml), and Go hosts get scriptling.SetProgramCacheMaxBytes plus scriptling.GetProgramCacheStats for hit, miss and eviction counts. See Command-Line Options and Go Integration Basics.