// docs
Logs
Every job, on every host, in one place — traceable by job ID, mirrored to the API, and with the agent's full conversation history on disk.
Where everything lives
Everything is written under C:\ProgramData\ServerEngine\Logs — always machine-wide, even when the app itself runs in user scope. That is deliberate: the service writes as LocalSystem in session 0, and the console has to read the same files.
Logs\
├─ logs.html console Logs tab, live history
├─ service.log headless engine log (service mode)
├─ Saved\hhmmss-yyyyMMdd_logs.html archived snapshots (manual)
├─ Failed\[yyyy.MM.dd][hh-mm-ss]<tag>.txt per-failed-job console output
└─ Sentri\
├─ yyyy-MM-dd_hhmm_<jobId>.txt scheduled / unattended transcript
└─ Chat-<USERNAME>-yyyy-MM-dd.txt interactive chat historySaved\ and Failed\ are created at startup; Sentri\ appears the first time the agent writes to it.
The engine log
A single logging call fans out to four places at once. Every line is formatted [yyyy.MM.dd][hh:mm:ss] text and carries a display colour — black, blue, red, darkGreen or darkMagenta — which travels through the API unchanged.
| Sink | What it is for |
|---|---|
| Windows Event Log | Source ServerEngine, event ID 1337 — so your existing event collector picks it up with no extra agent. |
| Console signal | When the engine runs in-process, the Logs tab appends the line directly. |
| In-memory ring | The last 1,000 lines. This is what GET /api/v1/logs serves. |
| service.log | Append-only, written in service mode only — so a headless run is still verifiable with no console attached. |
Each entry in the ring carries a monotonic sequence number, so a poller asks for everything after N and never sees a line twice. Positions cannot be used as cursors, because the ring drops old entries as it fills. A broken log path is reported once rather than on every line.
The Logs tab
On startup the console restores logs.html, then merges any lines it has never seen from service.log — capped at 1,000 entries, merged in chronological order, and de-duplicated on exact text so genuine same-second repeats survive. Continuation lines without a timestamp attach to the entry above them.
From then on the console mirrors the service live, polling GET /api/v1/logs?from=<seq>. On first contact — or if the sequence counter goes backwards, which means the service restarted — it falls back to the file merge, so nothing filed between restore and the first poll is lost.
Save archives the current log to Saved\hhmmss-yyyyMMdd_logs.html and clears the live file. Failed jobs additionally get their own console dump under Failed\, which is what the failed-log button opens.
S.E.N.T.R.I history
The agent keeps two separate trackers — different processes, different writers, different formats.
Unattended runs
A scheduled or API-triggered prompt is a real job: it shows up on the dashboard and in GET /api/v1/jobs like any other, with the host S.E.N.T.R.I and the first 60 characters of the prompt as its name.
The job's output is the transcript. It is written as the run happens, so GET /api/v1/jobs/<id>/output lets you watch the agent think in real time:
S.E.N.T.R.I unattended run
Schedule: <id>
Provider: OpenRouter Model: <model> Effort: <effort|off>
Started: yyyy.MM.dd hh:mm:ss
Allowed skills: <list|(none)>
── User ──
── S.E.N.T.R.I ──
── Skill: <name> ──
── Error ──
Run completed after N turn(s).When the run ends, a durable copy is written to Logs\Sentri\yyyy-MM-dd_hhmm_<jobId>.txt. That file exists because the live output is held in memory and dies with the service. Two lines also land in the engine log for the timeline: a per-turn token count, and a green or red completion line carrying the job ID and turn count.
Interactive chat
Chat in the app is recorded to Logs\Sentri\Chat-<USERNAME>-yyyy-MM-dd.txt — one file per user per day. The username is in the filename because Logs\ is shared ProgramData: on a shared admin box every operator keeps their own history.
[hh:mm:ss] ── User [<USERNAME>] ──
patch my servers
[hh:mm:ss] ── S.E.N.T.R.I (<model> / <provider>) ──
Checking 12 hosts for pending updates…
[hh:mm:ss] ── System ──
New conversation startedThese files are append-only and write-only — nothing in the app reads them back, they are purely for audit. Empty or interrupted turns are skipped, so a cancelled reply leaves no orphan block behind.
Masking
Masking follows the mask sensitive data setting, which is on by default. Chat history is masked before every write, and the unattended transcript is masked when the file is written.
The live API transcript is not masked
GET /api/v1/jobs/<id>/output while the run is still going is unmasked. Worth knowing before you put that endpoint behind a shared dashboard or forward it off the box.Retention
There is no automatic retention of log files. Auto-cleanup clears completed jobs from the queue roughly ten seconds after it goes idle, but it does not touch anything on disk. In practice service.log and everything under Sentri\ and Failed\ grow without bound, and logs.html only rotates when someone archives it.
Grab the file, not the endpoint
/jobs/<id>/output transcript disappears shortly after a run completes. The file under Logs\Sentri\ is the permanent record — point your archiver at that.Custom log lines
Emit your own messages into the ServerEngine log from any script with a simple Write-Host marker — good for surfacing meaningful checkpoints in a long workflow:
Write-Host '<WRITE-LOG = "*Backup completed: 4.2 GB archived*">'Wrap the message in * asterisks inside the marker. A common pattern is a small helper so steps can log cleanly:
function Write-Log {
param($Message)
Write-Host "<WRITE-LOG = `"*$Message*`">"
}
Write-Log "Step 2 complete: 148 running services"Read logs from the API
/logs endpoint, so you can tail ServerEngine's activity from a dashboard or forward it into your own log pipeline.