SentnelOpsSentnelOpsbeta
LAYER 2 · LIFECYCLE & POLICY

How lifecycle & policy work

The state machine that governs what an agent may become, the precedence ladder that decides what it may call, the YAML those rules live in, and the cadence that keeps humans in the loop.

The state machine

draft ──approve──▶ approved ──activate──▶ active ⇄ suspended
  │                                          │  (suspend / reactivate)
  │                                          │
  └── production agents start here           ▼
      dev & staging start active       decommissioned (terminal)

Transitions are validated by pure functions before any database write — an illegal move never half-happens. Five rules carry the compliance story:

RuleWhat the API does
Segregation of dutiesThe approver must differ from the agent's creator — self-approval returns 403 segregation_of_duties, no exceptions, no role high enough to bypass it.
No skipping approvalActivating a draft agent directly returns 409 approval_required. The only path to active runs through a second person.
Scope frozen while suspendedSuspension requires a reason string (400 reason_required without one), and while suspended, edits to allowed_mcp/denied_mcp return 409 scope_frozen — nobody quietly widens a quarantined agent.
30-day stale suspensionA suspension under 30 days old is undone with reactivate. Over 30 days, reactivate returns 409 reapproval_required — the agent must go back through approve, with a fresh second person on record.
Terminal decommissionAny state can be decommissioned. It revokes every active token at that instant, snapshots the final agent row into the audit log, and after it every transition returns 409 terminal_state.

The starting state is environment-aware: production agents are created as draft; dev and staging agents self-activate at registration, so the approval chain never slows a developer's inner loop.

Policy evaluation precedence

Every decision — advisory via POST /policy/check or live in layer 3 — walks the same ladder, top to bottom, first match wins:

1. agent status ≠ active         → deny   ("agent status is 'suspended', not active")
2. server in denied_mcp          → deny   (policy_rule: denied_mcp)
3. server not in allowed_mcp     → deny   (policy_rule: default_deny)
4. no tool policy for the agent  → allow  (policy_rule: allowed_mcp)
5. tool in the server's deny     → deny   (policy_rule: <server>.deny)
6. tool in the server's allow    → allow  (policy_rule: <server>.allow)
7. deny_unlisted (default true)  → deny   (policy_rule: deny_unlisted)
   deny_unlisted switched off    → allow  (policy_rule: allow_unlisted)

The verdict is a Decision with three fields: permitted (boolean), reason (a human-readable sentence), and policy_rule (the name of the rule that won). Every response and log row carries all three, so a decision is always explainable after the fact. Note rule 1: lifecycle state is policy input — a suspended or decommissioned agent fails every check before any list is consulted.

The YAML policy

Your organization edits its own policy from the Policy page (security role): validate, save — live within ~5 seconds — or revert to the platform default. Your org's policy overlays the platform default below and is isolated from every other tenant. Self-hosted deployments can still manage the default as files on disk.

policies/example.yaml
policies:
  defaults:
    deny_unlisted: true              # tools not listed are denied (shipped default)
    high_risk_requires_review: true

  prod-remediation:                  # key = agent id OR agent name
    github-mcp:                      # key = registered MCP server name
      allow: [read_file, list_repos, create_issue]
      deny: [delete_repo, force_push]
      blast_radius: {delete_repo: 0.9, force_push: 0.7, read_file: 0.1}
    aws-prod:
      allow: [ec2.describe, cloudwatch.read]
      deny: [ec2.terminate, iam.modify]
      blast_radius: {ec2.terminate: 0.9, iam.modify: 0.8}

Under policies:, each key except defaults is an agent — matched by id first, then name. Under an agent, each key is an MCP server, holding up to three things: an allow list of tool names, a deny list, and a blast_radius map scoring each tool 0.0–1.0 for "how bad if this goes wrong?". An agent with no entry at all keeps its server-level decision — tool-level policy is adopted one agent at a time.

Loading and hot reload

BehaviorDetail
MergeEvery *.yaml/*.yml in the policies directory is loaded and merged, sorted by filename — later files win on collisions.
Hot reloadFiles are re-read whenever any file's mtime changes. An edit lands on the next evaluation — no API restart, no deploy.
Strict validationA malformed file fails loudly at load, naming the file and the offending key. There is never a silently-empty policy.
Version controlKeep the YAML in git. A policy change is a diff, a review, and a merge — the same discipline as any other code.

Review cadence

Every agent carries a next_review deadline derived from its risk level. A daily sweep flags what is overdue — as audit events, not status changes; reviews are advisory pressure, not an outage mechanism.

Risk levelReview cadenceWhen overdue
highevery 30 daysOne review_overdue audit event per missed deadline; surfaced on the dashboard and governance report
mediumevery 90 daysSame — flagged once per deadline, not once per sweep run
lowevery 180 daysSame; agents past their expires_at are additionally flagged expired
Marking a review done (POST /agents/:id/review) sets last_review to now and advances next_review by the cadence. It is a record of human attention, not a state transition.