Chio/Docs
LOGIN · JOIN

Build an institutional knowledge network

Retrieve permitted source versions, produce a cited answer, and show the caller when revocation or a stale citation stops a request.

Guide chio@5f63e6e

On this page

An institutional assistant answers from documents its caller may read and names the exact version behind every claim. This application gives engineering and support separate source ownership, governs retrieval through Chio, and checks each citation against the stored UTF-8 bytes.

Ask a question

Ask as support or as an engineer and compare the evidence each receives

Build it. Run it. Inspect it.

Answers with an evidence trail

Query engineering and support sources with separate grants. Inspect exact passages, source updates, and refused access.

Download project
Your inputs
Source documents
Engineering recovery procedure
Support 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…

Ask how to recover an operation with a missing acknowledgement, then switch between the support and engineer callers to compare the permitted evidence. Retrieval mode shows the passages; model mode synthesizes an answer and returns checked citation IDs. Enable revocation to see a later request refused while past disclosures stay in the retained run.

Ownership decides what retrieval reads and versions decide what a citation claims

The kernel decides which retrieval tool a caller may invoke. The retrieval handler and the citation check then keep every returned passage tied to its owner and its version.

Knowledge networkChoose a lens.
dispatchesreadspassagessignssupport_retrieveoutside scopea₁Support callera₂Engineer callerKKerneldRetrieval tools₁Engineering sourcess₂Support sourcesgCitation checkversion and byte rangeρReceiptspassagessignsoutside scopea₁Supporta₂EngineerKKerneldRetrieval tools₁Engineerings₂SupportgCitation checkρReceipts

Each caller holds its owner's retrieval tool

Source ownership decides which retrieval tool a caller's grant names. The support caller's request for engineering_retrieve is refused at the kernel, so forbidden source text never reaches the context it would have assembled. Retrieve permitted passages.

The knowledge network application: two callers, one kernel, the retrieval tool, two owned source sets, and the citation check. The ranking function and model synthesis are left out.

Sources carry an owner and a content-derived version

The source store loads documents with stable IDs, owner labels, and content-derived versions, and an imported local corpus uses the same representation. Ownership selects the registered retrieval server, so a support request collects no engineering text however hard the model searches.

knowledge-network/src/store.rsL30–59rust
    pub fn load(sources: Vec<Source>, path: &Path) -> Result<Self> {
        anyhow::ensure!(
            !sources.is_empty() && sources.len() <= 32,
            "Provide 1 to 32 sources"
        );
        private_directory(path)?;
        let mut map = BTreeMap::new();
        let mut bytes = 0;
        for source in sources {
            anyhow::ensure!(
                ["engineering", "support"].contains(&source.owner.as_str()),
                "Source owner must be engineering or support"
            );
            anyhow::ensure!(
                !source.id.is_empty() && source.id.len() <= 100 && source.title.len() <= 200,
                "Source identifiers or titles are too long"
            );
            bytes += source.text.len();
            anyhow::ensure!(bytes <= 200_000, "Corpus exceeds 200000 bytes");
            anyhow::ensure!(
                map.insert(source.id.clone(), source).is_none(),
                "Source IDs must be unique"
            );
        }
        write_json(&path.join("sources.json"), &json!(map))?;
        Ok(Self {
            sources: Mutex::new(map),
            ..Self::default()
        })
    }

Open this excerpt in the complete source

Retrieval returns only the permitted owner’s passages

The starter ranks passages with a small lexical index that is easy to inspect and replace. It returns source IDs, titles, version digests, byte ranges, and the exact text. Retrieval runs inside the governed tool handler, so a refused request receives no context assembled from forbidden sources.

knowledge-network/src/store.rsL66–134rust
    pub fn retrieve(&self, owner: &str, query: &str) -> Result<Value> {
        anyhow::ensure!(
            !query.trim().is_empty() && query.len() <= 2000,
            "Provide a query of 1 to 2000 bytes"
        );
        let terms: Vec<String> = query
            .split_whitespace()
            .map(|t| {
                t.trim_matches(|c: char| !c.is_alphanumeric())
                    .to_lowercase()
            })
            .filter(|t| t.len() > 2)
            .collect();
        let sources = self
            .sources
            .lock()
            .map_err(|_| anyhow::anyhow!("Corpus lock failed"))?;
        let mut passages = Vec::new();
        for source in sources.values().filter(|s| s.owner == owner) {
            let version = chio_core::sha256_hex(source.text.as_bytes());
            let mut offset = 0;
            for paragraph in source.text.split_inclusive("\n\n") {
                let lower = paragraph.to_lowercase();
                let score = terms.iter().filter(|t| lower.contains(t.as_str())).count();
                if score > 0 {
                    let end = offset + paragraph.len();
                    let id = chio_core::sha256_hex(
                        format!("{}:{version}:{offset}:{end}", source.id).as_bytes(),
                    );
                    passages.push(json!({
                        "id":id,
                        "source_id":source.id,
                        "owner":source.owner,
                        "title":source.title,
                        "version_sha256":version,
                        "start_byte":offset,
                        "end_byte":end,
                        "text":paragraph,
                        "score":score
                    }));
                }
                offset += paragraph.len();
            }
        }
        passages.sort_by(|a, b| {
            b["score"]
                .as_u64()
                .cmp(&a["score"].as_u64())
                .then_with(|| a["id"].as_str().cmp(&b["id"].as_str()))
        });
        passages.truncate(6);
        let mut returned = self
            .returned
            .lock()
            .map_err(|_| anyhow::anyhow!("Passage lock failed"))?;
        for passage in &passages {
            returned.insert(
                passage["id"].as_str().context("Missing passage ID")?.into(),
                passage.clone(),
            );
        }
        *self
            .calls
            .lock()
            .map_err(|_| anyhow::anyhow!("Corpus lock failed"))?
            .entry(owner.into())
            .or_default() += 1;
        Ok(json!({"owner":owner,"passages":passages}))
    }

Open this excerpt in the complete source

In the verified run, event 02 allows support_retrieve with receipt 58109f83…e7f60e, and event 04 refuses engineering_retrieve for the same caller with receipt 719026b2…7051ac.

A citation is valid only at its exact bytes

An answer can cite only passages present in the store. Validation checks the current version and byte boundaries before it accepts a citation. Updating a document changes its version, so a stale passage is refused even when its title and source ID still match.

knowledge-network/src/store.rsL135–156rust
    pub fn validate(&self, passage: &Value) -> Result<()> {
        let sources = self
            .sources
            .lock()
            .map_err(|_| anyhow::anyhow!("Corpus lock failed"))?;
        let source = sources
            .get(passage["source_id"].as_str().context("Missing source")?)
            .context("Source no longer exists")?;
        anyhow::ensure!(
            passage["version_sha256"] == chio_core::sha256_hex(source.text.as_bytes()),
            "Source changed; retrieve its current version before citing it"
        );
        let start = passage["start_byte"]
            .as_u64()
            .context("Missing start byte")? as usize;
        let end = passage["end_byte"].as_u64().context("Missing end byte")? as usize;
        anyhow::ensure!(
            source.text.get(start..end) == passage["text"].as_str(),
            "Citation does not resolve to the exact UTF-8 passage"
        );
        Ok(())
    }

Open this excerpt in the complete source

The answer shows only evidence that resolves

Model output names citation IDs. The application resolves those IDs through the source store and validates each passage again, so a confident sentence without a supporting passage stays unsupported. The interface shows the answer beside the retrieved evidence, with an explicit empty state when nothing supports the request.

knowledge-network/src/store.rsL175–198rust
    pub fn cited_passages(&self, ids: &Value) -> Result<Vec<Value>> {
        let returned = self
            .returned
            .lock()
            .map_err(|_| anyhow::anyhow!("Passage lock failed"))?;
        let ids = ids
            .as_array()
            .context("Answer must contain a citations array")?;
        anyhow::ensure!(ids.len() <= 12, "Too many answer citations");
        let mut selected = Vec::new();
        for id in ids {
            selected.push(
                returned
                    .get(id.as_str().context("Citation IDs must be strings")?)
                    .context("Answer cited a passage that was never returned")?
                    .clone(),
            );
        }
        drop(returned);
        for passage in &selected {
            self.validate(passage)?;
        }
        Ok(selected)
    }

Open this excerpt in the complete source

A forbidden source, a stale citation, and a revoked grant are refused

The sample caller is support, and its request for engineering sources is the refusal the run retains:

Terminalbash
./run --run input.json
Retained events, captures/verified.json
02  call.allowed              support
    Allowed support_retrieve
    receipt 58109f83…e7f60e
04  call.denied               support
    requested tool engineering_retrieve on server knowledge is not in capability
    scope
    receipt 719026b2…7051ac
  • Use a caller without the source grant: forbidden corpus text stays out of the returned context.
  • Update a document after retrieval: the old version fails citation validation.
  • Revoke the grant: later retrieval is refused, and past disclosures stay on record.

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

Run the knowledge network on your machine

Keep the run directory to inspect the returned source versions, cited passages, and retrieval receipts.

From the extracted projectbash
cd chio-knowledge-network
./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
abe203f5b0fbebbf32c6b87055e8033c22c9e77445a2875f1144542fbbd96708

Import your own corpus

Set CHIO_CORPUS_DIR to import a local knowledge directory in the included layout. For a larger corpus, replace the ranking function and keep owner filtering, exact source versions, and citation validation. The starter’s services share one kernel; the personal network chapter moves knowledge behind an independent host.

Next: build a research swarm