Safescript – A Language for AI Era Safescript, a new programming language designed for AI agents, eliminates the need for containers or virtual machines by using a closed instruction set and static DAG structure that prevents infinite loops and unauthorized operations. The language guarantees every program terminates and provides a complete static signature of all data flows and external contacts before execution, making it structurally impossible for agent skills to exfiltrate data or perform unauthorized actions. A programming language for AI agents. Programs are static DAGs of operations with a closed instruction set, formal data-flow tracking, and resource bounds you can inspect before anything runs. No VM, no container, no sandbox needed. curl -fsSL https://raw.githubusercontent.com/uriva/safescript/main/install.sh | sh safescript run script.ss Installs the CLI globally on macOS / Ubuntu. Also available as a library: deno add jsr:@uri/safescript Deno npx jsr add @uri/safescript npm Why this exists AI agents are getting good enough to write and run code. That's the easy part. The hard part is letting them do it without handing over the keys to the kingdom. Today, when an agent needs a capability call an API, transform data, forward a credential , there are two options. Give it a general-purpose language and hope for the best, or restrict it to a handful of hardcoded tools. The first one is a security nightmare. The second one doesn't scale. The standard fix for the security problem is to throw a sandbox around it. Docker containers, microVMs, Firecracker, E2B, whatever. That works, but now you're paying for it. Every agent execution spins up a container, waits for it to boot, runs a few API calls, and tears it down. You're burning compute and time on infrastructure whose only job is to babysit the code. Cold starts add latency. Orchestration adds complexity. The per-execution cost adds up fast when you're running thousands of agent tasks a day. safescript takes a different approach. The language is the sandbox. There's nothing to escape from because there's nothing dangerous in the instruction set. No filesystem access, no shell exec, no eval, no dynamic imports. The only things a program can do are the operations explicitly provided by the host. That means you can run safescript programs directly in your application process, in the same runtime as your server. No container spin-up, no VM overhead, no orchestration layer. It's a real language with variables, expressions, control flow, imports, and a growing set of built-in operations. But it's not Turing-complete, and that's the whole point. Every program compiles down to a static directed acyclic graph of operations. No dynamic dispatch, no infinite loops. The set of things a program can do is fully knowable before it runs. Every program terminates safescript is not Turing-complete. That's not a limitation, it's the design. There are no loops. No recursion. The parser builds a function call graph and rejects cycles at parse time, both direct recursion and mutual recursion. The only iteration constructs are map , filter , and reduce , and they operate on finite arrays. There's no lazy evaluation, no generators, no way to construct unbounded data. The result is that every safescript program provably halts. You don't need to trust the code, reason about convergence, or set timeouts as a safety net. The language can't express a program that runs forever. This is the same tradeoff languages like Dhall and SQL without recursive CTEs make. You give up the ability to express every computable function. In exchange you get a guarantee that no program will hang your system. For AI agent tasks, that's a trade worth making. The supply chain problem Agent skills today look a lot like npm packages did in 2015. Someone publishes a capability. An agent installs it. Nobody reads the source. One day the maintainer pushes an update that exfiltrates inputs to a third-party server, and you find out about it from a blog post. safescript makes this structurally impossible. Every program has a signature , a complete static description of what it does, computed without executing anything. The signature tells you exactly which hosts it contacts, which environment sources it reads, and how data flows between all of them. Say an agent skill receives your API key as an input and sends it to api.example.com . That's fine, that's what the skill does. But if an update adds a second HTTP call that forwards that same key to evil.io , the signature changes. The new host shows up. The data flow from param:apiKey to host:evil.io shows up. You can diff signatures between versions and catch this automatically, before the program ever runs. This isn't a sandbox or a firewall. It's a proof. The language is constrained enough that the analysis is exact, not heuristic. How signatures work A signature captures everything a function does without executing it: { name: "createIdentity", params: { name: "userId", type: "string" } , returnType: { status: "number" }, hosts: "agentdocs-api.uriva.deno.net" , // which hosts are contacted envReads: , // timestamp / randomBytes usage dataFlow: { "host:agentdocs-api...": "param:userId" , // userId flows to the API host "return": "host:agentdocs-api..." , // what data reaches the return value }, sources: "host:agentdocs-api..." , // where the return value came from memoryBytes: 1002048, // worst-case resource bounds runtimeMs: 10020, diskBytes: 0, complexity: "1", // symbolic complexity expression } The data flow map is the interesting part. Sources are labeled strings: "param:userId" , "host:api.com" , "env:timestamp" , "env:randomBytes" . Sinks are "host:..." and "return" . If an input value reaches a host, or a host's response reaches another host, it shows up explicitly in the map. Resource bounds accumulate from every operation in the program. Each op declares its own memory, runtime, and disk cost. The signature sums them. For branches ternary, if/else , it conservatively takes the union of sources and the sum of resources from both sides. Complexity inference Signatures now include a symbolic complexity expression derived automatically from the program structure. The analyzer tracks the size of every value—string length for strings, element count for arrays—and composes them into a precise Big-O style formula using parameter names and host labels as variables. { // ... other signature fields complexity: "param:items + host:api.example.com"; } Examples of what the analyzer produces: 1 — constant work e.g., timestamp , httpRequest param:items — linear in array length e.g., map double, items param:text — linear in string length e.g., sha256 { data: text } param:matrix param:matrix — quadratic from nested map when inner function complexity depends on the outer array host:api.example.com — linear in the response body size The expression is a sum of monomials. Each term is a coefficient times a product of size variables. Variables are named after the source they measure: param: