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.
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 questionAsk as support or as an engineer and compare the evidence each receives
Answers with an evidence trail
Query engineering and support sources with separate grants. Inspect exact passages, source updates, and refused access.
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.
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.
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.
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()
})
}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.
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}))
}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.
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(())
}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.
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)
}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:
./run --run input.json02 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.
cd chio-knowledge-network
./runThe 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
abe203f5b0fbebbf32c6b87055e8033c22c9e77445a2875f1144542fbbd96708Import 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.