AI.md, AI_AUTONOMY.md and PULSE_PRO.md linked nine times into docs/architecture/, which .gitignore marks as not for public release. The targets were never missing, they were deliberately unpublished, so every one of those links was dead for anybody but the maintainer. Write the three promised documents against the code and publish them under docs/ where the rest of the shipped set lives. PATROL_ARCHITECTURE.md covers a run end to end. The interesting part is that deterministic signal detection runs after the model, not before, so unmatched signals catch what the model failed to file. Signal types, the thresholds those signals derive from your own alert settings, and every condition in Finding.ShouldInvestigate are documented from internal/ai/patrol_signals.go and internal/ai/findings.go. ASSISTANT_SAFETY.md documents the session state machine in internal/ai/chat/fsm.go, its four states, the tool classification it runs on, and its invariants. No write without a validated target, no second write before the first is verified, no final answer about an unverified change, and no attempt count that wears the gate down. ASSISTANT_ARCHITECTURE.md covers the agentic loop around that machine, the three-phase pipeline, why only execution parallelises and at what cap, the read-before-write batch that must stay ordered, the look-before-asking gate and its bound, and the stable error codes. Also removed two older pointers into the same private directory, in API.md and UPGRADE_v6.md, and the two references to ENTITLEMENT_MATRIX.md in PULSE_PRO.md. That file exists locally and is a monetization document, so publishing it is not a documentation decision. Unresolvable intra-doc links are now 2 of 218, both internal release-control documents deliberately withheld from the shipped set. Contract-Neutral: documentation only
4.1 KiB
Pulse Assistant safety architecture
The state machine that governs what the Pulse Assistant is allowed to do during a chat session, the tool classification it runs on, and the invariants it holds.
The point of this machine is structural. Prompt wording can be argued with by
a model, and drifts as prompts are edited. These rules are enforced in code,
in internal/ai/chat/fsm.go, so neither a model nor a future prompt change
can talk its way past them.
Tool kinds
Every tool call is classified before it runs.
| Kind | Meaning |
|---|---|
resolve |
Discovery and query tools that find resources |
read |
Read-only tools such as logs, metrics, status, and config |
write |
Mutating tools such as restart, stop, start, delete, and file write |
user_input |
Interactive tools that ask you something |
Classification is ClassifyToolCall, which delegates to the shared
agentcapabilities classifier so the Assistant and the rest of the agent
surface agree on what counts as a write.
States
A session starts in RESOLVING and moves between four states.
| State | What it means |
|---|---|
RESOLVING |
No validated target yet, so resources must be discovered first |
READING |
A target is established and querying is allowed |
WRITING |
Transitional, entered around a mutation |
VERIFYING |
A write happened and evidence has not been gathered since |
Transitions on a successful tool call are as follows.
- A
resolveorreadinRESOLVINGmoves the session toREADING. - A
writefrom any state moves the session toVERIFYING, records the tool and timestamp, and clears the read-after-write flag. - A
resolveorreadwhile inVERIFYINGsets read-after-write, which is what satisfies the verification requirement. - A
user_inputcall does not advance state at all, because asking you a question is neither discovery nor verification.
CompleteVerification returns a verified session from VERIFYING to
READING so further writes become possible.
The invariants
No writing without a validated target. A write attempted in RESOLVING
is blocked. The model must establish what it is acting on before it acts.
No writing again until the last write is verified. A write attempted in
VERIFYING is blocked until a read or resolve has run since the write.
No final answer about an unverified change. CanFinalAnswer refuses while
the session is in VERIFYING with no read-after-write. The Assistant cannot
tell you it restarted something and then decline to look at whether the
restart worked.
Repeated attempts do not wear the gate down. Consecutive blocked writes in
VERIFYING increment a counter, and that counter is telemetry only. There is
no attempt threshold after which the verification requirement is waived.
Reads are never blocked. No state blocks a read, resolve, or
user_input. The machine constrains mutation and the claims made about
mutation, not information gathering.
Blocked calls and recovery
A blocked call returns an FSMBlockedError carrying the state, the tool, the
tool kind, a reason, and a recoverable flag. It surfaces to the model with the
stable code ErrCodeFSMBlocked rather than as prose, so the model can branch
on the code.
Blocks are recoverable rather than terminal. The session tracks a pending recovery per blocked operation, and a later successful call of the same tool clears it. Pending recoveries expire after ten minutes.
Resetting
Reset returns the session to RESOLVING and clears all tracking, which is
what a full session clear does.
ResetKeepProgress is the softer variant used when context is cleared but
pinned items are kept. It drops verification tracking and moves a VERIFYING
session back to READING, without discarding that a target was established.
Note that WroteThisEpisode means "wrote at all during this session" rather
than "wrote during the current verification cycle", and CompleteVerification
deliberately leaves it set.
Related reading
- AI features for the overview and configuration.
- Patrol deep dive for the scheduled analysis runtime, which is a separate loop from the Assistant.