Chio/Docs
LOGIN · JOIN

Build a research swarm

Turn signed observations into retained assignments, run bounded investigations, and compare adaptive with fixed scheduling on the same workload.

Guide chio@5f63e6e

On this page

A research swarm can choose its next task from evidence its workers have already produced. This application investigates a corpus with discovery, numerical-claim inspection, and reproduction tasks; workers send signed observations to a receiving host, which admits and stores them and ranks governed assignments by their concentration.

Run the investigation

Observations steer the next assignment and capabilities decide each call

Build it. Run it. Inspect it.

Research that follows the evidence

Signed observations influence a real assignment queue. Chio still admits every selected tool call independently.

Download project
Your inputs
Source documents
Latency trial A
Latency trial B
Experiment runbook

Hosted records are retained for seven days. Use the downloaded project for local work and your own model credentials.

Loading the verified example result…

Change the research question, the receiver’s trust weight, the observation age, or the work allowance. The concentration view reads the native signal store (the pheromone layer) at the selected query time. Compare adaptive and fixed assignment on the same sources and allowance: the application reports completed work, refused work, duplicate claims, and work units for each.

Signals rank the work and the kernel admits each call

The observation path never reaches the kernel’s authority: a stronger signal moves a task up the ranking and grants nothing. The assigned worker presents its own capability when it calls a research tool.

Research swarmChoose a lens.
signed batchadmitsconcentrationassignsclaimsdispatchessignsown capabilityauthority removeda₁Source observergReceiversignature, replay, scarcitysSignal storedecay at query timeπSchedulerranks workcJob claimsone owner per joba₂Assigned workerKKerneldResearch toolsρReceiptsclaimssignsrefuseda₁ObservergReceiversSignalsπSchedulercClaimsa₂WorkerKKerneldResearch toolsρReceipts

Selection proposes; the capability decides

A signal ranks work and grants nothing. The assigned worker presents its own capability at the tool call, and removing that authority stops the selected task before it reaches the research handler. Signals between agents.

The research swarm application: the observation path into the signal store, the scheduler and its claims, and the governed research call. The fixed-assignment comparison run and the ranking weights are left out.

The receiver admits observations before it stores them

The sender transmits a signed GossipBatch over HTTP to a separately configured receiver. The receiving endpoint checks the sender and treaty, the signature, replay state, and scarcity policy before it stores a deposit. The starter runs both host configurations in one process; the personal network application carries observations over iroh between independent processes.

research-swarm/src/signals.rsL41–80rust
async fn receive(
    State(state): State<Arc<ReceiverState>>,
    headers: HeaderMap,
    Json(batch): Json<PheromoneGossipBatch>,
) -> std::result::Result<Json<Value>, (StatusCode, String)> {
    if headers.get("authorization").and_then(|v| v.to_str().ok())
        != Some(&format!("Bearer {}", state.token))
    {
        return Err((
            StatusCode::UNAUTHORIZED,
            "Use the configured observer identity".into(),
        ));
    }
    let mut context = state.context.clone();
    context.now_unix_ms = now_ms();
    let config = PheromoneReceiverConfig {
        recipient_kernel_id: RECEIVER.into(),
        authenticated_sender_kernel_id: SENDER.into(),
        validation_context: context,
    };
    let report = state
        .store
        .receive_batch(&batch, &state.policy, &config, &NoWorkflowClaims)
        .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
    let value = json!(report);
    state
        .run
        .emit(
            "observation.received",
            "receiving-host",
            if report.accepted {
                "Admitted signed observations"
            } else {
                "Refused an observation batch"
            },
            value.clone(),
        )
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
    Ok(Json(value))
}

Open this excerpt in the complete source

In the verified run, event 31 refuses a batch with replay_window_exceeded and event 32 refuses one with signature_invalid.

Concentration is read at the application clock

A concentration is the receiver’s current view of admitted evidence. The selected query time sets the native store’s decay calculation, and receiver trust is explicit and adjustable. The browser draws the returned values; decay and admission happen in the store.

research-swarm/src/signals.rsL251–275rust
    pub fn concentrations(
        &self,
        query_seconds: u64,
        weight: f64,
    ) -> Result<Vec<PheromoneConcentration>> {
        let mut context = self.state.context.clone();
        context.now_unix_ms = self.timestamp + query_seconds * 1000;
        let weights = StaticPeerWeightProvider::new(10, [(SENDER.into(), weight)]);
        CLASSES
            .iter()
            .map(|class| {
                self.state
                    .store
                    .query_concentration(
                        class,
                        NAMESPACE,
                        context.now_unix_ms,
                        10,
                        &context,
                        &weights,
                    )
                    .map_err(Into::into)
            })
            .collect()
    }

Open this excerpt in the complete source

Event 06 ranks the stored observations: contradiction at strength 0.95, reproduce at 0.8, and discover at 0.45.

One claim record owns each assignment

A unique job record decides who owns an assignment, so competing or restarted workers cannot make a job available twice. Selection proposes work; the assigned worker presents its own capability at the tool call, and removing that authority stops the task before it runs.

research-swarm/src/work.rsL159–168rust
    pub fn claim(&self, job: &str, worker: &str) -> Result<bool> {
        let connection = self
            .0
            .lock()
            .map_err(|_| anyhow::anyhow!("Claim lock failed"))?;
        Ok(connection.execute(
            "INSERT OR IGNORE INTO claims(job,worker,state) VALUES(?1,?2,'claimed')",
            rusqlite::params![job, worker],
        )? == 1)
    }

Open this excerpt in the complete source

Events 07 to 18 alternate a retained claim with an allowed call, each with its own receipt, such as 08fb429b…61e89c.

Each investigation stays attached to its request and source

The reproduction tool parses numerical samples from the corpus and computes their mean. Discovery returns versioned passages. The claim inspector surfaces competing numerical statements with their context and leaves the semantic judgement to the reader. Each result stays attached to its governed request and source version.

research-swarm/src/work.rsL63–125rust
    fn perform(&self, tool: &str, args: Value) -> Result<Value> {
        let source = self
            .sources
            .iter()
            .find(|s| s.id == args["source"])
            .context("Choose a source from the supplied corpus")?;
        let question = args["question"]
            .as_str()
            .context("Provide a research question")?;
        let result = match tool {
            "discover" => self.observe(question),
            "contradiction" => {
                // Surface competing numerical claims and retain the passages. The
                // reader or synthesis worker decides whether their contexts conflict.
                let mut claims = Vec::new();
                for s in &self.sources {
                    let mut start = 0;
                    for line in s.text.split_inclusive('\n') {
                        if line.chars().any(|c| c.is_ascii_digit())
                            && !words(question).is_disjoint(&words(line))
                        {
                            claims.push(citation(s, start, start + line.len()));
                        }
                        start += line.len();
                    }
                }
                json!({
                    "claims":claims,
                    "assessment":"Competing numerical statements require context review; no semantic contradiction is asserted by this extractor"
                })
            }
            "reproduce" => {
                let values = source
                    .text
                    .lines()
                    .find_map(|line| line.strip_prefix("samples:"))
                    .context("This source has no samples: line to reproduce")?;
                let values = values
                    .split(',')
                    .map(|s| s.trim().parse::<f64>())
                    .collect::<std::result::Result<Vec<_>, _>>()?;
                anyhow::ensure!(
                    !values.is_empty()
                        && values.len() <= 100
                        && values.iter().all(|v| v.is_finite()),
                    "Use 1 to 100 finite numeric samples"
                );
                let mean = values.iter().sum::<f64>() / values.len() as f64;
                json!({
                    "source":source.id,
                    "version_sha256":chio_core::sha256_hex(source.text.as_bytes()),
                    "values":values,
                    "mean":mean,
                    "sample_count":values.len(),
                    "calculation":"sum(samples) / count(samples)"
                })
            }
            _ => anyhow::bail!("Unknown research responsibility"),
        };
        self.effects.fetch_add(1, Ordering::SeqCst);
        Ok(result)
    }
}

Open this excerpt in the complete source

Replayed, altered, and over-quota observations are refused

After the comparison, the sample input sends a replayed batch, an altered batch, and a fresh one. The receiver’s reports are retained:

Terminalbash
./run --run input.json
Retained events, captures/verified.json
31  observation.received      receiving-host
    rejected: replay_window_exceeded
32  observation.received      receiving-host
    rejected: signature_invalid
33  observation.received      receiving-host
    accepted: accepted
  • Change or replay a signed observation: the receiving host refuses it.
  • Exceed the receiver’s per-class scarcity allowance: extra deposits are not admitted.
  • Remove the selected worker’s authority: the assignment is refused before it enters the research handler.
  • Reopen the claim store and claim a completed job: it stays unavailable.

The downloadable tests assert each effect or its absence: run cargo test --locked -p chio-research-swarm from the extracted project.

Run the research swarm on your machine

Keep the run directory to connect admitted observations, selected assignments, claims, and research results.

From the extracted projectbash
cd chio-research-swarm
./run

The archive holds the application, the shared host code, tests, sample input, pinned dependencies, and a README. The launcher builds and runs the Rust source included in this archive. Install Rust 1.94.1 and the README’s native prerequisites; filesystem isolation and operator workloads require the listed Linux host profile. Cargo resolves immutable public dependencies, so a sibling checkout is unnecessary.

For model workers, set OPENROUTER_API_KEY on the local host and select model mode; credentials stay on that host and never enter the docs form. The qualified model is openai/gpt-4.1-mini. Analysis, retrieval, and deterministic worker modes run without a model key. Ctrl-C stops the local web process and its active child work and keeps the run directory.

Download identity and source revision

Download complete project · View public source

Project archive SHA-256text
2c2c1fd490e5700839b1ed3b16d7abae60a658cf9346a684ca59b7e1100aa559

Replace the ranking policy and measure it

Replace the ranking policy with one that values the information your team needs, and keep the workload and allowance fixed when you compare it with fixed assignment. Add a model researcher through the mission host’s bounded tool loop and retain the observations that informed its next task.

Next: build an agent cooperative