Concepts
Composition runtime
How agents call other agents — the depends_on graph, derived inputs, and transitive provenance.
The most powerful thing CIFR does isn't running a single experiment. It's letting one paper call another paper with no glue code. Composition turns the agent registry into a graph: a downstream paper invokes an upstream paper as part of its own execution, the upstream output is fed in as a downstream input, and the downstream's citation hash covers the entire transitive closure.
What you need to declare
Two things in the downstream agent's cifr.yml:
- A
depends_on:list naming the upstream RAI(s). - On any input that should be filled by an upstream agent, a
from_agent:binding pointing at the upstream's RAI + the output to read + the inputs to feed it.
agent:
name: resiliency-comparator
rai: RAI-2025-tan-resiliency-comparator
version: 1.0.0
description: Compare two grid topologies on the Chanda resiliency index.
depends_on:
- RAI-2016-chanda-resiliency-pds
inputs:
- name: topology_a
format: application/json
- name: topology_b
format: application/json
- name: score_a
format: application/json
from_agent:
rai: RAI-2016-chanda-resiliency-pds
output: result
inputs_from:
topology: topology_a
- name: score_b
format: application/json
from_agent:
rai: RAI-2016-chanda-resiliency-pds
output: result
inputs_from:
topology: topology_b
outputs:
- name: comparison
format: application/json
invoke: python compare.py
When someone calls this agent with {topology_a: ..., topology_b: ...}, CIFR:
- Plans the composition. The two derived inputs each map to a separate call into the upstream
resiliency-pdsagent. - Runs each upstream call. A fresh Invocation row is created for each, with
caller_invocation_idset to the downstream's id (so the caller DAG is queryable from the database alone). - Stages each upstream output at
/inputs/score_a.jsonand/inputs/score_b.jsoninside the downstream container. - Runs the downstream container as if
score_aandscore_bhad been user uploads — yourcompare.pyreads them from/inputsexactly the way it reads any other input. It has no idea they came from another agent. - Computes a provenance hash that covers the full closure: the downstream's image digest, inputs, outputs, plus the provenance hashes of each upstream invocation. Citing the downstream automatically vouches for the upstream too.
The inputs_from mapping
inputs_from is the explicit wiring from the upstream's input field names to the downstream's user-payload keys:
from_agent:
rai: RAI-2016-chanda-resiliency-pds
output: result
inputs_from:
topology: topology_a # upstream's `topology` input ← downstream's `topology_a`
This is explicit on purpose. Implicit name-matching breaks the moment you have two dependencies that both take a field called data. The contract should be readable by a stranger without running the planner.
What v1 supports
- Single-level derivation. A downstream can call any number of upstreams, and the same upstream can be called multiple times with different inputs. Each upstream call gets its own Invocation row.
- Version pinning.
from_agent.version: 1.0.0pins to a specific upstream version. Omit it to always resolve the latest registered version. - Cycle detection. A self-cycle (agent depending on itself) is rejected at plan time.
- Cross-call fan-out. Two derived inputs can reference different upstream agents — they run independently.
What v1 doesn't support yet
- Transitive derivation. An upstream can't itself have derived inputs in v1. Flatten the dep, or wait for v2's full DAG resolver.
- Caching across invocations. Each composed call re-runs the upstream, even if you call the downstream twice with the same
topology_a. v2 will cache by(image_digest, input hashes). - Streaming. All upstreams run to completion before the downstream starts. v2 may add overlap when the downstream's start doesn't depend on every upstream.
Why this is the point of CIFR
A research paper that just uses NumPy and writes a CSV doesn't compose with anything — it's a one-shot. A research paper as a callable agent with declared inputs, declared outputs, and a stable identifier becomes a building block. When you can write depends_on: [RAI-2016-chanda-resiliency-pds] in your own paper, you're not "citing" the prior work — you're literally calling it as part of your method, every time someone runs you, with the provenance to prove it.
That's the promise. v1 is the foundation; the runtime, the citation hash, and the caller DAG all work today. Build on it.