Skip to content

Creed Space for platform engineers ​

This page describes Creed Space as infrastructure: where it sits in a request, what it returns, how it fails, and what it does not do. Behaviour on this page is read from the code as of October 2026. Where code and this page disagree, the code wins. Please report the difference.

What it is ​

Creed Space is a policy decision point (PDP) for LLM agents. Before an agent runs a tool, your code asks for a decision on the exact tool name and arguments. An allow comes back as a short-lived signed token bound to those arguments. Your code redeems it once before running the tool. Denials and human-review holds come back as data. Separately, Guardian is an open-source (Apache-2.0) check that runs on your own hardware against a local model served by Ollama.

Request path ​

IntegrationRuns whereEnforcesCredential
Python SDK: decide then authorizeYour process, calling the hosted APIYes, if your code runs the tool only after authorize succeedscrd_live_… gateway key, Authorization: Bearer
Guardian library (Guardian.check)In-process, local modelYes, if you branch on the resultNone for local mode
Guardian proxy (creed-guardian proxy)Sidecar on 127.0.0.1:9090 by default, OpenAI- and Anthropic-compatibleYes. Blocked tool calls are removed from the model response and replaced with a text explanationNone for local mode
Guardian MCP server (creed-guardian-mcp)LocalNo. It exposes checks as tools, and nothing forces the agent to call themNone for local mode
Hosted gateway (/v1/chat/completions, /v1/messages)HostedIn enforce, gate_actions and strict modes: on /v1/chat/completions, PDP-allowed calls come back PENDING_APPROVAL and everything else comes back BLOCKED with arguments set to {}. On /v1/messages, only DENY comes back BLOCKED (input {}), and a REQUIRE_HUMAN call comes back PENDING_APPROVAL with its input. PENDING_APPROVAL calls still carry their arguments, so run them only after /v1/decide returns a token. tool_search is passed through as ALLOWED without evaluation. observe and soft do not gateCreed Space session token, not an API key
Content checks (/api/v1/safety/evaluate, /api/v1/pdp/adjudicate)HostedAdvisory. You act on the verdictcs_live_… key, X-API-Key header

The SDK's default base URL is https://api.creed.space. The gateway routes are mounted at /v1/..., not /api/v1/.... The SDK calls /v1/sdk/decide, /v1/authorize, /v1/reviews/{review_id} and /v1/audit.

What a decision looks like ​

The tool-call gateway returns one of three decisions:

DecisionHTTPCarries
ALLOW200decision_token (RS256 JWT), expires_at, args_hash, run, action and tool-call IDs
DENY200reasons, risk
REQUIRE_HUMAN202review_id to poll. The review record lasts 60 minutes, but the action it holds expires 15 minutes after the decision, so approve within 15 minutes

The decision token is bound to the tenant, project, run, action, tool call, tool name and a SHA-256 hash of the canonical arguments. By default the token's exp is 5 minutes after issue, or 15 minutes after approval for a human-approved action. authorize also requires the live action record, which expires 15 minutes after the original decision, so the usable window ends at whichever comes first. /v1/authorize checks every binding and the live server-side action, then consumes the authorization exactly once. A successful authorize is the point at which you start the side effect. A cancellation that arrives after that cannot stop a tool your code is already running.

python
import asyncio
import os

from creed_sdk import create_client  # creed-sdk >= 3.1

async def main() -> None:
    async with create_client(api_key=os.environ["CREED_GATEWAY_KEY"]) as client:
        d = await client.decide(tool_name="send_email", arguments={"to": "ops@example.com"})
        if d.decision == "ALLOW":
            auth = await client.authorize(
                d.decision_token,
                run_id=d.run_id,
                action_id=d.action_id,
                tool_call_id=d.tool_call_id,
                project_id="default",
                tool_name="send_email",
                args_hash=d.args_hash,
            )
            if auth.authorized:
                ...  # run the tool now
        elif d.decision == "REQUIRE_HUMAN":
            status = await client.review_status(d.review_id)  # poll until approved/denied/expired
        else:
            print(d.reasons)

asyncio.run(main())

Keys minted by earlier server versions can contain - or _. SDK builds made from this repository's source between March 2026 and this fix rejected those keys when the client was created. The published 1.0.0 packages on PyPI and npm do not check key format at all. The current SDK source accepts them (_API_KEY_PATTERN in creed_sdk/client.py), and the current server source mints alphanumeric suffixes. Minting a new key revokes the previous one.

The content endpoints use lowercase decisions instead, in two vocabularies. /safety/evaluate returns allow, modify, block or escalate. /pdp/adjudicate returns permit, forbid, divert or depends. A PDP transform maps to divert. escalate, and any type not listed, maps to depends.

What is logged. Each ALLOW or DENY writes an sdk.decision audit event with the tool name, argument hash, decision, risk score and labels, and the constitution and policy hashes (which can be empty on a DENY). A REQUIRE_HUMAN writes a review.created event with the review ID, tool name, risk score and expiry. The events store the hash of the arguments, not the arguments. The arguments of allowed and escalated actions are held in a Redis action store (by default 15 minutes for actions, 1 hour for runs) and in the review record. Hosted-gateway runs (/v1/chat/completions, /v1/messages) and their events and tool calls, including tool arguments, are in your account's data export. SDK decision runs record only the tenant, and the export does not include them yet.

Failure modes ​

FailureResult
Hosted triage model says blockDENY
Tier-2 model fails after one retry, or its circuit breaker is openBlock, so DENY
Triage component throwsFalls back to rule-based PDP only. Logged at ERROR after 5 consecutive failures
PDP evaluation throwsDENY, risk 1.0, code PDP_ERROR
Unknown PDP decision typeDENY
PDP allow without verifiable policy provenance, or a requested constitution hash that does not matchDENY, risk 1.0, code PDP_PROVENANCE_MISSING or PDP_PROVENANCE_MISMATCH
REQUIRE_HUMAN without provenance503
Audit write fails on an allow503, run cancelled
Review queue unavailable on REQUIRE_HUMAN503, denied
Token revocation store (Redis) down in productionauthorize returns authorized: false
/safety/evaluate exceeds SAFETY_EVALUATE_TIMEOUT (default 5 s)503
PDP audit write fails in strict mode500, request aborted
Audit write fails on a deny500
Fleet policy configured but unreachableREQUIRE_HUMAN, risk 1.0, FLEET_POLICY_UNAVAILABLE
Policy evaluator raises outside its PDP handler500, Policy evaluation failed

Setting GATEWAY_USE_TRIAGE=false is ignored in production. In the SDK, a response with no decision field is parsed as DENY.

Guardian is fail-closed by default. A timeout, an unreachable Ollama, a provider error or an uncertain verdict returns a block. Outside an explicit local or test ENVIRONMENT/ENV (development, dev, local, test, testing), and always on a hosted platform, Guardian(...), the proxy and --fail-open all force fail-closed on. An unset ENVIRONMENT counts as production.

Guardian(...) takes its settings as constructor arguments. Defaults: tier='3b', ollama_url='http://localhost:11434', fail_closed=True, evaluation_timeout=30.0, escalate_uncertain=False, no provider. creed-guardian proxy takes flags: --host (127.0.0.1), --port (9090), --tier (auto), --provider, --provider-model, --fail-open. python -m creed_guardian.proxy and creed-guardian-mcp read GUARDIAN_TIER (default auto) and GUARDIAN_PROVIDER / GUARDIAN_PROVIDER_MODEL. The python -m proxy also reads GUARDIAN_PROXY_HOST, GUARDIAN_PROXY_PORT and GUARDIAN_FAIL_CLOSED (default true).

Latency and cost ​

This page gives no latency or cost figures, because we have no reproducible benchmark to cite. Measure in your environment. Latency comes from two places:

  • Deterministic checks. /api/v1/safety/evaluate is a rule pass with no model call. It reports its own time in decision_time_ms.
  • Model round-trips. A hosted tool-call decision can call a first-tier model and then a second-tier model. Guardian adds one local inference, plus a hosted call if you enable escalation.

The hosted tool-call path has no overall deadline. The triage client allows 60 s per OpenAI request, with the OpenAI client's default retries, and one decision can make up to three sequential model calls (Tier-1, Tier-2, Tier-2 retry). The Python SDK gives up after 30 s by default (timeout_ms). Other caps: 5 s for /safety/evaluate, 30 s for a Guardian evaluation and 15 s for a Guardian cloud escalation.

Least privilege for agents ​

  • Content keys (cs_…) have scopes *, safety:evaluate, pdp:adjudicate and constitutions:lint. New keys default to *, so set scopes explicitly. A key can also carry an IP allowlist, allowed origins, an expiry, and per-key requests per minute and per day. The default is 100 requests per minute. Rotation keeps the old key valid for a grace period: 60 minutes by default, 7 days at most.
  • Gateway keys (crd_…) are one per account: minting a new key revokes the old one. Their scopes are fixed at runs:read and runs:write. The tenant always comes from the key. Any tenant_id, user or session ID sent in context is ignored.
  • Human in the loop. A tool call becomes REQUIRE_HUMAN when the PDP escalates, when triage escalates, when an operator fleet policy lists the tool name or action type as approval-required, or when a configured fleet policy cannot be checked. A Creed Space platform administrator, not a role in your own account, approves or denies the call at /v1/review/{review_id}/approve or /deny. The approver must be a different identity from the requester.
  • Stop switch. POST /api/v1/safety-stack/halt/self (session auth) stops new PDP adjudications, hosted MCP executions and hosted runs for your account. The flag lives in Redis: if Redis cannot be read the halt is treated as released, and a halt that nothing checks for 24 hours expires. Halt coverage of the SDK routes (/v1/sdk/decide and /v1/authorize) is being added and has not shipped.

Audit and receipts ​

Gateway audit events form a per-run hash chain in PostgreSQL. Each event's SHA-256 covers its content and the previous event's hash. client.audit(run_id) returns the run's events and the server's own re-verification result in integrity.verified. The SDK does not recompute the hashes, so this is Creed Space checking itself. Each SDK decide starts a new run. An ALLOW or DENY run's chain holds one sdk.decision event. A REQUIRE_HUMAN run's chain holds review.created followed by review.approved, review.denied or review.expired. Filtering audit by action_id drops the review.* events, because they do not record the action ID. The chain uses no key, so it detects an edit that does not recompute every later hash. It does not prove who wrote the chain.

Decision tokens and receipts are RS256-signed. The public keys are at /.well-known/jwks.json (decision tokens) and /.well-known/receipt-jwks.json (receipts). Receipts are producer-signed: Creed Space signs its own record. A valid receipt shows that Creed Space signed those claims with a key it published. It does not show that anyone else observed the action. For an authorization receipt, where your code runs the tool, it does not show that the tool ran. Receipts are not written to an append-only transparency log and carry no third-party timestamp.

Deployment ​

  • Local Guardian. Qwen models via Ollama, sized by tier from 1.5b to 32b. In local Ollama mode, evaluated content stays on the device. BYOK providers, cloud escalation and fleet features send the fields they need when you configure them. This page describes creed-guardian 2.1.0, which is not published: PyPI has 0.1.0 and 0.1.1, and the public repository's tags hold 0.1.1.
  • Hosted API. https://api.creed.space. The sample needs creed-sdk 3.1, which is not published: PyPI has 1.0.0, which predates the authorize binding shown above.
  • BYOK for Guardian. Pass provider= and provider_model= to Guardian(...), or --provider / --provider-model to creed-guardian proxy, to evaluate with a hosted provider you pay for directly. The MCP server and python -m creed_guardian.proxy read GUARDIAN_PROVIDER / GUARDIAN_PROVIDER_MODEL instead.

What it does not do ​

  • It does not rewrite arguments. The gateway maps a PDP modify or transform to ALLOW and leaves the arguments unchanged.
  • It does not enforce tool-registry controls on every route. /v1/tools stores risk metadata and controls (approval flags, per-tool rate limits, maximum argument size). The hosted gateway (/v1/chat/completions, /v1/messages) and /v1/sdk/decide enforce them; /v1/sdk/decide reads only controls registered under the default project. /v1/decide and /v1/decide/batch do not read them yet.
  • It does not keep hosted decisions on your network. Triage sends the tool name and JSON arguments to an OpenAI model. Use local Guardian if the arguments cannot leave your network.
  • It does not force an agent to use MCP tools. An MCP tool is something the agent may call. Use the proxy or authorize if you need a gate.
  • It does not provide third-party attestation. Receipts and the audit chain are records Creed Space produces itself.
  • It does not stop in-flight work. A tool your code starts after authorize succeeds is outside Creed Space's control.

Further reading ​

Creed Space API documentation. Privacy, Terms and Compliance live on creed.space.