| # | Feature | Status | Notes |
|---|---|---|---|
| 10.1 | Debug mode (--debug / config.debug) |
Done | |
| 10.2 | LLM request/response logging | Done | Provider, model, message count, elapsed |
| 10.2a | Verbatim prompt/response capture | Done | GOCLAW_LOG_PROMPTS=1 (or true), which also turns debug mode on — see below |
| 10.2b | Reasoning-model thinking progress | Done | THINKING n chars for DeepSeek v4 / o-series and Gemini thinking |
| 10.3 | Tool call/result logging | Done | Args, result length, elapsed, errors |
| 10.4 | Gateway event logging | Done | Inbound/outbound with session/content info |
| 10.5 | Error chain unwrapping | Done | Full errors.Unwrap chain in debug mode |
| 10.6 | Startup banner | Done | Provider, model, port in box |
| 10.7 | SSE log streaming to web UI | Done | Real-time logs in browser |
| 10.8 | Live agent traces | Done | A request's trace is on the Traces page from its start — see below |
Every request — a chat turn, a webhook, a cron job, a SOP run, a subagent run — has a trace on the Traces page: its model calls, tool calls and retrievals, with their inputs, outputs, tokens and timings, grouped by SOP step.
The list shows the tokens each trace used, prompt and output together, marked
from 200k (heavy) and 500k (a SOP run's default limit, sop.max_run_tokens).
It can keep only the traces past a token count (50k, 200k, 500k or 1M) and list
the ones that used the most first, so a costly run is found without opening
traces one by one.
A trace is on the page from the moment its request starts, not only when it ends. This follows how agent tracing systems such as LangSmith work: a run is created when it starts and updated as its children finish. While it runs:
The gateway writes a running trace at most every 3 seconds, and only when something in it changed, so a long SOP run is not rewritten for every span. When the request ends, its final write replaces the running one. A call that was still open when the request ended (a model call that failed, a tool left behind by a cancelled attempt) is kept in the trace, marked unfinished.
If the gateway stops while a request runs, its trace stays as it was last written. At the next start every trace still marked running is set to interrupted, with its last write as its end.
A request whose data a limit cut is tagged ⚠ data cut in the list. In the trace each cut is a ⚠ span, under the SOP step it happened in, that says what was cut, by which setting, and where the rest is:
| Span | What happened |
|---|---|
| answer cut at the output limit | The model stopped its answer at its output limit (sop.max_output_tokens in a SOP step, ai.gemini.max_output_tokens on Gemini, 4,096 for a chat reply elsewhere). The rest was never written. |
| earlier output cut in the prompt | Only the first part of an earlier SOP step's output went into this step's prompt (sop.inline_output_chars); the step was given the data_ref of the rest. |
| tool results taken out of the context | The context reached memory.auto_flush_tokens, and the request's older tool results left it; the model was given the ref of each. |
| items not run past max_items | A map step left out the items past its max_items. |
| result cells cut | mcp_call's results cut long cells; results_ref holds them whole. |
The same cuts are listed under the step in the SOP's run history, and the log
has a [WARN][data-cut] line for each. A tool span's output on this page is
itself kept to its first 16,000 characters, and says so: that cut is the
trace's, not the data's.
--debug logs the shape of each LLM call — provider, model, message count,
token counts, elapsed time — but not the text. To reproduce a bad answer you
need the exact bytes that were sent, so there is a separate switch:
GOCLAW_LOG_PROMPTS=1 goclaw
(GOCLAW_LOG_PROMPTS=true works too.) The dump goes through the debug logger,
so this switch turns debug mode on as well.
This writes every prompt and every response verbatim, with no truncation, for both agent loops (langchaingo and the native Gemini loop):
[DEBUG][LLM]
╔═══ PROMPT ═══════════════════════════════════════════════
║ [0] role=system chars=8214
╟──────────────────────────────────────────────────────────
You are GoClaw, a helpful agent.
...
║ [1] role=human chars=46
╟──────────────────────────────────────────────────────────
Show the last 7 days' usage as a table
╚══════════════════════════════════════════════════════════
[DEBUG][LLM]
╔═══ RESPONSE ═════════════════════════════════════════════
(no text content)
║ tool_call: memory_search({"query":"usage"})
╚══════════════════════════════════════════════════════════
Tool calls and tool results appear inline as [tool_call …] and
[tool_result …], so a multi-iteration turn can be replayed step by step.
Why this is not part of
--debug. A prompt contains the entire system instruction, the agent's memory, and every tool result from the turn. That routinely includes credentials, customer data and tens of thousands of characters. Turning on debug logging to diagnose a timeout should not silently start writing all of that to disk — opting in has to be deliberate. Do not leave it enabled in production.
DeepSeek v4 and the OpenAI o-series stream their chain of thought in a separate
reasoning_content delta while the content delta stays empty; Gemini models
with thinking send it as thought parts. A measured
deepseek-v4-flash turn produced 61 stream events, 25 of them with no content.
GoClaw consumes the reasoning stream separately: empty deltas never reach the channel, and thinking progress is reported on its own line so a long pause is visible rather than looking like a hang.
[DEBUG][LLM] THINKING 2000 chars (not shown to the user)
[DEBUG][LLM] THINKING 4000 chars (not shown to the user)
[DEBUG][LLM] STREAM chunk #1 len=12
The chain of thought itself is not forwarded to the user; only the final answer is delivered.
Native Go tools for SRE/DevOps workflows — all use net/http (no external binaries).
| # | Tool | Platform | Status | Notes |
|---|---|---|---|---|
| 10.9 | graylog_query |
Graylog | Done | Full-text log search, time-range filtering, field extraction |
| 10.10 | uptrace_query |
Uptrace | Done | UQL trace queries, project listing, service topology (dependency graph), compressed span listing (limit default 15, at most 50) |
| 10.11 | zabbix_query |
Zabbix | Done | Host metrics, triggers via JSON-RPC API; event.acknowledge needs allow_write on the instance |
| 10.12 | grafana_alerts |
Grafana | Done | Active alerts and dashboards; annotate needs allow_write on the instance |
| 10.13 | wazuh_query |
Wazuh | Done | FIM/syscheck events, agent security status |
| 10.14 | netbox |
NetBox | Done | IPAM/DCIM lookups (list, get, rack elevation); create, update, patch, delete and the other write actions need allow_write on the instance |
| 10.15 | graylog_alert |
Graylog | Done | Registers a Graylog alert (event definition + HTTP notification) and the webhook route /goclaw/hook/graylog/<key>; each alert runs a SOP. Human admin in a conversation only, Graylog 5.0+, allow_write on the instance — see Graylog Alerts That Run a SOP |
observability SkillGuides the LLM through dynamic topology mapping and multi-source incident correlation:
flowchart LR
S1["1. grafana_alerts<br/>Discover firing alerts"] --> S2["2. uptrace_query<br/>Map service dependencies"]
S2 --> S3["3. graylog_query<br/>Search related error logs"]
S3 --> S4["4. zabbix_query<br/>Check host CPU/memory/load"]
S4 --> S5["5. wazuh_query<br/>Check security events"]
S5 --> S6["6. Consolidate<br/>Single cohesive analysis"]
Every tool reads named instances from tools.observability in config.yaml (an agent's own
tools: block can override it). With one instance most tools use it without being told; with
several, the call names one (instance=prod).
tools:
observability:
graylog:
main:
url: "https://graylog.example.com/api"
token: "${secret:GRAYLOG_TOKEN}"
allow_write: false # true lets graylog_alert register alerts
uptrace:
main:
dsn: "https://<token>@api.uptrace.dev/<project>"
zabbix:
prod:
url: "https://zabbix.example.com/api_jsonrpc.php"
token: "${secret:ZABBIX_TOKEN}"
grafana:
prod:
url: "https://grafana.example.com"
token: "${secret:GRAFANA_TOKEN}"
wazuh:
siem:
url: "https://wazuh.example.com:55000"
user: "wazuh-api"
password: "${secret:WAZUH_PASSWORD}"
netbox:
dcim:
url: "https://netbox.example.com"
token: "${secret:NETBOX_TOKEN}"
allow_write: false # true lets netbox create, update and delete
More on each tool's keys and actions is in SRE Tools; the complete set of keys is the config structs in internal/config/config_types.go.