RadMah AI Python SDK
Typed client for the RadMah AI platform. Create jobs, upload datasets, stream results, and retrieve cryptographic evidence bundles — all from Python.
Requires: Python 3.10 or newer.
radmah_sdk.__version__reports the installed version.
Install
pip install radmah-sdk
From this repository during development:
pip install -e ./sdk
Authentication
Get a scoped API key from Settings → API Keys in the RadMah AI dashboard
(the key is shown once, at creation). Keys are prefixed sl_live_.
from radmah_sdk import RadMahClient
client = RadMahClient(api_key="sl_live_abcdef...")
Base URL defaults to https://api.radmah.ai. Enterprise customers running
their own deployment point the client at that deployment's API URL:
client = RadMahClient(
api_key="sl_live_...",
base_url="https://radmah.example.com", # your deployment's API URL
timeout=60.0, # per-request timeout seconds
max_retries=3, # transparent retry on 5xx / connection errors
)
Or via env var:
export RADMAH_API_URL=https://radmah.example.com
A deployment on your private network (an RFC 1918 address, an IPv6
unique-local address or a name under .internal) also needs an explicit
opt-in, so that a base URL reaching your program from somewhere else cannot
aim your API key at an internal service: pass allow_private_base_url=True,
or export RADMAH_ALLOW_PRIVATE_BASE_URL=1 when the code does not pass that
argument (an explicit allow_private_base_url=False always refuses). Use
https:// (set RADMAH_CA_BUNDLE or ca_bundle_path= to your CA's PEM
file when its certificate comes from your own CA). Plain http:// is
accepted only for this machine (localhost); to any other host it would send
the key unencrypted, so it is refused (BaseURLError, reason cleartext)
unless you opt in by name with allow_insecure_http=True or
RADMAH_ALLOW_INSECURE_HTTP=1, and even then the SDK issues a
radmah_sdk.InsecureBaseURLWarning. (The refusal and the opt-in are new in
1.4.0; SDK 1.3.0 accepts such a URL with the warning alone.)
Cloud instance-metadata endpoints are always refused, in every spelling
the transport accepts (Unicode dots, numeric and IPv6 forms). A server's
region redirect never moves the key from a public host into your network or
from https:// to http://. A refused base URL raises
radmah_sdk.BaseURLError, and an unusable CA bundle
radmah_sdk.CABundleError naming its setting and path; both are
ValueErrors.
Quickstart — reviewed Fabricate generation
from radmah_sdk import RadMahClient
with RadMahClient(api_key="sl_live_...") as client:
preview = client.create_fabricate_preview(
"I need 10,000 account records with a unique account_id, "
"a created_on date, an active flag, and a non-negative balance.",
requested_records=10_000,
seed=42,
)
preview_id = preview["preview_id"]
state = client.wait_fabricate_preview(preview_id, timeout=300)
# Review state["contract_versions"] before approval. Apply a refinement
# with client.refine_fabricate_preview(...) when the contract needs one.
approval = client.approve_fabricate_preview(preview_id)
job = client.get_job(approval["job_id"])
job = job.wait(timeout=300)
if job.status == "succeeded":
# The SDK requires exactly one server-declared primary artifact.
df = job.to_dataframe()
print(df.head())
evidence = client.get_evidence(job.id)
print("Evidence contract hash:", evidence.contract_hash)
Every job produces a cryptographically sealed evidence bundle. Ask the
platform to re-verify it with client.verify_job(job.id), or verify it
yourself offline — without trusting the API — by recomputing the hashes over
the downloaded bytes:
from radmah_sdk.verify import verify_evidence_bundle
result = verify_evidence_bundle(client.get_evidence_data(job.id))
print(result.ok, result.kind, result.failures)
Core operations
Jobs
from radmah_sdk import SynthesisReleasePolicy
release_policy = SynthesisReleasePolicy(
minimum_overall_fidelity=0.75,
minimum_column_fidelity=0.75,
minimum_bivariate_fidelity=0.60,
maximum_membership_advantage=0.20,
maximum_linkage_risk=0.05,
require_zero_exact_copies=True,
block_live_identifiers=False, # optional; off by default
)
# Fidelity thresholds use an equal-sized independent source baseline under the
# same untouched audit reference. Linkage limits apply to measured excess above
# the empirical source rate. Raw, baseline, and release values remain in the
# evidence bundle.
#
# block_live_identifiers=True refuses delivery when any delivered TEXT value is
# shaped like an email address, US SSN, NHS number, payment card number, IBAN or
# telephone number outside the formally reserved test ranges (example.com-style
# domains, 555-01xx numbers, SSN area 000). A refused job fails with
# SYNTHESIS_RELEASE_POLICY_FAILED, publishes nothing, and names each column,
# count and identifier format (never a value) in
# job.result_summary["synthesis_release_failure"]. Numeric columns are not
# checked. With the control off (the default) the data is delivered and any
# detection is reported in
# job.result_summary["release_policy_evaluation"]["value_safety"] and sealed as
# an observational record, which evaluate_value_safety_bundle() reports with
# enforcement="observational". It is a control you choose, not a guarantee that
# delivered data contains no identifiers.
client.jobs.create(
kind="synthesize",
dataset_id=dataset_id,
rows=10_000,
seed=42,
release_policy=release_policy,
)
client.jobs.list(status="succeeded", offset=0, limit=50)
client.jobs.get(job_id)
client.jobs.cancel(job_id)
client.jobs.rerun(job_id)
The generic jobs endpoint accepts synthesize, synthesis_probe (see
Quick probe), simulate, analyze, verify, train,
fit, lift, and scenario_fabricate. Prompt-to-data
Fabricate deliberately uses create_fabricate_preview, optional
refine_fabricate_preview, and approve_fabricate_preview; it cannot be
submitted through the generic jobs endpoint.
Datasets
# Upload from a path (auto-detects CSV / Parquet / JSON / JSONL).
# A display name without an extension inherits the real source extension.
dataset = client.upload_dataset("./data.csv", name="Transactions")
# Or from bytes.
dataset = client.upload_dataset_bytes(
filename="data.csv",
content=b"...",
)
# The server parses and stores the whole file before it answers, so a large
# upload waits up to an hour for its answer by default (the client's
# `timeout` still bounds connecting). Set `timeout=` in seconds, or
# RADMAH_UPLOAD_TIMEOUT; `rady datasets upload --timeout` does the same.
dataset = client.upload_dataset("./large.json", timeout=7200)
client.list_datasets()
client.get_dataset(dataset_id)
client.delete_dataset(dataset_id)
Saved generators: export and import
A job that trained a Synthesis model can export it as a signed generator
package (.rsg). The file carries no executable content: it is a
pickle-free tensor payload in a header signed by your workspace's key, and
it can be imported only into the workspace that exported it, for a dataset
holding the data the model was trained on.
exported = client.export_synthesis_model(job_id, "generator.rsg")
print(exported.sha256, exported.fitted_state_hash)
# Import it for a dataset uploaded from the same source file. The returned
# job verifies the package and the dataset; the generator is reusable once
# it has succeeded.
imported = client.import_synthesis_model("generator.rsg", dataset_id).wait()
client.list_synthesis_models(dataset_id, include_imported=True)
client.jobs.create(
kind="synthesize",
dataset_id=dataset_id,
rows=10_000,
seed=42,
checkpoint_source_job_id=imported.id,
release_policy=release_policy,
)
Generating from the imported generator with the same seed, rows and release policy delivers byte-identical data to generating from the model it was exported from.
Quick probe
Before paying for a full Synthesize run, probe it. A quick probe trains on a
sample of your dataset with the exact settings of the run you plan (rows,
mode, training controls, time cap, release policy, numeric constraints,
compute) and reports estimates: what the probe itself took, the full run's
wall time (at the epoch ceiling, which is not an upper bound: the full run
can take longer; and at the point the sample converged, or None with the
reason), whether the run would hit the 4-hour job limit or your training
cap, its peak memory against the full run's worker memory and admission
floor, and each release bar's likely outcome
(likely_pass, uncertain or likely_refuse; predictive utility is
advisory). A figure carries a range only where calibration supports one;
otherwise its label is uncalibrated or outside_calibrated_range and it
has no range. Every figure is an estimate, never a release decision, and the
probe delivers no rows and no model. Its arguments and defaults are the ones
the planned run takes.
quote = client.estimate_cost(
"synthesis_probe",
rows=10_000,
dataset_id=dataset_id,
epochs=400,
)
probe = client.probe_synthesis(
dataset_id,
10_000,
epochs=400,
seed=42,
release_policy=release_policy,
max_credits=quote["credits_required"],
).wait(timeout=3600)
report = client.get_synthesis_probe_report(probe.id)
print(report.deterministic_sha256) # same image, data, settings, seed: same digest
ceiling = report.estimates.time_at_ceiling_seconds
print(ceiling.point, ceiling.label, ceiling.calibrated_interval)
for row in report.release_predictions:
print(row.bar, row.sample_value, row.calibrated_interval, row.status)
for line in report.limitations:
print(line) # fixed statements; show them as given
# The report file the probe's evidence bundle hashes, digest-checked.
report_bytes = client.download_synthesis_probe_report(probe.id)
# Start the full run from the probe with the SAME settings; the run then
# records the probe's estimates against what it measured.
run = client.jobs.create(
kind="synthesize",
dataset_id=dataset_id,
rows=10_000,
seed=42,
options={"epochs": 400},
release_policy=release_policy,
probe_job_id=probe.id,
).wait(timeout=3600)
print(run.probe_validation.comparable, run.probe_validation.quantities)
A probe refuses seed data and model reuse with
SYNTHESIS_PROBE_UNSUPPORTED_CONTROL, and a dataset below the Synthesis
row floor with DATASET_TOO_SMALL_FOR_SYNTHESIS_TRAINING, before any job
or charge exists. From the shell, rady synthesize --probe takes every
rady synthesize flag, and --json prints the same public report.
Inference on a saved model
A saved generator (a succeeded train or synthesize job, or an imported
package's job) can answer questions about your own rows. Upload a table
with some or all of the model's source columns, then run one of five
operations:
| Operation | What each row gets |
|---|---|
impute |
Its missing cells filled; every present cell is delivered unchanged. By default (statistic="most_likely") each categorical cell gets the most likely fitted category given the row's present categorical cells and the bands of its present numeric cells (flow-only cells, for example dates, and positions within a numeric band are not used) and each number or date the conditional median of samples model draws; statistic="mean" gives decimals the conditional mean; statistic="draw" fills each row with one joint model draw (keeps the data's spread and relationships — use it, with several seeds, for multiple imputation). imputed_cells.csv lists each filled cell and its method. |
predict |
The target's most likely category and its probability, or, for a numeric target, the median (default) or mean (decimal targets only) of its sampled values. |
predict_proba |
The probability of each fitted category of the target, or of each interval of the bins you supply for a numeric target. |
log_prob |
The row's log-likelihood under the model, in nats: the log-probability that the model delivers exactly this row as written (categories and nulls as delivered, each number at its delivered precision), with its Monte-Carlo standard error and which parts were exact or estimated. |
state_log_prob |
The log-probability, in nats, of the row's fitted discrete state only: its categories, missing flags, numeric range bands and character states as the model holds them. |
model_job_id = trained_job.id # or an imported generator's job id
profile = client.inference_profile(model_job_id)
print(profile.enabled, profile.default_samples, profile.probability_tolerance)
for entry in profile.columns: # what each column supports
print(entry.column, entry.representation, entry.operations)
target = profile.columns[0].column # a column the profile allows as a target
# input is an uploaded dataset id, or a local file uploaded first.
job = client.predict_proba(model_job_id, "./my_rows.csv", target, seed=42).wait()
print(job.inference.results.rows_exact, job.inference.results.rows_estimated)
print(job.inference.privacy_statement)
result = client.inference_result(job.id) # checked against seal.json first
print(result.sha256, result.columns)
for row in result.rows[:5]:
print(row) # strings, None for an empty cell
filled = client.impute(model_job_id, dataset_id).wait()
print(client.inference_result(filled.id).imputed_cells[:5])
The same methods are on client.jobs and, as coroutines, on AsyncRadMah
(wait on a job there with await client.wait_for_job(job.id)). The rady CLI
runs them as rady synthesis-model impute | predict | predict-proba | log-prob | state-log-prob | profile.
Samples and precision. predict, predict_proba, log_prob and
state_log_prob draw samples model states per row. The default is
DEFAULT_INFERENCE_SAMPLES = 400: the draws that resolve any probability to
within ±0.05 at 95% confidence even in the worst case (p = 0.5), since the
95% half-width is at most 1/√n. Input rows × samples (× the model's
log_prob_draw_cost for log_prob) may not exceed INFERENCE_DRAW_BUDGET
(10,000,000) per job; impute with statistic="draw" uses one draw per row.
That product is the quoted and charged quantity for every row: a row computed
exactly, or one that stops early, is not cheaper.
Exact, estimated and sampled rows. Every result row carries a status:
exact— computed from the fitted state with no sampling, so it does not change with the seed;estimated— a Monte-Carlo estimate; the row reports its effective sample size and 95% half-width (1/√ESS), and the record gives the smallest ESS and the worst half-width injob.inference.results;sampled— reduced fromsamplesseeded model draws of the row (a numeric target; the result reports the draws used), or, forimpute, a row whose missing cells were filled by a model draw;outside_fitted_support— the row gets no score or prediction (never -inf or NaN), andoutside_support_reasonssays why. Most reasons mean the row's known values have no support in the fitted model (forcombination_never_delivered, the model's exact discrete-state probability of the row is zero). Forlog_prob,not_reproduced_in_sample_budgetmeans something weaker: the row's draws never reproduced its values after its full sample budget. That is not a proof of probability zero — the row's probability is below what that many draws can detect, and moresamplesmay score it.
job.inference.coverage names every column and how it was used: as the
target, as evidence, only through its range band, or not at all.
What log_prob computes. The model draws a discrete state (categories,
missing flags, numeric range bands, character states) from an exact
chain-rule model, then a latent vector from its base prior, transports it
with a flow (the ODE integrated by the same Euler steps the generator
ships), and decodes it. log_prob evaluates that same process for your row:
- the discrete part is the chain's exact probability of the row's
states (
discrete_part = exact), or, when a chain column is unknown, an unbiased estimate over its possible states (estimated); - the continuous part is the flow's density, by the change-of-variables
formula along the ODE, integrated over every latent value that decodes to
the row's delivered cells; latent coordinates the row does not pin
(nuisance coordinates, and the missingness indicator of a nullable
numeric) are marginalised by importance sampling, so this part is always
estimatedwhen the flow is read; - the divergence in the change-of-variables formula is computed exactly
from the full Jacobian for models whose latent has up to 64 coordinates
(
divergence = exact_jacobian) and by the Hutchinson/Skilling estimator beyond (hutchinson_skilling_estimate, withdivergence_variance).
Precision. log_prob estimates rows together and stops each one at a
declared precision: LOG_PROB_TARGET_STANDARD_ERROR = 0.025 nats (a 95%
half-width of ±0.05 on the log-likelihood, so the likelihood itself is known
to about ±5%). Rows draw 32, then 64, 128, … up to samples (default 400);
samples_used and target_met say where each row stopped.
The quote and the charge are for the most draws a row may take, on every row:
input rows × samples × profile.log_prob_draw_cost, whether a row is
exact, stops early or is outside the fitted support. A log_prob draw counts
as profile.log_prob_draw_cost model draws — the priced cost for that model:
1 for a model with no flow (discrete state only), 2 when the model's fitted
transport is affine (its learned residual was not accepted at training), more
when every Euler step needs the residual's Jacobian. Current servers always
state it; if an older server leaves it None, do not assume 1 — quote the
job (client.quote_inference(...)) and use its credits_required.
Each row reports log_prob, standard_error (the Monte-Carlo standard
error of that log), effective_sample_size, discrete_log_prob /
continuous_log_prob where the split is exact, and scored_columns /
marginalised_columns. A number is scored at its delivered precision —
a date to the day, a decimal to the model's output decimals — so the score
is the probability of that cell, not a density of an unrounded value. A row
the model can never deliver is outside_fitted_support with no score, and
outside_support_reasons says why, per column
(combination_never_delivered only when the model's exact discrete-state
probability of the row is zero). A row whose draws never reproduced it after
its full sample budget is also outside_fitted_support, with the reason
not_reproduced_in_sample_budget: that is not a proof of probability zero —
its probability is below what that many draws can detect, and more samples
may score it. The common case of a proved zero is a date:
when a date column is mostly repeated dates, the model learns it as a
schedule (the dates seen at least twice) and only ever produces those dates,
so a row with any other date has likelihood zero (date_not_in_fitted_schedule).
A date column learned as continuous time scores any date within its fitted
range. Mark such a column unknown (unknown_columns) to score the rest of
the row. A row whose flow inversion or
divergence estimate cannot be trusted is estimation_failed. Each sample
inverts the flow step by step; when the model's learned residual transport
was not accepted at training the transport is affine and that inversion and
its determinant are closed-form, otherwise each step takes the residual's
Jacobian (exact up to 64 latent coordinates, estimated beyond), which costs
more per sample than the other operations.
Refusals. A request the API cannot run is refused before any job exists
with HTTP 422 and a SYNTHESIS_INFERENCE_* code (for example
SYNTHESIS_INFERENCE_TARGET_REQUIRED, _BINS_INVALID,
_SAMPLES_OUT_OF_RANGE, _COLUMN_UNKNOWN, _NOT_ENABLED), raised as
ValidationError. The SDK checks the parts that need no knowledge of the
model — fields that do not apply to the operation, the target, the bin edges
and the sample count — itself, with the same codes, before anything is
uploaded. For a local file it also reads the model's inference profile first
and refuses, before uploading anything, what the profile already decides:
inference not enabled on the plan (_NOT_ENABLED), a model inference cannot
use, or a column the model does not have (_COLUMN_UNKNOWN). If the API
refuses the job after the SDK uploaded your file for it, the SDK deletes that
uploaded dataset (best effort) and the error's message and
detail["uploaded_input_deleted"] say whether it was deleted; after a server
or network failure the upload is kept (the job may exist) and the error names
its dataset id. Details name columns and counts only, never a cell value.
Quotes. client.quote_inference(model_job_id, dataset_id, operation, ...)
(also on client.jobs and AsyncRadMah) prices a request before submitting
it and reserves nothing; it sends POST /v1/client/jobs/quote with the same
body the client area quotes (kind, checkpoint_source_job_id, inference)
and is equivalent to client.estimate_cost(kind="synthesis_inference", checkpoint_source_job_id=..., inference={...}). A quote needs an uploaded
dataset id: it never uploads a file. Pass its credits_required as
max_credits to submit bounded by it. The rady inference commands show it
with --quote.
Privacy. These results are model inference on your own rows. They are
not a privacy-protected or differentially private release, the release
policy does not apply to them, and the job reads no source rows
(job.inference.source_rows_read is False). Treat the output with the same
care as the rows you uploaded.
Account + auth helpers
client.signup(email=..., password=..., org_name=...)
client.login(email=..., password=...) # returns {access_token, refresh_token, mfa_pending?}
client.mfa_setup() # TOTP provisioning URI
client.mfa_verify(code) # 6-digit TOTP OR 8-char backup code (xxxx-xxxx)
client.create_api_key(name="ci") # {raw_key, prefix, ...}
client.list_api_keys()
client.rotate_api_key(key_id)
client.revoke_api_key(key_id)
Platform introspection
client.health() # service liveness
client.version() # build + engine versions
client.get_openapi() # full OpenAPI spec
client.get_error_catalog() # machine-readable error_code → message map
Async client
For workloads that run many jobs in parallel or interleave with other
I/O (Jupyter, notebook pipelines, aiohttp servers), use
AsyncRadMahClient:
import asyncio
from radmah_sdk import AsyncRadMah
async def main():
async with AsyncRadMah(api_key="sl_live_...") as client:
preview = await client.create_fabricate_preview(
"I need 250 inventory records with a unique item_id and quantity "
"between 0 and 500.",
requested_records=250,
seed=42,
)
preview_id = preview["preview_id"]
await client.wait_fabricate_preview(preview_id)
approval = await client.approve_fabricate_preview(preview_id)
print("Final job:", approval["job_id"])
asyncio.run(main())
A quick probe from the async client:
async def probe(dataset_id, release_policy):
async with AsyncRadMah(api_key="sl_live_...") as client:
job = await client.probe_synthesis(
dataset_id, 10_000, seed=42, release_policy=release_policy,
)
await client.wait_for_job(job.id, timeout=3600)
return await client.get_synthesis_probe_report(job.id)
The async surface mirrors the sync one method-for-method.
Error handling
All API errors surface as RadMahError with a structured payload:
from radmah_sdk import RadMahError
try:
client.approve_fabricate_preview("unknown-preview-id")
except RadMahError as e:
print(e.status_code) # HTTP status
print(e.error_code) # machine-readable (CONTRACT_NOT_FOUND, etc.)
print(e.message) # human-readable
print(e.detail) # optional context dict
Common error codes: INVALID_API_KEY, CONTRACT_NOT_FOUND,
CREDIT_LIMIT_EXCEEDED, CONCURRENT_JOB_LIMIT,
AGENT_SESSION_LIMIT, GOAL_OUT_OF_SCOPE (ADS only).
Pull the full catalogue at runtime: client.get_error_catalog().
Agentic Data Scientist (ADS)
ADS turns a natural-language goal into a planned, human-approved,
self-healing multi-step run that seals a cryptographic evidence trail.
The SDK exposes the full lifecycle (same surface on AsyncRadMah,
awaited):
# 1. Create + wait for the plan to reach the approval gate.
project = client.create_agent_project(
goal="Profile the customers dataset, engineer tenure + spend "
"features, train a churn classifier, evaluate on a 20% holdout.",
title="Churn baseline — Q3",
)
project = client.wait_for_agent_project(project.id, timeout=120)
# 2. Approve the plan (HAGP gate) — execution begins.
if project.status == "awaiting_approval":
quote = project.cost_summary or {}
client.approve_agent_project(
project.id,
max_credits=quote["total_estimated_credits"],
plan_sha256=quote["approval_plan_sha256"],
)
# 3. Poll to a terminal state, routing any mid-run decision gate.
while True:
project = client.wait_for_agent_project(project.id, timeout=600)
s = (project.status or "").lower()
if s in ("complete", "failed", "blocked"):
break
if s == "awaiting_patch_approval":
client.submit_patch_decision(project.id, "accepted")
elif s == "awaiting_replan_approval":
client.submit_replan_decision(project.id, "accepted")
elif s == "awaiting_step_approval":
gate = project.pending_step_approval or {}
consent = gate.get("consent") or {}
if gate.get("trigger_reason") == "execution_consent":
# Running the simulation: bind the exact cost you were shown.
client.submit_step_approval(
project.id, "approved",
max_credits=consent["maximum_credits"],
consent_fingerprint=consent["fingerprint"],
)
elif gate.get("trigger_reason") == "pack_preparation_consent":
# Preparing a new reusable scenario pack.
client.submit_step_approval(
project.id, "approved", max_credits=consent["maximum_credits"],
)
else:
client.submit_step_approval(project.id, "approved")
elif s == "awaiting_escalation_review":
client.submit_escalation_decision(project.id, "retry")
# 4. Read the sealed output + verify the replay bundle offline.
if project.status == "complete":
output = client.get_agent_project_output(project.id)
verdict = client.verify_replay_bundle(project.id) # BLAKE3 + Ed25519, client-side
assert verdict["ok"]
elif project.status == "blocked":
client.resume_project(project.id, "retry_failed") # or skip_failed / restart_step
Simulation costs are approved in two parts. A SCADA simulation's price depends on its scenario pack, so it is never approved blind:
- Pack preparation. If a compatible pack already exists it is reused at
no cost. Otherwise the plan's
cost_summary["stages"]shows the one-off cost of preparing a new, reusable pack, and approving the plan authorises only that — the plan'stotal_estimated_creditsis the maximum you authorise now, andtotal_expected_creditsis the expected figure. - Execution. Once the pack exists, the run is priced from it and the
project pauses in
awaiting_step_approvalwithpending_step_approval["trigger_reason"] == "execution_consent". Theconsentblock carriesexpected_credits,maximum_credits, the workloadbasisand afingerprint; approve withmax_creditsequal to the maximum shown andconsent_fingerprint. The run cannot be charged above the credits you approve: authorising less than its maximum stops it before it starts (nothing is charged) and the cost is asked for again. If the cost changed after it was shown the API answers 409 and nothing is approved. When the pack already exists at planning time, the execution is priced in the plan and approving the plan covers it.
client.estimate_job(contract, prompt_text=...) gives the same two-part
price before you create a project (stages, with basis equal to
"bounded_estimate"). rady ads approve and rady ads decide <id> approve
show both costs and bind them the same way.
Other lifecycle methods: list_agent_projects, get_agent_project,
cancel_agent_project, delete_agent_project, stream_agent_project
(SSE), get_evidence_chain, verify_trail, get_evidence_entries,
get_replay_bundle, list_agent_memories / delete_agent_memory
(CSSM), list_agent_tools, and execute_agent_tool (run one planner
tool directly). The rady ads CLI drives the same surface from a
shell. ADS-only error codes: AGENT_SESSION_LIMIT, GOAL_OUT_OF_SCOPE.
Cryptographic evidence
Successful Fabricate, Synthesize, and simulation generation paths expose a BLAKE3-sealed multi-file evidence bundle. The exact file count varies by generation and output shape. Retrieve and verify programmatically:
evidence = client.get_evidence(job.id)
print(evidence.seal_hash) # binding hash over the bundle's artifacts
client.verify_job(job.id) # re-computes every artifact hash server-side
Artifacts include the sealed contract JSON, the run manifest, the generated data, the constraint / determinism / privacy / utility reports, and the hash manifest. The full schema is in the documentation (https://docs.radmah.ai/sdk).
More
- Full API reference: https://docs.radmah.ai/sdk
- OpenAPI spec:
client.get_openapi()at runtime
For issues, contact support@radmah.ai.
Licence
Proprietary (LicenseRef-Proprietary); see the LICENSE file shipped with
the package. You may read the source and use the unmodified package to access
the RadMah AI platform under your account agreement; you may not copy, modify,
redistribute or reuse it.
Metadata
Release files for radmah-sdk 1.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| radmah_sdk-1.4.0.tar.gz | 208.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| radmah_sdk-1.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 400.6 kB
Release files / radmah_sdk-1.4.0.tar.gz
| Download URL | radmah_sdk-1.4.0.tar.gz |
|---|---|
| Size | 208.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6d03265bf5b3b2ec2d4209f5b9b3943c4a86ddbd9f160006bb3493fe920a26ac
|
|
BLAKE2b-256 checksum How to use checksums |
d194de918d38c2f9b0f2047285282b99e08a797a52aaa218782bbade78239128
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / radmah_sdk-1.4.0-py3-none-any.whl
| Download URL | radmah_sdk-1.4.0-py3-none-any.whl |
|---|---|
| Size | 192.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d4bfa286ca5667ad03d7caaa58bd9a2a45e28612c3e973da326dcc795e77f77d
|
|
BLAKE2b-256 checksum How to use checksums |
24d13a3615cad85533cccf83829a6be2154c09b5635521fd9757bd12e64da10d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|