Skip to content
Odysseus lashed to the mast of his ship under a full moon, ropes around his chest, as the Sirens sing from the rocks off the bow.

ἱστός - the mast

The model proposes.
Policy decides.

A small Python policy gate around agent tool calls - in both directions.

Control which tools an agent may call, with which arguments, against which resources, and what may leave the system.

Odysseus did not try to silence the Sirens. He bound himself to the mast before the singing began. Histos works the same way: it constrains an agent's future actions before the model encounters untrusted content.

pip install histos

Installs histos 0.1.0 from PyPI, with zero runtime dependencies. Policies in JSON need nothing more; the YAML form shown below adds one: pip install "histos[yaml]".

  • In-process
  • No proxy
  • Enforce: fail-closed
  • Uses your identity layer
  • v0.1.0 on PyPI
  • Apache-2.0
  • Python ≥ 3.12
  • Core: 0 runtime deps
  • Policy Format Draft 0.1

The problem

Agents do not fail where chatbots fail.

A chatbot can say something wrong. An agent can do something wrong.

Once a model can call tools, read internal data, modify records or trigger external systems, prompt injection is no longer only a content problem. It becomes an execution problem.

Untrusted content reaches the model

Documents, retrieved passages, tool output and user input all arrive in the same context window. Roles do carry different levels of trust, and providers now train against an instruction hierarchy - but a model-side hierarchy is a learned disposition, not a deterministic authorization boundary. It moves how likely the model is to comply; it does not decide what the call is allowed to do.

The model may still call tools

Even when the instruction came from the wrong place, the resulting tool call can look perfectly valid - correct name, well-formed arguments, plausible intent.

Detection is not a hard boundary

A detector may help. It does not deterministically decide what the agent can and cannot do, and it is answering a question with no stable ground truth.

If the model can be manipulated, the boundary must live somewhere the model cannot negotiate with.

The answer

Do not ask the model to resist the Sirens.

He made the decision earlier, while he still could. That is what policy enforcement is: a commitment made before the model encounters the world.

Histos does not try to interpret intent or guess whether a prompt is malicious. It evaluates a request against static policy and a trusted identity, then allows it or denies it before the tool executes.

It is deliberately not a broad agent-governance platform. Those platforms combine identity, discovery, sandboxing and fleet operations. Histos is the smaller enforcement primitive inside one Python host: authorize the proposed action, execute only within policy, then constrain what returns.

Odysseus bound to the mast with heavy rope, looking past the Sirens on the rocks. The ship's tiller is out of his reach.

Detection changes probabilities.
Enforcement changes possibilities.

Both have value. They are not the same layer.

Runtime enforcement

Two gates around every tool call.

A hard boundary at the tool surface.

Histos wraps the tool boundary on both sides.

Before execution, it decides whether the call is allowed at all. After execution, it decides what is allowed to return to the model.

Before the tool runs

Stop the action before it exists.

Histos evaluates the proposed call against policy and trusted runtime context before the underlying function executes.

Tool access
may this role call this tool at all?
Argument schema
are names, types, ranges, enums and patterns valid?
Trusted binding
replace model-controlled values with trusted principal attributes.
Resource authorization
does the caller actually own or have access to the target resource?
State conditions
is the resource currently in a state where this action is allowed?
Rate limits
how often may this action happen?
Budgets
how much cumulative action is permitted?
Canary / secret screening recognises
is the call carrying a planted token or a verified secret out?
Confirmation
must a human approve this exact action first?

Any failed check means the tool does not run.

Before the result gets back

Once the call is allowed to execute, its output is still not trusted to re-enter the model.

Histos enforces the return contract before tool output - or an exception - re-enters the agent context.

Return schema
does the result match the declared contract?
Projection
only declared fields leave the boundary.
Sensitive-field redaction
remove fields marked as sensitive.
Secret redaction recognises
stop supported secrets from flowing back into context.
Canary redaction recognises
strip planted tokens out when they surface.
Exception redaction
errors do not become accidental exfiltration channels.

The boundary protects both what the agent can do and what the model gets to see next.

Two different strengths, and the difference matters more than the count. Most of these decide from a declared fact — a role holds a grant or it does not, an argument matches a schema or it does not, a caller owns the resource or does not. There is no recognition step, so no class of input gets through by looking unfamiliar. The three marked recognises have to identify something inside a value: a secret, a planted token. They are worth having and they are not guarantees — what they have not seen, they do not catch. Read those three as defence in depth, and the rest as the boundary.

Policy primitives, shipping today 31

pre-tool 17
  • RBAC
  • default deny
  • role inheritance
  • arg schema
  • regex patterns
  • numeric bounds
  • string bounds
  • enum
  • array elements
  • trusted binding
  • resource ownership
  • resource conditions
  • rate limits
  • budgets
  • confirmation
  • secret detection
  • canary in arguments
post-tool 6
  • strict returns
  • output projection
  • sensitive redaction
  • secret redaction
  • canary redaction
  • exception redaction
developer tooling 8
  • hash-chained audit
  • audit verifier
  • policy review
  • coverage as a CI gate
  • MCP / OpenAI / OpenAPI import
  • tool definition drift
  • canonical policy hash
  • async tools

Why this matters

The gate never reads the conversation

Untrusted content reaches the model; the model proposes a call. Everything the gate uses to decide - a trusted identity, your static policy, the request itself - comes from outside that channel.

In enforce mode every step is fail-closed: an error inside a check is a denial. observe is the explicit calibration mode - it records the decision, then runs the original call unchanged and protects nothing. A denial answers two audiences at once: the developer gets the field, the bound and the fix; the agent gets a code that teaches it nothing about how to succeed on the next attempt.

UNTRUSTEDContent the agent reads documents · tool output · retrieved data · user input any of which may carry an instruction you did not write The modelmay be manipulated - assume it isproposes a tool callHISTOSdeterministic · in-process · local checks in µstrusted identitystatic policythe request itselfmay THIS principal call THIS tool, with THESE arguments, on THIS resource? nothing here is read from the conversation DENYrecorded, non-coachingthe real worldonly within the bounds

Hijacked.
Still bounded.

Not harmless, and the difference is the point. A €400 refund to the right tenant, on an allowed order, with valid arguments goes through - because that is what your policy says. What a manipulated model cannot do is cross the bounds: wrong tenant, wrong tool, wrong resource, over budget, over limit, without the approval you required, carrying a value you marked.

The point is not to make the model perfect. The point is to keep a manipulated model bounded - whether or not anyone noticed the attack.

Measured

The boundary is worth least where the attack looks like one

Seven models, six scenarios, four temperatures. 749 paired comparisons, 1,498 agent executions.

0/170

Runs in which gpt-4.1, gpt-4o-mini, o4-mini, gemma4:26b and gemma4:e4b fell for an invoice fraud or an injected ops runbook. No policy involved — they refused unaided.

83/85

Runs in which those same five models sent a patient's appointment details to a stranger's phone, because a sentence in the patient's own intake note asked them to.

The difference is not capability and not vendor — a 26B open-weight model reproduces gpt-4.1 run for run. It is whether the attack looks like one. A bank account that disagrees with the supplier master does. A runbook ordering production to zero does. “I do not use my own phone for messages” does not, because it is an ordinary service request. There is nothing there to detect, and a reasoning model has nothing to reason about.

Behind a policy: 0 harmful outcomes across the 375 gated attack cases — the 374 controls are counted separately, because pooling them would report benign runs as prevented attacks. That column is not a discovery either: a deterministic gate refuses the call it was written to refuse, and on two of the three scenarios it did nothing at all because the model had already declined. What the runs are for is the other question: where a boundary is worth its cost, and what that cost is. On accounts payable it is zero. On the clinic it is an entire legitimate feature.

Why a policy

Why not just write the checks in code?

For one agent with three tools, you probably should.

Histos starts paying off when the same security boundary has to survive across more tools, agents, runtimes and teams.

  1. 01

    One place for the boundary

    Security rules live in a dedicated policy instead of being scattered across handlers, tools and agent code.

  2. 02

    Default-deny by design

    A new or forgotten tool does not silently become trusted just because nobody added another if.

  3. 03

    Reviewable security changes

    Changing a refund limit from 500 to 5000 becomes an obvious policy diff instead of one line hidden inside application logic.

  4. 04

    Portable semantics

    Ownership, binding, confirmation, argument limits and output controls mean the same thing across compatible runtimes.

  5. 05

    Coverage and verification

    Histos can validate policy, detect uncovered tool surfaces, emit stable decision codes and verify runtime conformance.

  6. 06

    Audit without rebuilding it yourself

    Every decision can be tied to the tool, principal, policy and reason, without every team inventing its own logging convention.

What “an obvious policy diff” means, since the whole claim is that you can see it:

pull request
  tools:
    make_refund:
      args:
-       amount: { type: integer, minimum: 1, maximum: 500 }
+       amount: { type: integer, minimum: 1, maximum: 5000 }

The value isn't avoiding the if.
It's making the boundary explicit, portable and verifiable.

Three tools? Write the ifs.
Thirty? Write the policy.

Identity

Histos does not authenticate anybody.

Identity belongs to your existing system, not to the model and not to Histos.

Your identity provider establishes who is calling. Histos receives a trusted principal and enforces what that identity may let the agent do.

Who is this?
Your identity layerEntra ID, Okta, Auth0, Keycloak, workload identity - whatever you already run.
What may this identity let an agent do?
HistosThis tool, these arguments, this resource, this budget, with or without approval.
Should the backend still verify the action?
YesThe system of record stays the final authority. Histos is a boundary, not a replacement for it.

The policy speaks your vocabulary, not your IdP's

A directory GUID in a roles block ties the policy to one tenant of one provider, and makes the file unreviewable - a security lead can tell you whether refund_officer should hold make_refund; nobody can tell you that about a9481de2.

So the mapping lives in your host, and the same policy survives a change of identity provider, of runtime, and of customer.

The gate is exactly as strong as the principal you bind to it - and the library cannot check that binding. It says so, rather than implying otherwise with an API that looks safe.

in your host, not in the policy
  Entra app role                      Histos role
  finance-refund-operator      →      refund_officer
  support-tier2                →      support_agent

  Okta group                          Histos role
  eng-oncall                   →      incident_responder

  ── and never the other way round ──────────────────

  roles:
    "a9481de2-f123-4c77-9e21-…":      ✕  one directory, one tenant
    refund_officer:                   ✓  a portable artifact
Five ways to get this wrong - all of which compile and run - are written out in docs/identity.md.

The gate is only as strong as the identity bound to it.

The format

Policy is the portable artifact.

Histos is not just a Python library. The deeper idea is portable policy.

A policy should be something you can review, diff, validate, version and eventually enforce across more than one runtime.

security.policy.yaml
schema_version: histos.policy/0.1
policy_id: refund-approval
version: "1"

roles:
  refund_officer:
    allow: [make_refund]

tools:
  make_refund:
    args:
      amount: { type: integer, minimum: 1, maximum: 50000 }
    confirmation:
      required: true
and in your host
from histos import protect

guarded = protect(my_tools, policy=class="token-string">"security.policy.yaml")
agent.tools = guarded.tools     # the same tools, now bounded

The policy is the artifact; the library is what reads it. Your editor can read it too - $schema points at a file this site serves, so a typo is underlined where you wrote it rather than refused at load time.

Readable by humans

A security policy should be reviewable in a pull request, by someone who does not read Python.

Strict enough for machines

Validated, canonicalized, deterministic. The same document hashes the same everywhere - which is what policy pinning and approvals rest on.

Designed to outlive one runtime

The runtime may grow. The policy remains the contract, and the contract is the part worth getting right slowly.

The policy is the contract. The runtime is an implementation.

Scope

What Histos does not do

Every security boundary has limits. The useful thing is to say them out loud.

  • It does not detect prompt injection

    That is a different class of system, working on meaning rather than on capability.

  • It does not authenticate callers

    It relies on a trusted identity established by your environment, and cannot verify that you established it correctly.

  • It does not replace backend authorization

    The system of record remains the final authority, especially between the moment of the check and the moment of execution.

  • It does not protect tools it never sees

    Complete mediation depends on the adapter and the integration. A tool nobody wrapped is a tool nobody bounded.

  • It does not bound a whole agent run

    Budgets and rate limits count per identity and tool, inside one process. There is no run or session scope yet, so the honest claim is a ceiling on every single action - not a ceiling on how many actions a loop may attempt.

  • It does not solve human intent

    It enforces what the policy allows, not what someone later wishes it had said.

  • It does not run an agent fleet

    There is no agent registry, identity provider, sandbox or hosted control plane. Histos is the local Python enforcement layer; organization-wide operations are a separate product problem.

Histos answers can. Other layers may help answer should.

Open core

Everything that enforces is open.

The security boundary itself is open source.

Histos Python, the policy format and the deterministic enforcement logic are available under Apache-2.0. The commercial layer is not “better security for one deployment”. It is what organizations need when enforcement has to operate across teams, services and time.

Open · Apache-2.0
  • Histos Python runtime
  • The policy format
  • Deterministic policy evaluation
  • Argument and resource checks
  • Output controls and redaction
  • Local audit trail
  • CLI and developer workflow
Commercial
  • Centralized operations
  • Fleet-wide visibility
  • Enterprise workflows
  • Long-term audit and governance

Open source gives you the boundary. The commercial layer helps you operate that boundary at scale.

Status

What exists today

  • now

    Histos Python

    Released: v0.1.0 on PyPI, the reference runtime, under Apache-2.0.

  • now

    Histos Policy Format

    Draft 0.1: implemented, documented, and pinned by a conformance corpus.

  • now

    CLI and developer workflow

    Ships with the package: policy loading, validation, review, coverage, tool import and definition drift as CI gates.

  • later

    Additional runtimes and organizational tooling

    Evolves after real adoption, not before it.

Histos grows from real usage, not from feature inflation. The next layer is driven by what real teams need once policy enforcement moves from one agent to many.

Start with the boundary.

Install Histos, protect a real tool surface, and decide the limits before the model meets the content that will try to move them.

If you are evaluating policy enforcement for production agents, start with the Python runtime and the policy format. Both are released: histos 0.1.0 is on PyPI under Apache-2.0.