Send the log to Elastic
The Elastic app streams every event in the box's action log into an Elasticsearch Serverless index, box-actions. It also installs an Elastic Agent Builder agent, GurtOS log (id gurtos-log), that answers questions about that index in plain words. In this guide you connect a box to Elastic, install the agent and ask it a question.
Before you start
Section titled “Before you start”- A box with a root shell and the Elastic app installed (it ships with GurtOS, in
tracks/elastic/). - An Elasticsearch Serverless project, with its Elasticsearch URL and Kibana URL.
- An Elastic API key that can write to the
box-actionsindex and manage Agent Builder tools and agents. - Node 22 or newer on the machine you install the agent from. That can be the box or your own computer.
-
Add the keys to the box's vault. See Add a secret.
ELASTIC_URL=https://your-project.es.example.elastic.cloudKIBANA_URL=https://your-project.kb.example.elastic.cloudELASTIC_API_KEY=your-elastic-api-keyRestart the box service. The app starts sending at once. On its first run it sends the whole log from the start.
-
Install the agent and its tools. From a checkout, with the same three values in your environment:
export ELASTIC_URL=https://your-project.es.example.elastic.cloudexport KIBANA_URL=https://your-project.kb.example.elastic.cloudexport ELASTIC_API_KEY=your-elastic-api-keynode tracks/elastic/agent.ts installThis creates or updates the seven tools and the agent through Kibana's Agent Builder API. It is safe to run again: the agent in Kibana always matches the code.
-
Ask a question. In Kibana, open Agents > GurtOS log, or go straight to
<KIBANA_URL>/app/agent_builder/agents/gurtos-log. Or from the command line:node tracks/elastic/agent.ts ask "What got blocked this week, and from where?"The answer ends with the tools the agent called.
By default the tools read only installed boxes, whose events have
box.runset toinstalled, so test runs never mix into an everyday answer. To ask about a run of the test suite, name it in the question, for example "What did my agents change this week? Look at demo run a18523."
Check it worked
Section titled “Check it worked”- In Kibana,
box-actionshas one document per log event. The count matches the box's log. - The agent answers "What did my agents change this week?" with the files each done task changed, and lists undone and failed tasks apart.
The box's test suite checks this app offline. DEMO_LIVE=elastic ./demo also ships a run to Elastic and asks four questions.
How it works
Section titled “How it works”- A log reader gets every event in order, from the start, and resumes after a restart. It turns each event into one flat document and appends it to an outbox file,
/var/lib/box/elastic/outbox.ndjson, synced to disk. - A flusher sends the outbox to Elastic with
_bulk, up to 4 MB per request, and remembers how far it got. If Elastic is down, it retries every 5 seconds from where it stopped. - Each document's id is built from the machine, the run and the event's line number, so a resend overwrites itself and nothing is counted twice. Only an installed box (
BOX_HOME=/var/lib/box) and aDEMO_LIVE=elastic ./demorun send anything; other test boxes stay out of the index. - Only the
elastic.bulkmodel job readsELASTIC_URLandELASTIC_API_KEY, inside the box service. Tasks never see the key. The log reader has no vault access at all.
With no Elastic keys in the vault, the outbox keeps everything until you add them.
What a document holds
Section titled “What a document holds”Each document stands on its own, so a question needs no joins.
| Field | Holds |
|---|---|
@timestamp, event.seq, event.type |
When, the line number in the log, and the kind of event |
message |
One plain sentence about the event |
task.id, task.title, task.source, task.hosts, task.budget_cents |
Copied onto every event of a task |
app |
The app that started the task |
task.state, error, summary |
On state changes |
files.added, files.modified, files.deleted |
On done events, from the box's diff |
cost_cents, cost.model, cost.tokens.* |
What the event spent: model tokens or a paid tool call |
tool.name, tool.ok, tool.error |
Tool calls and their results |
approval.question, approval.state, approval.by |
Requests for an OK and who answered |
net.host |
A connection the box refused |
box.name, box.machine, box.run |
Which box the event came from. Two boxes can share a name, never a machine. box.run is installed on a real box and the run id on a test-suite run. |
The agent's tools
Section titled “The agent's tools”| Tool | Answers |
|---|---|
box.changes |
Tasks done, undone or failed, with the files each done task changed |
box.costs_by_app |
Dollars per app: total, model, tools, number of tasks |
box.costs_by_task |
Dollars per task, with the paid tools it used |
box.failures |
Failed tasks and the error the box recorded |
box.task_timeline |
Everything one task did, in order |
box.blocked |
Refused hosts by task and app, with counts and allowed hosts |
box.search |
Free text over the log |
The first six are ES|QL tools. Those that take days default to 7, and those that take run default to installed. box.search is an index search tool.
Limits
Section titled “Limits”- A failed task's error is "worker exited with N" plus the worker's last error message. When that message is generic, the agent reads the task's timeline to find the cause, which usually works but is not exact.
tool.resultevents carry no tool name, so the reader takes it from thetool.callbefore it. A restart between the two loses the name on that one result. The cost and the task are still right.
Related
Section titled “Related”GurtOS is open source under the Apache License 2.0.Source code on GitHub.