Pulse/docs/ASSISTANT_SAFETY.md
rcourtman 63fc89f701 Write the deep-dive docs the AI pages promised
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
2026-08-03 22:20:57 +01:00

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 resolve or read in RESOLVING moves the session to READING.
  • A write from any state moves the session to VERIFYING, records the tool and timestamp, and clears the read-after-write flag.
  • A resolve or read while in VERIFYING sets read-after-write, which is what satisfies the verification requirement.
  • A user_input call 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.

  • AI features for the overview and configuration.
  • Patrol deep dive for the scheduled analysis runtime, which is a separate loop from the Assistant.