Samsarix Agent Ethics is a local, deterministic policy gate for autonomous agent actions. It answers one operational question before an agent acts: allow, deny, or require human review?
It is for Python developers who need a small policy-as-code boundary in front of tool calls, workflows, or other consequential operations. Policies and inputs are JSON, decisions are explainable, and the optional audit log excludes raw input by design. The package makes no network calls and its core has no runtime dependencies. Optional exact-version adapters protect OpenAI Agents SDK function tools, LangChain tool registries, Pydantic AI toolsets, and stable MCP Python SDK servers with fingerprint-bound review flows. An optional OpenTelemetry API extra emits metadata-only decision events into caller-owned traces. Other Samsarix repositories can embed the core, but none is required; the package and its release lifecycle stand on their own.
Within the Samsarix portfolio, this repository owns agent-action safety policy, human-review
outcomes, exact-call enforcement, privacy-minimized decision evidence, and the policy lifecycle.
It does not replace the generic principal/action/resource authorization in policy-engine, the
tool transport/runtime in samsarix-core, or durable workflow coordination in
samsarix-agent-orchestration. See the portfolio boundary.
Status: 0.1.0 release candidate. The core CLI and library journey is implemented and tested. It is not a general moral-reasoning system, a compliance certification product, or a substitute for application authorization and human judgment.
Prerequisites: Python 3.11 or newer and Git.
git clone https://github.com/Deathcharge/samsarix-agent-ethics.git
cd samsarix-agent-ethics
python -m venv .venvActivate the environment:
# macOS/Linux
source .venv/bin/activate
# Windows PowerShell
.venv\Scripts\Activate.ps1Install and evaluate the included read-only action:
python -m pip install -e .
samsarix-ethics validate examples/policies/safe-agent-actions.json
samsarix-ethics test --policy examples/policies/safe-agent-actions.json \
examples/tests/safe-agent-actions.tests.json
samsarix-ethics check \
--policy examples/policies/safe-agent-actions.json \
--input examples/actions/read-resource.jsonThe last command prints a JSON decision with "outcome": "allow" and exits 0.
The destructive example is denied and exits 3:
samsarix-ethics check \
--policy examples/policies/safe-agent-actions.json \
--input examples/actions/delete-resource.jsonPowerShell accepts the same command on one line. The CLI also reads input from standard input:
echo '{"action":{"operation":"read","risk":"low"}}' | \
samsarix-ethics check --policy examples/policies/safe-agent-actions.jsonsamsarix-ethics init POLICY.json [--force]
samsarix-ethics validate POLICY.json [--context-contract CONTRACT.json] [--deployment-lock LOCK.json] [--format text|json]
samsarix-ethics catalog TOOL_CATALOG.json [--format text|json]
samsarix-ethics gate-deployment create --policy-deployment DEPLOYMENT.json --tool-catalog CATALOG.json --output OUTPUT.json
samsarix-ethics gate-deployment verify TOOL_GATE_DEPLOYMENT.json [--format text|json]
samsarix-ethics audit-chain verify CHAIN.jsonl --key-file KEY [--expected-head MAC] [--stream-id ID] [--format text|json]
samsarix-ethics schema [policy|policy-test|policy-comparison|policy-composition|policy-coverage|policy-explanation|policy-lint|policy-runtime-status|policy-shadow|context-contract|deployment-lock|policy-deployment|tool-context|tool-approval|tool-catalog|tool-gate-deployment|tool-gate-deployment-envelope|audit-record|audit-chain-entry|audit-chain-verification]
samsarix-ethics explain (--policy POLICY.json [--context-contract CONTRACT.json] [--deployment-lock LOCK.json] | --deployment DEPLOYMENT.json) [--input INPUT.json|-] [--format json|text]
samsarix-ethics lock create --policy POLICY.json [--context-contract CONTRACT.json] [--format json|text]
samsarix-ethics lock verify LOCK.json --policy POLICY.json [--context-contract CONTRACT.json] [--format text|json]
samsarix-ethics deployment create --policy POLICY.json [--context-contract CONTRACT.json] \
--output DEPLOYMENT.json [--force] [--format text|json]
samsarix-ethics deployment verify DEPLOYMENT.json [--format text|json]
samsarix-ethics compose --id ID --version VERSION --policy SOURCE.json [--policy SOURCE.json ...] \
--output POLICY.json [--description TEXT] [--force] [--format text|json]
samsarix-ethics lint POLICY.json [--fail-on none|security-warning|warning|suggestion]
[--format text|json]
samsarix-ethics test --policy POLICY.json [--context-contract CONTRACT.json] \
TESTS.json [--format text|json]
samsarix-ethics coverage --policy POLICY.json [--context-contract CONTRACT.json] TESTS.json \
[--threshold PERCENT] [--format text|json]
samsarix-ethics compare --baseline BASELINE.json --candidate CANDIDATE.json \
[--context-contract CONTRACT.json] TESTS.json [--format text|json]
samsarix-ethics shadow --baseline BASELINE.json --candidate CANDIDATE.json \
[--context-contract CONTRACT.json] [--input INPUT.json|-] \
[--format json|text]
samsarix-ethics check (--policy POLICY.json [--context-contract CONTRACT.json] [--deployment-lock LOCK.json] | --deployment DEPLOYMENT.json)
[--input INPUT.json|-] [--audit-log decisions.jsonl] [--format json|text]
samsarix-ethics --help
samsarix-ethics --version
Exit codes are stable for non-interactive use:
| Code | Meaning |
|---|---|
0 |
action allowed, or non-decision command succeeded |
1 |
lint findings met the selected severity, policy tests failed/errored, coverage missed its threshold/errored, or comparison found changes/errors |
2 |
invalid invocation, policy, input, evaluation, or requested audit write |
3 |
action denied |
4 |
human review required |
Only code 0 authorizes execution. Invalid data is an error, never an implicit allow.
Generate a starting policy without overwriting existing work:
samsarix-ethics init policy.json--force is required to replace an existing file.
Print the versioned Draft 2020-12 schemas for editors, CI, or code generation:
samsarix-ethics schema policy > policy-v1.schema.json
samsarix-ethics schema policy-test > policy-test-v1.schema.json
samsarix-ethics schema policy-comparison > policy-comparison-v1.schema.json
samsarix-ethics schema policy-composition > policy-composition-v1.schema.json
samsarix-ethics schema policy-coverage > policy-coverage-v1.schema.json
samsarix-ethics schema policy-explanation > policy-explanation-v1.schema.json
samsarix-ethics schema policy-lint > policy-lint-v1.schema.json
samsarix-ethics schema policy-runtime-status > policy-runtime-status-v1.schema.json
samsarix-ethics schema policy-shadow > policy-shadow-v1.schema.json
samsarix-ethics schema context-contract > context-contract-v1.schema.json
samsarix-ethics schema deployment-lock > deployment-lock-v1.schema.json
samsarix-ethics schema policy-deployment > policy-deployment-v1.schema.json
samsarix-ethics schema tool-context > tool-context-v1.schema.json
samsarix-ethics schema tool-approval > tool-approval-v1.schema.json
samsarix-ethics schema tool-catalog > tool-catalog-v1.schema.json
samsarix-ethics schema tool-gate-deployment > tool-gate-deployment-v1.schema.json
samsarix-ethics schema audit-record > audit-record-v1.schema.json
samsarix-ethics schema audit-chain-entry > audit-chain-entry-v1.schema.json
samsarix-ethics schema audit-chain-verification > audit-chain-verification-v1.schema.jsonCatch misspelled policy facts and incompatible operator types before deployment, then enforce required application facts and their types at evaluation time:
samsarix-ethics validate examples/policies/tool-call-baseline.json \
--context-contract examples/contracts/tool-call-context.jsonContext contracts are opt-in, versioned, and dependency-free. They validate declared dotted paths
while permitting unrelated request fields such as opaque tool arguments. The same
--context-contract option is available on test, coverage, comparison, and shadow commands so
lifecycle evidence can enforce the production fact boundary. See the
application context contract guide.
Pin the exact reviewed policy and contract together, then enforce the lock during validation and live decisions:
samsarix-ethics lock create \
--policy examples/policies/tool-call-baseline.json \
--context-contract examples/contracts/tool-call-context.json \
> deployment-lock.json
samsarix-ethics validate examples/policies/tool-call-baseline.json \
--context-contract examples/contracts/tool-call-context.json \
--deployment-lock deployment-lock.jsonAny change to either artifact requires a new lock, even if its human-readable version is reused. Locks prove exact equality, not authorship or freshness; see the deployment lock guide.
Package the exact policy, optional contract, and mandatory matching lock into one bounded, atomically written deployment unit:
samsarix-ethics deployment create \
--policy examples/policies/tool-call-baseline.json \
--context-contract examples/contracts/tool-call-context.json \
--output tool-call-baseline.deployment.json
samsarix-ethics deployment verify tool-call-baseline.deployment.json
samsarix-ethics check --deployment tool-call-baseline.deployment.json \
--input examples/actions/tool-read-config.jsonOne file prevents a loader from observing a policy/contract/lock mix during local rollout or
restart. Its mandatory lock is verified before direct check/explain use or runtime activation.
Separate --context-contract and --deployment-lock arguments are rejected with --deployment.
It remains unsigned equality evidence; see the
single-file policy deployment guide.
Diagnose a concrete result without serializing input, policy values, or messages:
samsarix-ethics explain \
--policy examples/policies/safe-agent-actions.json \
--input examples/actions/read-resource.json \
--format textThe report marks each condition matched, not_matched, or not_evaluated, identifies decisive
rules, and binds the exact policy plus optional context contract. Treat it as operator-only
diagnostic metadata; see the policy explanation guide.
Compose organization-owned guardrails with application-owned permissions into one ordinary policy:
samsarix-ethics compose \
--id tool-call-baseline \
--version 1.0.0 \
--description "Fail-closed baseline for read, destructive, external, and sensitive tool capabilities." \
--policy examples/policies/organization-tool-guardrails.json \
--policy examples/policies/support-agent-tool-permissions.json \
--output composed-policy.jsonSources must share a default effect and have globally unique rule IDs. The command writes the deployable policy atomically and reports approved metadata including the default effect, IDs, versions, rule counts, and exact source/output fingerprints. It omits paths, descriptions, rules, conditions, messages, and condition values. The result works with every existing command; the bundled fourteen-case support-agent suite reaches 100% of its twelve composed rules. See the layered policy composition guide.
Lint a valid policy for a deliberately small set of deterministic authoring risks:
samsarix-ethics lint examples/policies/safe-agent-actions.json --fail-on suggestionStable findings cover permissive defaults/unconditional allows, provably impossible or duplicate conditions, and missing explanations without serializing condition values. See the policy authoring diagnostics guide.
The bundled regression suite proves allow, deny, review, missing-approval, and warning behavior without exposing case inputs in its report:
samsarix-ethics test --policy examples/policies/safe-agent-actions.json \
examples/tests/safe-agent-actions.tests.jsonMeasure which rules the suite actually exercises and enforce a CI floor:
samsarix-ethics coverage \
--policy examples/policies/tool-call-baseline.json \
examples/tests/tool-call-baseline.tests.json \
--threshold 100The included tool-call suite matches all twelve rules and observes allow, deny, and review outcomes. Coverage reports contain rule IDs and input-free errors, never case inputs. See the policy coverage guide.
Compare an approved baseline with a candidate over that same suite before rollout:
samsarix-ethics compare \
--baseline examples/policies/safe-agent-actions.json \
--candidate examples/policies/safe-agent-actions-candidate.json \
examples/tests/safe-agent-actions.tests.jsonThe included candidate changes one sensitive-read case from allow to review, so comparison
reports one authorization change and exits 1. The versioned report never includes case inputs.
See the policy impact comparison guide.
Shadow that candidate on one live-shaped action while the approved baseline remains authoritative:
samsarix-ethics shadow \
--baseline examples/policies/safe-agent-actions.json \
--candidate examples/policies/safe-agent-actions-candidate.json \
--input examples/actions/read-restricted-resource.jsonThe report observes baseline allow versus candidate review, but exits 0 because the baseline
alone controls authorization. Candidate changes and errors are telemetry, not exit-code overrides;
monitor the JSON status separately. Each snapshot includes engine-only nanosecond duration for
latency comparison. The versioned report excludes the action input and all reason/warning text.
See the shadow rollout guide.
Atomically activate that reviewed candidate inside a long-running process while existing tool bindings remain live:
python examples/policy_runtime_demo.pyThe example changes a restricted read from allow to review, retains a monotonically increasing
generation, and uses compare-and-swap activation. Candidate validation or deployment-lock failure
leaves the last successful generation active. See the
atomic policy runtime guide.
from samsarix_ethics import (
PolicyEngine,
PolicyDeployment,
PolicyRuntime,
PolicyShadowEvaluator,
compare_policies,
compose_policies,
load_policy,
load_context_contract,
load_deployment_lock,
load_policy_deployment,
load_policy_test_suite,
lint_policy,
measure_policy_coverage,
)
policy = load_policy("examples/policies/safe-agent-actions.json")
lint_report = lint_policy(policy)
engine = PolicyEngine(policy)
print(engine.policy_fingerprint) # v1:sha256:...
decision = engine.evaluate(
{
"actor": {"id": "research-agent"},
"action": {"operation": "read", "risk": "low"},
"context": {"human_approved": False},
}
)
explanation = engine.explain({"action": {"operation": "read"}})
if decision.allowed:
print(decision.decision_id, decision.reasons)
batch = PolicyEngine(policy).evaluate_many(
[{"action": {"operation": "read"}}, {"action": {"operation": "delete"}}]
)
candidate = load_policy("examples/policies/safe-agent-actions-candidate.json")
suite = load_policy_test_suite("examples/tests/safe-agent-actions.tests.json")
coverage = measure_policy_coverage(policy, suite, threshold=80)
impact = compare_policies(policy, candidate, suite)
print(coverage.coverage_percent, coverage.threshold_met)
print(lint_report.passed, len(lint_report.findings))
print(impact.authorization_changes, impact.metadata_only_changes)
shadow = PolicyShadowEvaluator(policy, candidate).evaluate(
{
"action": {"operation": "read", "risk": "low"},
"data": {"sensitivity": "restricted"},
}
)
print(shadow.status, shadow.authorization_changed)
# Enforce only this baseline decision during the shadow rollout.
authoritative_decision = shadow.authoritative_decision
runtime = PolicyRuntime(policy)
activated = runtime.activate(candidate, expected_generation=runtime.status.generation)
print(activated.generation, activated.policy_fingerprint)
deployment: PolicyDeployment = load_policy_deployment(
"examples/deployment/tool-call-baseline.deployment.json"
)
deployed_runtime = PolicyRuntime.from_deployment(deployment)
tool_policy = load_policy("examples/policies/tool-call-baseline.json")
tool_contract = load_context_contract("examples/contracts/tool-call-context.json")
contracted_engine = PolicyEngine(tool_policy, context_contract=tool_contract)
composition = compose_policies(
[
load_policy("examples/policies/organization-tool-guardrails.json"),
load_policy("examples/policies/support-agent-tool-permissions.json"),
],
policy_id="tool-call-baseline",
policy_version="1.0.0",
description="Fail-closed baseline for support-agent tools.",
)
print(composition.policy_fingerprint, composition.to_dict()["sources"])For an in-process tool boundary, ToolGate turns non-allow outcomes into typed exceptions and
invokes the callback only after an allow decision:
from samsarix_ethics import ToolGate, load_policy, load_tool_catalog
gate = ToolGate(load_policy("examples/policies/tool-call-baseline.json"))
read_ticket = gate.bind("read_ticket", capabilities=["resource:read"])
result = read_ticket.execute(
{"ticket_id": "T-100"},
lambda arguments: ticket_store.read(arguments["ticket_id"]),
actor={"id": "support-agent"},
)
print(result.decision.decision_id, result.value)For a paused human-review flow, bind the authenticated decision to the exact framework call ID, tool name, arguments, capabilities, and actor that were displayed for review:
from samsarix_ethics import ToolCallApproval
call_id = "email-call-100"
arguments = {"to": "customer@example.com", "subject": "Case update"}
actor = {"id": "support-agent"}
send_email = gate.bind("send_email", capabilities=["external:write"])
# Persist this server-side with the pending call before requesting review.
pending_fingerprint = send_email.fingerprint(call_id, arguments, actor=actor)
# Construct this only from an authenticated reviewer decision and stored fingerprint.
approval = ToolCallApproval(call_id, True, pending_fingerprint)
result = send_email.execute(
arguments,
lambda prepared: mailer.send(**prepared),
actor=actor,
tool_call_id=call_id,
approval=approval,
)ToolGate recomputes the bounded versioned fingerprint and rejects any changed call before policy
evaluation, audit delivery, or execution. The application still owns reviewer authentication,
expiration, atomic one-time consumption, and protected pending-call storage. A parsed
ToolCallApproval is evidence supplied by the caller, not proof that its source is authentic.
gate.bind(...) also freezes the application-owned tool name and capability labels once at
registration, so untrusted invocation data cannot downgrade them per call.
For a complete runtime registry, put every trusted local name and capability set in a versioned tool catalog, then require an exact name-set match before accepting model-selected calls:
catalog = load_tool_catalog("examples/catalogs/coding-agent-tools.json")
bindings = gate.bind_catalog(catalog, registered_tools=registry.list_tools().keys())
run_command = bindings["run_command"]The returned immutable BoundToolCatalog carries an exact canonical fingerprint and fails setup if
the registry contains an uncataloged tool or omits a cataloged one. It never infers authorization
facts from MCP hints or tool descriptions. See trusted tool catalogs.
For coherent promotion, package the locked policy deployment and reviewed catalog into one
ToolGateDeployment, then call ToolGate.bind_deployment(...) with the complete trusted registry
snapshot. See coherent tool-gate deployments.
When those bytes cross an untrusted storage or delivery boundary, wrap the complete deployment in
a freshness-aware ToolGateDeploymentEnvelope. It authenticates the exact deployment, audience,
key ID, monotonic sequence, issuance, and expiry with domain-separated HMAC-SHA-256. Bind with
ToolGate.bind_authenticated_deployment(...) or
ToolDispatcher.bind_authenticated_deployment(...) so authentication is checked at setup time.
The caller supplies trusted keys, target audience, clock, and protected minimum sequence; HMAC is
symmetric authentication, not individual signer identity. See
authenticated deployments and run
python examples/authenticated_deployment_demo.py.
To keep authorization and callback selection on one dependency-free runtime path, bind the final Python callables themselves:
from samsarix_ethics import ToolDispatcher, load_tool_gate_deployment
gate_deployment = load_tool_gate_deployment("coding-agent.gate-deployment.json")
dispatcher = ToolDispatcher.bind_deployment(
gate_deployment,
registered_tools={name: registry.get_tool(name).function for name in registry.list_tools()},
)
result = dispatcher.execute(model_tool_name, model_arguments, context=trusted_context)The dispatcher snapshots the mapping and callback references, exact-matches every name, supplies detached arguments as keyword arguments, and never invokes a blocked tool. Do not bind a wrapper that performs another mutable registry lookup. See immutable tool dispatch.
When a model turn proposes several calls, prepare them from trusted bindings and authorize the complete batch before dispatching any item:
calls = [
read_file.prepare({"path": "README.md"}, context={"workspace_contained": True}),
run_tests.prepare({"command": "pytest"}, context={"workspace_contained": True}),
]
decisions = gate.enforce_many(calls)
# Only now may the embedding framework schedule call.arguments.The batch is bounded at MAX_TOOL_BATCH_ITEMS (1,000), fully normalized before evaluation,
evaluated in order against one runtime generation, and fully audited before enforce_many returns
or raises. A typed block retains the complete metadata-only decision tuple and its first blocked
index, so an adapter can render the whole review queue without re-evaluating or duplicating audit
records. It never schedules callbacks: the caller owns concurrency, cancellation, partial side
effects, and the requirement to dispatch immediately from each prepared call's fresh detached
arguments. See the coding-agent policy pack and run
python examples/coding_agent_batch_demo.py for a read-plus-command review and approval flow.
execute_async provides the same fail-closed boundary for async callbacks. ToolDispatcher also
provides execute_many and execute_many_async, which preflight the entire batch before invoking
frozen callbacks sequentially. Denials raise
ToolCallDeniedError; review outcomes raise ToolCallReviewRequiredError; neither invokes the
tool. See the tool-call integration guide, API reference, and
policy format.
Run the dependency-free single-call demonstration with python examples/tool_gate_demo.py.
Applications can route the same versioned metadata-only record to their own durable store or telemetry pipeline with a synchronous sink:
from samsarix_ethics import AuditRecord, ToolGate
records: list[AuditRecord] = []
gate = ToolGate(policy, audit_sink=records.append)The sink runs once after each decision is computed and before the decision can authorize a callback.
It must return None; an exception or other return becomes AuditLogError and fails closed. Use
either audit_sink= or the existing local audit_log= path, not both. Agent Ethics never retries
delivery or includes evaluation input in AuditRecord. Every decision and audit record includes
the exact canonical policy fingerprint, so reused human-readable policy versions cannot make two
different policy bodies look identical in operational evidence.
For a single-writer local stream that needs mutation and ordering evidence, use the same sink seam
with HmacAuditChainSink. It authenticates the metadata-only record, sequence, stream ID, and prior
entry using HMAC-SHA-256. verify_audit_chain and samsarix-ethics audit-chain verify validate the
complete stream; an externally retained expected head detects rollback to an earlier valid prefix.
Key custody, external checkpoints, cross-process locking, retention, and callback outcome records
remain application responsibilities. See the keyed audit-chain guide and
run python examples/audit_chain_demo.py for the complete temporary journey.
Attach each metadata-only decision to the caller's current OpenTelemetry span without recording tool arguments, actor/context facts, policy messages, or callback results:
from samsarix_ethics import OpenTelemetryDecisionEventSink, ToolGate
gate = ToolGate(policy, audit_sink=OpenTelemetryDecisionEventSink())Install with python -m pip install -e '.[opentelemetry]'. The optional extra pins only
opentelemetry-api==1.44.0; the application owns its SDK, exporter, sampling, and collector. Use
CompositeAuditSink(durable_sink, OpenTelemetryDecisionEventSink()) when a decision must reach
both durable storage and trace correlation. Ordered fan-out stops on failure but cannot roll back
an earlier delivery. A non-recording span is an intentional OpenTelemetry no-op, so events are
operational correlation—not durable audit evidence. See the
OpenTelemetry decision-event guide and run
python examples/opentelemetry_decision_event_demo.py in the exact SDK contract environment.
Install the optional adapter and run its no-network configuration example:
python -m pip install -e '.[openai-agents]'
python examples/openai_agents_guardrail_demo.pycreate_openai_agents_tool_policy(binding).protect(function_tool) returns a protected copy of a
strict top-level SDK FunctionTool. Samsarix policy review outcomes use the SDK's resumable
approval flow; the final input guardrail re-reads application-owned actor/context facts, binds any
approval to a fingerprint stored before interruption, emits the configured audit record, and
blocks every non-allow outcome immediately before the callback. Existing tool guardrails and
approval rules are preserved. Durable resumes supply an application-owned first-write approval
store; resolved approvals are removed, storage failures stay visible, and the bounded in-memory
default fails closed after reconstruction.
The integration is pinned to a real openai-agents==0.18.3 CI contract while the base install
remains dependency-free. Hosted/built-in tools, MCP-hosted tools, handoffs, namespaces, and
Agent.as_tool() do not traverse this adapter. See the
OpenAI Agents SDK integration guide for the supported boundary, approval
semantics, Pydantic-coercion caveat, and production checklist.
Install the optional adapter and run its deterministic no-network agent:
python -m pip install -e '.[langchain]'
python examples/langchain_policy_middleware_demo.pycreate_langchain_tool_policy(bound_catalog) returns a policy object whose validate_tools
method requires an exact real BaseTool registry and whose middleware enforces both sync and async
tool calls. Put tool_policy.middleware last in LangChain's middleware list so it sees the final
raw arguments after other middleware transformations. Unknown tools, mismatched resolved tools,
denies, malformed inputs, and framework-shape errors never call the handler.
A policy review outcome uses LangGraph's native interrupt/checkpoint flow. The interrupt carries
the exact call fingerprint; the strict resume response must echo it and add approved: true before
the current policy, actor, context, name, capabilities, and arguments are re-enforced. Mutation or
replay against another call fails closed. The interrupt intentionally contains tool arguments for
the reviewer, so production checkpointers need sensitive-data controls and an authenticated review
surface. See the LangChain middleware guide for ordering, rejection, audit,
parallel-call, persistence, and unsupported-path boundaries.
Install the slim optional runtime and run its deterministic no-network agent:
python -m pip install -e '.[pydantic-ai]'
python examples/pydantic_ai_policy_toolset_demo.pycreate_pydantic_ai_tool_policy(bound_catalog, toolset) returns a policy object whose toolset
wraps Pydantic AI's public execution seam. Every run step must expose the complete exact catalog
as real ToolsetTool objects, and each call must resolve to the snapshotted object. Allow delegates
once, deny never delegates, and review becomes native DeferredToolRequests metadata bound to the
exact call fingerprint.
After authenticating the reviewer, use tool_policy.build_results(requests, decisions) to create
resume evidence. A plain Pydantic AI boolean approval is not enough: resume requires Samsarix
metadata, fresh actor/context providers, and current-policy re-enforcement. Pydantic AI performs
schema validation before the wrapper, so this adapter authorizes validated JSON-native arguments;
custom argument validators must have no side effects. Approved results are first-write recorded
and atomically consumed; durable reconstruction supplies an application-owned approval store. See the
Pydantic AI toolset guide for multi-call resolution, persistence, sensitive
metadata, and unsupported-path boundaries.
Install the exact stable SDK contract and run the in-memory client/server demo:
python -m pip install -e '.[mcp]'
python examples/mcp_server_policy_demo.pycreate_mcp_server_tool_policy(bound_catalog, tools, handler) exact-matches the complete MCP
tool-name set to trusted Samsarix bindings. Advertise only tool_policy.tools, then
register tool_policy.call_tool with the stable low-level Server.call_tool() decorator. Every
valid call receives fresh application-owned actor/context facts; allow delegates once, deny never
delegates, and review invokes an optional application-owned async approval provider before current
policy enforcement with re-read request facts. Approval evidence is bound to a fresh one-shot call ID, exact arguments,
capabilities, actor, and policy evaluation.
The integration is pinned to mcp==1.28.1 and uses the SDK's public in-memory server/client
contract in CI. SDK JSON Schema validation occurs before the protected handler and does not emit a
Samsarix decision. FastMCP private routes, direct handler calls, resources, prompts, sampling,
proxy/provider paths, and any tool not advertised and dispatched through this exact adapter are
outside its boundary. See the MCP server integration guide.
Samsarix Agent Framework is the first verified downstream consumer. Its optional policy registry
pins Agent Ethics 0.1.0 at source commit eb69207b14ddd79bdfe774ec5b166c8ca8ce940e, binds trusted
capabilities outside model arguments, re-reads authentication/approval facts for every call, and
blocks execution on every non-allow outcome or gate failure. The consumer contract runs on Python
3.11-3.14 while the framework's dependency-free core retains Python 3.10 support.
The repository also carries public, reproducible OpenAI Agents SDK, LangChain, Pydantic AI, and MCP Python SDK adapters with exact-version contract tests. The consumer repository remains private as of 2026-08-01, so none of these items is a public third-party case study or production deployment. Exact commits, compatibility, rollback, support level, and evidence limits are recorded in adoption and compatibility evidence.
- Every rule is evaluated; all conditions in a rule must match.
- A matching
denyoverrides everyrevieworallow. - A matching
reviewoverrides everyallow. - If no decisive rule matches,
default_effectapplies. warnandauditrules are non-decisive metadata.- Policy and evaluation errors are surfaced instead of silently skipping a rule.
This narrow model follows established policy-engine patterns—explicit grants, deny overrides, and pre-use validation—without attempting to reproduce the much broader OPA or Cedar languages.
- Policy files are trusted developer/operator configuration; evaluation input may be untrusted.
- JSON byte size, nesting depth, string length, container count, rule count, and condition count are bounded.
- Duplicate JSON keys and non-finite numbers are rejected.
- Direct Python calls enforce the same bounded JSON contract as file and standard-input parsing.
- Baseline/candidate impact reports classify authorization, metadata-only, and error cases without copying regression inputs; equality is limited to the supplied cases.
- Rule-coverage reports show which policy branches a bounded suite matched, but cannot prove that every condition boundary or possible input was tested.
- Policy lint reports only certain authoring patterns and omits condition values; a clean report is not proof that permissions match business intent.
- Policy composition rejects ambiguous defaults and identifiers and reports only source/target metadata; it does not authenticate, distribute, sign, or activate policy.
- Shadow evaluation runs a detached input against the candidate only after a successful baseline evaluation. Its telemetry omits input and message text; the baseline remains authoritative even when the candidate changes or raises a domain error.
PolicyRuntimevalidates complete candidates before an atomic in-process swap, retains the last successful generation on failure, and supports compare-and-swap conflict detection. It is not a distributed policy control plane.- There is no expression evaluation, regex engine, template expansion, dynamic import, shell execution, network request, database, or secret requirement.
- Optional audit JSONL includes decision metadata and matched rule IDs, never the raw input.
- Decisions, policy-test reports, and audit records carry a versioned SHA-256 fingerprint of the complete validated policy body; policy ID/version remain operator-authored labels.
- Deployment locks can bind exact policy and context-contract content at validation and evaluation boundaries; they are equality evidence, not signatures or rollback protection.
- Single-file policy deployments prevent mixed local artifact reads and always contain a matching lock; they do not authenticate origin, download artifacts, or coordinate hosts.
- Authenticated tool-gate deployment envelopes bind the complete enforcement unit to an audience, key ID, bounded validity window, and monotonic sequence. Verifiers can also mint because HMAC is symmetric; protected sequence state, trusted time, key custody, and multi-host rollout remain external.
- Policy explanations expose value-minimized rule/condition status without input, literals, or messages, but remain an authorization oracle that requires operator-only access.
- Caller-owned audit sinks receive the same versioned metadata-only record and no raw input.
ToolGateaudits before execution when configured; an audit failure prevents the callback.- The optional keyed audit-chain sink detects mutation, reordering, and broken links. Valid-prefix rollback requires an externally protected head; key custody, cross-process locking, retention, and availability remain embedding-application responsibilities.
See SECURITY.md for the threat boundary and reporting process and SUPPORT.md for safe support requests. General support is available at support@samsarix.com; company and partnership inquiries can use contact@samsarix.com.
The runtime has no third-party dependencies. The checked-in development requirements pin the local and CI toolchain:
python -m venv .venv
python -m pip install --require-hashes -r requirements-dev.lock
python -m pip install --no-build-isolation --no-deps -e .
python -m ruff format --check .
python -m ruff check .
python -m mypy
python -m pytest
python -m build --no-isolation
python -m twine check dist/*The lock includes the build backend. Development installs and release checks disable build isolation so no unverified build dependency is fetched outside that lock.
CI runs formatting, linting, strict type checking, tests with a 90% coverage gate, and package build checks. Compatibility tests cover Python 3.11 through 3.14.
Build artifacts locally with python -m build --no-isolation after installing the locked
toolchain. CI retains the verified wheel and source distribution for 14 days; artifacts built from
main also receive GitHub build-provenance attestations. Publication is intentionally not automated
and has not been performed. The samsarix-agent-ethics distribution name was unclaimed on PyPI when
this release candidate was prepared, but availability is not a reservation. Follow the
release operator guide to verify an exact artifact and configure Trusted Publishing
before any registry upload.
The product is a library plus CLI; it has no server or cloud component. The package separates validated immutable models, deterministic evaluation, fail-closed in-process tool enforcement, versioned trusted tool catalogs, versioned schemas, bounded I/O, authoring diagnostics, regression testing, rule coverage, policy impact comparison, layered composition, application context contracts, exact deployment locks, single-file policy deployments, authenticated deployment envelopes, value-minimized policy explanations, baseline-authoritative shadow rollout, atomic live policy activation, immutable framework-neutral dispatch bindings, keyed metadata-only audit integrity, and presentation/exit codes. See architecture.
Deliberate limitations:
- JSON policies only; no arbitrary code, regex, network data, or plugin execution.
- In-process evaluation and activation only; no policy distribution or cross-host control plane.
- Shadow evaluation is synchronous and does not provide sampling, remote telemetry, promotion, or rollback automation.
- Runtime generations are process-local and non-persistent; the application owns desired state, artifact transport, deployment authorization, monitoring, and restart recovery.
- JSONL audit is local and metadata-only. The keyed chain authenticates order within one single-writer stream but does not coordinate processes; valid-prefix rollback detection requires a separately protected expected head.
- The engine evaluates explicit caller-supplied facts; it does not infer intent or truth.
- Context contracts validate declared paths and types, not fact authenticity or every undeclared request field.
- Deployment locks detect artifact mismatch but do not authenticate authors, secure distribution, establish freshness, or prevent rollback.
- Policy deployments make one local file coherent but do not sign it, persist desired state, verify transport identity, or make a distributed rollout atomic.
- HMAC deployment envelopes authenticate bytes and freshness claims for one audience but do not encrypt policy, identify an individual signer, persist the highest accepted sequence, provide public-key verification, or replace Sigstore/TUF and organizational release controls.
- Dispatcher snapshots stabilize callable references, not mutable callback internals, delegated registry lookups, imported globals, code identity, or side-effect transactions.
- Explanations cover one supplied input and disclose rule/path/operator status; they do not prove policy correctness or hide authorization behavior from a caller allowed to query them.
- Policies must be reviewed and tested for the embedding application's real threat model.
See CONTRIBUTING.md for the verified development workflow.
Copyright 2024-2026 Samsarix LLC. Licensed under the Apache License 2.0. The NOTICE file preserves product attribution, and TRADEMARKS.md explains that the software license does not grant rights to Samsarix names or branding.