Release v0.18
v0.18 Release: Your Own Domains, a Cloud-Neutral Core, and a Chat That Shows Its Work
October 2026 · The Smarter Project
v0.17 made what already existed work. v0.18 builds on it in three directions. LLMClients can be served from your own domain. Everything that talks to the cloud now goes through one service layer, which also lets Smarter run on a laptop with no cloud account at all. And the prompt engineering workbench now shows you what a prompt is doing while it runs.
| Change | What it does |
|---|---|
| CustomDomain resource | Serve LLMClients from https://<llmclient>.<your-domain>, with verification, email notices and a console page. |
| Infrastructure service layer | DNS, certificates, Kubernetes and email go through one provider-neutral layer, with a ledger of billable resources. |
| Run locally without the cloud | make init works with no AWS, SMTP or LLM API keys, and tells you what to set when you need a feature. |
| Prompt progress, streamed | The workbench shows each LLM request, tool, plugin and MCP call as it happens, over Server-Sent Events. |
| Markdown chat and server logs | Responses render as GitHub flavored markdown, and the Console streams your server logs, DEBUG included. |
| Smarter Chat built in | The workbench chat is built and served with the platform, instead of being loaded from a CDN. |
Each change is described below.
Custom domains
Every deployed LLMClient gets a host name under the platform’s own domain. That’s fine for testing, but a customer-facing assistant should live at your company’s address, not ours.
Smarter has had a custom domain model for a while, but it wasn’t a real resource. It had no owner, so you couldn’t apply, list, share or delete it like everything else, and several code paths that managed it raised errors. Its verification could never succeed, and an LLMClient was never actually deployed on one.
In v0.18, CustomDomain is a resource kind like any other. Here is the built-in example, trimmed:
apiVersion: smarter.sh/v1
kind: CustomDomain
metadata:
name: example_custom_domain
description: Serves the company's LLMClients from llmclients.example.com.
version: 1.0.0
spec:
config:
domainName: llmclients.example.com
smarter apply -f custom-domain.yaml
smarter deploy customdomain example_custom_domain
smarter describe customdomain example_custom_domain
deploy creates the domain’s DNS hosted zone and its TLS certificate in the background. describe then shows, in status.dnsRecords, the NS records to add at your DNS registrar. Once they’re in place, every LLMClient that uses the domain is served at https://<llmclient-name>.<domain-name>, with its own certificate.
- Verification that tells you where it’s stuck.
status.verificationStatusisNot Verified,Verifying,VerifiedorFailed. A domain is Verified when its NS records are delegated and its certificate is issued. Until then,status.verificationMessagesays which step isn’t done, and why. - No waiting on your side. After
deploy, Smarter checks the domain every 30 minutes for up to 24 hours, and emails you when it’s verified or when it gives up, with the reason. - Deployed automatically. An LLMClient on a custom domain is deployed to it when the LLMClient deploys, when its domain becomes Verified, or when a verified domain is attached to a deployed LLMClient through the REST API.
- A console page. The new Custom Domains page has Your and Shared tabs, a Verification badge whose tooltip gives the verification date or the blocking reason, and Open, Edit, Visit, Clone, Rename and Delete actions.
Security
- A domain belongs to the user who applied it, and is shared with their account, like every other resource.
- A domain registered by another account can’t be registered again. Before v0.18, Smarter also rejected your own account’s domain as already taken, so
applyfollowed bydeployalways failed. domainNamecan’t be changed once the domain is registered.- A domain that an LLMClient uses can’t be deleted. Deleting it would have deleted the LLMClient too.
- Deleting a CustomDomain doesn’t delete its hosted zone. Remove that yourself if you no longer need it.
One layer between Smarter and the cloud
Smarter creates DNS records, TLS certificates and Kubernetes resources for every LLMClient it deploys, and sends email. That code was spread across helper modules that each called AWS directly. It was hard to test without mocking it away, which is how several of its bugs went unnoticed.
v0.18 replaces those helpers with a single infrastructure service layer. The platform asks for DNS zones and records, certificates, Kubernetes manifests and email in provider-neutral terms, and never imports a cloud SDK.
- Pluggable providers. A new setting,
SMARTER_CLOUD_PROVIDER(defaultaws), selects the provider. The AWS provider uses Route53, ACM and EKS. An in-memory provider keeps zones, records and certificates in memory, for local development and tests. Support for another cloud is a new provider package, not a change to the platform. - A ledger of what Smarter created. Every DNS zone, record, certificate and Kubernetes resource that Smarter creates or destroys is recorded. Superusers can see it in a new console page, Settings > Infrastructure Resources, with a summary and filters. DNS zones, persistent volumes and LoadBalancer services are flagged as billable.
- Faster deployments. Each
kubectlcall on EKS used to start a separateaws eks get-tokenprocess, which took seconds on a small worker. Smarter now builds the token itself and reuses it for 10 minutes, and checks an LLMClient’s ingress, certificate and secret in one call instead of three. A deployment no longer holds a worker for about 30 seconds.
Moving every caller onto the new layer, and testing it without mocks, turned up bugs that this release fixes:
- Custom domain verification could never succeed. One check called a function that wasn’t implemented, another read a field that DNS records don’t have, and the certificate check compared against a status ACM never reports. All three are fixed.
- Certificate validation records were corrupted. Every single-value DNS record was wrapped in quotes. Only TXT values are quoted now.
- Deleting an LLMHost failed after its Kubernetes resources were removed, because DNS cleanup was queued incorrectly.
- Admin copies of email were visible, and never arrived. The admin address went in a visible
Bccheader but was never delivered. It’s now a real blind copy, and emails that carry secrets, such as password reset and activation links, go only to their recipient. - Email to several recipients was sent to one comma-joined address. Each recipient now gets it.
📖 Infrastructure documentation
Run Smarter locally without cloud credentials
A new developer who ran make init without AWS, SMTP or LLM API keys got a broken platform. The OpenAI and Together AI providers were never created, so 15 built-in LLMClients failed validation, and the SET-ME-PLEASE placeholders in .env.example were stored as real API keys.
In v0.18, the same spin-up completes every step. The only required setting is SMARTER_FERNET_ENCRYPTION_KEY, and make generates it when it creates your .env.
- Every built-in provider is created, with or without a key. A provider without one has only its default model. A prompt to it returns an error that names the variable to set, instead of an exception from the provider’s SDK.
- Missing settings are reported where they matter, in a banner that says what to set and where to get it:
[API KEYS NOT SET]once at startup,[PROVIDER WITHOUT API KEY]at prompt time,[EMAIL NOT SENT]for each email that can’t be sent, and[KUBERNETES DISABLED]when no cluster is configured. .env.exampleis reorganized into Required, Local development, Optional LLM providers and tools, Optional cloud deployment, Optional email, and everything else.- Placeholders count as unset everywhere. Before, an unconfigured SMTP server was reported as configured, and Smarter could run
aws eks update-kubeconfig --name SET-ME-PLEASE.
One limit remains: without a Tavily key, the built-in smarter LLMClient isn’t created, because its web search plugin needs one. The other built-in LLMClients are.
Watch a prompt run
A prompt can take a while. The LLM may call tools, plugins and MCP servers, and each call adds another round trip. Until now the workbench showed a spinner until the whole thing finished.
In v0.18, the chat shows each step as it happens: each request to the LLM, and each tool, plugin and MCP server it calls. The steps are replaced by the response when it arrives. A prompt that the provider rejects shows the provider’s error message in the chat thread.
This works through the prompt API itself, so your own client can use it too. Send Accept: text/event-stream to an LLMClient’s prompt API and the response is Server-Sent Events: a progress event for each step, keepalive comments while the prompt waits, and a final result event that holds the same JSON a non-streaming client gets.
event: progress
data: {"type": "tool_requested", "message": "Calling tool get_current_weather", "tool": "get_current_weather", "arguments": "{...}"}
event: progress
data: {"type": "mcp_tool_called", "message": "Calling MCP server github: search_code", "mcpclient": "github", "tool": "search_code", "arguments": "{...}"}
event: result
data: {"status": 200, "response": {"data": {"statusCode": 200, "body": "..."}}}
- Same prompt, either way. The prompt runs exactly as it does for a JSON client, so its history, charges and journal are the same. A client without the header gets JSON, as before.
- The stream’s status is always 200. The prompt’s own status is in
result.status. - Disconnecting doesn’t cancel the prompt. It finishes, and is saved in the chat session’s history.
- Proxies must not buffer. Streaming needs Smarter’s ASGI server (uvicorn). The response sends
X-Accel-Buffering: nofor nginx; configure any other proxy in front of Smarter the same way.
Markdown responses and live server logs in the workbench
Markdown
Smarter Chat now renders LLM responses as GitHub flavored markdown, with syntax-highlighted code blocks. The LLM had no way to know that, so Smarter now adds a short note to the system prompt of every request saying that the chat window renders markdown. The note goes to the provider only. It isn’t saved in the chat history, isn’t shown in the thread, and isn’t added twice.
The built-in LLMClients’ system prompts now say how to format replies for their use: tables for data analysis, fenced code with a language for developer tools, light formatting for support. Their defaultMaxTokens is at least 4096, so formatted replies aren’t cut off. A new built-in LLMClient, Polly the Polyglot Programmer, shows off syntax-highlighted code in any language.
Server logs
The workbench Console has a Server Logs tab that streams your server logs while you chat, the same stream as the web console’s log viewer. Two problems kept it from being useful:
- DEBUG records never appeared, in either view, even on a platform configured to log at DEBUG. They do now. Only the DEBUG chatter of the libraries that carry the stream itself (asyncio, redis, uvicorn, daphne) is left out.
- The log views could appear and stay empty. Logs reach the stream only when a waffle switch is on, but the views checked only a setting. They now require both
SMARTER_ENABLE_DASHBOARD_SERVER_LOGSand theenable_web_console_server_logsswitch. When either is off, the sidebar item and the Console tab are hidden, and the page returns 404. A change to the switch in Django admin takes effect on the next request.
The workbench’s API Calls tab was always empty too, because requests that addressed an LLMClient by its hashed ID weren’t recognized as LLMClient requests. They are now, so the tab fills in.
Smarter Chat is part of the platform
The workbench chat was the one React app in the web console that Django didn’t build or serve. The page loaded a script from Smarter’s CDN, which fetched a build made from a separate repository, and the platform checked that CDN over the network when it started.
Smarter Chat is now built, tested, linted and served like every other console page. Its source still lives in its own repository, smarter-sh/smarter-chat, and is published to npm as @smarter.sh/ui-chat. make react-install clones it into the React workspace. The option to push every React app to S3 and CloudFront, and to download them into the Docker image, is removed. React apps ship in the image.
Behavior changes you may notice
verify_dns_configurationno longer overwrites A records. It used to copy the root domain’s A record to every platform zone, replacing any record that differed. The root domain often points at a marketing site, not the platform. In one of our own environments, a v0.18 patch release did exactly that, and every chatbot host was unreachable until the records were fixed by hand. The command now copies the platform domain’s A record, only into zones that have none, and logs a warning instead of changing an existing record. It also fails, instead of quietly creating it, when the root zone is missing.- Built-in LLMClients aren’t undeployed on every rebuild. Each redeploy of the platform deleted and recreated the DNS record of every built-in LLMClient that was already deployed. During that gap the host didn’t resolve, and resolvers could cache the failure for up to 15 minutes. Applying them again now changes nothing.
- Deploy and undeploy can’t race. When both were queued for the same LLMClient, they could run at once on different workers and leave it with a working DNS record but marked as not deployed. They now take turns, and a late undeploy of an LLMClient that has since been redeployed does nothing.
- A failing plugin no longer breaks every prompt. An LLMClient skips a plugin that fails to load, and logs the error with the plugin’s name, instead of returning a 500 for every prompt.
- The old CLI schema URL works again. The VS Code YAML extension still requests
/api/v1/cli/schema/<kind>, which a rename had broken. It now redirects to/api/v1/cli/json-schema/<kind>/. Schema requests no longer count toward the excessive-404 block, which had locked developers out for 10 minutes after a burst of lookups.
Security fixes
- The Django
SECRET_KEYwas written to the logs. It was logged at DEBUG level on every startup, and a randomly generated replacement key was logged at WARNING level. ADJANGO_*environment variable that created a new setting also had its value logged in plain text. None of these are logged now. A leakedSECRET_KEYlets anyone forge session cookies and password reset tokens, so if you have run with debug logging, or without a configured key, rotate it. - Emails with secrets no longer copy the admin. Password reset links, activation links and generated passwords go only to their recipient (see above).
- The EKS token Smarter now builds is never logged.
Fixes and operations
- Weather forecasts in US units always failed with “Unexpected error processing weather data”. They work now.
- Looking up a chat by session key raised an error for every model, and looking up a provider by name recursed until it crashed. Both fixed.
- Stackademy example plugins lost their data whenever the plugin tests ran on a platform that had them installed. The tests no longer touch them.
manage.py get_secretandupdate_secretran an older copy of each command from another app. The old copies are removed.register_custom_domainalways reported failure, even when it worked. Fixed.- Waffle switches checked before
initialize_wafflefirst ran were created as off, and stayed off. They are now created with Smarter’s own default. Several switches have consistent names:log_mcpclient,log_orchestratorandlog_vectorsearch. - The infrastructure worker’s CPU limit in the Helm chart is 2, up from 1, so its four concurrent tasks don’t slow one another down.
- Containers without a memory limit no longer log “Error parsing resource limits” at every startup.
- Test coverage grew by about 1,000 lines, which found and fixed five of the bugs above.
Upgrading
- Run the migrations. This release adds llmclient migration
0006(CustomDomain ownership and verification), journal migration0007, and the new infrastructure app’s0001_initial. Existing custom domains are given to the owner of the LLMClient that uses them, or else to a superuser, and verified domains stay Verified. - Upgrade every worker with the app, including
smarter-worker-infrastructure, which runs the new custom domain and DNS tasks. - Run
initialize_waffle. It deletes the renamed switches and createsenable_web_console_server_logs,log_infrastructureand the renamed logging switches with their defaults. Turnenable_web_console_server_logson if you want server logs in the browser. The new--resetflag sets every switch back to its default, which discards changes made in Django admin.account_loggingnow defaults to off. - Optional: set
SMARTER_CLOUD_PROVIDER. It defaults toaws, which is what every existing installation uses. - Remove
SMARTER_REACTJS_APP_LOADER_PATHand theDOCKER_REACT_REMOTE_CDN_URLandDOCKER_REACT_REMOTE_CACHE_BUSTERbuild arguments. They are ignored now. If you build your own image, build the React apps first (make react-build). - Rotate
SECRET_KEYif it may have reached your logs (see Security fixes). - Repair LLMClients left half-deployed. If an LLMClient shows as not deployed while its host still answers, redeploy it, one at a time.
- Check your DNS once.
verify_dns_configurationno longer corrects an existing A record. If one of your platform zones has the wrong A record, it now logs a warning; fix the record by hand. manage.py initialize_platformnow loads an example CustomDomain,example_com. Its verification fails by design, because Smarter doesn’t control example.com. The built-in LLMClients now includepolyglot.
What’s next
Custom domains are the last piece of serving an LLMClient as your own product. Next, we’ll finish the infrastructure layer’s provider model with a second cloud, and read the platform’s load balancer address from Kubernetes instead of from DNS, so the platform no longer depends on one DNS record being right. After that, we’ll return to the plan from v0.16: LLMClients on your self-hosted LLMHost models, and Vectorsearch on your Vectorstores.
The full list of changes is in the v0.18 changelog. To try the release, start with the documentation, or browse the code on GitHub.
