Playbook advisor
advisorOptionally let a model read signals and norms and propose one of the app's declared playbooks with a reason a person approves; off by default.
advisorFree in your app, Apache-2.0Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
advisor lets a model read your signals and propose one of the playbooks you declared, with a reason. A person approves. It is off unless declared, and it never runs on the request path.
At a glance
advisor:
on: [attack.suspected, anomaly.detected]
may: [attack.contain, pressure.slim]
run: approve
model: anthropic/claude-sonnet-4-5 # optional; the first of ai.models by default
feature: advisor # the ai feature its calls are made underimport { advisor } from "mainsdk/experimental/advisor";
const p = await advisor.propose();
console.log(p.choice, p.reason, p.evidence);Drivers
1 drivers. Select one to see its line in main.yaml.
advisor:
driver: builtinChange this one line to swap providers; your calls stay the same.Built-in and local drivers integration
Codes
| Code | Where it appears | Meaning |
|---|---|---|
MAIN-ADVISOR-0001 | Checkmainsdk check, before deploy | advisor is invalid: may empty or naming an undeclared rule, run: auto with a rule in may that has an app or people step, an unknown wake signal, or no agents.advisor |
MAIN-OPS-0001 | Checkmainsdk check, before deploy | An ops value is invalid: write: auto, a port outside 1024 to 65535, or http: on in production without ops.agent declared under agents |
MAIN-ADVISOR-0002 | RuntimeError responses and logs | See the spec |
MAIN-ADVISOR-0003 | RuntimeError responses and logs | See the spec |
MAIN-ADVISOR-0004 | RuntimeError responses and logs | See the spec |
MAIN-OPS-0002 | RuntimeError responses and logs | See the spec |
MAIN-OPS-0003 | RuntimeError responses and logs | See the spec |
MAIN-OPS-0004 | RuntimeError responses and logs | See the spec |
MAIN-OPS-0006 | RuntimeError responses and logs | See the spec |
Features
Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.
| Feature | Phase | Milestone | Spec ticket |
|---|---|---|---|
| Proposals limited to declared playbooks | Phase 2 | 0.6 | advisor#propose |
| Offline local driver for tests | Phase 2 | 0.6 | advisor#offline |
| The operate surface over MCP | Phase 1 | 0.4 | ops-mcp#tools-read |
Quick start
advisor:
on: [attack.suspected, anomaly.detected]
may: [attack.contain, pressure.slim]
run: approve
agents:
advisor: { budget: { spend: $2/day, calls: 50/day } }import { advisor } from "mainsdk/experimental/advisor";
const p = await advisor.propose();
console.log(p.choice, p.reason, p.evidence);Configuration
| Key | Default | Meaning |
|---|---|---|
on | none | Signals that wake it, at medium severity or above |
may | required | Playbooks it may propose; at least one |
run | approve | approve holds every proposal for a person. auto is allowed only when every step of the chosen rule is a notice or instance step |
window | 15m | How far back it reads signals |
max-per-hour | 4 | The most times it runs in an hour |
feature | advisor | The ai feature that sets models, redaction and limits |
model | first of ai.models | The model it asks |
What you get
Server only.
| Call | What it does |
|---|---|
advisor.propose({ signals? }) | Ask for a proposal now. Returns a Proposal with choice (one of may, or none), reason (up to 600 characters), evidence (signal ids), confidence (0 to 1, shown but never used to skip approval) and state |
advisor.get(id) | Read one proposal |
advisor.list({ cursor?, limit? }) | List proposals, newest first |
A proposal with a choice starts a playbook run with source advisor, held for approval unless run: auto applies.
Always:
- Chooses only from declared playbooks, or none.
- Never approves, and approval is the default.
- Sees signals and norms only, redacted, under 16 KB. It never sees logs, request content, app data, config values, secrets or user identifiers.
- Calls the model through
aias agentadvisor, so the model allowlist, region, budget and audit apply.
Examples
With ai.driver: local, replies come from the scripted fixture you give the test app:
import { createTestApp } from "@mainsdk/testing";
const app = await createTestApp({
ai: { replies: [JSON.stringify({ choice: "attack.contain", reason: "Many failed sign-ins", evidence: ["s1"], confidence: 0.8 })] },
});
const p = await app.advisor.propose(); // evidence aliases s1..sN map back to real signal idsErrors and checks
| Code | Meaning |
|---|---|
MAIN-ADVISOR-0001 | Check: advisor is invalid, for example may is empty or names an undeclared rule |
MAIN-ADVISOR-0002 | The model's output didn't fit; it was refused and logged |
MAIN-ADVISOR-0003 | The advisor stopped (budget, rate or model failure); playbooks carry on |
MAIN-ADVISOR-0004 | The input matched a secret or personal-data detector; the field was dropped |
Each code's cause and fix is in check codes.
Swapping drivers
| Driver | Provider | |
|---|---|---|
builtin | Built in, through the ai interface | default |
The advisor asks whichever ai driver the app uses. Proposals are kept in memory (the last 200); the playbook run they start is durable.
Testing
With ai.driver: local the advisor runs offline on the scripted reply and the fake clock, so a simulated attack gives the same proposals every time and makes no outbound connection. See Test your app.
Security and privacy
- The model sees only redacted signals and norms, never logs, app data, config values, secrets or user identifiers.
- It cannot approve, revert, change a rule or name a step.
- Calls run under the
advisoragent's budget and theaiallowlist and region.
Next
Was this page helpful?