v0.38 – v0.39 Release: Annotations You Can Query, Search Across Every Resource, and a Slack Bot

v0.37 taught the Smarter Project MCP Server to read every repository, every blog post and every manifest schema. v0.38 and v0.39 are about finding and connecting things. SAM annotations become a real integration point, modeled on Kubernetes, that other systems can query and react to. The web console gains one search box for every kind of resource, and a detail view that shows how each resource fits with the others. And an optional Slack bot brings any deployed LLMClient into a Slack workspace.

FeatureWhat it does
AnnotationsKubernetes-style annotations on every resource, stored as indexed rows, found with label-selector syntax, and reported to your code when they change.
Global searchOne search box on the dashboard that searches every kind of resource by name, tag or annotation selector. Every list can be narrowed the same way.
Resource detailChoosing a row in any list opens a dialog with the resource’s fields, its manifest, what it uses and what uses it.
Slack botAn optional pod in the Helm chart that answers questions in Slack with a deployed LLMClient.

Each feature is described below.


Annotations that other systems can use

Annotations have always been part of SAM’s metadata, but earlier releases stored them as a JSON column, in a list of one-key maps with loosely typed values. Nothing could find resources by their annotations without loading every row, and nothing told an integration when one changed. In v0.38, annotations work the way they do in Kubernetes, and they’re meant for the same job. They let other software associate a Smarter resource with something it knows about, such as the record that mirrors it in a CRM or a CMDB.

metadata:
  name: support_assistant
  annotations:
    acme.com/crm/account-id: A-10042
    acme.com/finance/cost-center: "4410"
    acme.com/support/owner: data-platform-team
  • A map of strings. Keys have an optional DNS prefix, acme.com, and a name of one or more slash-separated segments. Values are strings, and an unquoted YAML true or 4410 is kept as the text that was written. The smarter.sh prefix is reserved for the platform.
  • Kubernetes limits. At most 63 characters per name segment, 253 for the prefix, and 256 KiB for all of a resource’s annotations together.
  • Apply semantics. smarter apply makes a resource’s annotations exactly those of its manifest, as kubectl apply does, so a key that the manifest drops is removed.
  • Selectors. The syntax of Kubernetes label selectors, with exists (acme.com/crm/account-id), not-exists (!acme.com/crm/account-id), equals and not-equals. The get endpoint of the API takes them as a repeatable annotation query parameter, for every kind.
curl -X POST -H "Authorization: Token $SMARTER_API_KEY" \
  "https://<your-smarter>/api/v1/cli/get/LLMClient/?annotation=acme.com/crm/account-id=A-10042"

Reacting to changes

An integration written as a Django app registers an annotation handler for a prefix that it owns. Smarter calls it once for each annotation with that prefix that’s added, changed or removed on any resource, including when the resource is deleted. Handlers run after the database transaction commits, and an exception in one is logged without affecting the resource or other handlers. A new annotations_changed signal reports every change to every resource, whatever its prefix.

Where each resource came from

Every platform loads its example resources from two GitHub repositories, smarter-sh/example-manifests and smarter-sh/stackademy. As of v0.38.2, manage.py load_from_github annotates each resource it applies with smarter.sh/source-repository, smarter.sh/source-path and smarter.sh/source-commit, as Flux and Argo CD annotate the Kubernetes objects they apply from git. So smarter.sh/source-repository=https://github.com/smarter-sh/stackademy finds everything Stackademy installed, and !smarter.sh/source-repository finds everything that people created themselves. The blog post How Smarter’s Kubernetes-Style Annotations Trace Every Resource to Its GitHub Commit walks through it in detail.

📖 Annotations documentation


Search every resource at once

Until now, the web console’s lists could be searched by name and narrowed by a named filter, one list at a time. A user who wanted every resource tagged production, or every resource tied to one CRM account, had to open every list.

  • Global search. A search box on the dashboard searches every list at once and groups the results by list, the lists with the most matches first. It understands three kinds of query, namely plain text, a tag (tag:production) and an annotation selector. Each result opens that resource’s detail, and each group has a link to its list narrowed by the same query.
  • Narrow any list. Every list’s cards show their tags and annotations as chips. Clicking one narrows the list to the resources that share it, and a selector box adds annotation requirements by hand. The narrowing is part of the page’s address, so a narrowed list can be bookmarked or shared.
  • Same permissions as the lists. Search uses each list’s own permission rules, so a user finds exactly what their lists would show them.

📖 Web console documentation


A detail view for every resource

Each list in the web console used to offer two views, a table and a grid of cards, and the cards were hard to read. Neither showed how a resource fits with the others. In v0.38, choosing a row in any of the 15 lists, by clicking it or by focusing it and pressing Enter, opens a dialog with four tabs.

  • Details. The resource’s fields, laid out for its kind. Code fields, such as a SqlPlugin’s query, an LLMClient’s styles or a JSON or YAML value, are shown as highlighted, line-numbered source.
  • Manifest. The resource’s manifest as smarter describe returns it, with Copy, Download and Edit manifest.
  • Hierarchy. Every resource it uses, as a tree. A resource you can’t read appears as a locked node.
  • Dependencies. Every resource that uses it, which is why it can’t be deleted while they exist.

Previous and Next, or the arrow keys, browse the list, across pages, and keep the open tab, which makes it easy to compare two manifests. Clicking a resource in the Hierarchy or Dependencies tab opens that resource’s detail in the same dialog, with a Back button. The open detail is part of the page’s address too, so it can be bookmarked and shared.

The LLMClient list also gains a Workbench button under each LLMClient’s name, which opens its prompt workbench directly.

📖 Web console documentation


A Slack bot for any LLMClient

The Smarter Project now has a Slack workspace, and v0.39 adds the bot that answers questions in it. It’s an optional subchart of the Helm chart, off by default, that any installation can turn on for its own workspace and its own LLMClient.

  • No inbound traffic. The bot connects to Slack with Socket Mode, so it needs no Service, Ingress, DNS record or certificate.
  • The platform does the work. The bot calls the LLMClient’s chat API, so the LLMClient’s model, tools and Guardrails all apply, and every Slack conversation appears in its chat history and cost accounting.
  • Conversations follow Slack threads. A mention in a channel is answered in its thread, and each thread is one conversation. Direct messages work too, and a message that is only new, reset, start over or new conversation starts over.
  • Small and locked down. One pod on a stock Python image, running as a non-root user with a read-only file system and no Kubernetes service account token. It receives only the two Slack tokens.

To turn it on through the deploy workflow, set the secrets SLACK_BOT_TOKEN and SLACK_APP_TOKEN and the variable SLACK_BOT_AGENT_URL in one GitHub environment. The bot runs only where all three are set. To install by hand, set slackBot.enabled, slackBot.agentUrl, env.SMARTER_SLACK_BOT_TOKEN and env.SMARTER_SLACK_APP_TOKEN.

Every message the bot answers is a billed prompt, and anyone in the workspace can send one. Choose an LLMClient whose model and Budget suit that. The blog post How to Build a Slack Bot on Kubernetes That Answers With a Smarter LLMClient explains how the bot works.

📖 Slack bot documentation


Annotations are now returned as a map

The API, the SAM JSON Schemas and the chat contract now describe metadata.annotations as a map of strings, not a list of one-key maps. Clients that read annotations as a list need to change. Smarter still accepts the old list form when you apply a manifest, but a strict schema validator, such as the one in the VS Code YAML extension, will flag it. Write new manifests in the map form.


Web console improvements

  • The sidebar highlights the current page, however you reached it. It used to highlight an item only when it was clicked, so a bookmark, the back button or a link from another page left nothing highlighted.
  • Clearer resource fields. Yes-or-no fields are a dot and a word instead of a full-height badge, and a provider’s Deprecated, Flagged and Suspended flags are shown in warning colors instead of green.
  • A cookie notice on every console page, with a link to the privacy policy, in all four of the console’s languages.

Fixes and operations

  • More concurrent prompts. Streamed prompts ran in a shared thread pool of CPUs + 4 threads per web server worker, which is 6 on a 2-vCPU node. From the seventh concurrent prompt on, prompts queued behind each other, along with error pages and host name lookups. Prompts now have their own pool of 32 threads per worker, set by SMARTER_PROMPT_THREADS.
  • Quieter logs. Vulnerability scanners that probe a deployed LLMClient’s host for other software’s paths, such as /wp-json/ or /.env, are still rejected with a 400, but now log a warning instead of two errors.
  • API. Every list API’s rows, and the REST API’s contract models, gain an optional detailUrl. It’s a backward-compatible addition to the OpenAPI document.
  • Configuration. Nine settings that nothing read were removed, and .env.example now lists every environment variable that the platform reads, with its default.

Upgrading

  • Annotations move to their own table. v0.38 adds a migration to each of 15 apps that copies every resource’s annotations into rows, then drops the old column. The copy is lenient with older data. An invalid key is skipped, the last value of a repeated key wins, and a list or map value is kept as a JSON string. It can be reversed, and it’s safe to run again if it’s interrupted. Back up the database before you upgrade, as with any migration.
  • Annotations are a map in the API. Update any client that reads them as a list (see above). The Python, Node.js and Go SDKs need their contracts and SAM models regenerated to match.
  • New setting. SMARTER_PROMPT_THREADS, 32 by default, sets the prompt threads per web server worker. In the Helm chart it’s app.promptThreads.
  • Removed settings. Nine unused settings are gone, including the two test-database settings that the deploy action passed through. If your configuration sets any of them, they’re now ignored, and you can delete them. .env.example lists the settings that remain.
  • Optional Slack bot. It’s off unless you turn it on, as described above. Enable it in one environment only, because two bots with the same Slack app would each receive part of the messages.
  • manage.py initialize_platform now annotates every example resource with the repository, file and commit that it came from.

What’s next

Annotations are queryable in the API and the web console, but not yet from the smarter command-line tool. The next steps are a --annotation flag for smarter get, regenerated SDK models that read annotations as a map, and the map form throughout the example manifests.

The full list of changes is in the changelog. To try the release, start with the documentation, or browse the code on GitHub.