CSPL: a program is stored separately from the way it is executed
Version of 2 October 2026. Every statement is labelled with how we know it: [verified] means we ran it ourselves; where the raw answers are saved, the statement names the file; [documented] means it is in the platform's API reference and our working code relies on it; [stated] means the designer of the runtime says so and we have not reproduced it.
1. The problem, and what this document describes
An agent can call the tools its MCP server offers. When the server is a fixed set of functions, each new tool is a change to that server: edit the code, deploy it, restart it, and do the same on every machine where it should run. MCP does not require this: a server may change its list of tools while it runs, or offer a general executor. This document describes one way to add tools without changing the server.
In Extella a tool for the agent is a saved program, not a function in a server. A model, or a person, saves the program through an API (save, get, run, search), whose operations are also available as MCP tools, and runs it by name on a chosen device. The MCP server and its list of tools do not change.
If tools are saved programs, four questions follow at once. CSPL, Container Specific Programming Language, is the mechanism in Extella that answers them. It is our own design, described in the published international application WO 2026/102343 (priority date 8 November 2024). This document describes that mechanism and nothing else: not the Extella product, not agents, not model training.
| Question | CSPL's answer | Where in this document |
|---|---|---|
| In what language is the program written? | In any language for which the device has a handler; the record names its language | sections 2, 3 and 5 |
| What executes it? | That language's handler, registered on the device and replaceable | sections 4 and 5 |
| Where does it run? | On the device named in the call | sections 3 and 5 |
| How can a model be allowed to add code? | Not safely by default; a small language limits what a program can say, and its handler can check a program before running it | sections 4, 5 and 7 |
The mechanism in one line:
a saved program that names its language → a call that names the program and a device → the handler of that language on the device → a result.
A saved program is called an expert. Its record holds source text and, in the cspl field, the name of the language the text is written in. A handler for that language is registered on a device, a machine that runs the Extella listener. A handler can be registered or replaced without rewriting the programs written in its language.
When this can be useful. When the same saved program has to run on any of several devices you have prepared, with the device chosen in each call, or when many programs share a small language of your own whose handler you want to control in one place.
When it is not. A function in your own MCP server answers in about a millisecond, and a general executor with program storage is about a hundred lines of code; a call through Extella to a device took 6 to 14 seconds in our measurement (section 5). If that covers your case, either of those is the simpler choice; we did not run either of them on a second machine and do not know what that takes.
What this is, and what it is not. It is a way to run saved programs through pluggable handlers on prepared devices. It is not a general-purpose safe environment for running untrusted code written by a model; section 7 says why.
2. Terms
| Term | Meaning |
|---|---|
| CSPL | The mechanism: programs stored with the name of their language, executed by a handler for that language. |
| Language | A set of permitted statements and the rules for writing them. The cspl field of an expert holds the name of its language. |
| Expert | A saved program: name, code (source text), cspl (language name), default parameters, a description. It is stored in an account, in the scope of one of the account's agents (the X-Agent-Id header in section 3). |
| Handler | Code on a device that executes programs in one language. For a language of your own it is a Python function. |
| Device | A machine running the Extella listener, signed in to an account. It has an id and can be named as the target of a run. |
| Registrar | An expert whose job is to register a handler on the device it runs on. |
3. The expert record and the two calls
Saving and running go through an HTTP API [documented, and verified in our demo servers]:
POST https://api.extella.ai/api/expert/save
headers: X-Auth-Token, X-Profile-Id: default, X-Agent-Id: <agent>
body: {"name": "tally_demo", "code": "add 5\nadd 7\nsub 2\n", "cspl": "tally", "kwargs": {}, "description": "..."}
POST https://api.extella.ai/api/expert/run
body: {"name": "tally_demo", "params": {}, "targets": ["<device id>"]}
answer: {"status": "success", "expert_name": "tally_demo", "result": "...", "execution_log": [...]}
A long run comes back as {"task_id": ...} and is polled with POST /api/tasks/check. Saving under an existing name overwrites the record; there is no versioning [documented].
4. Declaring a language: an example you can run
The example has four parts: a language of two statements, its handler, a registrar, and a program. The handler below is the file as we ran it. The registrar is shown with the handler text elided; the script that builds it, saves both experts and runs them is one file, run_tiny.py. Both files are published next to this document.
python3 run_tiny.py # needs Extella installed and signed in; Python 3.8+
The handler. It receives the source text of an expert and returns a dictionary. It checks the whole program first; if any statement is not in the language, nothing is executed:
def tally(code = "", filtered_source_code = "", func_name = "", **extra):
# Handler of the language "tally": a program is lines "add N" or "sub N", N up to nine digits 0-9.
# The whole program is checked first; if any line is not a tally statement, nothing is executed.
import platform, socket
lines = [line.split() for line in (code or filtered_source_code or "").splitlines() if line.split()]
for n, parts in enumerate(lines, 1):
if len(parts) != 2 or parts[0] not in ("add", "sub") or not (parts[1].isascii() and parts[1].isdigit() and len(parts[1]) <= 9):
return {"status": "rejected", "reason": "statement %d is not a tally statement: %s" % (n, " ".join(parts)[:40])}
total = 0
for op, number in lines:
total += int(number) if op == "add" else -int(number)
return {"status": "success", "language": "tally", "expert": func_name, "total": total, "host": socket.gethostname(), "os": platform.system() + " " + platform.machine()}
Changed on 16 October 2026. The number check now accepts only the digits 0 to 9, at most nine of them. The version run on 1 October checked parts[1].isdigit(). Python counts “²” as a digit, so add ² passed the check and then failed in int() instead of being refused. The answers below are from 1 October and that version (918 bytes). On 16 October the fixed handler (991 bytes) was registered and run on a Linux server: the same program gave 10, import os was refused as before, and add ² was refused [verified, 16 October 2026; runs-2026-10-16.json].
The registrar is an expert in the built-in language fython (Python with a dependency directive). Run once on a device, it registers the handler there under the language name:
$extens("include.py")
include("import os", [])
def register_tally() -> dict:
import disnet
handler_code = '''...the handler text above, inserted by run_tiny.py...'''
d = disnet.Disnet(url="https://disnet.extella.ai")
envelope = {"filtered_source_code": "", "func_name": "", "args": None, "kwargs": None, "cspl": "tally", "response_format": None}
d.container(code=handler_code, func_name="tally", container_path="tally", args=None, kwargs=envelope, cspl_type="fython", push=False)
return {"status": "success", "registered": "tally", "bytes": len(handler_code)}
What the device answered on 1 October 2026, on a MacBook (Darwin arm64) [verified; the host name is shortened here, the raw answers are in the appendix file]:
| Step | The device's answer | Round trip |
|---|---|---|
Run register_tally | {'status': 'success', 'registered': 'tally', 'bytes': 918} | 13.1 s |
Run tally_demo (add 5, add 7, sub 2) | {'status': 'success', 'language': 'tally', 'expert': 'tally_demo', 'total': 10, 'host': '<laptop>', 'os': 'Darwin arm64'} | 11.1 s |
Run an expert whose second line is import os | {'status': 'rejected', 'reason': 'statement 2 is not a tally statement: import os'} | 11.1 s |
The round trip is the full time of one call as seen by the caller. In an earlier pass the same day the same steps took 7 to 9 s. These runs were not a measurement of speed; the measurement of 2 October is in section 5.
5. What we have shown
Adding a program does not change the server. We added the same ten small Python functions in three ways: as fixed tools of an MCP server (official Python SDK), through a general executor with program storage that we wrote in about a hundred lines, and through Extella's MCP server. Fixed tools: 10 edits and 10 restarts, because that is how this baseline is built; MCP does not require it. The general executor and Extella: 0 edits and 0 restarts. A new function, saved and run for the first time, took 0.36 s with fixed tools (restart included), 0.026 s with the general executor, and 9 to 12 s with Extella (median about 10 s over ten functions with the session already open; in this run every save overwrote a program of the same name, so creating a program for the first time was not timed; two earlier runs the same day gave medians of about 11 s, and the first function together with opening the session took 14.4 s). A repeat call took 0.0014 s, 0.025 s, and 6 to 14 s (median about 9 s, 30 calls). Only Extella was run on a second machine: 30 of 30 calls were correct there with no second save, in 5 to 9 s. No Extella call failed in the 80 calls of this measurement on that day (72 run_expert calls in the final run and 8 in a short check before it; that check is not in the published results); a re-run during review saw one failure in 73 calls, which we could not reproduce. A script made every call; no model was driving any of the servers. The two local ways ran as local processes with no network; Extella is a remote service that dispatches to a device, and that device was the same laptop that made the calls; the difference in seconds is therefore not a comparison of two remote services. Extella's MCP server lists 44 tools, 53 KB of definitions with every listed field and 33 KB in name, description and input schema; the general executor lists 4 tools in 2 KB [verified, 2 October 2026; measure3.py, generic_server.py, measure3-result-2026-10-02.json; two earlier runs: measure3-result-2026-10-02-run1.json, measure3-result-2026-10-02-run2.json]. What follows: the measurement confirms for Extella one narrow property, a program saved once runs on a prepared machine chosen by a parameter of the call. It does not show an advantage over a general executor placed on a second machine, because that was not run, and it says nothing about the products of Anthropic or Cloudflare, which we did not run.
Calling a saved expert through this API needs no call to a model. In our demo runs the counter of model calls showed 0 for a run and 1 for creating an expert from a sentence [verified, 1 October 2026]. The counter sits in the demo server, the only component of that demo that holds a model-provider key; its source is published at extella.dev/robot/server/. This is evidence from one component, not an audit of a closed system. An expert may itself be written to call a model; nothing prevents that.
The same program runs under different handlers. One expert in the language block (a JSON document describing a quote on a web page, sha256 ba2de0dd…) was executed by three handlers on two Linux servers in six runs. Two handlers produce HTML and differ only by a constant; the third is separately written code that renders the same documents as plain text. Registering a handler took 6.8 to 7.9 s; the model-call counter was 0 in every run; the handler hash returned by the device matched the hash of the published file in all six [verified, 1 October 2026; extella.dev/blocks/server/].
Replacing a handler changes how the programs of its language run on that device. A fix or a new rule goes into one handler and the stored programs stay as they are. The saved record is one stored program executed by three handlers without being edited [verified on the block language: one program, six runs]; the live page extella.dev/blocks/ renders its six stored blocks with whichever of the three handlers you choose. That the same holds for every program of a language is the stated design of the runtime [stated]; we have not measured it on a larger set of programs. This is a wide effect, not a guarantee of a safe update: keeping existing programs working when a handler is replaced is the responsibility of the handler's author, and the platform does not version handlers or programs. It applies to languages whose handler you write; for a general language the handler is that language's own toolchain.
Languages are not limited to small ones. Experts in four languages ran on one MacBook through the same call: tally, Go, Rust and C [verified, 1 October 2026]. The Go and Rust handlers were already registered on the device. For C we wrote a handler of sixteen lines that compiles the source with the device's compiler and runs the binary (handler_c.py). A language of this kind needs its toolchain on the device.
No separate application service has to be deployed around each program. Every expert is called by name through the one Extella API, so a chain is a sequence of calls, and a caller does not need to know what language the next step is written in. The API and the platform behind it are a network layer; what goes away is a service per program. A chain of three experts in three languages (a read-only query language, Go, and a page language) ran on one machine on 29 September 2026 [verified; the raw answers of that run are not published], and a chain of three experts in three languages (a read-only query language, Python, and a page language) ran on a Linux server on 2 October 2026, each result passed to the next as parameters [verified; three_languages.py, three-languages-2026-10-02.json].
One expert can be replaced without touching the others. Saving new source under the same name replaces the program; the next run uses it. We replaced the tally program between two runs and the answer changed from 10 to 99 [verified, 1 October 2026].
A small language limits what a program can say. A program in tally cannot open a file because the language has no statement for it. Our public demos use this: visitors add content to a live page in a language of seven block types with text fields only [verified for those languages]. This is a property of one language and its handler. It does not carry over to other experts or to the platform; see section 7.
An expert is exported and installed as data. A bundle of two experts, the registrar and the program, exported as one JSON file, was saved into an account and run on a laptop by one script [verified on macOS, 1 October 2026; extella.dev/robot/classic/take_home.py]. The target device needs Extella installed and the handler registered; the script does the second.
6. Related work
Three published approaches are close to this one. They address different parts of a system, and we do not claim they cannot do what is described here.
| Code execution with MCP (Anthropic) | Code Mode (Cloudflare) | Agent Skills (Anthropic) | This document | |
|---|---|---|---|---|
| Subject | Presenting tools to a model as code it can call, so tool definitions and intermediate data stay out of its context | Converting MCP tools into a typed API and running model-written code in an isolate | Packaging instructions, resources and scripts that an agent loads when relevant | Storing a program with the name of its language and executing it by a handler on a chosen device |
| Language of the code | TypeScript in the post's examples | TypeScript | Instructions plus scripts | Declared per program |
| What executes it | A sandboxed execution environment | A V8 isolate with bindings to MCP servers | The code execution tool | A handler registered on a device of the account |
| Reuse of code | The post describes saving working code as reusable functions | Not in the post; the Code Mode documentation describes named snippets that a model can find and run again | A skill is reusable by design | An expert is saved and called by name |
| Replacing the executor | Not addressed | The Code Mode package has an executor interface you can implement for another environment; it runs JavaScript only, and we found no handler per named language in what we read | Not addressed | A handler is installed by an expert |
| Isolation | Sandboxing is required by the design | An isolate without general network access | Sandboxed code execution | None by default, see section 7 |
Sources, as we read them on 1 October 2026: anthropic.com/engineering/code-execution-with-mcp; blog.cloudflare.com/code-mode; claude.com/blog/skills. Read on 2 October 2026: developers.cloudflare.com/agents/tools/codemode/how-it-works; github.com/cloudflare/agents/blob/main/packages/codemode/README.md.
MCP is a way into this mechanism: the operations of section 3 are exposed as MCP tools, and an application that already speaks MCP can keep doing so.
How Extella puts this together: a named program, the name of its language, a handler that can be registered and replaced on each device, and the device chosen in each call. On 2 October 2026 a small language of two statements was registered on a laptop, four programs in it were run, its handler was replaced and the same four programs, not saved again, ran under the new handler; the first handler was then registered on a Linux server, and two of the four programs ran there under it; the laptop, whose last runs were under the second handler, was not called again after that. Every step was a call of the MCP tools save_expert and run_expert [verified; handler_via_mcp.py, handler-via-mcp-2026-10-02.json].
This document describes how Extella connects a stored program, its language, a handler and a chosen device through one calling interface. We have not compared it experimentally with the approaches above.
7. Limits
- No security sandbox. A handler and a
fythonexpert run with the operating-system rights of the user who runs the listener. A restricted language limits only what programs in that language can express. Do not run experts or handlers you have not read. - The runtime is closed. What can be inspected is the API, the expert and handler sources shown here, and the servers of our demos.
- Separate library versions per expert are stated, not reproduced. The designer says each expert gets its own environment, so two experts can depend on different versions of one library. In our test on 29 September 2026 two Python experts that asked for different versions of a library ran in the same environment. This does not affect combining experts written in different languages, which works (section 5).
- The MCP server is not a small interface. It lists 44 tools, about 53 KB of definitions with every listed field; saving, reading, running and searching programs takes four of them.
- A call takes seconds. 6 to 14 s in the measurement of 2 October (median about 9 s, 30 calls, one laptop, one day), for functions that compute in microseconds.
- A device must be prepared in advance. Extella is installed and signed in there, and the handler of the language is registered.
- No versioning. Saving under an existing name overwrites silently. Handlers are not versioned either.
- Handler state is per device. Two callers who register different handlers for one language on one device affect each other.
- A device must be online. If the listener is stopped, the run fails.
- A toolchain language needs its toolchain. A Go, Rust or C expert runs only where that compiler is installed.
- Not measured by us: devices other than two Linux servers and a MacBook; a clean installation on Windows; what the run time reported by the platform consists of (of a call of 6 to 14 s, about one second is the service and the network between the client and the service; the rest we have not broken down).
8. Reproduce
Without an account, call the demo server:
curl -s -X POST https://extella.dev/api/blocks/render -H "Content-Type: application/json" \
-d '{"device":"second","handler":3,"blocks":["skill_block_quote"],"force":true}'
With Extella installed and signed in (it writes ~/.extella/api_token.txt and ~/.extella/device.txt) and Python 3.8 or newer:
python3 run_tiny.py # the tally example of section 4 curl -sO https://raw.githubusercontent.com/AnvarBakiyev/extella-research/main/cspl/take-home/take_home.py python3 take_home.py "https://extella.dev/api/blocks/export/skill_block_quote?handler=3" --level render
Appendix. What was run
| Date | 1 October 2026 for the runs listed below; 2 October 2026 for the three-way measurement of section 5 |
| Machine | MacBook, macOS, Apple silicon; two Linux x86_64 servers for the block runs |
| Listener | extella-listener 1.2.5 (Extella desktop app 1.3.0) |
| Sources | handler.py, handler_c.py, run_tiny.py, verify_all.py |
| Raw answers | runs-2026-10-01.json: eight runs with time stamps, round-trip times and the device's answers |
| Measurement of section 5 | measure3.py, generic_server.py, measure3-result-2026-10-02.json: the script, the general executor and the raw results |
verify_all.py repeats the eight runs in order: register the tally handler, run a program, run a rejected program, replace the program and run again, run Go, run Rust, register the C handler, run C.
The runtime was designed by Timur Ryspekov, CTO and co-founder of Extella.