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.