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.0a2

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.0a2
File Size Uploaded Provenance Actions
felspar-0.1.0a2.tar.gz 134.6 kB Without provenance Details for felspar-0.1.0a2.tar.gz

Built distribution (wheel)

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

Total release size: 573.9 kB

Release files / felspar-0.1.0a2.tar.gz

Download URL felspar-0.1.0a2.tar.gz
Size 134.6 kB
Tags Source
SHA-256 checksum
How to use checksums
3be0bfdfb97bb8e77714eafda40d835f91df2609e94688543106e8f1c5d6e4b7
BLAKE2b-256 checksum
How to use checksums
cde48004f15ab036db024da5affbc73e2a50e25634c8b45607174fb680ee3b09
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.0a2-py3-none-any.whl

Download URL felspar-0.1.0a2-py3-none-any.whl
Size 439.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d39d0eaf5f7f2339b060252a1b0bb4297acc498531a5b7d074588d49056670e0
BLAKE2b-256 checksum
How to use checksums
8f239de54739e44d7f63b7a91e353244c96c63c4aabed98f0e6f40aed23db1a6
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.0a2

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