CIFR

Tooling

Python SDK Guide

Submit agents, publish research as callable code, and invoke published agents from Python using the cifr-sdk package.

The CIFR Python SDK (cifr-sdk on PyPI) lets you turn an ordinary Python function into a registered agent without writing YAML by hand. It also provides a CLI for interacting with a CIFR instance from the terminal.

Installation

pip install cifr-sdk

The SDK is pure Python with a small dependency tree (httpx, pydantic, pyyaml). It works with Python 3.11 and above.

Authentication

The SDK stores its configuration in ~/.cifr/config.toml. You need two things: the server URL and a personal access token. Mint a token from the web UI under your profile's API tokens page, then:

cifr-sdk login
# Server URL [https://cifr.org.in]: https://cifr.org.in
# Token: cifr_pat_XXXXXXXXXXXXXXXXXXXXXX
# Logged in as @yourusername

The token file is stored with restricted permissions (mode 0600).

Other authentication commands:

cifr-sdk logout    # Clear stored credentials
cifr-sdk whoami    # Show the currently authenticated user
cifr-sdk health    # Check that the server is reachable

For CI/CD environments, set environment variables instead of running login:

Variable Description
CIFR_CONFIG_DIR Override the config directory (default ~/.cifr).
CIFR_SERVER_URL Override the server URL without running login.
CIFR_TOKEN Override the token without running login.

The @expose decorator

The @expose decorator marks a Python function as an agent method. It does not wrap or alter the function -- your code stays callable from tests and notebooks exactly as before.

from cifr_sdk import expose

@expose
def compute_resiliency(topology: dict) -> dict:
    """Resilience index for a power distribution topology."""
    # Your existing research code -- no changes needed
    nodes = topology["nodes"]
    edges = topology["edges"]
    score = calculate_r_index(nodes, edges)
    return {"score": score, "components": [...]}

The decorator reads your function's type hints to build the input/output schema automatically. It also uses the first line of the docstring as the function's description.

Optional overrides:

@expose(
    name="custom-name",              # Override the auto-generated kebab-case name
    description="Custom description", # Override the docstring-derived description
    output_name="resiliency",         # Name for the output field (default: "result")
    output_description="The R index", # Description for the output field
)
def compute_resiliency(topology: dict) -> dict:
    ...

By default, the function name is converted from Python's snake_case to the kebab-case that CIFR requires: compute_resiliency becomes compute-resiliency.

The ResearchAgent class

ResearchAgent assembles your @expose-decorated function into a full agent definition with all the metadata CIFR needs.

from cifr_sdk import ResearchAgent, expose, Paper

@expose
def compute_resiliency(topology: dict) -> dict:
    """Resilience index for a power distribution topology."""
    return {"score": ..., "components": [...]}

agent = ResearchAgent(
    description="Topological resiliency index from Chanda 2016",
    functions=[compute_resiliency],
    version="1.0.0",
)

Parameters

Parameter Required Default Description
description yes -- What the agent does (1-2 sentences).
functions yes -- List containing one @expose-decorated function.
name no from function Agent name. Auto-derived from the function name if not provided.
version no "1.0.0" Semver version string.
rai conditional None Research Agent Identifier. Required if paper is set.
paper no None A Paper object with publication metadata.
provenance_type no "author_original" How the code relates to the paper. See Provenance Types.
depends_on no [] List of RAIs this agent calls at runtime.
benchmarks no [] Benchmark declarations as dicts with dataset, metric, value.

Adding paper metadata

Attach a publication record using the Paper class:

from cifr_sdk import Paper

agent = ResearchAgent(
    description="R index from Chanda 2016",
    rai="RAI-2016-chanda-resiliency-pds",
    version="1.0.0",
    functions=[compute_resiliency],
    paper=Paper(
        title="Defining and Enabling Resiliency of Electric Distribution Systems with Multiple Microgrids",
        doi="10.1109/TSG.2016.2561303",
        year=2016,
        venue="IEEE Transactions on Smart Grid",
        authors=[
            {"name": "Sayonsom Chanda", "orcid": "0000-0003-4178-9482"},
            {"name": "Anurag K. Srivastava"},
        ],
        keywords=["resilience", "microgrid", "distribution network"],
    ),
)

When paper is provided, rai becomes required -- CIFR enforces this because an RAI is how other papers cite your agent.

Adding benchmarks

Declare performance claims that CIFR will verify:

agent = ResearchAgent(
    description="R index from Chanda 2016",
    functions=[compute_resiliency],
    benchmarks=[
        {"dataset": "ieee-33bus", "metric": "r_index", "value": 0.847},
        {"dataset": "ieee-69bus", "metric": "r_index", "value": 0.791},
    ],
)

Setting provenance type

If your code is not the original author's implementation, declare how it came to exist:

agent = ResearchAgent(
    description="Community implementation of Chanda 2016 resiliency index",
    functions=[compute_resiliency],
    provenance_type="community_reimplementation",
    # ...
)

See Provenance Types for all six options and their trust implications.

Generating the cifr.yml contract

The SDK can write the cifr.yml for you instead of requiring you to author YAML by hand:

agent.write_contract("cifr.yml")

This produces a valid cifr.yml with all the fields derived from your ResearchAgent definition. You can commit this file to your repository and submit it to CIFR normally.

To inspect the contract as a Python dictionary without writing a file:

contract = agent.to_contract_dict()
print(contract)

Running locally

The SDK provides a local runtime entrypoint for testing:

agent.run_from_inputs(
    inputs_dir="/tmp/test-inputs",
    outputs_dir="/tmp/test-outputs",
)

This reads from the inputs directory, calls your function, and writes results to the outputs directory -- the same flow that happens inside a CIFR container, but without Docker.

Testing

Functions decorated with @expose remain plain Python functions. Test them directly:

from my_paper import compute_resiliency

def test_simple_topology():
    result = compute_resiliency({
        "nodes": [{"id": 1}, {"id": 2}, {"id": 3}],
        "edges": [{"from": 1, "to": 2}, {"from": 2, "to": 3}],
    })
    assert result["score"] > 0.5
    assert "components" in result

The decorator does not intercept or alter the function. agent.exposed_function also gives you a direct handle to the underlying callable.

CLI reference

# Authentication
cifr-sdk login       # Configure server URL and personal access token
cifr-sdk logout      # Clear stored credentials
cifr-sdk whoami      # Show the authenticated user

# Server
cifr-sdk health      # Check server connectivity

Additional commands for submitting, invoking, and searching agents are being added as the CLI matures.

Complete example: from function to published agent

Here is the full workflow -- from a Python function to a published, citable agent:

# resiliency.py

from cifr_sdk import ResearchAgent, expose, Paper

@expose
def compute_resiliency(topology: dict) -> dict:
    """Compute the topological resiliency index for a power distribution network.

    Uses analytic hierarchy process weights to evaluate network robustness
    under microgrid islanding scenarios.
    """
    nodes = topology["nodes"]
    edges = topology["edges"]
    # ... your research methodology ...
    return {
        "score": 0.847,
        "components": [
            {"node": 1, "contribution": 0.12},
            {"node": 2, "contribution": 0.08},
        ],
    }

agent = ResearchAgent(
    description="Topological resiliency index for power distribution systems with multiple microgrids.",
    rai="RAI-2016-chanda-resiliency-pds",
    version="1.0.0",
    functions=[compute_resiliency],
    provenance_type="author_original",
    paper=Paper(
        title="Defining and Enabling Resiliency of Electric Distribution Systems with Multiple Microgrids",
        doi="10.1109/TSG.2016.2561303",
        year=2016,
        venue="IEEE Transactions on Smart Grid",
        authors=[
            {"name": "Sayonsom Chanda", "orcid": "0000-0003-4178-9482"},
            {"name": "Anurag K. Srivastava"},
        ],
        keywords=["resilience", "microgrid", "distribution network"],
    ),
    benchmarks=[
        {"dataset": "ieee-33bus", "metric": "r_index", "value": 0.847},
    ],
)

# Generate the cifr.yml contract
agent.write_contract("cifr.yml")

Then from the terminal:

# Sign in
cifr-sdk login

# Submit to CIFR (via the web UI or git push)
# CIFR detects your cifr.yml, builds the container, runs the canonical run,
# and registers the agent with its RAI.

Your agent is now live. Other researchers can invoke it with fresh topologies, cite its RAI in their papers, and compose it into their own agents.