Eve Server

Eve is one language for a local client and a server backend. The same program, the Eve machine eve, runs on a workstation and on a server: it executes scripts, receives commands from scripts and from other machines, serves web pages and data over HTTP, and offers an API to AI assistants. This chapter shows how a machine is set up, how it is controlled from far away, how it serves HTTP with service scripts, and how two projects, a client and a server, work together.
Planned: the server features are designed for version 0.5 of Eve and are not in the virtual machine yet. The design is in plan/design-service.md of the Eve repository.

The Eve Machine

There is one Eve machine, the program eve, and it has one mode. It starts locally, on the computer of the user, or remotely, on a server; in both places it is the same program with the same commands. What a machine does depends only on the commands it receives and on its configuration.

  • A local machine runs scripts, answers the prompt eve:>, and may serve web pages on localhost;
  • A remote machine is an eve that listens on a port: it waits for commands, runs uploaded projects, schedules drivers and serves HTTP to other computers;
  • A machine talks to another machine by sending it commands and reading the answers.

Command channels

A machine is driven by commands: load, run, check, doc, serve, setup and the others listed in Commands. The same commands arrive through several channels, and a command gives the same answer whatever its channel:

ChannelExampleWhat happens
Execute a fileeve -x sales.evea new machine parses and executes the script, then exits
Prompteve, then eve:> load sales.evethe machine waits for commands typed by a person
Commandeve -c "run sales.eve"the command is sent to a running machine, which answers
Another machinesend prod "run sales.eve"a machine sends a command to another machine
AI assistanteve --apithe commands become tools of an MCP server (see Eve API for AI)

Commands from scripts

A shell script or a Python script controls a running machine with eve -c. Each call sends one command, prints the answer and ends with the exit status of that command. A sequence of commands is a sequence of calls to the same machine, which keeps its state between them:

eve --listen &                    ** start a machine that waits for commands
eve -c "load sales.eve"           ** first command
eve -c "run sales.eve"            ** a new command, same machine, same state
eve -c "report"
eve -c "stop"                     ** the machine ends
# drive an Eve machine from Python
import subprocess

for command in ["load sales.eve", "run sales.eve", "report"]:
    result = subprocess.run(["eve", "-c", command], capture_output=True, text=True)
    print(result.stdout)
    if result.returncode != 0:
        break

Machine Setup

A machine is its configuration, and its configuration lives in a folder: the machine folder. Without any configuration, eve still works: the default values are built in, and it does its best to execute the command.

Machine folder

eve --setup <folder> (short -s) chooses the machine. If the folder holds a configuration, the machine reads it. If not, --setup creates the folder, the configuration file eve.cfg and the sub-folders: a new machine, named after the folder, with the default values and ports that are free on this computer.

eve --setup ./evemachine/local              ** creates the machine "local", or reads it
eve --setup ./evemachine/server --listen    ** creates or reads "server", then starts it
ItemContent
eve.cfgthe configuration of the machine
web/the pages that serve returns: HTML, CSS, images, WebAssembly
out/output: run logs, files written by drivers, the checksum of the configuration
data/input data
lib/, asp/modules and aspects of the project

A machine folder is a project folder: the drivers, aspects, modules and services of one project, and the one machine that runs them. Another project is another machine.

Configuration

The configuration file holds system variables, one per line. Every value has a default built into eve:

VariableMeaningDefault
$EVE_NAMEthe label of the machine, used by people, logs and remote commandsthe folder name, or eve
$EVE_DOMAINwhere the machine lives: localhost, a domain name or an IP addresslocalhost
$EVE_PORTport for commands, uploads and data4042
$EVE_HTTP_PORTport of serve, for HTML8042
$EVE_WEBfolder of the pages that serve returnsweb
$EVE_OUToutput folderout
$EVE_TOKENthe secret a remote client must presentnone: only local commands

Identity

The identity of a machine is its address, domain and port: localhost:4042, etl1.example.com:4142. Two programs can't use the same port on one computer, so the address is unique. $EVE_NAME is only a label.

  • Start: --listen or --serve first checks that the domain and the ports are free. If one is taken, by another machine or another program, eve ends with an error;
  • Checksum: when a machine reads its configuration, it keeps a checksum of the file outside the file, in out/<name>.sum, and never writes into eve.cfg. So the file can be kept under version control;
  • Changes: a running machine reads its configuration again on the command setup. It applies the changes without a restart, as long as its identity stays the same. A new domain or port describes another machine, which is started on its own.

Client and server projects

A client/server application is two projects, a client and a server, each with its machine. They can stay in the same repository, with the README at its root:

sales_app/                  ** one repository
  README.md                 ** what the application does, how to start both machines
  client/                   ** project and machine "client"
    eve.cfg                 ** localhost:4042, serve 8042
    sales_load.eve          ** driver: reads files, sends batches to the server
    asp/  lib/  data/  out/  web/
  server/                   ** project and machine "server"
    eve.cfg                 ** etl1.example.com:4142, serve 8142
    orders_api.eve          ** service
    asp/  lib/  data/  out/  web/

To test on one computer, both machines use localhost with different ports. In production, the configuration of the server names its domain.

Remote Control

A machine that listens on a port can be controlled from far away: by a script, by a person at the prompt of another machine, or by another machine.

Listening

eve --listen (or eve:> listen) starts a machine that stays active and accepts commands on $EVE_PORT, 4042 by default. It keeps running until it receives stop. It binds to $EVE_DOMAIN, which is localhost unless the configuration names another address, and a remote client must present the token $EVE_TOKEN.

Sending commands

eve -c <command> (or --command) sends one command to the machine of the configuration and prints its answer. -r (--remote) names another machine, by its address or by a name defined in the configuration:

eve -s ./evemachine/server -c "run sales.eve"      ** the machine of that folder
eve -c "run sales.eve" -r etl1.example.com:4142    ** a remote machine by address
eve -c "run sales.eve" -r prod                     ** a remote machine by name

A machine can do the same: it keeps one connection to each partner and sends them commands, on different ports and domain names. At the prompt: eve:> send prod "report". The first version of the protocol is plain: one command per line, the same text as at the prompt; the answer is the text the prompt would show, followed by the exit status.

Login and credentials

One local machine works with many remote machines at once, and enters each one with its own credentials. A person logs in once per machine, preferably by single sign-on: the browser shows the page of the identity provider of the company, the person signs in there, and the remote machine receives a proof of identity, never the password. A machine without single sign-on has its own accounts, with a user name and a password. Scripts and schedules use the token of a machine, $EVE_TOKEN, as before.

The local machine names its remote machines in its configuration, with the way to log in to each, and never a secret:

machine prod at "etl1.example.com:4042" login sso
machine etl2 at "etl2.example.com:4042" login password
machine test at "localhost:4142"        login token

After the first login, the local machine keeps the credentials that the remote machine gave back in the keyring of the operating system: the Credential Manager of Windows, the Secret Service of Linux. The keyring is opened by the login of the person on the computer, so the next connection needs no form. The password itself is never stored.

eve:> login prod        ** first time: the single sign-on page opens in the browser
eve:> whoami prod       ** ana, developer, single sign-on
eve:> send prod "report"
eve:> logout prod       ** ends the session and deletes the stored credentials
eve:> forget prod       ** deletes the stored credentials only

Users and roles live on each remote machine, in its Eve database: the same person can be a developer on one machine and only read on another. The admin of a machine manages its users with commands, from the prompt or from the RAD Workbench.

Upload and run

eve -u <file> (or --upload) sends a source file to a machine, like a load on that machine. The remote machine compiles the file itself, under its own rules, and keeps it for the next commands:

eve -u sales_load.eve -r prod             ** upload: the remote machine compiles it
eve -c "run sales_load.eve" -r prod       ** run it there
eve -c "dismiss sales_load.eve" -r prod   ** free its memory

Sessions

One machine has one state, shared by all the commands it receives. Two independent sessions in parallel are two machines, with two configurations. Inside a machine, every driver has its own driver memory space (DMS): its globals, its modules and the state of its aspects.

  • load sales.eve creates the DMS of the driver. It stays until dismiss sales.eve, so a second run sees what the first one left: open connections, caches;
  • run other.eve for a driver that is not loaded creates a temporary DMS, runs the driver and frees the DMS at the end;
  • A machine can hold several loaded drivers; they never see each other's memory.

Rules of a remote machine

Code received by a machine runs under the rules of that machine. A machine that listens is stricter than a person at the prompt:

RuleAt the prompt or eve -xListening machine
Consoleprint and read workprint goes to the log; read is an error
panicat the prompt: back to the prompt; with eve -x: the process ends, exit code 1ends the request or the job; the machine goes on listening, and eve -c ends with exit code 1
halt, debug modeallowednever
Shell (call)allowedonly if the configuration allows it
Filesany path the user can openonly the folders of the machine
Network, databasesanyonly those listed in the configuration

Serving HTTP

eve:> serve (or eve --serve) starts an HTTP server inside the machine, at http://localhost:8042 by default ($EVE_HTTP_PORT). The prompt stays usable while the machine serves, and serve stop ends the HTTP server.

Static pages

Without any code, serve returns the files of the web folder: a request for /reports/today.html returns web/reports/today.html. A driver that writes an HTML report into web/ publishes it at once to every browser that can reach the machine.

Services

A service is a script that answers requests. It is a fourth kind of script, next to driver, aspect and module. It has no process main: it declares routes. A route joins an HTTP method and a path to the code that answers. Every request gets a new state, so two requests never share variables. A service has the shape of a module: declarations, initialize and finalize, closed by end name;.

A route has two forms. The block form holds the code that answers, with the request and the response as its parameters:


# orders service
service orders_api is
  from "lib" use (orders);

  route get_order(req: Request, @res: Response) on get "/orders/{id}" is
    new o := orders.find(req.params["id"]);
    if o == null do
      let res.status := 404;
    else
      let res.body := o.json();
    done;
  end get_order;

  route create_order(req: Request, @res: Response) on post "/orders" is
    new id := orders.create!(req.json());
    let res.status := 201;
    let res.body := {"id": id}.json();
  end create_order;

  initialize
    orders.connect($ORDERS_DB);
  finalize
    orders.disconnect();
end orders_api;

The one-line form names an aspect, written in its own file, that answers the request. The same aspect can also be applied by a driver:


  route get "/orders" apply list_orders;

Notes

  • The method is any HTTP method: get (read), post (create), put (replace), patch (change a part), delete;
  • A path starts with /, so it is a regex literal (see Regular Expressions): it keeps its text as written, {id} is a route parameter and never a placeholder. A route given as a regular expression is possible for the same reason; its rules are not designed yet;
  • {id} in the path becomes req.params["id"]; the body of a post, put or patch is req.body, and req.json() reads it as a DataMap;
  • The routes of a service run in parallel, one per request. A request that waits for a database gives its core away;
  • The private state of a service is shared by all requests: it follows the rule of modules, written in initialize and then only by unsafe methods (!). Most services keep their state in a database;
  • An error that a route does not recover becomes the response 500; the message goes to the log, not to the browser.

Eve API for AI

eve --api starts the machine as an MCP server (Model Context Protocol, an open protocol based on JSON-RPC 2.0). An AI assistant connects to it, on its standard input and output, or over HTTP on the path /mcp of the serve port, and works with Eve directly: it checks a script, runs it, reads the errors and the run log, and generates the documentation. Every command of the machine is a tool of the API, for example:

ToolCommandResult
eve_checkcheckok, or the syntax errors with line and column
eve_runrunexit code, output and errors
eve_docdocthe documentation pages written
eve_inspectinspectthe introspection report of a loaded script
eve_runlog(read)the lines of a run log

The API follows the rules of the machine: an AI gets the same rights as a person at the prompt of that machine, no more.

Security

  • Local first: --listen, serve and the API bind to localhost unless the configuration opens them to the network;
  • Authentication: a person logs in to each remote machine, by single sign-on or with an account of that machine, and the local machine keeps the credentials in the keyring of the computer (see Login and credentials); scripts and machines present the token of the machine, $EVE_TOKEN; over a network the connection uses TLS;
  • Authorization: the configuration says which routes are public and who may upload and run code;
  • Secrets: passwords and keys are never written in scripts; the machine gives them to a project from its configuration, and they never travel back to a client.

Commands

CommandOptionMeaning
setup-s, --setup <folder>choose the machine of a folder, create it when missing, or read its configuration again
execute-x <file>parse and execute a script, then exit
(any command)-c, --command "<command>"send one command to a running machine and print its answer
upload-u, --upload <file>send a source file to a machine, which compiles and keeps it
—-r, --remote <machine>the machine that receives -c or -u: an address or a name of the configuration
listen--listenwait for commands on $EVE_PORT
serve--serveserve HTTP on $EVE_HTTP_PORT; serve stop ends it
api--apistart the MCP server for AI assistants
send—send a command to another machine: send prod "report"
login, logout—log in to a remote machine (single sign-on, an account, or a token), or end the session; forget deletes the stored credentials, whoami shows the user and the role
load, dismiss—create, or free, the memory space of a driver
stop—end the machine

Read next: RAD Workbench