CIFR

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:

  1. A depends_on: list naming the upstream RAI(s).
  2. 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:

  1. Plans the composition. The two derived inputs each map to a separate call into the upstream resiliency-pds agent.
  2. Runs each upstream call. A fresh Invocation row is created for each, with caller_invocation_id set to the downstream's id (so the caller DAG is queryable from the database alone).
  3. Stages each upstream output at /inputs/score_a.json and /inputs/score_b.json inside the downstream container.
  4. Runs the downstream container as if score_a and score_b had been user uploads — your compare.py reads them from /inputs exactly the way it reads any other input. It has no idea they came from another agent.
  5. 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.0 pins 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.