GenHTTP Lambda

How this works

You write a snippet of C#. Whatever it returns is hosted at a public address, over HTTPS, in a few seconds. This is the whole of it, in the order you will meet it.

What a lambda is

A lambda is a snippet that returns a GenHTTP handler. The platform compiles it, loads it, and mounts whatever it returned under your own address. There is no project, no build file and no using statement. Every GenHTTP module is already imported for you.

return Content.From(Resource.FromString("hello"));

That is a complete lambda. Deployed at /lambda/your-key/, it answers every request with the word hello.

The snippet is statements, not a class. The last thing it does is return something that can serve requests: a handler, or a builder for one.

Your first one

  1. 1
    Press Create my lambda. You get a public address and an editor key. The key is the only way back in, so keep it. Nobody can recover it for you.
  2. 2
    You land in its control center, with a small REST service already written as the first version. It is only a starting point.
  3. 3
    Hand the editor key to an agent and tell it what to build - it writes new versions through MCP. Or open Code and write it yourself: Check compiles without storing anything and tells you what the compiler thinks, file and line.
  4. 4
    Press Deploy. Now it is online. Nothing is reachable before that, and deploying again extends how long it stays.

The control center

The editor link opens a control center rather than a text box: most of the code here is written by agents, so the first thing on the screen is how your lambda is doing. The sidebar holds the lambda - whether it is online, its address, and a button when a newer version is waiting to go online - and its sections. Anything done rarely, like changing the address or deleting it, is behind the ⋯ menu there.

Overview
Whether it is online, how many requests it had today and how many failed, the latest change, and how much room is left.
Change
Say what should be different, and the agent on this server does it while you watch. It tries the change on a draft - a copy with an address of its own - and puts it online once it works. Switch off Put it online when it is done to try the draft yourself first.
Drafts
Changes being tried before they go online, each at an address of its own and on test data of its own. Opened, a draft has its own code, test data and logs. The section is there once there is a draft.
Files
The files of a version: its code and assets, the program itself. A lock or a globe says whether the public can reach them.
Data
What the lambda keeps while it runs, shared by every version: the workspace. Look into it, upload and delete files, or switch it off.
Versions
What each version changed and what was asked for, and the difference to the one before. Deploy or roll back from here, or start a draft from any of them.
Deployments
What was online when, and what took it down.
Stats
Requests, failures, response times and the most asked-for paths, over the last hour or day.
Logs
Its requests, what it printed, and the stack trace of anything that went wrong, as it happens.
Code
Writing it by hand. Check compiles, Save makes a version, Deploy puts it online. In a draft, Save keeps it in the draft and shows it at the draft's address. Ctrl-S saves; F12 goes to a declaration.

Every section works the same way: its title, an ⓘ that explains it, its actions on the right, and - where it has more than one view - a row of pills underneath. The pills of the code are its files.

The traffic and the log are held in memory, for watching rather than keeping: a restart of the server begins them again. Versions and the deployment history are stored.

Saying why

A version is the code, and optionally two notes about it: the specification, what the user wants and why, in their words where possible, and the change, one line on what the version does. They are shown beside the diff in the version history, so the why survives next to the what - for you, and for the next agent that reads the history before changing anything.

POST /api/v1/lambdas/{editorKey}/versions
{
  "files": [ { "name": "lambda.cs", "code": "..." } ],
  "specification": "A guest book people can sign; entries must survive a restart",
  "change": "Keeps entries in the workspace so they survive a restart"
}

Agents pass the same two fields to write_code. In Code, saving asks for the change. Both are optional; a long specification is cut at 4000 characters and a change at 500 rather than refused. A draft keeps its own two, and the version it becomes takes them over.

Changing it safely

A version never changes once it is saved - which is what makes every one worth keeping: any of them can be compared with, and put back online exactly as it was. To change a lambda that people use, try the change in a draft first.

  1. 1
    Start it from any version under Versions, or let the agent start one. It is a copy of that version's code and assets, and of the lambda's data.
  2. 2
    Change it as often as it takes - in Code, or by asking the agent. Its preview answers at an address of its own, /features/…/, against test data of its own. Visitors of the lambda see none of it, and nothing it writes reaches the lambda's data.
  3. 3
    Put online once it is right: it becomes the next version, with its notes, and goes online. The draft goes with it - its preview and its test data.
POST /api/v1/lambdas/{editorKey}/features
{ "name": "Leaderboard" }

PUT  /api/v1/lambdas/{editorKey}/features/{feature}/files?deploy=true
POST /api/v1/lambdas/{editorKey}/features/{feature}/merge
{ "deploy": true }

Several drafts can be worked on at once. Only one that is up to date with the newest version can go online, so that it never undoes a version saved after the draft began. When another went online first, bring its changes in - or ask the agent to - and mark the draft as up to date. Nothing goes online on its own; that is deliberate. The API calls a draft a feature, and putting it online a merge.

More than one file

Types do not have to sit underneath the code that uses them. In Code, press + beside the files and it is compiled beside the snippet, in the same namespace, so nothing has to be imported to be reached. A name with no extension is taken to be C#.

lambda.cs
var shelf = new Shelf();

return Inline.Create()
             .Get(() => shelf.All())
             .Post((Book book) => shelf.Add(book));
Shelf.cs
public sealed class Shelf
{
    private readonly List<Book> _books = [];

    public IEnumerable<Book> All() => _books;

    public Book Add(Book book)
    {
        _books.Add(book);
        return book;
    }
}

public record Book(string Title, string Author);

Serving a page

There are two ways to serve a page, and one more for what people upload beside it.

One page, written inline

Fine for something small. The page is part of the snippet.

var page = Resource.FromString("""
                              <!doctype html>
                              <title>Mine</title>
                              <h1>It works</h1>
                              """)
                   .Type(new ContentType("text/html; charset=utf-8"));

return Content.From(page);

A folder of real files

What you want for anything with a stylesheet and a script. The files are added the same way a C# file is, and served exactly as written. Nothing compiles them.

return Layout.Create()
             .Add("api", api)
             .Add(Assets.App("site"));

Uploaded files, from the data

For what people upload or the lambda creates - pictures, documents - served beside the app. Not for the pages of the app itself: those belong in a folder of files, where they are versioned with the code that needs them.

return Layout.Create()
             .Add("api", api)
             .Add("uploads", Workspace.Files("uploads"))
             .Add(Assets.App("site"));

A front end, step by step

The second of those, in full. Every demo serves its page this way from a folder called web - open demo-crud to read one. Demos are read only; their editor key is their name.

  1. 1
    In Code, press + beside the files and type site/index.html. A name with a slash in it puts the file in a folder; a name with an extension is taken as the file it says it is.
  2. 2
    Add site/app.css and site/app.js the same way. Your page refers to them by name, as in href="app.css", because the folder is the root of what gets served rather than part of the address.
  3. 3
    For anything that is not text, like an image or a font, open a file in site and press the upload button beside the files: it lands in the same folder. A PNG cannot be typed into a text editor, so that is the way in.
  4. 4
    In lambda.cs, serve the folder:
    return Layout.Create().Add(Assets.App("site"));
  5. 5
    Press Deploy. site/index.html answers at /, site/app.css at /app.css, and any address matching no file is answered with the page, so a front end that does its own routing still works when somebody reloads on a deep link.
  6. 6
    Add an API beside it and the page has something to talk to:
    var api = Inline.Create().Get("notes", () => notes);
    
    return Layout.Create()
                 .Add("api", api)
                 .Add(Assets.App("site"));

The two places files live

A lambda keeps files in two places, and the editor shows them apart: Files holds the files of a version - the program - and Data holds the workspace - what the program keeps. The difference is whose they are. The files of a version belong to that version; the data belongs to the lambda, and every version shares it.

In a versionIn the data
what it holdsthe code and assets: the program, front end includedwhatever the lambda writes, or somebody uploads
when it changesnever - a change is a new versionthe moment something is written to it
a deployputs exactly these files onlinenever touches it
rolling backbrings the old files backno effect: every version shares it
a draftstarts as a copy of themworks on a copy of it
when it goeswith old versions, past the limitwith the lambda, or when you switch it off
reached from code asAssetsWorkspace

They cannot be one place. If they were, a deploy would either wipe everything your lambda had written since, or nothing could ever be removed from what it ships. A game that keeps a leaderboard wants the second; the page it serves wants the first. So the page goes in the version, and the leaderboard in the data.

Keeping data

Workspace is a private directory your lambda may read and write. It is the place for anything that has to outlive a request, or a deployment.

var notes = new List<string>();

if (Workspace.Exists("notes.json"))
{
    notes.AddRange(JsonSerializer.Deserialize<List<string>>(Workspace.ReadText("notes.json")) ?? []);
}

return Inline.Create()
             .Get("notes", () => notes)
             .Post("notes", (string text) =>
             {
                 notes.Add(text);
                 Workspace.WriteText("notes.json", JsonSerializer.Serialize(notes));
                 return notes.Count;
             });

There is also ReadBytes, WriteBytes, Delete, List, CreateFolder, and Tree/Files/App for serving it. Nothing else on the file system is reachable.

Websockets

Supported, and not an afterthought. The demo-game demo pairs players and runs every game on the server. The simplest form is three callbacks:

var room = new ConcurrentDictionary<IReactiveConnection, string>();

var socket = Websocket.Functional()
                      .OnOpen(c => { room[c] = "someone"; return ValueTask.CompletedTask; })
                      .OnMessage(async (c, text) =>
                      {
                          foreach (var other in room.Keys)
                          {
                              await other.WritePayloadAsync(text);
                          }
                      })
                      .OnClose((c, _) => { room.TryRemove(c, out _); return ValueTask.CompletedTask; });

return Layout.Create().Add("chat", socket);

One thing catches everybody: a browser cannot set headers on a websocket handshake. Pass what the handler needs in the query, where it reads it from connection.Request.Header.Query, or send secrets as the first message.

What it will not let you do

Your code runs on a shared server, so some of C# is refused before it compiles: starting processes, opening sockets of your own, loading assemblies, reaching the file system outside your workspace, and reflection used to get around any of that.

Everything else is there, including the whole of the GenHTTP module API. If something is refused you are told which line and why, not simply that it failed.

Taking it away

Download in the editor gives you the whole thing as a .NET project: a solution you can open, dotnet run, and keep. It has one package reference and no trace of this platform in it.

Your snippet becomes the body of Program.cs, wrapped in a host that serves what it returns. Your other files come across exactly as you wrote them. Workspace and Assets become two folders beside the code, with the same methods, so nothing in your code has to change.

Worth knowing before you build anything here: what you write is yours and it leaves whole. Nothing about running it on this machine locks it to this machine.

Letting an agent do it

There is an MCP endpoint at /mcp. Point an agent at it and it can do everything the editor does: read the guide, read a demo in full, write files, compile them, and deploy. It is the same API underneath.

It says why as it goes - write_code takes the specification and the change - and it can look at what it deployed: read_logs answers with the lambda's recent requests, what it printed and the stack trace of anything it threw, which is how an agent finds out its code works rather than assuming it. You watch the same thing in the control center.

More about that →