Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Felspar for Python (alpha)

Felspar gives agents a shared, versioned record of what your company has approved. This package is the Python client and a synchronous action gate: before an agent acts, the gate checks the action against a verified Felspar decision and denies anything it cannot verify.

This is an alpha. The API may change between releases, and it does not yet match a stable @felspar/sdk 1.0 contract; it implements the tested 0.4.1 gate behavior. Pin the exact version you test.

Install

pip install felspar --pre

Python 3.11+ is required. The typed client for all six JSON operations is included under felspar.generated.

Quickstart

You need an agent key and a job in a released Pack. Set FELSPAR_ORIGIN to https://api.felspar.ai, and keep the key in the environment, never in code.

import os
from felspar import Client, ActionGate, denial_text
from felspar.generated.api.agents import decide_job
from felspar.generated.models.decision_input_any_of_variant_0 import DecisionInputAnyOfVariant0

# request_json is the job's actual documented request, including source evidence.
# session_id and tool rules are controlled by your application, never the model.
with Client(os.environ["FELSPAR_ORIGIN"], os.environ["FELSPAR_AGENT_KEY"]) as client:
    gate = ActionGate(
        rules={"job": "review-request", "tools": {"reply": {
            "writes": "request-reply", "fields": ["caseId", "text"],
            "entity": {"field": "caseId"}, "outputCheck": {"fields": ["text"]},
        }}},
        verify=client.verify, describe=client.describe,
        verify_output_check=client.verify_output_check,
    )
    body = DecisionInputAnyOfVariant0.from_dict(request_json)
    response = decide_job.sync_detailed("review-request", client=client.transport, body=body)
    if response.status_code != 200:
        raise RuntimeError("Decision request failed")
    gate.decisions.record(session_id, {"job": "review-request", "entity": body.entity_id}, response.parsed)
    # Generate text, call generated check_output with OutputCheckInput, then:
    # gate.decisions.record_output_check(session_id, "review-request", checked.parsed)
    verdict = gate.check(session_id, "reply", {"caseId": case_id, "text": text})
    if not verdict["allow"]:
        raise RuntimeError(denial_text(verdict))
    reply(case_id, text)

The snippet's request, case, text and reply function belong to your application; job/output IDs must match its released Pack.

The gate

ActionGate.check(session_id, tool_name, input) returns a JSON verdict with allow, reason, and either the verified decision or optional escalate and escalation. denial_text formats the same denial as TypeScript. Rules retain wire-compatible camelCase keys so shared configuration and fixtures are exact. Use exactly one of outcomes or writes, and an explicit top-level field allow list. refuse takes precedence. Tool names match exactly.

Case binding supports provided/exact and ASCII case-insensitive IDs, issued, fields and content identities, explicit entity fields, or one implicit case. Legacy company-domain jobs bind all recipients; shared providers/public suffixes are excluded by the same compact list as TypeScript (sharedDomains extends it). Test recipients must all be listed. The newest recorded decision for that case wins. Replay must match job, case ID/mode and any releases pin. Evidence expires at the server-reported boundary; no freshness limit means no additional local expiry, as in TypeScript. Missing evidence produces no usable decision on the server. writes reads outputs from the release; output checks require exact UTF-8 text, decision digest, output identity and a fresh server readback.

Record only direct Felspar responses, never a model's reconstruction. The recorder copies data and bounds sessions (1000) and records per session (200), and supports forget(session_id). It is process-local; serialize access to a session and do not share it across tenants. Hooks must use host-owned session IDs. Verification is an action-time read, not a one-use authorization or a guarantee of downstream execution. Customers own tool execution, credentials and idempotency.

The generated brief route follows the agent grant, so a changed grant denies an older decision rather than substituting another release's description. The Python facade does not yet provide the TypeScript retained-release description route. JSON responses are supported; MCP/content-part unwrapping and split request/result recording are intentionally outside this JSON-only API. Network and malformed transport errors are sanitized and deny; no redirects or ambient HTTP proxies are used. Production requires HTTPS; loopback HTTP supports local testing.

OpenAI Agents hook

This hook uses the SDK's function-tool input guardrail API, tested with openai-agents==0.24.0 (upstream contract). Attach it to each action tool. The gate's tool rule must use the function's exact name, and the host's Runner context must contain session_id; never take it from tool input.

import asyncio
import json
from agents import Agent, Runner, ToolGuardrailFunctionOutput, function_tool, tool_input_guardrail
from felspar import denial_text


def guardrail(gate):
    @tool_input_guardrail
    async def verify_before_action(data):
        try:
            context = data.context
            arguments = json.loads(context.tool_arguments)
            session = context.context["session_id"]
            if not isinstance(session, str) or not session:
                raise ValueError("missing session")
            verdict = await asyncio.to_thread(gate.check, session, context.tool_name, arguments)
            if verdict["allow"] is True:
                return ToolGuardrailFunctionOutput.allow()
            if verdict["allow"] is not False:
                raise ValueError("invalid verdict")
            return ToolGuardrailFunctionOutput.reject_content(denial_text(verdict))
        except Exception:
            return ToolGuardrailFunctionOutput.reject_content(
                "Action denied: unreadable action, verdict or missing host session."
            )

    return verify_before_action


@function_tool(tool_input_guardrails=[guardrail(gate)])
def reply(caseId: str, text: str) -> str:
    """Write the verified reply for this case."""
    return write_reply(caseId, text)


agent = Agent(name="request assistant", tools=[reply])
# Supply a host-owned session that already contains the direct decision/check.
result = await Runner.run(agent, prompt, context={"session_id": session_id})

This covers function tools attached to this guardrail, not hosted tools or other unguarded execution paths. A denied call returns the denial to the model without running the function.

Documentation

Metadata

Release files for felspar 0.1.0a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for felspar 0.1.0a1
File Size Uploaded Provenance Actions
felspar-0.1.0a1.tar.gz 122.2 kB Without provenance Details for felspar-0.1.0a1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for felspar 0.1.0a1
File Interpreter ABI Platform Actions
felspar-0.1.0a1-py3-none-any.whl Python 3 none any Details for felspar-0.1.0a1-py3-none-any.whl

Total release size: 468.7 kB

Release files / felspar-0.1.0a1.tar.gz

Download URL felspar-0.1.0a1.tar.gz
Size 122.2 kB
Tags Source
SHA-256 checksum
How to use checksums
3540b3f76d78a9c560144925e0babe626705920d66bd3942f043be4102429909
BLAKE2b-256 checksum
How to use checksums
aba74d72cebe697bd6cdb4783a89cc202364f6c94bdac08481552e92a37f8a6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / felspar-0.1.0a1-py3-none-any.whl

Download URL felspar-0.1.0a1-py3-none-any.whl
Size 346.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f0247c2a0d63a36420efe1f4646ba08e51857fb2f1d689313fc11280bf853440
BLAKE2b-256 checksum
How to use checksums
14a8ee84fd957fd7e117266fb70ca93a59b01fb563119b26bd207bc27b824e90
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Security for felspar 0.1.0a1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page