Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
Sema — Agent-native Lisp for LLM Workflows
[go: Go Back, main page]

Agent-native Lisp·LLM workflows·Rust·MIT

Agent-native language.
Runtime you trust.

Sema builds the agent plumbing into the language itself. Model calls, typed tools, budgets, deterministic replay, journaled runs, OpenTelemetry traces, and single-binary deploys are primitives, not scaffolding — so the workflows your coding agent writes stay small, inspectable, and easy to constrain, replay, and ship.

$curl -fsSL https://sema-lang.com/install.sh | sh

macOS · Linux · Windows · single static binary, no toolchain required

The argument

The same agent, twice.

A coding agent that reads files and runs commands, with a tool loop, retries, and a spend limit. Once with an SDK, once in Sema.

agent.py — Python + SDKyou write the machinery
import anthropic, time

client = anthropic.Anthropic()

TOOLS = [{
    "name": "read_file",
    "description": "Read a file's contents",
    "input_schema": {
        "type": "object",
        "properties": {"path": {"type": "string"}},
        "required": ["path"],
    },
}, {
    "name": "run_command",
    "description": "Run a shell command",
    "input_schema": { # ...same again },
}]

def call_with_retry(messages, attempt=0):
    try:
        return client.messages.create(
            model=MODEL, max_tokens=4096,
            tools=TOOLS, messages=messages)
    except anthropic.RateLimitError:
        if attempt > 5: raise
        time.sleep(2 ** attempt)
        return call_with_retry(messages, attempt + 1)

def dispatch(name, args):
    if name == "read_file":
        return open(args["path"]).read()
    if name == "run_command":
        # subprocess, capture stdout+stderr...

messages = [{"role": "user", "content": task}]
for turn in range(10):
    resp = call_with_retry(messages)
    track_cost(resp.usage)  # you wrote this too
    if resp.stop_reason != "tool_use":
        break
    results = []
    for block in resp.content:
        if block.type == "tool_use":
            results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": dispatch(block.name, block.input),
            })
    messages.append(...)
And there's still no response cache, no hard spend cap, no fallback provider. That's another dependency — or another hundred lines.
agent.sema — Semathe machinery is the language
(deftool read-file
  "Read a file's contents"
  {:path {:type :string}}
  (lambda (path) (file/read path)))

(deftool run-command
  "Run a shell command"
  {:command {:type :string}}
  (lambda (command)
    (:stdout (shell "sh" "-c" command))))

(defagent coder
  {:system    "You are a coding assistant."
   :tools     [read-file run-command]
   :max-turns 10})

(llm/with-budget {:max-cost-usd 0.50}
  (lambda ()
    (agent/run coder "Find TODOs in src/")))
The tool loop, retries with backoff, rate limiting, and cost tracking live in the runtime. The spend cap is a scope — it can't be forgotten on the late-night code path.

Why agent-native matters

Agent-native means checkable.

Generated code is only useful if you can constrain it. Sema workflows are ordinary code, but the runtime sees the boundaries that matter:

01(agent/run coder task)

Every call passes through the runtime

Model calls, tool dispatches, results, and retries are all things the runtime can observe — not logic buried inside an SDK.

02(llm/with-budget {:max-cost-usd 1.00} f)

Budgets and checkpoints are scopes

A spend cap or a resume point is part of the run — not a comment the late-night code path can forget.

03(llm/extract {:amount :number} text)

Outputs are values, not free text

Schema-backed tools and extraction hand back typed data. Nothing downstream has to re-parse a blob of prose.

So a run can be replayed from cassettes, traced with OpenTelemetry, resumed from a journal, and shipped as one binary — and, coming soon, guarded by executable policies instead of “please be safe” prompt vibes.

Why Lisp?

Because the agent has to write it.

Sema is built as a small, stable target for generated programs — the language with the least surface for an agent to be wrong about. The code is already data, so the runtime can inspect it, check it, journal it, and replay it.

  • Sixty years of training data. Lisp predates nearly everything else in the corpus. Scheme, Common Lisp, Clojure, Racket — your agent has read all of it, and a Lisp is a Lisp.
  • Nothing to hallucinate. One syntax rule. No borrow checker, no venv, no lockfiles, no build config, no framework versions that drifted since training. The agent can't misremember machinery that doesn't exist.
  • The whole language fits in context. Point your agent at one short page — where Sema diverges from the dialects it already knows, and nothing else. Constraints, not a textbook.
  • Errors self-correct. Dialect drift is the shallow kind of wrong: “oh, it's equal? here, not string=? — one check, one fix, moving on.
claude — working in pipeline repo
$ claude "add urgency classification to the ticket pipeline"
Read pipeline.sema, llms.txt
Edit pipeline.sema
  (llm/classify [:low :medium :urgent] (:body ticket))
Run sema check pipeline.sema
  ✗ unbound symbol: string=?
llms.txt → "use equal? for all equality" — fixed, re-ran
  ✓ pipeline.sema ok
one self-correction. zero questions for you.

Sema is LLM-native in both directions: LLMs are primitives in the language — and the language is a target LLMs write without special training.

The other fair questions

“Why not just—”

…a Python script with the SDK?

That's where everyone starts, and it's fine — until the script matters. Then you bolt on retries, then a cache so dev runs stop costing money, then cost tracking, then the second provider. The scaffolding ends up bigger than the idea.

In Sema those are forms, not code you maintain: llm/with-cache, llm/with-budget, llm/with-fallback, defagent.

…a framework like LangChain?

Frameworks stack abstractions on a language that wasn't built for them — so a "chain" is a class, a prompt is a template object, a conversation is hidden inside an opaque memory wrapper.

Sema makes them language constructs instead. A conversation is an immutable value you can fork, diff, and inspect. A prompt is an s-expression. A tool is a lambda with a schema. There's nothing to wrap, because nothing is foreign.

The runtime, in one screen

The agent runtime, not another framework.

(llm/with-budget {:max-cost-usd 1.00} f)hard spend cap, scoped to a block
(llm/with-cache {:ttl 3600} f)response cache — dev loops stop costing money
(llm/with-fallback [:anthropic :openai] f)provider failover, in order
(llm/extract {:amount {:type :number}} text)typed maps back, not strings to re-parse
(conversation/say conv "...")immutable history — fork it, replay it, inspect it
(llm/pmap prompt-fn items)parallel batch over a collection

Eight chat providers plus embedding providers, configured from environment variables — set the key and go. Browse the LLM reference →

Then ship it

One file out the other end.

The part Python never solved. No virtualenv on the server, no dependency pinning, no container just to run a script.

  • Standalone executables. sema build traces your imports, bundles assets, and emits a self-contained binary.
  • Capability sandbox. --sandbox fences shell, filesystem, network, and LLM access per group.
  • Starts in milliseconds. Fast enough for a git hook, a cron job, or a CI step — no JVM tax, no import dance.
$ sema build agent.sema -o agent
→ traced 3 imports, bundled 1 asset
→ agent (self-contained, 12 MB)
 
$ scp agent prod: && ssh prod ./agent
→ runs. that's it.

Read this before adopting

Where Sema won't fit.

Knowing the boundaries up front beats discovering them in production.

  • Single-threaded. Rc-based values, no cross-thread sharing. Parallelism is at the LLM-call level, not the compute level.
  • No JIT. A bytecode compiler and a stack-based VM. If your bottleneck is number crunching, use Rust — or embed Sema in it.
  • Not a full Scheme. No numeric tower, no call/cc, auto-gensym instead of syntax-rules.
  • Young. Solid and tested, not battle-hardened at scale. Pin a version; read the changelog.

Your next LLM script, without the scaffolding.

Install it — or skip the tutorial and hand the docs to your agent.

curl$curl -fsSL https://sema-lang.com/install.sh | sh
brew$brew install helgesverre/tap/sema-lang
cargo$cargo install sema-lang
agent$curl -fsSL https://sema-lang.com/docs/for-agents.md >> AGENTS.md