Assistant
The Strataplane assistant helps teams write and change agent configuration:
build a Telegraf parser from a sample payload, add an input to a layer, find
out why a host gets a value, prepare a change set. It follows the design of
the Grafana Pi assistant (elohmeier/grafana-pi-app): one agent, four
tools, everything else is a shell command over a session filesystem, and
writes are reviewed change sets.
1. Principles
- Bash first. The model has
read,write,editandbash. Strataplane capabilities are commands in the shell (sp render,telegraf-parse, …), not extra tools. Skills add instructions, never tools. - Same commands as the CLI. The shell commands are the
stratactlcommands, from one registry (architecture.md §8). A workflow the assistant can do, a human can script, and vice versa. - The spec is files. The working copy of the spec is mounted at
/spec. Edits there are the change set's overlay. - Acts as the user. Every command calls the API with the user's session,
so ACLs apply. The assistant has no privileges of its own. Its requests
name their chat in
X-Assistant-Chat, so the audit tells them apart: their events have the actor kindassistant, the chat id as actor and the user inon_behalf_of; a header that names no chat of the caller (another user's, a deleted or an unknown one) is refused with 400assistant.invalid_chat. A user's actions in the audit include the assistant's on their behalf. - No remote writes without review. Its only remote writes stage change
sets: create, validate and submit them for review. Approval and merge
stay with humans, in the UI or with
stratactl, under the normal approval policies (§4). - No network in the sandbox. The shell is just-bash in a Web Worker with no network, process or link commands. Commands reach Strataplane only through the typed broker.
2. Runtime
Svelte UI
└─ ChatView ── AssistantSession (framework-free TS)
├─ Pi Durable harness (@earendil-works/pi-durable), one per chat
│ ├─ models: pi-ai provider "strataplane" → streamProxy → POST /api/v1/assistant/llm/stream
│ ├─ extension: tools read/write/edit/bash, system prompt sections, skills
│ └─ storage: LogStorage (MemoryStorage + chat log) → /api/v1/assistant/chats/{id}/commits
├─ SessionWorkspace (virtual FS, copy-on-write transactions)
├─ just-bash worker (coreutils, rg, jq-wasm, sed, awk, diff)
├─ wasm worker: telegraf-parse, vector-vrl (WASI shim)
└─ StrataplaneBroker (typed API client, user's session)
Rust server
├─ LLM proxy: OpenAI-compatible (Chat Completions / Responses) and Anthropic Messages; key never sent to the browser
├─ chat log store: one writer per chat (epochs), exactly-once commits (seq + digest), replacing rows, tombstones
└─ preview endpoints used by the commands (render, explain, impact, validate)
- Durable chats. The harness commits every step (user input, streamed
partials, tool calls and results, compaction, the session filesystem as the
app.workspacedocument) before the UI shows it. Reloading continues an interrupted answer;readcalls are replayed,write,editandbashcalls are reported to the model as interrupted because they may already have changed state. Retry backoffs are durable sleeps and resume when the chat is next open. A task cut off by crashes five times in a row is parked; the chat shows it with a retry action (retryTask). - Chat log API follows the grafana-pi-app contract (
open→ new writer epoch, creating the chat;commitswith sequence number and digest; pagedlogreplay), implemented in Rust overchat/chat_row(chat_row).LogStorageapplies a commit in memory only after the server stored it; opening the chat in another tab takes it over, and the previous view stops. - Bounded logs. Task and submission records and document bases replace
their predecessors, so a long streamed answer stores a handful of rows.
app.workspacewrites a base once 32 deltas follow the last one (checkpointWhen), which bounds both replay and stored rows. Transcript entries are never pruned; compaction only changes what the model sees. - Models are configured per tenant by its admins (
llm_provider): base URL, write-only API key, model list with context window and output limits, thinking settings and whether the model takes images. Images (screenshots the assistant attaches) reach the model in tool results; each request carries the latest three, older ones become a note, and a model without image input gets only the note. The default model and a prompt addendum the client cannot remove are theassistantSettings object (spec-format.md §3.8). Local OpenAI-compatible servers (llama-server, vLLM) work for air-gapped installs. - Browser only. The assistant runs while a user has it open.
3. Session filesystem
| Path | Kind | Content |
|---|---|---|
/spec/** | resource, writable | Working copy of the spec tree at the chat's base revision. Writes stage an overlay; files are canonicalised on commit. |
/spec/.base.json | generated, read-only | Base revision, tree hash, change set id if one exists. |
/inventory/hosts.ndjson | generated, read-only | Hosts visible to the user: name, kind, labels, owner. Lazily loaded, cached 5 min. |
/inventory/agents.ndjson | generated, read-only | Agent instances: kind, version, health, sync, desired/applied revision. |
/catalog/telegraf/<version>/<section>/<plugin>.json | generated, read-only | Plugin schema, sample.conf and README from the catalog bundle. Listed for every imported version, content fetched on read. |
/catalog/telegraf/<version>/reference/docs/<name>.md | generated, read-only | Telegraf's documentation of the versions the UI was built with (CONFIGURATION.md, DATA_FORMATS_INPUT.md, …). |
/catalog/telegraf/<version>/reference/src/<section>/<plugin>.go | generated, read-only | The plugin's Go sources of those versions, one file per plugin (common/<package>.go for shared code such as the SNMP translator). A version's reference is one static asset of the UI, fetched on the first read. |
/catalog/elastic-agent/<version>/inputs/<type>.json | generated, read-only | Input types and known options. |
/catalog/vector/<version>/<section>/<type>.json | generated, read-only | Vector component schemas per section, and global/options.json. |
/agents/<host>/<kind>/rendered.{toml,json} | generated, read-only | Currently served artifact (secrets masked), fetched on read. |
/agents/<host>/<kind>/logs.ndjson | generated, read-only | Recent heartbeat log entries / failed component units. |
/samples/ | scratch, persisted | Sample payloads the user uploaded or pasted. |
/workspace, /session | scratch, persisted | Working files, notes (/session/findings.md). |
/tmp | scratch | Not persisted. |
/artifacts | generated, read-only | Bulky command outputs (impact lists, render diffs) as JSON for jq. |
/.agents/skills | generated, read-only | Bundled skills. |
Every mutation (tools, bash, commands) runs in one copy-on-write transaction
per tool call. read returns a content revision, and write/edit can
require it, so concurrent edits are detected.
4. Commands
The shell offers the commands of cli.md whose effect is not
remote-write: the working copy, previews, reads, telegraf-parse,
vector-vrl,
creating, validating and submitting change sets, and exploring journey
targets on an exploration runner (remote-run). In the shell the CLI is
called sp: sp render is stratactl render. /spec is the working
copy, so sp spec pull without a directory (or with /spec) moves its base
to another revision; into any other directory it writes a plain copy. Bulky
outputs (impact lists, render diffs) also go to /artifacts as JSON for jq.
There is no command to review, merge, roll out, pin, write secrets or
change agents; those are remote-write and exist only in stratactl.
5. telegraf-parse and vector-vrl
Runs a Telegraf parser config on sample payloads, in the browser worker, without a Telegraf sandbox. The module, its ABI and where it runs are described in agents/telegraf.md §8.
telegraf-parse --version 1.40.1 --parser json_v2 --config /workspace/parser.toml \
[--metric-name http] [--time 2026-10-21T12:00:00Z] SAMPLE…
--version defaults to the most common version among the agents that the
working copy's layer targets. Output is the line protocol plus a summary
(metric count, tag and field keys, type conflicts); the full JSON goes to
/artifacts.
Caching. Results are deterministic for a fixed --time; the durable
session caches them by (version, parser, sha256(config), sha256(samples)),
so a resumed run does not recompute.
Data handling. Samples are often real payloads. The wasm runs locally in
the browser or CLI, so samples are not sent to Strataplane unless the user
uploads them into a chat (/samples/, stored with the chat). A sample
reaches the LLM only if the model reads the file; admins choose which
providers and models are available.
vector-vrl runs a VRL program, the source of a Vector remap
transform, on sample events the same way: in the browser worker, with the
module of agents/vector.md §8.
vector-vrl --version 0.60.0 --program /workspace/remap.vrl \
[--time 2026-10-24T12:00:00Z] [--timezone Europe/Berlin] EVENTS…
An events file holds a JSON array, one JSON value or JSON lines; - reads
stdin. --version defaults to the most common version among the tenant's
Vector agents. Output is one line of JSON per event (or dropped), the
compile diagnostics and runtime errors on stderr, and a summary; the full
JSON goes to /artifacts, cached like telegraf-parse by (version, sha256(program), sha256(events), timezone) for a fixed --time. Events,
like samples, stay in the browser.
6. Example: building a parser
user: Here is the JSON our payment API exposes at /status. I want queue depth and
latency per queue on all payment hosts. [attaches status.json]
assistant (bash):
$ jq 'paths | map(tostring) | join(".")' /samples/status.json | head
$ cat /catalog/telegraf/1.40.1/parsers/json_v2.json | jq '.properties | keys'
$ cat > /workspace/parser.toml <<'EOF'
[[inputs.http.json_v2]]
measurement_name = "payment_queue"
[[inputs.http.json_v2.object]]
path = "queues"
tags = ["name"]
[inputs.http.json_v2.object.fields]
depth = "int"
latency_ms = "float"
EOF
$ telegraf-parse --parser json_v2 --config /workspace/parser.toml /samples/status.json
payment_queue,name=settlement depth=12i,latency_ms=48.2 1792598400000000000
payment_queue,name=refunds depth=0i,latency_ms=3.1 1792598400000000000
$ sp hosts --selector 'app=payment,stage=prod' | wc -l
14
(edits /spec/layers/app-payment.toml: instance inputs.http.payment_status)
$ sp validate && sp impact --by stage
telegraf: 22 agents change (prod 14, preprod 8); approvals: team-payments, four-eyes (prod)
$ sp render pay03.example.com | rg -A12 'payment_status'
$ sp changeset submit -m "Payment queue metrics from /status"
CS-418 in review: https://strataplane.example.com/change-sets/418
7. Skills
| Skill | Content |
|---|---|
telegraf-authoring | Instance keys and aliases, layer vs bundle, parser iteration with telegraf-parse, namepass/routing conventions, how to read plugin schemas. |
elastic-agent-inputs | Input types, streams, use_output, processors, data stream naming. |
vector-authoring | Instance keys and component ids, inputs and wildcard routes, remap iteration with vector-vrl, settings.global and its restart, how to read component schemas. |
layering | Precedence bands, selectors, merge directives, explain-driven debugging, avoiding per-host layers. |
troubleshooting | Reading health/sync, heartbeat logs, component units; out-of-sync and stalled-watcher diagnosis. |
change-sets | Impact review, approval requirements, writing good change set descriptions. |
8. Journeys
The assistant writes Journey files under /spec/journeys/ like any spec
file and tries them on an exploration runner. Its mounts, commands and the
journey-authoring skill are in journeys.md §8.3.