Logger setup
internal/cli/root.go builds one *slog.Logger per process — a slog.NewTextHandler when log.format is empty or text, a slog.NewJSONHandler when it is json. Level maps from log.level (debug, info, warn/warning, error) with info as the default. Handler writes to stderr; every daemon inherits it.
For a production deployment, always set log.format: json. Downstream log pipelines (journald + journalctl -o json, Loki, Vector, Datadog Agent) parse structured output natively.
Output shape
Text
time=2026-07-13T18:00:14.202Z level=INFO msg=tool.execute name=grep id=t_1
Slog's default text layout: time, level, msg, then key=value pairs.
JSON
{"time":"2026-07-13T18:00:14.202Z","level":"INFO","msg":"tool.execute","name":"grep","id":"t_1"}
Same fields, JSON encoded. The msg field is the stable event identifier — filter and alert on it, not on human text.
Message vocabulary
Every message name emitted from internal/**/*.go is listed below with source location and expected level. Grouped by subsystem; alphabetised within a group.
Agent loop (internal/agent/)
| Message |
Level |
Fields |
Meaning |
agent.compressed |
INFO |
messages |
LLM compressor rewrote a session; the new message count is messages. |
agent.compress_failed |
WARN |
err |
The compressor returned an error; the session is left untouched. |
tool.denied |
WARN |
name, reason |
Approver blocked a tool call. Fields from internal/agent/agent.go:179. |
tool.execute |
INFO |
name, id |
The approver allowed and the tool ran. |
tool.error |
WARN |
name, err |
The tool ran but returned an error. |
turn.failed |
ERROR |
err |
The TUI turn errored. Emitted from internal/tui/model.go. |
session.save_failed |
WARN |
err |
Persisting a session failed post-turn. |
Cron (internal/cron/scheduler.go)
| Message |
Level |
Fields |
Meaning |
cron.started |
INFO |
poll_interval |
Scheduler boot. |
cron.scheduled |
INFO |
job, expr |
Job added to the in-memory schedule. |
cron.schedule_failed |
WARN |
job, expr, err |
robfig/cron/v3 rejected the expression. |
cron.sync_failed |
WARN |
err |
Reconcile pass against cron_jobs failed. |
cron.firing |
INFO |
job |
Job is about to run. |
cron.completed |
INFO |
job |
Job finished successfully. |
cron.run_failed |
ERROR |
job, err |
Provider call inside the job failed. |
cron.delivery_failed |
ERROR |
job, target, err |
Delivery to the transport failed. |
cron.record_failed |
WARN |
job, err |
Writing last_run_at failed. |
MCP (internal/mcp/server.go)
| Message |
Level |
Fields |
Meaning |
mcp.encode_error |
WARN |
err |
Could not JSON-encode a response (rare). |
mcp.tool_error |
WARN |
tool, err |
A tool handler returned an error; surfaced to the host with isError=true. |
Router (internal/transport/router.go)
| Message |
Level |
Fields |
Meaning |
transport.rejected |
WARN |
from |
Sender not in the allowlist; message dropped. |
router.save_failed |
WARN |
err |
Post-turn session save failed. |
router.stale_mapping |
WARN |
jid, err |
JID→session mapping pointed at a session that no longer loads. |
WhatsApp (internal/transport/whatsapp/)
| Message |
Level |
Fields |
Meaning |
whatsapp.starting |
INFO |
store, allowlist |
Bridge booting; store is the DSN. |
whatsapp.qr_ready |
INFO |
— |
QR rendered to stdout; scan it. |
whatsapp.qr_event |
WARN |
event |
Non-success QR event from whatsmeow. |
whatsapp.paired |
INFO |
— |
Phone accepted the QR. |
whatsapp.connected |
INFO |
— |
WebSocket to Meta is up. |
whatsapp.disconnected |
WARN |
— |
Lost the socket. Retries automatically. |
whatsapp.logged_out |
ERROR |
reason |
Meta logged the device out — usually a policy trip. |
whatsapp.voice_enabled |
INFO |
binary, model |
Voice-note transcription is on. |
whatsapp.incoming |
INFO |
from |
Inbound message accepted. |
whatsapp.skipped |
DEBUG |
reason |
Router discarded a message (self-echo, etc). |
whatsapp.empty_reply |
INFO |
elapsed |
The agent produced no text this turn. |
whatsapp.handler_ok |
INFO |
elapsed, bytes |
Reply delivered. |
whatsapp.handler_failed |
ERROR |
err |
Turn errored — usually a provider or tool failure. |
whatsapp.send_failed |
ERROR |
err |
Delivery to Meta failed. |
whatsapp.presence_failed |
DEBUG |
err |
Typing-presence write failed (best-effort). |
whatsapp.audio_ignored |
INFO |
size |
Voice note received but transcription is disabled. |
whatsapp.audio_downloaded |
INFO |
size |
Voice-note bytes fetched from Meta. |
whatsapp.transcribed |
INFO |
elapsed |
whisper.cpp returned a transcript. |
whatsapp.transcribe_failed |
ERROR |
err |
whisper invocation failed. |
Slack (internal/transport/slack/client.go)
| Message |
Level |
Fields |
Meaning |
slack.starting |
INFO |
allowlist |
Bridge booting. |
slack.started |
INFO |
— |
Socket Mode session accepted. |
slack.session_failed |
WARN |
err |
Failed to open the Socket Mode session; retry. |
slack.frame_failed |
WARN |
err |
Malformed frame from Slack. |
slack.incoming |
INFO |
from, channel, text |
Message accepted. |
slack.handler_failed |
ERROR |
err |
Turn errored. |
Discord (internal/transport/discord/client.go)
| Message |
Level |
Fields |
Meaning |
discord.starting |
INFO |
allowlist |
Bridge booting. |
discord.ready |
INFO |
bot_id |
Discord gateway ready. |
discord.started |
INFO |
— |
Session up. |
discord.session_failed |
WARN |
err |
Gateway open failed; retry. |
discord.frame_failed |
WARN |
err |
Bad frame from Discord. |
discord.incoming |
INFO |
from, channel |
Message accepted. |
discord.handler_failed |
ERROR |
err |
Turn errored. |
Telegram (internal/transport/telegram/client.go)
| Message |
Level |
Fields |
Meaning |
telegram.starting |
INFO |
allowlist |
Bridge booting. |
telegram.started |
INFO |
— |
First long-poll succeeded. |
telegram.poll_failed |
WARN |
err |
Long-poll HTTP failed. |
telegram.incoming |
INFO |
from |
Message accepted. |
telegram.handler_failed |
ERROR |
err |
Turn errored. |
telegram.send_failed |
ERROR |
err |
Outbound HTTP failed. |
Matrix (internal/transport/matrix/client.go)
| Message |
Level |
Fields |
Meaning |
matrix.starting |
INFO |
homeserver, allowlist |
Bridge booting. |
matrix.started |
INFO |
homeserver |
First /sync accepted. |
matrix.sync_failed |
WARN |
err |
/sync HTTP failed. |
matrix.incoming |
INFO |
from, room |
Message accepted. |
matrix.handler_failed |
ERROR |
err |
Turn errored. |
matrix.send_failed |
ERROR |
err |
Outbound HTTP failed. |
Signal (internal/transport/signal/)
| Message |
Level |
Fields |
Meaning |
signal.starting |
INFO |
account, allowlist |
signal-cli JSON-RPC subprocess starting. |
signal.started |
INFO |
— |
Subprocess reported ready. |
signal.frame_failed |
WARN |
err |
Malformed JSON frame from signal-cli. |
signal.stderr |
WARN |
line |
Passthrough of signal-cli stderr. |
signal.incoming |
INFO |
from |
Message accepted. |
signal.handler_failed |
ERROR |
err |
Turn errored. |
iMessage (internal/transport/imessage/client.go)
| Message |
Level |
Fields |
Meaning |
imessage.starting |
INFO |
base |
BlueBubbles server URL logged. |
imessage.started |
INFO |
server |
First poll succeeded. |
imessage.prime_failed |
WARN |
err |
Priming state fetch failed; retries. |
imessage.poll_failed |
WARN |
err |
Poll HTTP failed. |
imessage.incoming |
INFO |
from |
Message accepted. |
imessage.handler_failed |
ERROR |
err |
Turn errored. |
imessage.send_failed |
ERROR |
err |
Outbound HTTP failed. |
Email + SMS (internal/transport/email/, internal/transport/sms/)
Follows the same <transport>.starting / .started / .poll_failed / .incoming / .handler_failed / .send_failed shape as the polling transports above.
Recipes
Show every failed tool call today
journalctl --user -u rousseau-agent --since today -o json \
| jq -c 'select(.MESSAGE | fromjson? | .msg == "tool.denied")'
Follow a single transport session live
journalctl --user -u rousseau-agent -f -o cat \
| grep -E 'whatsapp\.|tool\.|cron\.'
Alert on cron failures
Prometheus/alertmanager rule sketch (via the promtail → Loki → alert pipeline in Guides: Observability):
- alert: RousseauCronFailure
expr: |
sum by (job) (
count_over_time({app="rousseau-agent"} |= "cron.run_failed" [5m])
) > 0
Redacting
slog does not redact by default. Configure a downstream processor to redact err fields on whatsapp.send_failed, tool.error, etc. — provider errors can occasionally include prompt fragments. See Guides: Observability for the pipeline.
Related