Skip to main content
Attesta uses Python Protocol classes (structural sub-typing) to define the interfaces for its pluggable components. Any class that implements the required methods is accepted — no inheritance or registration needed. All protocols are decorated with @runtime_checkable, so you can use isinstance() checks at runtime.

RiskScorer

Assigns a continuous risk score in [0.0, 1.0] to an action.

Import

from attesta import RiskScorer

Required Methods

Method/PropertySignatureDescription
scorescore(ctx: ActionContext) -> floatEvaluate the action and return a risk score between 0.0 and 1.0.
name@property name -> strA human-readable identifier for the scorer (used in RiskAssessment.scorer_name).

Implementing a Custom Scorer

from attesta import RiskScorer, ActionContext

class KeywordRiskScorer:
    """Scores risk based on keyword presence in function names."""

    DANGEROUS_KEYWORDS = {"delete", "drop", "destroy", "purge", "truncate"}

    @property
    def name(self) -> str:
        return "keyword"

    def score(self, ctx: ActionContext) -> float:
        tokens = ctx.function_name.lower().split("_")
        if any(t in self.DANGEROUS_KEYWORDS for t in tokens):
            return 0.85
        return 0.15

# Verify it satisfies the protocol
assert isinstance(KeywordRiskScorer(), RiskScorer)

# Use it
from attesta import gate

@gate(risk_scorer=KeywordRiskScorer())
def delete_user(user_id: str) -> str:
    return f"Deleted {user_id}"

Built-in Scorers

Attesta ships with several ready-to-use scorers in attesta.core.risk:
ScorerDescription
DefaultRiskScorer5-factor heuristic scorer (function name, arguments, docstring, hints, novelty).
CompositeRiskScorerWeighted average of multiple scorers. Weights are auto-normalized.
MaxRiskScorerTakes the maximum score from multiple scorers (most conservative).
FixedRiskScorerAlways returns a constant score. Useful for testing or as a floor/ceiling.
Python
from attesta.core.risk import DefaultRiskScorer, CompositeRiskScorer, FixedRiskScorer

# Compose multiple scorers
scorer = CompositeRiskScorer([
    (DefaultRiskScorer(), 0.7),
    (KeywordRiskScorer(), 0.2),
    (FixedRiskScorer(0.3), 0.1),   # risk floor
])

Renderer

The UI/UX layer that presents gates, challenges, and status messages to the human operator. All methods are async.

Import

from attesta import Renderer

Required Methods

MethodSignatureDescription
render_approvalasync render_approval(ctx: ActionContext, risk: RiskAssessment) -> VerdictPresent a simple approval prompt and return the operator’s verdict.
render_challengeasync render_challenge(ctx: ActionContext, risk: RiskAssessment, challenge_type: ChallengeType) -> ChallengeResultPresent a verification challenge (confirm, quiz, teach-back, multi-party) and return the result.
render_infoasync render_info(message: str) -> NoneDisplay an informational message to the operator.
render_auto_approvedasync render_auto_approved(ctx: ActionContext, risk: RiskAssessment) -> NoneNotify the operator that an action was auto-approved (LOW risk). Can be a no-op for silent auto-approval.

Implementing a Custom Renderer

from attesta import (
    Renderer, ActionContext, RiskAssessment,
    ChallengeType, ChallengeResult, Verdict,
)

class SlackRenderer:
    """Sends approval requests to a Slack channel."""

    def __init__(self, webhook_url: str, channel: str):
        self.webhook_url = webhook_url
        self.channel = channel

    async def render_approval(
        self, ctx: ActionContext, risk: RiskAssessment
    ) -> Verdict:
        # Send a Slack message with approve/deny buttons
        # Wait for button click response
        response = await self._send_and_wait(ctx, risk)
        return Verdict.APPROVED if response == "approve" else Verdict.DENIED

    async def render_challenge(
        self, ctx: ActionContext, risk: RiskAssessment, challenge_type: ChallengeType
    ) -> ChallengeResult:
        # For Slack, present a simplified confirm challenge
        response = await self._send_and_wait(ctx, risk)
        return ChallengeResult(
            passed=(response == "approve"),
            challenge_type=challenge_type,
            responder=self.channel,
        )

    async def render_info(self, message: str) -> None:
        await self._post_message(message)

    async def render_auto_approved(
        self, ctx: ActionContext, risk: RiskAssessment
    ) -> None:
        # Optionally notify on auto-approvals
        pass

    async def _send_and_wait(self, ctx, risk):
        # Implementation details...
        pass

    async def _post_message(self, message):
        # Implementation details...
        pass

# Verify protocol compliance
assert isinstance(SlackRenderer("https://...", "#approvals"), Renderer)

Built-in Renderers

RendererModuleDescription
TerminalRendererattesta.renderers.terminalRich terminal UI with color-coded risk panels, animated prompts, and quiz formatting. Requires pip install attesta[terminal].
_DefaultRendererattesta.core.gate (internal)Python internal fallback renderer. Auto-approves in CI/headless environments when rich is not installed or stdin is not a TTY.
Python Attesta auto-detects the best renderer at runtime. If rich is installed and stdin is a TTY (interactive session), TerminalRenderer is used. Otherwise, Python falls back to _DefaultRenderer (auto-approve). In TypeScript, non-interactive mode defaults to deny unless you provide a renderer. You can always override behavior by passing an explicit renderer.

AuditLogger

Persists approval records for compliance and forensic analysis.

Import

from attesta import AuditLogger

Required Methods

MethodSignatureDescription
logasync log(ctx: ActionContext, result: ApprovalResult) -> strPersist the approval record and return a unique audit entry ID.

Implementing a Custom Audit Logger

import json
import uuid
from attesta import AuditLogger, ActionContext, ApprovalResult

class PostgresAuditLogger:
    """Persists audit entries to a PostgreSQL table."""

    def __init__(self, connection_string: str):
        self.connection_string = connection_string

    async def log(self, ctx: ActionContext, result: ApprovalResult) -> str:
        entry_id = uuid.uuid4().hex
        await self._insert(
            entry_id=entry_id,
            action_name=ctx.function_name,
            action_description=ctx.description,
            verdict=result.verdict.value,
            risk_score=result.risk_assessment.score,
            risk_level=result.risk_assessment.level.value,
            agent_id=ctx.agent_id or "",
            environment=ctx.environment,
            review_seconds=result.review_time_seconds,
        )
        return entry_id

    async def _insert(self, **kwargs):
        # Database insertion logic...
        pass

# Verify protocol compliance
assert isinstance(PostgresAuditLogger("postgres://..."), AuditLogger)

Built-in Audit Logger

The built-in AuditLogger in attesta.core.audit writes SHA-256 hash-chained entries to a JSONL file. See the Audit Trail concept page for details on the hash chain and verification.
Python
from attesta.core.audit import AuditLogger

logger = AuditLogger(path=".attesta/audit.jsonl")

# Verify chain integrity at any time
intact, total, broken = logger.verify_chain()
print(f"Chain intact: {intact}, entries: {total}, broken links: {broken}")

ChallengeProtocol

Defines a verification challenge that can be presented to an operator. This protocol is used internally by challenge implementations and is less commonly implemented by end users.

Import

from attesta import ChallengeProtocol

Required Methods

Method/PropertySignatureDescription
presentasync present(ctx: ActionContext, risk: RiskAssessment) -> ChallengeResultPresent the challenge to the operator and return the outcome.
challenge_type@property challenge_type -> ChallengeTypeThe type of challenge this implementation provides.

Implementing a Custom Challenge

from attesta import (
    ChallengeProtocol, ActionContext, RiskAssessment,
    ChallengeResult, ChallengeType,
)
import time

class TimedConfirmChallenge:
    """A confirmation challenge that requires the operator to wait
    a minimum amount of time before confirming."""

    def __init__(self, min_wait_seconds: float = 10.0):
        self.min_wait_seconds = min_wait_seconds

    @property
    def challenge_type(self) -> ChallengeType:
        return ChallengeType.CONFIRM

    async def present(
        self, ctx: ActionContext, risk: RiskAssessment
    ) -> ChallengeResult:
        start = time.monotonic()
        # Present information to the user...
        response = input(f"Approve '{ctx.description}'? (y/n): ")
        elapsed = time.monotonic() - start

        if elapsed < self.min_wait_seconds:
            # Too fast -- reject as rubber-stamping
            return ChallengeResult(
                passed=False,
                challenge_type=ChallengeType.CONFIRM,
                response_time_seconds=elapsed,
                details={"reason": "responded too quickly"},
            )

        return ChallengeResult(
            passed=(response.lower() == "y"),
            challenge_type=ChallengeType.CONFIRM,
            response_time_seconds=elapsed,
        )

assert isinstance(TimedConfirmChallenge(), ChallengeProtocol)
The ChallengeProtocol.present() method is async. Even if your challenge implementation is synchronous, you must declare it as an async method (or wrap it) to satisfy the protocol.

Protocol Compliance Checking

All Attesta protocols are @runtime_checkable, so you can verify compliance at runtime:
Python
from attesta import RiskScorer, Renderer, AuditLogger, ChallengeProtocol

# These return True if the object has the required methods
isinstance(my_scorer, RiskScorer)           # True/False
isinstance(my_renderer, Renderer)           # True/False
isinstance(my_logger, AuditLogger)          # True/False
isinstance(my_challenge, ChallengeProtocol) # True/False
Runtime isinstance() checks with @runtime_checkable protocols only verify that the required methods and properties exist on the object. They do not validate signatures, return types, or the async nature of methods. Use a type checker like mypy or pyright for full static verification.