This repository is the build system: everything needed to configure, generate, and run a Basalt deployment. What Basalt is — the architecture and the services — is described in BASALT.md. This document is the operating manual: starting the stack, stopping it, and connecting your people and your agents.
Basalt is the Genworks-maintained fork of Basilisk.
A standard deployment runs an interactive console (Emacs, with
MCP and HTTP endpoints), two engine services (Gendl on CCL and on
SBCL — Common Lisp computation and geometry, reachable over HTTP,
MCP, and SLIME/Swank), and a monitor (autoheal). Users and agents
address any service by its hostname. The whole set is declared in one
file, basalt.sexp, and every other configuration file is generated
from it.
Requirements: Git and Docker.
git clone https://github.com/genworks/basalt
cd basalt
./basalt upImages are pulled as needed, services start and are health-validated
one by one, and the deployment comes up under a generated name —
assigned on first start, kept in .ship, and used as the name of the
Docker network all services join.
If you have a ~/projects/ directory it is bind-mounted at
/projects in every container, and created if missing. To mount a
different directory (never edit .env by hand — it is generated by
./generate-env.sh):
PROJECTS_DIR=/path/to/projects ./basalt upA first up also makes a few deliberate changes on the host:
eskewandegskewbecome shell commands — they connect you to the console (see Connecting, below).- Placeholder credential files are created under
~/.claude/,~/.gemini/,~/.codex/and~/.grok/where missing, so the console's file mounts resolve cleanly. - If a
~/.claude.jsonalready routes MCP through somemcp/mcp-exec, it is conservatively re-pointed at this clone's copy (a backup is kept; nothing else in the file is touched).
./basalt <command>| command | what it does |
|---|---|
up [service...] |
start the deployment, or named services only; --pull fetches fresh images |
down |
stop and remove all services |
restart [service...] |
restart one service, or all |
stop [service...] |
stop without removing containers |
status, ps |
container status |
logs [service] |
follow a service's logs |
emacs |
attach an emacsclient terminal to the console |
pull |
pull missing images (PULL_ALWAYS=1 for latest) |
config |
show the merged compose configuration |
install-shell-functions |
(re)write the eskew/egskew helpers |
clean |
remove containers, networks, and images — asks first |
Variant switches (--lite, --default, --tui, --gui, --full)
pick the console image variant for the invocation; the dev stack
defaults to full.
For a deployment that should survive host reboots unattended, hand it to systemd:
cd systemd && sudo ./install
sudo systemctl start basilisk # likewise stop, restartStop the deployment before pulling the repository, in case the pull changes files a running stack would refuse to stop cleanly against:
cd ~/projects/basalt
./basalt down
git pull
./basalt up --pullBASILISK_INSTANCE=alpha BASILISK_PORT_OFFSET=100 ./basalt upEach instance gets its own generated name, network, and ports.
Within any instance the services keep their canonical hostnames —
captain resolves to that instance's console — so nothing configured
against a service name needs to know which instance it runs in. To
reach a second instance's console from the host, prefix the command:
BASILISK_PREFIX=alpha- eskew.
Ways in for a human user:
| way in | how |
|---|---|
eskew |
the console, in your current terminal |
egskew |
the console in a graphical frame (falls back to the terminal without a display) |
| the web terminal | a terminal in your browser: http://localhost:6942 |
./basalt emacs |
the console, without the shell helpers |
Detach from eskew/egskew with ctrl-^.
./basalt up generates client registry files into mcp/, detecting
the platform as it goes:
| file | for |
|---|---|
mcp/claude_desktop_config.json |
Claude Desktop on this or another machine — walkthrough in docs/CLAUDE_DESKTOP.md |
mcp/claude-code-mcp.json |
Claude Code on the host — mcp/install-claude-code-config splices it into ~/.claude.json |
mcp/mcp.toml, mcp/mcp-container.json, mcp/mcp-windows.json |
other clients, and the agents inside the console |
Agents in the console. The full console variant ships terminal
agents — Claude Code, Gemini CLI, Codex, Grok — pre-configured with
the same registries, so an agent running at the console reaches the
same services an outside client would. The lite variant carries
none; outside clients work identically either way. Which agents, and
where each keeps its configuration, are documented with the image:
see
skewed-emacs/docker/README.md.
Every entry in these registries connects directly to the service it names, with no intermediary gate: treat issuing them as issuing an invitation. An authenticating front door for uninvited clients is a supported pattern that the standard deployment does not configure.
Once a client is connected,
mcp/opening-prompt.md is a suitable
ready-made first message.
The deployment's configuration is basalt.sexp — the one file
written by hand. Its companion glossary.sexp defines the
configuration vocabulary and the display strings; editing either
requires no change to any shipped code. The generator reads the configuration file named
for its own directory and no other — configuration written for a
differently-named build system is not read, and the error names both
files.
After editing the configuration, regenerate:
(load-file "/projects/basalt/generate-configs.el")
(skewed-generate-configs "/projects/basalt/")This is commonly run from the console of an already-running deployment, in preparation for starting another.
Generated outputs include, among others:
| generated file | what it is |
|---|---|
docker-compose.yml |
the compose configuration the deployment runs from |
mcp/mcp*.json, mcp/mcp.toml |
MCP client registries |
generated/services-generated.el |
the console's copy of the service inventory |
generated/crew.env |
the service ledger: each role, and the service filling it |
generated/vocabulary.env |
display strings for startup and status output |
The generated files are outputs. Never edit them directly; edit the configuration and regenerate.
Everything beyond the standard set — licensed engine variants, an ingress (reverse proxy), a monitoring dashboard — arrives by stack repository: a repository beside this one, one per deployment design, carrying its own additional configuration. Stack configuration is sparse: it states only what deviates from or adds to the base, and inherits the rest.
mkdir ~/projects/my-stack && cd ~/projects/my-stack
$EDITOR basalt.sexp # your deviations and additionsA stack repository may be named anything; its configuration file is
always named for the build system it feeds — hence basalt.sexp
here.
Generate the stack's output files, then install them and start:
(load-file "/projects/basalt/generate-configs.el")
(skewed-generate-configs "/projects/my-stack/")./install # copies the outputs into ../basalt/
cd ../basalt && ./basalt upSet BASILISK_DIR= if your Basalt clone is not the sibling
directory. Re-run both the generation and ./install after every
configuration edit.
Configuration that must name other services — an ingress's routing
rules, a services-init hook — names a role
(${BASILISK_POST_CAPTAIN} and kin), resolved against the service
ledger at start time, so the rules survive service renames.
Bringing your own image is not a special case: a stack repository
whose configuration adds one more service. It joins the deployment's
network and resolves by name from every container. An image deployed
with no role is entered on the roster as unassigned
(unassigned-<repo>) — running and monitored, assigned to nothing.
A stack repository can only add. For a deployment that runs only what you declare, fork this repository, rename it for your own system, name the configuration file to match, and cut the service list to taste. The generator derives everything from the directory name, so the rename requires no code changes.
Startup will warn and proceed for every standard role you omit — a console is recommended, not required, and a deployment without one is a standalone service group rather than an interactive one.
Copyright © 2026 Gornskew Enterprises. Basalt modifications copyright © 2026 Genworks International.
Basalt is free software, released under the GNU Affero General Public License, version 3 or later — the full text is in LICENSE. The images a deployment pulls carry their own terms, stated at their own registries.
The origin of record for this repository is maintained by Genworks International, and mirrored to github.com/genworks/basalt. Public issues and pull requests may be lodged against the github mirror.