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:
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:
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:
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:
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
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
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
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:
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:
Wiring policies
CLI
noesis run "my task" --intuition my_module:MyPolicy
Python
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:
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:
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
Keep policies focused. One policy should address one concern. Compose multiple policies for complex scenarios.
Never mutate state directly. Return patches through intervene() and let the core runner apply them.
Test edge cases thoroughly. Policies are security-critical—test empty inputs, unicode, and boundary conditions.
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
Governed Side Effects Step-by-step tutorial for enforcing side-effect governance.
Governance guide Configure planner modes for different governance levels.