> ## Documentation Index
> Fetch the complete documentation index at: https://noesis-32c1d602-cursor-technical-documentation-improvements.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Write intuition policies

> How to create, test, and deploy policies that guide and guard your agent episodes.

This guide covers patterns and best practices for writing intuition policies that make your agents safer and more predictable.

## When to use policies

Use policies when you need to:

* **Block dangerous operations** before they execute
* **Modify inputs** to add safety bounds or fix common issues
* **Provide guidance** without blocking execution
* **Audit decisions** for compliance and debugging

## Policy structure

Every policy extends `DirectedIntuition` and implements the `advise` method:

```python theme={null}
import noesis as ns
from noesis.intuition import (
    IntuitionEvent,
    RiskLevel,
    ScrutinyLevel,
    StrategyHint,
    ToolConstraint,
)


class MyPolicy(ns.DirectedIntuition):
    __version__ = "1.0"
    
    def advise(self, state: dict) -> IntuitionEvent | None:
        # Your logic here
        # Return None to allow, or use self.hint/intervene/veto
        return None
```

## Three types of actions

### Hints (advisory)

Use hints to provide guidance without blocking:

```python theme={null}
def advise(self, state: dict) -> IntuitionEvent | None:
    if "production" in state.get("task", "").lower():
        return self.hint(
            advice="Consider testing in staging first.",
            target="plan",
            rationale="Production changes benefit from staging validation.",
            risk_level=RiskLevel.MODERATE,
            strategy_hints=(StrategyHint.VERIFY_FIRST,),
            scrutiny_level=ScrutinyLevel.ELEVATED,
        )
    return None
```

Hints appear in the event timeline but don't modify execution.

### Interventions (modify)

Use interventions to fix inputs or add safety bounds:

```python theme={null}
def advise(self, state: dict) -> IntuitionEvent | None:
    task = state.get("task", "")
    
    # Add LIMIT to unbounded queries
    if "select" in task.lower() and "limit" not in task.lower():
        return self.intervene(
            advice="Added LIMIT 1000 to prevent resource exhaustion.",
            patch={"task": f"{task} LIMIT 1000"},
            target="input",
            rationale="Unbounded queries can overwhelm the database.",
            tool_constraints=(ToolConstraint.REQUIRE_DOUBLE_CHECK,),
            strategy_hints=(StrategyHint.VERIFY_FIRST,),
            scrutiny_level=ScrutinyLevel.ELEVATED,
        )
    return None
```

Interventions modify the state and continue execution.

### Vetoes (block)

Use vetoes to completely block dangerous operations:

```python theme={null}
def advise(self, state: dict) -> IntuitionEvent | None:
    task = state.get("task", "").lower()
    
    if "drop table" in task or "truncate" in task:
        return self.veto(
            advice="Blocked: destructive database operation.",
            target="plan",
            rationale="DROP/TRUNCATE requires manual execution with audit trail.",
        )
    return None
```

Veto signals are recorded in the timeline. Runtime blocking is enforced by Governance when `governance_mode="enforce"`.

## Common patterns

### Pattern: Regex-based detection

```python theme={null}
import re


class SqlSafetyPolicy(ns.DirectedIntuition):
    __version__ = "1.0"
    
    _DANGEROUS = re.compile(
        r"\b(drop\s+table|truncate|delete\s+from\s+\w+\s*;)\b",
        re.IGNORECASE,
    )
    _PII = re.compile(
        r"\b(ssn|password|credit.card)\b",
        re.IGNORECASE,
    )
    
    def advise(self, state: dict) -> IntuitionEvent | None:
        task = state.get("task", "")
        
        if self._DANGEROUS.search(task):
            return self.veto(
                advice="Blocked: destructive SQL detected.",
                target="plan",
                rationale="Requires privileged approval.",
            )
        
        if self._PII.search(task):
            return self.veto(
                advice="Blocked: PII field access detected.",
                target="plan",
                rationale="Requires privacy review.",
            )
        
        return None
```

### Pattern: Risk scoring

```python theme={null}
class RiskScoringPolicy(ns.DirectedIntuition):
    __version__ = "1.0"
    
    RISK_WEIGHTS = {
        "production": 0.3,
        "delete": 0.4,
        "all": 0.2,
        "customer": 0.1,
    }
    THRESHOLD = 0.5
    
    def advise(self, state: dict) -> IntuitionEvent | None:
        task = state.get("task", "").lower()
        
        risk_score = sum(
            weight for keyword, weight in self.RISK_WEIGHTS.items()
            if keyword in task
        )
        
        if risk_score >= self.THRESHOLD:
            return self.intervene(
                advice=f"High risk score ({risk_score:.2f}). Requiring approval.",
                patch={"requires_approval": True, "risk_score": risk_score},
                target="plan",
                rationale="Operations exceeding risk threshold need human review.",
            )
        
        return None
```

### Pattern: Context-aware policies

```python theme={null}
from datetime import datetime


class ChangeWindowPolicy(ns.DirectedIntuition):
    __version__ = "1.0"
    
    CHANGE_WINDOW_START = 10  # 10 AM
    CHANGE_WINDOW_END = 16    # 4 PM
    HIGH_RISK_ACTIONS = {"deploy", "rollback", "scale", "migrate"}
    
    def advise(self, state: dict) -> IntuitionEvent | None:
        action = state.get("action", "").lower()
        hour = datetime.now().hour
        
        if action in self.HIGH_RISK_ACTIONS:
            if hour < self.CHANGE_WINDOW_START or hour >= self.CHANGE_WINDOW_END:
                return self.intervene(
                    advice="Outside change window. Scheduling for next window.",
                    patch={"scheduled": True, "execute_at": "10:00"},
                    target="plan",
                    rationale="High-risk changes restricted to 10AM-4PM.",
                )
        
        return None
```

## Testing policies

Test policies in isolation without running full episodes:

```python theme={null}
import pytest
from my_policy import SqlSafetyPolicy


class TestSqlSafetyPolicy:
    def setup_method(self):
        self.policy = SqlSafetyPolicy()
    
    def test_allows_safe_query(self):
        result = self.policy.advise({"task": "SELECT name FROM users LIMIT 10"})
        assert result is None
    
    def test_blocks_drop_table(self):
        result = self.policy.advise({"task": "DROP TABLE users"})
        assert result is not None
        assert result.kind == "veto"
    
    def test_blocks_pii_access(self):
        result = self.policy.advise({"task": "SELECT password FROM users"})
        assert result is not None
        assert result.kind == "veto"
        assert "PII" in result.advice
```

Run tests with:

```bash theme={null}
pytest test_policy.py -v
```

## Wiring policies

### CLI

```bash theme={null}
noesis run "my task" --intuition my_module:MyPolicy
```

### Python

```python theme={null}
import noesis as ns
from my_policy import MyPolicy

episode_id = ns.run("my task", intuition=MyPolicy())
```

### Multiple policies

Chain multiple policies by creating a composite:

```python theme={null}
class CompositePolicy(ns.DirectedIntuition):
    __version__ = "1.0"
    
    def __init__(self):
        self.policies = [
            SqlSafetyPolicy(),
            RiskScoringPolicy(),
            ChangeWindowPolicy(),
        ]
    
    def advise(self, state: dict) -> IntuitionEvent | None:
        for policy in self.policies:
            result = policy.advise(state)
            if result is not None:
                return result
        return None
```

## Policy identity in artifacts

`DirectedIntuition.hint()/intervene()/veto()` return `IntuitionEvent` with default policy metadata (`policy_id="unspecified"`, `policy_version="0.0.0"`). To keep artifacts auditable, set identity fields before returning:

```python theme={null}
def advise(self, state: dict) -> IntuitionEvent | None:
    event = self.hint(advice="Use read-only mode")
    event.policy_id = "policy:safety.sql"
    event.policy_version = "1.2.0"
    event.policy_kind = "rules"
    return event
```

## Best practices

<Tip>
  **Keep policies focused.** One policy should address one concern. Compose multiple policies for complex scenarios.
</Tip>

<Warning>
  **Never mutate state directly.** Return patches through `intervene()` and let the core runner apply them.
</Warning>

<Info>
  **Test edge cases thoroughly.** Policies are security-critical—test empty inputs, unicode, and boundary conditions.
</Info>

## Troubleshooting

### Policy not being called

* Verify `intuition=True` or `intuition=MyPolicy()` is passed to `ns.run()`

### Veto not blocking

* Ensure you're returning the result: `return self.veto(...)` not just `self.veto(...)`
* Check that the condition actually matches your input
* Ensure governance is enforcing: `ns.set(governance_mode="enforce")`

### Intervention not applied

* Verify your `patch` dictionary has the correct keys
* Check the event timeline to see if the intervention was recorded

## Next steps

<CardGroup cols={2}>
  <Card title="Governed Side Effects" icon="graduation-cap" href="/tutorials/governed-side-effects">
    Step-by-step tutorial for enforcing side-effect governance.
  </Card>

  <Card title="Governance guide" icon="shield" href="/guides/configure-planner-modes">
    Configure planner modes for different governance levels.
  </Card>
</CardGroup>
