Skip to main content
Noēsis organizes agent cognition into four faculties that operate at different points in the cognitive loop. Each faculty is a pure domain entity—free of side effects and infrastructure dependencies.

Faculty hook order

The faculties execute in a canonical order:
observe → interpret → plan → direction → governance.pre_act → act → reflect → finalize
This ordering is enforced by validate_hook_sequence() in the runtime. Any violation raises an error.

Overview

FacultyRolePhasesKey types
IntuitionPolicy guidanceinterpretIntuitionEvent, DirectedIntuition
DirectionPlan mutationsdirectionPlannerDirective, DirectiveDiff
GovernancePre-act auditgovernance.pre_actGovernanceResult, PreActGovernor
InsightMetrics computationfinalizeInsightMetrics, compute_metrics

Intuition

Intuition provides policy-driven guidance during the interpret phase. It observes episode state and emits events that can hint, intervene, or veto.

Intuition modes

from noesis.domain.faculties import IntuitionMode

class IntuitionMode(str, Enum):
    ADVISORY = "advisory"       # Hints only, non-blocking
    INTERVENTIVE = "interventive"  # Can modify state
    HYBRID = "hybrid"           # Context-dependent

IntuitionEvent

The core data structure emitted by intuition policies:
from noesis.domain.faculties import IntuitionEvent

event = IntuitionEvent(
    kind="intervention",          # hint | intervention | veto
    advice="Added safety bounds",
    confidence=0.8,               # 0.0 - 1.0
    policy_id="SafetyPolicy",
    policy_version="1.0.0",
    policy_kind="rules",          # rules | llm | hybrid
    rationale="Unbounded query detected",
    patch={"limit": 100},         # State modifications
    target="input",               # input | plan
    scope="episode",              # episode | session
    blocking=False,               # True for vetoes
)

Intuition implementations

ClassDescriptionUse case
NullIntuitionNo-op, returns NoneDisabled intuition
HeuristicIntuitionRule-based, no LLM callsFast, deterministic checks
LLMIntuitionWraps LLM responsesComplex reasoning
DirectedIntuitionBase class for custom policiesYour own policies

DirectedIntuition

The base class for writing custom policies:
import noesis as ns
from noesis.domain.faculties import DirectedIntuition, IntuitionEvent


class SafetyPolicy(DirectedIntuition):
    """Custom policy with hint, intervene, and veto methods."""
    
    mode = ns.IntuitionMode.ADVISORY
    
    def advise(self, state: dict) -> IntuitionEvent | None:
        task = state.get("task", "").lower()
        
        # Veto dangerous operations
        if "drop table" in task:
            return self.veto(
                advice="Blocked: destructive SQL operation",
                confidence=0.95,
                rationale="DROP TABLE requires manual approval",
            )
        
        # Intervene to add safety bounds
        if "select" in task and "limit" not in task:
            return self.intervene(
                advice="Added LIMIT 1000",
                patch={"task": f"{task} LIMIT 1000"},
                confidence=0.8,
                rationale="Unbounded queries can exhaust resources",
            )
        
        # Hint about best practices
        if "production" in task:
            return self.hint(
                advice="Consider testing in staging first",
                confidence=0.6,
                rationale="Production changes benefit from validation",
            )
        
        return None

DirectedIntuition methods

MethodEffectDefault confidence
hint()Advisory guidance, non-blocking0.5
intervene()Modifies state via patch0.6
veto()Blocks execution, sets blocking=True0.8

Direction

Direction handles plan mutations through versioned directives. It operates after planning and before governance.

DirectiveKind

from noesis.domain.faculties import DirectiveKind

class DirectiveKind(str, Enum):
    HINT = "hint"           # Advisory, no state change
    INTERVENTION = "intervention"  # Modifies plan/input
    VETO = "veto"           # Blocks execution

DirectiveStatus

from noesis.domain.faculties import DirectiveStatus

class DirectiveStatus(str, Enum):
    APPLIED = "applied"     # Directive was applied
    SKIPPED = "skipped"     # Directive was skipped
    BLOCKED = "blocked"     # Directive caused a block (veto)

PlannerDirective

The versioned contract for plan mutations:
from noesis.domain.faculties import PlannerDirective, DirectiveStatus, DirectiveDiff

directive = PlannerDirective(
    steps=["detect:Gather data", "act:Process"],
    status=DirectiveStatus.APPLIED,
    reason="Added safety bounds",
    diff=[
        DirectiveDiff(key="plan.steps[0].params.limit", before=None, after=100)
    ],
    applied=True,
    policy_id="SafetyPolicy",
    policy_version="1.0.0",
    policy_kind="rules",
)

# Serialize for persistence
payload = directive.to_mapping()

DirectiveDiff

Represents a single field-level mutation:
from noesis.domain.faculties import DirectiveDiff

diff = DirectiveDiff(
    key="plan.steps[0].params.limit",
    before=None,
    after=100,
)

# Convert to mapping
diff.to_mapping()
# {"key": "plan.steps[0].params.limit", "before": None, "after": 100}

Deterministic directive IDs

Direction uses deterministic identifiers for reproducible lineage:
# Directive IDs are computed from content
directive_id = make_directive_identifier(
    policy_id="SafetyPolicy",
    policy_version="1.0.0",
    policy_kind="rules",
    steps=("detect:Gather data",),
    status="applied",
    reason="Added safety bounds",
    applied=True,
    diff=({"key": "limit", "before": None, "after": 100},),
)
# Returns: DirectiveIdentifier("dir-abc123...")

Governance

Governance is the pre-action audit layer that evaluates proposed actions before execution. It operates in the governance.pre_act hook.

GovernanceDecision

from noesis.domain.faculties import GovernanceDecision

class GovernanceDecision(str, Enum):
    ALLOW = "allow"   # Action proceeds normally
    AUDIT = "audit"   # Action proceeds, flagged for review
    VETO = "veto"     # Action blocked entirely

GovernanceResult

Immutable, versioned result of a governance evaluation:
from noesis.domain.faculties import GovernanceResult, GovernanceDecision

result = GovernanceResult(
    decision=GovernanceDecision.AUDIT,
    rule_id="rules.audit.sensitive",
    score=0.6,
    message="Sensitive action requires review",
    policy_id="governance.rules",
    policy_version="1.0.0",
    policy_kind="rules",
    details={"goal": "Write config file"},
)

# Serialize for persistence
payload = result.to_mapping()

PreActGovernor

The default rule-based governor:
from noesis.domain.faculties import PreActGovernor
from noesis.domain.state import PlanStep

governor = PreActGovernor(
    policy_id="governance.rules",
    policy_version="1.0.0",
    policy_kind="rules",
)

result = governor.evaluate(
    goal="Delete user data",
    plan=[PlanStep(description="Execute DELETE query")],
)

print(result.decision)  # GovernanceDecision.VETO
print(result.rule_id)   # "rules.veto.protected"
print(result.message)   # "Task blocked by governance policy"

Built-in governance rules

The PreActGovernor includes these default rules:
Rule IDTriggerDecision
rules.veto.danger”danger” in goal/stepsVETO
rules.veto.protected”veto”, “destroy”, “shutdown”, “wipe”VETO
rules.audit.sensitive”write”, “delete”, “drop”AUDIT
rules.allow.defaultNo matchesALLOW

Deterministic governance IDs

Governance uses deterministic identifiers for audit trails:
# Governance IDs are computed from content
governance_id = make_governance_identifier(
    policy_id="governance.rules",
    policy_version="1.0.0",
    policy_kind="rules",
    decision="audit",
    rule_id="rules.audit.sensitive",
    score=0.6,
    message="Sensitive action requires review",
    details={"goal": "Write config"},
)
# Returns: GovernanceIdentifier("gov-xyz789...")

Insight

Insight computes metrics from episode traces. It operates during the finalize phase to produce the canonical InsightMetrics payload.

InsightMetrics

The canonical metrics payload persisted in summary artifacts:
from noesis.domain.faculties import InsightMetrics

metrics = InsightMetrics(
    phase_ms={"observe": 5, "interpret": 12, "plan": 150, "act": 2000, "reflect": 10},
    veto_count=0,
    branching_factor=2.0,
    plan_adherence=0.95,
    success=True,
    plan_revisions=1,
    tool_coverage=3.0,
)

# Serialize for persistence
payload = metrics.to_mapping()

InsightMetrics fields

FieldTypeDescription
phase_msdict[str, int]Duration per phase in milliseconds
veto_countintNumber of vetoes issued
branching_factorfloatCount of direction events
plan_adherencefloatExecuted steps / planned steps (0-1)
successboolWhether episode succeeded
plan_revisionsintNumber of applied directives
tool_coveragefloatNumber of unique tools used

compute_metrics

Compute roll-up metrics from summary and events:
from noesis.domain.faculties.insight import compute_metrics

metrics = compute_metrics(
    summary={"metrics": {"ideal_steps": 5}},
    events=[
        {"phase": "observe", "timestamp": "..."},
        {"phase": "plan", "payload": {"steps": ["step1", "step2"]}},
        {"phase": "act", "payload": {"tool": "reader"}},
        {"phase": "reflect", "payload": {"success": True}},
    ],
)

print(metrics["success"])        # 1
print(metrics["plan_count"])     # 1
print(metrics["act_count"])      # 1
print(metrics["veto_count"])     # 0

build_insight_metrics

Construct InsightMetrics from raw events:
from noesis.domain.faculties.insight import build_insight_metrics

insight = build_insight_metrics(
    events=[...],
    summary_metrics={"success": 1, "act_count": 3},
)

print(insight.plan_adherence)  # 0.95
print(insight.tool_coverage)   # 2.0

Computed metrics

The insight faculty computes these metrics:
MetricComputation
successFrom terminate event status or reflect success
plan_countCount of non-synthetic plan events
act_countCount of non-synthetic act events
direction_appliedCount of applied directives
direction_vetoedCount of blocked directives
direction_applied_rateapplied / total_direction_events
veto_ratevetoed / total_direction_events
first_action_msTime from start to first act
time_to_veto_msTime from start to first veto
alignmentMean confidence difference (applied vs rejected)
policy_confidence_histogram10-bucket histogram of confidences

Faculty boundaries

Each faculty has clear responsibilities:
Intuition observes and advises—it should never execute actions directly.
Direction handles plan mutations—it should never evaluate outcomes.
Governance audits pre-action—it should never modify plans.
Insight computes metrics—it should never modify state or plans.
This separation ensures:
  • Clear audit trails
  • Testable components
  • Reproducible behavior
  • Schema versioning compatibility

Schema versioning

All faculty types include schema version tracking:
from noesis.domain.faculties import IntuitionEvent, PlannerDirective, GovernanceResult, InsightMetrics

# Each type has a class-level schema_version
print(IntuitionEvent.schema_version)    # "1.0.0"
print(PlannerDirective.schema_version)  # "1.0.0"
print(GovernanceResult.schema_version)  # "1.0.0"
print(InsightMetrics.schema_version)    # "1.0.0"

# Compatibility is checked on deserialization
result = GovernanceResult.from_mapping(payload)  # Validates version

Next steps