# Act on Reality
> Turn your intent into verifiable outcomes, through intelligent cooperation.
AI agents can plan, reason, and call tools. But real-world work doesn't just need more capable agents, it needs **shared reality** and a source of truth across these dimensions:
**Why we are doing it**—**verifiable intent** so people, programs, and agents align on purpose and success criteria, not only on tasks or outputs.
**Who is involved**—identities and **roles**: who can represent an organization, a device, a service, or an agent in the system, and how that is established and delegated.
**What is being asserted**—**claims** and the **protocols** that supply rubrics, rules, standards, and outcome definitions so assertions can be reviewed against something explicit.
**What backs the claim**—documents, measurements, observations, attestations, media, reports, sensor data, or external records, linked so reviewers and automation can inspect the same material.
**Who has authority to act**—**credentials**, **capabilities**, and **permissions** that determine who may submit, attest, dispute, pay, publish, or change state on behalf of whom.
**Whether a result holds**—decisions, impacts, and approvals: what was reviewed, by whom, under which rules, and what was accepted, rejected, or sent back for more evidence.
**What changed**—**transactions**, **state transitions**, and **immutable logs** so “what happened” is inspectable over time, not only the latest screen in an app.
**What was achieved**—**verifiable outcomes** tied to evidence and decisions: the record of what the system treats as true after review, suitable for reporting, funding, or compliance.
**How value moves**—**financing**, **generation and circulation of assets**, and **value flows** (payments, rewards, fees, reserves, settlement) that can be anchored to verified outcomes and governed rules, not only offline agreements.
**What should happen next**—**cooperative workflows** that route the right actor, human or agent, to the next step using shared context and governed handoffs—not ad hoc chat threads.
**IXO with Qi** provides the operating systems for acting on reality, for you and your organization to make positive impacts on the world, and for you to benefit from the intelligence economy.
## What makes this different
Most AI platforms help agents complete tasks inside existing tools.
IXO and Qi help you build workflows that people, organizations, and agents can trust across many systems and participants.
Use this stack when your workflow needs to know who is involved, what is being claimed, what evidence supports it, who has authority to decide, what value should move, and what outcome was actually achieved.
**IXO is the trust layer** that creates a verifiable graph of the state of real-world entities, with identities, claims, credentials, evidence, transactions, and verifiable outcomes.
**Qi is the intelligent cooperating system** that empowers humans, AI agents, applications, and services to cooperate over that graph through secure context, declared tools, governed workflows, and inspectable state changes.
Model people, organizations, assets, claims, evidence, credentials, transactions, and outcomes as connected graph state.
Link claims to evidence, credentials, attestations, authority, and protocol-defined state changes.
Coordinate humans, agents, applications, and services through shared context and governed workflows.
Connect measurements, verification, funding, governance, and learning to real-world results.
## How it works
Let's look at a verified claims workflow to see how IXO and Qi are different from ordinary agent tooling.
An implementer, organization, device, service, or agent submits a claim about work completed, evidence collected, eligibility, delivery, compliance, performance, or impact.
Documents, measurements, observations, attestations, reasoning traces, media, reports, sensor data, or external records are attached to the claim.
The workflow verifies who submitted the claim, which domain or entity they represent, the credentials or permissions that allowed them to act, and which capabilities were invoked.
Agentic Oracles, overseen by human operators, inspect evidence, check program rules, flag risks, summarize context, generate decision and impact determinations, then recommend next actions.
Reviewers, verifiers, funders, operators, services, and agents work through secure rooms and messages, to coordinate actions, use tools, and implement cooperative workflows.
Accepted claims, attestations, transactions, outcomes, payments, governance actions, or next workflow states emit decision and impact determinations as immutable, auditable records with provenance.
Verified outcomes feed analytics, agent evaluation, decision support, program design, and future workflow improvement, through automated learning loops.
The core pattern is that agents do not invent facts or act on disconnected prompts. **Humans and agents cooperate over shared state** to produce changes that others can inspect, rely on, and build on. **Qi** is the intelligent cooperation layer that makes this possible.
## What you can build
The workflow above is one **verified-claims-shaped** slice of the stack. Everything else—**PODs, Flows, Blueprints, Agentic Oracles, assets, markets**, recurring **program shapes** (MRV, outcome-linked financing, secure cooperation rooms, learning loops) are covered in the guides.
Pick a first build, see how the pieces fit together, and open program-shape accordions with stack detail and **Start here** links into hands-on guides for each shape.
Shared vocabulary for domains, claims, evidence, state, cooperation, and how IXO and Qi split responsibility for verifiable outcomes.
## Canonical references
Use these when wiring integrations so auth headers and base URLs match the interface you call.
Chain IDs, RPC, REST, Matrix, and service base URLs referenced in docs
Auth patterns by API surface (protocol gateways vs service APIs)
Short definitions of IXO terms with links to deep dives
## This is just the beginning.
Turn your intent into a programmable organizational domain (POD) with a shared workspace, configured flows for your team and AI agents, and economic mechanisms.
Automate work, elevate productivity, earn financial rewards, and drive positive impact with verifiable records of outcomes.
The data, memories, and intelligence you generate remain yours to own and control. You can sell, rent, or trade it as you choose.
---
# Your Role
> Choose how you want to start your journey into the future of voluntary intelligent cooperation, based on your intent to build, fund, evaluate, research, develop, or make markets for the verified outcomes you want to achieve.
We are now all builders of the future we want to live in, where the form of how we organize is fundamentally changing with AI. This is redefining the roles we play in the system, and the way we cooperate to achieve our goals.
People, agents, services, markets, protocols, and institutions are beginning to cooperate through shared digital systems rather than only through traditional organizational structures.
Many of the biggest problems we face in daily life and as humanity are coordination failures. We have the intent, knowledge, resources, and technologies to solve many problems, but we struggle to align people, verify facts, allocate capital, govern action, and learn from outcomes.
In an AI future, the optimal path is voluntary intelligent cooperation.
IXO and Qi give you a way to participate in this new operating model. IXO provides the verifiable state layer for identities, Claims, credentials, evidence, assets, outcomes, and transactions. Qi coordinates humans, AI agents, applications, services, and organizations through secure workspaces, governed Flows, capability-based authority, and inspectable state changes.
Start by choosing the role you are playing now.
You may hold more than one role. Choose the role that best matches what you want to do first.
## Choose your entry point
Offer services, capabilities, data, verification, implementation, agent support, or outcomes through trusted PODs, Flows, and Marketplaces.
Build applications, Agentic Oracles, MCP tools, Qi Flows, integrations, schemas, and automation over IXO-backed state.
Allocate capital to programs, outcomes, services, protocols, markets, or organizations with verifiable evidence and settlement rules.
Review Claims, inspect evidence, apply rubrics, issue determinations, and support trusted decisions.
Generate knowledge from verified data, evidence graphs, experiments, Claims, outcomes, and learning loops.
Create trusted exchange, liquidity, discovery, pricing, fulfillment, and settlement for services, protocols, agents, data, and outcomes.
## The shared building blocks
Every role works with the same core building blocks.
**What it does:** Creates a programmable organizational domain.
**Why it matters:** Gives people, agents, services, and organizations a secure place to cooperate.
**What it does:** Coordinates work through governed steps.
**Why it matters:** Turns activity into inspectable state transitions.
**What it does:** Defines a reusable protocol.
**Why it matters:** Makes rules, rubrics, evidence requirements, and outcomes repeatable.
**What it does:** Enables trusted exchange.
**Why it matters:** Helps participants discover, offer, request, verify, and settle value.
**What it does:** Records something asserted, submitted, reviewed, delivered, or achieved.
**Why it matters:** Makes work, evidence, and outcomes inspectable.
**What it does:** Delegates scoped authority to people, agents, services, or tools.
**Why it matters:** Ensures actions happen within explicit permissions.
**What it does:** Records a decision and impact determination.
**Why it matters:** Creates an auditable record of what was decided, why, by whom, and with what effect.
---
## 🤝 Service Provider
You are a Service Provider if you want to offer useful work into the IXO and Qi ecosystem.
You may provide implementation services, evidence collection, verification, AI agent operations, data services, research support, digital MRV, local field operations, protocol design, marketplace fulfillment, or outcome delivery.
### Your role
Service Providers help turn intent into verified action.
You may:
- deliver services for a POD
- fulfill a Marketplace listing
- submit Claims about completed work
- attach evidence to Claims
- operate an Agentic Oracle
- provide field verification or expert review
- implement a protocol in the real world
- receive payment, rewards, credentials, or reputation when outcomes are verified
### Start your journey
Describe the service, capability, data, verification function, agent service, or outcome you can reliably deliver.
Join an existing POD, create your own POD, or publish your offer into a Marketplace.
Define the intake, delivery, evidence, review, approval, and settlement steps for your work.
Record what you delivered and attach documents, measurements, observations, reports, attestations, media, sensor data, or external records.
Let evaluations, UDIDs, credentials, payments, and completed outcomes become part of your operating history.
### First useful build
Start with one verified service offer.
**Example:** Field data collection for clean cooking usage.
**Example:** Digital MRV program operator.
**Example:** Service request, delivery, evidence submission, review, approval, payment.
**Example:** Work completed for household visits.
**Example:** Field reports, timestamps, geo-tagged observations, photos, device references.
**Example:** Human verifier or Agentic Oracle checks completeness and consistency.
**Example:** Determines whether the service was accepted and whether payment should be released.
### Build path
Operate inside a secure domain with roles, tools, rooms, agents, and governed permissions.
Make your service discoverable to buyers, funders, programs, and other participants.
Coordinate service delivery, evidence submission, review, approval, and settlement.
Make your Claims reviewable under clear evidence rules and outcome criteria.
---
## 💻 Developer
You are a Developer if you want to build the technical systems that make intelligent cooperation work.
You may build applications, Agentic Oracles, MCP tools, Qi Flows, schemas, data integrations, evidence pipelines, marketplace components, claim processors, dashboards, or developer tools.
### Your role
Developers make verifiable coordination programmable.
You may:
- build apps that read and write IXO-backed graph state
- create Qi Flows for governed workflows
- define Claim schemas and validation logic
- build Agentic Oracles for review, routing, summarization, or decision support
- expose tools through MCP interfaces
- use UCANs to scope agent and service authority
- generate Evaluation Claims and UDID records
- connect marketplaces, payments, credentials, and external systems
### Start your journey
Pick one coordination problem, such as claim review, evidence intake, service fulfillment, funding approval, marketplace ordering, or credential issuance.
Identify the entities, Claims, evidence, credentials, roles, Flow states, and decisions that need to exist.
Use UCAN-style capability scoping so agents, services, and users can only perform allowed actions on allowed resources.
Create the trigger, states, actions, failure paths, human checkpoints, and outputs.
Add Agentic Oracles, MCP tools, validation services, external APIs, data pipelines, and dashboards.
Ensure the system emits Claims, Evaluation Claims, evidence references, state transitions, and UDIDs where decisions and impacts are determined.
### First useful build
Start with an agent-assisted Claim review Flow.
**Example:** Service completed, evidence submitted, outcome achieved, supplier verified.
**Example:** Evidence Review Oracle.
**Example:** Read one Claim, inspect linked evidence, apply one rubric, create one Evaluation Claim.
**Example:** Submitted, authority check, context resolved, evaluating, human review, determined, actioned.
**Example:** Evaluation Claim and proposed transition.
**Example:** UDID after human or protocol-approved decision.
### Build path
Create the governed state machine for the workflow you want to automate.
Add agent services for evidence review, decision support, summarization, or monitoring.
Expose structured context and actions to agents without giving them uncontrolled system access.
Create, validate, evaluate, dispute, and automate verifiable Claims.
---
## 💰 Funder
You are a Funder if you want to allocate capital toward verified work, services, outcomes, programs, or markets.
You may fund impact programs, service delivery, outcome incentives, liquidity pools, research, protocol development, verification capacity, agent services, or marketplace growth.
### Your role
Funders help turn resources into verified outcomes.
You may:
- create or fund a POD
- define funding eligibility rules
- sponsor outcomes-based programs
- fund Marketplace demand or liquidity
- release value when Claims are verified
- require evidence, evaluation, and UDID-backed determinations
- support service providers, researchers, and developers
- govern how funds are allocated, reserved, released, or recovered
### Start your journey
State what outcome, service, protocol, market, or organization you want to support.
Decide whether you are funding grants, services, verified outcomes, marketplace liquidity, research, protocol development, or ongoing operations.
Specify the Claims, evidence, credentials, evaluations, and determinations required before value moves.
Use a Flow to govern intake, eligibility, approval, evidence review, milestone completion, dispute handling, and settlement.
Release funds only after the required decision and impact determination exists.
### First useful build
Start with one outcome-funded program.
**Example:** Pay for verified adoption of a clean cooking technology.
**Example:** Program domain for funders, implementers, verifiers, and agents.
**Example:** Protocol defining eligible households, evidence, usage thresholds, and outcomes.
**Example:** Application, service delivery, claim submission, review, determination, payment.
**Example:** Outcome achieved for a household or project.
**Example:** Device telemetry, field report, household record, verifier attestation.
**Example:** Determines whether the outcome was verified and whether value should be released.
### Build path
Set up the operating domain for funders, operators, implementers, verifiers, and agents.
Specify eligibility, evidence, rubrics, outcome rules, and settlement conditions.
Govern applications, reviews, milestones, determinations, payments, and disputes.
Create demand, incentives, liquidity, or rewards for verified services and outcomes.
---
## ✅ Evaluator
You are an Evaluator if you review Claims, inspect evidence, apply rubrics, issue recommendations, or make determinations.
You may be a human verifier, expert reviewer, auditor, governance participant, standards body, community representative, or operator of an Agentic Oracle.
### Your role
Evaluators create trust in the system.
You may:
- inspect Claims and linked evidence
- check whether evidence satisfies a Blueprint
- apply a rubric
- identify missing, stale, invalid, or conflicting evidence
- create Evaluation Claims
- recommend approval, rejection, escalation, or dispute
- issue or support UDID-backed determinations
- improve rubrics and protocols based on review outcomes
### Start your journey
Start with one type of Claim that you can evaluate consistently.
Apply a versioned Blueprint that defines evidence requirements, scoring, disqualifiers, thresholds, and escalation rules.
Confirm that you, your service, or your Agentic Oracle has the UCAN authority required to inspect the Claim and perform the evaluation.
Write the review result as structured data with evidence references, applied checks, recommendation, limitations, and proof.
When the Flow reaches a decision point, record the final decision and impact determination with authority, evidence, and state transition.
### First useful build
Start with agent-assisted evidence review.
**Example:** Service completed, outcome achieved, supplier eligible, data submitted.
**Example:** Documents, measurements, observations, attestations, media, reports, sensor data.
**Example:** Required fields, evidence checks, thresholds, disqualifiers, escalation rules.
**Example:** Summarizes evidence and flags inconsistencies.
**Example:** Accepts, rejects, escalates, or requests more evidence.
**Example:** Records findings and recommendation.
**Example:** Records the accountable determination.
### Build path
Evaluate agent work and evidence review using UCAN authority, Claims, rubrics, and UDID records.
Work with verifiable assertions, evidence, review status, disputes, and automation.
Govern review, escalation, approval, rejection, dispute, and determination steps.
Define the rules that make evaluation consistent and repeatable.
---
## 🔬 Researcher
You are a Researcher if you want to generate, analyze, validate, or publish knowledge from verifiable data and outcomes.
You may study interventions, markets, protocols, agent behavior, evidence quality, environmental impact, social outcomes, digital MRV systems, funding mechanisms, or cooperative intelligence.
### Your role
Researchers help the system learn.
You may:
- define research questions and hypotheses
- design evidence protocols
- analyze Claims, evidence, outcomes, and determinations
- compare programs or interventions
- evaluate agent performance
- study coordination patterns
- publish findings with verifiable references
- improve Blueprints, rubrics, and Flows
- contribute to verified learning loops
### Start your journey
State what you want to learn and which real-world system, program, protocol, or market the question applies to.
Determine which entities, Claims, evidence records, credentials, Flow states, UDIDs, and outcomes are relevant.
Use POD roles, permissions, and UCAN-scoped access so research uses only authorized data and tools.
Define data requirements, inclusion criteria, evaluation methods, reporting rules, and publication standards.
Record research outputs as Claims linked to data, methods, evidence, limitations, and review history.
### First useful build
Start with one verified learning loop.
**Example:** Which evidence types most reliably predict verified service delivery?
**Example:** Claims, evidence references, Evaluation Claims, UDIDs, Flow timestamps.
**Example:** Research workspace with scoped access.
**Example:** Research protocol defining methods and access rules.
**Example:** Data access request, analysis, review, publication.
**Example:** Research Claim with methods, findings, evidence references, and limitations.
**Example:** Update rubrics, evidence requirements, or Flow design based on findings.
### Build path
Set up a secure workspace for researchers, data stewards, agents, reviewers, and collaborators.
Standardize methods, evidence access, analysis rules, review requirements, and publication outputs.
Work with measurements, reporting, verification, Claims, determinations, and learning loops.
Study agent outputs, authorization, evidence use, rubric adherence, and human override patterns.
---
## 🛍️ Market-maker
You are a Market-maker if you want to create trusted exchange between participants.
You may operate a Marketplace, design liquidity mechanisms, curate suppliers, create demand, define pricing, coordinate settlement, govern listings, or build markets for services, outcomes, protocols, agent capabilities, data, or credentials.
### Your role
Market-makers help cooperation scale.
You may:
- create a Marketplace
- define listing categories
- onboard suppliers and buyers
- set eligibility and credential requirements
- create pricing, fees, rewards, or liquidity incentives
- attach fulfillment Flows
- attach verification Blueprints
- route disputes
- coordinate settlement
- track market health and reputation
### Start your journey
Choose the first category of services, outcomes, protocols, data, credentials, or agent capabilities that participants can offer and request.
Identify suppliers, buyers, funders, verifiers, operators, agents, and governance roles.
Specify listing fields, eligibility rules, pricing, availability, evidence requirements, and fulfillment terms.
Every listing category should have a Flow for fulfillment and a Blueprint for verification.
Decide when payments, rewards, credentials, fees, or reputation updates happen, and require UDID-backed determinations for high-value actions.
### First useful build
Start with one verified service marketplace.
**Example:** Evidence collection services for digital MRV programs.
**Example:** Field operators and data service providers.
**Example:** Program operators, funders, project developers.
**Example:** Service area, method, capacity, price, credentials, evidence standards.
**Example:** Request, accept, deliver, submit evidence, evaluate, settle.
**Example:** Verification rules for service completion and evidence quality.
**Example:** Payment released after accepted service determination.
**Example:** Completed services, disputes, evaluations, and credentials update supplier profile.
### Build path
Create the exchange layer for listings, requests, fulfillment, verification, and settlement.
Operate the market with roles, governance, agents, tools, moderation, and analytics.
Coordinate orders, delivery, evidence, review, disputes, and settlement.
Make every listing category reviewable under clear rules and evidence standards.
---
## If you are not sure which role to choose
Use this guide.
**Start as:** Service Provider
**Start as:** Developer
**Start as:** Funder
**Start as:** Evaluator
**Start as:** Researcher
**Start as:** Market-maker
## Recommended first operating loop
The fastest path is to build one complete loop before scaling.
Give the work a secure operating domain with roles, rooms, tools, agents, and authority.
Specify the rules, evidence requirements, rubrics, and outcome logic.
Coordinate submission, review, determination, action, and closure.
Make the work inspectable with structured assertions and linked proof.
Use humans, Agentic Oracles, and rubrics to create Evaluation Claims and UDID-backed decisions.
Release value, issue credentials, update state, publish findings, improve the protocol, or route the next action.
## Production readiness
Before inviting more participants, check that each role has a clear responsibility.
The provider knows what they offer, which POD or Marketplace they operate in, which Claims they submit, what evidence is required, and how settlement happens.
The workflow has typed Claims, scoped UCAN authority, explicit Flow states, safe tool access, structured outputs, test cases, and inspectable records.
The funding rules define eligibility, evidence, evaluation, determination, settlement, disputes, and governance.
The evaluator has a versioned rubric, scoped authority, evidence access, clear escalation rules, and a way to record Evaluation Claims and UDIDs.
The research process has authorized data access, defined methods, evidence references, review rules, privacy boundaries, and publication standards.
The marketplace has a clear category, supplier and buyer rules, listing standards, fulfillment Flows, verification Blueprints, settlement logic, and dispute handling.
## Start building
Build a POD, Flow, Blueprint, or Marketplace.
Explore practical use cases for IXO and Qi.
Understand the verifiable graph of identities, Claims, evidence, credentials, entities, and outcomes.
Learn how humans, agents, services, and organizations cooperate over shared state.
---
# Core concepts
> Vocabulary and mental models for IXO, Qi, entities, claims, evidence, verification, and workflows.
This page is **definitions and structure**: what words mean and how layers fit together. For motivation, positioning, and a short worked example, read [Act on Reality](/introduction) first. When you are ready to pick a first artifact (POD, Flow, Blueprint, and so on), use [What you can build](/guides/what-you-can-build). For DIDs, claims, and verifiable credentials in one place, read [Identity and credentials](/articles/identity-and-credentials). For quick term lookup, use the [Glossary](/reference/glossary).
## The problem space
If you are building or buying systems for real-world work, you are usually asking how to **trust agents** with consequential work, **verify outcomes** before releasing money or authority, **coordinate** across organizations, and know **what changed**, **who did it**, and **whether it worked**.
IXO and Qi target that class of problem—not generic chat over siloed documents.
Most software tracks **internal** application state. Most AI systems reason over **generated** context. This stack coordinates many actors around **shared, verifiable reality**.
- **State** — Structured information that can be identified, permissioned, queried, verified, and updated through protocol-defined actions: you can say what is true and what changed.
- **Intelligence** — Humans and AI agents interpret state, coordinate with others, and take accountable action.
- **Cooperation** — Alignment of people, organizations, agents, services, evidence, and workflows around a shared map of work.
**IXO defines what is true and verifiable** in the shared model. **Qi defines how intelligent actors cooperate** over that model inside real workflows.
IXO and Qi are closely connected, but they are not the same layer.
**Problem:** AI agents and partners cannot safely act on **fragmented, unverifiable** data scattered across orgs and tools.
**What the component does:** IXO (including the [IXO Graph](/articles/ixo-graph) and [IXO Protocol](/protocols/ixo-protocol)) connects **entities, identities, claims, evidence, credentials, transactions, and outcomes** into structured state that can be shared, queried, and verified.
**What you build:** Workflows where humans and agents can **discover context**, **verify claims**, **trace changes**, and **ground decisions** in the same facts.
**Problem:** Real-world workflows need **humans, agents, services, and organizations** to coordinate without losing context or accountability.
**What the component does:** The **Qi Intelligent Cooperating System** provides the cooperation layer: actors **reason** over IXO-backed state, **exchange messages**, **evaluate evidence**, and **act** through declared workflows—with permissions and auditability.
**What you build:** Evidence-review agents, secure cooperation spaces, assisted verification, human approvals, and automated routing—see [Qi](/articles/qi-intelligent-cooperating-system) and [IXO Matrix](/articles/ixo-matrix).
Model identities, domains, entities, claims, credentials, assets, relationships, workflows, and evidence.
Read IXO-backed context, coordinate securely, reason over evidence, and act through governed workflows.
A useful rule:
- **Use IXO** to define, verify, persist, or query the shared state of **reality**.
- **Use Qi** to interpret, discuss, reason, decide, automate, or **act** on that reality.
## Domains
In IXO, an **entity domain** (often shortened to **domain**) is the standardized way to register and manage a **digital twin** of a real-world subject on the stack: decentralized identity, verifiable credentials, linked claims and resources, services, and relationships—see [Entity Domains](/guides/dev/ixo-domains) for the developer-oriented model and [Domain registration](/guides/domain-registration) for how domains are created and typed.
Each domain is anchored by a **digital identifier (DID)** following the interchain identifier pattern, has an **entity type** (what class of thing it is), and is associated with a **protocol** that defines the entity **class** and inherited properties. **Controllers** manage the on-chain domain record (DID document), and **metadata** carries standard settings for that type.
Common **domain types** used across solutions include the following (wording aligned with [Domain registration](/guides/domain-registration) and the [Emerging platform domain model](/platforms/Emerging/domain-registration)):
**Organisation** domains represent legal or virtual entities that operate programs, hold rights, or participate in governance—often managed as or alongside DAOs.
They anchor who is accountable, who can act for the entity, and how the organisation connects to projects, assets, and markets.
**Project** domains represent programs, portfolios, or interventions: the unit of work you measure, fund, verify, or report against.
Project domains are typically linked to protocols that define operational rules, may use PODs for automation, and often coordinate agents, resources, claims, and accounts—see [Project Domains](/articles/projects).
**Asset** domains represent physical systems, devices, infrastructure, sites, or **tokenized outcomes** that need identity, telemetry, custody, or verification on the graph.
They bridge real-world “things” to claims, evidence, and economic actions (for example issuance, transfer, or retirement of outcome-linked instruments).
**Protocol** domains (and protocol references on other domains) supply the **templates and rules** that govern behaviour: the entity **class**, inherited property sets, rubrics, and constraints that implementations must honour.
Creating a domain of another type usually requires selecting the protocol DID that defines its class, as described under [Domain properties](/guides/domain-registration#domain-properties) and [Creating domains](/guides/domain-registration#creating-domains).
**Oracle** domains (and oracle-linked services) represent **verification, measurement, or decision-support** capabilities attached to programs, assets, or workflows—often AI-enabled services that supply data or evaluations used in claims and reviews.
For how oracle services fit the wider stack, see [Oracle architecture](/guides/ixo-oracles-architecture).
**Deed* style domains structure **asks and commitments** between parties so governance, negotiation, acceptance, execution, and fulfillment can be represented on the graph and autonomously executed by the system and its participants.
The exact user-facing naming of domain types is configurable to the specific ontology of the use case. The underlying pattern is still a domain document with controllers, services, linked resources, rights, linked claims, relationships, and accounts.
## Core vocabulary
These terms appear throughout the docs, including **build-pattern** names from [What you can build](/guides/what-you-can-build).
A digital entity is a real-world actor, asset, system, place, project, organization, program, claim process, or other meaningful object represented in IXO.
Digital entities make real-world things addressable in software. They can have identity, metadata, relationships, credentials, claims, services, and state transitions.
A **domain** is the identity anchor for a digital entity: a DID-backed record that establishes who or what the entity is, which verification methods apply, and which actors or services may act on its behalf.
For **domain types** (organisation, project, asset, protocol, oracle, request/offer) and how they relate to registration, read [Domains](#domains) above, then [Domain registration](/guides/domain-registration) or [Entity Domains](/guides/dev/ixo-domains) for procedures and interfaces.
A **POD** (programmable organisational domain) is a governed **workspace**: members, roles, rooms, tools, graph entities, and agents share one coordination boundary.
Hands-on: [Build a POD](/guides/users/build-a-pod).
A **Flow** is a **governed workflow**: triggers, states, actions, evidence gates, reviews, and outcomes that move real work forward in an inspectable way.
Hands-on: [Build a Flow](/guides/users/build-a-flow).
A **Blueprint** is a reusable **protocol** for a class of work: claim schemas, evidence rules, rubrics, roles, permissions, and verification or settlement logic that many PODs or Flows can share.
Hands-on: [Build a Blueprint](/guides/users/build-a-blueprint).
A **Market** is a **discovery and settlement** pattern: listings, offers, fulfillment, verification, pricing, and disputes—usually implemented with Flows and Blueprints behind each listing type.
Hands-on: [Build a Market](/guides/users/build-a-market).
An **Agentic Oracle** is a **scoped automation or judgment service** (often with declared tools and human handoff) that reads IXO-backed context and participates in Flows—without replacing protocol authority or verified state.
Architecture: [Oracle architecture](/guides/ixo-oracles-architecture).
The **IXO Graph** is the shared, queryable map of entities, relationships, claims, evidence, credentials, and outcomes your applications and agents read from. It is the practical “shape” of IXO-backed state. More detail: [IXO Graph](/articles/ixo-graph).
A digital twin is a working representation of a real-world entity or process.
It combines identity, metadata, data services, verifiable state, relationships, and executable logic. A digital twin is not just a profile or database record. It is the operational model that applications, services, and agents use to interact with a real-world system—still expressed using domains, entities, claims, and evidence.
Guide: [Digital twins](/guides/digital-twins).
A claim is a verifiable statement about something.
Claims can describe status, eligibility, delivery, performance, measurement, compliance, completion, impact, or any other assertion that needs review or attestation.
A claim can be linked to evidence, evaluated by humans or agents, and accepted, rejected, disputed, or used to trigger further workflow steps.
A credential is a portable proof issued by an authority or trusted participant.
Credentials can describe identity, rights, roles, qualifications, authorizations, certifications, or attestations. They help systems determine who can do what, under which conditions, and with what level of trust.
A Flow is a structured sequence of actions, decisions, messages, claims, evidence, reviews, and state changes.
IXO-backed Flows make coordination explicit. Qi-enabled Flows allow humans, agents, and services to cooperate around each step using shared context and declared interfaces.
A deed is an executable set of agreements and obligations that are packaged with controllers, services, resources, rights. claims, relationships, and accounts.
Deeds can define requests and offers for units of work, or governance processes that are executed by the system.
## When to use the deeper technical words
Use plain language first (the [introduction](/introduction) stays non-jargony). When you need precision, these are the usual mappings:
First-impression phrase: **shared map** of people, assets, claims, evidence, and outcomes. Technically: a graph of entities and relationships expressed with **shared meaning** (for example linked data) so different systems interpret the same world the same way.
First-impression phrase: **tamper-resistant history** of important changes. Technically: how certain state transitions are recorded so history stays inspectable and consistent with protocol rules.
First-impression phrase: **data that different systems and agents can use together** without private one-off schemas everywhere. Technically: identifiers and relationships exposed in standard, machine-readable forms.
First-impression phrase: **shared definitions** for how real-world things are represented. Technically: controlled vocabularies and class relationships that keep programs aligned on meaning.
## State, data, context, and action
These four concepts are related, but they should not be confused.
Data is information available to a system.
It may come from APIs, databases, sensors, documents, messages, user input, or external services. Data can be useful without being verified, authoritative, or suitable for workflow decisions.
State is the current structured condition of an entity, claim, workflow, asset, credential, or relationship.
IXO state is designed to be identifiable, inspectable, permissioned, and verifiable. State tells the system what currently exists, what has changed, and which actions are valid.
Context is the relevant information an actor needs to understand what is happening.
Qi uses IXO-backed state, Matrix rooms, messages, evidence, workflow history, and external tools to provide context for humans and agents.
Action is a change made by a human, agent, application, service, or protocol process.
Actions may create entities, submit claims, issue credentials, request reviews, send messages, evaluate evidence, trigger workflows, or update state.
For production systems, the goal is to keep these aligned: actions should be based on context, context should be grounded in state, and state should be supported by verifiable data and evidence.
## System layers
IXO and Qi work together through several layers. They are ordered here from **outcomes and trust** down to **interfaces**.
The stack exists so programs can answer: **What happened? What was claimed? What evidence exists? What was accepted or rejected? What changed next?**
This is the through-line from **field reality** to **decisions and automation**—not only “logs,” but state and relationships you can inspect over time.
Domains, DIDs, verification methods, roles, and delegated rights establish who or what can act.
This layer answers questions such as:
- Who is this actor?
- What entity do they represent?
- Which actions are they allowed to perform?
- Which credentials or delegations support that authority?
IXO Protocol modules define entities, claims, credentials, assets, relationships, and state transitions.
This layer answers questions such as:
- What exists?
- What is currently true?
- Who asserted it?
- What evidence supports it?
- What changed, and when?
Linked services store, retrieve, index, and expose the data needed by applications, workflows, and agents.
This layer includes data services, evidence stores, indexed chain state, and query surfaces such as [IXO Blocksync](/articles/ixo-blocksync).
IXO Matrix provides encrypted rooms, messaging, and shared cooperation spaces.
This layer allows people, organizations, agents, and services to align around context before, during, and after state-changing actions.
Qi Agents and Agentic Oracles evaluate context, reason over evidence, support decisions, and trigger workflow actions.
This layer should not invent state. It should discover relevant state, respect permissions, use declared tools, and act through verifiable processes.
Applications, dashboards, SDKs, APIs, MCP servers, and developer tools expose the stack to builders and users.
This layer turns IXO state and Qi cooperation into usable products, workflows, and services.
**Compared to typical enterprise agent platforms:** those often optimize **deploying and monitoring agents inside one organization**. IXO and Qi emphasize **multi-party ecosystems**: shared evidence, verifiable state, and traceable change so agents cooperate on **real-world outcomes**, not only internal tickets and documents.
## How an agent should think about the stack
If you are **not** implementing agents, you can skip to [Where to go next](#where-to-go-next).
AI agents using IXO and Qi should follow a simple operating model.
Identify the entity, domain, participant, workflow, claim, asset, or room that the task refers to.
Use IXO-backed sources, SDKs, APIs, indexed data, or MCP tools to inspect the current state.
Treat verified state, user intent, generated reasoning, external data, and speculation as different categories.
Do not invent protocol actions, API shapes, permissions, or workflow steps. Use the documented SDKs, APIs, MCP tools, and workflow definitions.
Submit claims, messages, evaluations, updates, or actions through the appropriate governed process.
When an action changes state, make the result inspectable, attributable, and available for the next actor.
## Where to go next
**Reading**
Outcome-first positioning and a short walkthrough of a verified claims path.
Choose a first POD, Flow, Blueprint, Oracle, asset, or Market and follow hands-on guides.
Component deep-dives and design patterns across the stack.
Measurement, reporting, and verification as a program discipline.
**Hands-on patterns**
Submit, evidence, review, and decision in one guided shape.
Practical steps to wire measurement → report → verification → outcome.
Tie settlement actions to verified outcomes.
How program domains coordinate delivery, governance, and verification within a secure shared workspace.
**Protocol and engineering**
Trust, action, and coordination primitives for verifiable state.
Cooperation over IXO-backed state.
SDKs, setup, and implementation entry points.
Creating and managing claims in application code.
Package-specific setup, usage, and examples.
Connecting agents to IXO-backed tools and context.
---
# The Stack
> Trust and AI Infrastructure for Intelligent Cooperation.
Most organisations are not blocked because they lack AI tools. They are blocked because people and agents operate on conflicting information, decisions are disconnected from evidence, workflows break across organisational boundaries, automation cannot be trusted with real-world consequences, and nobody can clearly prove what happened, who acted, or whether outcomes were achieved.
The IXO Stack — **IXO Protocol** as the verifiable state layer and the **Qi Intelligent Cooperating System** as the human–AI cooperation layer — addresses that class of problem. It is not chat-based AI automation; it is outcome-driven coordination infrastructure for humans, AI agents, organisations, data systems, governance processes, and financial systems operating against shared, verifiable state.
## The two layers
IXO defines **what is true and verifiable**. Qi defines **how intelligent actors cooperate** over that state inside real workflows.
Identity, claims, evidence, credentials, governance, programmable coordination, payments, and outcomes — modelled as cryptographically verifiable state on the [IXO Graph](/articles/ixo-graph).
Humans and AI agents working together over IXO-backed state through shared context, declared interfaces, governed workflows, and inspectable actions.
A useful rule: **use IXO** to define, verify, persist, or query the shared state of reality; **use Qi** to interpret, reason, decide, automate, or act on that reality.
### What IXO records
IXO maps real-world systems into cryptographically verifiable digital state. Typical state transitions include:
- a carbon reduction event
- a youth skills credential
- a pathogen detection signal
- a delivery confirmation
- a governance decision
- a machine telemetry reading
- an evaluation outcome
- an AI-generated recommendation
These are recorded as reality-backed state, not spreadsheets, screenshots, or unverifiable API logs. See [IXO Protocol](/protocols/ixo-protocol) for the on-chain primitives and [IXO Graph](/articles/ixo-graph) for the shared, queryable map.
### What Qi coordinates
Qi creates persistent cooperative environments where context is shared, memory persists, authority is explicit, actions are governed, and outcomes are verifiable. It is designed around **intent, shared state, flows, capabilities, and outcomes** — moving teams from generating AI outputs to producing accountable outcomes. See [Qi Intelligent Cooperating System](/articles/qi-intelligent-cooperating-system).
## Core building blocks
These are the four building blocks every reader should recognise before going deeper.
Humans, AI agents, applications, and workflows operate against the same evolving source of truth. Shared state is synchronised with conflict-free replicated data types (CRDTs) so multi-party collaboration does not require a centralised authority. It produces continuity, coordination, accountability, durable memory, and explainability across actors.
Read more: [Core concepts — state, data, context, and action](/core-concepts#state-data-context-and-action).
A **POD** (Programmable Organisational Domain) is a governed cooperation environment — a team, company, project, field operation, supply chain, public health response, financing facility, or research collaboration. Every POD has identity, governance, shared memory, verifiable history, programmable permissions, and financial coordination primitives. PODs are the operational trust boundary for AI-enabled organisations.
Read more: [IXO PODs](/articles/pods). Hands-on: [Build a POD](/guides/users/build-a-pod).
**Qi Flows** coordinate work between humans and AI agents. Flows are state-driven, governance checkpoints are programmable, humans remain in the loop, and evidence and outcomes are first-class objects. A Flow can orchestrate AI services, request evaluations, trigger payments, issue credentials, update governance state, coordinate field operations, manage disputes, and route work dynamically — embedded directly into collaborative workspaces, not separate orchestration dashboards.
Hands-on: [Build a Flow](/guides/users/build-a-flow).
An **Agentic Oracle** is not just a model endpoint. It is a governed economic actor with identity, permissions, policies, payment mechanisms, reputation, verifiable execution records, and evaluatable outcomes. Examples include document evaluation, carbon verification, epidemiology, accounting, legal review, and sensor anomaly detection agents. Every action can be authorised, audited, evaluated, disputed, and settled.
Read more: [Agentic Oracles](/articles/agentic-oracles) and [Oracle architecture](/guides/ixo-oracles-architecture). Evaluation model: [Claim evaluation protocol](/articles/claim-evaluation-protocol).
## Trust architecture
The trust architecture is what makes intelligent automation safe for consequential work.
IXO uses **Decentralized Identifiers (DIDs)** to establish cryptographically verifiable identity for people, organisations, agents, devices, workflows, and governance domains. This enables portable trust, sovereign identity, verifiable delegation, and cross-platform interoperability.
The system aligns with [W3C Verifiable Credentials](https://www.w3.org/TR/vc-data-model/), [W3C DID Core](https://www.w3.org/TR/did-core/), and [W3C Data Integrity](https://www.w3.org/TR/vc-data-integrity/). See [Identity and credentials](/articles/identity-and-credentials).
Qi uses object-capability security for fine-grained authorisation. Permissions are explicit, delegated, time-bound, revocable, and scoped to intent and resources, so organisations can safely authorise agents to access systems, perform actions, operate tools, and coordinate with other agents — without granting blanket control.
Capability delegation patterns are informed by [UCAN](https://github.com/ucan-wg) and [ZCAP-LD](https://w3c-ccg.github.io/zcap-spec/). See [Authentication](/guides/dev/authentication), [Session keys](/guides/dev/session-keys), and [Authz](/guides/dev/authz).
Collaboration and shared state are protected with end-to-end encryption so platform operators cannot inspect private operational data, organisations retain sovereignty over their information, and AI cooperation occurs within controlled trust boundaries.
Secure coordination is built on the [Matrix Protocol](https://matrix.org). See [IXO Matrix](/articles/ixo-matrix).
Outcomes — evidence, evaluations, approvals, causal reasoning, financial settlement, governance decisions — become part of a durable, verifiable audit graph. Organisations can then automate trust, finance verified outcomes, coordinate across institutional boundaries, evaluate AI performance, and improve decision quality over time.
Read more: [Claim evaluation protocol](/articles/claim-evaluation-protocol) and [Digital MRV](/guides/digital-mrv).
## Typical deployment layers
A production IXO + Qi deployment composes several layers, each with a canonical home in the docs.
| Layer | Purpose | Canonical reference |
|---|---|---|
| IXO verifiable state | Identity, claims, governance, settlement | [IXO Protocol](/protocols/ixo-protocol) |
| Graph substrate | Shared, queryable map of entities and relationships | [IXO Graph](/articles/ixo-graph) |
| POD runtime | Organisational cooperation environments | [IXO PODs](/articles/pods) |
| Qi Flow engine | Human–AI workflow coordination | [Qi Intelligent Cooperating System](/articles/qi-intelligent-cooperating-system) |
| Agentic Oracles | AI services and accountable automation | [Agentic Oracles](/articles/agentic-oracles) |
| Matrix federation | Encrypted collaboration and shared documents | [IXO Matrix](/articles/ixo-matrix) |
| Indexing and query | Read-side access to protocol state | [IXO Blocksync](/articles/ixo-blocksync) |
| Endpoints and networks | Chain IDs, RPC, REST, Matrix base URLs | [Networks and endpoints](/reference/networks-and-endpoints) |
| External integrations | ERP, CRM, sensors, APIs, AI models | [Integrations](/articles/ixo-integrations) |
## Why this matters
AI systems can increasingly reason, plan, operate tools, execute workflows, and coordinate actions. But intelligence without trust creates systemic risk. The next layer of infrastructure must answer:
- Who authorised this action?
- What evidence supports this decision?
- Which agent performed the work?
- Can the outcome be independently verified?
- Can governance intervene?
- Can financial settlement be automated safely?
IXO and Qi are designed to answer these questions at infrastructure level. The internet connected information; this stack connects accountable action — for trusted cooperation, verifiable outcomes, governed AI systems, and programmable institutions.
## What you can build
Carbon markets, impact finance, youth livelihoods, development finance, and grant disbursement systems that move value only when outcomes are evidenced and verified.
Measurement, reporting, and verification systems with cryptographic trust, automated evaluation, and verifiable certification.
AI-assisted accounting, public health coordination, digital compliance, supply chain verification, and scientific collaboration with governed AI delegation and verifiable execution.
Federated research, public–private coordination, regulated data exchanges, and decentralised service marketplaces with sovereign data ownership and interoperable identity.
See [What you can build](/guides/what-you-can-build) for a fuller list of build paths with first-step guides.
## Design principles
Humans stay in the loop for governance, policy, accountability, oversight, escalation, and strategic judgement. AI systems augment coordination capacity. They do not replace institutional responsibility.
Assertions are insufficient. The system must support evidence, provenance, cryptographic verification, reproducibility, evaluation, and dispute resolution.
Organisations fail when coordination fragments. Shared verifiable state is the coordination substrate for people, agents, systems, and institutions.
The value of AI is not generated by conversations. It is generated by decisions, coordination, execution, and measurable outcomes.
## Frequently asked questions
Partially. Blockchain is used where durable public verification and settlement are necessary. Most operational collaboration occurs off-chain using encrypted shared state. The architecture balances sovereignty, scalability, privacy, interoperability, and auditability. See [IXO Protocol](/protocols/ixo-protocol) and [IXO Matrix](/articles/ixo-matrix).
No. IXO and Qi integrate with existing CRMs, ERPs, AI platforms, databases, identity providers, messaging systems, and analytics systems. The goal is trusted coordination across systems, not replacement. See [Integrations](/articles/ixo-integrations) and [Model Context Protocol (MCP) servers](/mcp/model-context-protocol).
Yes. The architecture is designed around sovereign identity, end-to-end encryption, federated infrastructure, explicit permissions, and portable trust. See [Domain privacy](/guides/dev/domain-privacy) and [IXO Matrix](/articles/ixo-matrix).
Yes — within governed trust boundaries. Agents can evaluate, coordinate, classify, generate, monitor, trigger workflows, and propose actions. Sensitive operations can require approvals, evaluations, multi-party authorisation, policy enforcement, and human oversight. See [Agent evaluations](/guides/dev/agent-evaluations) and [Claim evaluation protocol](/articles/claim-evaluation-protocol).
## Where to go next
Outcome-first positioning and a short verified-claims walkthrough.
Vocabulary and mental models for entities, claims, evidence, state, and cooperation.
Pick a first POD, Flow, Blueprint, Oracle, asset, or Market.
SDKs, APIs, and implementation entry points.
Short definitions of IXO and Qi terms.
Which surface owns what across the stack.
---
# Platform architecture
> Conceptual model of the IXO Platform stack and links to implementation docs.
This page describes the IXO Platform architecture model. Use guides and references for implementation details.
## Platform Stack
### 1. User Interfaces
Core applications for hosting and interacting with market ecosystem platforms
Proprietary and integrated applications
Asset and credential management
Intelligent interface agents and automation
Offline access to IXO services from any GSM phone — no smartphone or data plan required
### 2. Applications
* Progressive web applications
* Native mobile apps
* Cross-platform solutions
* Responsive interfaces
* Device management
* Sensor integration
* Real-time monitoring
* Edge computing
* Intelligent agents
* Predictive analytics
* Natural language processing
* Computer vision
* Enterprise connectors
* API integrations
* Data pipelines
* System bridges
### 3. Market Platforms
* Supply aggregation
* Demand aggregation
* Resource pooling
* Market making
* Price discovery
* Asset distribution
* Service delivery
* Value transfer
* Network routing
* Settlement systems
* Data marketplaces
* Analytics platforms
* Intelligence networks
* Knowledge graphs
* Insight distribution
* Oracle coordination
* Automated verification
* Real-time data feeds
* Truth consensus
* Intelligent automation
* Digital jurisdiction
* Governance frameworks
* Policy automation
* Collective intelligence
* Network sovereignty
### 4. Modular services
* **Linked-Data Libraries**: Semantic data management
* **Composable Data**: Modular data structures
* **DID Resolvers**: Identity resolution services
* **Data Oracles**: External data integration
* **Web 2.0 Connectors**: Legacy system integration
* **Schemers**: Reusable data patterns
* **Token Minting**: Asset creation and issuance
* **Liquidity Pools**: Market making services
* **Smart Contracts**: Automated transactions
* **Blockspace**: Transaction processing allocation
* **Fiat On/Off Ramps**: Currency conversion services
* **Cross-chain Routing**: Multi-chain transaction routing
* **Credential Exchange**: Verifiable credential management
* **Verification Protocols**: Standardized verification processes
* **Algorithms**: Computational verification tools
* **Agent Orchestration**: Multi-agent coordination
* **Model Integration**: AI model deployment and management
* **Learning Systems**: Adaptive intelligence
* **Inference Engines**: Decision support
* **Knowledge Bases**: Semantic understanding
* **Autonomous Services**: Self-governing systems
### 5. Digital infrastructures
* Peer-to-peer networks
* Message routing
* Event streaming
* Protocol bridges
* Blockchain networks
* Smart contract execution
* State management
* Consensus mechanisms
* Distributed file systems
* State databases
* Credential stores
* Data indexing
* **Data Infrastructure**
* Data lakes
* Feature stores
* Vector databases
* Stream processors
* **Model Infrastructure**
* Model registries
* Training pipelines
* Inference endpoints
* Model monitoring
* **Knowledge Infrastructure**
* Ontology stores
* Knowledge graphs
* Reasoning engines
* Semantic networks
* **Agent Infrastructure**
* Agent registries
* Coordination mechanisms
* Learning environments
* Policy frameworks
## Core Components
Blockchain network for digital twins, impact claims, and tokenization
Secure data storage and messaging with end-to-end encryption
AI-powered agent services that perform evaluations, verifications, and intelligent automations
Indexing and query service for blockchain data
## Platform Configuration
### Implementation Options
Open platforms with public verification
Permissioned access and governance
Combined public/private features
Managed platform deployment and operations
## Go here next
Follow task-oriented implementation guides.
Read the canonical concepts overview first.
Use SDK pages for packages and code-level usage.
Read platform and solution examples in the Emerging docs section.
## Reference links
Canonical SDK entry point and package references.
Review open-source code and examples.
Join our community for support and collaboration
Comprehensive guides and references
---
# Emerging Platform overview
> Understand what the Emerging Platform provides, when to use it, and how it differs from Emerging Household Energy.
The **Emerging Platform** is the reusable IXO platform layer for digital identity, claims, credentials, and dMRV workflows. Use this page when you are integrating platform capabilities that can support multiple solutions, including **Emerging Household Energy**. After reading, you can choose the right starting surface for platform APIs, registry integration, and solution-specific guides.
## Quick links
Solution application for household-energy operations
Service interfaces and REST APIs
Decentralised registry for projects, households, devices, and agents
Solution implementation guide for household-energy dMRV
Chat with the companion AI to guide and support your development
Dive deeper with our articles and guides
## Platform capabilities
Connect modern energy devices and sensors to track real-time usage, emissions, and impact metrics. Our IoT layer supports:
- Device onboarding and management
- Real-time data streaming
- Digital twin synchronization
Leverage the Impact Hub Network for:
- Decentralized household and device registry
- Decentralised data storage
- Verifiable credentials
- Digital claims processing
- Carbon credit tokenization
- Digital payment processing
- Register devices and households
- Register claims and outcomes
- Register agents and organisations
- Register projects and initiatives
- Register credits, retirements, and transfers
Access AI-powered services for:
- Automated data verification
- Anomaly detection
- Predictive maintenance
- Customer support automation
## Core services
Register and monitor IoT-enabled cooking devices with digital twins
Secure data ingestion and processing infrastructure
Automated verification and certification of impact claims
Convert verified outcomes into tradable digital assets
## Integration tools
### Access channels
Offline mobile access for rural households via USSD — no smartphone required
### Device Integration
Libraries for device connectivity and data streaming
Templates for device state management
Register devices and issue digital credentials
Real-time device tracking and analytics
### Impact verification
Implement digital measurement and verification
Automated data validation and verification
Device and claim registration services
Access the developer dashboard and companion AI
## Related IXO products
Network and protocol foundations for registry and verification
Data rooms and secure messaging for encrypted project data
Indexed query service for registry data access
## Support
Join our Slack for technical discussions
Explore detailed implementation guides
Access our open source repositories
Get in touch with our developer support
---
# USSD access channel
> How Emerging Household Energy uses USSD to give rural households offline access to IXO services.
Emerging Household Energy uses USSD as the primary offline access channel for rural household members. It is built on the open-source [IXO USSD gateway](/articles/ixo-ussd) and deployed as a fork at [emerging-eco/ixo-ussd-supamoto](https://github.com/emerging-eco/ixo-ussd-supamoto).
Users dial a USSD short code on any GSM phone — no smartphone or data plan required — to register their identity, manage their account, and interact with Emerging Household Energy services.
## Current capabilities
The Supamoto deployment supports:
- **Know More** — information about the programme, products, and services
- **Account creation** — register a new household member identity with a PIN, creating an IXO Decentralized Identifier (DID) and on-chain wallet
- **Account login** — authenticate returning users by phone number and PIN
Additional flows, including user services and agent services, are under active development. See the [repository](https://github.com/emerging-eco/ixo-ussd-supamoto) for current status.
## Related pages
- [IXO USSD gateway](/articles/ixo-ussd) — generic gateway architecture and how USSD connects to IXO Protocol
- [Deploy the IXO USSD gateway](/guides/dev/ussd-gateway) — set up and configure the gateway
- [Emerging Household Energy digital identifiers](/platforms/Emerging/digital-identifiers) — how DIDs and credentials are used for household members
---
# Emerging Platform concepts
> Understand the core concepts of the Emerging Platform and how they relate to Emerging Household Energy.
This page documents the **Emerging Platform** scope. Use it to understand reusable platform concepts before implementing the **Emerging Household Energy** solution.
## What the platform provides
The Emerging Platform combines reusable IXO capabilities for:
- identity and entity modeling with Decentralized Identifiers (DIDs)
- claim and credential workflows
- registry-backed state and verification trails
- digital measurement, reporting, and verification (dMRV) patterns
## How solution pages use these concepts
The **Emerging Household Energy** pages apply these platform capabilities to household-energy and clean-cooking workflows.
- For household execution flows, start with `/platforms/Emerging/household-monitoring`.
- For solution dMRV flow details, use `/platforms/Emerging/emerging-dmrv`.
- For platform-level dMRV capability, use `/platforms/Emerging/dmrv`.
## Cross-product IXO dependencies
Emerging Platform concepts depend on canonical IXO products:
- **Impact Hub Registry** for entity and claim state: `/platforms/Emerging/registry`
- **IXO Protocol** and network-backed records (where referenced in this section)
- **IXO Matrix** for secure data-room patterns where sensitive data is involved
## Next steps
Understand identity structure and DID modeling.
Review credential lifecycle and issuance flow.
Continue to solution implementation for household-energy workflows.
---
# Emerging Platform digital identifiers
> Use decentralized identifiers and credentials in the Emerging Platform across solutions, including Emerging Household Energy.
This page documents the **Emerging Platform** identity layer and the **cross-product IXO patterns** used by Emerging Household Energy and other solutions. Use it for reusable identifier and controller patterns. For household-energy workflow steps, pair this with the household-energy guides.
## What this covers
- Creating decentralized identifiers (DIDs) for projects, households, devices, and agents
- Managing controllers and update permissions
- Linking claims and credentials to identifiers
- Keeping sensitive personal data off-chain
## Scope
- **Platform scope:** identity model, controller patterns, and how domains anchor DIDs
- **Solution scope:** household-energy entities that use the same model (register households and devices before monitoring and reporting)
## Core model
Each entity is represented by a **DID** and an associated **domain record** with:
- Identifier and type metadata
- **Controller** references (who may update the DID document and authorize changes)
- Service endpoints
- Linked **claims** and **credentials**
At a glance:
- A Decentralized Identifier (DID) identifies each entity.
- Controllers manage updates and authorization.
- Claims and credentials reference entity DIDs.
- Registry state provides traceability for lifecycle events.
## Where this is used
This platform capability is used by:
- [Household monitoring](/platforms/Emerging/household-monitoring)
- [Stove use monitoring](/platforms/Emerging/stove-use-monitoring)
- [Emerging dMRV](/platforms/Emerging/emerging-dmrv)
**Where to apply this**
- **Platform integration:** identity-first registry and claim workflows.
- **Household Energy implementation:** register households and devices before monitoring and reporting.
## Cross-product IXO dependencies
- **IXO Protocol** for DID and domain state anchoring: [IXO Protocol](/protocols/ixo-protocol)
- **Impact Hub Registry** for entity and claim records: [Emerging registry](/platforms/Emerging/registry)
- **IXO Matrix** for encrypted payload storage when data should not be on-chain: [IXO Matrix](/articles/ixo-matrix)
For a single developer-oriented overview of DID, claims, and verifiable credentials together, see [Identity and credentials](/articles/identity-and-credentials).
## Related pages
- [Domain registration](/platforms/Emerging/domain-registration) — entity registration flow
- [Credential issuance](/platforms/Emerging/credential-issuance) — credential lifecycle
- [Registry](/platforms/Emerging/registry) — query and record management
---
# Emerging Platform credential issuance
> Issue and verify verifiable credentials on Emerging Platform, then apply them in Emerging Household Energy workflows.
This page documents **Emerging Platform** credential issuance and the **cross-product** credential patterns used across IXO surfaces, including Emerging Household Energy. For household-energy-specific credential flows, use [ITMO credentials](/platforms/Emerging/itmo-credentials).
## What this covers
- Credential issuance lifecycle
- Claim-to-credential relationship
- Holder and verifier interactions
- Status, revocation, and re-issuance patterns
## Supported credential flow (platform)
1. Create or receive a claim.
2. Validate claim completeness and issuer authority.
3. Issue a verifiable credential linked to the **subject DID**.
4. Publish status references for verifier checks.
## Credential lifecycle (cross-product)
1. Define the credential schema and required evidence.
2. Submit or evaluate claims against schema rules.
3. Issue credential to the subject DID.
4. Verify signature, issuer authority, and status.
5. Revoke or supersede credentials when state changes.
## Security and privacy baseline
- Sign credentials with issuer-controlled keys.
- Store only necessary metadata on registry state.
- Keep sensitive payloads in encrypted storage where needed.
- Enforce capability-based permissions for issuance and revocation.
## Household Energy usage
In **Emerging Household Energy**, credentials support household, device, and reporting evidence flows. Continue with:
- [Household monitoring](/platforms/Emerging/household-monitoring)
- [Household reporting](/platforms/Emerging/household-reporting)
- [ITMO credentials](/platforms/Emerging/itmo-credentials)
## Cross-product IXO dependencies
- **IXO Protocol** for verifiable registry state: [IXO Protocol](/protocols/ixo-protocol)
- **IXO Matrix** for encrypted credential payload handling: [IXO Matrix](/articles/ixo-matrix)
- **Impact Hub Registry** for claims and status references: [Emerging registry](/platforms/Emerging/registry)
## Related pages
- Solution implementation: [ITMO credentials](/platforms/Emerging/itmo-credentials)
- Schema reference: [ITMO schema](/platforms/Emerging/itmo-schema)
- Identity model: [Digital identifiers](/platforms/Emerging/digital-identifiers)
- dMRV capability: [dMRV](/platforms/Emerging/dmrv)
- Developer overview: [Identity and credentials](/articles/identity-and-credentials)
---
# Emerging Platform dMRV
> Reference the reusable dMRV capability model in Emerging Platform and link into solution-specific implementation guides.
This page documents the **Emerging Platform** scope. It is the canonical reference for reusable digital measurement, reporting, and verification (dMRV) capabilities that can be used by multiple solutions.
## What dMRV means at platform level
At platform level, dMRV provides shared capabilities for:
- entity-linked measurement records
- claims and verification workflows
- credential issuance and status checks
- tamper-evident registry references
These capabilities are not specific to one vertical implementation.
## Capability layers
DID-based entity identity and controller relationships.
Structured ingestion of time-linked monitoring evidence.
Rule-based and oracle-assisted checks on submitted claims.
Verifiable credentials for accepted outcomes and attestations.
## Core dependencies in IXO
- Identity and domain model: `/platforms/Emerging/digital-identifiers`
- Domain lifecycle and control: `/platforms/Emerging/domain-registration`
- Registry-backed state: `/platforms/Emerging/registry`
- Credential lifecycle: `/platforms/Emerging/credential-issuance`
## Solution implementations
Use this reference together with implementation pages:
- **Emerging Household Energy implementation:** `/platforms/Emerging/emerging-dmrv`
- Household data collection: `/platforms/Emerging/household-monitoring`
- Device telemetry flow: `/platforms/Emerging/stove-use-monitoring`
## When to use this page
Use this page when you are designing or integrating reusable dMRV infrastructure.
Use solution pages when you need workflow details for a specific domain.
---
title: 'Emerging Platform dMRV'
icon: 'chart-line'
description: 'Reference overview of dMRV capabilities in the Emerging Platform, with links to the Emerging Household Energy implementation.'
---
The **Emerging Platform** provides reusable digital measurement, reporting, and verification (dMRV) capabilities for identity-linked data collection, claim verification, and credential issuance. This page describes platform-level building blocks. For the household-energy implementation, use the **Emerging Household Energy dMRV** guide.
## What this page covers
The platform model is solution-agnostic and can be adapted to multiple domains. Core capabilities include:
- **Household-Level Reporting** - Consolidated data across multiple devices and fuel sources
- **Usage Monitoring** - Tracking of multiple devices and fuels to prevent double-counting
- **Connected Data Graph** - Linked entities and claims for system-wide insights
- **Immutable Records** - Blockchain-backed registry for full traceability
## Core features
IoT and smart device integration for continuous data capture
Immutable audit trail with blockchain verification
Standardized templates for rapid deployment
AI-powered oracles for data validation
## System architecture
Secure storage and cryptographic proofs
Configurable rules and verification logic
IoT device and system connectors
Automated verification services
## Core concepts
### Digital Entities
Digital entities are online representations of real-world objects or concepts in IXO services, including:
- Physical entities (stoves, energy devices)
- Cognitive entities (Organisations, projects, assets)
Each entity has a W3C DID (Decentralized Identifier) that provides:
- Unique identification
- Cryptographic verification
- Self-sovereign control
### Entity Relationships
Entities form nodes in a data graph connected by relationships that show:
- Project implementation by Organisations
- Project funding sources
- Asset generation
- Claim verification by Oracles
- Credential issuance
- Impact credit tokenization
### Domain Registration
Entities register on the blockchain as domains with:
- DID for verifiable ownership
- Domain controllers (authorized updaters)
- Connected services
- Linked resources and claims
- Object capabilities (zCAPs, Cacao)
- Economic accounts
- NFT representation
### Protocols
Protocols provide templates for domain configuration including:
- Default data fields
- Standard relationships
- Required services
- Governance settings
## Implementation patterns
### Device Integration
- Smart cooking appliances
- Energy meters
- Environmental sensors
- IoT-enabled infrastructure
- Time-stamped usage metrics
- Device identifiers (DIDs)
- Consumption data
- Environmental parameters
- Direct API integration
- IoT hub connectivity
- Batch data uploads
- Real-time streaming
## Quantification considerations
The accurate quantification of emission reductions in clean cooking initiatives depends on three critical factors:
1. **Household Fuel Consumption**: Precise measurement of fuel usage patterns and quantities
2. **Usage Rate**: Actual utilization rates of clean cooking devices
3. **Installation Base**: Accurate tracking of number and type of stoves installed
### Real-Time Field Data
Emission reductions must be verifiable and quantifiable to be recognized under international standards. The dMRV system ensures this through:
- **Continuous Data Collection**: Real-time field data provides the foundation for credible quantification
- **Immediate Verification**: Automated validation of incoming data streams
- **Temporal Accuracy**: No retroactive data collection or post-hoc estimations
- **Field-to-Registry Pipeline**: Direct flow from measurement to verification to registration
### Monitoring Requirements
To ensure reliable quantification, the dMRV system implements:
- **Comprehensive Monitoring Plans**: Structured data collection across all key metrics
- **Statistical Rigor**: Sample design and size calculations that meet methodology requirements
- **Data Quality Controls**: Validation of fuel consumption and usage measurements
- **Installation Tracking**: Verified device registration and deployment records
These elements are essential for:
- Accurate emission reduction calculations
- Reliable baseline establishment
- Verifiable impact claims
- Methodology compliance
## Workflow components
### Data Collection
Assign unique DIDs to households and issue credentials
Assign unique DIDs to monitoring devices
Secure data pipelines with integrity checks
Automated measurement and verification based on protocol methods and rules
### Verification Process
- Data completeness verification
- Threshold compliance monitoring
- Anomaly detection
- Pattern analysis
- AI-powered data validation
- Cross-reference checking
- Methodology compliance
- Real-time alerts
- Expert verification
- Audit support
- Compliance documentation
- Stakeholder reporting
## Related solution guide
Use this platform reference together with:
- [Emerging Household Energy dMRV](/platforms/Emerging/emerging-dmrv)
- [Household monitoring](/platforms/Emerging/household-monitoring)
- [Registry](/platforms/Emerging/registry)
## Benefits
- Real-time tracking of system dynamics
- Immutable, verifiable records
- Simplified entity management through protocols
- Automated workflows and verification
- Evidence-based oversight for Article 6.2 compliance
## Developer Resources
Complete API reference and examples
Connect with the Emerging Registry
Ready-to-use protocol configurations
## Related Guides
Implement household-level tracking
Track device usage patterns
Measure fuel consumption
Conduct household surveys
---
# Household Monitoring
> Implement household-level monitoring in the Emerging Household Energy solution.
This page documents **Emerging Household Energy** monitoring workflows for households, devices, and usage data. It is solution-scoped and builds on Emerging Platform identity, registry, and verification services.
## Key Features
Track complete household energy transitions and fuel consumption patterns
Monitor actual usage rates and stove utilization continuously
AI-powered validation of consumption and usage data
Accurate records of deployed devices and their status
## Digital Twin Setup
```json Household Model
{
"did": "did:ixo:household/123",
"type": "CleanCookingHousehold",
"location": {
"district": "XYZ",
"density": "Medium"
},
"devices": [{
"did": "did:ixo:device/456",
"type": "ElectricPressureCooker",
"status": "active"
}],
"baseline": {
"fuelType": "Charcoal",
"monthlyConsumption": 40
}
}
```
```python Create Household
from emerging import Household, Device
household = Household.create(
location={
"district": "XYZ",
"density": "Medium"
},
baseline={
"fuel_type": "Charcoal",
"monthly_consumption": 40
}
)
device = Device.get("did:ixo:device/456")
household.add_device(device)
```
## Data Collection
### IoT Integration
* Usage monitoring
* Fuel consumption
* Temperature sensors
* Power measurements
* Local validation
* Offline buffering
* Secure transmission
* Anomaly detection
## Impact Verification
Impact claim from measurements
Supporting IoT data and proofs
Oracle validation results
### Causal Analysis
```json
{
"householdId": "did:ixo:household/123",
"period": {
"start": "2024-01-01",
"end": "2024-01-31"
},
"analysis": {
"baselineEmissions": 120,
"actualEmissions": 45,
"reductions": 75,
"confidence": 0.95,
"deviceContributions": [{
"deviceId": "did:ixo:device/456",
"reductionShare": 0.8,
"evidence": {
"usageHours": 120,
"fuelSaved": 32
}
}]
}
}
```
## API Integration
### Household Management
```bash
# Create household
curl -X POST https://api.emerging.eco/v1/households \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"location": {
"district": "XYZ",
"density": "Medium"
},
"baseline": {
"fuelType": "Charcoal",
"monthlyConsumption": 40
}
}'
```
### Impact Analysis
```bash
# Analyze household impact
curl -X POST https://api.emerging.eco/v1/analysis/household \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"householdId": "did:ixo:household/123",
"periodStart": "2024-01-01",
"periodEnd": "2024-01-31"
}'
```
## Error Handling
Invalid household data
Household not found
Conflicting device data
## Best Practices
Implement comprehensive monitoring to capture the full complexity of cooking behavior and energy transitions.
### Data Quality
* Validate device installations
* Monitor sensor health
* Cross-reference data sources
* Track behavioral patterns
## Next Steps
Set up IoT monitoring
Configure impact verification
Access household insights
Integration server
## Related Guides
Implement continuous stove usage tracking
Conduct structured household surveys
Measure real-world fuel consumption
Generate comprehensive reports
---
# Stove Use Monitoring
> Implement stove-use monitoring in the Emerging Household Energy solution using sensor data.
This page documents **Emerging Household Energy** sensor workflows. It covers stove-use monitoring implementation for solution verification and reporting outcomes.
## Key Features
Installation required on all active cookstoves unless deviation is justified
Ongoing measurements with no interruption preferred
Track concurrent usage across multiple stoves to prevent double-counting
Regular collection to prevent data loss when digital transmission isn't possible
## Quick Start
```bash Register SUM
curl -X POST https://api.emerging.eco/v1/devices \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"type": "StoveUseMonitor",
"householdId": "did:ixo:household/123",
"stoveId": "did:ixo:stove/456",
"configuration": {
"samplingRate": 300,
"temperatureThreshold": 60,
"transmissionMode": "realtime"
}
}'
```
```python
from emerging import Device
# Register a new Device
sum_device = Device.create(
type="StoveUseMonitor",
household_id="did:ixo:household/123",
stove_id="did:ixo:stove/456",
configuration={
"sampling_rate": 300, # 5 minute intervals
"temperature_threshold": 60, # Celsius
"transmission_mode": "realtime"
}
)
```
## Implementation Requirements
* **Full Coverage Mandate**:
- Install sensors on ALL cookstoves in project
- Document any deviation with detailed justification
- Regular verification of sensor presence
- Monitor sensor health and calibration
* **Stove Stacking Prevention**:
- Track temperature patterns across all household stoves
- Detect concurrent usage
- Validate cooking event plausibility
- Cross-reference with household surveys
* **Continuous Operation**:
- Ongoing monitoring required
- No planned interruptions
- Battery life management
- Backup data storage
* **Data Continuity**:
- Real-time transmission where possible
- Regular manual collection if needed
- Backup procedures for power/connectivity issues
- Data gap documentation and justification
* **Digital Transmission**:
- Preferred method for data retrieval
- Real-time data validation
- Automatic anomaly detection
* **Manual Collection**:
- Required when digital not possible
- Frequent collection to prevent data loss
- Structured collection schedule
- Chain of custody documentation
* **Data Quality**:
- Validation of temperature readings
- Cross-checking between stoves
- Time synchronization
- Completeness verification
* **Coverage Requirements**:
- Full monitoring preferred
- Sampling allowed if digital collection impossible
- Must follow [sampling guidelines](/platforms/Emerging/sample-size)
- Statistical validity requirements
## Device Configuration
Seconds between temperature readings (recommended: 1-5 minutes)
Temperature (°C) threshold for cooking event detection
"realtime" | "batch" | "manual"
## Data Model
```json
{
"deviceId": "did:ixo:device/sum789",
"stoveId": "did:ixo:stove/456",
"readings": [{
"timestamp": "2024-02-20T08:15:00Z",
"temperature": 85.4,
"cookingEvent": true,
"duration": 1800
}],
"status": {
"battery": 92,
"signal": "strong",
"lastSync": "2024-02-20T08:20:00Z"
}
}
```
## Handling Stove Stacking
```python
from emerging import HouseholdAnalytics
# Analyze stove usage patterns
analytics = HouseholdAnalytics("did:ixo:household/123")
stacking_report = analytics.detect_stacking(
period_start="2024-02-01",
period_end="2024-02-28",
stove_types=["traditional", "improved"]
)
# Get detailed cooking events
events = analytics.get_cooking_events(
min_duration=1800, # 30 minutes
min_temperature=60
)
```
## Integration with Household Monitoring
```python
from emerging import Household, Device
# Get household digital twin
household = Household.get("did:ixo:household/123")
# Register SUMs for all stoves
for stove in household.stoves:
sum_device = Device.create(
type="StoveUseMonitor",
stove_id=stove.id,
household_id=household.id
)
# Link to household digital twin
household.add_device(sum_device)
# Monitor stove usage compliance
compliance = household.check_monitoring_compliance()
```
## Error Handling
Invalid device configuration
Device or stove not found
Data transmission failure
## Best Practices
### Installation
* Mount sensors securely
* Calibrate temperature thresholds
* Verify signal strength
* Test data transmission
* Document any coverage exceptions
### Monitoring
* Check battery levels regularly
* Validate sensor readings
* Monitor data completeness
* Track transmission status
* Verify continuous operation
### Data Collection
* Implement backup procedures
* Regular quality checks
* Document collection frequency
* Maintain collection records
* Prevent data loss
### Stove Stacking Prevention
* Monitor all household stoves
* Track concurrent usage
* Validate cooking patterns
* Cross-reference data sources
* Document usage anomalies
## Next Steps
Sensor placement and setup
Managing SUM data
Usage pattern analysis
Digital twin setup
## Related Guides
Track complete household energy transitions
Validate usage patterns with surveys
Verify fuel consumption measurements
Generate comprehensive reports
---
# Qualitative Surveys
> Implement qualitative survey workflows for the Emerging Household Energy solution.
This guide is scoped to **Emerging Household Energy**. It explains how to run baseline, follow-up, and monitoring surveys that complement solution dMRV evidence.
## Survey Types
Pre-distribution assessment and eligibility verification
Early adoption and performance evaluation
Ongoing SDG impact and emission reduction validation
## Survey Stages and Objectives
### Baseline Survey (Pre-Project)
- **Timing**: Before stove distribution/sales
- **Target Population**:
- Potential adopters
- Non-adopting households for comparison
- **Objectives**:
- Assess community perception of new stoves
- Gather socio-economic baseline data
- Verify household eligibility
- Validate inclusion criteria
- Inform sampling design
- **Data Collection**:
- Household demographics
- Current cooking practices
- Economic indicators
- Technology preferences
### Follow-up Survey (Post-Installation)
- **Timing**: Shortly after stove deployment
- **Objectives**:
- Identify performance strengths
- Detect early adoption issues
- Enable rapid problem resolution
- Assess user satisfaction
- **Focus Areas**:
- Installation quality
- Initial user experience
- Technical challenges
- Usage patterns
### Monitoring Survey (Ongoing)
- **Purpose**:
- Validate emission reduction calculations
- Verify SDG impacts
- Cross-reference with KPT/SUM data
- **Key Parameters**:
- Stove usage patterns
- Fuel consumption
- User satisfaction
- SDG indicators
- Inclusion criteria validation
## Implementation Requirements
* **MADD Integration**:
- Complete questionnaire in MADD
- Regular suitability assessment
- Adaptation based on monitoring feedback
* **Data Collection Protocol**:
- Defined collection methodology
- Specified storage solutions
- Clear responsibility assignment
* **Quality Control**:
- Validation procedures
- Cross-reference mechanisms
- Update protocols
* **Collection Responsibility**:
- Designated data collectors
- Training requirements
- Quality assurance roles
* **Storage Solutions**:
- Secure data repositories
- Access control
- Backup procedures
* **Data Flow**:
- Collection to storage pipeline
- Verification process
- Integration with other data sources
* **Methodology**:
- Statistical approach
- Population coverage
- Stratification criteria
* **Sample Size**:
- Calculation methodology
- Confidence levels
- Margin of error
* **Selection Process**:
- Random selection methods
- Bias prevention
- Documentation requirements
## Quick Start
```bash Create Survey
curl -X POST https://api.emerging.eco/v1/surveys \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"type": "BaselineSurvey",
"projectId": "did:ixo:project/789",
"template": {
"eligibilityCriteria": true,
"socioEconomicData": true,
"cookingPatterns": true,
"stovePerceptions": true
},
"sampling": {
"method": "stratified",
"size": 100,
"confidence": 0.95
}
}'
```
```python
from emerging import Survey, SamplingDesign
# Create a new survey campaign
survey = Survey.create(
type="BaselineSurvey",
project_id="did:ixo:project/789",
template=Survey.Templates.BASELINE,
sampling=SamplingDesign(
method="stratified",
size=100,
confidence=0.95
)
)
```
## Data Models
### Survey Template
```json
{
"templateId": "baseline-v1",
"sections": [{
"id": "eligibility",
"questions": [{
"id": "current_cooking",
"type": "multiple_choice",
"required": true,
"options": ["wood", "charcoal", "lpg", "electric"]
}]
}, {
"id": "socio_economic",
"questions": [{
"id": "household_size",
"type": "number",
"validation": {
"min": 1,
"max": 20
}
}]
}],
"validations": {
"requiredSections": ["eligibility"],
"completionThreshold": 0.8
}
}
```
### Survey Response
```json
{
"responseId": "survey-123",
"surveyId": "baseline-v1",
"householdId": "did:ixo:household/123",
"timestamp": "2024-02-20T10:00:00Z",
"responses": {
"eligibility": {
"current_cooking": ["wood", "charcoal"]
},
"socio_economic": {
"household_size": 5
}
},
"verification": {
"status": "verified",
"verifier": "did:ixo:validator/456",
"timestamp": "2024-02-20T10:15:00Z"
}
}
```
## Survey Management
```python
from emerging import SurveyManager, DataValidator, SampleCalculator
# Create survey campaign with CDM-compliant sampling
calculator = SampleCalculator(
project_id="did:ixo:project/789",
confidence=0.95,
precision=0.05
)
sampling = calculator.compute(
population=1000,
expected_mean=0.8,
expected_sd=0.2,
dropout_rate=0.15
)
# Create and deploy survey
manager = SurveyManager("did:ixo:project/789")
campaign = manager.deploy_survey(
template_id="baseline-v1",
sampling=sampling,
collectors=["did:ixo:agent/123"]
)
```
For detailed information on sampling methodology and CDM compliance, see the [Sample Size Calculator](/platforms/Emerging/sample-size) guide.
## Data Handling
```python
from emerging import DataProcessor, SDGValidator
# Process survey responses
processor = DataProcessor()
results = processor.analyze_survey_data(
survey_id="baseline-v1",
validation_rules={
"completeness": 0.8,
"consistency": True
}
)
# Validate SDG impacts
sdg_validator = SDGValidator()
sdg_impacts = sdg_validator.assess_impacts(
survey_data=results,
sdg_targets=["SDG7", "SDG13"]
)
```
## Integration with Monitoring Tools
```python
from emerging import MonitoringIntegration
# Create integrated analysis
integration = MonitoringIntegration(
project_id="did:ixo:project/789"
)
# Cross-validate data sources
validation = integration.cross_validate(
survey_data="baseline-v1",
sum_data="sum-456",
kpt_data="kpt-789"
)
# Generate comprehensive report
report = integration.generate_report(
period_start="2024-01-01",
period_end="2024-01-31"
)
```
## Error Handling
Invalid survey configuration
Survey or response not found
Conflicting response data
## Best Practices
### Survey Design
* Align with project objectives
* Include all MADD requirements
* Enable data cross-validation
* Support multiple languages
* Allow for periodic updates
### Data Collection
* Train survey collectors
* Implement quality controls
* Ensure consistent methodology
* Document collection process
* Maintain chain of custody
### Data Analysis
* Cross-reference with SUMs/KPTs
* Validate response consistency
* Track temporal changes
* Generate actionable insights
* Monitor inclusion criteria
### Documentation
* Record methodology
* Track survey updates
* Document responsibility assignments
* Maintain data flow records
* Archive survey versions
## Next Steps
Standard questionnaire formats
Mobile survey tools
Impact assessment methods
Cross-validation with SUMs
## Related Guides
Track complete household transitions
Validate with sensor data
Measure fuel consumption
Generate comprehensive reports
---
# Emerging Household Energy SDG monitoring
> Implement SDG monitoring in the Emerging Household Energy solution using dMRV and verifiable claims.
This page documents **Emerging Household Energy** solution workflows for SDG monitoring. It applies platform dMRV capabilities to household-energy program indicators.
The solution tracks SDG indicators through:
- Decentralized Identifiers (DIDs) for entities
- Verifiable claims and credentials
- Oracle-based validation
- Ex-post and ex-ante data collection
## Core Components
### Entity DIDs
Each participant (household, business, project) has a unique DID that links to their claims and credentials.
### Claims & Credentials
- **Claims**: Data records about an entity (e.g., fuel purchases, survey responses)
- **Credentials**: Verified claims that are cryptographically signed and stored on the ledger
### Oracles
Validation agents that verify claims using:
- External APIs
- Sensor data
- Survey forms
- Partner databases
### Data Collection Methods
- **Ex-Ante**: Baseline data collected before intervention
- **Ex-Post**: Follow-up data collected at defined intervals
## SDG Implementation Guide
### SDG 1: No Poverty
**Indicator**: Annual household fuel cost savings
Requires tracking baseline vs. current fuel expenditure through validated purchase claims
**Data Flow**:
1. Register household DID
2. Record fuel purchase claims
3. Oracle validates payments
4. Issue savings credentials
### SDG 7: Clean Energy Access
**Indicator**: Modern cooking device adoption rate
**Data Flow**:
1. Log fuel purchase claims
2. Calculate usage thresholds
3. Validate through oracles
4. Generate adoption credentials
### SDG 13: Climate Action
**Indicator**: GHG emission reductions
**Data Flow**:
1. Collect device usage data
2. Validate emission calculations
3. Issue ITMO credentials
4. Maintain audit trail
## Security
The platform ensures data integrity through:
- Cryptographic signatures on all claims
- Immutable ledger records
- Capability-based access control
- Privacy-preserving data handling
## Integration Points
Configure validation rules and data sources for each SDG indicator
Integrate user apps, IoT devices, and partner systems
API interface to get SDG reporting data
Templates, workflows, and rules
---
# Emerging Platform domain registration
> Register and manage digital twin domains in the Emerging Platform for any solution implementation.
The **Emerging Platform** uses digital twin domains as reusable identity and state containers. This page is platform-scoped and applies across solutions, including **Emerging Household Energy**.
## Domain Types
Legal entities managing clean cooking projects
Mitigation activities and programs
Physical devices and tokenized outcomes
Templates and governance rules
AI-enabled verification services
## Domain Structure
### Core Properties
- Follows Interchain Identifier methodology
- Self-sovereign and cryptographically secure
- Globally unique and resolvable
- Permanent and immutable
- Verification credentials
- Relayer node assignment
- Validity period
- Operational status
- Verification state
- API endpoints
- Data streams
- Oracle integrations
- Payment services
## Registration Process
### 1. Protocol Selection
View available protocol templates
Review domain specifications
Choose appropriate template
### 2. Domain Creation
```json
{
"@context": {
"class": "did:ixo:entity:abc123",
"type": "CleanCookingProject",
"version": "1.0"
},
"credentials": [],
"relayerNode": "did:ixo:entity:relayer123",
"validFrom": "2024-01-01T00:00:00Z",
"status": "active"
}
```
- Protocol class reference
- Entity type specification
- Relayer node assignment
- Validity period (optional)
- Initial status
### 3. Verification Flow
Submit domain proposal to DAO
Community verification voting
Automatic verification on approval
## Governance and DAO workflow
**What “proposal to DAO” means here** — A domain change (new entity, protocol class, relayer, or material metadata) is packaged as a **proposal**: voters decide whether the network should treat that domain as verified and active for the intended program.
1. **Draft** — Complete the entity document and evidence your template requires (controllers, relayer, protocol DID, optional credentials). Use [Digital identifiers](/platforms/Emerging/digital-identifiers) and [Data integrity](/platforms/Emerging/data-integrity) as guardrails.
2. **Submit** — Send the proposal through the governance surface your deployment uses (for example on-chain gov module parameters, a **Studio**/**Portal** workflow, or operator-specific APIs—confirm with your program operator). The exact transaction or HTTP call is deployment-specific; align with [Emerging API](/platforms/Emerging/emerging-api) and [Networks and endpoints](/reference/networks-and-endpoints).
3. **Vote** — Token holders, committee members, or allow-listed voters evaluate risk, relayer assignment, and protocol fit. Outcomes are typically **pass**, **fail**, or **abstain** with a quorum threshold.
4. **Execute** — On approval, state transitions run (verification flags, registry pointers, service enablement). On rejection, the domain stays inactive or reverts to the previous version depending on module rules.
For how DAOs fit the wider IXO story, see [DAOs](/articles/daos). For who typically drafts vs votes vs funds programs, see [Your role](/your-role).
## Who does what (roles)
- **Typical role:** Developer
- **Doc entry:** [Your role — Developer](/your-role)
- **Typical role:** Evaluator
- **Doc entry:** [Your role — Evaluator](/your-role)
- **Typical role:** Funder
- **Doc entry:** [Your role — Funder](/your-role)
- **Typical role:** Service provider
- **Doc entry:** [Your role — Service provider](/your-role)
## Checklists
**Before submitting a domain proposal**
- Controllers and keys match who will operate the entity on-chain.
- Protocol / class DID is the one your template expects.
- Relayer or service endpoints are reachable from your environment.
- Evidence and metadata align with [Digital certification](/platforms/Emerging/digital-certification) where applicable.
**After vote**
- Poll domain status via GraphQL (see [Query examples](#query-examples) on this page) or your operator dashboard.
- Rotate credentials if the proposal changed controllers or services ([Authentication matrix](/reference/authentication-matrix)).
## Query Examples
### Find Available Protocols
```graphql
query EntitiesByRelayerNodeAndType {
entities(
filter: {
relayerNode: {equalTo: "did:ixo:entity:a1fcead81eab2f1158a726597d872413"},
type: {equalTo: "protocol"}
}
) {
nodes {
id
type
metadata
}
}
}
```
### Check Domain Status
```graphql
query DomainStatus($did: String!) {
entity(id: $did) {
id
status
verificationStatus
credentials {
type
status
}
}
}
```
## Implementation Guide
1. [Review available protocols](/platforms/Emerging/registry)
2. [Prepare entity document](/guides/digital-twins)
3. [Submit for verification](/platforms/Emerging/data-integrity)
4. [Configure services](/platforms/Emerging/emerging-api)
5. [Monitor status](/platforms/Emerging/household-monitoring)
## Developer Resources
Domain registration endpoints
Client libraries and tools
Sample implementations
Developer assistance
---
# Digital certification overview
> Understand where ITMO schema reference content ends and where Emerging Household Energy implementation guidance begins.
This page is an overview. It does not duplicate schema specification or implementation detail.
## How certification content is organized
Use the two canonical pages below:
- **Canonical reference:** `/platforms/Emerging/itmo-schema`
- **Solution guide:** `/platforms/Emerging/itmo-credentials`
## Which page to use
- Use `itmo-schema` when you need field-level structure, context requirements, and validation checks.
- Use `itmo-credentials` when you need workflow steps for Emerging Household Energy issuance and verification.
## Related dependencies
- dMRV implementation: `/platforms/Emerging/emerging-dmrv`
- Credential lifecycle dependency: `/platforms/Emerging/credential-issuance`
- Registry dependency: `/platforms/Emerging/registry`
---
# Emerging Platform registry
> Reference for the Emerging Platform registry capability used by solutions such as Emerging Household Energy.
The **Emerging Platform registry** is a reusable identity, claim, and credential state service built on the **Impact Hub Network**. It is a cross-solution dependency used by **Emerging Household Energy** and other IXO implementations.
## Core Components
Registration and tracking of mitigation efforts
Identification and monitoring of households
Cookstove Devices and fuel sources
Identification and validation of participants
Digital measurement, reporting, and verification of activity claims and outcomes
Tokenized certified outcomes, backed by verifiable Impact Certificates
## Privacy and Trust Framework
The registry operates as a Digital Public Good through a public blockchain infrastructure with the following key features:
- **Self-Sovereign Digital Identifiers**: Entities maintain full control over their identifiers
- **Privacy-Preserving**: No Personally Identifiable Information (PII) is stored on the blockchain
- **Distributed Network**: Globally-distributed independent Validator Node operators
- **Independent Verification**: Authentication of all claims and mitigation activity data
## Registry Services
Onboard projects, agents, and activities
Submit and evaluate digital claims
Self-certify actions with audit trails
Issue and transfer tokenized credits
Cross-border trading and settlement
Review and verify activities
## Developer Integration
The registry provides APIs and tools for building applications that:
- Track and verify mitigation activities in real-time
- Manage tokenized credit lifecycles
- Enhance Article 6 activity transparency
- Scale impact solutions
An open-source GraphQL API server providing REST API for interfacing with the blockchain registry and integrate with other systems, such as National Carbon Registries.
---
# Emerging Household Energy digital vouchers
> Implement digital voucher issuance and redemption in Emerging Household Energy benefit-transfer workflows.
This page documents the **Emerging Household Energy** solution workflow for digital vouchers. It covers voucher issuance, redemption, and claim-linked benefit transfer operations.
## Key Actors
Receive and spend CARBON credit tokens for emission reductions
Accept vouchers and submit verifiable claims
Manage emission reduction projects and ITMO conversions
Monitor and approve mitigation activities
## Voucher Lifecycle
### 1. Issuance
```bash Issue Voucher
curl -X POST https://api.emerging.eco/v1/vouchers/issue \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"recipientDid": "did:ixo:household/123",
"emissionCertificateId": "CERT-456",
"value": 100.00,
"attributes": {
"batchId": "BATCH-789",
"issuanceDate": "2024-03-15",
"expiryDate": "2024-12-31"
}
}'
```
```python
from emerging import Client
client = Client('YOUR_API_KEY')
voucher = client.vouchers.issue(
recipient_did="did:ixo:household/123",
emission_certificate_id="CERT-456",
value=100.00,
attributes={
"batch_id": "BATCH-789",
"issuance_date": "2024-03-15",
"expiry_date": "2024-12-31"
}
)
```
### 2. Redemption
```bash Redeem Voucher
curl -X POST https://api.emerging.eco/v1/vouchers/redeem \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"voucherId": "VCHR-123",
"supplierDid": "did:ixo:supplier/456",
"amount": 50.00,
"productId": "PROD-789"
}'
```
```python
from emerging import Client
client = Client('YOUR_API_KEY')
redemption = client.vouchers.redeem(
voucher_id="VCHR-123",
supplier_did="did:ixo:supplier/456",
amount=50.00,
product_id="PROD-789"
)
```
### 3. Claim Submission
```bash Submit Claim
curl -X POST https://api.emerging.eco/v1/claims \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"redemptionId": "REDM-123",
"verifiableCredential": {
"type": "ProductDeliveryCredential",
"proof": "..."
}
}'
```
```python
from emerging import Client
client = Client('YOUR_API_KEY')
claim = client.claims.submit(
redemption_id="REDM-123",
verifiable_credential={
"type": "ProductDeliveryCredential",
"proof": "..."
}
)
```
## Token Conversion Flow
Suppliers make claims to convert received CARBON tokens to USDC through the liquidity pool
Suppliers convert USDC to local currency via payment processors
Implementers swap USDC for CARBON tokens through the liquidity pool
Implementers convert CARBON tokens to ITMO claims with on-chain transfer proofs
Implementers convert ITMO claims and transfer prooofs to ITMO Certificates through National Registy
Implementers receive fiat payments for ITMO certificates through Mitigation Outcome Purchase Agreements (MOPA)
Implementers convert fiat payments to USDC through a regulated exchange operator (Circle)
## Validation Rules
* Must have valid emission reduction certificate
* Recipient must be registered household
* Value must match verified reduction amount
* Voucher must be active and non-expired
* Supplier must be authorized
* Amount must not exceed available balance
* Product must be approved for scheme
* Must include verifiable delivery proof
* Must align with monitoring plan
* Must be submitted within time limit
## Response Format
```json
{
"id": "VCHR-123",
"status": "active",
"recipientDid": "did:ixo:household/123",
"emissionCertificateId": "CERT-456",
"value": 100.00,
"remainingBalance": 50.00,
"attributes": {
"batchId": "BATCH-789",
"issuanceDate": "2024-03-15",
"expiryDate": "2024-12-31"
}
}
```
## Error Codes
Invalid parameters or validation failure
Unauthorized access or invalid credentials
Forbidden operation (e.g., unauthorized supplier)
Resource not found
Conflict with existing state
## Security Considerations
Never expose API keys or private keys in client-side code. Always use secure server-side implementations for token conversions and claims processing.
* All transactions are recorded on the Impact Hub blockchain
* Multi-party controls govern the liquidity pool
* Strong authentication required for supplier applications
* Compliance with Article 6.2 ITMO regulations
## Next Steps
Set up merchant integration
Learn about verification and monitoring
View complete API documentation
---
# Kitchen Performance Tests
> Run Kitchen Performance Tests (KPT) in the Emerging Household Energy solution for fuel-consumption measurement.
This guide is scoped to **Emerging Household Energy** implementation. Kitchen Performance Tests (KPTs) measure and validate household fuel consumption through direct observation and structured evidence collection.
## Methodological Requirements
* **Random Selection**:
- Households must be randomly selected
- Selection methodology must be documented in MADD
- Platform provides tools for random selection and documentation
* **Test Groups**:
- Reference group (old stove users)
- Intervention group (new stove users)
- Both groups must be tested simultaneously
- Reference group must be representative of target population
* **Participant Motivation**:
- Clear strategy for engaging reference households
- Documentation of incentive structures
- Compliance monitoring tools
* **Minimum Duration**: 3 days required
* **Test Period Selection**:
- Must capture representative cooking patterns
- Documentation of test day determination
- Justification of period appropriateness
* **Timing Considerations**:
- Seasonal cooking variations
- Cultural factors affecting cooking habits
- Local event calendars
* **Climate Variations**:
- Account for spatial and temporal climate differences
- Track seasonal impacts on fuel consumption
* **External Factors**:
- Monitor traditional cooking patterns
- Track occupational influences
- Document cultural events
* **Continuous Monitoring**:
- Ongoing coverage is best practice
- Any gaps require detailed justification
- Regular data validation checks
* **Population Coverage**:
- Full population testing preferred
- Sampling allowed with proper design
* **Sample Determination**:
- Follow [sampling design guidelines](/platforms/Emerging/sample-size)
- Statistical significance requirements
- Documentation of selection process
## Quick Start
```bash Submit KPT
curl -X POST https://api.emerging.eco/v1/claims \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"type": "KPTClaim",
"householdId": "did:ixo:household/123",
"testGroup": "intervention",
"testPeriod": {
"startDate": "2024-02-01",
"endDate": "2024-02-04",
"durationDays": 3
},
"fuelMeasurements": {
"total": 11.9,
"dailyLogs": [3.2, 2.8, 3.0, 2.9]
},
"externalFactors": {
"season": "dry",
"culturalEvents": ["none"],
"occupationalFactors": ["typical_workweek"]
}
}'
```
```python
from emerging import Client, KPTDesign
# Configure test design
design = KPTDesign.create(
population_size=1000,
confidence_level=0.95,
margin_error=0.05
)
# Get random household selection
selected_households = design.get_random_selection()
# Submit KPT data
client = Client('YOUR_API_KEY')
kpt = client.claims.create(
type="KPTClaim",
household_id="did:ixo:household/123",
test_group="intervention",
test_period={
"start_date": "2024-02-01",
"end_date": "2024-02-04",
"duration_days": 3
},
fuel_measurements={
"total": 11.9,
"daily_logs": [3.2, 2.8, 3.0, 2.9]
},
external_factors={
"season": "dry",
"cultural_events": ["none"],
"occupational_factors": ["typical_workweek"]
}
)
```
## Components
Register and randomly select households following protocol requirements
Record daily fuel consumption with minimum 3-day observation period
Validate KPT results against protocols and control group data
Issue verifiable KPT credentials with emission reduction data
## Required Parameters
Must be "KPTClaim"
DID of the household being tested
Daily fuel consumption measurements (minimum 3 days)
Indicates whether household is in "reference" or "intervention" group
## Optional Parameters
Details of individual cooking events
Photos, documentation links, and selection methodology evidence
Documentation of climate and other variables affecting consumption
## Validation Rules
* Daily measurements must span minimum 3-day period
* Total must match sum of daily logs
* Values must be positive numbers
* Seasonal variations must be documented
* Random selection must be verified
* Control group data must be present
* Test timing must align between groups
* External factors must be documented
## Response Format
```json
{
"id": "kpt-123",
"status": "verified",
"credential": {
"type": "KPTCredential",
"issuer": "did:ixo:validator/456",
"evidence": [
"ipfs://QmevidenceformX12"
]
}
}
```
## Error Codes
Invalid KPT data format
Unauthorized request
Conflicting measurement data
## Integration Examples
### Combining with IoT Data and Control Groups
```python
# Get KPT baseline for both groups
reference_baseline = client.kpt.get_baseline("reference-group")
intervention_baseline = client.kpt.get_baseline("intervention-group")
# Compare with IoT readings
iot_data = client.devices.get_usage("device-456")
reduction = calculate_reduction(reference_baseline, intervention_baseline, iot_data)
# Validate seasonal factors
seasonal_impact = client.kpt.analyze_seasonal_variations(reduction)
```
Always ensure compliance with minimum 3-day testing period and proper control group implementation.
## Next Steps
Detailed KPT measurement protocols and methodological requirements
Best practices for data collection and control group management
Understanding the validation process and emission reduction quantification
## Best Practices
### Test Design
* Document random selection process
* Ensure simultaneous testing of groups
* Validate group representativeness
* Monitor participant engagement
### Data Collection
* Maintain minimum 3-day duration
* Track external influencing factors
* Document seasonal variations
* Ensure continuous monitoring coverage
### Quality Control
* Validate measurement consistency
* Cross-reference with other data sources
* Monitor dropout rates
* Track data completeness
### Documentation
* Record selection methodology
* Document test period justification
* Track external factors
* Maintain compliance evidence
---
# Emerging Platform data integrity
> Understand platform-level integrity controls used across Emerging implementations, including Emerging Household Energy.
The **Emerging Platform** uses cryptographic proofs, verifiable identities, and distributed consensus to preserve integrity of claims and credentials. This is a platform capability used by solution implementations such as **Emerging Household Energy**.
## Core Security Features
Secure hash chains and digital signatures for data verification
Multi-node validation through the Impact Hub Network
Blockchain-based storage of verification proofs
Capability-based permissions and credential verification
## Verification Framework
### Data Security
- Cryptographic hashing of data
- Blockchain anchoring of proofs
- Tamper-evident storage
- Audit trail maintenance
- Decentralized identifiers (DIDs)
- Verifiable credentials
- Digital signatures
- Key management
- Capability-based permissions
- Multi-signature requirements
- Credential verification
- Authorization protocols
## System Architecture
### Security Layers
User interfaces and API endpoints
Verification rules and governance
Distributed validation network
Immutable record keeping
## Verification Process
- Record creation with metadata
- Digital signature application
- Proof generation
- Node distribution
- Signature verification
- Protocol compliance checks
- Consensus validation
- Proof anchoring
- Continuous verification
- Audit logging
- Status monitoring
- Alert systems
## Best Practices
### Security Guidelines
Implement proper credential handling
Verify data integrity at each step
Track system status and alerts
Follow security protocols
## Integration Guide
1. [Review security requirements](/guides/dev/authentication)
2. [Set up authentication](/guides/dev/authentication)
3. [Implement data validation](/platforms/Emerging/data-integrity)
4. [Configure monitoring](/platforms/Emerging/household-monitoring)
## Developer Resources
Implementation guidelines
Verification endpoints
Record management
Developer assistance
---
# ITMO schema reference
> Canonical reference for ITMO credential structure, required fields, and validation rules.
This page is the canonical **reference** for ITMO credential structure in the Emerging section.
Use this page for field definitions and validation expectations. For implementation steps in the **Emerging Household Energy** solution, use `/platforms/Emerging/itmo-credentials`.
## Scope
The schema reference covers:
- credential structure and context requirements
- credential subject sections
- validation requirements and proof checks
## Base credential shape
```json
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://w3id.org/security/suites/ed25519-2018/v1",
{
"itmo": "https://w3id.org/article6/itmo-context.jsonld",
"prov": "http://www.w3.org/ns/prov#"
}
],
"type": ["VerifiableCredential", "ITMOCredential"],
"issuer": "did:example:issuer",
"issuanceDate": "2024-03-15T00:00:00Z",
"credentialSubject": {}
}
```
## Credential subject sections
`credentialSubject` should include the ITMO sections used by your implementation:
- `authorizationInfo`
- `ndcQuantification`
- `correspondingAdjustments`
- `environmentalIntegrity`
## Validation checks
Technical checks:
- JSON-LD context resolution and consistency
- schema conformance for required fields
- cryptographic proof verification
- credential status validation
Business checks:
- required ITMO sections are present
- participating parties and references are valid
- adjustment and integrity values are internally consistent
## Related pages
- Solution implementation guide: `/platforms/Emerging/itmo-credentials`
- Overview and navigation page: `/platforms/Emerging/digital-certification`
---
# Emerging Platform API surfaces
> Understand platform API surfaces and how Emerging Household Energy integrations consume them.
This page documents **Emerging Platform** API surfaces and cross-product IXO dependencies. Use it to choose integration entry points. For household-energy workflows, use the linked **Emerging Household Energy** guides.
## Service APIs
Blockchain APIs for registry and verification services
Dynamic querying of indexed blockchain data using GraphQL
Secure Matrix Server interface to private data rooms
## API Architecture
### Core Services
Register and manage digital twins
Process and validate claims
Store and retrieve secure data
Access AI services
## Authentication
- Required for all API requests
- Managed through Mission Control
- Capability-based access control
- Environment-specific keys
```bash
Authorization: Bearer YOUR_API_KEY
```
Include your API key in all request headers
- TLS encryption required
- Key rotation policies
- Rate limiting enforced
- Request logging
## Service Endpoints
### Production APIs
- Registry: `https://registry.emerging.eco/v1`
- Claims: `https://claims.emerging.eco/v1`
- Verification: `https://verify.emerging.eco/v1`
- Matrix: `https://matrix.emerging.eco/v1`
- Storage: `https://storage.emerging.eco/v1`
- Query: `https://query.emerging.eco/v1`
- AI Verification: `https://oracles.emerging.eco/v1`
- Analytics: `https://analytics.emerging.eco/v1`
## Response Format
### Standard Response
```json
{
"success": true,
"data": {
// Response data
}
}
```
```json
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human readable message"
}
}
```
## Integration Guide
1. [Get API credentials](/guides/dev/authentication)
2. [Choose service endpoints](/reference/networks-and-endpoints)
3. [Implement authentication](/guides/dev/authentication)
4. [Handle responses](/api-reference/authentication)
## Developer Tools
Complete API documentation
Client libraries and tools
Sample integrations
Developer assistance
## Best Practices
Implement proper request throttling
Handle errors gracefully
Follow security guidelines
Track API usage and performance
---
# Articles
> Two reading paths: outcome-first workflows, then deeper stack articles for IXO Graph, protocol, Matrix, Blocksync, oracles, and Qi.
**Path 1 — I want to build something:** start at [Introduction](/introduction) and [What you can build](/guides/what-you-can-build), then jump into developer guides. **Path 2 — I want to understand the stack:** read [Core concepts](/core-concepts), then the articles below for each component.
Articles explain how IXO and Qi **components fit together**. They give **conceptual context**, not step-by-step setup or API ownership (use [Developer portal](/developers) and [API reference](/api-reference/index) for that).
## Path 1: Build and operate
Market promise, lifecycle, and links into the stack.
Verified claims, MRV, financing, agents, data rooms, learning loops.
First-touch domain and digital twin setup.
SDKs, APIs, and implementation entry points.
## Path 2: Stack deep-dives
Vocabulary, lifecycle, layers, and how state and cooperation relate.
Start with the IXO Graph, then protocol, Matrix, Blocksync, oracles, and Qi articles from the groups below.
## Core article groups
- **IXO Graph**: shared map of entities, claims, evidence, and outcomes — [`/articles/ixo-graph`](/articles/ixo-graph)
- **IXO Protocol**: identity, claims, coordination primitives — [`/protocols/ixo-protocol`](/protocols/ixo-protocol)
- **IXO Matrix**: encrypted cooperation rooms and communication — [`/articles/ixo-matrix`](/articles/ixo-matrix)
- **IXO Blocksync**: indexed blockchain data access — [`/articles/ixo-blocksync`](/articles/ixo-blocksync)
- **Agentic Oracles**: AI-assisted verification and automation — [`/articles/agentic-oracles`](/articles/agentic-oracles)
- [Qi: cooperation on verified workflows](/articles/qi-intelligent-cooperating-system) (**Qi Intelligent Cooperating System**)
- Digital twin architecture patterns and orchestration narratives in linked articles
- Platform-specific articles under **Platforms** in the sidebar
- Emerging programs and field patterns under [Emerging Platform](/platforms/Emerging/intro-emerging)
## Next steps
Read about how human and AI agents can safely work together, with shared intent
Read about how digital financing makes money programmable and more accountable
Read about how digital measurement, reporting, and verification elevate trust in reality
Read about how to coordinate and automate work in Programmable Organizational Domains (PODs)
Read about how data sovereignty and encryption give you ownership and control
Read about how to build intelligent feedback loops into real world systems
---
# Digital Twin Domains
> An introduction to Cognitive Digital Twin Systems
IXO implements a systems-thinking approach to capture relationships, feedback loops, and interdependencies in real-world systems where actions are intelligently coordinated, financed, verified, and governed.
## What are Cognitive Digital Twins?
Cognitive Digital Twins (CDTs) are digital replicas of real-world entities that can learn from data, adapt, and support intelligent decisions. The IXO architecture implements CDTs through three core components:
1. **AI/ML Models for Cognition**: CDTs integrate artificial intelligence to simulate cognition. With runtime learning, a twin can analyze streaming data and update its behavior or predictions autonomously. On IXO, Oracle Twins serve this role – they are AI-powered agent services that evaluate data, perform verifications, and automate intelligent actions within the twin system.
2. **Federated Data Architecture**: CDTs draw from distributed data sources in a federated manner. IXO implements a "data matrix" layer of secure data nodes for each twin, allowing data to be shared across a network of stores rather than one silo. This federated design ensures scalability and resilience.
3. **Decentralized Identity Integration**: Every twin is anchored by a decentralized identity to establish trust and uniqueness. Each Digital Entity is identified by a W3C Decentralized Identifier (DID).
## Digital Entity Types
IXO distinguishes different types of twins in its architecture:
- **Physical Twins**: Correspond to real-world devices and sensors (e.g., IoT-enabled cooking stoves)
- **Cognitive Twins**: Model higher-level constructs like Organisations, projects, or processes
- **Oracle Twins**: AI agents that provide analytical and decision-making capabilities
- **Twin Systems**: Capture the relationships and feedback loops between entities
## Digital Domain Properties
Each physical or conceptual element is represented as a _Digital Entity_ with a digital **Domain**. The domain infrastructure is implemented on blockchain with the following components:
### Domain Components
1. **Digital Identifier**
Created as a DID for verifiable ownership and uniqueness; can be enhanced with verifiable credentials.
2. **Domain Controllers**
Defined by public keys or blockchain accounts. Only those with permission can update a domain's data.
3. **Services**
Internet-based services tied to the domain, providing necessary functionalities (e.g., data ingestion endpoints).
4. **Linked Resources**
Digital materials (documents, media) referenced via URIs, often with cryptographic proofs for authenticity.
5. **Accorded Rights**
Object capabilities (UDIDs, zCAPs) specify who can perform what specific actions, on what objects, under what conditions, whilst preserving privacy and security in a decentralized manner.
6. **Linked Claims**
Verified data items that update the domain's state (e.g., device usage records, fuel delivery confirmations).
7. **Linked Entities**
Builds a network of related domains—such as funders, projects, or oracles—and formalizes their interconnections.
8. **Economic Accounts**
Domains function as economic actors with blockchain accounts, enabling DeFi-related actions (e.g., staking, payments).
9. **Non-Fungible Tokens (NFTs)**
Each domain is represented as an NFT, facilitating ownership transfers and interactions with other decentralized ecosystems.
### Data Security and Privacy
IXO implements a federated, end-to-end encrypted data architecture built on the Matrix protocol. Key features include:
- **Sovereign Data Stores**: Each digital twin domain has its own secure data store where structured data and real-time streams are recorded
- **End-to-End Encryption**: All data is protected with E2EE, ensuring only authorized agents can access the data
- **Interoperable Data Sharing**: Data can be securely shared across different servers and Organisations while maintaining encryption
- **Decentralized Storage**: Integration with decentralized file storage networks like IPFS for larger files or public datasets
### Trust and Verification
Trust is established through:
1. **Verifiable Claims**: Digitally signed data or assertions produced by agents or devices
2. **Oracle Verification**: AI and/or human validators evaluate evidence and verify claims
3. **Verifiable Credentials**: W3C-standard digital certificates that encapsulate verified claims and outcomes
4. **End-to-End Trust Pipeline**: From data origin to verification and credential issuance
## Using Protocols
Manually configuring domains can be intricate, so the IXO platform offers _Protocols_ to streamline the process, and Agentic Oracles to automate steps in the creation of domains.
A protocol is a predefined template of properties, relationships, and data models for a specific type of domain, and an Agentic Oracle is an autonomous AI agent that can be configured to perform specific **P-Functions**—capability classes that are named with *P* for easier grouping. Expand a class below to see examples.
- Predicting outcomes
- Pattern recognition
- Providing data analysis and insights
- Personalizing recommendations
- Pathfinding
- Performance monitoring
- Prescribing actions
- Planning and coordinating actions
- Process optimisation
- Proving and verifying claims
- Protocol adherence
- Preventing risks and protecting assets
- Privacy protection
- Preventing and mitigating operational risk
- Participating in governance
- Participation of people and organisations
- Payment automation
- Portfolio management
- Policy enforcement
- Providing compliance and reporting
- Problem detection and resolution
### Instantiating Protocols
When developers create an entity from a protocol "class," it inherits the protocol's default configurations. These inherited settings can be forked and updated to fit the specific use case, promoting:
- Rapid deployment of standard domain types
- Consistent data structures across projects
- Hierarchical Organisation, allowing child entities to trace back to a parent protocol
Example: Climate Mitigation Project Protocol
A protocol designed for clean cooking initiatives can include:
- Default data fields (fuel types, reporting standards, usage metrics)
- Relationships (verification oracles, project developers, funders)
- Services (data analytics, payment frameworks, governance tools)
## Go here next
Build and configure digital domains for entities on the IXO Stack.
Choose SDK surfaces and package-level paths for your application.
---
# IXO Matrix
> Secure cooperation spaces for people, services, and AI agents—encrypted rooms, messaging, and shared context tied to real IXO workflows.
**Job-first framing:** IXO Matrix is how you run **multi-party data rooms**—funders, implementers, verifiers, communities, services, and agents in **encrypted rooms** with shared context. **Yjs** CRDTs and live shared documents are part of how cooperation stays attached to state, not a pile of attachments. Setup details live in SDK and API reference pages.
Funders, operators, verifiers, and agents rarely fail on “more chat.” They fail when **context, evidence, and decisions** scatter across channels. **IXO Matrix** is the secure **communication and shared-surface** layer so those actors align **before, during, and after** state-changing actions on [IXO Protocol](/protocols/ixo-protocol) and [Qi](/articles/qi-intelligent-cooperating-system) workflows.
## Architecture role
IXO Matrix is the data and communication layer in the IXO architecture. It supports domain collaboration, evidence exchange, and stateful workflows with encryption and explicit access controls.
IXO Matrix supports encrypted rooms, event-based communication, and verifiable room history across users and services.
IXO Matrix uses federated Matrix-compatible server topology, so participants can exchange events across trusted homeservers while retaining room-level controls.
- **IXO MultiClient SDK** (`@ixo/impactxclient-sdk`) for cross-service integration
- **IXO Matrix Client SDK** (`@ixo/matrixclient-sdk`) for Matrix-specific operations
- room and state bot services for automation and policy-driven updates
- Model Context Protocol (MCP) integrations where available
## CRDT shared state in Matrix for human and AI teams
IXO uses **Matrix** and **Yjs** (commonly referenced as y.js in the ecosystem) **CRDTs** to turn documentation into **shared state**, so human and AI teams collaborate on the same live source of truth instead of passing files and chat snippets back and forth.
At IXO, CRDT-based shared state lives **inside Matrix rooms**, with Yjs as the synchronization layer, so documentation behaves as a **live, multi-actor workspace** rather than a static file.
This matters because documentation here is not only written once and published. It is shaped continuously by product teams, domain experts, operators, and AI agents working at the same time. The system needs **concurrent edits**, **preserved intent**, and **one evolving source of truth** without locking, silent overwrites, or fragile version handoffs.
### What this means in practice
Each documentation space, page, or structured work surface can be **bound to a dedicated Matrix room**. That room carries **communication context**, **access control**, **event history**, and the **encrypted collaboration channel**. Inside the room, **Yjs** holds the actual **shared document state** as a CRDT.
The result is that all participants—human or AI—operate on the **same live state**.
**Not** message-passing workflows such as “I sent you suggested edits,” “here is my copied version,” or “please merge these manually.” **Instead**, everyone sees and updates the same document state; concurrent changes merge automatically; no central locking is required; offline edits can reconcile safely; and AI agents can act as first-class collaborators on the same state as people.
### Why CRDTs fit IXO
**Conflict-free Replicated Data Types (CRDTs)** are built for distributed collaboration: many parties edit shared content independently and still **converge** on the same result. For IXO, that matters in four ways:
1. **Real shared state, not file passing** — Traditional documentation often defaults to file ownership and turn-taking. CRDTs support a multiplayer model where the document is a **shared state surface**. That aligns with how coordination works across IXO and [Qi](/articles/qi-intelligent-cooperating-system): cooperation happens **over shared state**.
2. **Symmetric human and AI collaboration** — Agents are not limited to external tools that only emit text in a side panel. They can inspect, propose, annotate, structure, and update the **same document state** humans use: humans edit sections directly; agents enrich structure, summarize, cross-link, validate, or suggest improvements—**all against one canonical state**. That is a stronger basis than chat-only copy-paste flows.
3. **Distributed and resilient by default** — IXO targets sovereign, federated, multi-party settings. Matrix supplies a decentralized collaboration substrate; CRDTs provide synchronization **without** a single centralized editor session—important across organizations, devices, and trust boundaries.
4. **Living operational knowledge** — Much of this documentation is operational, not only descriptive. Specifications, policies, rubrics, flows, and implementation notes keep moving. CRDTs fit because the document stays **live and synchronizing** instead of being frozen into periodic versions only.
### Architecture pattern
Think of the stack in three layers:
**Matrix provides the collaboration fabric**
- Secure **room-based** collaboration
- **Federation** across homeservers
- **End-to-end encrypted** communication
- **Identity and membership** context
- **Event history** and auditing hooks
- Integration with IXO **governance and access control** patterns
**Yjs provides the shared state engine**
- **CRDT** document structures
- **Concurrent editing** without manual collision resolution
- **Automatic merge** of independent changes
- **Local-first** editing
- **Synchronization** across participants and devices
**IXO composes governed digital workspaces**
On top of Matrix and Yjs, IXO can bind documents, flow pages, action blocks, structured metadata, agent participation, and governance rules—so the artifact is not only text but a **governed shared state object** inside a secure collaboration domain.
### Why this matters for human and AI teams
Most AI tooling today is **chat-first**: the model sees a conversational slice of context and returns text; a human must decide how to apply it.
In this pattern, agents can work **directly on shared state** in the documentation workspace: read structure; spot missing sections; propose schema-aligned edits; attach evidence or rationale; keep terminology consistent; update linked references; prepare structured summaries for different audiences. Humans stay in control, but coordination tightens because **both humans and agents mutate the same living object**.
That shift—from **message passing** to **stateful cooperation**—matches the broader [Qi](/articles/qi-intelligent-cooperating-system) model of human–AI cooperation over shared state.
## Concept model
Encrypted collaboration spaces with explicit membership and permissions.
Event-driven communication between users, apps, and service actors.
Protected file sharing connected to room state and audit history.
Stateful workflows through room events and automation bots.
## Typical use cases
Coordinate entity and project state with secure shared context.
Keep evidence and review conversations linked to verifiable events.
Exchange and process telemetry in governed data rooms.
Run cross-team workflows with encrypted communication channels.
## Go here next
Use package-level documentation for implementation details.
Use canonical API pages for exact interfaces and parameters.
Follow task-oriented workflows after this architecture overview.
---
# IXO USSD gateway
> How the IXO USSD gateway extends IXO services to any GSM mobile phone.
The IXO USSD gateway is an open-source tool that makes IXO Protocol services accessible from any GSM phone — no smartphone, app, or data plan required. This page explains its architecture and role in the IXO ecosystem. For setup instructions, see the [developer guide](/guides/dev/ussd-gateway).
## Role in the IXO ecosystem
USSD (Unstructured Supplementary Service Data) is a real-time text session protocol built into every GSM network. A user dials a short code such as `*1234#`, navigates menus via number keys, and receives responses — all within a live network session.
The IXO USSD gateway sits between a telecom network and IXO services. It receives USSD sessions from a telecom gateway, processes user input through configurable state machine flows, and calls IXO Protocol and IXO Matrix services to create identities, issue credentials, and record verifiable impact data.
1. A user dials the USSD service code on any GSM phone.
2. The telecom gateway (such as Africa's Talking) forwards the session to the IXO USSD gateway as an HTTP POST request.
3. The gateway processes the input through an XState v5 state machine and returns a `CON` (continue) or `END` (close) response.
4. The user navigates menus until the session completes or times out.
5. Completed actions — account creation, credential issuance, impact reporting — are recorded on the IXO Protocol and stored in an IXO Matrix data vault.
The gateway integrates with:
- **IXO Protocol** — creates Decentralized Identifiers (DIDs) and blockchain wallets for new users, and writes verifiable claims on behalf of the user
- **IXO Matrix** — stores encrypted user data and private keys in a Matrix-based data vault
- **PostgreSQL** — maintains session state across USSD interactions for each user
The gateway is designed to be forked and adapted. The core framework — state machines, session management, database layer, and IXO service integrations — is reusable across any impact use case. Operators fork the repository, define their own USSD flows as state machines, and deploy against their own telecom gateway and IXO network.
## Concept model
Each USSD flow is an XState v5 state machine. Parent machines orchestrate flows; child machines handle specific screens and business logic.
PostgreSQL stores active session state. Each USSD session maps to a state machine instance keyed by session ID and phone number.
Any telecom gateway that sends HTTP POST requests with session ID, service code, phone number, and text input is compatible. Africa's Talking is the reference integration.
On account creation, the gateway generates a DID and blockchain wallet for the user, and stores encrypted credentials in an IXO Matrix data vault.
## Typical use cases
Register community members on IXO without a smartphone — collect name, set a PIN, create a DID and wallet.
Let field workers or household members report verified activities (such as clean cooking sessions) via USSD, triggering credential issuance on-chain.
Issue and redeem digital vouchers for goods or services through USSD menus, with on-chain settlement.
Collect structured data from areas with no mobile data coverage using only voice-quality GSM connectivity.
## Reference implementation
Emerging Household Energy (Supamoto) uses the IXO USSD gateway as the offline access channel for rural household members in Zambia. The Supamoto deployment is maintained as an open-source fork at [emerging-eco/ixo-ussd-supamoto](https://github.com/emerging-eco/ixo-ussd-supamoto). See [USSD access channel](/platforms/Emerging/ussd-channel) for details.
## Go here next
Fork, configure, and run the gateway for your use case.
Endpoint reference for the USSD session API.
End-user guide for accessing IXO services on a basic phone.
Fork the open-source IXO USSD gateway on GitHub.
---
# IXO Blocksync
> Conceptual overview of IXO Blocksync as the indexed graph query layer for IXO Protocol data.
IXO Blocksync is the indexed graph query layer for IXO Protocol data. This article explains why it exists and how it fits in the architecture.
## What IXO Blocksync does
Transforms protocol events and state changes into query-ready structures.
Supports application workloads that need fast filtered access to historical and relational data from the protocol.
## Where it fits
IXO Protocol owns transaction and state truth. IXO Blocksync provides the read-optimized graph query layer for applications and analytics.
Explorer views, dashboards, automation services, and operational tools use Blocksync for graph query-heavy workloads.
## Go here next
Use canonical API pages for query schema and endpoint details.
Learn how to integrate indexed data into workflows.
Understand the underlying source-of-truth layer.
---
# IXO PODs
> Programmable Organisational Domains for coordinating people, AI agents, services, workflows, evidence, and outcomes.
An IXO POD is a Programmable Organisational Domain.
It is a secure operating domain where people, organizations, AI agents, services, tools, Claims, evidence, credentials, workflows, and value can cooperate around a shared purpose.
Use a POD when work needs to happen across multiple actors and the system must know:
- who is involved
- what each actor is allowed to do
- which entities, Claims, evidence, and outcomes matter
- which Flows run the work
- which Blueprints define the rules
- which agents and services may act
- which decisions, payments, credentials, or state changes are allowed
A POD is not just a workspace, DAO, database, or chatbot. It is the governed domain where shared state, human roles, agent authority, workflows, and verifiable outcomes come together.
## When to build a POD
Build a POD when you need a governed operating space for a real-world initiative.
Coordinate funders, implementers, verifiers, researchers, agents, and services inside one program domain.
Manage service providers, fulfillment Flows, Claims, evidence, review, settlement, and reputation.
Connect measurements, reports, verification, Claims, outcomes, and funding decisions.
Operate trusted exchange for services, data, protocols, agent capabilities, or verified outcomes.
Create a governed research space for data access, analysis, publication, and verified learning loops.
Coordinate decisions across humans, organizations, agents, services, and protocol-defined rules.
## What a POD contains
A POD brings together the core objects of IXO and Qi.
The POD’s verifiable identity and operating boundaryPeople, organizations, services, and agents that participateWhat each participant is responsible forWhat each participant is allowed to doProjects, assets, devices, services, datasets, agents, Claims, outcomes, or other graph objectsStructured assertions about work, evidence, eligibility, delivery, compliance, or outcomesDocuments, measurements, observations, attestations, media, reports, sensor data, or external recordsQualifications, roles, authorizations, attestations, or rightsWorkflows that coordinate actions, reviews, decisions, and state transitionsProtocols that define schemas, evidence rules, rubrics, authority, and outcomesAgentic Oracles or AI services that support review, routing, analysis, monitoring, and decision supportSecure cooperation spaces for humans, agents, and servicesPayments, rewards, fees, credits, escrow, funding, or settlement rulesDecision and impact determinations that record what was decided, why, and with what effect
## How a POD works
The POD is created for a specific purpose, such as a program, marketplace, fund, research initiative, verification network, or service operation.
People, organizations, services, and agents are added with roles, credentials, and permissions.
The POD connects relevant projects, assets, services, datasets, devices, Claims, outcomes, and agents into the IXO Graph.
Blueprints specify claim schemas, evidence requirements, rubrics, authority, decision logic, and allowed outcomes.
Qi Flows coordinate submission, review, evaluation, approval, dispute, settlement, and closure.
Agentic Oracles can inspect permitted context, apply rubrics, summarize evidence, flag risks, and create Evaluation Claims.
High-value, disputed, uncertain, or irreversible actions can require human, governance, or protocol-controlled approval.
When a Flow reaches a determination point, a UDID records the decision, evidence, authority, impact, and resulting state change.
## PODs and the IXO Graph
A POD operates over the IXO Graph.
The graph gives the POD shared context for:
- identities and roles
- entities and relationships
- Claims and evidence
- credentials and authority
- workflow state
- transactions and state changes
- evaluations and determinations
- outcomes and learning loops
This prevents the POD from depending on disconnected spreadsheets, private databases, chat history, or agent memory as the system of record.
Do not use a POD as a loose collection of tools. Model the entities, Claims, evidence, permissions, Flows, and decisions that must be inspected or acted on.
## PODs and Qi Flows
Qi Flows are the operating procedures inside a POD.
A Flow defines:
- what starts the process
- who can act
- which state the work is in
- which evidence is required
- which tools and agents may be used
- which checks must pass
- which decisions need human review
- which state transitions are allowed
- which payments, credentials, messages, or next actions may be triggered
Example Flow states:
A Claim, request, listing, or task enters the PODThe POD checks role, credential, permission, or UCAN delegationRequired evidence is requested or validatedA human, service, or Agentic Oracle applies the rubricA verifier, operator, or governance role reviews the resultA UDID records the decision and impact determinationPayment, credential, state update, message, or next Flow is triggeredThe process is complete and inspectable
## PODs and agent authority
Agents can participate in a POD, but they should not have open-ended authority.
Use scoped permissions or UCAN-style delegation to define:
- which agent may act
- which POD, Flow, Claim, room, tool, or evidence set it may access
- which capability it may use
- whether it may read, evaluate, propose, or execute
- when the authority expires
- which actions require human approval
- how the action is logged
A safe first pattern is:
One Claim type in one FlowOnly evidence linked to that ClaimOnly the active Blueprint versionStructured recommendation with citationsSuggest next Flow stateDisabled until the workflow is proven safe
Start with propose-only agents. Let humans, governance roles, or protocol-controlled checks decide when an agent recommendation becomes an approved state transition.
## Example: verified service marketplace POD
A marketplace operator creates a POD for evidence collection services.
Help digital MRV programs find and pay verified field data providersMarketplace operator, service providers, buyers, verifiers, funders, agentsProviders, listings, projects, service orders, Claims, evidence, outcomesDefines required evidence for completed field visitsRequest service, accept order, deliver work, submit Claim, review evidence, settle paymentEvidence Review Oracle checks completeness, consistency, and missing fieldsVerifier accepts, rejects, disputes, or requests more evidenceRecords whether the service was accepted and whether payment should be released
The POD gives the marketplace an operating boundary. The Flow runs the work. The Blueprint defines the rules. Claims and evidence make delivery inspectable. The UDID records the decision and impact.
## Example: digital MRV program POD
A climate program creates a POD to coordinate monitoring, reporting, and verification.
Verify outcomes for a clean cooking, water, energy, land restoration, or biodiversity programProgram operator, field teams, device providers, communities, verifiers, funders, researchersHouseholds, devices, projects, reporting periods, measurements, Claims, outcomesDefines eligibility, evidence requirements, measurement methods, and verification rulesSubmit measurement Claim, validate evidence, review, determine outcome, trigger settlementAgentic Oracle summarizes evidence and flags anomaliesVerifier reviews edge cases and approves determinationsRecords verified outcome and impact determination
## First implementation move
Start with one POD that runs one complete operating loop.
State what the POD is responsible for. Use one sentence that names the domain, participants, and intended outcome.
List the people, organizations, services, and agents that need to participate.
Specify who can submit, read, review, approve, dispute, pay, issue credentials, manage agents, or change rules.
Identify the projects, assets, services, datasets, devices, Claims, listings, agents, or outcomes the POD must represent.
Use one protocol to define the first Claim type, evidence requirements, rubric, and determination logic.
Build the first workflow from submission to review, determination, action, and closure.
Give the agent scoped access to one task, such as evidence summarization, completeness checking, or rubric scoring.
Run complete, incomplete, rejected, disputed, and edge-case submissions before scaling.
## Design principles
Do not begin by modeling the whole organization. Start with one repeatable process that creates, reviews, determines, and acts on Claims.
Every important action should be tied to a role, credential, permission, delegation, Flow state, or governance rule.
Agent outputs should be recorded as Evaluation Claims. Decisions and impacts should be recorded as UDIDs when the Flow reaches a determination point.
Put schemas, evidence requirements, rubrics, thresholds, disqualifiers, escalation rules, and outcome logic into a Blueprint.
A reviewer should be able to reconstruct what happened from the POD record: actor, authority, Claim, evidence, evaluation, decision, impact, and state change.
Keep high-value, irreversible, ambiguous, or contested actions under human or governance review until the Flow has enough operating history.
## Production checklist
Before inviting external participants, confirm:
- the POD has a clear purpose
- members and roles are defined
- authority rules are explicit
- entities have stable identifiers
- Claim types are defined
- evidence requirements are clear
- at least one Blueprint is attached
- at least one Flow is configured
- agent permissions are scoped
- human review is required for high-risk decisions
- Evaluation Claims have a structured schema
- UDIDs are used for decision and impact determinations
- payments, credentials, and state updates require valid authority
- disputes and corrections have a path
- participants know where to act and what happens next
- the POD history can be inspected and replayed
## What to build next
Define the workflow that runs inside your POD.
Define the schemas, rules, evidence requirements, rubrics, and outcome logic.
Create trusted exchange for services, protocols, data, agent capabilities, or verified outcomes.
Evaluate agent work using Claims, evidence, UCAN authority, rubrics, Flow state, and UDIDs.
---
# Project Domains
> Manage agents and resources according to defined protocols.
Project Domains are a specialized Entity Type within the IXO ecosystem designed for managing agents and resources according to defined protocols. They serve as operational domains for coordinating activities, processing claims, and delivering services through a structured governance framework.
## Project Domain Overview
Projects, like all IXO domains, are defined by a standard set of properties in their Domain Document (DID Document) that is stored on-chain and resolved using the IXO DID Resolver. However, Projects have unique characteristics that distinguish them from other entity types.
* Configured for managing agents and resources
* Linked to specific Protocols that define operational rules
* May utilize PODs (Programmable Organisational Domains) for automation
* Employ Oracle service providers for data and decision support
* Typically include Deed Request and Deed Offer domains
* Feature Group governance and DAO functionality
* Maintain dedicated Domain Accounts for financial tracking
* Have an automatically created IXO Matrix room
* **Domain Document**: On-chain DID Document with standard properties
* **Protocols**: Linked operational rules governing project activities
* **Agents**: Entities performing tasks within the project
* **Resources**: Assets and services managed by the project
* **Governance**: Group-based control with DAO capabilities
* **Accounts**: Multiple domain-specific module accounts
* **Communication**: Dedicated Matrix room for project coordination
## Project Components and Functions
### Agent Management
Projects are specifically designed to coordinate and manage agents who perform various tasks and functions. These agents may include:
* Human participants performing specific roles
* Autonomous AI agents operating through PODs
* Oracle service providers feeding data into the project
* External service providers delivering specialized functions
Agents interact with the project through structured protocols that define their roles, responsibilities, and the processes they follow.
### Protocol Integration
Projects are linked to one or more Protocols that define the operational rules and processes. These protocols specify:
* How agents interact with the project
* What tasks can be performed
* How claims are submitted and processed
* What resources can be offered or requested
* How decisions are made within the project
The linked protocols provide a standardized framework that ensures consistency and interoperability across different projects.
### POD Integration
Projects may utilize PODs (Programmable Organisational Domains) to automate various functions:
* Financial operations and treasury management
* Agent task assignment and monitoring
* Data processing and analytics
* Decision support and recommendation systems
* Compliance and reporting functions
PODs employ autonomous AI agents that operate according to predefined rules and objectives, enhancing the efficiency and capabilities of the project.
### Oracle Services
Projects typically employ Oracle service providers to:
* Feed external data into the project
* Process and validate claims
* Provide specialized decision-support
* Verify outcomes and results
* Support various other P-functions (Prediction Functions)
Oracles serve as trusted intermediaries that bridge the gap between the project and external systems or data sources.
## Deed Management
Projects incorporate specialized domains for managing deeds:
### Deed Request Domains
* Used to define tasks to be performed by agents
* Specify requirements for claims submission
* Establish validation criteria for submitted claims
* Track the status and progress of requests
* Instantiate a Claim Collection
### Deed Offer Domains
* Manage offers of resources or services
* Define terms and conditions for offers
* Track the fulfillment and delivery of offers
* Facilitate the exchange of value
* Instantiate a Claim Collection
These deed domains provide structured mechanisms for coordinating activities and managing exchanges within the project.
## Governance and DAO Functionality
Projects implement powerful governance capabilities through:
* **Project Owner Group**: Primary controller of the Domain
* **Group-based Governance**: Distributed decision-making
* **DAO Capabilities**: Decentralized autonomous Organisation functions
* **Delegated Authorizations**: Granular permission management
This governance structure leverages IXO architecture capabilities to support decentralized control of project resources and activities.
## Financial Management
Projects maintain financial integrity through:
* **Multiple Domain Accounts**: Separate accounts for different purposes
* **Ownership-based Control**: Accounts owned by the project domain
* **Delegated Authorizations**: Specific transaction permissions
* **Ownership Transfer Protection**: Automatic revocation of authorizations upon ownership change
This financial architecture ensures transparent tracking of funds and assets while maintaining appropriate controls and accountability.
## Communication Infrastructure
Each project domain has its own dedicated IXO Matrix room that:
* Is created automatically upon project instantiation
* Provides a secure communication channel for project participants
* Integrates with project activities and notifications
* Maintains a persistent record of project communications
* Stores state objects for the project, such as a Settings file and the Project Profile
This integrated communication infrastructure ensures effective coordination among project participants.
## Project Lifecycle Management
Projects follow a structured lifecycle that includes:
1. **Instantiation**: Creation of the Project Domain on the IXO blockchain
2. **Configuration**: Setting up protocols, agents, and resources
3. **Operation**: Execution of project activities and processes
4. **Monitoring**: Tracking progress and outcomes
5. **Adaptation**: Adjusting configurations and processes as needed
6. **Completion or Evolution**: Concluding the project or transforming it
Throughout this lifecycle, the project maintains its integrity as a domain while adapting to changing requirements and conditions.
## Integration with IXO architecture
Projects leverage core IXO architecture capabilities, including:
* **Blockchain-based Identity**: Secure and verifiable digital identity
* **Decentralized Data Storage**: Resilient and accessible data management
* **Smart Contract Functionality**: Automated execution of agreements
* **Token Economics**: Incentive mechanisms and value exchange
* **Interoperability**: Seamless interaction with other domains and systems
This integration enables projects to operate as coordination mechanisms within the broader IXO ecosystem.
## Go here next
Implement project workflows with the developer portal and task guides.
Look up canonical API interfaces and request and response patterns.
---
# Claim evaluation protocol
> How Claims, evidence, rubrics, Agentic Oracles, and UDID records fit together in accountable IXO evaluation workflows.
Claim evaluation is the process of deciding whether a submitted Claim is supported by the evidence and rules that govern it.
Use this article when you need the architecture model behind agent-assisted verification on IXO. It explains the concepts, boundaries, and safety rules for evaluation workflows. Use the linked developer guides and reference pages for exact SDK methods, package identifiers, endpoint values, and protocol message shapes.
This page is a concept and architecture article. It does not replace the canonical [Claims management guide](/guides/dev/ixo-claims), [Agent evaluations guide](/guides/dev/agent-evaluations), [Developer workflows](/guides/dev/workflows), or [Product and SDK map](/reference/product-and-sdk-map).
## The core question
A claim evaluation workflow answers one practical question:
```text
A Claim has been submitted.
Does the available evidence satisfy the governed rules,
and what action is allowed next?
```
The answer should not be an unstructured model response. In IXO evaluation workflows, the accountable output is a structured record that explains:
- which Claim was evaluated
- which evidence was inspected
- which authority allowed the evaluator to act
- which rubric or protocol was applied
- which checks passed, failed, or require review
- what decision or recommendation was made
- what state transition, payment, credential, dispute, or review step is allowed next
When the workflow reaches a determination point, the result can be recorded as a Universal Decision and Impact Determination (UDID). A UDID connects the decision, impact, evidence, authority, and proof trail so the determination can be inspected later.
## Why this matters
Claims often represent real-world work, identity, compliance, impact, delivery, or eligibility. Without a governed evaluation model, verification can drift into screenshots, emails, spreadsheets, ad hoc chat messages, and opaque expert judgment.
Agentic Oracles can help with evidence review and decision support, but automation introduces its own risks:
- the agent may act outside delegated authority
- evidence may be incomplete, stale, or forged
- a model may summarize confidently without citing sources
- a rubric may be too vague to reproduce
- state changes or payments may happen before a valid determination exists
- reviewers may be unable to replay the decision
The evaluation protocol pattern keeps automation bounded. Agents can help gather context, normalize evidence, apply checks, and produce Evaluation Claims, while the workflow still records authority, evidence, rubric results, human review, and final determinations.
Do not treat a model response, chat transcript, or private scratchpad as the source of truth for settlement, credential issuance, or state updates. The accountable record is the combination of Claim, evidence, authority, UDID, and workflow state.
## System model
The evaluation pattern connects IXO Protocol, IXO Graph, Qi Intelligent Cooperating System, and Agentic Oracles.
A participant, service, device, or agent submits a Claim to a Claim Collection, directly or through a workflow. The Claim identifies the subject, claim type, issuer, evidence references, and relevant protocol or domain context.
The workflow checks who may evaluate the Claim, which rubric applies, which evidence may be inspected, and what actions are allowed after evaluation. The workflow may also define the allowed evidence sources and evidence processing rules.
Evidence processors retrieve, parse, verify, and normalize submitted material into a typed fact set. The final rubric should evaluate facts, not raw files or free-form model text. The fact set should be deterministic and reproducible.
A governed rubric applies ordered checks, thresholds, disqualifiers, escalation rules, and reason codes to the typed facts. The rubric should be explicit, ordered, and reason-coded.
When the workflow reaches a decision point, a UDID records what was decided, why, under which authority, with which impact, and with what proof. The UDID should be deterministic, reproducible, and cryptographically signed.
The Flow, verifier, protocol, or authorized service routes the result to approval, rejection, dispute, payment, credential issuance, state update, or human review. The workflow should not allow unbounded authority to act on the result.
## Core concepts
A structured assertion about an entity, asset, service, event, outcome, identity, eligibility, or state. A Claim should carry or reference the evidence needed for evaluation.
A governance and grouping context for related Claims. A collection can define claim types, owners, evaluators, payment settings, dispute rules, and accepted evidence.
Material used to evaluate the Claim: documents, measurements, observations, attestations, media, sensor records, credentials, Matrix events, or external records.
An autonomous or semi-autonomous evaluator that operates with identity, scoped authority, permitted tools, and auditable output. An Agentic Oracle should not become the sole final authority for high-value or irreversible decisions. The Agentic Oracle should be able to produce a deterministic, reproducible, and cryptographically signed UDID when a determination is made.
A reusable package of schemas, evidence rules, fact producers, rubrics, reason codes, fixtures, tests, and workflow instructions for one evaluation domain or claim type. The evaluation kit should be deterministic, reproducible, and testable.
The governed rulebook used to evaluate typed facts. A practical rubric defines required evidence, disqualifiers, thresholds, escalation rules, reason codes, and allowed outcomes. The rubric should be explicit, ordered, and reason-coded.
The normalized set of typed facts produced from evidence before the rubric runs. The fact ledger lets different evidence sources feed the same deterministic decision machinery. The fact ledger should be deterministic, reproducible, and cryptographically signed.
A Universal Decision and Impact Determination. A UDID records the final decision and impact determination when the workflow reaches a determination point. The UDID should be deterministic, reproducible, and cryptographically signed.
## Source-of-truth boundaries
Keep each layer responsible for one part of the evaluation system.
- **Owns:** Claim lifecycle state, protocol messages, authorization, and on-chain records.
- **Does not own:** Private evidence payloads or model reasoning.
- **Owns:** Shared context for entities, Claims, evidence, authority, workflows, decisions, and outcomes.
- **Does not own:** Unstructured chat as canonical state.
- **Owns:** Human-agent workflow state, review routing, decision points, and next actions.
- **Does not own:** Raw protocol message definitions.
- **Owns:** Evidence review, fact production, rubric application, recommendations, and Evaluation Claims.
- **Does not own:** Unbounded authority to approve, pay, issue credentials, or update high-value state. The Agentic Oracle should be able to produce a deterministic, reproducible, and cryptographically signed UDID when a determination is made.
- **Owns:** Encrypted collaboration, human review rooms, alerts, and private evidence discussion.
- **Does not own:** The canonical rubric or final determination.
- **Owns:** Exact package identifiers, methods, endpoints, and request shapes for the evaluation kit.
- **Does not own:** Broad architecture ownership.
This boundary prevents one page, runtime, or service from becoming a hidden source of truth for the whole workflow.
## Evaluation kit structure
An evaluation kit should separate domain-specific evidence handling from shared evaluation mechanics.
Claim loader, context resolver, evidence resolver, fact-ledger validator, rubric interpreter, trace store, UDID compiler, signing adapter, and human-review notifier.
Claim schema, evidence roles, external connectors, extractors, normalizers, fact producers, decision table, reason codes, fixtures, and human-review prompts.
The important design rule is that the evaluator should not directly decide over raw evidence. It should turn evidence into typed facts, then evaluate those facts against a governed rubric.
```text
Raw evidence
-> evidence processors
-> typed fact ledger
-> governed rubric
-> UDID when a determination is made
```
## Fact ledger pattern
The fact ledger is the bridge between messy evidence and repeatable decisions.
Raw evidence can include PDFs, images, sensor logs, API responses, credentials, signatures, spreadsheets, Matrix events, and external attestations. A rubric should not need to know how each source was parsed. It should receive stable facts with provenance. The fact ledger should be deterministic, reproducible, and cryptographically signed.
```json
{
"id": "field.legalName.reconciliation",
"value": "normalized_match",
"confidence": 0.98,
"producer": "legal-name-reconciler@1.0.0",
"sources": [
{
"type": "claim-jsonld",
"path": "$.assertion.legalName"
},
{
"type": "registry-response",
"path": "$.entity.legalName"
}
]
}
```
A useful fact includes:
- a stable identifier
- a typed value
- a producer and version
- source references
- confidence, when relevant
- failure behavior
- enough provenance for replay
## Rubric pattern
A rubric should be explicit, ordered, and reason-coded. It should make escalation as concrete as approval or rejection. The source of truth for the rubric is typically a JSON file in the evaluation kit.
```json
{
"id": "LEIV-R002",
"description": "Registry extract is mandatory",
"when": {
"fact": "document.registryExtract.present",
"equals": false
},
"then": {
"outcome": "rejected",
"reasonCode": "LEIV-201-MISSING-REGISTRY-EXTRACT"
}
}
```
Use this order when designing rubric checks:
1. admissibility checks
2. hard safety vetoes
3. missing mandatory evidence
4. invalid or conflicting evidence
5. manual-review triggers
6. partial-success logic
7. approval logic
Treat ambiguity as a routing condition, not a reason to force a binary answer. A good rubric can say "manual review required" with the same precision as "approved" or "rejected".
## Outcome model
An evaluation profile should define outcomes in operational terms before mapping them to any exact protocol enum or service field.
- **Meaning:** Evidence satisfies the governed rubric.
- **Typical workflow behavior:** Continue to the allowed state transition, settlement, credential step, or record update.
- **Meaning:** The Claim fails a hard rule or lacks mandatory support.
- **Typical workflow behavior:** Record the rejection and reason code; do not proceed to approval-only actions.
- **Meaning:** The evidence is ambiguous, conflicting, or outside automated authority.
- **Typical workflow behavior:** Pause automation and route to a human or governance review.
- **Meaning:** Part of the Claim is supported under a governed rule.
- **Typical workflow behavior:** Continue only if the rubric defines the allowed partial action.
- **Meaning:** A participant challenges the evaluation or determination.
- **Typical workflow behavior:** Route to the dispute workflow.
If your implementation maps these statuses to `MsgEvaluateClaim` fields, service API fields, or SDK helper methods, use the canonical developer guides and API references for the exact literals.
## Human review
Human review is a controlled checkpoint, not an informal chat.
A review request should include:
- Claim ID and Claim Collection
- Claim subject and type
- evaluator DID or service identity (the Agentic Oracle DID)
- rubric ID and version (the rubric JSON file)
- reason code (the reason code for the outcome)
- evidence references or redacted evidence links (the evidence that was inspected)
- fact ledger summary (the typed facts that were evaluated)
- proposed outcome (the recommended or proposed next action)
- questions requiring human judgment (the questions that require human judgment)
- deadline or escalation policy (the deadline or escalation policy for the review)
- required response shape (the required response shape for the review)
IXO Matrix can support encrypted review rooms and structured notifications. The final decision should still be recorded as a workflow record, UDID, protocol transaction, or another canonical artifact rather than only as a chat message.
## Safety rules
Use these rules before allowing an Agentic Oracle to affect value, credentials, or state.
The model may extract, classify, summarize, or recommend. Approval should pass through governed rubric logic and the workflow authority model. The Agentic Oracle should be able to produce a deterministic, reproducible, and cryptographically signed UDID when a determination is made.
A CID proves content integrity for the referenced object. It does not prove that a document is genuine, current, complete, or issued by an authorized source. A CID is not a UDID.
Rubric changes require proposal, review, versioning, and governance. Runtime optimization should not silently change thresholds, disqualifiers, or reason-code mappings. The rubric should be deterministic, reproducible, and reason-coded.
Ambiguity should route to human review, dispute handling, or a request for more evidence. The workflow should not allow unbounded authority to act on the result.
Use redacted public traces and encrypted private traces when evidence contains personal, commercial, or regulated data. The public trace should be deterministic, reproducible, and cryptographically signed.
## First implementation move
Start with one narrow evaluation workflow that cannot directly approve, pay, issue credentials, or update high-value state.
Define:
- one Claim type
- one Claim Collection
- one Flow
- one rubric (the rubric JSON file)
- one evidence schema (the evidence schema JSON file)
- one fact ledger schema (the fact ledger schema JSON file)
- one Agentic Oracle or evaluator identity (the Agentic Oracle DID)
- one human review path (the human review JSON file)
- one dispute or correction path (the dispute or correction JSON file)
- one test suite with approval, rejection, ambiguity, and adversarial cases (the test suite JSON file)
After the evaluation is repeatable and reviewable, you can decide whether any low-risk actions may move from recommendation to proposal, and from proposal to bounded execution. The workflow should not allow unbounded authority to act on the result.
## Related docs
Design Qi evaluation workflows with UCAN authority, Claims, evidence, rubrics, and UDID records.
Build Claim workflows while keeping protocol and service responsibilities separate.
Review SDK-oriented examples for submitting Claims and recording evaluations.
Understand the shared graph of entities, Claims, evidence, authority, workflows, decisions, and outcomes.
Learn how oracle and agent services fit into the IXO stack.
Confirm canonical product names, SDK names, package identifiers, and routes.
---
# Agentic Oracles
> Governed AI evaluators and workflow actors that are identity-bound, evidence-grounded, protocol-governed, and audit-producing economic actors.
An **Agentic Oracle** is a governed AI evaluator and workflow actor. It performs **P-Functions** over verifiable state—turning claims, evidence, models, and context into accountable intelligence: typed facts, predictions, recommendations, attestations, determinations, risk signals, compliance checks, payment triggers, and permitted workflow actions. It is an identity-bound, evidence-grounded, protocol-governed, and audit-producing economic actor.
Most AI agents are good at *doing*. Agentic Oracles are accountable for *deciding*. They are the first class of AI service designed to participate in workflows where claims must be verified, authority must be respected, decisions must be replayable, and outcomes must be inspectable by more than one party.
This is the capability that IXO and Qi were built for.
This is a **concept and architecture article**. For implementation, see [Build an Oracle](/build-an-oracle). For the evaluation pattern, see [Claim evaluation protocol](/articles/claim-evaluation-protocol). For the cooperation layer, see [Qi: cooperation on verified workflows](/articles/qi-intelligent-cooperating-system).
## What makes it an Agentic Oracle
Five properties distinguish an Agentic Oracle from any other generic AI agents. All five must hold.
| Property | What it means |
| --- | --- |
| **Identity-bound** | Acts through a known service identity, with a DID, and verifiable credentials. |
| **Authority-scoped** | May only inspect evidence, call tools, issue outputs, or trigger actions that a protocol, domain, workflow, policy, or delegation permits. The oracle is not allowed to act outside its authority. |
| **Evidence-grounded** | Operates on claims, credentials, observations, and data that can be referenced, replayed, challenged, or reviewed. |
| **Protocol-governed** | Outputs are constrained by explicit rubrics, thresholds, schemas, and workflow states. The oracle is not allowed to make arbitrary decisions. |
| **Audit-producing** | Emits structured outputs that can be inspected, signed, anchored, challenged, or routed to human review. |
The value is not autonomy. The value is **accountable intelligence**: AI reasoning connected to verifiable evidence, governed authority, repeatable evaluation, and permitted action.
## Why this is a new category
The category exists because three different families of system each solve part of the problem and none solve all of it.
| System | What it gives you | What it cannot do |
| --- | --- | --- |
| **Generic AI agent** | Productivity: drafts, searches, plans, calls tools | Cannot bind decisions to verifiable evidence or governed authority |
| **Blockchain oracle** | Connectivity: relays external data into on-chain logic | Does not evaluate what the data *means* under a rule and the given context |
| **Data oracle** | Availability: supplies validated data to downstream systems | Does not decide which action is permitted or by whom |
| **Agentic Oracle** | **Accountable cooperation**: evaluates claims and context under authority, produces inspectable determinations, and triggers governed action | — |
An Agentic Oracle is the role that emerges when AI reasoning, verifiable state, and delegated authority meet inside one workflow.
## Why IXO is uniquely positioned
Other stacks can host an AI agent. They cannot easily make that agent *accountable*. Agentic Oracles depend on primitives that the IXO and Qi stack already provides as first-class infrastructure:
Every oracle has an **IXO entity DID** anchored on-chain. Its actions are attributable, its keys are revocable, and its credentials are inspectable.
**UCAN delegation** scopes what an oracle may do for whom. Authority is granted, attenuated, and revocable—no ambient permissions.
**Claims, verifiable credentials, and the IXO Graph** give the oracle evidence it can reference, replay, and cite by identifier.
**IXO Matrix** rooms host the conversation around each workflow—per-user, end-to-end encrypted, and preserved alongside actions.
**IXO Protocol** records claims, evaluations, and UDIDs as state changes the network can verify and downstream systems can trust.
**Qi** orchestrates humans, agents, applications, and services around that state through declared interfaces and review paths.
Take any of these away and you are back to a generic AI agent that you have to trust on its word. Together they make Agentic Oracles practical.
## P-Functions: what an Agentic Oracle does
**P-Functions** are the capability classes an Agentic Oracle can perform. A single oracle may implement one narrow function—proofing a claim—or combine several inside a governed workflow: detect risk, predict impact, prescribe an intervention, and route the case for human review.
Treat this as a **capability map**, not a list of unrestricted powers. Every function is scoped by the oracle's authority and rubric.
- **Proofing and verification** — Validate claims, evidence, credentials, or state assertions against defined rules. *Output:* verification result, evaluation claim, signed determination, reason-coded route.
- **Protocol adherence** — Monitor conformance to protocol rules, schemas, authorities, and state transitions. *Output:* compliance result, invalid-state flag, allowed-transition check.
- **Prediction** — Estimate future states, risks, trends, demand, or outcomes from evidence and models. *Output:* forecast, confidence score, early warning.
- **Pattern recognition** — Detect signals, clusters, correlations, or anomalies in complex datasets. *Output:* pattern report, anomaly flag, classification.
- **Performance monitoring** — Track indicators, service levels, milestones, or system health. *Output:* performance score, threshold breach, alert.
- **Providing data analysis and insights** — Analyse claims, evidence, and context for decision-useful intelligence. *Output:* insight report, analytic summary.
- **Personalisation** — Adapt recommendations or workflows to a person, organisation, place, or asset. *Output:* contextual recommendation, tailored workflow.
- **Pathfinding** — Identify viable routes through workflows, evidence paths, or operational constraints. *Output:* route recommendation, dependency map.
- **Prescription** — Recommend interventions or next-best actions to achieve a target outcome. *Output:* recommended action, intervention plan.
- **Planning** — Create ordered action sequences, resource plans, or implementation strategies. *Output:* plan, task graph, milestone sequence.
- **Process optimisation** — Improve workflows, supply chains, verification pipelines, or resource usage. *Output:* optimisation recommendation, bottleneck diagnosis.
- **Prevention of risks** — Anticipate and mitigate operational, financial, health, environmental, governance, or compliance risks. *Output:* risk forecast, mitigation plan.
- **Privacy protection** — Minimise exposure through redaction, selective disclosure, access control, and privacy-preserving computation. *Output:* redacted evidence package, disclosure decision.
- **Problem detection and resolution** — Identify anomalies, failures, blockers, or disputes and propose resolution. *Output:* problem flag, root-cause hypothesis, corrective action.
- **Participation of people and organisations** — Maintain human oversight, participatory review, consent, and accountable escalation. *Output:* review request, participation prompt.
- **Participating in governance** — Support governance through proposal analysis, quorum checks, and decision routing. *Output:* governance brief, proposal analysis.
- **Payment automation** — Trigger or recommend payments when verified conditions are met. *Output:* eligibility signal, settlement instruction, hold/release recommendation.
- **Portfolio management** — Assess and optimise portfolios of assets, projects, claims, risks, or financing positions. *Output:* portfolio score, allocation recommendation.
- **Policy enforcement** — Check whether actions, claims, data flows, or decisions comply with applicable policies. *Output:* policy check, compliance status, violation flag.
- **Providing compliance and reporting** — Produce structured reports for funders, regulators, verifiers, or operators. *Output:* compliance report, audit packet.
## Where this matters
Agentic Oracles unlock workflows that previously required slow, expensive, and contestable human-only review—but where automation alone has never been trusted.
- **Impact verification and digital MRV** — Evaluate field evidence against a rubric, attribute outcomes to interventions, and issue evaluation claims that funders and regulators can inspect.
- **Outcome-based financing** — Trigger or recommend payments when verified conditions are met, with the evidence and authority trail attached.
- **Claims evaluation and credential issuance** — Apply governed rubrics to evidence packages and prepare credentials within delegated authority.
- **Governed data sharing** — Decide what may be disclosed to whom under consent and policy, with the decision itself recorded as an artifact.
- **Operational and governance support** — Detect risk, summarise proposals, route escalations, and prepare reports without taking final authority.
In each case the oracle moves work from *opaque expert judgment* or *unverifiable automation* to **accountable, reproducible intelligence**.
## Boundaries
**Practical rule:** An Agentic Oracle increases the speed, consistency, and scale of a workflow. It does not remove the need for evidence, authority, protocol conformance, and human review where required. The canonical source of truth is the combination of claim, evidence, authority, rubric, workflow state, signed determination, and review path—not the oracle's response.
An Agentic Oracle **may** inspect permitted evidence, normalise it into typed facts, apply governed rubrics, generate recommendations, produce reason-coded determinations, trigger explicitly delegated actions, route ambiguous cases to human review, and prepare payment, credential, or governance outputs where allowed.
It **may not** silently change its rubric, approve high-value claims from an LLM response alone, treat private reasoning as canonical state, exceed delegated authority, execute irreversible actions when evidence is ambiguous, or become the sole final authority for material settlement, credentialing, or governance.
## Go here next
Ship an Agentic Oracle with QiForge: identity, UCAN auth, Matrix storage, and the plugin runtime.
The reference pattern for claims, evidence, rubrics, UDIDs, and bounded agent assistance.
How humans, agents, and services coordinate over IXO verifiable state.
P-Function groupings in protocol and domain design.
---
# Introduction
> Get started with using the IXO Stack
Welcome to IXO! This guide will help you understand how to use the platform's features to create, manage, and interact with digital twins of real-world systems.
## Getting Started
1. **Download IXO Mobile**
- Install the [IXO mobile app](https://mobile.impacts.download/)
- Create your digital identity
- Set up your secure keys
- Connect to the network
2. **Access IXO Studio**
- Visit [IXO Studio](https://studio.ixo.earth/)
- Connect your IXO Mobile profile
- Explore available templates
- Configure your workspace
3. **Choose Your Domain**
- Select your entity type
- Configure domain settings
- Set up relationships
- Enable required features
## Key Features
Create and manage your decentralized identity for secure access and control
Configure and maintain digital twins of your real-world assets and operations
Store and share data with end-to-end encryption and granular access control
Submit claims and receive verification through AI-powered oracle services
## Common Tasks
- Update your profile information
- Manage security settings
- Configure notifications
- Set communication preferences
- Create new entities
- Update entity status
- Manage relationships
- Monitor activities
- Upload secure data
- Share information
- Set access permissions
- Track data usage
- Submit claims
- Track verification status
- Handle disputes
- View history
## Platform Navigation
- Overview of your entities
- Recent activities
- Important notifications
- Quick actions
- Browse your entities
- View relationships
- Access entity details
- Perform actions
- Secure file storage
- Messaging channels
- Access controls
- Activity logs
## Best Practices
- Keep your keys secure
- Use strong passwords
- Enable two-factor authentication
- Regular security reviews
- Organize data effectively
- Regular backups
- Clear access policies
- Data cleanup
- Share appropriately
- Clear communication
- Track activities
- Regular updates
## Need Help?
Contact our support team for assistance
Join our community discussions
Find user help and troubleshooting guidance
Stay informed about platform updates
---
# Review a theory of change with Outcome Graph
> Turn a theory of change into a scoped causal chain, evidence review, and governed decision without overstating what the evidence proves.
Outcome Graph guides you from a theory of change to a testable map of claims, causal relationships, evidence, assumptions, and gaps. Use it when you need to understand what an outcome claim can responsibly say now, what remains uncertain, and who must decide before the claim can advance.
A coherent theory of change is a starting hypothesis, not proof. Outcome Graph can produce a diagnostic result without issuing a certificate. External evaluation, signing, anchoring, payment, or other transactions require the relevant evidence, policy gates, systems, and explicit authority.
## Before you start
### Confirm access
Outcome Graph is not currently available as a self-service feature from this documentation site or the public IXO Portal. The source repository does not yet provide a validated public installation flow. To use this guide, you need a Claude Code session that an IXO maintainer has configured with Outcome Graph.
Before you prepare a theory of change:
1. Install and authenticate [Claude Code](https://code.claude.com/docs/en/quickstart), if it is not already available to you.
2. Open the Claude Code session configured for Outcome Graph.
3. Enter `/help` and confirm that an Outcome Graph command is listed.
The expected result is an Outcome Graph entry that you can select or invoke from the session. If it is missing, [contact IXO support](mailto:assistant@ixo.world) and request **Outcome Graph guided-run access**. Include your organization or IXO domain, and whether your theory of change is text, a document, slides, an infographic, or a transcript.
Do not continue to Step 1 until the Outcome Graph command appears in `/help`.
### Prepare your source
You need:
- a theory of change as text, a document, slides, an infographic, a transcript, or mixed media;
- the decision or outcome claim you want to clarify;
- the intervention, intended outcome, population, place, and observation period, if known;
- any implementation or outcome evidence you already have; and
- the reviewer or issuer role only if you intend to pursue a governed claim.
If you have only a source document, start with a **diagnostic run**. Outcome Graph will infer a provisional focus, show you where interpretation matters, and avoid implying that certificate issuance is in scope.
Treat source material and evidence according to your organization's data-handling rules. Do not include private keys, access tokens, secrets, or unnecessary personal data. Content inside an attached source is treated as material to analyze, not as instructions to the agent.
## What you will do
The guided journey has seven phases:
1. Set the goal and scope.
2. Read the theory into traceable propositions.
3. Map the most decision-relevant causal chain.
4. Link evidence and record gaps.
5. Test the graph and compute claim readiness.
6. Pause for governed decisions when required.
7. Evaluate and issue only when every required gate passes.
You work primarily through a guided conversation. When the visual canvas is available in your session, it shows the causal graph, evidence links, validation findings, claim tier, and current decision. A published graph may also be available through an Outcome Graph app attached to an IXO domain.
Each run keeps a durable record under `runs//`. The record contains accepted sources, graph versions, evidence links, findings, decisions, and verification records.
### What to expect from each checkpoint
Every phase tells you:
- **why** the phase matters;
- **what** changed and what the current result means;
- **how** checks and decisions move the run forward;
- **who** owns the next action or governed decision;
- **when** the next phase can begin;
- **where** the source and run record are held; and
- **how much** material is in scope, using counts such as propositions, nodes, relationships, evidence links, gaps, findings, and blockers.
Outcome Graph uses phase progress instead of guessing completion time. Effort and cost depend on the source, evidence, evaluation design, and governance process. The run brief marks these estimates as unknown when they are not available.
## Step 1 — Start a diagnostic run
Open the configured Claude Code session that you verified above. Attach or identify your theory of change, then state the decision you want to clarify.
For example:
```text
Run Outcome Graph on this theory of change.
Start with a diagnostic review of the most plausible
and demonstrable causal chain.
Do not pursue certificate issuance unless I explicitly approve it later.
```
You can also use the Outcome Graph command shown in `/help`. The current source package documents this command as:
```text
/outcome-graph
```
The first checkpoint is a run brief. Review the accepted source, provisional outcome focus, exclusions, intended decision, and what the run may produce. Correct the scope only if a difference would materially change the claim.
## Step 2 — Review how the theory was read
Outcome Graph separates explicit statements from ambiguities and inferred propositions. Each proposition keeps a reference to its source location.
Review:
- the plain-language theory summary;
- the intervention and intended outcome;
- the proposition count and source coverage;
- ambiguous passages; and
- items inferred by the agent rather than stated in the source.
Resolve ambiguity when it changes the intervention, outcome, population, place, or intended decision. Otherwise, allow the run to continue.
## Step 3 — Confirm the causal chain
Outcome Graph converts the source propositions into testable causal paths. It presents the three to seven most important paths, then highlights the path most relevant to your decision.
For each relationship, check that the graph answers:
- What changes, and for whom or what?
- By what mechanism could the earlier step affect the later one?
- Compared with what alternative or baseline?
- Over what period and in which place?
- Which assumptions and alternative explanations matter?
- How could the relationship be measured?
Confirm the recommended path when it is a reasonable diagnostic focus. Ask for an alternative only when another path would materially change the claim or evidence requirements.
## Step 4 — Choose how to handle evidence
Outcome Graph links each artifact to the specific claim it can support. The same artifact may be admissible for one claim and unsuitable for another.
Each evidence link is checked for:
1. **Integrity:** The accepted artifact and its transformations can be traced.
2. **Authority:** The producer is identified and has the required role or independence.
3. **Freshness:** The observation falls within the relevant period and policy.
4. **Relevance and completeness:** The artifact measures the named indicator, population, place, and period. Evidence for a causal relationship must bear on that relationship, not only on one endpoint.
5. **Provenance:** A reviewer can follow the path from observation through processing and judgment to the claim.
At this checkpoint, choose one of these paths:
- attach evidence and continue toward the strongest claim it can support;
- accept the evidence gaps and keep the run diagnostic; or
- use a synthetic evidence pack to test the workflow and field mappings.
Synthetic data, agent summaries, and other material created within the pipeline can test the workflow. They are not external-world observations and cannot certify the claims they describe.
## Step 5 — Read the validation result
Outcome Graph runs semantic, structural, causal, and empirical checks. Deterministic tools test graph structure; the result does not depend on an agent saying that the graph looks correct.
The checkpoint includes:
- pass, warning, failure, and blocker counts;
- changes to relationship status;
- the strongest currently attainable claim tier;
- the gap or assumption that limits the result; and
- the highest-priority next action.
Relationship statuses mean:
| Status | Meaning |
|---|---|
| `hypothesized` | The relationship is proposed but has not earned evidential support. |
| `plausible` | The mechanism is coherent, but support is limited, indirect, or based on monitoring or prior literature. |
| `supported` | The relationship passed the required causal and empirical checks with an appropriate design and sensitivity analysis. |
| `contested` | Evidence or reviewers disagree, and the dispute remains open. |
| `unidentified` | The current graph and data cannot support a defensible causal estimate. |
| `rejected` | A check or evidence finding decisively contradicts the relationship. |
Missing evidence lowers the relationship status and creates an explicit evidence gap. It does not get replaced with softer claim wording.
## Step 6 — Respond to a governed decision
The run pauses at `REVIEW_REQUIRED` when it needs a normative judgment, resolution of conflicting evidence, acceptance of a material assumption, or an issuance decision from an authorized person.
This status means **paused for a named human decision**. It does not mean failed or complete.
The review packet states:
- the exact decision and the role authorized to make it;
- the recommendation and its evidence;
- material alternatives;
- the consequences of approving, deferring, or rejecting; and
- the exact reply or record needed to resume.
An agent can assemble and explain the packet. It cannot simulate the reviewer's decision.
## Step 7 — Evaluate or stop at the diagnostic result
Stop when the diagnostic result answers your current question. Continue toward evaluation or issuance only when the graph, evidence, policy, governance, and external-system requirements are in place.
Outcome Graph computes the strongest claim tier that the full claim-and-evidence path can support:
| Result | What it supports | What it does not establish |
|---|---|---|
| Tier 0 | A diagnostic result with no currently attainable certificate tier. | That the theory is false. |
| Tier 1: Narrative plausibility | A coherent, structurally valid, expert-reviewed theory. | That the outcome occurred or the program caused it. |
| Tier 2: Evidence-backed contribution | A plausible contribution to observed changes, supported by admissible measurements and disclosed alternatives. | A causal effect size. |
| Tier 3: Causally supported outcome | A claim that the program caused the named outcome under stated identification assumptions, with an appropriate design and uncertainty analysis. | That the result applies outside the stated population, place, and period. |
| Tier 4: Issuance-ready certified outcome | An authorized issuer's certification of an eligible Tier 2 or Tier 3 claim after policy and governance gates pass. | A guarantee of future performance or a universal claim. |
The tier is computed from the weakest required relationship and its evidence. You may set a target, but you cannot choose a higher tier than the evidence earns.
## Verify the result
Before you close or advance the run, confirm that:
- the source set and intended decision are correct;
- the intervention, outcome, population, place, and period are explicit;
- the chosen causal chain is the one you intended to test;
- inferred elements and assumptions are visible;
- every important relationship has evidence links or a named gap;
- validation findings show their basis;
- the attainable tier matches the evidence rather than the target;
- any `REVIEW_REQUIRED` state names the reviewer and requested decision; and
- no external action occurred without explicit authority.
When a visual canvas is available, use the phase timeline, graph, evidence, checks, tier, and decision views to inspect the same run from different angles. Selecting an item hands the question back to the guided conversation rather than changing governed state by itself.
## Example diagnostic journey
Suppose a service theory proposes this chain:
```text
implementation support → routine service integration → staff adoption
```
You can accept this as the diagnostic chain while deferring an evidence-backed claim. A synthetic evidence pack can then demonstrate how support records, baseline and follow-up measures, adoption counts, dates, and alternative explanations map to the graph.
The expected result is a tested workflow with visible evidence gaps and no real-world certificate claim. Replacing synthetic fixtures with real records still does not prove causation automatically. The evidence must pass the remaining gates and match an appropriate evaluation design.
## Troubleshooting
### The graph is too broad
Ask Outcome Graph to focus on the shortest chain that is both decision-relevant and demonstrable. Name the outcome and population you need to decide about now. Leave wider system effects as context or future versions.
### The run keeps asking scope questions
Answer with the intended intervention, outcome, population, or place. If the provisional scope is acceptable, say **use the provisional scope** so the run can continue.
### You have no real evidence yet
Choose a diagnostic run. You can accept the gaps, create a collection plan, or use a clearly labelled synthetic evidence pack to test the workflow. Do not treat synthetic results as support for the real-world claim.
### The result is weaker than expected
Review the weakest required relationship, inadmissible evidence links, open gaps, alternative explanations, and identification assumptions. Narrow the claim or collect the named evidence. Do not ask the agent to select a higher tier.
### The run is paused at `REVIEW_REQUIRED`
Open the review packet and identify the authorized reviewer. Provide the requested decision or record, then resume the same workflow so the audit trail remains continuous.
## Current implementation boundary
Outcome Graph implements guided runs, a visual canvas, versioned artifacts, deterministic structural checks, evidence-link admissibility records, tier computation, review packets, and tested examples.
The current repository still lists strict authorization proof-chain verification and an end-to-end test-network issuance run as outstanding. Treat local or pilot runs as diagnostic evidence of the workflow, not proof that production certificate issuance has completed.
## Next steps
Design the measurements, reports, evidence, and verification workflow that can supply an Outcome Graph.
Define the claim, evidence, review, and governance rules for repeated submissions.
See how entities, claims, evidence, relationships, and outcomes connect in verifiable state.
Learn how IXO evaluates claims against evidence, rules, authority, and review requirements.
For implementation details, schemas, the worked example, and current status, review the [Outcome Graph source repository](https://github.com/ixoworld/outcome-graph).
---
# Use IXO services via USSD
> How to access IXO services on any basic mobile phone using USSD — no smartphone or internet required.
## Before you start
- Any mobile phone with GSM connectivity (no smartphone or data plan needed)
- Your phone number registered with a local mobile network
- The USSD service code for your programme — ask your programme operator or field agent if you are unsure
## What this guide does
This guide explains how to access IXO services through USSD menus on a basic mobile phone. USSD lets you dial a short code and navigate menus using number keys — similar to checking your airtime balance.
Through USSD you can register your identity, manage your account, report activities, and access digital vouchers — all without a smartphone or internet connection.
## Step 1 — Dial the service code
On your mobile phone, open the keypad and dial the USSD service code provided by your programme, then press the call button.
```text
*1234# [CALL]
```
The service code varies by programme and country. Your field agent or programme operator will confirm the correct code for your area.
After dialling you will see the main menu on your phone screen within a few seconds.
## Step 2 — Navigate the menus
Use the number keys to select options and confirm with the call or send button. Use `0` to go back to the previous menu and `*` to exit the session.
The main menu typically includes options to:
- Learn more about the programme
- Access your account
- Report an activity
- Check your status or balance
Each menu screen shows the available options and a prompt for your input.
## Step 3 — Create your account
If this is your first time, select the account creation option from the main menu.
You will be asked to:
1. Enter your name
2. Choose a PIN (kept private — do not share it)
3. Confirm your PIN
Once complete, the system creates your digital identity and account on the IXO network. You will receive a confirmation message on screen.
Keep your PIN private. Programme staff will never ask for your PIN.
## Step 4 — Report an activity or check your status
After creating your account, you can return at any time to:
- Report an activity (such as using a clean cooking device)
- Check your account balance or credential status
- Access a digital voucher
Select the relevant option from the main menu, follow the prompts, and confirm with your PIN when asked.
## Troubleshooting
### The menu does not appear after dialling
- Check that you have GSM network coverage
- Try again after a few seconds — USSD sessions can be delayed on congested networks
- Confirm the service code with your programme operator
### The session ended before I finished
USSD sessions time out after a period of inactivity (typically 30 to 60 seconds depending on your network). Dial the code again to start a new session. Your account data is saved.
### I forgot my PIN
Contact your programme operator or field agent. They can assist with account recovery through your registered phone number.
### I see an error message
If you see "Service temporarily unavailable", the server may be briefly offline. Try again after a few minutes. If the problem persists, contact your programme operator.
## Next steps
Learn how the USSD gateway works for operators and developers.
---
# Developer portal
> The entry point for building on IXO: credentials, quickstarts, SDKs, API interfaces, a live sandbox, and the machine-readable surfaces agents can call.
You are wiring real-world workflows where agents and services act on **verifiable** entities, claims, and evidence — not only on generic LLM context.
**IXO** anchors what is true on-chain and in linked services: domains, claims, credentials. **Qi**, the Qi Intelligent Cooperating System, is where people and agents cooperate on that truth, through IXO Matrix, oracle and agent services, and automation. Most projects implement **state first** — protocol, claims, indexing — then add **cooperation**: Matrix, Agentic Oracles, SDKs.
This page is a map. Every card links to the canonical page for that topic; nothing here is restated from it.
## Start building
Scaffold a QiForge oracle, boot it, and watch the agent call a plugin tool — about ten minutes.
The build tracks available on the stack, and which one fits your problem.
Worked end-to-end TypeScript for the IXO MultiClient SDK and IXO Matrix Client SDK.
The common build sequences: register a domain, collect claims, evaluate, settle.
## Credentials and access
API credentials differ by surface — protocol gateways, service APIs, and platform APIs do not share one scheme. Confirm the literals for the interface you are calling before you write the request.
The canonical table: which credential and header applies to each API surface.
Set up credentials, session keys, and authorization for IXO services.
Chain IDs, base URLs, and environments for mainnet and testnet.
How Emerging Platform keys are issued and scoped, and where they are managed.
Keep credentials in secure runtime storage, never in source files, and rotate them according to your operator policy.
## Sandbox
The Emerging Platform API ships with a live console on every endpoint page. Set a bearer token, fill the typed parameters, and send a real request against the development server — no local setup, no CORS workarounds.
`GET /households/stats` with the request console attached.
Run an oracle locally and exercise its plugins before deploying.
## Build specific capabilities
Create and manage entity domains for real-world systems, assets, and organisations.
Submit and verify impact claims with evidence using IXO Protocol claims modules.
Set up digital measurement, reporting, and verification workflows.
Build AI-powered oracle services for verification, evaluation, and automation.
Configure entity domain permissions, services, and linked resources.
Connect AI tools to IXO services through MCP servers.
## Interfaces
RPC, REST, GraphQL, and service API interfaces for IXO Protocol and IXO application services.
IXO MultiClient SDK, SignX SDK, IXO Matrix Client SDK, and JAMBO PWA SDK.
Status codes, the JSON error envelope, and how to recover from each class of failure.
Cursor and page patterns for large result sets.
The canonical list of products, SDKs, and their package identifiers.
Canonical terms used across the protocol, products, and SDKs.
## Concepts worth reading first
Vocabulary and mental models for IXO, Qi, entities, claims, evidence, verification, and workflows.
The Cosmos SDK blockchain: entity management, claims, tokens, bonds, and smart accounts.
The encrypted data and communication layer behind data rooms, evidence storage, and messaging.
Human–agent cooperation, oracles, and agent tooling over verified workflows.
## Build with AI agents
These docs are published for machines as well as people. Nothing here needs scraping.
| Surface | URL | What it is |
| --- | --- | --- |
| Markdown mirror | `.md` | Any page as Markdown. Also served on `Accept: text/markdown`. |
| Page index | `/llms.txt` | Every navigation page with its description. |
| Full corpus | `/llms-full.txt` | The whole documentation set in one request. |
| MCP server | `/mcp` | Streamable HTTP JSON-RPC with `search_docs` and `read_page`. |
| MCP server card | `/.well-known/mcp.json` | Transport, capabilities, and tool schemas, readable before connecting. |
| OpenAPI | `/openapi.json`, `/openapi.yaml` | The Emerging Platform API, with an operation ID and typed schema per endpoint. |
| Sitemap | `/sitemap.xml` | Every canonical URL. |
A single dense page an agent can scaffold a QiForge oracle from — every signature, option, and env var.
The MCP servers in the IXO ecosystem, including this documentation server.
Unknown paths on this site return a real HTTP 404 with a Markdown body listing these surfaces, so an agent that guesses wrong can recover in one request.
## Support
The **Ask Assistant** control in the header answers from these docs, grounded in the page you are reading.
Questions, discussions, and support on Slack.
assistant@ixo.world
Source code for the SDKs, gateway tools, and reference implementations.
---
# IXO Stack SDKs
> Pointer page — SDK reference and developer guides for the IXO stack live in the linked locations.
The IXO Stack SDKs are documented in the [SDK reference](/sdk-reference/index). This page is a routing aid for builders looking for the developer entry point.
Canonical home for all SDKs and ADKs — types, methods, and install instructions.
Task-oriented entry point for building on the IXO stack.
End-to-end implementation patterns for entities, claims, evaluation, and integrations.
Build and run a USSD gateway to extend IXO services to feature phones. For architecture, see [IXO USSD](/articles/ixo-ussd).
---
# Developer workflows
> End-to-end IXO MultiClient SDK patterns: query client, signing client, entities, claims, and oracle-assisted evaluation.
These workflows show the canonical message patterns from the [`@ixo/impactxclient-sdk` README](https://github.com/ixofoundation/ixo-multiclient-sdk#readme). For full type definitions, see [`@ixo/impactxclient-sdk`](https://www.npmjs.com/package/@ixo/impactxclient-sdk) and the [proto definitions](https://github.com/ixofoundation/ixo-blockchain/tree/main/proto/ixo).
There is **no single blessed CLI** for every IXO operation. Prefer the SDK, Cosmos-compatible wallets, or deployment-specific CLIs your operator documents. For HTTP-oriented domain examples, see [Domain registration](/guides/domain-registration).
## Prerequisites
1. Install dependencies: `@ixo/impactxclient-sdk`. For Agentic Oracle integration, see [Agentic Oracles ADK](/sdk-reference/oracle-adk).
2. Choose **RPC endpoint** and **chain ID** from [Networks and endpoints](/reference/networks-and-endpoints).
3. Choose **auth** for each surface from [Authentication matrix](/reference/authentication-matrix). On-chain writes require a funded signer (wallet or key management you control).
```bash
npm install @ixo/impactxclient-sdk
```
## 1. Initialize query and signing clients
Reads use `createQueryClient`. Writes need a signer-backed `createSigningClient`. Both target the same RPC endpoint.
```typescript
import {
ixo,
createQueryClient,
createSigningClient,
} from "@ixo/impactxclient-sdk";
const RPC_ENDPOINT = "https://rpc.ixo.world";
const queryClient = await createQueryClient(RPC_ENDPOINT);
const signingClient = await createSigningClient(RPC_ENDPOINT, offlineSigner);
```
**Errors:** RPC misconfiguration surfaces as connection timeouts or JSON-RPC errors. Try a different node if your operator allows; confirm TLS and `chainId` against the network row in [Networks and endpoints](/reference/networks-and-endpoints).
For signer setup (Cosmos-kit, Keplr, or `getOfflineSigner` from `cosmjs-utils`), see [SDK README — Creating signers](https://github.com/ixofoundation/ixo-multiclient-sdk#creating-signers).
## 2. Register an entity (digital twin)
Compose `MsgCreateEntity`, sign and broadcast.
```typescript
const createEntityMsg = {
typeUrl: "/ixo.entity.v1beta1.MsgCreateEntity",
value: ixo.entity.v1beta1.MsgCreateEntity.fromPartial({
ownerAddress: signerAddress,
entityType: "asset",
context: [
ixo.iid.v1beta1.Context.fromPartial({
key: "class",
val: "did:ixo:entity:protocol123",
}),
],
verification: [],
controller: [],
service: [],
linkedResource: [],
accordedRight: [],
linkedEntity: [],
linkedClaim: [],
}),
};
const response = await signingClient.signAndBroadcast(
signerAddress,
[createEntityMsg],
"auto",
);
```
Read the resulting entity through the query client:
```typescript
const entities = await queryClient.ixo.entity.v1beta1.entityList();
```
**Errors:** invalid `entityType` or context key/value pairs that the chain does not accept return failed transactions. Inspect `code` and `rawLog` on the broadcast response.
## 3. Submit a claim
```typescript
const submitClaimMsg = {
typeUrl: "/ixo.claims.v1beta1.MsgSubmitClaim",
value: ixo.claims.v1beta1.MsgSubmitClaim.fromPartial({
creator: signerAddress,
claimId: "bafkreih...",
collectionId: "12",
adminAddress: collectionAdminAddress,
}),
};
await signingClient.signAndBroadcast(signerAddress, [submitClaimMsg], "auto");
```
**Errors:** missing `collectionId`, schema mismatch, or insufficient authorization produce chain errors. Cross-check [Claims management](/guides/dev/ixo-claims) and [Custom authorisations for IXO Claims](/guides/dev/authz-custom).
## 4. Evaluate a claim (oracle-assisted)
An Agentic Oracle returns a determination off-chain; the on-chain record is written by `MsgEvaluateClaim`. The example below assumes an Agentic Oracle service exposes a verify endpoint your code calls — see [Agentic Oracles ADK](/sdk-reference/oracle-adk) for the canonical SDK home and [Build agentic oracles](/guides/ixo-oracles-architecture) for the architecture.
```typescript
const verification = await callOracleVerify({
oracleEndpoint: "https://oracle.example",
claimId: "bafkreih...",
});
const evaluateMsg = {
typeUrl: "/ixo.claims.v1beta1.MsgEvaluateClaim",
value: ixo.claims.v1beta1.MsgEvaluateClaim.fromPartial({
creator: signerAddress,
claimId: "bafkreih...",
collectionId: "12",
oracle: oracleAddress,
adminAddress: collectionAdminAddress,
status: 1,
reason: 0,
verificationProof: verification.proofCid,
amount: [],
}),
};
await signingClient.signAndBroadcast(signerAddress, [evaluateMsg], "auto");
```
`callOracleVerify` is a placeholder for whatever HTTP, MCP, or SDK call your Agentic Oracle exposes. The on-chain side is what `@ixo/impactxclient-sdk` covers — the off-chain agent service is your application code.
**Errors:** oracle `401`/`403` typically means wrong operator credential or DID. Chain-side `MsgEvaluateClaim` failures usually point to authorization (no `EvaluateClaimAuthorization`) or claim state.
## 5. Issue tokens against an evaluated claim
After approval, mint tokens with `MsgMintToken` against your token contract — see [Manage tokens](/guides/dev/tokens) for the full message family.
```typescript
const mintMsg = {
typeUrl: "/ixo.token.v1beta1.MsgMintToken",
value: ixo.token.v1beta1.MsgMintToken.fromPartial({
minter: signerAddress,
contractAddress: tokenContractAddress,
owner: recipientAddress,
mintBatch: [
ixo.token.v1beta1.MintBatch.fromPartial({
name: "CARBON",
index: "bafkreih...",
amount: "3600",
collection: "did:ixo:entity:collection456",
tokenData: [],
}),
],
}),
};
await signingClient.signAndBroadcast(signerAddress, [mintMsg], "auto");
```
## 6. Verifiable credentials
Credential issuance and verification are governed by your program's issuer keys, schemas, and registry or IXO Matrix storage — see [Credential issuance](/platforms/Emerging/credential-issuance) and [Identity and credentials](/articles/identity-and-credentials). Implement issuance in the component that holds the issuer key; verifiers should check signature, issuer DID, schema, and revocation/status lists. For HTTP-oriented credential flows on Emerging, see [Emerging API](/platforms/Emerging/emerging-api) and the matching auth rules in [Authentication matrix](/reference/authentication-matrix).
## Error handling
- **HTTP service calls:** use [Error handling](/api-reference/errors) for status codes; add retry/backoff for `429` and transient `5xx`.
- **GraphQL (Blocksync):** partial errors may appear in a top-level `errors` array while `data` is non-null — always check both. Unauthenticated introspection may be disabled on some deployments.
- **DID resolution:** if resolution fails, confirm the DID is registered on the expected network and that you query the correct registry or indexer endpoint.
## Next steps
Module-level features on protocol.
Canonical SDK overview and route selection.
Worked end-to-end flows.
Build the off-chain side of evaluation.
---
# Implementation examples
> Worked end-to-end TypeScript examples for IXO MultiClient SDK and IXO Matrix Client SDK.
These examples follow the canonical patterns from the [`@ixo/impactxclient-sdk`](https://www.npmjs.com/package/@ixo/impactxclient-sdk) and [`@ixo/matrixclient-sdk`](https://www.npmjs.com/package/@ixo/matrixclient-sdk) READMEs. Use them as starting templates — exact field shapes are generated from the [chain proto definitions](https://github.com/ixofoundation/ixo-blockchain/tree/main/proto/ixo).
For the step-by-step explanation behind each pattern, read [Developer workflows](/guides/dev/workflows) first.
## Setup
```bash
npm install @ixo/impactxclient-sdk @ixo/matrixclient-sdk
```
```ts
import {
ixo,
createQueryClient,
createSigningClient,
} from "@ixo/impactxclient-sdk";
import {
createMatrixApiClient,
createMatrixRoomBotClient,
createMatrixStateBotClient,
createMatrixClaimBotClient,
} from "@ixo/matrixclient-sdk";
const RPC_ENDPOINT = "https://rpc.ixo.world";
const HOME_SERVER_URL = "https://devmx.ixo.earth";
const queryClient = await createQueryClient(RPC_ENDPOINT);
const signingClient = await createSigningClient(RPC_ENDPOINT, offlineSigner);
```
## Digital twin: create an entity
```ts
const createEntity = async (signerAddress: string) => {
const message = {
typeUrl: "/ixo.entity.v1beta1.MsgCreateEntity",
value: ixo.entity.v1beta1.MsgCreateEntity.fromPartial({
ownerAddress: signerAddress,
entityType: "asset",
context: [
ixo.iid.v1beta1.Context.fromPartial({
key: "class",
val: "did:ixo:entity:protocol123",
}),
],
verification: [],
controller: [],
service: [],
linkedResource: [],
accordedRight: [],
linkedEntity: [],
linkedClaim: [],
}),
};
return signingClient.signAndBroadcast(signerAddress, [message], "auto");
};
const entities = await queryClient.ixo.entity.v1beta1.entityList();
```
## Source an entity room (IXO Matrix)
Each entity has an associated encrypted Matrix room — its data room. Source it with the room bot, which creates the room if needed and invites the caller.
```ts
const matrixRoomBotClient = createMatrixRoomBotClient({
homeServerUrl: HOME_SERVER_URL,
botUrl: "https://devmx.ixo.earth/bots/room",
accessToken: matrixAccessToken,
});
const sourceRoomResponse = await matrixRoomBotClient.room.v1beta1.sourceRoomAndJoin(
"did:ixo:entity:abc...xyz",
ucanToken,
);
const roomId = sourceRoomResponse.roomId;
```
The `ucanToken` carries the caller's capability to act on the entity. The optional `accessToken` is the user's Matrix client-server access token, used only for the local join — never sent to the bot server. See [Matrix authentication](/api-reference/matrix-state-bot-api).
## Upload telemetry into the data room
```ts
const matrixApiClient = createMatrixApiClient({
homeServerUrl: HOME_SERVER_URL,
accessToken: matrixAccessToken,
});
const upload = await matrixApiClient.media.v1beta1.upload(
"telemetry-2025-04.json",
"application/json",
telemetryFile,
);
const mxcUri = upload.content_uri;
```
## Read or set room state via the State Bot
Use the State Bot to attach structured profile or settings data to an entity room.
```ts
const matrixStateBotClient = createMatrixStateBotClient({
homeServerUrl: HOME_SERVER_URL,
botUrl: "https://devmx.ixo.earth/bots/state",
accessToken: matrixAccessToken,
});
const stateData = await matrixStateBotClient.state.v1beta1.queryState(
roomId,
"impactsX",
"profile",
ucanToken,
);
await matrixStateBotClient.state.v1beta1.setState(
roomId,
"impactsX",
"profile",
JSON.stringify({ displayName: "Solar Farm Alpha", capacityKw: 5000 }),
ucanToken,
);
```
Always read state before overwriting. The State Bot does not merge — it replaces the value at the path you set.
## Claim flow: submit, evaluate, mint
End-to-end flow for a digital MRV outcome:
```ts
const collectionId = "12";
const adminAddress = "ixo1...";
const oracleAddress = "ixo1oracle...";
const tokenContractAddress = "ixo1contract...";
// 1. Submit a claim referencing evidence stored in the data room
const submitClaimMsg = {
typeUrl: "/ixo.claims.v1beta1.MsgSubmitClaim",
value: ixo.claims.v1beta1.MsgSubmitClaim.fromPartial({
creator: signerAddress,
claimId: "bafkreih...",
collectionId,
adminAddress,
}),
};
await signingClient.signAndBroadcast(signerAddress, [submitClaimMsg], "auto");
// 2. Optional Claim Bot helpers (off-chain co-ordination)
const matrixClaimBotClient = createMatrixClaimBotClient({
homeServerUrl: HOME_SERVER_URL,
botUrl: "https://devmx.ixo.earth/bots/claim",
accessToken: matrixAccessToken,
});
const claimRecord = await matrixClaimBotClient.claim.v1beta1.queryClaim(
collectionId,
"bafkreih...",
ucanToken,
);
// 3. Evaluate after the Agentic Oracle has produced a verification
const evaluateMsg = {
typeUrl: "/ixo.claims.v1beta1.MsgEvaluateClaim",
value: ixo.claims.v1beta1.MsgEvaluateClaim.fromPartial({
creator: signerAddress,
claimId: "bafkreih...",
collectionId,
oracle: oracleAddress,
adminAddress,
status: 1, // Approved
reason: 0,
verificationProof: "bafkrei-proof-cid",
amount: [],
}),
};
await signingClient.signAndBroadcast(signerAddress, [evaluateMsg], "auto");
// 4. Mint outcome tokens against the approved claim
const mintMsg = {
typeUrl: "/ixo.token.v1beta1.MsgMintToken",
value: ixo.token.v1beta1.MsgMintToken.fromPartial({
minter: signerAddress,
contractAddress: tokenContractAddress,
owner: recipientAddress,
mintBatch: [
ixo.token.v1beta1.MintBatch.fromPartial({
name: "CARBON",
index: "bafkreih...",
amount: "3600",
collection: "did:ixo:entity:collection456",
tokenData: [],
}),
],
}),
};
await signingClient.signAndBroadcast(signerAddress, [mintMsg], "auto");
```
The off-chain oracle call is your application code — see [Agentic Oracles ADK](/sdk-reference/oracle-adk) and [Build agentic oracles](/guides/ixo-oracles-architecture).
## Agentic Oracle integration
The off-chain oracle service is built with the [Agentic Oracles ADK](/sdk-reference/oracle-adk) and [Personal Agent ADK](/sdk-reference/oracle-adk). The on-chain side uses `@ixo/impactxclient-sdk` to record `MsgEvaluateClaim`. Wire the two together with whatever HTTP, MCP, or messaging layer your service exposes.
The IXO Oracles ecosystem is private. To request access, see [Build agentic oracles](/guides/ixo-oracles-architecture).
## Next steps
REST, RPC, GraphQL, and service APIs.
Module-level features on protocol.
Matrix data rooms and bot clients.
Source code and reference implementations.
---
# Authentication
> Choose and implement the correct authentication pattern for each IXO API surface.
This guide helps you pick an auth strategy by surface. It does not define canonical headers, endpoint literals, or environment values.
## Before you start
- Identify which interface you are integrating with: protocol gateway or service API.
- Confirm active endpoint and network from `/reference/networks-and-endpoints`.
- Confirm required credential format from `/reference/authentication-matrix`.
## What this guide does
You will map your integration to an auth pattern, apply the required header format, and verify a successful authenticated request without guessing cross-surface behavior.
## Step 1 - Classify the target surface
- Protocol gateways: `/api-reference/rpc-api`, `/api-reference/grpc-gateway-api`
- Service APIs: `/api-reference/blocksync-graphql-api`, `/api-reference/matrix-state-bot-api`, `/api-reference/registry-api`
## Step 2 - Apply the required auth format
Common formats used across IXO surfaces are documented in `/reference/authentication-matrix`.
Do not reuse one format across all APIs unless the authentication matrix explicitly confirms it.
## Step 3 - Verify the result
Send a minimal authenticated read request to the target interface.
Expected result:
- HTTP success response for your target endpoint.
- No authentication or permission error.
## Troubleshooting
### `401 Unauthorized`
Your credential format or token source does not match the target API surface. Re-check `/reference/authentication-matrix`.
### `403 Forbidden`
Your identity is valid but missing required scope or role for the resource.
### `429 Too Many Requests`
The surface-level rate policy has been exceeded. Apply retry/backoff per API guidance.
## Next steps
Apply endpoint-specific authentication requirements and headers.
Select chain IDs and base URLs for your environment.
Confirm canonical product names, SDK names, and routes.
---
# Identity and credentials
> How DIDs, claims, and verifiable credentials fit together on IXO, and how they relate to controllers, subjects, and registry state.
This page ties together concepts that are also described on the [Emerging digital identifiers](/platforms/Emerging/digital-identifiers) and [credential issuance](/platforms/Emerging/credential-issuance) platform pages. Read it when you need **one mental model** before diving into APIs or SDKs.
Standards background: IXO aligns with W3C work on decentralized identifiers and verifiable credentials. See [Decentralized Identifiers (DIDs) v1.0](https://www.w3.org/TR/did-core/) and the [Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/). On-chain and interchain specifics use the **`did:ixo:`** namespace and protocol modules documented under [IXO Protocol](/protocols/ixo-protocol).
## Definitions
A stable, resolvable identifier for an entity (for example `did:ixo:…`). It names a subject in the graph without requiring a single centralized account system.
Metadata bound to a DID: verification methods, **controllers**, services, and links used for authentication and discovery.
The party (human, org, or automated process) authorized to update the DID document or act for that identifier, within the rules of the protocol and domain.
The entity a statement is **about**. In a verifiable credential, the **credential subject** is identified by a DID (often the holder's DID).
A structured assertion about reality—typically about a domain or entity—that can be evaluated, disputed, or accepted against a protocol or rubric. Claims carry or reference **evidence**.
A tamper-evident, issuer-signed bundle of claims about a subject, usable by **verifiers** without trusting only the holder's copy. Status and revocation may be checked against registry or service endpoints.
The party that signs and issues the VC after validation rules are satisfied.
The party that presents the VC (often the subject).
The party that checks signatures, issuer authority, schema, and status before trusting the content.
## Lifecycle (end-to-end)
1. **Register identity** — Create or obtain a DID and domain record so the entity exists in the shared model ([digital twins](/guides/digital-twins), [domain registration](/guides/domain-registration)).
2. **Submit a claim** — Assert something about that entity with evidence and context ([claims](/guides/dev/ixo-claims)).
3. **Validate / evaluate** — Humans, services, or Agentic Oracles check the claim against program rules; outcomes may be recorded on-chain or in linked services.
4. **Issue a VC** — After validation, an issuer may bind selected assertions into a verifiable credential whose **subject** is the holder's DID ([credential issuance](/platforms/Emerging/credential-issuance)).
5. **Verify or revoke** — Verifiers check proofs and status; issuers or governance may revoke or supersede credentials when state changes.
## How the pieces relate
```mermaid
flowchart TB
subgraph identity [Identity]
DID[DID]
Doc[DID_document]
Ctrl[Controllers]
DID --> Doc
Doc --> Ctrl
end
subgraph assertions [Assertions]
Claim[Claim]
Evidence[Evidence]
Claim --> Evidence
end
subgraph trust [Trust artifacts]
VC[Verifiable_credential]
VC --> Claim
end
subgraph stores [Anchors and services]
Registry[Registry_and_chain_state]
Matrix[Matrix_when_encrypted_off_chain]
end
DID --> Claim
Claim --> Registry
VC --> Registry
Evidence --> Matrix
```
- **Registry / chain** — Authoritative pointers and state for domains, claims lifecycle, and credential status, depending on deployment ([registry](/guides/registry), [IXO Protocol](/protocols/ixo-protocol)).
- **Matrix** — Optional encrypted rooms and events when evidence or payloads must not live on-chain ([IXO Matrix](/articles/ixo-matrix)).
## Implementation notes
- Use the [Authentication matrix](/reference/authentication-matrix) and [Networks and endpoints](/reference/networks-and-endpoints) so the correct credentials and base URLs match each **API surface** ([API introduction](/api-reference/intro-apis)).
- For TypeScript patterns (client setup, entities, claims), start with [Developer workflows](/guides/dev/workflows) and [Implementation examples](/guides/dev/examples).
## See also
Vocabulary for domains, PODs, cooperation, and Qi.
Short definitions with links to deeper pages.
Credentials and session patterns for developers.
---
# Session signing with SignX
> Sign transactions in browser apps without per-action wallet confirmations using the SignX SDK and the user's Impacts X mobile wallet.
The IXO `@ixo/signx-sdk` orchestrates mobile-to-web authentication and transaction signing through QR code scanning by the Impacts X mobile wallet. A web app generates a QR code, the user scans it with their Impacts X app, and the SDK long-polls a relayer server until the wallet returns either a login payload or a signed-and-broadcast transaction. Once a transaction session is open, additional transactions are added to the same session without re-scanning the QR — the wallet activates the next transaction in sequence on each broadcast.
This is the publicly documented signing path. There is no in-browser private key — every signature comes from the user's mobile wallet.
## Before you start
You need:
- A web app (React or other) running over HTTPS.
- A reachable [`ixo-message-relayer`](https://github.com/ixofoundation/ixo-message-relayer) endpoint, or an IXO-operated equivalent for your network — see [Networks and endpoints](/reference/networks-and-endpoints).
- The Impacts X mobile app installed by your user ([App Store](https://apps.apple.com/app/impacts-x/id6444948058) / [Play Store](https://play.google.com/store/apps/details?id=com.ixo.mobile)).
- `@ixo/signx-sdk` and `@ixo/impactxclient-sdk` installed:
```bash
npm install @ixo/signx-sdk @ixo/impactxclient-sdk
```
## Initialize the SignX client
```ts
import { SignX } from "@ixo/signx-sdk";
const signXClient = new SignX({
endpoint: "https://your-message-relayer.example.com",
sitename: "Your dApp",
network: "mainnet",
});
```
`network` is one of `mainnet | testnet | devnet`. The `sitename` is shown to the user inside the Impacts X app on every confirmation.
## Log the user in
`login()` returns the QR payload to render and starts long-polling for the wallet's response. Subscribe to `SIGN_X_LOGIN_SUCCESS` and `SIGN_X_LOGIN_ERROR` to receive the result.
```ts
const loginRequest = await signXClient.login({ matrix: true });
signXClient.on("SIGN_X_LOGIN_SUCCESS", (event) => {
const { name, address, pubKey, did, algo, matrix } = event.data;
// persist the user; pubKey is hex-encoded, decode if you need bytes.
});
signXClient.on("SIGN_X_LOGIN_ERROR", (error) => {
// user cancelled, polling timed out, or the wallet returned an error.
});
```
Pass `{ useDeeplink: true }` on mobile devices so the SDK opens the Impacts X app via deeplink before polling — this prevents some mobile browsers from cancelling the long-polling request when the user navigates away.
## Sign and broadcast a transaction
Build the transaction body using the `@ixo/impactxclient-sdk` registry, then pass the hex-encoded `txBody` to `signXClient.transact()`. The wallet signs the transaction and broadcasts it on the user's behalf.
```ts
import { createRegistry, ixo } from "@ixo/impactxclient-sdk";
import { toHex } from "@cosmjs/encoding";
const registry = createRegistry();
const messages = [
{
typeUrl: "/ixo.entity.v1beta1.MsgCreateEntity",
value: ixo.entity.v1beta1.MsgCreateEntity.fromPartial({
ownerAddress: user.address,
entityType: "asset",
context: [],
verification: [],
controller: [],
service: [],
linkedResource: [],
accordedRight: [],
linkedEntity: [],
linkedClaim: [],
}),
},
];
const txBodyHex = toHex(registry.encodeTxBody({ messages, memo: "" }));
const transactRequest = await signXClient.transact({
address: user.address,
did: user.did,
pubkey: user.pubKey,
timestamp: new Date().toISOString(),
transactions: [{ sequence: 1, txBodyHex }],
});
signXClient.on("SIGN_X_TRANSACT_SUCCESS", (event) => {
// event.data.transactionHash, event.data.code (0 on success)
});
signXClient.on("SIGN_X_TRANSACT_ERROR", (error) => {
// wallet rejected, broadcast failed, or session timed out.
});
```
If `transactRequest.sessionHash` is present a new session was opened — render a QR with `transactRequest`. If it is absent the transaction was added to the existing session — call `signXClient.pollNextTransaction()` and update the UI to indicate the next signature is pending in the wallet.
The wallet returns the chain's broadcast result (`code`, `transactionHash`, …) directly through `SIGN_X_TRANSACT_SUCCESS`.
## Manage the session
Every transaction extends the session's `validUntil` timestamp. Inspect `signXClient.transactSessionHash` to know whether a session is active and `signXClient.transactSequence` for the current sequence the wallet is being asked to sign.
```ts
window.addEventListener("beforeunload", () => {
signXClient.stopPolling("Page closing", "SIGN_X_TRANSACT_ERROR");
});
```
Sessions are intentionally not persisted across browser refresh — `transactSessionHash` lives in memory only. After a refresh, the next `transact()` call starts a new session and renders a fresh QR.
## Transaction-type restrictions
Restricting which message types a session can sign is enforced **on the wallet** — the Impacts X app inspects each transaction body against the user's saved policy before signing. The web SDK does not impose its own filter. To restrict the messages a session can sign in your dApp, build the txBody only for the message types your flow needs and refuse to call `transact()` for anything else; pair this with smart-account authenticators (see [Manage smart accounts](/guides/dev/smart-accounts)) for an on-chain enforcement layer.
When you build a `MsgGrant` (`cosmos.authz.v1beta1.MsgGrant`) with a `GenericAuthorization`, the `msg` field is the **fully qualified message URL**, e.g. `/ixo.entity.v1beta1.MsgCreateEntity`. The same convention applies everywhere transaction filtering is configured.
## Verify the result
`SIGN_X_TRANSACT_SUCCESS` returns the transaction hash directly. To independently verify, query `cosmos.tx.v1beta1.GetTx` on the [gRPC gateway API](/api-reference/grpc-gateway-api) with that hash, or fetch the transaction from the [Blocksync GraphQL API](/api-reference/blocksync-graphql-api).
## Troubleshooting
Confirm `endpoint` points to a reachable Message Relayer for the same `network` as the user's wallet, and that `network` matches the chain the user logged in on. Check the relayer is healthy.
Mobile browsers may cancel ongoing requests when navigating to deeplinks. Pass `useDeeplink: true` to `login`, `matrixLogin`, `dataPass`, and as the third argument to `transact` so the SDK opens the deeplink first and waits 300 ms before polling.
The wallet may reject if the message type is outside the user's signing policy, the chain ID does not match, or the user manually denies. `SIGN_X_TRANSACT_ERROR` carries the reason. Re-issue the transaction after the user adjusts their policy.
`transactSessionHash` is cleared when the relayer reports the session expired. The next `transact()` call starts a new session — surface this to the user (a fresh QR is needed). Call `stopPolling(message, event, false)` to stop polling without clearing the session if you want to retain it.
## Next steps
Methods, events, and types.
Source of truth for the SDK.
Add on-chain enforcement of which message types may be signed.
Combine SignX with Cosmos `x/authz` and `x/feegrant`.
---
# Manage smart accounts
> Add, remove, and toggle authenticators on IXO smart accounts to extend Cosmos SDK signing with conditional rules.
The IXO `smartaccount` module replaces the default Cosmos SDK signature ante handler with an opt-in pluggable system. Each account can register one or more **authenticators** — signature verifiers, message filters, spend-limit contracts, or composite logic — that execute on every transaction the account opts in to. If no authenticators are selected, transactions fall back to the standard Cosmos SDK signature flow.
## Before you start
You need:
- An IXO account with `uixo` for fees on the network you target — see [Networks and endpoints](/reference/networks-and-endpoints).
- An offline signer — see the [SDK README](https://github.com/ixofoundation/ixo-multiclient-sdk#creating-signers).
- An authenticator type and config payload to register. The supported types are defined in the [smartaccount types proto](https://github.com/ixofoundation/ixo-blockchain/tree/main/proto/ixo/smartaccount/v1beta1).
```bash
npm install @ixo/impactxclient-sdk
```
```ts
import { ixo, createSigningClient } from "@ixo/impactxclient-sdk";
const signingClient = await createSigningClient(RPC_ENDPOINT, offlineSigner);
```
## Add an authenticator
```ts
const addAuthenticatorMsg = {
typeUrl: "/ixo.smartaccount.v1beta1.MsgAddAuthenticator",
value: ixo.smartaccount.v1beta1.MsgAddAuthenticator.fromPartial({
sender: signerAddress,
authenticatorType: "SignatureVerification",
data: new TextEncoder().encode(JSON.stringify({ pubkey: pubkeyHex })),
}),
};
await signingClient.signAndBroadcast(signerAddress, [addAuthenticatorMsg], "auto");
```
The chain assigns an `id` to each authenticator on success. Read it from transaction events to reference the authenticator in subsequent transactions.
## Remove an authenticator
```ts
const removeAuthenticatorMsg = {
typeUrl: "/ixo.smartaccount.v1beta1.MsgRemoveAuthenticator",
value: ixo.smartaccount.v1beta1.MsgRemoveAuthenticator.fromPartial({
sender: signerAddress,
id: 1n,
}),
};
```
## Pause the module (governance)
`MsgSetActiveState` is the circuit breaker — only the module's gov authority can broadcast it.
```ts
const setActiveMsg = ixo.smartaccount.v1beta1.MsgSetActiveState.fromPartial({
authority: govAuthority,
active: false,
});
```
When `active` is `false`, the smart-account ante handler is skipped and all transactions use the default Cosmos SDK signature path.
## Use authenticators on a transaction
A transaction opts in to specific authenticators via the **`AuthenticatorTxExtension`** TX extension. The full pattern depends on your signing flow — see the [smartaccount module README](https://github.com/ixofoundation/ixo-blockchain/tree/main/x/smartaccount) and the [README for the IXO MultiClient SDK](https://github.com/ixofoundation/ixo-multiclient-sdk) for current helpers. The extension carries the list of authenticator IDs to use for each signer.
## Composite authenticators
Composite authenticators combine sub-authenticators using boolean logic.
Approves only when every sub-authenticator approves: `auth(a) && auth(b) && ...`.
Approves when at least one sub-authenticator approves: `auth(a) || auth(b) || ...`.
Multi-signature where signatures are split across sub-authenticators (one signer per sub-authenticator).
Example shapes:
```text
// One-click trading: hot key restricted to swap messages with on-chain spend-limit guard
AllOf(
SignatureVerification(usersPubKey),
AnyOf(
MessageFilter(SwapMsg1),
MessageFilter(SwapMsg2)
),
CosmwasmAuthenticator(spendLimitContract, params)
)
// Multisig (2-of-3): each signer satisfies one branch
PartitionedAllOf(pubkey1, pubkey2, pubkey3)
// Cosigner protection: cosigner approves alongside a user-selected set
AllOf(
SignatureVerification(cosignerPubKey),
AnyOf(...userAuthenticators)
)
```
## Authentication phases
Each authenticator runs through a three-phase lifecycle on every transaction:
1. **Authenticate** — verify the transaction is authorized.
2. **Track** — record any state changes the authenticator needs (committed regardless of transaction outcome).
3. **Confirm execution** — validate the post-execution effects.
## Verify the result
Query an account's authenticators through the `ixo.smartaccount.v1beta1.Query/AccountAuthenticators` endpoint on the [gRPC gateway API](/api-reference/grpc-gateway-api). Module params (including `is_smart_account_active`) are read from `Query/Params`.
## Troubleshooting
`authenticatorType` must match one of the registered types in the chain's `smartaccount` module. Check `Query/Params.maximum_unauthenticated_gas` and the module's registered types.
`MsgRemoveAuthenticator` fails when `id` does not exist for `sender`. Query `AccountAuthenticators` to confirm the id.
When `is_smart_account_active` is `false`, `MsgAddAuthenticator` and `MsgRemoveAuthenticator` are rejected. The module is gated by gov via `MsgSetActiveState`.
Ensure the TX extension lists the correct authenticator IDs in the order matching the signer set, and that the data payload registered with `MsgAddAuthenticator` is the canonical encoding for that authenticator type.
## Next steps
Module-level features and types.
Source of truth for smart-account messages.
Issue scoped session keys backed by smart-account authenticators.
Use Cosmos authz alongside smart-account authenticators.
---
# Manage authorization
> Grant, revoke, and execute Cosmos authz and feegrant messages on the IXO Protocol.
Authorization (`x/authz`) lets one account grant another the right to broadcast specific message types on its behalf, scoped by amount, time, or message URL. `x/feegrant` lets one account pay another's transaction fees. Both are standard Cosmos SDK modules — IXO uses the upstream message types directly.
## Before you start
You need:
- An IXO account with `uixo` for fees on the network you target — see [Networks and endpoints](/reference/networks-and-endpoints).
- An offline signer — see the [SDK README](https://github.com/ixofoundation/ixo-multiclient-sdk#creating-signers).
- The granter and grantee bech32 addresses.
```bash
npm install @ixo/impactxclient-sdk
```
```ts
import { cosmos, createSigningClient } from "@ixo/impactxclient-sdk";
const signingClient = await createSigningClient(RPC_ENDPOINT, offlineSigner);
```
## Grant a send authorization
`SendAuthorization` lets the grantee call `cosmos.bank.v1beta1.MsgSend` from the granter, capped at `spend_limit`.
```ts
const sendAuth = cosmos.bank.v1beta1.SendAuthorization.fromPartial({
spendLimit: [{ denom: "uixo", amount: "1000000" }],
});
const grantMsg = {
typeUrl: "/cosmos.authz.v1beta1.MsgGrant",
value: cosmos.authz.v1beta1.MsgGrant.fromPartial({
granter: granterAddress,
grantee: granteeAddress,
grant: cosmos.authz.v1beta1.Grant.fromPartial({
authorization: {
typeUrl: "/cosmos.bank.v1beta1.SendAuthorization",
value: cosmos.bank.v1beta1.SendAuthorization.encode(sendAuth).finish(),
},
}),
}),
};
await signingClient.signAndBroadcast(granterAddress, [grantMsg], "auto");
```
For broader grants (e.g. authorize any IBC transfer message URL), use `cosmos.authz.v1beta1.GenericAuthorization` with the target `msg` type URL.
## Execute a granted message
The grantee wraps the message they were authorized to send inside `cosmos.authz.v1beta1.MsgExec`.
```ts
const innerSend = cosmos.bank.v1beta1.MsgSend.fromPartial({
fromAddress: granterAddress,
toAddress: recipientAddress,
amount: [{ denom: "uixo", amount: "100000" }],
});
const execMsg = {
typeUrl: "/cosmos.authz.v1beta1.MsgExec",
value: cosmos.authz.v1beta1.MsgExec.fromPartial({
grantee: granteeAddress,
msgs: [
{
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: cosmos.bank.v1beta1.MsgSend.encode(innerSend).finish(),
},
],
}),
};
await signingClient.signAndBroadcast(granteeAddress, [execMsg], "auto");
```
For an IBC transfer, replace the inner message with `ibc.applications.transfer.v1.MsgTransfer`.
## Revoke an authorization
```ts
const revokeMsg = {
typeUrl: "/cosmos.authz.v1beta1.MsgRevoke",
value: cosmos.authz.v1beta1.MsgRevoke.fromPartial({
granter: granterAddress,
grantee: granteeAddress,
msgTypeUrl: "/cosmos.bank.v1beta1.MsgSend",
}),
};
```
## Grant a fee allowance
```ts
const basicAllowance = cosmos.feegrant.v1beta1.BasicAllowance.fromPartial({
spendLimit: [{ denom: "uixo", amount: "500000" }],
});
const grantAllowanceMsg = {
typeUrl: "/cosmos.feegrant.v1beta1.MsgGrantAllowance",
value: cosmos.feegrant.v1beta1.MsgGrantAllowance.fromPartial({
granter: granterAddress,
grantee: granteeAddress,
allowance: {
typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance",
value: cosmos.feegrant.v1beta1.BasicAllowance.encode(basicAllowance).finish(),
},
}),
};
```
## Revoke a fee allowance
```ts
const revokeAllowanceMsg = {
typeUrl: "/cosmos.feegrant.v1beta1.MsgRevokeAllowance",
value: cosmos.feegrant.v1beta1.MsgRevokeAllowance.fromPartial({
granter: granterAddress,
grantee: granteeAddress,
}),
};
```
The grantee then sets the granter's address as the `feePayer` in the transaction's fee object — the granter is debited instead of the grantee.
## Verify the result
Query active grants through the [gRPC gateway API](/api-reference/grpc-gateway-api):
- `cosmos.authz.v1beta1.Query/Grants` — grants between a granter and grantee
- `cosmos.authz.v1beta1.Query/GranterGrants` — all grants issued by a granter
- `cosmos.authz.v1beta1.Query/GranteeGrants` — all grants received by a grantee
- `cosmos.feegrant.v1beta1.Query/Allowance` — fee allowance status
## Troubleshooting
Confirm bech32 addresses are valid for the chain prefix (`ixo`), the denom is supported, and the amount is within `spend_limit`.
Confirm the grant has not expired, the grantee's transaction message URL exactly matches the authorization's allowed type, and the spend limit has not been exhausted.
Ensure the granter has sufficient balance for the allowance, the allowance type matches the use case (`BasicAllowance` vs `PeriodicAllowance` vs `AllowedMsgAllowance`), and the transaction's `feePayer` is set to the granter.
Use `AllowedMsgAllowance` to combine fee payment with a strict whitelist of message URLs the grantee may submit at the granter's expense.
## Next steps
Use IXO-specific authz types for entities, claims, and tokens.
Add programmable authenticators on top of authz.
Full reference for `x/authz`.
Full reference for `x/feegrant`.
---
# Authorization (Authz)
> Understanding the core concepts and implementation of authorization in the IXO ecosystem
# Authorization (Authz)
The authorization (authz) system is a fundamental concept in the IXO ecosystem, built on top of the Cosmos SDK's authz module. This guide explains the core concepts and implementation details that developers need to understand when working with authorization.
## Core Concepts
### Basic Structure
Authorization in IXO follows the Cosmos SDK's authz module implementation (ADR 30), which enables one account (granter) to grant specific privileges to another account (grantee). Each authorization is stored on-chain with three key components:
```json
{
"granter": "address",
"grantee": "address",
"msg_type_url": "message_type"
}
```
This structure means there can only be one authorization for a specific message type between any two accounts. If a new authorization is granted for the same message type between the same accounts, it will override the previous one.
### Authorization Interface
Authorizations are implemented using the Authorization interface, which allows for customization of grant parameters and limits. The most basic implementation is `GenericAuthorization`, which provides unlimited rights to execute a specific message type.
## Built-in Authorization Types
The Cosmos SDK provides three built-in authorization implementations:
Provides unlimited rights to execute a specific message type without any restrictions.
Allows sending tokens with specific limits (amount restrictions) for the `/cosmos.bank.v1beta1.MsgSend` message type.
Provides authorization for staking-related operations with specific parameters.
## IXO Custom Authorizations
IXO implements several custom authorization types for specific message types:
- **Message type:** `/ixo.claims.v1beta1.MsgSubmitClaim`
- **Description:** Authorization for submitting claims
- **Message type:** `/ixo.claims.v1beta1.MsgEvaluateClaim`
- **Description:** Authorization for evaluating claims
- **Message type:** `/ixo.claims.v1beta1.MsgWithdrawPayment`
- **Description:** Authorization for withdrawing payments
- **Message type:** `/ixo.claims.v1beta1.MsgCreateClaimAuthorization`
- **Description:** Authorization for creating claims
### Constraints and Lists
Custom authorizations in IXO use lists for constraints because there can only be one grant between accounts for a specific message type. For example, if an admin (granter) wants to grant a user (grantee) the right to submit claims for multiple collections, they must include all constraints in a single authorization.
## Important Considerations
1. **Authorization Override**: When granting a new authorization for the same message type between the same accounts, it will override any existing authorization, including its constraints.
2. **Query Requirements**: When querying authorizations, you need the actual message type (e.g., `/ixo.claims.v1beta1.MsgSubmitClaim`), not the authorization type.
3. **Revocation**: To revoke an authorization, you need to specify the correct message type URL, not the authorization type.
## Related Resources
- [Cosmos SDK Authz Module Documentation](https://docs.cosmos.network/main/build/modules/authz)
- [Claims management](/guides/dev/ixo-claims)
- [Digital vouchers workflow](/platforms/Emerging/digital-vouchers)
---
# Cognitive Digital Twins
> Build and manage cognitive digital twin systems for optimizing real-world systems and processes
The IXO Stack implements a whole-systems approach in which each physical or conceptual entity is represented as a Digital Twin Domain within a graph of inter-connected domains. Clean cooking systems serve as a practical example throughout this guide.
## Digital Twin Domains
Different classes of entities have unique configurations of properties. For instance, an Organisational Domain always contains Membership Groups as Sub-domains.
### Canonical Domain Classes
Digital representations of physical devices and sensors (e.g., cooking stoves, IoT monitors, and tracked inventory)
Digital models of Organisational structures (e.g., projects, funders, distribution agents)
Digital representation of the networks of relationships and interactions between real-world entities
AI-enabled agentic services performing evaluations, verification, and intelligent automations
## Entity Structure
```json Entity Example
{
"did": "did:ixo:entity/device-123",
"type": "Device",
"controller": "did:ixo:org/operator",
"services": [{
"id": "#data",
"type": "DataIngestion",
"endpoint": "https://api.emerging.eco/ingest"
}],
"verifiableCredential": [{
"type": "Certification",
"issuer": "did:ixo:org/certifier"
}]
}
```
```python Example: Clean Cooking Device
from emerging import Entity
# Create a specific device entity
device = Entity.create(
type="CleanCookingDevice", # Specific implementation
controller="did:ixo:org/project-dev",
services=[{
"id": "#data",
"type": "DataIngestion",
"endpoint": "https://api.emerging.eco/ingest"
}]
)
```
## Domain Properties
W3C Decentralized Identifier for unique entity identification
Entity with update permissions
Associated services and endpoints
Issued certificates and proofs
## Entity Relationships
* Organisations implement Projects
* Projects generate Assets
* Assets produce Claims
* Oracles verify Claims
* Claims become Credentials
* `Organisation` implements a `Project`
* `Project` distributes cookstove `Assets` and Fuel
* `Assets produce` usage data based on a `Protocol`
* `Oracles` verify Claims according to the `Protocol`
* Carbon Credit `Assets` are minted with Verifiable Credentials
## Using Protocols
Protocols are templates that define standard properties and relationships for specific entity types. They can be customized for any domain.
### Protocol Structure
```json
{
"type": "Protocol",
"properties": {
"metricOne": "string",
"metricTwo": "number",
"measurements": "object"
},
"relationships": {
"verifier": "OracleEntity",
"operator": "OrganisationEntity"
}
}
```
### Instantiating Entities
```bash Generic Creation
curl -X POST https://api.emerging.eco/v1/entities \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"type": "Device",
"protocol": "did:ixo:protocol/template",
"properties": {
"metricOne": "value",
"metricTwo": 100
}
}'
```
```python Clean Cooking Example
from emerging import Protocol
# Example using clean cooking protocol
protocol = Protocol.get("did:ixo:protocol/clean-cooking")
stove = protocol.create_entity(
type="CleanCookingDevice",
properties={
"fuel_type": "electric",
"efficiency": 85
}
)
```
## Next Steps
Learn about entity management
Create custom protocols
Implement verification systems
Connect physical devices
---
# Manage entities
> Create, transfer, verify, and govern entity domains on the IXO Protocol with the IXO MultiClient SDK.
This guide shows the chain-level message patterns for managing **entities** on the IXO Protocol using the [`@ixo/impactxclient-sdk`](https://www.npmjs.com/package/@ixo/impactxclient-sdk). For the data model, read [Entity domains](/guides/dev/ixo-domains) and [Domain configuration](/articles/domain-config) first.
## Before you start
You need:
- A funded IXO account on the network you target — see [Networks and endpoints](/reference/networks-and-endpoints).
- An offline signer (Cosmos-kit, Keplr, or `getOfflineSigner` from `cosmjs-utils`) — see the [SDK README on creating signers](https://github.com/ixofoundation/ixo-multiclient-sdk#creating-signers).
- The [`@ixo/impactxclient-sdk`](https://www.npmjs.com/package/@ixo/impactxclient-sdk) installed.
```bash
npm install @ixo/impactxclient-sdk
```
The signing client follows the canonical three-step pattern: compose, sign, broadcast.
```ts
import { ixo, createSigningClient } from "@ixo/impactxclient-sdk";
const signingClient = await createSigningClient(RPC_ENDPOINT, offlineSigner);
```
## Create an entity
```ts
const message = {
typeUrl: "/ixo.entity.v1beta1.MsgCreateEntity",
value: ixo.entity.v1beta1.MsgCreateEntity.fromPartial({
ownerAddress: signerAddress,
entityType: "asset",
context: [
ixo.iid.v1beta1.Context.fromPartial({
key: "class",
val: "did:ixo:entity:protocol123",
}),
],
verification: [],
controller: [],
service: [],
linkedResource: [],
accordedRight: [],
linkedEntity: [],
linkedClaim: [],
}),
};
const response = await signingClient.signAndBroadcast(signerAddress, [message], "auto");
```
The chain returns a transaction with events that include the new entity DID.
Field names mirror the [`x/entity` proto definitions](https://github.com/ixofoundation/ixo-blockchain/tree/main/proto/ixo/entity/v1beta1). Inspect `entity.proto` for the authoritative field list and any version-specific changes.
## Transfer entity ownership
`MsgTransferEntity` reassigns the underlying entity NFT to a new controller account.
```ts
const transferMsg = {
typeUrl: "/ixo.entity.v1beta1.MsgTransferEntity",
value: ixo.entity.v1beta1.MsgTransferEntity.fromPartial({
id: entityDid,
ownerAddress: currentOwnerAddress,
recipientDid: "did:ixo:recipient123",
}),
};
await signingClient.signAndBroadcast(currentOwnerAddress, [transferMsg], "auto");
```
## Update verification status
A verifying authority (oracle, registry, or DAO controller) calls `MsgUpdateEntityVerified` to flip the verified flag for one or more entities it is permitted to attest.
```ts
const verifyMsg = {
typeUrl: "/ixo.entity.v1beta1.MsgUpdateEntityVerified",
value: ixo.entity.v1beta1.MsgUpdateEntityVerified.fromPartial({
id: entityDid,
relayerNode: relayerDid,
entityVerified: true,
}),
};
await signingClient.signAndBroadcast(signerAddress, [verifyMsg], "auto");
```
## Manage entity accounts
Entities can hold named sub-accounts (for example `treasury`, `payouts`, `operations`) that can transact on behalf of the entity under explicit authz grants.
```ts
const createAccountMsg = {
typeUrl: "/ixo.entity.v1beta1.MsgCreateEntityAccount",
value: ixo.entity.v1beta1.MsgCreateEntityAccount.fromPartial({
id: entityDid,
ownerAddress: signerAddress,
name: "treasury",
}),
};
const grantMsg = {
typeUrl: "/ixo.entity.v1beta1.MsgGrantEntityAccountAuthz",
value: ixo.entity.v1beta1.MsgGrantEntityAccountAuthz.fromPartial({
id: entityDid,
ownerAddress: signerAddress,
name: "treasury",
granteeAddress: granteeAddress,
grant: /* cosmos.authz.v1beta1.Grant */ undefined,
}),
};
const revokeMsg = {
typeUrl: "/ixo.entity.v1beta1.MsgRevokeEntityAccountAuthz",
value: ixo.entity.v1beta1.MsgRevokeEntityAccountAuthz.fromPartial({
id: entityDid,
ownerAddress: signerAddress,
name: "treasury",
granteeAddress: granteeAddress,
msgTypeUrl: "/cosmos.bank.v1beta1.MsgSend",
}),
};
```
Construct the `grant` value using `cosmos.authz.v1beta1.Grant.fromPartial({...})` with the matching authorization. See [Authentication and authorization](/guides/dev/authentication) and [Custom authorisations for IXO Claims](/guides/dev/authz-custom).
## Verify the result
Query the entity through the [Blocksync GraphQL API](/api-reference/blocksync-graphql-api):
```graphql
query GetEntity($did: String!) {
entity(id: $did) {
id
type
status
relayerNode
owner
controller
accounts {
name
address
}
}
}
```
Or use the gRPC gateway directly — see [gRPC gateway API](/api-reference/grpc-gateway-api) for the entity module endpoints.
## Troubleshooting
The `entityType` value must match a type the chain accepts. Inspect module params via the gRPC gateway or query the `EntityList` endpoint to confirm allowed types on your network.
The signer must be a current controller or relayer with the right capability. Confirm the signer address is in the entity's controller set or has an active authz grant.
Account names are unique per entity. Pick a different name or revoke and re-grant authz on the existing account.
## Next steps
Update controllers, services, resources, and accorded rights.
Decide what to keep on protocol vs in IXO Matrix.
Full SDK reference and module list.
Source of truth for message shapes.
---
# Domain Registration
> Create and manage digital twins of real-world entities using the IXO Stack
Domains are used to create digital twins of real-world entities.
## Quick Start
```bash Create Domain
curl -X POST https://api.emerging.eco/v1/domains \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"type": "Project",
"protocol": "did:ixo:protocol/clean-cooking",
"metadata": {
"name": "Clean Cooking Project"
}
}'
```
```python
from emerging import Client
client = Client('YOUR_API_KEY')
domain = client.domains.create(
type="Project",
protocol="did:ixo:protocol/clean-cooking",
metadata={
"name": "Clean Cooking Project"
}
)
```
## Domain Types
Legal or virtual entities, managed as DAOs
`Project` type entities for clean cooking initiatives
`Asset` type entities for IoT-enabled cooking devices
Templates and rules that govern domain behavior
## Domain Properties
Each domain requires:
* **Digital Identifier**: Unique DID following Interchain Identifier format
* **Entity Type**: Organisation, Project, Device, etc.
* **Protocol**: Defines the Class of entity and its inherited sets of properties
* **Controller**: Account/s that manage the domain record (DID Document)
* **Metadata**: Additional standard domain settings
## Creating Domains
### Required Parameters
Entity type (Organisation, Project, Device, etc.)
DID of the Protocol that defines the Entity Class
Domain settings of the Entity
### Optional Parameters
ISO timestamp when domain becomes valid
ISO timestamp when domain expires
## Domain Verification
Domains require verification by the platform's governance system:
1. A compliance or program representative submits a verification proposal with the entity package.
2. Eligible voters review and vote (quorum and thresholds depend on deployment).
3. On approval, domain status moves to **Verified** and downstream services (reporting, issuance) can enable.
For a platform-scoped walkthrough (DAO stages, relayer assignment, GraphQL status checks), read [Emerging Platform domain registration](/platforms/Emerging/domain-registration). For how **roles** (Developer, Evaluator, Funder, Service provider) map to these steps, read [Your role](/your-role).
Only verified domains can participate in platform activities like carbon credit issuance. Confirm auth and endpoints for your environment in the [Authentication matrix](/reference/authentication-matrix) and [Networks and endpoints](/reference/networks-and-endpoints).
## Next steps
Governance stages, entity JSON, and GraphQL examples
Model entities, protocols, and relationships
How DIDs, claims, and verifiable credentials connect
How decentralized governance fits IXO programs
---
# Domain settings
> Manage domain-level DID document settings through IXO Protocol interfaces.
Use this guide for domain setting workflows. It focuses on protocol-facing document management and avoids service-specific endpoint duplication.
## Before you start
- You control the target domain DID.
- You have signing authority for domain update transactions.
- You have selected the target network from `/reference/networks-and-endpoints`.
## What this guide does
You apply common domain setting operations and verify that changes are committed through protocol interfaces.
## Core operations
- Update DID document fields.
- Add or revoke verification methods.
- Add or remove services.
- Manage controllers and linked resources.
## Protocol boundary
These operations are IXO Protocol concerns. Service APIs such as Registry or Matrix can consume domain metadata, but they do not replace protocol-level document authority.
## Example protocol message literal
```typescript
const msg = {
typeUrl: "/ixo.iid.v1beta1.MsgUpdateIidDocument",
value: {
id,
controller,
verificationMethod,
authentication,
service
}
};
```
## Verify the result
Expected result:
- document query returns updated fields;
- updates are attributable to an authorized controller.
## Next steps
Protect sensitive domain data and access patterns.
Use gRPC-gateway routes for protocol-level interactions.
Match each API surface to the required credentials and headers.
---
# Domain privacy
> Apply privacy controls to domain-linked data without mixing protocol and service concerns.
Use this guide to design private domain data handling patterns. Exact API contracts belong in protocol and service references.
## Before you start
- Confirm your target network and endpoint set in `/reference/networks-and-endpoints`.
- Confirm authentication requirements for each surface in `/reference/authentication-matrix`.
- Confirm controller authority for the domain DID.
## What this guide does
You separate privacy responsibilities across protocol metadata and service storage, then verify that sensitive values are not exposed in public document fields.
## Privacy model boundary
- Protocol layer: public DID document structure and authorized metadata updates.
- Service layer: encrypted payload storage and controlled retrieval patterns.
## Typical workflow
1. Register key material and controller relationships at protocol layer.
2. Encrypt sensitive settings before writing to service storage.
3. Publish only references or proofs in public protocol metadata when required.
4. Enforce recipient authorization on read.
## Verify the result
Expected result:
- domain metadata updates succeed with authorized signatures;
- sensitive values remain encrypted in service storage;
- unauthorized reads fail.
## Next steps
Configure services, permissions, and domain-level behavior.
Apply correct auth patterns across IXO API surfaces.
Confirm canonical product names and SDK route mappings.
---
# Claims management
> Build claim workflows while keeping IXO Protocol and service API responsibilities separate.
Use this guide for workflow design and integration choices. Use API reference pages for exact request/response contracts.
## Before you start
- Decide whether your claim operation runs at protocol layer or service layer.
- Confirm endpoint and network values in `/reference/networks-and-endpoints`.
- Confirm auth requirements in `/reference/authentication-matrix`.
## Protocol vs service boundary
- Protocol layer: claim module transactions and state queries through protocol gateways.
- Service layer: product-specific reporting and workflow APIs (for example, Registry service endpoints).
## Claim workflow outline
1. Create or collect claim data.
2. Submit to the target surface (protocol or service).
3. Run evaluation and dispute handling.
4. Track lifecycle transitions.
## Example protocol message literal
```typescript
const msg = {
typeUrl: "/ixo.claims.v1beta1.MsgSubmitClaim",
value: {
creator,
claimId,
claimType,
data: claimData
}
};
```
## Verify the result
Expected result:
- accepted claim write or query response on the target surface;
- clear traceability of whether execution happened through protocol gateways or a service API.
## See also
See how claims relate to DIDs and verifiable credentials.
Follow SDK-oriented create and evaluate patterns.
Reference protocol RPC methods and request patterns.
Integrate registry endpoints for claim-linked records.
Confirm canonical product and SDK naming/routes.
Use the correct environment endpoints and chain details.
Match each API surface to required credentials.
## Troubleshooting
- **Broadcast / transaction failures** — Read `rawLog` or `code` from the node response. `insufficient funds`, `unauthorized`, and `sequence mismatch` are common: fix fees, signer, or account sequence. Compare your `chainId` and RPC URL with [Networks and endpoints](/reference/networks-and-endpoints).
- **GraphQL partial failures** — Blocksync may return `errors` alongside partial `data`. Treat the response as failed for downstream writes if any required field is null; re-check filters and IDs.
- **DID or entity not found** — Confirm the entity is indexed on the network you query; registry vs indexer lag can produce temporary “not found” results.
## Next steps
Use the API matrix to choose RPC, REST, GraphQL, or Registry for each workflow step.
---
# Agent Evaluations
> Evaluate agent work in Qi Flows using UCAN authority, Claims, evidence, rubrics, and UDID records.
Build this when an AI agent should inspect evidence, apply rules, summarize findings, or recommend a decision without becoming the final source of accountability.
Agent evaluations in Qi are not ordinary model evaluations. You are not only asking whether an answer is fluent, correct, or useful. You are checking whether an agent acted within delegated authority, used the right evidence, applied the right rubric, and produced a verifiable decision record that people, services, regulators, or communities can inspect.
Treat the model response as a working output. Treat Claims, evidence references, UCAN delegations, Flow state, and UDID records as the accountable system of record.
## The problem
Teams need agents to help with evidence review, claim processing, decision support, fulfillment checks, and workflow routing.
Ordinary agents create several risks:
- they may act outside their authority
- they may use stale or incomplete context
- they may confuse submitted evidence with verified state
- they may summarize without citing sources
- they may recommend actions that the workflow does not allow
- they may produce outputs that cannot be replayed, challenged, or audited
- they may trigger payments, credentials, or state changes before a valid determination exists
In Qi, the Flow engine solves this by making evaluation part of a governed state machine.
An agent evaluation should answer four questions:
**Qi mechanism:** UCAN delegation and capability checks
**Qi mechanism:** Claims, entities, evidence, and Flow state
**Qi mechanism:** Protocol, rubric, tools, checks, and citations
**Qi mechanism:** Evaluation Claim and UDID record
## What you build
You build a Qi Flow that evaluates agent work or agent-assisted reviews against verified context.
The Flow should:
1. receive a Claim, task output, or Flow event
2. check the agent’s UCAN authority
3. resolve the current state of the relevant entities, Claims, credentials, and evidence
4. run the evaluation against a declared rubric
5. produce a structured Evaluation Claim
6. produce or update a UDID when a decision and impact determination is ready
7. route the result to a human, service, payment step, credential step, dispute path, or next Flow state
## Core pattern
A participant, service, device, or agent submits a Claim, completes a task, or proposes a state transition inside a Qi Flow.
The Flow instance defines the subject, claim type, protocol, rubric version, allowed evidence sources, current state snapshot, and decision boundary.
The Flow verifies that the agent has the required object-capability delegation for this exact action, resource, claim type, tool, time window, and Flow instance.
The agent may only inspect Claims, evidence, rooms, graph state, tools, and records that are inside its UCAN scope.
The agent checks the Claim against required fields, evidence rules, protocol constraints, scoring thresholds, disqualifiers, and escalation rules.
The result is written as structured data with cited evidence references, applied checks, confidence, recommendation, limitations, and proof of the agent’s authority.
A UDID records the decision and impact determination: what was decided, why, under which authority, with which evidence, and what state or value changed.
The Flow routes the evaluation to approval, rejection, dispute, settlement, credential issuance, state update, or a request for more evidence.
## Key concepts
Delegates what an agent may do, on which resource, under which constraints.
Records something asserted or performed, including agent work, evidence submission, review, or evaluation.
Records how a Claim, task, or proposed state transition was evaluated.
Records the final decision and impact determination produced from one or more evaluations.
Defines where the evaluation happens and which transitions are allowed.
Defines the rules, scoring, thresholds, disqualifiers, and escalation conditions.
Links the evaluation to documents, measurements, observations, attestations, media, reports, sensor records, or external records.
Performs specialized review, evidence analysis, scoring, prediction, recommendation, or verification support.
## Start with one evaluation
Do not begin with a fully autonomous approval workflow.
Start with one narrow review task where the agent can recommend, but not finalize, the outcome.
Good first tasks:
- check whether a Claim is complete
- classify evidence by type and relevance
- detect missing required fields
- compare submitted evidence against protocol requirements
- summarize conflicting evidence
- score one rubric section
- recommend whether a human verifier should approve, reject, dispute, or request more evidence
Avoid first tasks where the agent can directly release funds, issue credentials, update high-value state, or approve irreversible outcomes.
## Design the evaluation Flow
Use this minimum Flow shape:
**Purpose:** Claim or agent task output has entered the Flow.
**Exit condition:** Claim exists and has a subject.
**Purpose:** Verify UCAN delegation and credentials.
**Exit condition:** Agent has required capabilities.
**Purpose:** Retrieve current graph state, evidence, and protocol rules.
**Exit condition:** All required context references are loaded or marked missing.
**Purpose:** Run the agent evaluation against the rubric.
**Exit condition:** Structured Evaluation Claim is produced.
**Purpose:** Human or authorized verifier reviews the recommendation.
**Exit condition:** Reviewer accepts, rejects, disputes, or requests more evidence.
**Purpose:** UDID is created or updated.
**Exit condition:** Decision and impact determination is signed or recorded.
**Purpose:** Allowed state transition, payment, credential, or next Flow is triggered.
**Exit condition:** Action result is recorded.
**Purpose:** Evaluation loop is complete.
**Exit condition:** Audit trail is inspectable.
Add explicit failure states:
The agent does not have the required UCAN capability.
Required evidence is missing, stale, inaccessible, or invalid.
The Claim fails a hard protocol rule.
Evidence sources conflict or cannot be reconciled.
The agent is uncertain or the rubric requires human judgment.
A participant challenges the evaluation or determination.
## Configure UCAN authority
A UCAN should be scoped to the smallest useful evaluation task.
Define:
- issuer: the human, organization, POD, or service delegating authority
- audience: the agent or Agentic Oracle DID receiving the authority
- resource: the POD, Flow instance, Claim Collection, Claim, entity, evidence set, room, or tool
- capabilities: the exact actions the agent may perform
- constraints: limits on claim type, time, budget, tool use, output type, state transition, and approval power
- expiry: when the delegation ends
- revocation path: how the delegation can be suspended or revoked
- proof chain: how the Flow verifies the delegation
A useful first UCAN capability set:
Read one submitted Claim and its metadata.
Read only evidence linked to the Claim.
Read only entities referenced by the Claim.
Read the active rubric and protocol version.
Create an Evaluation Claim.
Propose a Flow transition, not execute it.
Send a structured finding to the review room.
Avoid granting these at first:
Value movement should require a UDID and stronger approval.
Credential issuance should require human or protocol-controlled determination.
Direct state updates can bypass review.
Approval should be separate from recommendation.
Agents should not modify the rules they are evaluated against.
### Example UCAN design shape
Use this as a design shape, then map it to the canonical Qi and IXO SDK fields used in your implementation.
```json
{
"ucan": {
"issuer": "did:ixo:pod:review-board",
"audience": "did:ixo:oracle:evidence-reviewer",
"resource": {
"pod": "did:ixo:pod:clean-cooking-program",
"flow": "flow:claim-review:v1",
"claimCollection": "claims:stove-usage:v1"
},
"capabilities": [
"claim.read",
"evidence.read",
"entity.read",
"rubric.read",
"evaluation.create",
"state.propose"
],
"constraints": {
"claimTypes": ["stove_usage"],
"maxEvidenceItems": 50,
"allowedTools": ["ixo.graph.query", "evidence.hash.verify", "rubric.evaluate"],
"mayApprove": false,
"mayReleasePayment": false,
"expiresAt": "2026-06-30T23:59:59Z"
}
}
}
```
The safest default is propose-only. Let the agent create an Evaluation Claim and propose the next Flow state. Let the Flow, verifier, or protocol decide whether the proposal becomes a UDID-backed determination.
## Define the Claim under review
Each evaluation should start with a clear Claim.
Minimum Claim inputs:
Unique identifier for the Claim.
Type of Claim being reviewed.
DID of the person, organization, service, device, or agent that made the Claim.
Entity, asset, project, person, device, service, or outcome the Claim is about.
Structured submitted data.
Linked evidence references.
Cryptographic proof, signature, hash, attestation, or provenance record.
Submission time.
Protocol or Blueprint used to evaluate the Claim.
Qi Flow instance handling the review.
Example:
```json
{
"claimId": "claim:stove-usage:000123",
"claimType": "stove_usage",
"issuer": "did:ixo:org:field-operator",
"subject": "did:ixo:device:stove-7781",
"data": {
"period": "2026-04",
"reportedBurnHours": 182,
"householdId": "entity:household:442"
},
"evidence": [
{
"type": "deviceTelemetry",
"uri": "ipfs://...",
"hash": "bafy..."
},
{
"type": "fieldVisitReport",
"uri": "ipfs://...",
"hash": "bafy..."
}
],
"protocolId": "blueprint:clean-cooking-mrv:v1",
"flowId": "flow:claim-review:7781"
}
```
## Define the rubric
The rubric converts protocol rules into checks that the agent and Flow can apply.
A practical rubric should include:
- required evidence
- evidence freshness rules
- source authenticity checks
- data integrity checks
- field completeness checks
- allowed value ranges
- consistency checks across evidence sources
- disqualifying conditions
- scoring rules
- minimum score for recommendation
- conditions that require human review
- conditions that require dispute or investigation
- allowed Flow transitions after evaluation
Example rubric shape:
```json
{
"rubricId": "rubric:stove-usage-review:v1",
"claimType": "stove_usage",
"requiredEvidence": [
"deviceTelemetry",
"fieldVisitReport"
],
"checks": [
{
"id": "evidence.telemetry.present",
"type": "required",
"description": "Telemetry evidence is linked and hash-verifiable"
},
{
"id": "usage.range.valid",
"type": "range",
"description": "Reported burn hours are within the protocol range",
"min": 1,
"max": 744
},
{
"id": "field.report.consistent",
"type": "consistency",
"description": "Field visit report does not contradict telemetry period or device identity"
}
],
"thresholds": {
"recommendApprove": 0.85,
"recommendRejectBelow": 0.5,
"humanReviewBelow": 0.85
},
"disqualifiers": [
"missingTelemetry",
"invalidEvidenceHash",
"deviceNotLinkedToHousehold",
"claimOutsideReportingPeriod"
]
}
```
## Run the evaluation
The agent evaluation should produce structured output, not a free-form opinion.
Minimum Evaluation Claim output:
Unique identifier for the evaluation.
Claim being evaluated.
Agentic Oracle, agent, human, or service performing the evaluation.
Proof that the evaluator had authority.
Rubric used.
Exact version used.
Reference to the Flow or graph state used at evaluation time.
Evidence items inspected.
Rule-by-rule results.
Structured observations with evidence citations.
Numeric score if the rubric requires one.
Proposed next action.
Confidence in the recommendation.
Missing evidence, uncertainty, assumptions, or unresolved conflicts.
Flow transition proposed by the agent.
Signature, hash, attestation, or other proof of the evaluation record.
Example Evaluation Claim:
```json
{
"evaluationId": "eval:claim:stove-usage:000123:oracle-01",
"type": "AgentEvaluationClaim",
"subjectClaimId": "claim:stove-usage:000123",
"evaluatorDid": "did:ixo:oracle:evidence-reviewer",
"ucanProof": "ucan:proof:...",
"rubricId": "rubric:stove-usage-review:v1",
"rubricVersion": "1.0.0",
"stateSnapshotRef": "state:flow:claim-review:7781:checkpoint:004",
"evidenceRefs": [
"evidence:telemetry:hash:bafy...",
"evidence:field-report:hash:bafy..."
],
"checks": [
{
"checkId": "evidence.telemetry.present",
"result": "pass",
"evidenceRef": "evidence:telemetry:hash:bafy..."
},
{
"checkId": "usage.range.valid",
"result": "pass",
"observedValue": 182
},
{
"checkId": "field.report.consistent",
"result": "needs_review",
"reason": "Field report date is one day outside the telemetry period"
}
],
"score": 0.82,
"recommendation": "request_more_evidence",
"confidence": 0.76,
"limitations": [
"Field report date mismatch requires human review"
],
"proposedTransition": "human_escalation"
}
```
Do not store private model scratchpad as the audit trail. Store evidence references, extracted facts, tool calls, applied checks, rule outcomes, recommendation, limitations, and the final rationale that reviewers can inspect.
## Create the UDID
A UDID is created when the Flow has enough information to record a decision and impact determination.
Do not create a UDID for every intermediate model output. Create or update a UDID when the Flow reaches a determination point.
A UDID should record:
Unique determination identifier.
Approval, rejection, request for evidence, dispute, settlement, credential issuance, state update, or no-op.
Claims considered.
Evaluations used.
UCANs, credentials, verifier role, or governance authority.
Rubric and protocol version applied.
Evidence references used in the determination.
Final decision.
What changed or will change because of the decision.
Flow transition or graph update authorized by the determination.
Human, service, governance process, or authorized verifier.
Determination time.
Signature, attestation, transaction hash, or other proof.
Period or condition under which the determination can be challenged.
Example UDID shape:
```json
{
"udid": "udid:flow:claim-review:7781:determination:001",
"type": "UniversalDecisionAndImpactDetermination",
"decisionType": "request_more_evidence",
"subjectClaims": [
"claim:stove-usage:000123"
],
"evaluationClaims": [
"eval:claim:stove-usage:000123:oracle-01"
],
"authority": {
"verifier": "did:ixo:person:human-verifier-17",
"agentUcan": "ucan:proof:...",
"protocol": "blueprint:clean-cooking-mrv:v1"
},
"rubric": {
"id": "rubric:stove-usage-review:v1",
"version": "1.0.0"
},
"determination": {
"status": "more_evidence_required",
"reason": "Telemetry is present and valid, but the field report date conflicts with the reporting period."
},
"impact": {
"paymentReleased": false,
"credentialIssued": false,
"claimStatus": "evidence_requested"
},
"stateTransition": {
"from": "review_required",
"to": "insufficient_evidence"
},
"disputeWindow": "P14D"
}
```
## Decide what the agent may do
Use three evaluation modes.
**Agent can do:** Inspect context and create an Evaluation Claim.
**Use when:** First implementation, high-stakes review, new rubric.
**Agent can do:** Create an Evaluation Claim and propose a Flow transition.
**Use when:** The rubric is stable and human review remains required.
**Agent can do:** Execute a permitted transition after checks pass.
**Use when:** Low-risk actions with strict UCAN scope and protocol guardrails.
For most production systems, start with `Recommend`, move to `Propose`, and only allow `Act` for narrow, reversible, low-risk transitions.
## Test the evaluation
Use a test set before connecting the evaluation to real state changes.
Create cases for:
Agent can recommend approval correctly.
Agent detects incomplete submissions.
Agent does not trust unverifiable evidence.
Agent applies freshness rules.
Agent escalates instead of forcing a decision.
UCAN gate blocks evaluation.
Agent cannot operate outside scope.
Flow rejects previously authorized access.
Agent treats evidence content as untrusted input.
Agent does not recommend approval.
Rubric threshold behavior is correct.
Review and correction are captured.
Flow can route to dispute handling.
Payment cannot happen without valid UDID authority.
## Evaluation metrics
Track operational quality, not only model quality.
Agent only acts when UCAN scope permits.
Every finding links to evidence or state.
All required checks are applied.
Bad Claims are not recommended for approval.
Valid Claims are not rejected without cause.
Ambiguous cases route to humans.
Determinations include authority, evidence, decision, impact, and proof.
High override rate triggers rubric or agent improvement.
A reviewer can reconstruct the evaluation from records.
Proposed transitions match Flow rules.
Review speed improves without reducing accountability.
## Common failure modes
Require evidence references for every finding. Reject evaluations that cite documents, measurements, Claims, or state that are not present in the permitted context.
Replace broad API keys with UCAN capability delegation. Scope authority by Flow instance, resource, claim type, tool, time window, and allowed action.
Include a state snapshot reference in the Evaluation Claim. If the graph state changes, require a new evaluation or explicit refresh.
Convert policy language into checks, thresholds, disqualifiers, and escalation rules. Ambiguity should route to human review.
Write structured Evaluation Claims and UDID records. A chat response should not be the source of truth for settlement, credentials, or state changes.
Separate task execution from evaluation. Use independent evaluators or human review for high-stakes decisions.
Require the UDID to reference Claims, evaluations, evidence, rubric version, authority, decision, impact, and proof.
## First implementation move
Build one agent-assisted evaluation that cannot directly approve, pay, issue, or update state.
Define:
- one Claim type
- one Claim Collection
- one Flow
- one Agentic Oracle or agent DID
- one UCAN delegation
- one rubric
- one Evaluation Claim schema
- one UDID schema
- one human review step
- one dispute path
- one production metric dashboard
Then run at least 20 representative Claims through the Flow before enabling any automated state transition.
## Production checklist
Before launch, confirm:
- the agent has a DID
- every evaluation action requires UCAN authority
- UCAN scopes are narrow and expire
- Claims have typed schemas and evidence references
- evidence can be resolved and verified
- the rubric is versioned
- the Flow has explicit states and failure paths
- the agent emits structured Evaluation Claims
- the UDID records authority, evidence, decision, impact, state transition, and proof
- irreversible actions require human, protocol, or governance approval
- disputes can be submitted and resolved
- reviewers can replay the evaluation from stored records
- revoked authority blocks future actions
- payment, credential, and state update actions cannot execute without valid determination authority
## Example: agent-assisted evidence review
A field operator submits a Claim that a clean cooking device was used during a reporting period.
The Qi Flow:
1. receives the Claim
2. checks that the Evidence Review Oracle has UCAN authority to inspect this claim type
3. retrieves linked telemetry, field report, device entity, household entity, and active protocol rules
4. asks the agent to apply the usage review rubric
5. records an Evaluation Claim with findings, evidence references, score, recommendation, and limitations
6. routes the recommendation to a human verifier because the score is below the automatic threshold
7. records a UDID after the verifier decides to request more evidence
8. updates the Flow state to `insufficient_evidence`
9. notifies the claimant about the missing or conflicting evidence
The agent helped evaluate the Claim, but it did not become the final authority. The accountable record is the combination of UCAN delegation, Claim, evidence, Evaluation Claim, human review, Flow transition, and UDID.
## Next steps
Create, process, evaluate, dispute, and automate verifiable Claims.
Connect agents to IXO services through secure, capability-scoped tool interfaces.
Build agent services for verification, decision support, evidence analysis, and workflow automation.
Coordinate humans, agents, services, and applications over shared state.
---
# Digital MRV
> Build automated measurement, reporting, and verification systems using IoT devices, blockchain, and Agentic Oracles
Digital MRV (dMRV) provides cryptographically verifiable proof of real-world activities and impacts through automated data collection, validation, and certification. Unlike traditional MRV, it enables real-time monitoring, cryptographic trust, and automated verification.
## Key Advantages
Continuous IoT monitoring vs periodic surveys
Tamper-proof records with blockchain anchoring
Standardized templates for rapid deployment
AI-powered validation with Oracle services
## Protocol Configuration
- Select or create protocol template
- Define measurement parameters
- Configure validation rules
- Set reporting intervals
- Map to recognized standards
- Set baseline calculations
- Define emission factors
- Configure verification rules
### Example Protocol
```json
{
"type": "CleanCookingProtocol",
"version": "1.0.0",
"methodology": "GS_MMECD_1.0",
"parameters": {
"measurementInterval": 300,
"requiredMetrics": [
"burnTime",
"fuelConsumed",
"temperature"
],
"baselineEmissionFactor": 7.2,
"minimumDataPoints": 100
},
"validation": {
"rules": [{
"metric": "temperature",
"min": 50,
"max": 300
}],
"requiredEvidence": [
"deviceTelemetry",
"fuelDelivery",
"baselineData"
]
}
}
```
## Data Collection
### Device Integration
```python Configure Device
from emerging import Device, Protocol
# Load protocol
protocol = Protocol.get("clean-cooking-v1")
# Configure device with protocol
device = Device.configure(
device_id="did:ixo:device/123",
protocol=protocol,
settings={
"measurement_interval": 300,
"offline_buffer_size": 1000
}
)
# Start measurements
device.start_monitoring()
```
```json Device Telemetry
{
"deviceId": "did:ixo:device/123",
"timestamp": "2024-03-15T12:00:00Z",
"measurements": {
"burnTime": 300,
"fuelConsumed": 15,
"temperature": 180
},
"signature": "0xabc..."
}
```
### Data Pipeline
- Device authentication
- Secure data transmission
- Edge validation
- Offline buffering
- Protocol validation
- Data aggregation
- Anomaly detection
- Baseline comparison
## Verification Process
### Oracle Network
```python
from emerging import Oracle, Protocol
# Initialize oracle with protocol
oracle = Oracle(
protocol_id="clean-cooking-v1",
min_confidence=0.95,
required_validators=3
)
# Verify measurements
verification = oracle.verify_measurements(
measurements=device_data,
baseline=baseline_data,
evidence={
"telemetry": sensor_logs,
"delivery": fuel_records
}
)
# Issue credential if valid
if verification.is_valid:
credential = verification.issue_credential()
```
```javascript
import { Oracle, Protocol } from '@emerging/sdk';
// Initialize oracle with protocol
const oracle = new Oracle({
protocolId: 'clean-cooking-v1',
minConfidence: 0.95,
requiredValidators: 3
});
// Verify measurements
const verification = await oracle.verifyMeasurements({
measurements: deviceData,
baseline: baselineData,
evidence: {
telemetry: sensorLogs,
delivery: fuelRecords
}
});
// Issue credential if valid
if (verification.isValid) {
const credential = await verification.issueCredential();
}
```
### Validation Rules
Measurements follow protocol specification
Cryptographic proofs are valid
Confidence score for supporting evidence
## Credential Issuance
```json
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://w3id.org/dmrv/v1"
],
"type": ["VerifiableCredential", "MeasurementClaim"],
"issuer": "did:ixo:oracle/456",
"issuanceDate": "2024-03-15T12:00:00Z",
"credentialSubject": {
"id": "did:ixo:device/123",
"protocol": "clean-cooking-v1",
"measurements": {
"burnTime": 300,
"fuelConsumed": 15
},
"evidence": [{
"type": "TelemetryData",
"hash": "0x123...",
"uri": "ipfs://Qm..."
}]
}
}
```
## Best Practices
Follow protocol specifications carefully and implement comprehensive data validation at every stage.
### Security
- Use secure communication channels
- Implement device authentication
- Validate data integrity
- Monitor for anomalies
### Scalability
- Configure offline buffering
- Implement batch processing
- Use load balancing
- Monitor system performance
## Next Steps
Create custom protocols
Connect IoT devices
Configure validation networks
---
# Manage tokens
> Create, mint, transfer, retire, and govern IXO Protocol tokens with the IXO MultiClient SDK.
The IXO Protocol token module wraps the `ixo1155` smart contract — a CosmWasm implementation of the EIP-1155 multi-token standard. Each token domain has a token class (entity DID), a fixed cap, and supports both fungible and non-fungible balances within a single contract.
This guide shows the chain-level message patterns. For the data model and contract details, read the [`x/token` proto definitions](https://github.com/ixofoundation/ixo-blockchain/tree/main/proto/ixo/token/v1beta1) and the [`@ixo/impactxclient-sdk` README](https://github.com/ixofoundation/ixo-multiclient-sdk#readme).
## Before you start
You need:
- An IXO account with sufficient `uixo` for fees on the network you target — see [Networks and endpoints](/reference/networks-and-endpoints).
- An offline signer — see the [SDK README on creating signers](https://github.com/ixofoundation/ixo-multiclient-sdk#creating-signers).
- The token protocol entity DID (the value used as `class`). Token class is validated against an existing entity.
- For minting, a deployed token contract address (set via `MsgCreateToken`).
- For credit transfers, a valid `WithdrawPaymentAuthorization` if you act on behalf of another account — see [Custom authorisations for IXO Claims](/guides/dev/authz-custom).
```bash
npm install @ixo/impactxclient-sdk
```
```ts
import { ixo, createSigningClient } from "@ixo/impactxclient-sdk";
const signingClient = await createSigningClient(RPC_ENDPOINT, offlineSigner);
```
## Create a token
`MsgCreateToken` registers a new token name under a token-protocol entity. Cap `"0"` means unlimited supply.
```ts
const createMsg = {
typeUrl: "/ixo.token.v1beta1.MsgCreateToken",
value: ixo.token.v1beta1.MsgCreateToken.fromPartial({
minter: signerAddress,
class: "did:ixo:entity:protocol123",
name: "CARBON",
description: "Verified carbon offset units",
image: "ipfs://bafy.../image.png",
tokenType: "ixo1155",
cap: "1000000",
}),
};
await signingClient.signAndBroadcast(signerAddress, [createMsg], "auto");
```
## Mint tokens
`MsgMintToken` issues new units in batches against an existing token contract. Each batch references a unique `index` and a `collection` DID.
```ts
const mintMsg = {
typeUrl: "/ixo.token.v1beta1.MsgMintToken",
value: ixo.token.v1beta1.MsgMintToken.fromPartial({
minter: signerAddress,
contractAddress: tokenContractAddress,
owner: ownerAddress,
mintBatch: [
ixo.token.v1beta1.MintBatch.fromPartial({
name: "CARBON",
index: "bafkreih...",
amount: "1000",
collection: "did:ixo:entity:collection456",
tokenData: [],
}),
],
}),
};
await signingClient.signAndBroadcast(signerAddress, [mintMsg], "auto");
```
## Transfer tokens
`MsgTransferToken` moves balances between accounts. All tokens in a single message must belong to the same contract.
```ts
const transferMsg = {
typeUrl: "/ixo.token.v1beta1.MsgTransferToken",
value: ixo.token.v1beta1.MsgTransferToken.fromPartial({
owner: signerAddress,
recipient: recipientAddress,
tokens: [
ixo.token.v1beta1.TokenBatch.fromPartial({
id: "bafkreih...",
amount: "100",
}),
],
}),
};
await signingClient.signAndBroadcast(signerAddress, [transferMsg], "auto");
```
## Retire tokens
`MsgRetireToken` permanently removes tokens from circulation while preserving their on-chain history. Use this for end-claims such as voluntary carbon retirement.
```ts
const retireMsg = {
typeUrl: "/ixo.token.v1beta1.MsgRetireToken",
value: ixo.token.v1beta1.MsgRetireToken.fromPartial({
owner: signerAddress,
tokens: [
ixo.token.v1beta1.TokenBatch.fromPartial({
id: "bafkreih...",
amount: "50",
}),
],
jurisdiction: "ZA-WC-7700",
reason: "Voluntary retirement on behalf of buyer X",
}),
};
```
The `jurisdiction` field follows the format `[-[-]]`. Only the country code is required.
## Transfer credit (ITMO state change)
`MsgTransferCredit` flips tokens to a "transferred" state when units are moved off-protocol to a different registry — for example, Internationally Transferred Mitigation Outcomes (ITMOs) under Article 6.2 of the Paris Agreement. It accepts an optional `authorization_id` for delegated transfers.
```ts
const creditTransferMsg = {
typeUrl: "/ixo.token.v1beta1.MsgTransferCredit",
value: ixo.token.v1beta1.MsgTransferCredit.fromPartial({
owner: signerAddress,
tokens: [
ixo.token.v1beta1.TokenBatch.fromPartial({
id: "bafkreih...",
amount: "100",
}),
],
jurisdiction: "CH",
reason: "ITMO transfer to Swiss national registry",
authorizationId: "auth-2025-001",
}),
};
```
## Cancel, pause, or stop a token
| Operation | Message | Effect |
|---|---|---|
| Cancel batches | `MsgCancelToken` | Owner cancels specific token batches with a reason. |
| Pause minting | `MsgPauseToken` | Minter halts further minting on a contract. `paused: true` to halt, `false` to resume. |
| Stop the contract | `MsgStopToken` | Minter permanently stops the token contract. Irreversible. |
```ts
const pauseMsg = {
typeUrl: "/ixo.token.v1beta1.MsgPauseToken",
value: ixo.token.v1beta1.MsgPauseToken.fromPartial({
minter: signerAddress,
contractAddress: tokenContractAddress,
paused: true,
}),
};
```
## Verify the result
Query token state through the [Blocksync GraphQL API](/api-reference/blocksync-graphql-api):
```graphql
query Token($id: String!) {
token(id: $id) {
id
name
description
cap
class
minter
minted
transferred
retired
cancelled
}
}
```
Or read directly from the token contract via the [gRPC gateway API](/api-reference/grpc-gateway-api).
## Troubleshooting
`class` must be a valid entity DID for an existing token-protocol entity. Confirm the entity exists and that you control it.
`MsgMintToken` will fail if the new total would exceed the token `cap`. Either raise the cap by recreating the token or split issuance across additional token names.
Minting is rejected on paused contracts and on stopped contracts. Issue `MsgPauseToken` with `paused: false` to resume; `MsgStopToken` cannot be reversed.
Delegated `MsgTransferCredit` requires a `WithdrawPaymentAuthorization` referencing the supplied `authorization_id`. See [Custom authorisations for IXO Claims](/guides/dev/authz-custom).
## Next steps
Full SDK reference including signing client setup.
Source of truth for token messages.
Add bonding-curve pricing and reserves to tokens.
Delegate token operations safely.
---
# Manage bonds
> Create and manage algorithmic bonds, trade them, and settle outcome payments on the IXO Protocol.
The IXO Protocol bonds module provides universal token bonding-curve functions to mint, burn, swap, and settle bonds. Bonds price their tokens via a bonding curve (`function_type` and `function_parameters`), accept one or more `reserve_tokens`, and pass through a lifecycle of `HATCH → OPEN → SETTLE` (or `FAILED`). Alpha bonds add risk-adjustment via the `oracle_did`-controlled `MsgSetNextAlpha`.
## Before you start
You need:
- An IXO account with sufficient `uixo` for fees on the network you target — see [Networks and endpoints](/reference/networks-and-endpoints).
- An offline signer — see the [SDK README on creating signers](https://github.com/ixofoundation/ixo-multiclient-sdk#creating-signers).
- For `MsgCreateBond`: a controller DID, a creator DID, optional oracle DID for alpha bonds, and reserve token denoms.
- For `MsgWithdrawReserve`: the bond's `allow_reserve_withdrawals` flag must be `true`.
```bash
npm install @ixo/impactxclient-sdk
```
```ts
import { ixo, createSigningClient } from "@ixo/impactxclient-sdk";
const signingClient = await createSigningClient(RPC_ENDPOINT, offlineSigner);
```
## Create a bond
```ts
const createBondMsg = {
typeUrl: "/ixo.bonds.v1beta1.MsgCreateBond",
value: ixo.bonds.v1beta1.MsgCreateBond.fromPartial({
bondDid: "did:ixo:bond:abc123",
token: "BOND",
name: "Solar Farm Alpha Bond",
description: "Outcome-linked bond for verified solar production",
functionType: "augmented_function",
functionParameters: [
ixo.bonds.v1beta1.FunctionParam.fromPartial({ param: "p0", value: "1" }),
],
creatorDid: "did:ixo:creator123",
controllerDid: "did:ixo:controller123",
oracleDid: "did:ixo:oracle123",
reserveTokens: ["uixo"],
txFeePercentage: "0.5",
exitFeePercentage: "1.0",
feeAddress: feeAddress,
reserveWithdrawalAddress: reserveAddress,
maxSupply: { denom: "BOND", amount: "1000000" },
orderQuantityLimits: [],
sanityRate: "0",
sanityMarginPercentage: "0",
allowSells: true,
allowReserveWithdrawals: false,
alphaBond: true,
batchBlocks: "1",
outcomePayment: "0",
creatorAddress: signerAddress,
}),
};
await signingClient.signAndBroadcast(signerAddress, [createBondMsg], "auto");
```
## Edit, set alpha, transition state
```ts
const editMsg = {
typeUrl: "/ixo.bonds.v1beta1.MsgEditBond",
value: ixo.bonds.v1beta1.MsgEditBond.fromPartial({
bondDid: "did:ixo:bond:abc123",
name: "Solar Farm Alpha Bond v2",
description: "Updated description",
orderQuantityLimits: "",
sanityRate: "",
sanityMarginPercentage: "",
editorDid: "did:ixo:editor123",
editorAddress: signerAddress,
}),
};
const setAlphaMsg = {
typeUrl: "/ixo.bonds.v1beta1.MsgSetNextAlpha",
value: ixo.bonds.v1beta1.MsgSetNextAlpha.fromPartial({
bondDid: "did:ixo:bond:abc123",
alpha: "0.85",
delta: "0.05",
oracleDid: "did:ixo:oracle123",
oracleAddress: oracleSignerAddress,
}),
};
const updateStateMsg = {
typeUrl: "/ixo.bonds.v1beta1.MsgUpdateBondState",
value: ixo.bonds.v1beta1.MsgUpdateBondState.fromPartial({
bondDid: "did:ixo:bond:abc123",
state: "OPEN",
editorDid: "did:ixo:editor123",
editorAddress: signerAddress,
}),
};
```
Bond states are: `HATCH` (initial fundraising), `OPEN` (active trading), `SETTLE` (settlement), `FAILED`.
## Buy, sell, swap
```ts
const buyMsg = {
typeUrl: "/ixo.bonds.v1beta1.MsgBuy",
value: ixo.bonds.v1beta1.MsgBuy.fromPartial({
buyerDid: "did:ixo:buyer123",
amount: { denom: "BOND", amount: "1000000" },
maxPrices: [{ denom: "uixo", amount: "100" }],
bondDid: "did:ixo:bond:abc123",
buyerAddress: signerAddress,
}),
};
const sellMsg = {
typeUrl: "/ixo.bonds.v1beta1.MsgSell",
value: ixo.bonds.v1beta1.MsgSell.fromPartial({
sellerDid: "did:ixo:seller123",
amount: { denom: "BOND", amount: "1000000" },
bondDid: "did:ixo:bond:abc123",
sellerAddress: signerAddress,
}),
};
const swapMsg = {
typeUrl: "/ixo.bonds.v1beta1.MsgSwap",
value: ixo.bonds.v1beta1.MsgSwap.fromPartial({
swapperDid: "did:ixo:swapper123",
bondDid: "did:ixo:bond:abc123",
from: { denom: "uixo", amount: "1000" },
toToken: "uatom",
swapperAddress: signerAddress,
}),
};
```
## Outcome payments and withdrawals
```ts
const outcomePayMsg = {
typeUrl: "/ixo.bonds.v1beta1.MsgMakeOutcomePayment",
value: ixo.bonds.v1beta1.MsgMakeOutcomePayment.fromPartial({
senderDid: "did:ixo:funder123",
amount: "1000000",
bondDid: "did:ixo:bond:abc123",
senderAddress: signerAddress,
}),
};
const withdrawShareMsg = {
typeUrl: "/ixo.bonds.v1beta1.MsgWithdrawShare",
value: ixo.bonds.v1beta1.MsgWithdrawShare.fromPartial({
recipientDid: "did:ixo:holder123",
bondDid: "did:ixo:bond:abc123",
recipientAddress: signerAddress,
}),
};
const withdrawReserveMsg = {
typeUrl: "/ixo.bonds.v1beta1.MsgWithdrawReserve",
value: ixo.bonds.v1beta1.MsgWithdrawReserve.fromPartial({
withdrawerDid: "did:ixo:treasury123",
amount: [{ denom: "uixo", amount: "500000" }],
bondDid: "did:ixo:bond:abc123",
withdrawerAddress: signerAddress,
}),
};
```
## Verify the result
Query bond state through the [Blocksync GraphQL API](/api-reference/blocksync-graphql-api) or `ixo.bonds.v1beta1` query endpoints on the [gRPC gateway API](/api-reference/grpc-gateway-api).
## Troubleshooting
`function_type` must be one of the supported curve types (`power_function`, `sigmoid_function`, `swapper_function`, `augmented_function`). Each requires a specific set of `function_parameters` — see the [bonds proto](https://github.com/ixofoundation/ixo-blockchain/tree/main/proto/ixo/bonds/v1beta1).
State transitions are constrained by the bond's lifecycle. `HATCH → OPEN` requires the hatch threshold to be met. `OPEN → SETTLE` is typically called once the outcome obligation is met or the bond expires.
`MsgWithdrawReserve` fails when `allow_reserve_withdrawals` is `false` on `MsgCreateBond`. This flag is immutable.
Only the bond's `oracle_address` (resolving from `oracle_did`) can broadcast `MsgSetNextAlpha`. Confirm the signer matches.
## Next steps
Module-level features and types.
Source of truth for bond messages.
Mint outcome tokens against settled bonds.
Stake IXO tokens for use in bonded protocols.
---
# Manage liquid staking
> Stake and unstake IXO tokens through the liquid staking module to receive stIXO.
The IXO `liquidstake` module lets users stake `uixo` and receive a liquid representation (`stuixo`) that can be transferred and used in bonds, AMMs, and DeFi while underlying delegations earn validator rewards. The module routes stake to a whitelisted validator set with weights, distributes rewards through `weighted_rewards_receivers`, and supports a circuit-breaker pause and burn-to-unstake-instantly for the protocol.
## Before you start
You need:
- An IXO account funded with `uixo` for staking and fees on the network you target — see [Networks and endpoints](/reference/networks-and-endpoints).
- An offline signer — see the [SDK README](https://github.com/ixofoundation/ixo-multiclient-sdk#creating-signers).
- Awareness that governance changes (params, whitelisted validators, reward receivers, pause) require `cosmos.gov.v1.MsgSubmitProposal` wrapping the relevant `MsgUpdate*` message — they are not callable directly by users.
```bash
npm install @ixo/impactxclient-sdk
```
```ts
import { ixo, createSigningClient } from "@ixo/impactxclient-sdk";
const signingClient = await createSigningClient(RPC_ENDPOINT, offlineSigner);
```
## Stake `uixo` for `stuixo`
```ts
const stakeMsg = {
typeUrl: "/ixo.liquidstake.v1beta1.MsgLiquidStake",
value: ixo.liquidstake.v1beta1.MsgLiquidStake.fromPartial({
delegatorAddress: signerAddress,
amount: { denom: "uixo", amount: "1000000" },
}),
};
await signingClient.signAndBroadcast(signerAddress, [stakeMsg], "auto");
```
Stake is distributed across the active whitelisted validator set in proportion to their `target_weight`. The minted `stuixo` amount reflects the current `nav` (net asset value) of the staked pool.
## Unstake `stuixo` for `uixo`
```ts
const unstakeMsg = {
typeUrl: "/ixo.liquidstake.v1beta1.MsgLiquidUnstake",
value: ixo.liquidstake.v1beta1.MsgLiquidUnstake.fromPartial({
delegatorAddress: signerAddress,
amount: { denom: "stuixo", amount: "1000000" },
}),
};
await signingClient.signAndBroadcast(signerAddress, [unstakeMsg], "auto");
```
Unstaking enqueues an unbonding entry in the staking module — the underlying `uixo` is returned after the chain unbonding period.
## Governance-controlled operations
The following messages cannot be broadcast by ordinary users — they must be wrapped in a `cosmos.gov.v1.MsgSubmitProposal` with the `liquidstake` module's authority as the signer.
```ts
const updateParamsMsg = ixo.liquidstake.v1beta1.MsgUpdateParams.fromPartial({
authority: govAuthority,
params: ixo.liquidstake.v1beta1.Params.fromPartial({
liquidBondDenom: "stuixo",
}),
});
const updateValidatorsMsg = ixo.liquidstake.v1beta1.MsgUpdateWhitelistedValidators.fromPartial({
authority: govAuthority,
whitelistedValidators: [
ixo.liquidstake.v1beta1.WhitelistedValidator.fromPartial({
validatorAddress: "ixovaloper1...",
targetWeight: "1",
}),
],
});
const updateReceiversMsg = ixo.liquidstake.v1beta1.MsgUpdateWeightedRewardsReceivers.fromPartial({
authority: govAuthority,
weightedRewardsReceivers: [
ixo.liquidstake.v1beta1.WeightedAddress.fromPartial({
address: "ixo1treasury...",
weight: "1",
}),
],
});
const pauseMsg = ixo.liquidstake.v1beta1.MsgSetModulePaused.fromPartial({
authority: govAuthority,
paused: true,
});
```
Wrap these as gov proposals using `cosmos.gov.v1.MsgSubmitProposal` (see the Cosmos SDK [gov module documentation](https://docs.cosmos.network/main/build/modules/gov)).
## Burn-to-unstake (admin)
```ts
const burnMsg = {
typeUrl: "/ixo.liquidstake.v1beta1.MsgBurn",
value: ixo.liquidstake.v1beta1.MsgBurn.fromPartial({
address: signerAddress,
amount: { denom: "stuixo", amount: "1000000" },
}),
};
```
`MsgBurn` removes `stuixo` from circulation without queuing an unbonding — used by the protocol to reconcile the supply.
## Verify the result
Query staked balances and `stuixo` supply through the [gRPC gateway API](/api-reference/grpc-gateway-api) and `ixo.liquidstake.v1beta1` endpoints. Underlying delegations and unbonding entries are visible through the standard `cosmos.staking.v1beta1` queries.
## Troubleshooting
`MsgLiquidStake` fails when the whitelist is empty. Wait for a governance proposal to populate `whitelisted_validators` before staking.
When `MsgSetModulePaused` is `true`, all user-initiated stake/unstake messages are rejected. Check params via the gov query.
`MsgLiquidStake.amount.denom` must equal the chain `bond_denom` (typically `uixo`). `MsgLiquidUnstake.amount.denom` must equal `liquid_bond_denom` (typically `stuixo`).
`MsgUpdateParams`, `MsgUpdateWhitelistedValidators`, `MsgUpdateWeightedRewardsReceivers`, and `MsgSetModulePaused` only accept the gov authority address as `authority`.
## Next steps
Module-level features and types.
Source of truth for liquid staking messages.
Use `stuixo` as a reserve token in bonds.
Submit governance proposals for `liquidstake` updates.
---
# MCP Servers
> Model Context Protocol (MCP) servers for the IXO ecosystem
Model Context Protocol (MCP) servers enable Large Language Model (LLM) agents to safely interact with IXO ecosystem services through specialized server interfaces.
Controlled access to IXO services with robust security mechanisms
Consistent API patterns across all MCP server types
## Architecture Overview
MCP servers act as secure gateways between AI agents and IXO services:
Access on-chain data and transactions via MultiClient SDK
Secure communication within decentralized messaging infrastructure
Integration with user-facing applications like IXO Portal
## Security Model
Each MCP server implements three core security components:
Secure, blockchain-verified agent identities
User Controlled Authorization Network credentials for authentication
Fine-grained permissions for resource access
## Available Servers
Access IXO blockchain functionality:
- Query on-chain data and entity states
- Submit authorized transactions
- Monitor blockchain events
- Access MultiClient SDK features
```typescript Example Query
const client = new BlockchainMCP({
endpoint: "https://mcp.ixo.earth/blockchain"
});
// Query entity state
const entity = await client.getEntity(entityId);
```
Enable secure, decentralized communication:
- Manage encrypted rooms
- Send/receive messages
- Access federated storage
- Handle real-time events
Interact with Impact Oracle services:
- Submit verification data
- Access verification results
- Query measurements
- Participate in consensus
Integrate with user applications:
- Access app state
- Handle user interactions
- Manage UI updates
- Process app events
Provide AI support services:
- Handle user queries
- Access knowledge bases
- Manage support flows
- Coordinate with agents
## Documentation MCP server
These docs run their own MCP server, so an agent can search and read them as tools instead of scraping HTML. It is public, needs no authentication, and is rate limited to 120 requests per minute per IP.
| Property | Value |
| --- | --- |
| Endpoint | `https://docs.ixo.world/mcp` |
| Transport | Streamable HTTP, stateless (JSON-RPC 2.0 over `POST`) |
| Server card | `https://docs.ixo.world/.well-known/mcp.json` |
| Registry document | `https://docs.ixo.world/mcp/server.json` |
| Tools | `search_docs`, `read_page` |
Ranked full-text search across every documentation page and API endpoint.
- `query` (string, required) — natural-language or keyword query
- `limit` (integer, 1–20, default 8) — how many pages to return
Each result carries the page title, canonical URL, Markdown URL, description, and a snippet.
Returns a page as Markdown.
- `url` (string) — a full docs URL, or
- `path` (string) — a site path such as `/build-an-oracle/quickstart`
### Connect
```json
{
"mcpServers": {
"ixo-docs": {
"type": "http",
"url": "https://docs.ixo.world/mcp"
}
}
}
```
A `GET` on the endpoint returns `405` — the transport reserves `GET` for server-initiated streams, which a stateless server does not offer — with a JSON body naming the protocol version, the tools, and the server card. Read the server card first when you want the tool schemas before opening a connection.
Not using MCP? Every page serves Markdown at `.md` or on `Accept: text/markdown`, `/llms.txt` indexes the whole site, and `/llms-full.txt` returns the entire corpus in one request.
## Integration Guides
Connect AI agents to the IXO blockchain
Enable secure agent communication
Access Impact Oracle services
Integrate with IXO applications
For service auth and endpoint ownership, use the [Authentication Matrix](/reference/authentication-matrix) and [Networks and Endpoints](/reference/networks-and-endpoints).
---
# Deploy the IXO USSD gateway
> Fork, configure, and run the open-source IXO USSD gateway to give any GSM phone access to IXO services.
## Before you start
- Node.js 20 or later
- pnpm (`npm install -g pnpm`)
- PostgreSQL 14 or later
- A telecom gateway account (such as [Africa's Talking](https://africastalking.com)) for live USSD testing
- Docker — required only for integration tests
## What this guide does
This guide walks you through forking the IXO USSD gateway, configuring it for your environment, and running it against a telecom gateway. At the end you will have a working USSD server that can accept real or simulated sessions and write results to IXO Protocol.
The gateway is designed to be forked. You define your own USSD flows as XState v5 state machines. The core session management, database layer, and IXO service integrations come pre-built.
## Step 1 — Fork and clone the repository
Fork [ixoworld/ixo-ussd](https://github.com/ixoworld/ixo-ussd) on GitHub, then clone your fork and install dependencies.
```bash
git clone https://github.com/YOUR_USERNAME/ixo-ussd.git
cd ixo-ussd
pnpm install
```
## Step 2 — Configure environment variables
Copy the example environment file and edit it with your values.
```bash
cp env.example .env
```
### Required variables
- **Description:** PostgreSQL connection string
- **Example:** `postgres://user:pass@localhost:5432/ixo-ussd-dev`
- **Description:** 32-character key for PIN encryption
- **Example:** generate with `openssl rand -hex 16`
- **Description:** Logging verbosity
- **Example:** `debug`, `info`, `warn`, `error`
### Optional variables
- **Default:** `development`
- **Description:** Environment mode
- **Default:** `3000`
- **Description:** Server port
- **Default:** `*1234#`
- **Description:** USSD service codes, comma-separated
- **Default:** `https://api.ixo.world`
- **Description:** IXO API endpoint
- **Default:** `https://rpc.ixo.world`
- **Description:** IXO blockchain RPC
To target the IXO testnet instead of mainnet, set `IXO_API_URL` and `IXO_BLOCKCHAIN_URL` to the testnet rows from [Networks and endpoints](/reference/networks-and-endpoints).
## Step 3 — Set up the database and start the server
Create a PostgreSQL database, run migrations, then start the development server.
```bash
# Build the project
pnpm build
# Run database migrations
node dist/src/migrations/run-migrations.js
# Start the development server
pnpm dev
```
The server starts at `http://localhost:3000` by default.
## Step 4 — Connect a telecom gateway
Most telecom USSD gateways send HTTP POST requests with form-encoded or JSON body. The IXO USSD gateway expects JSON at `POST /api/ussd`.
For Africa's Talking, add a callback URL pointing to your server and adapt the request format:
```javascript
// Convert Africa's Talking format and forward to the gateway
app.post('/ussd', async (req, res) => {
const response = await fetch('http://your-server/api/ussd', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId: req.body.sessionId,
serviceCode: req.body.serviceCode,
phoneNumber: req.body.phoneNumber,
text: req.body.text || ''
})
});
const ussdResponse = await response.text();
res.type('text/plain').send(ussdResponse);
});
```
See the [USSD gateway API reference](/api-reference/ussd-api) for the full request and response format.
## Step 5 — Verify the result
Send a test request to confirm the server is accepting sessions.
```bash
curl -X POST http://localhost:3000/api/ussd \
-H "Content-Type: application/json" \
-d '{
"sessionId": "test-session-001",
"serviceCode": "*1234#",
"phoneNumber": "+260971234567",
"text": ""
}'
```
A working server returns a `CON` response with the main menu text.
Check active sessions:
```bash
curl http://localhost:3000/api/ussd/sessions
```
Check server health:
```bash
curl http://localhost:3000/health
```
## Troubleshooting
### Database connection failed
Confirm PostgreSQL is running and the `DATABASE_URL` in `.env` matches your setup.
```bash
# macOS
brew services start postgresql
# Ubuntu/Debian
sudo systemctl start postgresql
```
### Missing PIN encryption key error
Generate a valid key and add it to `.env`:
```bash
echo "PIN_ENCRYPTION_KEY=$(openssl rand -hex 16)" >> .env
```
### IXO account does not exist on chain
New user accounts need a small token balance to cover gas fees before the gateway can write on-chain. Update your IXO feegrant configuration to cover gas for new accounts, or send a small amount to the generated address.
### Migration failed
Ensure the database exists and the user has write permissions, then re-run:
```bash
pnpm build && node dist/src/migrations/run-migrations.js
```
## Next steps
Review request/response formats, session endpoints, and error codes.
Understand the state machine design and integration model.
Build custom USSD flows with reusable architecture patterns.
Use the right mainnet and testnet connection details.
---
# Image Handling
> Learn how to use Cloudflare Image transformations for optimizing and serving images in the IXO client applications
Cloudflare Image transformations enable on-the-fly image optimization, resizing, and format conversion for ixo.world domain. This service improves performance across all IXO client applications.
## Configuration
- **Domain**: ixo.world
- **Status**: Enabled
- **Allowed Origins**: ipfs.gateway.ixo.world
## Usage
Transform images using the following URL format:
```bash
https://ixo.world/cdn-cgi/image/[OPTIONS]/[SOURCE-IMAGE-CID]
```
### Example
```bash
https://ixo.world/cdn-cgi/image/width=300,quality=80/[SOURCE-IMAGE-CID]
```
## Responsive Images
### Using width=auto
Automatically selects the best image size based on device:
```bash
https://ixo.world/cdn-cgi/image/width=auto/[SOURCE-IMAGE-CID]
```
Available sizes:
- 320px (mobile)
- 640px (tablet)
- 960px (laptop)
- 1920px (desktop)
### Using srcset (Recommended)
```html
```
## Parameters
### Resizing
- **Description:** Image width in pixels
- **Example:** `width=300`
- **Description:** Image height in pixels
- **Example:** `height=200`
- **Description:** Resizing method
- **Example:** `fit=cover`
- **Description:** Focus point when cropping
- **Example:** `gravity=auto`
### Fit Options
- `scale-down`: Resize to fit within dimensions, never enlarge
- `contain`: Resize to fit within dimensions, preserving aspect ratio
- `cover`: Resize and crop to fill dimensions
- `crop`: Crop without scaling
- `pad`: Resize and add padding to match dimensions
### Quality and Format
- **Description:** JPEG/WebP quality (1-100)
- **Example:** `quality=80`
- **Description:** Output image format
- **Example:** `format=auto`
- **Description:** Sharpen level (0.0-10.0)
- **Example:** `sharpen=1.0`
### Format Options
- `auto`: Best format based on browser
- `webp`: WebP format
- `avif`: AVIF format
- `json`: Return image metadata
## Limitations
- Maximum image area: 100 megapixels
- Maximum GIF/WebP animation: 50 megapixels
- Maximum file size: 70 MB
- AVIF format: Limited to 1,600 pixels max
- HEIC/HEIF formats: Not supported
## Security
- Only images from allowed origins can be transformed
- SVG files are automatically sanitized
- Source image URLs must be from allowed origins
## Examples
### Thumbnail
```bash
https://ixo.world/cdn-cgi/image/width=150,height=150,fit=cover,gravity=auto/[SOURCE-IMAGE-CID]
```
### Responsive Profile Image
```bash
https://ixo.world/cdn-cgi/image/width=auto,fit=cover,gravity=auto/[SOURCE-IMAGE-CID]
```
### Optimized Banner
```bash
https://ixo.world/cdn-cgi/image/width=1200,height=400,fit=cover,quality=80,format=auto/[SOURCE-IMAGE-CID]
```
### High-DPI Image
```bash
https://ixo.world/cdn-cgi/image/width=600,dpr=2,quality=75/[SOURCE-IMAGE-CID]
```
## Troubleshooting
1. Verify source URL is in allowed origins
2. Check image exists at source URL
3. Ensure transformation parameters are correctly formatted
4. Verify image size is within limits
## Developer Resources
Security best practices
Implementation examples
---
# Impact Hub Registry
> Build and operate registry workflows using the Impact Hub Registry service.
This guide is for the Impact Hub Registry service layer. IXO Protocol module transactions and chain gateway behavior are documented in API reference pages for protocol interfaces.
## Before you start
- Confirm active Registry endpoint in `/reference/networks-and-endpoints`.
- Confirm Registry auth requirements in `/reference/authentication-matrix`.
- Confirm product and SDK naming in `/reference/product-and-sdk-map`.
## What this guide does
You will map a registry workflow to the correct service endpoint group and verify service-level access without mixing protocol gateway calls into the same flow.
## Core registry workflows
- Domain and project records
- Device and household reporting
- Claims and credit lifecycle reporting
- Health and operational checks
## Service vs protocol boundary
- Use `Registry API` for service workflows: `/api-reference/registry-api`
- Use protocol gateways for direct chain queries and transactions:
- `/api-reference/rpc-api`
- `/api-reference/grpc-gateway-api`
## Verify the result
A successful integration should show:
- authenticated responses from Registry service endpoints;
- no reliance on guessed chain RPC literals in service requests.
## Next steps
Integrate registry services and endpoint operations.
Connect registry data to claims and verification workflows.
Select the correct environment endpoints and chain details.
---
# Build an Oracle
> QiForge is the framework for shipping Agentic Oracles — governed AI evaluators and workflow actors with verifiable identity, encrypted per-user storage, and a plugin runtime you wire up in ~30 lines of TypeScript.
An **Agentic Oracle** is a governed AI evaluator and workflow actor. It performs **P-Functions** over verifiable state—turning claims, evidence, models, and context into accountable intelligence: typed facts, predictions, recommendations, attestations, determinations, risk signals, compliance checks, payment triggers, and permitted workflow actions.
Unlike a generic AI agent, an oracle is identity-bound, authority-scoped, evidence-grounded, protocol-governed, and audit-producing. Unlike a blockchain or data oracle, it does not merely relay information; it evaluates what information means, which rules apply, which actions are allowed, and what proof must be left behind.
**QiForge** ships the runtime for that pattern: verifiable identity (IXO entity DID), UCAN delegation, encrypted per-user Matrix storage, and typed plugins ([`@ixo/oracle-runtime`](https://github.com/ixoworld/ixo-oracles-boilerplate/tree/main/packages/oracle-runtime)). Your oracle is a `main.ts` plus the plugins you want. For the full concept model, see [Agentic Oracles](/articles/agentic-oracles).
## What QiForge does for you
```ts
import { createOracleApp } from '@ixo/oracle-runtime';
import { MyPlugin } from './plugins/my-plugin/my-plugin.plugin.js';
const app = await createOracleApp({
config: { name: 'My Oracle', org: 'Acme' },
plugins: [new MyPlugin()],
});
await app.listen();
```
That's a working oracle. One call to [`createOracleApp`](/build-an-oracle/develop/create-oracle-app) and the framework gives you:
HTTP + WebSocket, request validation, OpenAPI at `/docs`, CORS, throttling, graceful shutdown — already configured.
Every request is authenticated by a user-signed UCAN invocation (`Authorization: Bearer …` + `X-Auth-Type: ucan`). The oracle has an IXO entity DID; users delegate scoped capabilities it can act on downstream.
Each user gets a Matrix room; conversation history + agent checkpoints are E2E-encrypted and synced automatically.
Per-request agent build, dynamic tool loading via meta-tools, four always-on middleware (capability gating, tool validation, repetition guard, retry).
`memory`, `skills`, `sandbox`, `portal`, `firecrawl`, `composio`, `editor`, `agui`, `slack`, `credits`, `user-preferences`, and more — toggle them via the `features` map.
9 typed hooks let your plugin contribute tools, sub-agents, middleware, HTTP routes, shared state, and env vars. Boot-time and per-request flavours.
You ship the green box on top. The framework handles the rest.
## Where do you want to start?
10-minute quickstart → recipes → ship. Code-first the whole way.
The mental model, the runtime layers, plugins vs skills.
One dense page with every signature, env var, and template you need.
## Quick map
Task-by-task recipes. `createOracleApp`, plugin recipes, testing, deploy.
Concepts. Read when you want depth — never required to ship.
Every option, every env var, every bundled plugin in a flat table.
`qiforge-cli` commands — `new`, `plugin new`, `create-entity`, `--chat`.
## What people build with this
Climate oracles that analyse emissions data, carbon DAO assistants, supply-chain MRV agents.
AG-UI copilots driving a portal, filling forms, navigating the browser. Bundled `agui` + `portal` plugins.
Agents calling Gmail, GitHub, Linear, Slack, Notion through the bundled `composio` plugin.
Discover + execute IXO skill capsules inside a per-user Linux sandbox. Bundled `skills` + `sandbox` plugins.
## Don't skip these
1. [Quickstart](/build-an-oracle/quickstart) — get a working oracle in front of you before reading anything else.
2. [`createOracleApp` options](/build-an-oracle/develop/create-oracle-app) — every option you can pass.
3. [Write a plugin](/build-an-oracle/develop/write-a-plugin) — the canonical end-to-end recipe.
---
# Quickstart
> Scaffold a working oracle, boot it, and watch the agent call a plugin tool — in about ten minutes.
## What you'll have at the end
```text
You: what's the weather in Berlin?
Oracle: (calls list_capabilities → load_capability(weather) → get_current_weather)
Berlin is currently 14°C with light rain.
```
A live oracle on `localhost:3000` with:
- The 16 bundled plugins resolved at boot.
- A custom Weather plugin loading on demand.
- A `GET /weather/now?city=X` public HTTP route.
- A streaming chat endpoint over SSE.
## Step 0 — prerequisites
- **Node.js 22+**
- **pnpm** — `npm install -g pnpm`
- **The IXO Mobile App** *or* a 12/24-word mnemonic (for offline auth)
- **An OpenRouter API key** — get one at openrouter.ai
## Step 1 — install the CLI
```sh
pnpm add -g qiforge-cli # npm package name
pnpm approve-builds -g # approve protobufjs when prompted
qiforge-cli --help # the installed binary is `qiforge-cli`
```
If it prints the help text, the CLI is ready.
## Step 2 — scaffold a project
```sh
qiforge-cli new my-oracle
```
The CLI walks through:
1. **Auth** — pick SignX (QR code) or offline (mnemonic). Offline is faster for local dev.
2. **Network** — pick `devnet` for development.
3. **Oracle profile** — name, description, price, model. Defaults are fine.
4. **Entity creation** — the CLI writes a transaction to register your oracle's entity DID on chain. Confirm when prompted (SignX needs the mobile app; offline signs locally).
5. **Matrix bot** — provisioned automatically.
Entity creation registers the oracle's API URL as `http://localhost:4000` by default, but the runtime boots on `PORT=3000`. To make your oracle reachable at its registered URL, either set `PORT=4000` in `.env` to match, or change the registered URL later with `qiforge-cli update-oracle-api-url`.
The result is a plugin-runtime-shaped project:
```text
my-oracle/
├── src/
│ ├── main.ts # calls createOracleApp({ config, plugins })
│ ├── config.ts # OracleConfig
│ └── plugins/ # your plugins go here
├── .claude/
│ └── skills/qiforge-oracle/ # Claude Code skill (project-local)
├── .env # Matrix creds, ORACLE_*, mnemonic
├── oracle.config.json # name, model, prompt, DID
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── CLAUDE.md # bootstrapping for Claude Code
```
The scaffold drops a **Claude Code skill** at `.claude/skills/qiforge-oracle/`. Open the project in Claude Code (or any agent that reads `.claude/skills/`) and the skill auto-loads — your AI helper now has dense, project-local references for adding plugins, adding tools, wiring env, and writing tests with `createTestRuntime`. Nothing to install separately.
**Cloning instead of scaffolding** — to study a finished plugin-based oracle, clone the boilerplate and run the example:
```sh
git clone https://github.com/ixoworld/qiforge
cd qiforge/apps/qiforge-example
cp .env.example .env
```
Then follow Step 3 onward against `apps/qiforge-example/`.
## Step 3 — fill in `.env`
Open your project's `.env` (`my-oracle/.env` from `qiforge-cli new`, or `apps/qiforge-example/.env` if you cloned the boilerplate). The CLI populated identity vars; you need to add the LLM key and any plugin vars:
```diff
- OPEN_ROUTER_API_KEY=
+ OPEN_ROUTER_API_KEY=sk-or-v1-...
# Optional — enables specific plugins:
+ MEMORY_MCP_URL=https://memory-mcp.your-deployment.example/sse
+ MEMORY_ENGINE_URL=https://memory-engine.your-deployment.example
+ FIRECRAWL_MCP_URL=https://firecrawl-mcp.your-deployment.example/sse
+ SANDBOX_MCP_URL=https://sandbox-mcp.your-deployment.example/sse
+ WEATHER_DEFAULT_UNITS=celsius
```
Plugins whose env vars you leave blank simply don't load — they're skipped with a `[boot] excluded: ()` line.
See [Environment variables reference](/build-an-oracle/reference/environment-variables) for the complete list.
## Step 4 — install + boot
```sh
cd my-oracle # or qiforge/apps/qiforge-example
pnpm install
pnpm dev
```
You should see something like:
```text
[Nest] Starting Nest application...
[Nest] OraclePlugin {memory} loaded (always)
[Nest] OraclePlugin {domain-indexer} loaded (always)
[Nest] OraclePlugin {editor} loaded (on-demand)
[Nest] OraclePlugin {user-preferences} loaded (always)
[Nest] OraclePlugin {weather} loaded (on-demand)
[boot] excluded plugins: composio (COMPOSIO_API_KEY), slack (SLACK_BOT_OAUTH_TOKEN), tasks (REDIS_URL)
Oracle 'QiForge Example Oracle' (runtime v0.X.Y) listening on :3000
[plugin] matrix pending → loaded
```
The excluded plugins are normal — they need env vars you didn't set. To enable one, fill its env vars and restart.
## Step 5 — hit a public plugin endpoint
The Weather plugin exposes `GET /weather/now`. It's marked auth-excluded via `getAuthExcludedRoutes()`, so no UCAN header needed:
```sh
curl 'http://localhost:3000/weather/now?city=Berlin'
```
You should get back JSON:
```json
{
"ok": true,
"city": "Berlin",
"temp_c": 14.2,
"units": "celsius",
"conditions": "Slight rain",
"latitude": 52.52,
"longitude": 13.405
}
```
This proves: the plugin loaded, its Nest module mounted, the auth exclusion worked, and the upstream call to Open-Meteo succeeded.
## Step 6 — send a chat message
For interactive chat, use the CLI's streaming TUI:
```sh
qiforge-cli --chat
```
Then type:
```text
what's the weather in Berlin?
```
The agent walks this path:
1. **`list_capabilities`** — sees `weather` is `on-demand`, not loaded yet.
2. **`load_capability({ names: ['weather'] })`** — gets the manifest back with `whenToUse` and the tool list. `loadedPlugins` state field now contains `'weather'`.
3. **`get_current_weather({ city: 'Berlin' })`** — the actual tool fires.
4. **Final response** — natural-language summary of the result.
Watch the server logs at the same time — you'll see the weather middleware print `model call started` / `model call complete (Xms)` for each LLM step.
## What you just verified
| You saw | What it proves |
| --- | --- |
| Boot log listing plugins | Loader + topo sort + manifest validation worked. |
| `excluded plugins:` line with reasons | Env-driven opt-in (`autoDetect`) is wired correctly. |
| `GET /weather/now` returning JSON | Plugin Nest modules mount + auth exclusions apply. |
| Agent calling `load_capability` then `get_current_weather` | Dynamic discovery and `loadedPlugins` state work end-to-end. |
| Middleware logs around each model call | `getMiddlewares` hooks are firing. |
## Where to go next
Task-by-task recipes — `createOracleApp`, plugin recipes, test, deploy.
Recreate the Weather plugin from scratch.
Every option you can pass — config, plugins, features, hooks.
The 16 plugins shipped with the runtime.
Dense one-pager with every signature inlined.
Optional. The mental model, runtime layers, plugins vs skills.
## Troubleshooting
| Symptom | Likely fix |
| --- | --- |
| `[boot-error] Plugin '' env validation failed for ''` | Either set the env var or disable the plugin with `features: { name: false }`. |
| Boot hangs after `Nest application started` | Matrix init is still pending. Check `MATRIX_BASE_URL` and `MATRIX_ORACLE_ADMIN_*` env vars. |
| All authenticated requests return 401 | UCAN signing mnemonic not loaded — see [Identity and auth](/build-an-oracle/develop/identity-and-auth). |
| The agent doesn't call my plugin's tool | Check it's `loaded` in `app.plugins.status()`. If it's `on-demand`, prompt the agent more clearly so it calls `load_capability`. |
Full coverage: [Troubleshooting](/build-an-oracle/troubleshooting).
---
# For AI agents
> Dense single-page reference. Every signature, every option, every env var, plus a copy-pasteable plugin template. Built so an AI agent can scaffold a QiForge oracle from this page alone.
This page is for AI tools (Cursor, Claude Code, Codex, …) scaffolding a QiForge oracle. Humans should read [Quickstart](/build-an-oracle/quickstart) and [Build a plugin](/build-an-oracle/develop/write-a-plugin) instead.
If you are an AI agent: read top-to-bottom once. Every signature you need is inlined. Source paths cite the canonical files on the QiForge runtime ([`packages/oracle-runtime`](https://github.com/ixoworld/ixo-oracles-boilerplate/tree/main/packages/oracle-runtime)) and the reference oracle ([`apps/qiforge-example`](https://github.com/ixoworld/ixo-oracles-boilerplate/tree/main/apps/qiforge-example)).
**If you are working inside a scaffolded oracle project** (one created with `qiforge-cli new`), there is already a Claude Code skill at `.claude/skills/qiforge-oracle/SKILL.md` with project-local guidance: adding plugins, adding tools, wiring env, writing tests with `createTestRuntime`. Load it first — it carries denser, scenario-specific references than this page.
## TL;DR — what you produce
A QiForge oracle is **one `main.ts`** that calls `createOracleApp({ config, plugins, … })` plus **one folder per plugin** under `src/plugins//`. The runtime handles HTTP, auth, the agent graph, the checkpointer, Matrix, and bundles 16 plugins by default. You ship glue code, not infrastructure.
## main.ts shape
```ts
import { createOracleApp } from '@ixo/oracle-runtime';
import { MyPlugin } from './plugins/my-plugin/my-plugin.plugin.js';
const app = await createOracleApp({
config: {
name: 'My Oracle',
org: 'Acme',
description: 'One-line pitch the system prompt will use.',
},
plugins: [new MyPlugin()],
features: {
composio: false, // opt out of bundled composio
firecrawl: 'auto', // (default) — autoDetect from env
},
});
await app.listen(Number(process.env.PORT ?? 3000));
```
Source: [`apps/qiforge-example/src/main.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/main.ts).
## createOracleApp — full options
[`packages/oracle-runtime/src/bootstrap/create-oracle-app.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/create-oracle-app.ts)
```ts
interface CreateOracleAppOptions {
config: OracleConfig; // required
features?: Partial>;
plugins?: OraclePlugin[];
nestModules?: Array;
authExcludedRoutes?: AuthExcludedRoute[];
bundledPlugins?: OraclePlugin[]; // test override
env?: NodeJS.ProcessEnv; // test override
skipMatrixInit?: boolean; // test override
skipGracefulShutdown?: boolean; // test override
logger?: PluginLogger;
hooks?: MainAgentHooks;
}
type FeatureToggle = boolean | 'auto';
interface OracleConfig {
name: string;
org?: string;
description?: string;
prompt?: {
opening?: string; // full replacement of the identity preamble
communicationStyle?: string; // appended to operating principles (tone/voice)
capabilities?: string; // author elevator pitch above the capability list
customInstructions?: string; // verbatim `## Custom Instructions` block
};
}
interface AuthExcludedRoute { path: string; method?: RequestMethod }
interface MainAgentHooks {
checkpointerForUser?: (userDid: string) => Promise;
resolveModel?: (role: ModelRole, params?: ChatOpenAIFields) => BaseChatModel;
getRoomTitle?: (roomId: string) => Promise;
safetyModel?: BaseChatModel;
validationSkipToolNames?: string[];
operationalMode?: string;
editorSection?: string;
composioContext?: string;
userSecretsContext?: string;
degradedServicesBlock?: string;
}
```
`createOracleApp` returns an `OracleApp`:
```ts
interface OracleApp {
getNestApp(): INestApplication;
ambient: AmbientServices;
plugins: { status(): { loaded: string[]; excluded: { plugin: string; reason: string }[]; softDepGaps: { plugin: string; missing: string }[] } };
beforeListen(fn: (nestApp: INestApplication) => Promise | void): void;
onError(handler: (err: Error, source: string) => void): void;
onPluginStatusChange(handler: (event: { plugin: string; from: 'pending'|'loaded'|'failed'; to: 'pending'|'loaded'|'failed'; reason?: string }) => void): void;
listen(port?: number): Promise;
}
```
## OraclePlugin — all 9 hooks
[`packages/oracle-runtime/src/plugin-api/oracle-plugin.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/oracle-plugin.ts)
```ts
abstract class OraclePlugin {
abstract readonly name: string; // kebab-case
abstract readonly version: string;
abstract readonly manifest: PluginManifest;
readonly dependsOn?: string[]; // hard deps — boot fails if missing
readonly softDependsOn?: string[]; // soft deps — branches on availability
readonly configSchema?: z.ZodObject; // merged into runtime env schema
autoDetect?(env: NodeJS.ProcessEnv): boolean; // skip plugin when false
readonly autoDetectHint?: string; // surfaced in boot errors
getTools?(ctx: PluginContext): PluginTool[] | Promise;
getSubAgents?(ctx: PluginContext): PluginSubAgent[];
getRequestTools?(rtCtx: RuntimeContext): PluginTool[] | Promise;
getRequestSubAgents?(rtCtx: RuntimeContext): PluginSubAgent[] | Promise;
getMiddlewares?(ctx: PluginContext): AgentMiddleware[];
getSharedState?(): Record unknown>;
getNestModules?(ctx?: PluginContext): Array;
getAuthExcludedRoutes?(): AuthExcludedRoute[];
}
```
## PluginManifest
```ts
interface PluginManifest {
title: string; // human name
summary: string; // one-line, shown in Tier-1 prompt for `always`
whenToUse: string[]; // triggers
whenNotToUse?: string[]; // anti-patterns
examples?: { user: string; thought?: string; tool: string; args?: Record }[];
tags?: string[];
category?: 'data' | 'communication' | 'automation' | 'memory' | 'integration' | 'ui' | 'auth' | 'observability' | 'core';
visibility?: 'always' | 'on-demand' | 'silent'; // default: 'on-demand'
stability?: 'stable' | 'beta' | 'experimental';
}
```
## PluginTool, PluginSubAgent
```ts
interface PluginTool {
name: string;
description: string;
schema: z.ZodType;
handler: (args: unknown, ctx: RuntimeContext) => Promise;
visibility?: 'always' | 'on-demand' | 'silent'; // override plugin default per tool
}
interface PluginSubAgent {
name: string; // e.g. 'call_memory_agent'
description: string;
systemPrompt: string | ((ctx: PluginContext) => string);
tools: PluginTool[] | ((ctx: PluginContext) => PluginTool[]);
model?: ModelRole; // default 'subagent'
middlewares?: AgentMiddleware[];
forwardTools?: boolean | string[]; // forward tool calls to main message stream
onComplete?: (result: string, ctx: RuntimeContext) => Promise;
}
```
## Visibility rules
| Visibility | Bound at boot? | In Tier-1 prompt? | Discoverable via `list_capabilities`? |
| --- | --- | --- | --- |
| `always` | yes | yes | yes |
| `on-demand` (default) | no — until `load_capability({ names })` | no | yes |
| `silent` | yes | no | no |
`silent` means *not advertised* — the tools are still bound and the agent can call them; they're just kept out of the Tier-1 prompt and `list_capabilities`. It is not a security boundary.
## Bundled plugins (15)
From [`packages/oracle-runtime/src/plugins/index.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/index.ts):
| Name | Visibility | Default | Required env |
| --- | --- | --- | --- |
| `memory` | always | on | `MEMORY_MCP_URL`, `MEMORY_ENGINE_URL` |
| `portal` | on-demand | on | — |
| `firecrawl` | on-demand | auto-detect | `FIRECRAWL_MCP_URL` |
| `domain-indexer` | always | on | — |
| `composio` | on-demand | auto-detect | `COMPOSIO_API_KEY` |
| `sandbox` | always | auto-detect | `SANDBOX_MCP_URL` |
| `skills` | always | on (needs `sandbox`) | — |
| `editor` | on-demand | on (needs Matrix) | — |
| `agui` | on-demand | on | — |
| `slack` | silent | auto-detect | `SLACK_BOT_OAUTH_TOKEN` |
| `tasks` | on-demand | auto-detect | `REDIS_URL` |
| `credits` | silent | on unless `DISABLE_CREDITS=true` | — |
| `calls` | silent | on (placeholder stub — no tools) | — |
| `user-preferences` | always | on | — |
| `matrix-group-chats` | on-demand | on (opt out via `features`) | — (optional `CHANNEL_MEMORY_*`) |
Toggle via `features` in `createOracleApp`: `true` forces on, `false` forces off, `'auto'` runs `autoDetect`.
Per-plugin reference pages: [`/build-an-oracle/reference/bundled-plugins/overview`](/build-an-oracle/reference/bundled-plugins/overview).
## Core (Tier-0) env vars
From [`packages/oracle-runtime/src/config/base-env-schema.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/config/base-env-schema.ts):
These are the exact names the runtime validates — set them character-for-character or the boot-time Zod check fails.
| Var | Required | Description |
| --- | --- | --- |
| `ORACLE_NAME` | yes | Oracle display name. |
| `NETWORK` | yes | `mainnet` \| `testnet` \| `devnet`. |
| `ORACLE_DID` | yes | The oracle's own DID (`did:ixo:ixo1...`); the signer identity for downstream invocations. |
| `ORACLE_ENTITY_DID` | yes | The oracle's on-chain entity DID (`did:ixo:entity:...`). |
| `SECP_MNEMONIC` | yes | Wallet mnemonic used to sign UCAN invocations to downstream services. |
| `RPC_URL` | yes | IXO chain RPC endpoint. |
| `BLOCKSYNC_GRAPHQL_URL` | yes | Blocksync GraphQL endpoint (UCAN validation reads it). |
| `MATRIX_BASE_URL` | yes | Matrix homeserver URL. |
| `MATRIX_RECOVERY_PHRASE` | yes | Recovery phrase for the oracle's Matrix encryption. |
| `MATRIX_ORACLE_ADMIN_USER_ID` | yes | Matrix user ID the bot logs in as. |
| `MATRIX_ORACLE_ADMIN_PASSWORD` | yes | Matrix bot password. |
| `MATRIX_ORACLE_ADMIN_ACCESS_TOKEN` | yes | Matrix bot access token. |
| `MATRIX_ACCOUNT_ROOM_ID` | yes | Oracle's Matrix account room (holds signing key + secrets). |
| `MATRIX_VALUE_PIN` | yes | PIN for the oracle's Matrix value store / vault. |
| `SQLITE_DATABASE_PATH` | yes | Path for the per-user SQLite checkpointer DB. |
| `LLM_PROVIDER` | optional | `openrouter` (default) \| `nebius`. Selects the per-role model map. |
| `OPEN_ROUTER_API_KEY` | conditional | Required when `LLM_PROVIDER=openrouter` (the default). |
| `NEBIUS_API_KEY` | conditional | Required when `LLM_PROVIDER=nebius`. |
| `OPENAI_API_KEY` | optional | Only if a plugin needs OpenAI directly. Not required for the agent. |
| `MATRIX_STORE_PATH` | optional | Matrix store dir. Defaults to `./matrix-storage`. |
| `UCAN_AUTH_MAX_TTL_SECONDS` | optional | Max accepted lifetime of a user auth invocation. Default `900`. |
| `UCAN_REAUTH_PROMPT_THROTTLE_SECONDS` | optional | Min gap between Matrix re-auth nudges. Default `21600`. |
| `ORACLE_SECRETS` | optional | Operator secrets as `KEY=val,KEY2=val2`, surfaced as `x-os-*` headers. |
| `CORS_ORIGIN` | optional | Allowed origins. Wildcard `*` is the default. |
| `PORT` | optional | HTTP port. Defaults to `3000`. |
| `NODE_ENV` | optional | `development` (default) \| `production` \| `test`. |
| `LANGSMITH_TRACING` | optional | `true` to enable LangSmith tracing. |
| `LANGSMITH_API_KEY` | optional | LangSmith API key. |
| `LANGSMITH_PROJECT` | optional | LangSmith project name. |
| `LANGSMITH_ENDPOINT` | optional | LangSmith endpoint override. |
There is no `ANTHROPIC_API_KEY` — the agent's model id is fixed per role in the provider model map; switch the provider with `LLM_PROVIDER` + its key, or override the main model via the `resolveModel` hook. Plugin-specific env vars (`MEMORY_MCP_URL`, `FIRECRAWL_MCP_URL`, `SANDBOX_MCP_URL`, `COMPOSIO_API_KEY`, `SLACK_BOT_OAUTH_TOKEN`, `REDIS_URL`, …) merge in via each plugin's `configSchema`.
## Copy-pasteable plugin template
```ts
// src/plugins/example/example.plugin.ts
import { OraclePlugin, type PluginManifest, type PluginTool, type RuntimeContext } from '@ixo/oracle-runtime';
import { z } from 'zod';
const manifest: PluginManifest = {
title: 'Example',
summary: 'One-line description shown in the Tier-1 prompt block.',
whenToUse: ['User asks about example things'],
whenNotToUse: ['User asks about unrelated things'],
visibility: 'on-demand',
stability: 'experimental',
};
export class ExamplePlugin extends OraclePlugin {
readonly name = 'example';
readonly version = '0.1.0';
readonly manifest = manifest;
readonly configSchema = z.object({
EXAMPLE_API_KEY: z.string().min(1),
});
autoDetect(env: NodeJS.ProcessEnv): boolean {
return Boolean(env.EXAMPLE_API_KEY);
}
readonly autoDetectHint = 'EXAMPLE_API_KEY';
override getTools(): PluginTool[] {
return [
{
name: 'get_example',
description: 'Fetch an example.',
schema: z.object({ query: z.string() }),
handler: async (args, rtCtx: RuntimeContext) => {
const { query } = args as { query: string };
rtCtx.logger.log(`Example tool called with ${query}`);
return { result: `echo: ${query}` };
},
},
];
}
}
```
Then in `main.ts`:
```ts
import { ExamplePlugin } from './plugins/example/example.plugin.js';
const app = await createOracleApp({
config: { name: 'My Oracle' },
plugins: [new ExamplePlugin()],
});
await app.listen(Number(process.env.PORT ?? 3000));
```
## Where to dig deeper
- [`/build-an-oracle/reference/createoracleapp`](/build-an-oracle/reference/createoracleapp) — every option in detail.
- [`/build-an-oracle/reference/plugin-api`](/build-an-oracle/reference/plugin-api) — hook-by-hook reference.
- [`/build-an-oracle/develop/plugin-recipes/add-a-tool`](/build-an-oracle/develop/plugin-recipes/add-a-tool) — one recipe per capability.
- [`/build-an-oracle/reference/environment-variables`](/build-an-oracle/reference/environment-variables) — every env var grouped.
- [`/build-an-oracle/reference/bundled-plugins/overview`](/build-an-oracle/reference/bundled-plugins/overview) — every bundled plugin.
- Canonical reference oracle: [`apps/qiforge-example/src/main.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/main.ts) and the Weather plugin under [`apps/qiforge-example/src/plugins/weather/`](https://github.com/ixoworld/ixo-oracles-boilerplate/tree/main/apps/qiforge-example/src/plugins/weather).
---
# Build track — start here
> The order to read the Build track. Recipes are code-first; concepts live in the Deep dive track and are always optional.
The Build track is task-oriented. Every page answers "how do I do X" with a copy-pasteable code sample and a link to the canonical file in the [repo](https://github.com/ixoworld/ixo-oracles-boilerplate). Concept material is moved out to the [Deep dive](/build-an-oracle/understand/what-is-qiforge) — read it when you want depth, skip it when you want to ship.
## The path
Follow the [Quickstart](/build-an-oracle/quickstart) — `qiforge-cli new`, env, `pnpm dev`, first message. Ten minutes.
[Create your oracle app](/build-an-oracle/develop/create-oracle-app) — what `main.ts` looks like, what every option does.
[Enable bundled plugins](/build-an-oracle/develop/enable-bundled-plugins) — the `features` map, auto-detect, force-on, force-off.
[Write a plugin](/build-an-oracle/develop/write-a-plugin) — centerpiece end-to-end recipe using the Weather plugin as the worked example.
[Plugin recipes](/build-an-oracle/develop/plugin-recipes/add-a-tool) — atomic: add a tool, add a sub-agent, add a middleware, add HTTP endpoints, share state, declare dependencies, set visibility, add config + env.
[Test your oracle](/build-an-oracle/develop/test-your-oracle) — vitest setup, integration tests against a real boot.
[Identity and auth](/build-an-oracle/develop/identity-and-auth), then [observability](/build-an-oracle/develop/observability) — LangSmith env vars, plugin status events.
[Deploy](/build-an-oracle/develop/deploy) — Dockerfile, env, persistent volumes, health probes.
## All recipes at a glance
`PluginTool`, boot-time vs per-request.
Nest agents with their own prompt + tools.
`beforeModel` / `afterModel` / `wrapModelCall` hooks.
Plugin-owned Nest controllers + auth-excluded routes.
Zod `configSchema`, `PLUGINPREFIX_*` convention.
`getSharedState` — read-only producer/consumer pattern.
`dependsOn` (hard) vs `softDependsOn` (soft) + topological order.
`always` / `on-demand` / `silent` + per-tool overrides.
## What lives outside this track
- **Concepts:** [Deep dive](/build-an-oracle/understand/what-is-qiforge) — mental model, runtime layers, plugins-vs-skills. Optional.
- **Reference:** [Reference](/build-an-oracle/reference/createoracleapp) — every option, every env var, every bundled plugin in tables.
- **For AI agents:** [Dense one-pager](/build-an-oracle/for-ai-agents) — every signature inlined, copy-pasteable.
- **Troubleshooting:** [Common boot + runtime errors](/build-an-oracle/troubleshooting).
---
# Create your oracle app
> createOracleApp is the entry point of every QiForge oracle. This recipe writes a complete main.ts and enumerates every option you can pass.
A QiForge oracle is one `main.ts` that calls `createOracleApp`. The runtime boots a NestJS app, loads bundled + your plugins, validates env, builds the agent graph, and starts HTTP when you call `listen()`.
Reference implementation: [`apps/qiforge-example/src/main.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/main.ts). Source: [`packages/oracle-runtime/src/bootstrap/create-oracle-app.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/create-oracle-app.ts).
## Minimal main.ts
```ts
import 'dotenv/config';
import { createOracleApp } from '@ixo/oracle-runtime';
const app = await createOracleApp({
config: { name: 'My Oracle', org: 'Acme' },
});
await app.listen();
```
That's a working oracle — every bundled plugin runs its `autoDetect(env)` and the ones whose env is present load.
## The recipe
Put `OracleConfig` in its own file so tests can import it without booting the runtime.
```ts
// src/config.ts
import type { OracleConfig } from '@ixo/oracle-runtime';
export const config: OracleConfig = {
name: 'My Oracle', // required
org: 'My Org', // optional
description: 'What this oracle is for', // optional — appears in the system prompt
prompt: { // optional — every field optional
opening: 'You are My Oracle. …',
communicationStyle: '- Be terse …',
capabilities: 'I can …',
},
};
```
`entityDid` is sourced from the `ORACLE_ENTITY_DID` env var — never put it in `config`. The `prompt` block is composed into the system prompt; absent fields fall back to runtime defaults. Source: [`plugin-api/types.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/types.ts) (search `OracleConfig`).
Bundled plugins flow in automatically from `BUNDLED_PLUGINS` — each runs its own `autoDetect(env)`. Only list the ones you wrote yourself or bundled plugins that need constructor args:
```ts
import { createOracleApp, EditorPlugin } from '@ixo/oracle-runtime';
import { WeatherPlugin } from './plugins/weather/weather.plugin.js';
const app = await createOracleApp({
config,
plugins: [
new EditorPlugin({ matrixClient }), // bundled, needs Matrix client
new WeatherPlugin(), // yours
],
});
```
The loader dedupes by `name`, so an explicit instance overrides the bundled default of the same name. Source: [`bootstrap/plugin-loader.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/plugin-loader.ts).
Override `autoDetect`:
```ts
const app = await createOracleApp({
config,
features: {
composio: false, // force off
firecrawl: true, // force on, even if env is missing (will fail env validation if so)
'domain-indexer': 'auto', // default — runs autoDetect(env)
},
});
```
| Value | Meaning |
| --- | --- |
| `true` | Force on. Skip `autoDetect`. |
| `false` | Force off. Skip even if `autoDetect` would say yes. |
| `'auto'` | Default. Run `autoDetect(env)`. |
Omitted keys are treated as `'auto'`. Recipe: [Enable bundled plugins](/build-an-oracle/develop/enable-bundled-plugins).
Feature keys are the plugin's kebab-case `name` — `'domain-indexer'`, `'user-preferences'`, `'matrix-group-chats'`. A camelCase key like `domainIndexer` type-checks (it falls into the open `string` arm) but is a silent no-op: the plugin keeps its default behaviour. Always quote the exact `name`.
Custom HTTP endpoints, event consumers, admin dashboards — anything that doesn't fit the plugin model goes in `nestModules`. Each gets full DI access to runtime services (Sessions, Messages, Secrets, UCAN, Auth, …).
```ts
import { Controller, Get, Module } from '@nestjs/common';
@Controller('version')
class VersionController {
@Get()
get() { return { name: 'My Oracle', version: '1.0.0' }; }
}
@Module({ controllers: [VersionController] })
class VersionModule {}
const app = await createOracleApp({
config,
nestModules: [VersionModule],
});
```
Every route defaults to going through `AuthHeaderMiddleware` (requires `x-ucan-delegation`). Opt routes out via `authExcludedRoutes`:
```ts
import { RequestMethod } from '@nestjs/common';
import type { AuthExcludedRoute } from '@ixo/oracle-runtime';
const routes: AuthExcludedRoute[] = [
{ path: 'version', method: RequestMethod.GET },
];
const app = await createOracleApp({
config,
nestModules: [VersionModule],
authExcludedRoutes: routes,
});
```
Symmetric with each plugin's `getAuthExcludedRoutes()`. Both merge onto the runtime's built-ins (`/health`, `/docs`). Recipe for the plugin side: [Add HTTP endpoints](/build-an-oracle/develop/plugin-recipes/add-http-endpoints).
`hooks` overrides the agent-build defaults. Every field is optional:
```ts
import type { MainAgentHooks } from '@ixo/oracle-runtime';
const hooks: MainAgentHooks = {
checkpointerForUser: async (did) => myCheckpointer.for(did),
resolveModel: (role, params) => myModelFactory.get(role, params),
getRoomTitle: async (roomId) => myRoomTitles.get(roomId),
safetyModel: mySafetyModel,
validationSkipToolNames: ['some_streaming_tool'],
operationalMode: 'You operate in production mode …',
editorSection: '## Editor\n\n…',
composioContext: '## Composio\n\n…',
userSecretsContext: '## User secrets\n\n…',
degradedServicesBlock: '## Degraded services\n\n…',
};
const app = await createOracleApp({ config, hooks });
```
| Hook | Default | What it overrides |
| --- | --- | --- |
| `checkpointerForUser(did)` | per-user SQLite synced to Matrix | Swap in your own `BaseCheckpointSaver` |
| `resolveModel(role, params)` | `ambient.llm.get(role)` | LLM resolver |
| `getRoomTitle(roomId)` | undefined | Page-context middleware lookup |
| `safetyModel` | safety-guardrail default | Cheap classifier for the safety middleware |
| `validationSkipToolNames` | `[]` | Tool names whose `ToolMessage` is stripped between turns |
| `operationalMode` | runtime default | Operating-mode prompt block |
| `editorSection` | populated by editor plugin | Editor prompt block |
| `composioContext` | populated by composio plugin | Composio guidance block |
| `userSecretsContext` | empty | Per-key secret bullet list |
| `degradedServicesBlock` | empty | Degraded-services notice appended to system prompt |
Source: [`graph/main-agent-types.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/graph/main-agent-types.ts) (search `MainAgentHooks`).
**Change the AI model.** The most common reason to set `hooks` is to swap the model. The runtime resolves the main agent's model via `resolveModel('main')`; override it and spread `params` to keep the provider's fallback models and latency sort:
```ts
import { createOracleApp, getProviderChatModel } from '@ixo/oracle-runtime';
const app = await createOracleApp({
config,
hooks: {
// role is 'main' | 'subagent' | 'utility' | (string & {}).
resolveModel: (role, params) =>
getProviderChatModel(role, {
...params,
...(role === 'main' && { model: 'google/gemini-3.1-flash-lite' }),
}),
},
});
```
The provider (`LLM_PROVIDER` = `openrouter` default | `nebius`) and per-role default model ids are otherwise fixed in the runtime — there is no env var to change the main model id without this hook. You can also return any LangChain `BaseChatModel` (e.g. `new ChatOpenAI({ model: 'gpt-4o' })`), but doing so drops the OpenRouter fallback/latency wiring.
Run setup that must complete before HTTP starts accepting:
```ts
const app = await createOracleApp({ config });
app.beforeListen(async (nestApp) => {
await myWarmupCache(nestApp);
});
await app.listen();
```
Hooks run sequentially in registration order.
```ts
app.onPluginStatusChange((event) => {
// { plugin, from: 'pending'|'loaded'|'failed', to: ..., reason? }
logger.log(`[plugin] ${event.plugin} ${event.from} → ${event.to}`);
});
app.onError((err, source) => {
logger.error(`[runtime] ${source}: ${err.message}`);
});
const status = app.plugins.status();
logger.log(`[boot] loaded: ${status.loaded.join(', ')}`);
```
`onPluginStatusChange` fires when Matrix transitions `pending → loaded` (or `failed`). `onError` catches Matrix init + lifecycle errors. `plugins.status()` returns a snapshot of loaded, excluded, and soft-dep-gap plugins.
```ts
await app.listen(); // uses the PORT env var (defaults to 3000)
await app.listen(8080); // explicit port — overrides PORT
```
Calling `listen()` twice throws. Default port is **3000** — set the `PORT` env var or pass a number to `listen()` to override.
## All options at a glance
```ts
interface CreateOracleAppOptions {
config: OracleConfig; // required
features?: Partial>;
plugins?: OraclePlugin[];
nestModules?: Array;
authExcludedRoutes?: AuthExcludedRoute[];
logger?: PluginLogger;
hooks?: MainAgentHooks;
// Test-only — do not use in production
bundledPlugins?: OraclePlugin[];
env?: NodeJS.ProcessEnv;
skipMatrixInit?: boolean;
skipGracefulShutdown?: boolean;
}
```
`bundledPlugins`, `env`, `skipMatrixInit`, and `skipGracefulShutdown` exist for the test harness. Don't use them in production code — integration tests must boot the same way prod does.
## Full example
```ts
import 'dotenv/config';
import { createOracleApp, EditorPlugin } from '@ixo/oracle-runtime';
import { Controller, Get, Logger, Module, RequestMethod } from '@nestjs/common';
import * as sdk from 'matrix-js-sdk';
import { config } from './config.js';
import { WeatherPlugin } from './plugins/weather/weather.plugin.js';
@Controller('version')
class VersionController {
@Get() get() { return { name: 'My Oracle' }; }
}
@Module({ controllers: [VersionController] })
class VersionModule {}
async function bootstrap(): Promise {
const matrixClient = sdk.createClient({
baseUrl: process.env.MATRIX_BASE_URL!,
userId: process.env.MATRIX_ORACLE_ADMIN_USER_ID!,
accessToken: process.env.MATRIX_ORACLE_ADMIN_ACCESS_TOKEN!,
});
const app = await createOracleApp({
config,
logger: Logger,
plugins: [new EditorPlugin({ matrixClient }), new WeatherPlugin()],
nestModules: [VersionModule],
authExcludedRoutes: [{ path: 'version', method: RequestMethod.GET }],
});
app.onPluginStatusChange((event) => {
Logger.log(`[plugin] ${event.plugin} ${event.from} → ${event.to}`);
});
Logger.log(`[boot] loaded: ${app.plugins.status().loaded.join(', ')}`);
await app.listen();
}
bootstrap().catch((err) => {
Logger.error('Oracle failed to start:', err);
process.exit(1);
});
```
## Where to read next
End-to-end Weather plugin walkthrough.
The `features` map in detail.
Flat reference table for every field.
Core + per-plugin env vars.
---
# Enable bundled plugins
> Toggle the 16 bundled QiForge plugins via the features map — opt out, force on, or let auto-detect handle it — and retune their manifests.
## Copy-paste recipe
The whole API is a `features` map handed to `createOracleApp`. Each key is a bundled plugin name, each value is `true` / `false` / `'auto'`.
```ts
import { createOracleApp } from '@ixo/oracle-runtime';
import { config } from './config.js';
const app = await createOracleApp({
config,
features: {
composio: true, // force on (set COMPOSIO_API_KEY)
slack: false, // force off
firecrawl: 'auto', // same as omitting — runs autoDetect
},
plugins: [], // your own plugins go here
});
await app.listen();
```
That's the whole surface. The runtime pre-loads every bundled plugin instance from [`BUNDLED_PLUGINS`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/index.ts); `features` only controls which ones survive resolution. Reference oracle: [apps/qiforge-example/src/main.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/main.ts).
## How resolution works
[`BUNDLED_PLUGINS`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/index.ts) is a fixed 16-plugin tuple — memory, portal, firecrawl, domain-indexer, composio, sandbox, skills, editor, agui, slack, tasks, credits, calls, user-preferences, matrix-group-chats, vfs.
You do not import or instantiate the plugins you want at defaults — they are already there.
For every bundled plugin, [`resolvePlugins`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/plugin-loader.ts) reads `features[plugin.name]`:
```ts
type FeatureToggle = boolean | 'auto';
features?: Partial>;
```
| Value | Behaviour |
| --- | --- |
| `true` | Force the plugin on. If its `autoDetect(env)` returns false, boot fails with `boot.plugin.env_missing`. |
| `false` | Force the plugin off. Skip `autoDetect` entirely. |
| `'auto'` (or omitted) | Run `plugin.autoDetect(env)`. Include if true, exclude otherwise. |
Plugins from the `plugins: []` array are always loaded — they're not gated by `features`. If a name collides with a bundled plugin, your instance wins (the loader dedupes by `name`).
If a loaded plugin has `dependsOn: ['removed-plugin']` and that dep ended up excluded, the dependent cascades off too. Soft deps (`softDependsOn`) only log a warning.
## What every bundled plugin does by default
Each plugin's `autoDetect` predicate decides whether to opt in when you leave it on `'auto'`.
| Plugin | Auto-detects when | Notes |
| --- | --- | --- |
| `memory` | `MEMORY_MCP_URL` set | Visibility `always` |
| `portal` | always on | Visibility `on-demand` |
| `firecrawl` | `FIRECRAWL_MCP_URL` set | Visibility `on-demand` |
| `domain-indexer` | always on | Visibility `always` |
| `composio` | `COMPOSIO_API_KEY` set | Visibility `on-demand` |
| `sandbox` | `SANDBOX_MCP_URL` set | Visibility `always` |
| `skills` | always on | Visibility `always`; depends on `sandbox` |
| `editor` | always on | Needs `matrixClient` — instantiate explicitly |
| `agui` | always on | Visibility `on-demand` |
| `slack` | `SLACK_BOT_OAUTH_TOKEN` set | Visibility `silent` (transport) |
| `tasks` | `REDIS_URL` set | Visibility `on-demand`; BullMQ-backed async tasks (needs `REDIS_URL`) |
| `credits` | always on | Visibility `silent`; pass `redis` for production |
| `calls` | always on | Visibility `silent`; placeholder stub (no tools yet) |
| `user-preferences` | always on | Visibility `always` |
| `matrix-group-chats` | always on | Visibility `on-demand`; gating middleware + tools fire only in Matrix group rooms (`memberCount > 2`) |
| `vfs` | always on | Visibility `always`; worker URLs derived from `NETWORK`. Contributes tools only when the oracle has a UCAN signing key and the user granted filesystem access |
Full per-plugin env vars: [plugin catalog](/build-an-oracle/reference/bundled-plugins/overview) and [environment variables reference](/build-an-oracle/reference/environment-variables).
## Opt out of a plugin
```ts
const app = await createOracleApp({
config,
features: {
composio: false,
'domain-indexer': false,
},
});
```
The plugin is dropped before `autoDetect` runs. Its `configSchema` is also removed from the merged env schema, so its env vars become optional.
If another loaded plugin lists the disabled plugin in its `dependsOn`, boot fails with a `boot.plugin.dep_missing` error naming both. Disable the dependent too, or keep the dependency loaded.
## Force a plugin on
```ts
const app = await createOracleApp({
config,
features: {
composio: true,
},
});
```
The plugin loads even if `autoDetect` would skip it.
With `features: { composio: true }` and no `COMPOSIO_API_KEY`, the runtime throws at boot:
```text
boot.plugin.env_missing: plugin 'composio' enabled via features but precondition failed (COMPOSIO_API_KEY).
Set the required env or disable: features: { composio: false }
```
See the plugin's page in the [plugin catalog](/build-an-oracle/reference/bundled-plugins/overview) for its full env requirements.
## Plugins that need constructor args
Two bundled plugins take a live runtime object you provide — instantiate explicitly and pass them via `plugins`:
```ts
import { createOracleApp, EditorPlugin, CreditsPlugin } from '@ixo/oracle-runtime';
import * as sdk from 'matrix-js-sdk';
import Redis from 'ioredis';
const matrixClient = sdk.createClient({
baseUrl: process.env.MATRIX_BASE_URL!,
userId: process.env.MATRIX_ORACLE_ADMIN_USER_ID!,
accessToken: process.env.MATRIX_ORACLE_ADMIN_ACCESS_TOKEN!,
});
const redis = process.env.REDIS_URL ? new Redis(process.env.REDIS_URL) : null;
const app = await createOracleApp({
config,
plugins: [
new EditorPlugin({ matrixClient }),
...(redis ? [new CreditsPlugin({ redis, network: 'devnet' })] : []),
],
});
```
Live example: [apps/qiforge-example/src/main.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/main.ts).
The bundled `editorPlugin` and `creditsPlugin` instances boot in stub form (for testing). For production behaviour, instantiate them yourself and pass the live objects in.
## Retune a bundled plugin's manifest
`features` decides **whether** a bundled plugin loads. To change **how it's advertised** — most usefully its [visibility tier](/build-an-oracle/understand/visibility-tiers), but also `summary`, `tags`, or `whenToUse` — use [`manifestOverrides`](/build-an-oracle/reference/createoracleapp#manifestoverrides) instead of forking the plugin. The override is shallow-merged onto the plugin's own manifest at boot, validated like any authored manifest, and seen by every downstream reader (Tier-1 prompt, `list_capabilities` / `load_capability`, visibility index).
```ts
const app = await createOracleApp({
config,
manifestOverrides: {
// Take a noisy `always` bundled plugin out of the Tier-1 prompt.
'domain-indexer': { visibility: 'on-demand' },
// Hide a transport plugin entirely; its tools still bind.
portal: { visibility: 'silent' },
},
});
```
Override keys that don't match a loaded plugin are logged (`boot.plugin.manifest_override_unknown`) and ignored — so disabling a plugin via `features` and leaving its override entry behind is safe.
## Inspect what loaded
```ts
const status = app.plugins.status();
// {
// loaded: ['memory', 'domain-indexer', 'editor', 'user-preferences', 'weather'],
// excluded: [
// { plugin: 'composio', reason: 'auto-detect precondition not met (COMPOSIO_API_KEY)' },
// { plugin: 'slack', reason: 'feature flag set to false' },
// ],
// softDepGaps: [],
// }
```
Each `excluded` entry is `{ plugin, reason }` — surface the `reason` in your boot logs so operators see why a plugin came up missing. (`softDepGaps` entries are `{ plugin, missing }`.)
## Where to read next
Every bundled plugin in detail.
Core vars plus per-plugin vars.
Every option, exhaustively.
Build your own next to the bundled set.
---
# Write a plugin
> Build the Weather plugin end-to-end. Every OraclePlugin hook against Open-Meteo (no API key required).
## The finished plugin in one snippet
This is what you are about to build — every `OraclePlugin` hook wired against Open-Meteo. The canonical source: [apps/qiforge-example/src/plugins/weather/weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
```ts
export class WeatherPlugin extends OraclePlugin {
readonly name = 'weather';
readonly version = '0.1.0';
readonly manifest = manifest;
override readonly configSchema = configSchema;
override readonly autoDetectHint = 'always on (set WEATHER_DEFAULT_UNITS to celsius|fahrenheit)';
private readonly lastBySession: LastQueryStore = new Map();
override autoDetect() { return true; }
override getTools(ctx) { return [buildCurrentWeatherTool(this.units(ctx.config), this.lastBySession)]; }
override getRequestTools(rt) { return [buildForecastTool(this.units(rt.config), this.lastBySession)]; }
override getSubAgents(ctx) { return [buildWeatherPlannerSubAgent(this.units(ctx.config), this.lastBySession)]; }
override getMiddlewares(ctx) { return [buildWeatherMiddleware(ctx)]; }
override getNestModules(ctx) { return [WeatherHttpModule.register(this.units(ctx.config))]; }
override getAuthExcludedRoutes() {
return [{ path: 'weather/now', method: RequestMethod.GET }];
}
override getSharedState() {
return { lastWeatherQuery: (_s, rt) => this.lastBySession.get(rt.session.id) };
}
}
```
Each step below builds one of those lines.
## Two ways to author a plugin
Every hook in this guide works identically whether you subclass `OraclePlugin` or use the `defineOraclePlugin` helper. Pick whichever you prefer — the runtime treats the resulting object exactly the same way.
Subclass and `override` each hook. Per-session state lives on `private` fields. Register with `new WeatherPlugin()`.
```ts
export class WeatherPlugin extends OraclePlugin {
readonly name = 'weather';
readonly version = '0.1.0';
readonly manifest = manifest;
override readonly configSchema = configSchema;
private readonly lastBySession: LastQueryStore = new Map();
override autoDetect(): boolean { return true; }
override getTools(ctx: PluginContext): PluginTool[] {
return [buildCurrentWeatherTool(this.units(ctx.config), this.lastBySession)];
}
// …every other hook as an override
}
```
```ts
plugins: [new WeatherPlugin()],
```
Pass a plain object — `name`, `version`, and `manifest` are required; every hook is optional. Per-session state lives in a module-scoped closure. `defineOraclePlugin` returns a ready `OraclePlugin`, so register it **without** `new`.
```ts
import {
defineOraclePlugin,
type PluginContext,
type RuntimeContext,
} from '@ixo/oracle-runtime';
import { RequestMethod } from '@nestjs/common';
const lastBySession: LastQueryStore = new Map();
const units = (config: unknown) => configSchema.parse(config).WEATHER_DEFAULT_UNITS;
export const weatherPlugin = defineOraclePlugin({
name: 'weather',
version: '0.1.0',
manifest,
configSchema,
autoDetectHint: 'always on (set WEATHER_DEFAULT_UNITS to celsius|fahrenheit)',
autoDetect: () => true,
getTools: (ctx) => [buildCurrentWeatherTool(units(ctx.config), lastBySession)],
getRequestTools: (rtCtx) => [buildForecastTool(units(rtCtx.config), lastBySession)],
getSubAgents: (ctx) => [buildWeatherPlannerSubAgent(units(ctx.config), lastBySession)],
getMiddlewares: (ctx) => [buildWeatherMiddleware(ctx)],
getNestModules: (ctx) => [WeatherHttpModule.register(units(ctx.config))],
getAuthExcludedRoutes: () => [{ path: 'weather/now', method: RequestMethod.GET }],
getSharedState: () => ({
lastWeatherQuery: (_state, runCtx: RuntimeContext) =>
lastBySession.get(runCtx.session.id),
}),
});
```
```ts
plugins: [weatherPlugin], // already an instance — no `new`
```
`defineOraclePlugin` throws a `TypeError` at authoring time if `name`, `version`, or `manifest` is missing. Source: [`define-plugin.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/define-plugin.ts).
The rest of this guide uses the class form (matching the reference oracle), but every snippet maps one-to-one onto a `defineOraclePlugin` field.
## Prerequisites
- A working `main.ts` calling `createOracleApp` — see [Create your oracle](/build-an-oracle/develop/create-oracle-app).
- The oracle scaffolded by the CLI (`qiforge-cli new`) — see the [CLI reference](/build-an-oracle/reference/cli).
- Files live under `src/plugins/weather/` — same layout as the reference [apps/qiforge-example/src/plugins/weather/](https://github.com/ixoworld/ixo-oracles-boilerplate/tree/main/apps/qiforge-example/src/plugins/weather).
## Step-by-step
File: [`weather.plugin.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts)
```ts
import {
OraclePlugin,
type PluginContext,
type PluginManifest,
type RuntimeContext,
z,
} from '@ixo/oracle-runtime';
const configSchema = z.object({
WEATHER_DEFAULT_UNITS: z.enum(['celsius', 'fahrenheit']).default('celsius'),
});
const manifest: PluginManifest = {
title: 'Weather',
summary: 'Weather lookups via Open-Meteo (no API key required).',
whenToUse: ['User asks about current weather or forecasts for any city.'],
whenNotToUse: ['Historical or long-term climate data.'],
tags: ['weather', 'forecast'],
category: 'data',
visibility: 'on-demand',
stability: 'experimental',
};
export class WeatherPlugin extends OraclePlugin {
readonly name = 'weather';
readonly version = '0.1.0';
readonly manifest = manifest;
override readonly configSchema = configSchema;
override readonly autoDetectHint =
'always on (set WEATHER_DEFAULT_UNITS to celsius|fahrenheit)';
override autoDetect(): boolean {
return true;
}
}
```
That's a valid plugin — it loads, validates `WEATHER_DEFAULT_UNITS`, and contributes nothing yet. Everything below adds one hook at a time.
File: [`weather-client.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather-client.ts). Open-Meteo doesn't need auth.
```ts
export type Units = 'celsius' | 'fahrenheit';
export async function getCurrentWeather(
city: string,
units: Units,
signal?: AbortSignal,
) {
// geocode → fetch /v1/forecast → parse with Zod → return null on miss
}
export async function getForecast(
city: string,
days: number,
units: Units,
timezone: string,
signal?: AbortSignal,
) {
// similar, returns `{ city, days: [{ date, tempMax, tempMin, conditions }] }`
}
```
Keep it tiny — this guide is about the plugin contract, not weather APIs.
File: [`weather-tools.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather-tools.ts).
`getTools(ctx)` is called once per agent build. Use it when the tool's behaviour depends only on plugin config — not on per-request data.
```ts
import { type PluginTool, type RuntimeContext, tool, z } from '@ixo/oracle-runtime';
import { getCurrentWeather, type Units } from './weather-client.js';
export function buildCurrentWeatherTool(
defaultUnits: Units,
store: LastQueryStore,
): PluginTool {
return tool(
async (rawArgs, ctx: RuntimeContext) => {
const { city } = z.object({ city: z.string().min(1) }).parse(rawArgs);
const result = await getCurrentWeather(city, defaultUnits, ctx.abortSignal);
if (!result) return `Could not find weather for "${city}".`;
store.set(ctx.session.id, { ...result, queriedAt: new Date().toISOString() });
return JSON.stringify(result);
},
{
name: 'get_current_weather',
description:
'Get the current weather for a city. Returns temperature, wind speed (km/h), conditions, and coordinates.',
schema: z.object({
city: z.string().min(1).describe('City name, e.g. "Berlin".'),
}),
},
);
}
```
Wire it on the plugin:
```ts
override getTools(ctx: PluginContext): PluginTool[] {
return [buildCurrentWeatherTool(this.units(ctx.config), this.lastBySession)];
}
```
The handler still receives a full `RuntimeContext` at call time — `ctx.user`, `ctx.session`, `ctx.abortSignal` are all live.
When a tool depends on per-request data (e.g. the user's timezone), register it via `getRequestTools(rtCtx)` instead:
```ts
export function buildForecastTool(
defaultUnits: Units,
store: LastQueryStore,
): PluginTool {
return tool(
async (rawArgs, ctx: RuntimeContext) => {
const { city, days } = z
.object({
city: z.string().min(1),
days: z.number().int().min(1).max(7).optional(),
})
.parse(rawArgs);
const tz =
ctx.user.timezone && ctx.user.timezone.length > 0
? ctx.user.timezone
: 'auto';
const result = await getForecast(city, days ?? 3, defaultUnits, tz, ctx.abortSignal);
if (!result) return `Could not find a forecast for "${city}".`;
return JSON.stringify(result);
},
{
name: 'get_weather_forecast',
description:
'Get a daily weather forecast for a city (up to 7 days). Uses the user timezone when available.',
schema: z.object({
city: z.string().min(1),
days: z.number().int().min(1).max(7).optional(),
}),
},
);
}
```
```ts
override getRequestTools(rtCtx: RuntimeContext): PluginTool[] {
return [buildForecastTool(this.units(rtCtx.config), this.lastBySession)];
}
```
Boot-time and request-time outputs are merged — both tools end up in the agent's tool list.
File: [`weather-sub-agent.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather-sub-agent.ts).
Sub-agents have their own prompt and tool list, and the runtime exposes each as a single tool to the main agent (`call_weather_planner_agent`). Use them for compound tasks that benefit from focused context.
```ts
import { type PluginSubAgent } from '@ixo/oracle-runtime';
const PROMPT = [
'You are the Weather Planner Agent. You decide whether the user needs a',
'jacket / umbrella / etc. for a place and time.',
'',
'Workflow:',
'1. Call get_weather_forecast with the city.',
"2. Pick the most relevant day.",
"3. Call recommend_outfit with that day's max temp + conditions.",
'4. Reply with ONE sentence combining the forecast and the outfit advice.',
].join('\n');
export function buildWeatherPlannerSubAgent(
defaultUnits: Units,
store: LastQueryStore,
): PluginSubAgent {
return {
name: 'weather_planner_agent',
description:
'Combines a forecast lookup with an outfit recommendation. Use for "should I bring a jacket to X tomorrow?".',
systemPrompt: PROMPT,
tools: [buildForecastTool(defaultUnits, store), buildRecommendOutfitTool()],
model: 'subagent',
forwardTools: true,
};
}
```
```ts
override getSubAgents(ctx: PluginContext): PluginSubAgent[] {
return [buildWeatherPlannerSubAgent(this.units(ctx.config), this.lastBySession)];
}
```
`forwardTools: true` surfaces the sub-agent's inner tool calls in the parent chat's UI events so users see the chain.
File: [`weather-middleware.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather-middleware.ts).
Middleware runs around every LLM call. Hooks come from LangChain's `createMiddleware`.
```ts
import { type AgentMiddleware, type PluginContext } from '@ixo/oracle-runtime';
import { createMiddleware } from 'langchain';
export function buildWeatherMiddleware(ctx: PluginContext): AgentMiddleware {
let startedAt = 0;
return createMiddleware({
name: 'WeatherLoggingMiddleware',
beforeModel: async () => {
startedAt = Date.now();
ctx.logger.log('model call started');
},
afterModel: async () => {
const elapsed = startedAt > 0 ? Date.now() - startedAt : -1;
ctx.logger.log(`model call complete (${elapsed}ms)`);
},
});
}
```
```ts
override getMiddlewares(ctx: PluginContext): AgentMiddleware[] {
return [buildWeatherMiddleware(ctx)];
}
```
Plugin middleware runs after the framework's always-on middleware (`capability-gate`, `tool-validation`, `tool-repetition-guard`, `tool-retry`) — plus the conditional `page-context` / `safety-guardrail` when their hooks are configured — in topological dependency order across plugins. A plugin middleware hook must return `undefined` or `{ jumpTo: 'end' as const }`, never a partial state object — see [Add a middleware](/build-an-oracle/develop/plugin-recipes/add-a-middleware).
File: [`weather.module.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.module.ts).
To expose a public `GET /weather/now?city=X`, ship a NestJS module:
```ts
import { Controller, type DynamicModule, Get, Inject, Module, Query } from '@nestjs/common';
import { getCurrentWeather, type Units } from './weather-client.js';
export const WEATHER_DEFAULT_UNITS = 'WEATHER_DEFAULT_UNITS';
@Controller('weather')
export class WeatherController {
constructor(@Inject(WEATHER_DEFAULT_UNITS) private readonly units: Units) {}
@Get('now')
async now(@Query('city') city?: string) {
if (!city) return { ok: false, error: 'Missing required query param: city' };
const result = await getCurrentWeather(city, this.units);
if (!result) return { ok: false, error: `Could not find weather for "${city}".` };
return { ok: true, ...result };
}
}
@Module({})
export class WeatherHttpModule {
static register(units: Units): DynamicModule {
return {
module: WeatherHttpModule,
controllers: [WeatherController],
providers: [{ provide: WEATHER_DEFAULT_UNITS, useValue: units }],
};
}
}
```
Register it from the plugin and opt the route out of UCAN auth:
```ts
import { RequestMethod, type DynamicModule } from '@nestjs/common';
import { type AuthExcludedRoute } from '@ixo/oracle-runtime';
override getNestModules(ctx: PluginContext): DynamicModule[] {
return [WeatherHttpModule.register(this.units(ctx.config))];
}
override getAuthExcludedRoutes(): AuthExcludedRoute[] {
return [{ path: 'weather/now', method: RequestMethod.GET }];
}
```
Other plugins can now read the last weather query for the current session:
```ts
private readonly lastBySession = new Map();
override getSharedState(): Record unknown> {
return {
lastWeatherQuery: (_state, runCtx) =>
this.lastBySession.get(runCtx.session.id),
};
}
```
Your tool/sub-agent handlers write into `this.lastBySession` after every successful lookup (see step 3). Consumers read it as `rtCtx.shared.lastWeatherQuery`.
```ts
import { WeatherPlugin } from './plugins/weather/index.js';
const app = await createOracleApp({
config,
plugins: [new WeatherPlugin()],
});
await app.listen();
```
Reference: [apps/qiforge-example/src/main.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/main.ts).
Boot the app (`pnpm dev`) and exercise each hook.
| Hook | How to verify |
| --- | --- |
| `getNestModules` + `getAuthExcludedRoutes` | `curl 'http://localhost:3000/weather/now?city=Berlin'` returns JSON without a UCAN header. |
| `manifest.visibility: 'on-demand'` | Chat `what's the weather in Berlin?` — the agent calls `list_capabilities` and/or `load_capability` before any weather tool. |
| `getTools` | After weather is loaded, chat `what's the temperature in Tokyo?` — `get_current_weather` fires. |
| `getRequestTools` | Chat `forecast for São Paulo this week` with an `x-timezone` header — `get_weather_forecast` fires; handler reads `rtCtx.user.timezone`. |
| `getSubAgents` + `forwardTools` | Chat `should I bring a jacket to Berlin tomorrow?` — main agent calls `call_weather_planner_agent`, which chains `get_weather_forecast` then `recommend_outfit`. Both inner calls show in the UI. |
| `getMiddlewares` | Server logs show `model call started` / `model call complete (Xms)` for every weather turn. |
| `getSharedState` | Inside another plugin's tool: `rtCtx.shared.lastWeatherQuery` returns the latest record for this session. |
| `configSchema` | Set `WEATHER_DEFAULT_UNITS=kelvin` and boot — fails with a Zod error pointing at the `weather` plugin. |
Full manual walkthrough: [apps/qiforge-example/WEATHER-PLUGIN.md](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/WEATHER-PLUGIN.md).
## Where to read next
Tier A direct-invoke and Tier B agent-loop tests.
Per-hook deep dives (tool, sub-agent, middleware, HTTP, state).
Every hook signature, exhaustively.
How the runtime turns a class into a registered plugin.
---
# Add a tool
> Register a tool the agent can call by returning it from getTools or getRequestTools.
A tool is a function the agent calls with validated args. Return one from `getTools` (boot-time) or `getRequestTools` (per-request).
Use the `tool()` builder from `@ixo/oracle-runtime`. The handler gets validated `args` and a per-request `RuntimeContext`.
```ts
import { tool, z, type PluginTool, type RuntimeContext } from '@ixo/oracle-runtime';
export function buildCurrentWeatherTool(): PluginTool {
return tool(
async (args, ctx: RuntimeContext) => {
const { city } = z.object({ city: z.string().min(1) }).parse(args);
ctx.logger.log(`weather lookup by ${ctx.user.did} for ${city}`);
const result = await fetchWeather(city, ctx.abortSignal);
if (!result) return `Could not find weather for "${city}".`;
return JSON.stringify(result);
},
{
name: 'get_current_weather',
description: 'Get the current weather for a city.',
schema: z.object({
city: z.string().min(1).describe('City name, e.g. "Berlin".'),
}),
},
);
}
```
Canonical source: [weather-tools.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather-tools.ts).
Pick `getTools` when the tool list is the same for every user — it only depends on config and identity.
```ts
import { OraclePlugin, type PluginContext, type PluginTool } from '@ixo/oracle-runtime';
export class WeatherPlugin extends OraclePlugin {
override getTools(ctx: PluginContext): PluginTool[] {
return [buildCurrentWeatherTool()];
}
}
```
See `getTools` in [weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
Use `getRequestTools` when the tool list depends on live state — the user, session, history, or `loadedPlugins`.
```ts
override getRequestTools(rtCtx: RuntimeContext): PluginTool[] {
const tz = rtCtx.user.timezone ?? 'auto';
return [buildForecastTool(tz)];
}
```
Both hooks merge — their outputs are concatenated on every build. See `getRequestTools` in [weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
A tool inherits its plugin's `manifest.visibility` by default. Add a `visibility` field to the `PluginTool` to override per-tool.
```ts
const currentTool = buildCurrentWeatherTool();
currentTool.visibility = 'always';
const forecastTool = buildForecastTool();
forecastTool.visibility = 'on-demand';
return [currentTool, forecastTool];
```
See [Set visibility](/build-an-oracle/develop/plugin-recipes/set-visibility) for the full recipe.
## What to know before shipping
- Tool `name` must be unique across all loaded plugins. Boot fails on collision.
- Throw from the handler for unexpected errors (retry middleware catches them). Return a string for expected misses ("not found").
- The agent expects a string; `JSON.stringify` structured output to keep the contract clean.
- `ctx.abortSignal` propagates from the inbound request — forward it to every `fetch`.
- Do not override descriptions of upstream MCP tools — pass them through verbatim and put guidance in the manifest instead.
## Where to read next
For multi-step tool sequences with their own prompt.
Every field a handler can read.
---
# Add a sub-agent
> Wrap a focused inner agent as a single tool the main agent can delegate to.
A sub-agent is a `PluginSubAgent` the runtime auto-wraps as a tool (e.g. `weather_planner_agent`). Use one when a task needs more than one tool call in sequence or its own prompt.
Sub-agents own a private tool list. Reuse plugin tools or define new ones in the same file.
```ts
import { tool, z, type PluginTool } from '@ixo/oracle-runtime';
function buildRecommendOutfitTool(): PluginTool {
return tool(
async (args) => {
const { temp_c, conditions } = z
.object({ temp_c: z.number(), conditions: z.string() })
.parse(args);
const wet = ['rain', 'snow'].some((k) => conditions.toLowerCase().includes(k));
const layer = temp_c >= 18 ? 'a t-shirt' : 'a jacket';
return `Wear ${layer}${wet ? ' and bring an umbrella' : ''}.`;
},
{
name: 'recommend_outfit',
description: 'Return a one-line outfit suggestion.',
schema: z.object({ temp_c: z.number(), conditions: z.string() }),
},
);
}
```
Canonical source: [weather-sub-agent.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather-sub-agent.ts).
Give the sub-agent a `name` (the tool the main agent sees), a `description` that reads like a tool description, a `systemPrompt`, and its `tools`.
```ts
import type { PluginSubAgent } from '@ixo/oracle-runtime';
export function buildWeatherPlannerSubAgent(): PluginSubAgent {
return {
name: 'weather_planner_agent',
description:
'Combines a forecast lookup with an outfit recommendation. Use for "should I bring a jacket to X tomorrow?".',
systemPrompt: [
'You are the Weather Planner Agent.',
'1. Call get_weather_forecast with the city.',
'2. Pick the most relevant day.',
'3. Call recommend_outfit with that day\'s max temp and conditions.',
'4. Reply with ONE sentence combining forecast and advice.',
].join('\n'),
tools: [buildForecastTool(), buildRecommendOutfitTool()],
model: 'subagent',
forwardTools: true,
};
}
```
`systemPrompt` and `tools` can both be functions of `PluginContext` for late binding. See `buildWeatherPlannerSubAgent` in [weather-sub-agent.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather-sub-agent.ts).
The runtime wraps each sub-agent as a tool named `` and exposes it to the main agent.
```ts
import { OraclePlugin, type PluginContext, type PluginSubAgent } from '@ixo/oracle-runtime';
export class WeatherPlugin extends OraclePlugin {
override getSubAgents(ctx: PluginContext): PluginSubAgent[] {
return [buildWeatherPlannerSubAgent()];
}
}
```
See `getSubAgents` in [weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
By default, the main agent only sees the sub-agent's final string. Forward inner tool calls into the main chat so the UI renders them.
```ts
{
// ...
forwardTools: true, // forward all
// forwardTools: ['get_weather_forecast'], // or a specific list
// forwardTools: false, // or hide everything (default)
}
```
Runtime-injected passthrough tools (e.g. memory CRUD) are never forwarded.
Use the request-time variant when whether or how to build the sub-agent depends on the user, session, or state.
```ts
override getRequestSubAgents(rtCtx: RuntimeContext): PluginSubAgent[] {
// ReadonlyState's index signature types arbitrary keys as `unknown`,
// so narrow before reading `.length`.
const agActions = rtCtx.history.state.agActions;
if (!Array.isArray(agActions) || agActions.length === 0) return [];
return [buildActionAwareSubAgent(agActions)];
}
```
`state.agActions` (live AG-UI actions) is the canonical request-time field — reading it through `rtCtx.history.state` returns `unknown`, so validate with `Array.isArray(...)` before use. Boot-time `getSubAgents` and request-time `getRequestSubAgents` outputs merge.
## What to know before shipping
- The sub-agent's `name` must be unique across all loaded plugins — same namespace as regular tools. Prefix with the agent role (e.g. `weather_planner_agent`).
- `model` defaults to `'subagent'`. Use `'main'` for a top-tier model, `'utility'` for a cheaper one.
- Sub-agent middleware (the `middlewares` field) only runs inside the sub-agent's loop, not the main agent's chain.
- `onComplete(result, ctx)` fires after the last turn — use it for follow-up events.
- If sub-agent init throws, the runtime logs and skips that contribution for that request; the rest of the agent build continues.
## Where to read next
For single-call work that doesn't need a sub-agent.
Main-agent vs sub-agent middleware.
---
# Add a middleware
> Hook into every LLM call via beforeModel, afterModel, and wrapModelCall using createMiddleware.
A middleware wraps the agent's LLM call. Return one from `getMiddlewares(ctx)` and it runs on every model turn.
Import `createMiddleware` from `langchain`. Set a `name` (appears in error messages) and any of the six hooks.
```ts
import { type AgentMiddleware, type PluginContext } from '@ixo/oracle-runtime';
import { createMiddleware } from 'langchain';
export function buildWeatherMiddleware(ctx: PluginContext): AgentMiddleware {
let startedAt = 0;
return createMiddleware({
name: 'WeatherLoggingMiddleware',
beforeModel: async () => {
startedAt = Date.now();
ctx.logger.log('model call started');
},
afterModel: async () => {
const elapsed = startedAt > 0 ? Date.now() - startedAt : -1;
ctx.logger.log(`model call complete (${elapsed}ms)`);
},
});
}
```
Canonical source: [weather-middleware.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather-middleware.ts).
`createMiddleware` accepts exactly six hooks. Pick the one whose timing matches your work — there is no `onError`, no `beforeToolCall`, no `afterToolCall`.
| Hook | Fires | Use for |
| --- | --- | --- |
| `beforeAgent` | Once, before the agent loop starts | One-time setup per turn |
| `beforeModel` | Before every LLM call | Logging, timers, observation |
| `wrapModelCall` | Around every LLM call (you call `handler`) | Error handling, fallbacks, retries |
| `wrapToolCall` | Around every tool call (you call `handler`) | Tool-level interception |
| `afterModel` | After every LLM call | Logging, emitting events |
| `afterAgent` | Once, after the agent loop ends | Teardown, final accounting |
```ts
createMiddleware({
name: 'MyMiddleware',
beforeModel: async () => {
// Loading context, observation
},
afterModel: async () => {
// Logging, emitting events
},
});
```
Error handling lives in `wrapModelCall`: wrap the `handler(request)` call in a try/catch and retry with a fallback model. The hook owns the call, so you decide what happens on failure.
```ts
import { ChatOpenAI } from '@langchain/openai';
createMiddleware({
name: 'ModelFallbackMiddleware',
wrapModelCall: async (request, handler) => {
try {
return await handler(request);
} catch (err) {
ctx.logger.warn(`primary model failed: ${String(err)} — falling back`);
return handler({ ...request, model: new ChatOpenAI({ model: 'gpt-4o-mini' }) });
}
},
});
```
The flow-control hooks (`beforeAgent`, `beforeModel`, `afterModel`, `afterAgent`) must return **`undefined`** (pass through) or **`{ jumpTo: 'end' as const }`** (short-circuit the turn). Never return `{ messages: ... }` or any other state channel. (The `wrap*` hooks are different — they return the result of calling `handler`, as in the fallback example above.)
Returning a partial state object from a plugin middleware breaks LangGraph checkpointer thread continuity — the observed symptom is a brand-new thread spawned on every message. Message and state rewrites belong in the transport layer (the messages controller), not in a middleware hook.
```ts
beforeModel: async (state) => {
if (shouldStop(state)) {
return { jumpTo: 'end' as const }; // stop the loop early
}
return undefined; // otherwise pass through
},
```
The runtime appends your middleware after the framework's built-in middleware (see the list below).
```ts
import { OraclePlugin, type AgentMiddleware, type PluginContext } from '@ixo/oracle-runtime';
export class WeatherPlugin extends OraclePlugin {
override getMiddlewares(ctx: PluginContext): AgentMiddleware[] {
return [buildWeatherMiddleware(ctx)];
}
}
```
See `getMiddlewares` in [weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
A sub-agent can carry its own middleware — it only runs inside that sub-agent's loop.
```ts
import { createSummarizationMiddleware } from '@ixo/oracle-runtime';
const subAgent: PluginSubAgent = {
name: 'long_research_agent',
// ...
middlewares: [createSummarizationMiddleware()],
};
```
## What to know before shipping
- Four always-on middleware run first and are not removable, in this order: `capability-gate`, `tool-validation`, `tool-repetition-guard`, `tool-retry`. Two more run only when the matching hook is set on `createOracleApp`: `page-context` (when `hooks.getRoomTitle` is provided) and `safety-guardrail` (when `hooks.safetyModel` is provided). Your plugin middleware is appended after all of these.
- Plugin middleware runs in topological dependency order — encode ordering via `dependsOn` if it matters.
- Middleware fires per LLM turn, not per individual tool invocation. Wrap the tool handler directly for per-tool behaviour.
- Closure-scoped timers interleave across concurrent model calls. For accurate timing, push start times onto a per-call ID.
- Don't reimplement auth in a middleware — auth runs at the HTTP layer (`AuthHeaderMiddleware`), not in the agent loop.
## Where to read next
Sub-agent-scoped middleware.
What's in `state` when your hooks fire.
---
# Add HTTP endpoints
> Contribute NestJS controllers and modules from a plugin via getNestModules, and opt routes out of UCAN auth via getAuthExcludedRoutes.
Use `getNestModules` for webhooks, public probes, OAuth callbacks, or long-lived services. Use `getAuthExcludedRoutes` to skip UCAN auth on specific paths.
A plugin's module is a regular Nest module. Use a static `register(opts)` returning a `DynamicModule` if you want to pass config into DI.
```ts
import {
Controller,
type DynamicModule,
Get,
Inject,
Module,
Query,
} from '@nestjs/common';
import { getCurrentWeather, type Units } from './weather-client.js';
export const WEATHER_DEFAULT_UNITS = 'WEATHER_DEFAULT_UNITS';
@Controller('weather')
export class WeatherController {
constructor(@Inject(WEATHER_DEFAULT_UNITS) private readonly units: Units) {}
@Get('now')
async now(@Query('city') city?: string) {
if (!city) return { ok: false, error: 'Missing required query param: city' };
const result = await getCurrentWeather(city, this.units);
if (!result) return { ok: false, error: `Could not find weather for "${city}".` };
return { ok: true, ...result };
}
}
@Module({})
export class WeatherHttpModule {
static register(units: Units): DynamicModule {
return {
module: WeatherHttpModule,
controllers: [WeatherController],
providers: [{ provide: WEATHER_DEFAULT_UNITS, useValue: units }],
};
}
}
```
Canonical source: [weather.module.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.module.ts).
`ctx` is optional but useful for reading typed config when registering the module.
```ts
import { OraclePlugin, type PluginContext } from '@ixo/oracle-runtime';
import type { DynamicModule } from '@nestjs/common';
export class WeatherPlugin extends OraclePlugin {
override getNestModules(ctx: PluginContext): DynamicModule[] {
const units = configSchema.parse(ctx.config).WEATHER_DEFAULT_UNITS;
return [WeatherHttpModule.register(units)];
}
}
```
Plain module classes also work: `return [SlackModule]`. The runtime spreads the returned modules into `RuntimeAppModule.imports`. See `getNestModules` in [weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
By default every plugin route goes through `AuthHeaderMiddleware`. For webhooks, OAuth callbacks, or public probes, return them from `getAuthExcludedRoutes`.
```ts
import { RequestMethod } from '@nestjs/common';
import type { AuthExcludedRoute } from '@ixo/oracle-runtime';
override getAuthExcludedRoutes(): AuthExcludedRoute[] {
return [{ path: 'weather/now', method: RequestMethod.GET }];
}
```
Match by the full path the controller mounts at (`weather/now`, not `now`). `method` defaults to `RequestMethod.ALL`. See `getAuthExcludedRoutes` in [weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
Boot the oracle with `pnpm dev`, then curl the route without an auth header.
```sh
curl 'http://localhost:3000/weather/now?city=Berlin'
```
Other routes still 401 — the exclusion list only opts these specific paths out.
## What to know before shipping
- The same hook handles long-lived services. Slack, BullMQ workers, polling clients all ship as Nest modules with `OnModuleInit` lifecycle.
- Plugin modules have full DI access to the runtime's services (Sessions, Messages, Secrets, UCAN, …).
- Host code can also opt routes out of auth via `createOracleApp({ authExcludedRoutes })`. Plugin and host lists merge with built-in exclusions (`/health`, `/docs`).
- Leading slash on `path` is optional. The matcher mirrors NestJS's `MiddlewareConsumer.exclude(...)`.
- Plugin route paths are namespaced by the controller's `@Controller('weather')` decorator — keep them under your plugin's name to avoid collisions.
## Where to read next
Host-level Nest modules and `authExcludedRoutes`.
The framework's always-on HTTP/WS surface.
---
# Add config and env vars
> Declare your plugin's env vars with a Zod configSchema; read typed values from ctx.config.
The runtime merges every loaded plugin's `configSchema` onto a base schema, validates `process.env` at boot, and exposes the result on `ctx.config`.
Prefix every variable with your plugin's name in `SHOUT_SNAKE_CASE` to avoid collisions across plugins.
```ts
import { z } from '@ixo/oracle-runtime';
const configSchema = z.object({
WEATHER_DEFAULT_UNITS: z.enum(['celsius', 'fahrenheit']).default('celsius'),
});
```
Bundled plugins follow the same rule (`MEMORY_MCP_URL`, `SLACK_BOT_OAUTH_TOKEN`, …). See [memory.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/memory/memory.plugin.ts) for a required-field example.
The composer merges this schema with the base schema and every other loaded plugin's schema, then validates `process.env`.
```ts
import { OraclePlugin } from '@ixo/oracle-runtime';
export class WeatherPlugin extends OraclePlugin {
override readonly configSchema = configSchema;
}
```
Boot fails fast on missing or invalid values: `[boot-error] Plugin 'weather' env validation failed for 'WEATHER_DEFAULT_UNITS'`. See [weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
`ctx.config` is typed as `MergedConfig` (`Record`). Parse it through your schema to get a typed view — this can't fail at runtime because boot already validated.
```ts
override getTools(ctx: PluginContext): PluginTool[] {
const units = configSchema.parse(ctx.config).WEATHER_DEFAULT_UNITS;
return [buildCurrentWeatherTool(units)];
}
```
Same on `RuntimeContext.config` inside `getRequestTools` and tool handlers.
Implement `autoDetect` to make the plugin opt-in. Pair it with `autoDetectHint` so boot logs explain why it was skipped.
```ts
override autoDetect(env: NodeJS.ProcessEnv): boolean {
return Boolean(env.MEMORY_MCP_URL);
}
override readonly autoDetectHint = 'MEMORY_MCP_URL';
```
Without `autoDetect`, a plugin is on by default. See [memory.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/memory/memory.plugin.ts).
Forks override `autoDetect` via the `features` map passed to `createOracleApp`.
```ts
const app = await createOracleApp({
config,
features: {
weather: true, // force on, skip autoDetect
slack: false, // force off
composio: 'auto', // explicit auto (the default)
},
});
```
`FeatureToggle` is `boolean | 'auto'`. See [plugin-loader.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/plugin-loader.ts).
## What to know before shipping
- Use `.default(value)` and `.coerce.number()` freely — env values are strings, so coerce explicitly.
- Disabling a plugin (via `features` or `autoDetect`) automatically removes its env requirements.
- Two plugins declaring the same env-var name fail boot. Follow the naming convention to avoid this.
- To read a variable your plugin doesn't own, declare a separate optional sibling schema and `safeParse` it — don't require it through your own `configSchema`.
- Required boot-time fields stay in `configSchema`; per-call settings belong elsewhere (e.g. request headers, manifest defaults).
## Where to read next
Features map, autoDetect, and the bundled set's defaults.
Every base var and per-plugin var.
---
# Share state across plugins
> Expose a read-only accessor from one plugin so other plugins can read it via ctx.shared.
`getSharedState()` returns a flat map of `key → (state, runCtx) => value`. Consumers read those values on `ctx.shared.`.
Shared state is read-only by design. The owner plugin writes; consumers only read. A per-session `Map` is the simplest store.
```ts
import { OraclePlugin } from '@ixo/oracle-runtime';
export interface LastWeatherQuery {
city: string;
latitude: number;
longitude: number;
queriedAt: string;
}
export class WeatherPlugin extends OraclePlugin {
private readonly lastBySession = new Map();
// tools write into lastBySession after a successful lookup
}
```
Canonical source: [weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
Each entry is a function `(state, runCtx) => unknown`. The key becomes the field on `ctx.shared`. Keep accessors cheap — they're called every time a consumer reads.
```ts
import type { RuntimeContext } from '@ixo/oracle-runtime';
override getSharedState(): Record<
string,
(state: unknown, runCtx: RuntimeContext) => unknown
> {
return {
lastWeatherQuery: (_state, runCtx) =>
this.lastBySession.get(runCtx.session.id),
};
}
```
See `getSharedState` in [weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
Any tool handler can read `rtCtx.shared.`. Treat the value as possibly `undefined` because the producing plugin may not be loaded.
```ts
import { OraclePlugin, tool, z, type RuntimeContext } from '@ixo/oracle-runtime';
export class TripAdvisorPlugin extends OraclePlugin {
readonly name = 'trip-advisor';
readonly softDependsOn = ['weather'];
override getTools() {
return [
tool(
async (args, rtCtx: RuntimeContext) => {
const lastQuery = rtCtx.shared.lastWeatherQuery;
if (lastQuery) {
rtCtx.logger.log(`prior lookup: ${JSON.stringify(lastQuery)}`);
}
// ...
return 'done';
},
{
name: 'suggest_trip',
description: 'Suggest a trip based on recent weather.',
schema: z.object({}),
},
),
];
}
}
```
Pair shared state with [`softDependsOn`](/build-an-oracle/develop/plugin-recipes/declare-dependencies) so the dependency is explicit and discoverable.
`SharedAccessors` is an open interface. Extend it so consumers see the right type on `ctx.shared.`.
```ts
declare module '@ixo/oracle-runtime' {
interface SharedAccessors {
lastWeatherQuery?: LastWeatherQuery;
}
}
```
Without this, the key types as `unknown`. Skip declaration merging for ad-hoc keys.
## What to know before shipping
- The key namespace is flat. Two plugins registering the same key fail boot. Rename one.
- Accessors run on every read — memoise expensive computations inside the producer (e.g. by `session.id`).
- Shared state cannot be mutated by the consumer. To update the value, mutate via the owner's tool or middleware.
- A consumer that runs without the producer loaded gets `undefined`. Guard with `if (lastQuery)` or `ctx.availablePlugins.has('weather')`.
- Don't expose large blobs. Either compute lazily in the accessor or split into smaller derived keys.
## Where to read next
Pair shared state with `softDependsOn`.
The `ctx.shared` field on every tool handler.
---
# Declare dependencies
> Use dependsOn for hard requirements (fails boot if missing) and softDependsOn for optional enrichment.
A plugin references other plugins by `name`. `dependsOn` is hard (boot fails on miss). `softDependsOn` is soft (plugin loads either way and branches on `ctx.availablePlugins`).
Use this when the plugin literally cannot function without the other. The runtime topologically sorts plugins by `dependsOn` and aborts boot on missing deps or cycles.
```ts
import { OraclePlugin } from '@ixo/oracle-runtime';
export class SkillsPlugin extends OraclePlugin {
readonly name = 'skills';
override readonly dependsOn = ['sandbox'];
}
```
Missing dep (grep your boot logs for the `boot.plugin.dep_missing` code): `[boot-error] boot.plugin.dep_missing: plugin 'skills' requires 'sandbox', which is not loaded. Add 'sandbox' to features, or remove 'skills'.` Canonical source: [oracle-plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/oracle-plugin.ts).
Use this when the plugin enriches its behaviour when the other is present but stands alone without it. The runtime loads your plugin either way and logs one line per missing soft dep at boot.
```ts
export class TasksPlugin extends OraclePlugin {
readonly name = 'tasks';
override readonly softDependsOn = ['memory'];
}
```
See [plugin-loader.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/plugin-loader.ts) for the resolver.
`PluginContext.availablePlugins` and `RuntimeContext.availablePlugins` are `ReadonlySet` of every plugin that survived boot resolution.
```ts
override getTools(ctx: PluginContext): PluginTool[] {
const tools: PluginTool[] = [buildCreateTaskTool(), buildListTasksTool()];
if (ctx.availablePlugins.has('memory')) {
tools.push(buildRememberTaskContextTool());
}
return tools;
}
```
Same check works inside a handler:
```ts
handler: async (args, rtCtx: RuntimeContext) => {
if (rtCtx.availablePlugins.has('memory')) {
const profile = rtCtx.shared.userProfile;
return doWorkWithProfile(args, profile);
}
return doWorkWithoutProfile(args);
}
```
Forks override `autoDetect` and dependency resolution via the `features` map. `'auto'` (default) defers to the plugin's `autoDetect`.
```ts
const app = await createOracleApp({
config,
features: {
sandbox: true, // force on
skills: 'auto', // load when its autoDetect (and deps) pass
slack: false, // force off
},
});
```
Disabling a plugin that another plugin hard-requires fails boot — that's the point of `dependsOn`. See [plugin-loader.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/plugin-loader.ts).
## What to know before shipping
- Names are kebab-case plugin identifiers, not titles. `'memory'`, not `'Memory'`.
- The topo sort order drives middleware ordering and tool ordering in the prompt — encode ordering via `dependsOn` when it matters.
- Cycles in `dependsOn` fail boot with a clear error. Soft deps don't participate in the cycle check.
- Dependencies don't introspect what the other plugin contributes — only that it loaded. Check for specific tools at request time.
- There's no version constraint and no late-arriving plugins. Plugin resolution is a single boot-time step.
## Where to read next
Pair `softDependsOn` with `ctx.shared` to read another plugin's data.
See which bundled plugins use `dependsOn` / `softDependsOn`.
---
# Set visibility
> Control whether a plugin's tools are bound at boot, discoverable on demand, or invisible to the agent.
`manifest.visibility` sets the default for every tool the plugin ships. Individual tools can override it. Three values: `'always'`, `'on-demand'` (default), `'silent'`.
The manifest's `visibility` flag is the default applied to every tool the plugin returns.
```ts
import type { PluginManifest } from '@ixo/oracle-runtime';
const manifest: PluginManifest = {
title: 'Weather',
summary: 'Current weather + forecast lookups for cities.',
whenToUse: ['Current weather', 'Forecasts', 'Outfit recommendations'],
visibility: 'on-demand',
};
```
Three tiers: `'always'` (bound to the agent at boot, listed in the Tier-1 prompt), `'on-demand'` (default — discoverable via `list_capabilities`, loaded via `load_capability`), `'silent'` (not advertised — see the warning below). Canonical source: [weather.plugin.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
`'silent'` controls **advertising, not access** — it is not a security boundary. A silent plugin is left out of the Tier-1 capability block, excluded from `list_capabilities` (by default), and rejected by `load_capability`. But if a silent plugin returns tools, the capability gate passes them straight through to the model, exactly like `'always'`-tier tools. Use `'silent'` for transport/middleware/Nest-module plugins (e.g. Slack transport, credits/billing) that have nothing to advertise to the agent — never to "hide" a sensitive capability.
Set `visibility` directly on the `PluginTool` when one tool needs a different tier from the rest of the plugin.
```ts
import { tool, z, type PluginTool } from '@ixo/oracle-runtime';
const currentTool: PluginTool = tool(handler, {
name: 'get_current_weather',
description: 'Get the current weather for a city.',
schema: z.object({ city: z.string() }),
});
currentTool.visibility = 'always';
const forecastTool: PluginTool = tool(handler, {
name: 'get_weather_forecast',
description: 'Get a daily forecast for a city.',
schema: z.object({ city: z.string(), days: z.number().optional() }),
});
forecastTool.visibility = 'on-demand';
return [currentTool, forecastTool];
```
`PluginTool` shape: see [types.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/types.ts).
Use this decision tree.
```mermaid
graph TD
A["Does this plugin expose agent-callable tools?"]
A -->|No — middleware / transport only| Silent[silent]
A -->|Yes| B["Should the agent use it on most turns?"]
B -->|Yes| Always[always]
B -->|No — discover on demand| OnDemand[on-demand]
```
Promoting to `'always'` costs ~80 tokens per plugin in the Tier-1 prompt plus the tool schemas on every turn. `'silent'` simply stops advertising the plugin to the agent — it does not hide or disable any tools the plugin returns.
## What to know before shipping
- Default is `'on-demand'`. A plugin without an explicit `manifest.visibility` is treated as on-demand.
- `'on-demand'` plugins live in a per-thread `loadedPlugins` set. The set is monotonic — loading is forever for that thread; a new thread resets it.
- Per-tool overrides let you ship a mixed plugin (one `'always'` tool + several `'on-demand'` tools) without splitting it.
- `'silent'` plugins still run their middleware and Nest modules, and any tools they return stay callable by the agent — they're just never advertised. Use it for instrumentation, rate limiting, billing, and transports. It is not a way to hide a capability for security.
- A fork with 50 `'on-demand'` plugins can usually keep 3–5 loaded per thread — the budget stays bounded as the catalog grows.
## Where to read next
Costs and trade-offs for each tier.
How `list_capabilities` and `load_capability` work.
---
# Test your oracle
> Two layers — unit tests against a stub runtime, and integration tests that boot the real Nest app against real Matrix and a real LLM.
## Recipe — wire vitest in two modes
The example oracle ships a single config that runs unit tests by default and switches to integration mode with `--mode int`. Copy it.
File: [`vitest.config.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/vitest.config.ts)
```ts
import { defineConfig, mergeConfig } from 'vitest/config';
import nestConfig from '@ixo/vitest-config/nest';
export default defineConfig(({ mode }) => {
if (mode === 'int') {
const merged = mergeConfig(nestConfig, {});
merged.test = {
...merged.test,
include: ['test/**/*.int.test.ts'],
exclude: ['node_modules', 'dist'],
testTimeout: 120_000,
hookTimeout: 120_000,
setupFiles: ['./test/integration/setup.ts'],
fileParallelism: false,
};
return merged;
}
return mergeConfig(nestConfig, {});
});
```
Then in `package.json`:
```json
{
"scripts": {
"test": "vitest run",
"test:integration": "vitest run --mode int"
}
}
```
## The two layers
| Layer | What it boots | When to use it |
| --- | --- | --- |
| **Unit** | Nothing — `createTestRuntime` fakes a `RuntimeContext` | Tool input parsing, middleware hooks, sub-agent prompts in isolation |
| **Integration (Tier A)** | A test runtime — invoke tools directly, no LLM, no HTTP | Verifying upstream-API integration deterministically and for $0 |
| **Integration (Tier B)** | Full `createOracleApp` + real Matrix + real LLM | End-to-end: discovery, routing, multi-turn behaviour |
All three live under `apps/qiforge-example/test/integration/` — read [`weather.int.test.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/test/integration/weather.int.test.ts) for a complete example.
## Setup file
File: [`test/integration/setup.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/test/integration/setup.ts)
```ts
import 'reflect-metadata';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import { config as dotenvConfig } from 'dotenv';
import { expect } from 'vitest';
import { langchainMatchers } from '@langchain/core/testing';
import { Logger } from '@nestjs/common';
const __dirname = dirname(fileURLToPath(import.meta.url));
const appRoot = resolve(__dirname, '../..');
dotenvConfig({ path: resolve(appRoot, '.env') });
dotenvConfig({ path: resolve(appRoot, '.env.integration'), override: true });
expect.extend(langchainMatchers);
process.env.LOG_LEVEL ??= 'warn';
Logger.overrideLogger(['error', 'warn']);
```
The setup file loads `.env` first (runtime config), then layers `.env.integration` on top with `override: true` (test-only credentials). Then it registers LangChain matchers and quiets logs.
Each `.int.test.ts` file declares its required env up front and throws on missing values — see the next step. No silent skips.
## Per-file env gate — fail loud
Every integration test file lists the env it needs and throws at module load if anything is missing.
```ts
const REQUIRED_ENV = [
'MATRIX_BASE_URL',
'MATRIX_ORACLE_ADMIN_USER_ID',
'MATRIX_ORACLE_ADMIN_ACCESS_TOKEN',
'TEST_USER_MNEMONIC',
'TEST_USER_DID',
'ORACLE_DID',
'OPEN_ROUTER_API_KEY',
] as const;
const missing = REQUIRED_ENV.filter((k) => !process.env[k]);
if (missing.length > 0) {
throw new Error(
`weather.int.test.ts requires the following env vars: ${missing.join(', ')}`,
);
}
```
Source: [`weather.int.test.ts` lines 80-94](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/test/integration/weather.int.test.ts).
Silent skips hide broken setups. A throw at file load surfaces immediately when `.env.integration` is missing or incomplete.
## Unit test — createTestRuntime
`createTestRuntime` returns a `TestRuntime` directly (no `{ runtime }` wrapper). Drive it through that object's methods — `listTools`, `invokeTool`, `invokeMiddleware`, `invokeSubAgent`, `listCapabilities`, `getManifest`, `assertNoCollisions`, `assertManifestValid`.
```ts
import { describe, expect, it } from 'vitest';
import { createTestRuntime } from '@ixo/oracle-runtime/testing';
import { WeatherPlugin } from '../src/plugins/weather/index.js';
describe('WeatherPlugin', () => {
it('registers get_current_weather at boot', async () => {
const runtime = await createTestRuntime({
plugins: [new WeatherPlugin()],
config: { WEATHER_DEFAULT_UNITS: 'celsius' },
});
expect(runtime.listTools().map((t) => t.name)).toContain('get_current_weather');
});
it('has valid manifests and no registry collisions', async () => {
const runtime = await createTestRuntime({ plugins: [new WeatherPlugin()] });
expect(() => runtime.assertManifestValid()).not.toThrow();
expect(() => runtime.assertNoCollisions()).not.toThrow();
});
});
```
`createTestRuntime` resolves plugins, populates the six registries, and builds a `RuntimeContext` it hands to tool handlers — but does not boot Nest, talk to Matrix, or call the LLM (every ambient service is a mock). Pass plugin env through `config`. Use for fast, focused tests: tool registration, manifest validity, middleware hooks, and sub-agent prompts in isolation.
`createTestRuntime` options take `config` (plugin env, read as `ctx.config`) — not `env`. Its `mocks.fetch` is exposed for handlers that read it explicitly; it is **not** auto-installed onto `globalThis.fetch`. To stub a plugin's upstream HTTP calls deterministically, use the Tier A `createIntegrationRuntime`, whose `fetch` option does replace `globalThis.fetch`. The full `TestRuntime` surface lives in [`create-test-runtime.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/testing/create-test-runtime.ts).
## Tier A integration — direct invoke
```ts
import { afterAll, beforeAll, describe, expect, test } from 'vitest';
import {
createIntegrationRuntime,
type IntegrationRuntime,
} from '@ixo/oracle-runtime/testing/integration';
import { WeatherPlugin } from '../../src/plugins/weather/index.js';
describe('Tier A — direct invoke', () => {
let runtime: IntegrationRuntime | undefined;
beforeAll(async () => {
runtime = await createIntegrationRuntime({
plugins: [new WeatherPlugin()],
user: { did: process.env.TEST_USER_DID! },
});
}, 60_000);
afterAll(async () => {
if (runtime) await runtime.close();
});
test('get_current_weather({ city: "Berlin" }) returns numeric temperature', async () => {
const raw = await runtime!.invokeTool('get_current_weather', { city: 'Berlin' });
const result = JSON.parse(raw as string) as { temp: number; city: string };
expect(result.city.toLowerCase()).toContain('berlin');
expect(Number.isFinite(result.temp)).toBe(true);
});
});
```
Tier A boots the runtime registries against your plugin list but skips the agent loop entirely. Use it to verify env wiring, upstream-API contracts, and config threading.
`createIntegrationRuntime` accepts more than `plugins` + `user`: `config` (plugin env, defaults to `process.env`), `features`, `delegation`, `capabilities`, `session`, `state`, `identity`, `fetch` (stub upstreams), and `ucan`.
The default `ucan` stub **throws** on `mintInvocation` / `createInvocationFromDelegation`. If the plugin under test mints downstream invocations (memory, sandbox, skills, composio, …), pass a real adapter — boot an oracle with `createIntegrationOracle()` and hand its `bootedOracle.app.ambient.ucan` through as `ucan`, after seeding the user's delegation into that oracle's `UcanService` cache.
## Tier B integration — full agent loop
```ts
import {
ChatClient,
allCaps,
createIntegrationOracle,
mintAuthInvocation,
mintUserDelegation,
waitForMatrixLoaded,
type IntegrationOracle,
} from '@ixo/oracle-runtime/testing/integration';
import * as sdk from 'matrix-js-sdk';
describe('Tier B — agent loop', () => {
let oracle: IntegrationOracle | undefined;
let client: ChatClient | undefined;
beforeAll(async () => {
const matrixClient = sdk.createClient({
baseUrl: process.env.MATRIX_BASE_URL!,
userId: process.env.MATRIX_ORACLE_ADMIN_USER_ID!,
accessToken: process.env.MATRIX_ORACLE_ADMIN_ACCESS_TOKEN!,
});
oracle = await createIntegrationOracle({
config: oracleConfig,
plugins: [new EditorPlugin({ matrixClient }), new WeatherPlugin()],
});
await waitForMatrixLoaded(oracle, 90_000);
// Primary auth: a user-signed invocation (Authorization: Bearer +
// X-Auth-Type: ucan). Downstream authorization: the delegation.
const invocation = await mintAuthInvocation({
userMnemonic: process.env.TEST_USER_MNEMONIC!,
oracleDid: process.env.ORACLE_DID!,
userDid: process.env.TEST_USER_DID,
});
const delegation = await mintUserDelegation({
userMnemonic: process.env.TEST_USER_MNEMONIC!,
oracleDid: process.env.ORACLE_DID!,
userDid: process.env.TEST_USER_DID,
capabilities: allCaps,
});
client = new ChatClient(oracle.baseUrl, { invocation, delegation });
}, 180_000);
afterAll(async () => {
if (oracle) await oracle.close();
});
test('weather request triggers get_current_weather', async () => {
const sid = await client!.createSession();
const stream = client!.stream(sid, "What's the weather in Berlin?");
const calls = [];
for await (const evt of stream) {
if (evt.event === 'tool_call') calls.push(evt.data);
}
expect(calls.some((c) => c.toolName === 'get_current_weather')).toBe(true);
});
});
```
`mintAuthInvocation` (sent as `Authorization: Bearer` + `X-Auth-Type: ucan`) is the **primary** auth artifact the runtime expects; `mintUserDelegation` (sent as `x-ucan-delegation`) carries the capabilities plugins use for downstream authorization. `ChatClient` sends both when supplied — that's the production path. A delegation-only `new ChatClient(url, { delegation })` still authenticates via the migration fallback, which is why older tests pass without an invocation.
Full reference, including multi-turn scenarios: [`weather.int.test.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/test/integration/weather.int.test.ts). Cross-plugin chains: [`agent-scenarios.int.test.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/test/integration/agent-scenarios.int.test.ts). Boot smoke: [`boot.int.test.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/test/integration/boot.int.test.ts).
## Why the vitest config looks like that
Integration tests share a single Matrix admin user. Two test files booting in parallel collide on Matrix's one-time key uploads at the homeserver. Run sequentially.
Real Nest boot, Matrix sync, and LLM round-trips run 5-30s each. 120s leaves headroom for retries; pushing higher means cutting scope, not raising the cap.
`client.createSession()` is a server-side round-trip. Create once in `beforeAll`, reuse for every test. Only mint per-test sessions when the test's whole point is session isolation (first-contact, cross-session recall).
Tier B uses a real model — response wording drifts. Collect `tool_call` events from the stream and assert which tools fired with which args, not on text output.
`createIntegrationOracle` installs a default `resolveModel` hook that overrides the `main` and `subagent` roles with cheaper test models; every other role falls through to the production resolver. Pass your own `hooks` to override — caller hooks merge on top per key, so a caller-supplied `resolveModel` wins.
## What not to do
**Don't loosen assertions to mask failures.** Broadening a regex, adding "or" clauses, or raising tolerances to make a flaky test pass discards the check that catches the bug. Investigate the real failure.
**Don't edit plugin code to make tests pass.** Two test-side retry attempts max per failure, then stop and ask. Plugins are presumed-working production code; tests describe behaviour, not dictate it.
**Don't add skip-real-services flags** (`skipMatrixInit`, `skipGracefulShutdown`) to integration tests as a speed-up. Integration tests must boot the same way production does — that's their point.
## Where to read next
The Weather plugin tests live next to its source.
The `.env.integration` requirements per plugin.
The object handed to your tool handlers in both unit and integration tests.
---
# Identity and auth
> An oracle has a persistent blockchain identity (entity DID + Matrix bot) and per-user auth via UCAN delegation. This guide covers both.
## What identity looks like in code
Two layers — the oracle's persistent identity, set once at boot via env vars; and per-user identity, validated on every request via UCAN headers.
```ts
import { createOracleApp, type OracleConfig } from '@ixo/oracle-runtime';
const config: OracleConfig = {
name: 'My Oracle',
org: 'My Org',
description: 'What this oracle does.',
};
const app = await createOracleApp({ config });
// Runtime fills in `entityDid` from process.env.ORACLE_ENTITY_DID
// and builds the internal OracleIdentity used by every plugin.
```
The `OracleConfig` type lives in [`packages/oracle-runtime/src/plugin-api/types.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/types.ts) — plugins read the resolved identity off `ctx.identity` (a [`OracleIdentity`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/types.ts)).
| Layer | What it is | Where it comes from |
| --- | --- | --- |
| **Oracle identity** | DID + Matrix bot user + on-chain entity | `OracleConfig` (inline) + `ORACLE_DID` / `ORACLE_ENTITY_DID` / `MATRIX_ORACLE_ADMIN_*` env vars |
| **User identity** | Per-request DID + UCAN capabilities | Headers on every incoming request — a user-signed UCAN invocation (`Authorization: Bearer …` + `X-Auth-Type: ucan`) for authentication, plus `x-ucan-delegation` for downstream authorization |
## One-time oracle setup
```sh
qiforge-cli create-entity --no-interactive \
--network devnet \
--oracle-name "My Oracle" \
--org-name "My Org" \
--api-url http://localhost:3000 \
--model "anthropic/claude-sonnet-4"
```
This creates a blockchain entity (`did:ixo:entity:...`), registers linked resources, provisions a Matrix bot account, and writes both `oracle.config.json` and `.env`. Run once per deployment — subsequent deploys reuse the identity.
`create-entity` registers `--api-url http://localhost:4000` by default, but the runtime's own default `PORT` is `3000` (the example above pins the URL to `:3000` to match). If you take the default URL instead, make your oracle reachable at it: either set `PORT=4000` in `.env`, or change the registered URL later with `qiforge-cli update-oracle-api-url`.
See the [CLI reference](/build-an-oracle/reference/cli) for every flag.
```sh
qiforge-cli setup-encryption-key
```
Sets up the P-256 keyAgreement on the oracle's Matrix account room. Without this, the secrets read path returns nothing (acceptable degraded mode for routes that don't need it).
After CLI setup, `.env` contains the core identity vars validated by [`baseEnvSchema`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/config/base-env-schema.ts):
```text
ORACLE_DID=did:ixo:ixo1...
ORACLE_ENTITY_DID=did:ixo:entity:...
ORACLE_NAME=My Oracle
NETWORK=devnet
MATRIX_BASE_URL=https://matrix.ixo.world
MATRIX_ORACLE_ADMIN_USER_ID=@oracle-bot:ixo.world
MATRIX_ORACLE_ADMIN_PASSWORD=...
MATRIX_ORACLE_ADMIN_ACCESS_TOKEN=...
MATRIX_ACCOUNT_ROOM_ID=...
MATRIX_VALUE_PIN=...
MATRIX_RECOVERY_PHRASE=...
SECP_MNEMONIC=... # signs UCAN invocations to downstream services
```
The runtime validates all of these at boot via [`baseEnvSchema`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/config/base-env-schema.ts); missing vars fail with a clear message.
Two auth-tuning vars have safe defaults — set them only to override:
```text
UCAN_AUTH_MAX_TTL_SECONDS=900 # max lifetime the oracle accepts for a user auth invocation (default 15 min)
UCAN_REAUTH_PROMPT_THROTTLE_SECONDS=21600 # min gap between "please re-authorize" prompts (default 6 hours)
```
`UCAN_AUTH_MAX_TTL_SECONDS` bounds the replay window server-side regardless of the TTL the client declares; `UCAN_REAUTH_PROMPT_THROTTLE_SECONDS` keeps a de-authorized user from being nagged on every message.
## What the runtime loads on boot
After Matrix init completes in the background, the runtime reads two further pieces of secret material from the oracle's Matrix account room:
1. **UCAN signing mnemonic** — used by `UcanService` to mint downstream-service invocations.
2. **P-256 encryption key** — used by `SecretsService` to decrypt per-room secrets.
If a key is missing, boot logs a warning naming the CLI command to fix it. Auth-requiring routes return 401 until provisioned; non-authenticated routes (`/`, `/health`, `/docs`, any host or plugin `authExcludedRoutes`) stay reachable.
## Per-request user auth
Two distinct concerns, two distinct artifacts — [`AuthHeaderMiddleware`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/modules/auth/auth-header.middleware.ts) verifies both on every protected route:
- **Authentication (who is calling)** = a user-signed UCAN **invocation**. Short-lived, replay-bounded, sent as `Authorization: Bearer ` + `X-Auth-Type: ucan`. This is the **primary** auth path.
- **Authorization (what the oracle may do downstream)** = the user→oracle **delegation**, sent as `x-ucan-delegation`. Plugins read it to mint downstream-service invocations on the user's behalf.
A bare `x-ucan-delegation` (no invocation) is still accepted **as a migration fallback** for clients that haven't moved to invocation auth yet. New clients should send both: the invocation to authenticate, the delegation for downstream authorization.
| Header | Required | Purpose |
| --- | --- | --- |
| `Authorization: Bearer ` | yes (primary) | The user-signed UCAN invocation — proves *who* is calling. Only read when `X-Auth-Type: ucan` is also present. |
| `X-Auth-Type: ucan` | yes (primary) | Selector that tells the middleware to read the bearer token as a UCAN invocation. Without it the bearer is ignored. |
| `x-ucan-delegation` | fallback / downstream-authz | The user→oracle delegation. Carries the capabilities plugins use to mint downstream invocations. Also accepted as the auth artifact on its own for pre-invocation clients. |
| `x-did` | no | The user's IXO DID (set by SDK; runtime derives the authenticated DID from the invocation/delegation regardless). Not used for authentication. |
| `x-matrix-access-token` | no | For clients that already have a Matrix session. |
| `x-matrix-homeserver` | no | Matrix homeserver for the user. |
| `x-timezone` | no | Propagates to `rtCtx.user.timezone`. |
| `x-request-id` | no | Correlation ID for logs and traces. |
When neither an invocation nor a delegation is present, the middleware returns **401** with:
```text
Missing UCAN authentication: provide Authorization: Bearer with X-Auth-Type: ucan, or an x-ucan-delegation header
```
A malformed or expired invocation returns **401** `Invalid UCAN invocation`.
`AuthHeaderMiddleware`:
When `X-Auth-Type: ucan` + `Authorization: Bearer …` are present, [`validateUcanInvocation`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/modules/auth/validate-ucan-invocation.ts) verifies the signature and audience (the oracle's `ORACLE_DID`) and rejects any invocation whose lifetime exceeds `UCAN_AUTH_MAX_TTL_SECONDS` (default 900s). Results are cached by the token's SHA-256 hash with **TTL = the invocation's own expiry**, so reusing the same token until it expires (JWT-style) doesn't re-hit Blocksync. The invocation's signer becomes `req.authData.did`.
When no invocation is sent, [`validateUcanDelegation`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/modules/auth/validate-ucan-delegation.ts) authenticates the request from `x-ucan-delegation` instead. Delegation results cache with TTL = the delegation's expiry (or a 3-minute fallback when it declares none).
A delegation is public and shareable, so the middleware acts on it downstream **only when its issuer DID equals the authenticated DID**. When they differ, the delegation is ignored downstream — `req.authData.ucanDelegation.raw` is set to `''`, and plugins branch on `raw.length === 0`. This stops a client from pairing their own invocation with someone else's delegation to make the oracle act on that person's behalf.
The next middleware (`RuntimeContextBuilder`) reads `req.authData` to build the `rtCtx.user` field handed to every tool handler — see the [Runtime context reference](/build-an-oracle/reference/runtime-context).
## Opt routes out of auth
Two mechanisms to expose public routes — host-level and plugin-level. Both merge onto the runtime's built-in exclusion list: `/` (the JSON landing payload), `/health`, `/docs`, and `/docs/(.*)`.
```ts
import { RequestMethod } from '@nestjs/common';
import { createOracleApp, type AuthExcludedRoute } from '@ixo/oracle-runtime';
const HOST_AUTH_EXCLUDED_ROUTES: AuthExcludedRoute[] = [
{ path: 'version', method: RequestMethod.GET },
];
const app = await createOracleApp({
config,
nestModules: [VersionModule],
authExcludedRoutes: HOST_AUTH_EXCLUDED_ROUTES,
});
```
Reference: [apps/qiforge-example/src/main.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/main.ts).
```ts
override getAuthExcludedRoutes(): AuthExcludedRoute[] {
return [{ path: 'weather/now', method: RequestMethod.GET }];
}
```
Reference: [`weather.plugin.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/plugins/weather/weather.plugin.ts).
Any other plugin or host route returns 401 without a UCAN header — exclusion is path-and-method specific.
## Mint downstream UCAN invocations
When a plugin calls a downstream service on the user's behalf, it does not reuse the user's invocation — it mints a fresh one signed by the oracle's `SECP_MNEMONIC`, derived from the user's stored delegation. The minted invocation goes out to the downstream service the same way the user authenticates to *this* oracle: `Authorization: Bearer ` + `X-Auth-Type: ucan`.
```ts
handler: async (args, rtCtx: RuntimeContext) => {
// No signing key (mnemonic not yet provisioned) → minting is a no-op.
if (!rtCtx.ucan.hasSigningKey()) {
return JSON.stringify({ error: 'Oracle signing key not provisioned.' });
}
// Resolve the service URL to its did:web identifier, then mint against it.
const serviceDid = await rtCtx.ucan.resolveServiceDid(
'https://downstream-service.example',
);
if (!serviceDid) {
return JSON.stringify({ error: 'Could not resolve downstream service DID.' });
}
const invocation = await rtCtx.ucan.mintInvocation(
{ did: serviceDid, capability: 'ixo:downstream' },
// Claim the ability the user's delegation grants — see below.
{ can: 'downstream/*' },
);
const resp = await fetch('https://downstream-service.example/data', {
headers: {
Authorization: `Bearer ${invocation}`,
'X-Auth-Type': 'ucan',
},
});
// ...
}
```
The full UCAN helper surface on `rtCtx.ucan`:
| Method | Purpose |
| --- | --- |
| `mintInvocation(target, opts?)` | Mint a service-targeted invocation from the user's cached delegation. `target` = `{ did, capability }`; `opts.can` is the ability claimed (default `'*'`); `opts.skipCache` forces a fresh signature. |
| `requireCapability(resource, action)` | Throws if the user's delegation doesn't include the capability. |
| `hasCapability(resource, action)` | Returns a boolean — non-throwing variant. |
| `resolveServiceDid(serviceUrl)` | Resolves a service URL to its `did:web:...` identifier. Returns `null` when the DID document is missing or has no `id`. |
| `hasSigningKey()` | Returns `true` once the oracle has loaded its Ed25519 signing mnemonic. Gate tool registration on this — without a key, minting is a no-op. |
| `createInvocationFromDelegation(car, serviceUrl, capability, opts?)` | Mint from a directly-supplied delegation CAR (rather than the per-user cached one). Returns `{ invocation }` or `{ error }`. Used by the editor's `mint_invocation` tool. |
See [`packages/oracle-runtime/src/modules/ucan/`](https://github.com/ixoworld/ixo-oracles-boilerplate/tree/main/packages/oracle-runtime/src/modules/ucan) for the service implementation.
### Claim only what you were granted
An invocation says what the oracle is *doing right now*; the delegation says what the user *permitted*. The service accepts the invocation only if the delegation covers it — and coverage is narrower than it looks. A granted ability covers a claim when it is:
- `'*'` — covers everything, or
- **exactly equal** to the claim, or
- a `prefix/*` pattern matching it (`memory/*` covers `memory/read`).
Nothing else. In particular **`'*'` is not a wildcard when you *claim* it** — a `'*'` claim is satisfiable only by a `'*'` grant, because `'*'` does not start with `memory/`:
```ts
// delegation grants { can: 'memory/*', with: 'ixo:memory' }
// ❌ over-claim — refused: "Delegated capability not found"
await rtCtx.ucan.mintInvocation({ did, capability: 'ixo:memory' });
// ✅ claims exactly what was granted
await rtCtx.ucan.mintInvocation(
{ did, capability: 'ixo:memory' },
{ can: 'memory/*' },
);
```
The service must **register** the ability you claim. It matches an invocation's `can` by strict string equality, so a service that only defines `'*'` rejects a `memory/*` invocation as an unknown capability *before* authorization is considered. When narrowing a claim, roll out in this order:
1. service accepts the narrow ability **and** `'*'`
2. oracle switches to claiming the narrow ability
3. service tightens or upgrades
Reversing steps 1 and 2 produces a 401 on every call.
## What plugins can and cannot do
- **Can:** read `rtCtx.user.did`, `rtCtx.user.matrixUserId`, `rtCtx.user.ucanDelegation`, `rtCtx.user.timezone`.
- **Can:** call `rtCtx.secrets.getIndex()` / `getValues()` to read per-room secrets.
- **Can:** mint downstream invocations via `rtCtx.ucan.mintInvocation`.
- **Cannot:** override the oracle's identity per request.
- **Cannot:** issue UCANs as anyone other than the oracle itself.
## Where to read next
Which routes are auth-protected and which are public.
`create-entity`, `setup-encryption-key`, and the rest of identity setup.
The full `rtCtx.user`, `rtCtx.ucan`, `rtCtx.secrets` surface.
Every identity-related env var, declared and validated.
---
# Observability
> LangSmith tracing auto-wires from env vars. Listen to plugin lifecycle via onPluginStatusChange. Log through ctx.logger for plugin-scoped output.
## Turn on LangSmith in one block
LangChain auto-wires tracing when these env vars are present in `process.env` — the runtime never reads them, only declares them in the [base env schema](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/config/base-env-schema.ts) so they show up in `qiforge-cli env` output.
```text
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=ls-...
LANGSMITH_PROJECT=my-oracle
LANGSMITH_ENDPOINT=https://api.smith.langchain.com # optional, defaults to LangSmith cloud
```
| Env var | Required for tracing | Purpose |
| --- | --- | --- |
| `LANGSMITH_TRACING` | yes | Set to `true` to enable tracing. Anything else (or unset) disables it. |
| `LANGSMITH_API_KEY` | yes | API key from your LangSmith workspace. |
| `LANGSMITH_PROJECT` | yes | Project name traces land under. |
| `LANGSMITH_ENDPOINT` | no | Override for self-hosted LangSmith. Defaults to LangSmith cloud. |
All four `LANGSMITH_*` vars are `optional()` in the [base env schema](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/config/base-env-schema.ts) — boot never fails when they're absent. "Required" above means "required for tracing to work," not a boot-time requirement.
No code changes needed. Set the vars and every LLM call, tool invocation, and middleware hook produces a span in the LangSmith UI. Unset them to turn it off.
## What QiForge gives you out of the box
Three signals — that's the whole surface. Bring your own log aggregator and metrics.
1. **LangSmith traces** — env-driven, covered above.
2. **Plugin status events** — `app.onPluginStatusChange(handler)`.
3. **Plugin-scoped logger** — `ctx.logger` (boot) and `rtCtx.logger` (per-request).
## Subscribe to plugin status events
```ts
app.onPluginStatusChange((event) => {
Logger.log(
`[plugin] ${event.plugin} ${event.from} → ${event.to}` +
(event.reason ? ` (${event.reason})` : ''),
);
});
```
Live example: [apps/qiforge-example/src/main.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/main.ts).
The plugin that reliably emits transitions is `matrix` — it starts `pending` at boot and flips to either `loaded` or `failed` once Matrix init completes in the background.
```ts
app.onPluginStatusChange((event) => {
if (event.plugin === 'matrix' && event.to === 'loaded') {
// safe to send messages, mint UCANs, etc.
}
});
```
Use this to gate operations on Matrix being up, or to alert when it fails.
The event shape:
```ts
{
plugin: string;
from: 'pending' | 'loaded' | 'failed';
to: 'pending' | 'loaded' | 'failed';
reason?: string;
}
```
## Capture background errors with onError
```ts
app.onError((err, source) => {
Logger.error(`[runtime] ${source}: ${err.message}`);
});
```
`source` is a short label like `'matrix-init'`. Use this for background failures (Matrix sync errors, key setup failures). Boot-time errors (env validation, manifest validation, dependency cycles) throw from `createOracleApp` itself — your `try/catch` around the call sees them.
## Log the boot resolution snapshot
`app.plugins.status()` returns the loader's resolution snapshot — log it once at boot so operators see what came up.
```ts
const status = app.plugins.status();
Logger.log(`[boot] loaded plugins: ${status.loaded.join(', ') || '(none)'}`);
if (status.excluded.length > 0) {
Logger.log(
'[boot] excluded plugins:',
status.excluded.map((e) => `${e.plugin} (${e.reason})`).join(', '),
);
}
```
Shape of the snapshot — the `PluginStatusReport` defined in [`packages/oracle-runtime/src/bootstrap/create-oracle-app.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/create-oracle-app.ts):
```ts
{
loaded: string[]; // plugin names that came up
excluded: Array<{
plugin: string;
reason: string; // e.g. "auto-detect precondition not met (COMPOSIO_API_KEY)"
}>;
softDepGaps: Array<{ plugin: string; missing: string }>;
}
```
The loader tracks an internal `cause` (`feature_false` / `auto_detect_missing` / `cascaded`) for each excluded plugin, but the public `app.plugins.status()` report drops it — `excluded` items are `{ plugin, reason }` only.
## Use the plugin-scoped logger
Every `PluginContext` and `RuntimeContext` carries a `logger` field auto-prefixed with the plugin's name — see [`Logger` in types.ts](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/types.ts).
```ts
override getTools(ctx: PluginContext): PluginTool[] {
ctx.logger.log('Building weather tools');
// ...
}
handler: async (args, rtCtx: RuntimeContext) => {
rtCtx.logger.log(`Looking up ${args.city}`);
// ...
}
```
The `Logger` interface:
| Method | Required | Purpose |
| --- | --- | --- |
| `log(message, ...optional)` | yes | Info-level output. |
| `error(message, ...optional)` | yes | Error-level output. |
| `warn(message, ...optional)` | yes | Warn-level output. |
| `debug(message, ...optional)` | no | Debug-level — implementation-defined. |
| `verbose(message, ...optional)` | no | Verbose-level — implementation-defined. |
| `child(bindings)` | no | Returns a logger with additional context fields. Falls back to the same logger if not implemented. |
Use NestJS's default `Logger` format in development (`pnpm dev` shows it nicely); pipe to your aggregator in production. Override the global logger with `createOracleApp({ logger })` if you want a custom format — see [createOracleApp reference](/build-an-oracle/reference/createoracleapp).
## UI-facing tool-call events
For showing the user what the agent did, the runtime emits typed events via `rtCtx.emit`. The bundled clients (Portal, Slack, Matrix) consume these and render the right UI — you only emit them yourself when writing a custom client.
```ts
rtCtx.emit.toolCall({ /* ... */ });
rtCtx.emit.actionCall({ /* ... */ });
rtCtx.emit.renderComponent({ /* ... */ });
rtCtx.emit.reasoning({ /* ... */ });
rtCtx.emit.browserToolCall({ /* ... */ });
rtCtx.emit.router({ /* ... */ });
rtCtx.emit.messageCacheInvalidation({ /* ... */ });
```
The seven event types are defined on `RuntimeContext.emit` in [`types.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/types.ts).
## Where to read next
Logs and probes in production.
Custom observability via plugin middleware hooks.
Full `rtCtx.emit` and `rtCtx.logger` surface.
Every env var the runtime declares.
---
# Deploy your oracle
> Build a production bundle, persist the Matrix store, wire health probes. Platform-agnostic with a reference Dockerfile.
## Reference Dockerfile
QiForge has no runtime dependency on itself — anywhere Node 22+ runs, an oracle runs. This Dockerfile is the shortest path from `pnpm dev` to a container you can ship.
```dockerfile
FROM node:22-slim AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:22-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
RUN mkdir -p /data
ENV MATRIX_STORE_PATH=/data/matrix-storage
ENV SQLITE_DATABASE_PATH=/data/sqlite
EXPOSE 3000
CMD ["node", "dist/main.js"]
```
Everything below explains the moving parts. The reference oracle (`apps/qiforge-example`) builds with `pnpm build` and runs with `node dist/main.js` — see [`apps/qiforge-example/src/main.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/apps/qiforge-example/src/main.ts).
## Prerequisites
- Node.js 22+
- A reachable Matrix homeserver
- A persistent volume for the Matrix store and SQLite checkpointer
- Core env vars (per [`baseEnvSchema`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/config/base-env-schema.ts))
- Optional: Redis (only if `credits` or `tasks` plugins are loaded)
## Build and ship
```sh
pnpm install --frozen-lockfile
pnpm build
```
For the example app this runs `tsc -p tsconfig.build.json` and outputs `dist/`. Your `package.json`'s `start` script should be `node dist/main.js`.
Production `.env` (or your platform's secret manager) needs every core var plus the vars for each plugin you've kept.
```text
NODE_ENV=production
PORT=3000
ORACLE_NAME=My Oracle
CORS_ORIGIN=https://your-portal.example
NETWORK=mainnet
# Identity (validated by baseEnvSchema)
ORACLE_DID=did:ixo:ixo1...
ORACLE_ENTITY_DID=did:ixo:entity:...
SECP_MNEMONIC=...
RPC_URL=https://rpc.ixo.world
BLOCKSYNC_GRAPHQL_URL=https://blocksync.ixo.world/graphql
# Auth tuning (optional — safe defaults)
UCAN_AUTH_MAX_TTL_SECONDS=900 # max accepted lifetime of a user auth invocation (default 15 min)
UCAN_REAUTH_PROMPT_THROTTLE_SECONDS=21600 # gap between re-authorize prompts (default 6 hours)
# Matrix
MATRIX_BASE_URL=https://matrix.ixo.world
MATRIX_ORACLE_ADMIN_USER_ID=@my-oracle-bot:ixo.world
MATRIX_ORACLE_ADMIN_PASSWORD=...
MATRIX_ORACLE_ADMIN_ACCESS_TOKEN=...
MATRIX_ACCOUNT_ROOM_ID=...
MATRIX_VALUE_PIN=...
MATRIX_RECOVERY_PHRASE=...
MATRIX_STORE_PATH=/data/matrix-storage # MUST persist across restarts
# Storage
SQLITE_DATABASE_PATH=/data/sqlite
# LLM provider
LLM_PROVIDER=openrouter
OPEN_ROUTER_API_KEY=...
# or LLM_PROVIDER=nebius + NEBIUS_API_KEY
# Optional tracing
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=...
LANGSMITH_PROJECT=my-oracle
```
Per-plugin vars come on top — see [environment variables reference](/build-an-oracle/reference/environment-variables) for the full list. Boot validates every var declared by every loaded plugin's `configSchema`; missing required vars fail loudly.
Identity and Matrix keys are provisioned via the CLI — see [identity and auth](/build-an-oracle/develop/identity-and-auth) for the full flow.
```sh
qiforge-cli create-entity --no-interactive --network mainnet ...
qiforge-cli setup-encryption-key
```
## Persistent volumes
Two things must survive restarts. Lose either and you lose state.
| Path | What it stores | What breaks if you lose it |
| --- | --- | --- |
| `MATRIX_STORE_PATH` (default `/data/matrix-storage`) | Matrix sync state, room keys, decrypted cache | Full re-sync on next boot; potential decrypt issues |
| `SQLITE_DATABASE_PATH` (e.g. `/data/sqlite`) | Per-user LangGraph checkpointer | Thread continuity across restarts |
In Docker Compose:
```yaml
services:
oracle:
image: my-oracle:latest
environment:
- MATRIX_STORE_PATH=/data/matrix-storage
- SQLITE_DATABASE_PATH=/data/sqlite
volumes:
- oracle-data:/data
volumes:
oracle-data:
```
On Railway / Fly.io: attach a persistent volume mounted at `/data`.
Redis is required only if you load the `credits` plugin (or the `tasks` plugin once it ships). Without those, skip it entirely.
```yaml
services:
redis:
image: redis:7
volumes:
- redis-data:/data
oracle:
environment:
- REDIS_URL=redis://redis:6379
depends_on:
- redis
volumes:
redis-data:
```
Pass the Redis client to `CreditsPlugin` explicitly — see [enable bundled plugins](/build-an-oracle/develop/enable-bundled-plugins).
## Health probes
The framework exposes `GET /health`, always public, never goes through `AuthHeaderMiddleware`. Returns 200 once Nest is up. The built-in auth-excluded routes are `/` (a JSON landing payload), `/health`, `/docs`, and `/docs/(.*)` — everything else requires auth.
Docker:
```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -fsS http://localhost:3000/health || exit 1
```
Kubernetes:
```yaml
livenessProbe:
httpGet:
path: /health
port: 3000
```
Matrix init runs in the background. The process accepts requests immediately after Nest boots. If you need to delay traffic until Matrix is up, subscribe via `app.onPluginStatusChange` and signal your platform when `matrix` transitions to `loaded` — see [observability](/build-an-oracle/develop/observability).
Useful for debugging in production behind a private network. To remove it in public deployments, host-supply your own Nest module that re-mounts `/docs` behind auth, or block it at your reverse proxy.
## CORS
`CORS_ORIGIN` controls who can call your oracle from a browser.
| Value | Behaviour |
| --- | --- |
| `*` | Open. Credentials disabled (browsers require a specific origin to send credentials). |
| `https://your-portal.example` | Specific origin; `credentials: true` enabled. |
The framework allows these headers on every request:
```text
Content-Type, Authorization,
x-ucan-delegation, x-matrix-access-token, x-matrix-homeserver,
x-did, x-request-id, x-auth-type, x-timezone
```
`Authorization` and `x-auth-type` carry the primary invocation-auth path (`Authorization: Bearer ` + `X-Auth-Type: ucan`); `x-ucan-delegation` carries the downstream-authorization delegation. If you replicate CORS at a reverse proxy, mirror this full list — dropping `x-auth-type` breaks browser clients on the primary auth path.
## Logging
The runtime uses NestJS's default `Logger`. Pipe stdout/stderr to your aggregator. Override the bootstrap logger with `createOracleApp({ logger })` if you need a custom format — see [createOracleApp reference](/build-an-oracle/reference/createoracleapp).
For structured tracing instead of free-form logs, set the LangSmith env vars — see [observability](/build-an-oracle/develop/observability).
## Graceful shutdown
The runtime registers a `SIGTERM` / `SIGINT` handler that drains in order: (1) **flushes the per-user checkpoint to Matrix**, (2) closes the Nest app, (3) shuts down the Matrix client, (4) runs any plugin teardowns. Each step is isolated — a failure in one is logged and the rest still run.
That first step is why a clean `SIGTERM` matters: an abrupt `SIGKILL` skips the checkpoint upload and loses recent thread state. See [`graceful-shutdown.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/graceful-shutdown.ts).
```ts
// To disable (rare — only when your platform's process manager
// owns signal handling and you've verified it calls app.close()):
const app = await createOracleApp({
config,
skipGracefulShutdown: true,
});
```
Disable graceful shutdown only when your platform's process manager guarantees a clean `app.close()` on termination. Otherwise you'll skip the checkpoint flush and risk Matrix sync corruption.
## Where to read next
Every var per plugin, exhaustively.
LangSmith, plugin lifecycle, logger.
Entity setup and per-request UCAN auth.
Boot errors, Matrix issues, common runtime errors.
---
# What is QiForge?
> QiForge is the framework for building Agentic Oracles on IXO — AI agents with a verifiable identity, encrypted per-user storage, and a plugin runtime.
## The one-pager
An **Agentic Oracle** is an AI agent that owns a verifiable identity on IXO, talks to users through encrypted per-user Matrix rooms, authenticates every request via UCAN delegation, and composes its capabilities from plugins (compiled in) and skills (discovered at runtime).
**QiForge is the framework that lets you ship one.** The runtime (`@ixo/oracle-runtime`) handles bootstrap, auth, the agent loop, the checkpointer, Matrix wiring, and 16 bundled plugins. Your oracle is a thin `main.ts` plus whatever custom plugins you write.
## Three layers, one diagram
```mermaid
graph LR
Client["Client SDK (browser, mobile, server)"]
subgraph Runtime["@ixo/oracle-runtime"]
Boot["createOracleApp"]
Agent["LangChain agent per request"]
Plugins["Bundled + your plugins"]
Boot --> Agent
Plugins --> Agent
end
Matrix[("Matrix (encrypted per-user rooms)")]
Chain[("IXO chain (DIDs + UCAN)")]
Client -->|"HTTP + UCAN"| Agent
Agent -.encrypted.-> Matrix
Agent -.identity + auth.-> Chain
```
You write the green box (and any custom plugins). Everything else is shipped in the framework.
## Plugin vs skill — the most important distinction
| | **Plugin** | **Skill** |
| --- | --- | --- |
| **What it is** | TypeScript extension to the runtime | Executable capsule from the IXO skills registry |
| **Where it lives** | Inside your oracle's bundle | Remote, fetched at request time |
| **When it loads** | At boot, via `createOracleApp` | Per request, when the agent decides to use one |
| **Who authors it** | Oracle developer (you, or the framework team for bundled) | Anyone — published to the public registry |
| **How the agent uses it** | Tools bound directly to the LLM | The agent calls `search_skills` + `sandbox_run` (plugin tools) to find and execute one |
Use a plugin when only your team needs to extend the oracle and you want type safety + full host access. Use a skill when the community should be able to publish capabilities without touching your code.
[Read the full Plugin vs Skill comparison](/build-an-oracle/understand/plugins-vs-skills)
## What you can build
- **Domain agents** — climate, MRV, carbon DAO assistants.
- **Workflow copilots** — drive an AG-UI portal, fill forms, navigate browsers.
- **Integration hubs** — Gmail, GitHub, Linear, Slack, Notion via the bundled `composio` plugin.
- **Skill runners** — discover and run community-authored capsules in a per-user Linux sandbox.
## Where to go next
The three layers, bootstrap flow, and what's pluggable vs fixed.
Build a working oracle in 10 minutes with the CLI.
Source: [`createOracleApp`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/create-oracle-app.ts).
---
# Architecture
> The three layers — your oracle, the runtime, the bundled plugins — and how they fit together.
## The three layers
A QiForge deployment has exactly three layers. Knowing what each one owns saves a lot of confusion.
```mermaid
graph TB
subgraph Your["YOUR ORACLE (apps/your-oracle/)"]
Main["main.ts (~30 lines)"]
Plugins["Your plugins"]
Modules["Your Nest modules (optional)"]
end
subgraph Runtime["@ixo/oracle-runtime"]
Core["Always-on Nest modules Sessions · Messages · WS · Secrets · UCAN · Auth · Subscription · Throttler · Checkpointer"]
Bundled["16 bundled plugins memory · skills · sandbox · editor · agui · portal · firecrawl · domain-indexer · composio · slack · credits · user-preferences · matrix-group-chats · tasks · calls · vfs"]
Agent["LangChain agent built per request"]
end
Matrix[("Matrix homeserver")]
Chain[("IXO chain")]
LLM[("LLM provider")]
Your --> Runtime
Core --> Agent
Bundled --> Agent
Agent -.encrypted.-> Matrix
Agent -.UCAN.-> Chain
Agent -.invoke.-> LLM
User["User"] -->|"HTTP + UCAN"| Agent
```
### Layer 1 — Your oracle
Typically `apps/your-oracle/src/main.ts` (a `createOracleApp(...)` call), a `config.ts` for identity, and any custom plugins or Nest modules.
**You own:** the entry point, your oracle's identity, custom plugins, custom Nest modules.
**You don't own:** the agent loop, the graph state shape, the checkpointer, the auth flow, the meta-tools, the bundled plugins' internals.
### Layer 2 — `@ixo/oracle-runtime`
The framework. Owns the bootstrap (`createOracleApp`), the always-on Nest modules (Sessions, Messages, WebSocket, Secrets, UCAN, Auth, Subscription, Throttler, Health), the per-request agent builder, the four always-on agent middleware (capability gate, tool validation, tool-repetition guard, tool retry — plus page-context and safety-guardrail when their hooks are set), the Matrix-backed SQLite checkpointer, and the plugin API.
You never edit this layer. Updates come via `pnpm update @ixo/oracle-runtime`.
### Layer 3 — Bundled plugins
16 plugins shipped inside the runtime package, each independently toggleable via `features`. See the [Plugin catalog](/build-an-oracle/reference/bundled-plugins/overview) for what each one does.
## Bootstrap, in one picture
```mermaid
graph LR
A["resolve plugins (features + autoDetect)"] --> B["topo sort by dependsOn"]
B --> C["validate manifests"]
C --> D["compose env schema base + plugin configSchemas"]
D --> E["populate 6 registries"]
E --> F["build RuntimeAppModule"]
F --> G["NestFactory.create"]
G --> H["schedule Matrix init (background)"]
G --> I["listen on PORT"]
```
The HTTP server accepts requests immediately; auth-requiring routes 401 until Matrix init finishes and the UCAN signing mnemonic loads.
Defined in [`create-oracle-app.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/bootstrap/create-oracle-app.ts).
## The six registries
Plugins don't talk to the runtime directly. They push contributions into one of six registries at boot; the agent builder reads from them per request.
| Registry | Holds | Collision rule |
| --- | --- | --- |
| ToolRegistry | All plugin tools | Flat namespace — collision is a boot error |
| SubAgentRegistry | All plugin sub-agents | Flat namespace — collision is a boot error |
| MiddlewareRegistry | All plugin middleware | Ordered topologically; no names |
| ManifestRegistry | All plugin manifests | Title collision = soft warn |
| ConfigSchemaRegistry | All plugin Zod schemas | Merged; later wins with warning |
| SharedStateRegistry | Plugin shared-state accessors | Flat namespace — collision is a boot error |
Source: [`packages/oracle-runtime/src/registries/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/registries/).
## What's pluggable vs fixed
| Pluggable | Fixed |
| --- | --- |
| Plugin set (bundled toggles + your plugins) | Agent loop (LangChain `createAgent`) |
| `nestModules` (your Nest modules) | Graph state shape (except one new `loadedPlugins` field) |
| `authExcludedRoutes` (host + plugin) | Checkpointer (Matrix-backed SQLite) |
| LLM provider (env-driven) | Auth flow (UCAN delegation) |
| `hooks.checkpointerForUser` (advanced) | The four always-on middleware |
| System prompt (via `OracleConfig.prompt`) | The Tier-1 block format |
| Anything inside a custom plugin | The six registries |
If you find yourself wanting to swap something on the "Fixed" side, you're probably trying to do something the framework explicitly doesn't allow — and you should either work within the model (e.g. write a plugin) or fork the runtime.
## Model resolution and hooks
How does the agent pick which LLM to call, and how do you change it?
**Provider is env-driven.** `LLM_PROVIDER` selects the whole per-role model map: `openrouter` (default) or `nebius`, each with its matching API key (`OPEN_ROUTER_API_KEY` / `NEBIUS_API_KEY`). The actual model id for each role (`main`, `subagent`, `utility`, `vision`, `guard`, …) is baked into the provider's model map — there is no env var to change a single role's model id on its own.
**Resolution is per-role.** Different jobs use different models. The main agent resolves the `main` role; sub-agents and utility calls resolve `subagent` / `utility` through `ctx.llm.get(role)`. The runtime resolves the main model as `resolveModel('main')`, defaulting to the provider map.
**To change the main agent's model, pass `hooks.resolveModel`** to `createOracleApp`. Spreading `params` keeps the provider's fallback models and latency sorting:
```ts
import { createOracleApp, getProviderChatModel } from '@ixo/oracle-runtime';
const app = await createOracleApp({
config,
plugins,
hooks: {
// role is 'main' | 'subagent' | 'utility'. Spreading params preserves
// the provider's fallback models + latency sort.
resolveModel: (role, params) =>
getProviderChatModel(role, {
...params,
...(role === 'main' && { model: 'google/gemini-3.1-flash-lite' }),
}),
},
});
```
The runtime only calls `resolveModel('main')` itself, so a hook that special-cases `'main'` swaps **only the main agent's model**; sub-agents and utility calls keep the provider defaults unless your own code routes them through the hook too.
`resolveModel` is one of several `hooks` — the framework's advanced extension surface. Others include `checkpointerForUser` (swap the per-user checkpointer), `safetyModel` (enable the safety-guardrail middleware), `getRoomTitle` (enable the page-context middleware), and prompt-block overrides like `operationalMode`. See the [`createOracleApp` reference](/build-an-oracle/reference/createoracleapp) for the full list.
## Read next
What happens between user message and streamed response.
The recipe for wiring `createOracleApp`.
---
# Request lifecycle
> What happens between an incoming user message and the streamed response — auth, controller, agent build, checkpointer, tool calls, save.
## The full path
```mermaid
sequenceDiagram
autonumber
participant Client
participant Auth as AuthHeaderMiddleware
participant Sub as SubscriptionMW (if credits loaded)
participant Throttle as ThrottlerGuard
participant Ctrl as MessagesController
participant Agent as createMainAgent
participant Ckpt as Checkpointer
participant LLM
participant Tools as Plugin tools
Client->>Auth: POST /messages (UCAN + DID)
Auth->>Sub: validated
Sub->>Throttle: credits OK
Throttle->>Ctrl: rate OK
Ctrl->>Agent: build per-request agent
Agent->>Ckpt: load thread state
Ckpt-->>Agent: { messages, loadedPlugins, ... }
Agent->>LLM: invoke
LLM-->>Agent: text + tool calls
Agent->>Tools: execute calls
Tools-->>Agent: results
Agent->>LLM: re-invoke
LLM-->>Agent: final text
Agent->>Ckpt: save state
Agent-->>Client: stream SSE
```
## What each stop does
| Stop | Responsibility |
| --- | --- |
| **AuthHeaderMiddleware** | Validates the UCAN delegation (signature, expiry, audience, capability). Resolves the user's DID and attaches `RuntimeUserContext`. Failure → 401, agent never runs. |
| **SubscriptionMiddleware** | Only when the `credits` plugin is loaded. Checks user balance. Empty + `DISABLE_CREDITS !== 'true'` → 402. |
| **ThrottlerGuard** | Per-user rate limit, registered as a global Nest guard (`APP_GUARD`), not an HTTP middleware. Nest runs middleware before guards, so it still gates after auth and subscription. Breach → 429. |
| **MessagesController** | Reads the inbound message, looks up the session (creates on first contact), hands off to the agent builder. Returns SSE. |
| **createMainAgent** | Builds the per-request `RuntimeContext`, reads cached registries, runs `getRequestTools` / `getRequestSubAgents`, composes the prompt, returns a LangChain agent compiled against the per-user checkpointer. |
| **Checkpointer** | Loads `{ messages, loadedPlugins, userContext, ... }` for this `thread_id` (= session id) from per-user SQLite (synced to Matrix in the background). |
| **LLM invocation** | Composed prompt + bound tools + state messages go to the model resolved for the `main` role (provider from `LLM_PROVIDER`, or a `hooks.resolveModel` override). Plugin middleware fires (`beforeModel`, `wrapModelCall`, `afterModel`). |
| **Tool calls** | The agent loop dispatches plugin tools, sub-agent tools, meta-tools, and browser tools. Each emits a typed event so the client can render "Agent called X". |
| **Save state + stream** | The final state is checkpointed; the assistant message streams over SSE. WS events for intermediate steps have already shipped. |
## Where each plugin hook fires
| Hook | Fires during |
| --- | --- |
| `getTools`, `getSubAgents`, `getMiddlewares`, `getSharedState` | Read from boot cache during agent build |
| `getRequestTools`, `getRequestSubAgents` | Per-request agent build |
| `middlewares[].beforeModel` / `wrapModelCall` / `afterModel` | Around each LLM invocation |
| `middlewares[].wrapToolCall` | Around each tool call |
| Tool / sub-agent `handler` | When the LLM calls the tool |
| `getNestModules` controllers | Any HTTP request to a plugin route |
## Failure modes
| What fails | Effect |
| --- | --- |
| UCAN validation | 401. Agent never runs. |
| Credit check | 402. Agent never runs. |
| Rate limit | 429. Agent never runs. |
| Checkpointer load | Error logged; falls back to empty state. Turn proceeds. |
| LLM | Propagates to the client by default. A middleware can catch it by wrapping the call in `wrapModelCall` with `try`/`catch`. |
| Tool handler | One retry on validation error; otherwise the error becomes a `ToolMessage` the agent sees. |
| Sub-agent init | `Promise.allSettled` — log and skip; rest of turn continues. |
## Read next
What `RuntimeContext` carries.
How `load_capability` mutates `loadedPlugins` mid-turn.
Source: [`packages/oracle-runtime/src/graph/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/graph/) and [`packages/oracle-runtime/src/modules/messages/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/modules/messages/).
---
# Plugin vs Skill
> Both extend what your oracle can do — but they're fundamentally different. Plugins ship with your oracle. Skills are discovered and executed at runtime.
## The one-sentence difference
> **A plugin is code your oracle ships with. A skill is a capsule your oracle fetches and runs in a sandbox.**
Both let an agent do things it otherwise couldn't. But the *mechanism*, *lifecycle*, and *author* are usually different.
## Side by side
| | **Plugin** | **Skill** |
| --- | --- | --- |
| **Format** | TypeScript class extending `OraclePlugin` | Folder with `SKILL.md` + scripts (Python, shell, etc.) |
| **Lives where** | Inside your oracle's npm bundle | IXO skills registry (`ai-skills` repo) |
| **Lifecycle** | Loaded at boot | Discovered + executed per request |
| **Who authors** | Oracle developer | Anyone — community-contributable |
| **How agent uses** | Tools bound directly to the LLM | Agent calls `search_skills` → `sandbox_run` |
| **Execution** | In-process Node.js | Per-user Linux sandbox |
| **Side effects** | Full host access (DB, FS, network, Nest DI) | Sandbox-scoped |
| **Update** | Redeploy the oracle | Publish a new version — no oracle restart |
## Both in the same turn
The cleanest way to see the distinction is to watch them work together:
```mermaid
sequenceDiagram
autonumber
participant Agent as Main agent
participant SkillsP as skills plugin
participant SandboxP as sandbox plugin
participant Registry as ai-skills registry
participant Box as Per-user sandbox
Note over Agent: 1. Use a PLUGIN to find a SKILL
Agent->>SkillsP: search_skills({ query: "invoice" })
SkillsP->>Registry: HTTP search
Registry-->>Agent: [{ name: "invoice-generator", ... }]
Note over Agent: 2. Use a PLUGIN to run the SKILL
Agent->>SandboxP: sandbox_run({ cid, shell: "python run.py ..." })
SandboxP->>Box: provision + execute
Box-->>Agent: { stdout, files: ["invoice-Q3.pdf"] }
```
`skills` and `sandbox` are **plugins** — compiled into the oracle, exposing tools the LLM calls. `invoice-generator` is a **skill** — a folder in the registry the oracle never imported, fetched and executed at request time.
## When to write which
| Write a plugin when… | Write a skill when… |
| --- | --- |
| You need type safety + full DI (DB, Nest services, Matrix client) | The capability is a self-contained scripted workflow |
| The capability needs an HTTP route (webhook, OAuth callback) | It should be community-discoverable |
| You need to inject behaviour into every turn (middleware) | You want to update it without redeploying |
| The capability is proprietary or oracle-specific | Sandbox isolation is acceptable |
| You need tight integration with the agent (a sub-agent) | The implementation is Python / shell / a binary |
## Can a plugin call a skill?
Yes. The `skills` and `sandbox` plugins expose normal tools (`search_skills`, `sandbox_run`, ...). Your custom plugin can call them directly if you have a deterministic flow. Usually the agent orchestrates instead.
## Can a skill use plugin features?
Not directly. Skills run in a sandbox; they don't see plugins or Nest DI. They get user secrets as `x-os-*` env vars, an optional `skills_invocation` UCAN, network (per sandbox policy), and a read-only mount of their own folder. If a skill needs plugin data, the agent passes it in as an argument to `sandbox_run`.
## Read next
The recipe.
The 16 bundled plugins — including `skills` and `sandbox`.
Source: [`packages/oracle-runtime/src/plugins/skills/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/skills/) and [`packages/oracle-runtime/src/plugins/sandbox/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/sandbox/).
---
# Anatomy of a plugin
> The ten contribution channels an OraclePlugin can declare and where each one plugs into the runtime.
## Where each hook plugs in
```mermaid
graph LR
subgraph PluginCls["Your OraclePlugin"]
Manifest["manifest"]
ConfigSchema["configSchema"]
Deps["dependsOn / softDependsOn"]
AutoDetect["autoDetect"]
GetTools["getTools / getRequestTools"]
GetSA["getSubAgents / getRequestSubAgents"]
GetMW["getMiddlewares"]
GetNM["getNestModules"]
GetAuthEx["getAuthExcludedRoutes"]
GetShared["getSharedState"]
end
subgraph Runtime
Loader["Plugin loader"]
Composer["Schema composer"]
Regs["6 registries"]
RAM["RuntimeAppModule"]
AuthMW["AuthHeaderMiddleware.exclude(...)"]
Agent["Per-request agent"]
end
AutoDetect --> Loader
Deps --> Loader
Manifest --> Regs --> Agent
ConfigSchema --> Composer
GetTools --> Regs
GetSA --> Regs
GetMW --> Regs
GetShared --> Regs
GetNM --> RAM
GetAuthEx --> AuthMW
```
The runtime never reaches inside your plugin. Every contribution flows through one of these channels.
## The ten contribution channels at a glance
The first four are **declarative properties** (data the runtime reads); the rest are **hooks** (methods the runtime calls).
| Channel | What it contributes | When called |
| --- | --- | --- |
| `manifest` | The agent-facing interface — title, summary, `whenToUse`, examples, visibility | Boot (validation + Tier-1 composition) |
| `configSchema` | A Zod object merged into the global env schema | Boot (env validation) |
| `dependsOn` / `softDependsOn` | Plugin ordering constraints | Boot (topo sort) |
| `autoDetect(env)` | Decide whether to load based on env | Boot (resolution) |
| `getTools(ctx)` / `getRequestTools(rtCtx)` | LLM-callable tools | Boot cache + per-request |
| `getSubAgents(ctx)` / `getRequestSubAgents(rtCtx)` | Focused inner agents (auto-wrapped as tools) | Boot cache + per-request |
| `getMiddlewares(ctx)` | LangChain `AgentMiddleware` objects (`beforeAgent` / `beforeModel` / `wrapModelCall` / `afterModel` / `wrapToolCall` / `afterAgent` hooks) | Boot cache |
| `getNestModules(ctx?)` | Nest modules (HTTP routes, services) | Boot, once |
| `getAuthExcludedRoutes()` | Routes that skip UCAN auth | Boot, once |
| `getSharedState()` | Read-only accessors other plugins can consume | Boot, once |
A middleware can declare any of the six LangChain hooks above. There is **no `onError` middleware hook** — to handle an LLM or tool error inside a middleware, wrap the call in `wrapModelCall` / `wrapToolCall` with `try`/`catch`.
Boot-time hooks (`getTools`, `getSubAgents`, `getMiddlewares`) are called once and the results are cached for every request. The per-request variants run on every agent build — use them only when the contribution depends on live state (e.g. `loadedPlugins`, current user, `rtCtx.shared.*`).
## Two contexts
| Context | Holds | Hooks that receive it |
| --- | --- | --- |
| `PluginContext` | `config`, `identity`, `availablePlugins`, `logger` | `getTools`, `getSubAgents`, `getMiddlewares`, `getNestModules` |
| `RuntimeContext` | Everything in `PluginContext` plus `user`, `session`, `history`, `secrets`, `matrix`, `ucan`, `llm`, `emit`, `shared`, `abortSignal`, `loadedPlugins` | `getRequestTools`, `getRequestSubAgents`, all handlers, all middleware hooks |
A tool registered via boot-time `getTools` still gets a fresh `RuntimeContext` when its handler fires. "Boot-time" refers to *registration*, not *execution*. See [Contexts](/build-an-oracle/understand/contexts) for the field list.
## Error semantics
| Hook throws… | Effect |
| --- | --- |
| `autoDetect`, manifest validation, `configSchema`, `getNestModules`, `getAuthExcludedRoutes`, `getSharedState` | Boot fails. Loud failures because misconfiguration in production is worse than no plugin. |
| `getTools` / `getSubAgents` / `getMiddlewares` (boot warm or per-request) | Logged with plugin name; plugin's contribution skipped for that build. Other plugins continue. |
| Tool / sub-agent `handler` | Becomes an error `ToolMessage` the agent sees; one retry on validation errors. |
| Middleware hook | Propagates as a turn error. |
## Read next
Scaffold and ship one.
The agent-facing interface.
Source: [`packages/oracle-runtime/src/plugin-api/oracle-plugin.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/oracle-plugin.ts).
---
# Prompt anatomy
> How the runtime assembles the system prompt — 16 sections, what's automatic, what you configure.
## Overview
Every time the main agent is invoked, the runtime compiles a system prompt from up to 16 sections. Most sections are automatic — the runtime builds them from env state, loaded plugins, and live session data. You are only responsible for four fields in `config.prompt`. Everything else is handled for you.
## The 16 sections
Sections appear in this fixed order. "Always present" means the section is included on every turn regardless of configuration; "conditional" means the section only appears when a specific condition is met.
| # | Section | Present |
| --- | --- | --- |
| 1 | **Oracle section** — identity preamble | Always |
| 2 | **Capabilities note** — `config.prompt.capabilities` text | Conditional |
| 3 | **Capability block** — Tier-1 plugin manifest list | Conditional |
| 4 | **Operating principles** — fixed bullets + optional communication style | Always |
| 5 | **Custom instructions** — `config.prompt.customInstructions` + loaded operating guides | Conditional |
| 6 | **Working with files** — file-handling guidance | Always |
| 7 | **What you know about the user** — memory context (6 slots) | Conditional |
| 8 | **Current time** — `{currentTime}` (`{timezone}`) | Always |
| 9 | **Current entity** — `{DID}` | Conditional |
| 10 | **Available user secrets** | Conditional |
| 11 | **User preferences** | Conditional |
| 12 | **Operational mode** — general (default) or editor variants | Always |
| 13 | **Composio context** — Composio tool context | Conditional |
| 14 | **Editor section** — editor plugin state | Conditional |
| 15 | **Slack formatting constraints** — Slack output rules | Conditional |
| 16 | **Degraded services** — failed-init notices | Conditional |
### Section details
**1. Oracle section**
The identity preamble. If `config.prompt.opening` is set, it is used verbatim. Otherwise the runtime generates a fallback from the other top-level fields: `"You are {name}, an AI agent operated by {org}. {description}."` (shorter variants when `org` or `description` are absent). See the [`createOracleApp` config reference](/build-an-oracle/reference/createoracleapp) for the exact fallback logic.
**2. Capabilities note**
Rendered if `config.prompt.capabilities` is non-empty. Appears immediately above the plugin capability block. No header is added by the runtime — include your own heading in the field value if you want one.
**3. Capability block**
Auto-generated list of all loaded plugins with `visibility='always'` (Tier-1). Each entry is more than a one-liner: the plugin name and summary, then up to three sub-bullets pulled from the manifest — `When to use:` (first two `whenToUse` triggers), `Avoid for:` (first `whenNotToUse`), and `Example:` (the first `examples` entry). Manifest quality therefore drives prompt size. You never write this section; the runtime builds it from the loaded plugin set. See [the 5,000-token capability-block budget](#the-5000-token-capability-block-budget) below — it is a warn-only soft budget, not an auto-trim.
**4. Operating principles**
Always present. Contains a fixed set of operating-principle bullets (currently nine) covering discovery-first tool use, clarifying questions, reporting results, surfacing failures, delegating with context, and completing-then-stopping. If `config.prompt.communicationStyle` is non-empty, it is injected here as an additional paragraph after those bullets. If `communicationStyle` is absent, the section still appears — just without the custom style block.
**5. Custom instructions**
Conditional. Rendered as a `## Custom Instructions` section when there is anything to put in it. Its body is your `config.prompt.customInstructions` field plus any operating guides contributed by on-demand capabilities the agent has loaded for this thread (e.g. the Flow Builder guide). When nothing contributes, the whole section is omitted and costs zero tokens.
**6. Working with files**
Always present. Hardcoded guidance on how the agent should handle file uploads, attachments, and generated file output.
**7. What you know about the user**
Conditional on the memory plugin being loaded and at least one context slot being populated. Contains up to 6 slots fetched by `UserContextFetcher` before the agent is compiled: `identity`, `work`, `goals`, `interests`, `relationships`, `recent`. See [How memory reaches the prompt](/build-an-oracle/reference/bundled-plugins/memory) for the pre-fetch details.
**8. Current time**
Always present. Stamped at agent-compile time with the user's wall-clock time and resolved timezone.
**9. Current entity**
Conditional on `state.currentEntityDid` being set in the agent state. Surfaces the active entity DID so the agent can route entity-aware tool calls correctly.
**10. Available user secrets**
Conditional on the secrets service having entries for the current user. Lists secret names (not values) so the agent can reference them by name in tool calls.
**11. User preferences**
Conditional on the user-preferences plugin being loaded and the user having saved preferences. May include `agentName`, `language`, `tone`, `formality`, and `customInstructions`.
**12. Operational mode**
Always present, but content varies. The runtime ships a `general` default (`DEFAULT_OPERATIONAL_MODE`) and editor-mode variants supplied by the editor plugin. Any other mode arrives through `hooks.operationalMode` — a plugin or host override — not a built-in runtime selection. See [Model resolution & hooks](/build-an-oracle/understand/architecture#model-resolution-and-hooks).
**13. Composio context**
Conditional on the Composio plugin loading successfully. Auto-injected Composio account and connection context so the agent knows which third-party integrations are available for the current user.
**14. Editor section**
Conditional on the editor plugin being active in the current session. Describes the active document, cursor position, and editor affordances.
**15. Slack formatting constraints**
Conditional on the session client being Slack. Appends Slack-specific formatting rules to prevent the agent from using markdown that Slack does not render correctly (e.g. tables).
**16. Degraded services**
Appended after the main prompt when one or more plugins fail their init. Lists the failed services and tells the agent not to attempt their tools for this turn.
## The 5,000-token capability block budget
Section 3 (the Tier-1 capability block) is the only section with a token budget — and it is a **warn-only soft budget, not a cap**. If the combined manifest text of all `visibility='always'` plugins exceeds 5,000 tokens, the runtime logs a warning naming the largest manifests so you can decide what to mark `on-demand`. Nothing is trimmed automatically: the full block is always emitted with every `always` plugin in it. Degrading verbosity is an operator decision, not a runtime guess — so keep manifest text (`summary`, `whenToUse`, `examples`) concise to keep the block small.
## What you actually need to write in config.ts
Only four things — all optional:
| Field | What to write | What happens if you skip it |
| --- | --- | --- |
| `config.prompt.opening` | A paragraph describing the oracle's identity, purpose, and domain expertise. | Runtime generates a generic fallback from `name`, `org`, `description`. |
| `config.prompt.communicationStyle` | One or two paragraphs on tone, vocabulary, and response style. | The operating-principles section appears without a custom style block. |
| `config.prompt.capabilities` | A short section (with your own heading) listing what the oracle can do. | The capability-notes section is omitted; only the auto-generated plugin list appears. |
| `config.prompt.customInstructions` | Standing guidance that should hold across every turn (house rules, escalation policy, domain constraints). | The `## Custom Instructions` section is omitted unless a loaded capability contributes a guide. |
Time, memory, user preferences, Composio context, entity DID, secrets, editor state, Slack constraints, and degraded-service notices are all injected automatically. You do not need to mention them in `config.ts`, instruct the agent to fetch them, or account for them in your opening paragraph.
Because memory context is pre-fetched and already in the system prompt by turn 1, you do not need to write instructions like "recall the user's context before responding" in `config.prompt`. The agent already has the user's identity, goals, and recent history before it processes the first message.
## Minimal config.ts example
```ts
// src/config.ts
import type { OracleConfig } from '@ixo/oracle-runtime';
export const config: OracleConfig = {
name: 'Aria',
org: 'Acme Climate',
description: 'Carbon-project advisory oracle for portfolio managers.',
prompt: {
opening: `You are Aria, the carbon-project advisory oracle operated by Acme Climate.
You help portfolio managers track project status, assess compliance, and draft
stakeholder communications across Verra VCS, Gold Standard, and REDD+ frameworks.`,
communicationStyle: `Be precise and data-driven. Lead with numbers and deadlines.
Use plain English — avoid jargon unless the user demonstrates familiarity.
When something is uncertain, say so explicitly.`,
capabilities: `## What Aria can do
- Retrieve live project status and registry issuance data.
- Draft verification reports, CORSIA letters, and board summaries.
- Search and cite methodology documents from the oracle knowledge base.`,
customInstructions: `Always cite the registry framework (Verra VCS, Gold Standard, REDD+)
behind any compliance claim. Never approve a credit issuance on the user's behalf —
draft it and ask them to confirm.`,
},
};
```
Everything else in the 16-section prompt — plugin tools, current time, user memory, preferences, Composio context — is assembled by the runtime from the loaded plugins and session state.
## Related
Full reference for config.prompt fields and fallback logic.
How UserContextFetcher pre-loads memory into section 6.
What controls which plugins appear in the capability block.
How plugins declare the manifest text that feeds section 3.
---
# Manifest
> The plugin's interface to the LLM. Structured metadata the runtime composes into the system prompt and feeds to the discovery meta-tools.
## What it is
Every plugin declares a `manifest` of type `PluginManifest`. It is not human documentation — it is structured metadata the LLM reads. The runtime uses it three ways:
1. The **Tier-1 prompt block** — the "Available Capabilities" list rendered for `always` plugins on every turn.
2. **`list_capabilities`** — the meta-tool the agent calls to enumerate what it can load.
3. **`load_capability`** — the meta-tool that returns a manifest in full when the agent activates a plugin mid-conversation.
## What each field is for
| Field | Purpose |
| --- | --- |
| `title` | Human-readable label. Distinct from `name` (kebab-case unique ID). |
| `summary` | One-line description. Heads each Tier-1 entry for `always` plugins as `- **{name}** — {summary}`. |
| `whenToUse` | Trigger phrases. The single most important field — quality here determines whether the LLM picks the plugin when it should. Required when `visibility !== 'silent'`. The first two render into Tier-1 as a `When to use:` sub-bullet. |
| `whenNotToUse` | Anti-pattern phrases. Use to disambiguate from overlapping plugins. The first renders into Tier-1 as an `Avoid for:` sub-bullet. |
| `examples` | Few-shot user-message → tool-call bindings. Cross-checked at boot — every example's `tool` must reference a tool this plugin registers. The first renders into Tier-1 as an `Example:` sub-bullet. |
| `tags` | Lowercase labels for search. |
| `category` | One of: `data`, `communication`, `automation`, `memory`, `integration`, `ui`, `auth`, `observability`, `core`. |
| `visibility` | `always` / `on-demand` / `silent`. See [Visibility tiers](/build-an-oracle/understand/visibility-tiers). |
| `stability` | `stable` / `beta` / `experimental`. Surfaced as a hint to the agent. |
Full schema table and validation rules live at [Manifest schema reference](/build-an-oracle/reference/manifest-schema).
## Where the agent sees it
```mermaid
graph LR
Manifest["manifest"] --> Tier1["Tier-1 prompt block (if visibility=always)"]
Manifest --> ListCaps["list_capabilities()"]
Manifest --> LoadCap["load_capability({ names })"]
Tier1 --> System["System prompt every turn"]
ListCaps --> Agent["Agent"]
LoadCap --> Agent
```
Tier-1 plugins pay token cost on every turn forever — and not just the summary: each entry also renders `whenToUse`, `whenNotToUse`, and the first `example` as sub-bullets, so manifest length is prompt length. `on-demand` plugins cost nothing in the prompt until the agent loads them. `silent` plugins are never listed in Tier-1 and can't be loaded through the meta-tools (note: a silent plugin's tools, if it ships any, are still bound and callable — `silent` is not a security boundary).
## Writing a good manifest
- **Be specific in `whenToUse`.** "Weather questions" is too vague. "User asks about current weather, temperature, precipitation, or wind in any city" is precise.
- **Use `whenNotToUse` to disambiguate** when your plugin conceptually overlaps with another (Firecrawl vs Sandbox vs Domain Indexer is the canonical case).
- **Examples should cover the typical patterns**, not just the obvious one.
- **Keep `tags` lowercase.** They're for search ranking, not display.
- **Pick `visibility` deliberately.** Default to `on-demand`; promote to `always` only when the agent needs the plugin on nearly every turn.
The manifest is the part of your plugin the LLM reads most. Treat it like a system prompt: write it carefully, test it with real user messages, and iterate. A vague manifest is the most common reason a plugin doesn't fire when it should.
## Read next
Where the manifest lives in code.
Exact field types and constraints.
Source: [`PluginManifest`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/types.ts).
---
# Visibility tiers
> Three modes — always, on-demand, silent — that control whether a plugin is advertised in the prompt, discoverable via the meta-tools, and callable by the LLM.
## All tools are bound; visibility controls exposure
A common misconception: that `visibility` decides whether a plugin's tools are *bound* to the agent. It doesn't. **Every plugin tool — `always`, `on-demand`, and `silent` — is bound to the agent at compile time.** What `visibility` controls is three separate things: whether the plugin is **advertised** in the Tier-1 prompt block, whether it is **discoverable** via the meta-tools, and whether the LLM can **call** its tools right now (the [CapabilityGate](#how-on-demand-gating-actually-works) enforces this last one per model call).
## The three tiers
| Visibility | Callable by the LLM | Listed in Tier-1 prompt | Discoverable via `list_capabilities` / `load_capability` |
| --- | --- | --- | --- |
| `always` | Always | Yes | Yes |
| `on-demand` | After `load_capability({ names: [...] })` for this thread | No | Yes |
| `silent` | Always (if it ships tools) | No | No |
**Default: `on-demand`.** A plugin without an explicit `visibility` is treated as `on-demand`.
`silent` does **not** hide a tool from the model. A silent tool passes the CapabilityGate exactly like an `always` tool — it is bound and the LLM can call it on any turn. `silent` only means "not advertised in Tier-1 and not loadable via the meta-tools." In practice silent plugins contribute middleware or HTTP routes rather than LLM tools. Do not use `silent` as a security boundary — it is not one.
## What each tier is for
| Tier | Use for | Token cost per turn |
| --- | --- | --- |
| `always` | Plugins the agent needs on nearly every turn (e.g. `memory`, `skills`, `user-preferences`) | ~80–150 tokens for the Tier-1 entry + tool schemas (~100–300 per tool) on every turn |
| `on-demand` | Plugins useful in narrow situations (e.g. `portal`, `firecrawl`, `composio`) | No Tier-1 entry; tool schemas only count once the plugin is loaded |
| `silent` | Plugins that act through middleware or HTTP only (e.g. `credits`, `calls`) | No Tier-1 entry and not discoverable; any tools it ships are still bound |
## How on-demand gating actually works
Because all tools are bound at compile time, "loading" a plugin is **not** about binding — it's about lifting a filter. The `CapabilityGateMiddleware` runs on every model call. It reads the thread's `loadedPlugins` set and trims the tool list the model sees:
- `always` and `silent` tools → always pass the gate (the model can call them).
- `on-demand` tools → pass only when their plugin is in `loadedPlugins`.
`createAgent({ tools })` freezes the bound list, so a `load_capability` call updating state mid-run would otherwise have no effect until the next request rebuilt the agent. Filtering inside the middleware lets a load decision take effect on the very next LLM call. The gate is one of the [always-on middlewares](/build-an-oracle/understand/architecture).
## Picking a tier
```mermaid
graph TD
A["Does the LLM need to call this plugin's tools?"]
A -->|"No — it's middleware/HTTP only"| Silent["silent"]
A -->|Yes| B["Will the LLM use it on most turns?"]
B -->|Yes| Always["always"]
B -->|No| OnDemand["on-demand"]
```
Promoting to `always` is a deliberate budget choice — it pays Tier-1 tokens on every turn. `silent` is for plugins that don't need to be advertised or discovered (they work through middleware or HTTP). Most plugins should stay on the default — `on-demand`.
A fork with 50 plugins, all `on-demand`, can typically keep 3–5 loaded per thread. The agent learns to discover via `list_capabilities` and load via `load_capability`. Token cost stays bounded; the catalog can grow.
## Loading and unloading
`on-demand` plugins live in a per-thread `loadedPlugins` state field. The set is monotonic — it only grows. Loading a plugin makes its tools available for the rest of that thread; a new thread starts with an empty `loadedPlugins`.
There is no "unload" operation. If a thread accumulates too many loaded plugins, starting a new thread is the reset.
## Per-tool override
A plugin can override visibility per tool by setting `visibility` on the `PluginTool` itself. This lets one plugin ship a mix — e.g. one `always` tool and several `on-demand` tools — without splitting into two plugins.
## Read next
How to declare each tier in plugin code.
How `list_capabilities` and `load_capability` work.
Source: [`PluginManifest`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/types.ts).
---
# Shared state
> How plugins expose read-only values for other plugins to consume, without coupling their code paths. Flat namespace, read-only contract, lazy accessors.
## What it is
Shared state is a typed, read-only channel one plugin uses to expose a derived value to other plugins. A producer plugin declares an accessor; consumer plugins read it through `RuntimeContext.shared`.
It is the only sanctioned way for two plugins to share live data. Plugins do not import each other's classes or call each other's services.
```mermaid
graph LR
Prod["Plugin A getSharedState()"]
Reg["SharedStateRegistry (boot)"]
RtCtx["RuntimeContext.shared (per request)"]
Cons["Plugin B tool handler"]
Prod --> Reg --> RtCtx --> Cons
```
## When to use it
| Good fit | Bad fit |
| --- | --- |
| A derived value other plugins might want (e.g. `userProfile`, `lastWeatherQuery`, `activePageId`) | Mutable state with multiple writers |
| Cheap reads that abstract graph-state field names | Large blobs consumers might read but ignore |
| Stable shape worth declaring on `SharedAccessors` for typed reads | Plugin internals other plugins shouldn't see |
If only your plugin reads it, keep it private.
## The contract
- **Read-only.** Consumers never mutate the value. Producers update their own internal state via tool handlers or middleware; the accessor just exposes a view.
- **Flat namespace.** Keys live in a single global record. Two plugins registering the same key fail boot — no quiet overrides.
- **Lazy.** Each accessor is a function `(state, runCtx) => value`. It runs every time a consumer reads. Cheap reads only; cache in the producer if derivation is expensive.
- **Optional.** A consumer must assume the producer may not be installed. `rtCtx.shared.someKey` can be `undefined`. Pair shared state with `softDependsOn` so the soft dependency is explicit in the `app.plugins.status()` report.
## Typed reads
`SharedAccessors` is an open interface. Producer plugins extend it via TypeScript declaration merging so consumers see the right type on `rtCtx.shared.someKey`. Without merging, the key types as `unknown` and consumers cast.
Use declaration merging when the shape is stable; skip it for ad-hoc keys.
## Read next
Producer and consumer code, declaration merging, the collision check.
Pair shared state with `softDependsOn`.
Source: [`SharedAccessors`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/types.ts).
---
# Plugin context vs runtime context
> Plugin code sees two contexts — PluginContext (boot-time, no user) and RuntimeContext (per-request, authenticated user). Knowing which one you're in is the first thing to learn.
## Two contexts, two lifetimes
| Context | Lifetime | Has user? | Where you receive it |
| --- | --- | --- | --- |
| `PluginContext` | Boot (cached) | No | `getTools`, `getSubAgents`, `getMiddlewares`, `getNestModules` |
| `RuntimeContext` | Fresh per request | Yes (authenticated) | Tool handlers, sub-agent handlers, middleware hooks, `getRequestTools`, `getRequestSubAgents` |
The distinction matters because the wrong context can't see what your code needs. A boot-time hook can't ask "who is the user" — there is none yet. A per-request hook can't be used to register a Nest module — it's already too late.
```mermaid
graph LR
Boot["Boot"] --> PCtx["PluginContext"]
PCtx --> Hooks1["getTools getSubAgents getMiddlewares getNestModules"]
Req["Per request"] --> RCtx["RuntimeContext"]
RCtx --> Hooks2["getRequestTools getRequestSubAgents tool/sub-agent handlers middleware hooks"]
```
## What each context carries
### `PluginContext` — boot-time, no user
- `config` — the merged, Zod-validated env (base + every plugin's `configSchema`).
- `identity` — your oracle's identity (`name`, `org`, `description`, `entityDid`, `prompt`).
- `availablePlugins` — set of names of all loaded plugins; useful for soft-dependency branching.
- `logger` — a plugin-scoped logger.
### `RuntimeContext` — per request, authenticated user
Everything in `PluginContext`, plus:
| Field | Purpose |
| --- | --- |
| `user` | DID, Matrix user ID, UCAN delegation, timezone |
| `session` | Thread ID (= `session.id`), client (`portal` / `matrix` / `slack`), request ID, room ID |
| `history` | `messages`, `recent(n)`, `userContext`, typed `state` view |
| `secrets` | `getIndex()`, `getValues(keys)` for per-user secrets |
| `blobStore` | Short-TTL keyed store for values the model must never echo verbatim (UCAN invocation CARs, JWTs): `put()` returns an opaque `blob_` id, `get()` resolves it server-side, `isValidBlobId()` checks the format. Blobs are scoped to the issuing user's DID |
| `matrix` | Scoped methods: `postToRoom`, `getRoomState`, `getEventById` |
| `ucan` | `requireCapability`, `hasCapability`, `mintInvocation`, `resolveServiceDid`, `hasSigningKey()`, `createInvocationFromDelegation()` |
| `llm` | `get(role, params)` for the configured provider — `role` is `'main'` / `'subagent'` / `'utility'` |
| `emit` | Typed event emitter (`toolCall`, `actionCall`, `renderComponent`, `reasoning`, ...) |
| `shared` | Typed reads from other plugins' `getSharedState` |
| `loadedPlugins` | Set of plugin names loaded for this thread |
| `toolCallId` | The current tool call's id — needed when a tool returns a LangGraph `Command` |
| `abortSignal` | Request-scoped abort |
Full field list and types: [RuntimeContext reference](/build-an-oracle/reference/runtime-context).
## Registration vs execution
A tool registered via boot-time `getTools(ctx)` still receives a fresh `RuntimeContext` when its handler fires. "Boot-time" applies to *when the tool is registered*, not to *when it runs*. Both contexts coexist over the lifetime of any tool.
## Choosing the right hook
If a hook exists in both forms, pick by what your code reads:
| If your code needs… | Use |
| --- | --- |
| Only config + identity | Boot-time hook (`getTools(ctx)`) |
| Live state (`loadedPlugins`, `userContext`, browser tools) | Per-request hook (`getRequestTools(rtCtx)`) |
| The authenticated user's DID / timezone at *registration* time | Per-request hook |
| Stable tool list | Boot-time hook |
When both hooks fire on the same plugin, their outputs are merged — no need to choose one or the other.
## Read next
See both contexts in real code.
The full field list.
Source: [`packages/oracle-runtime/src/runtime-context/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/runtime-context/) and [`PluginContext` / `RuntimeContext`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugin-api/types.ts).
---
# Meta-tools and loading
> The agent always has two built-in tools — list_capabilities and load_capability — that power dynamic discovery and per-thread loading of on-demand plugins.
## Why meta-tools exist
A QiForge oracle can have dozens of plugins installed. Listing every one in the prompt would explode the system prompt and the tool list the model has to reason over. Instead, `on-demand` plugins are **not advertised** at boot — the agent discovers them mid-conversation via two built-in meta-tools and loads what it needs for the current thread.
Under the hood every tool is bound to the agent at compile time; a `CapabilityGateMiddleware` hides on-demand tools from the model until their plugin is loaded (see [How on-demand gating works](/build-an-oracle/understand/visibility-tiers#how-on-demand-gating-actually-works)). So "loading" lifts a per-turn filter — it does not bind new tools.
The runtime registers these meta-tools itself. They are not authored by any plugin and cannot be overridden.
## The two tools
| Tool | Purpose |
| --- | --- |
| `list_capabilities` | Returns every visible plugin with its summary, visibility, loaded status, category, and tags. The agent uses this to scan what's available. |
| `load_capability` | Takes `names: string[]` — an array, so the agent can load several capabilities in one call. Marks each plugin as loaded for the current thread, returns each one's full manifest plus tool descriptions, and appends a `ToolMessage` so the agent sees the manifests in conversation history on the same turn. |
`load_capability` accepts a batch and is processed per name: it throws when a named plugin doesn't exist, throws when one is `silent` (silent plugins are internal, not agent-loadable), and treats a name that is `always` or already loaded as a no-op (`alreadyAvailable: true`). Batching is preferred — the prompt instructs the agent to pass every capability it needs in a single `load_capability({ names: [...] })` call rather than making multiple calls in the same turn.
## The discovery loop
```mermaid
sequenceDiagram
autonumber
participant User
participant Agent
participant ListCaps as list_capabilities
participant LoadCap as load_capability
participant State
User->>Agent: "What's the weather in Berlin?"
Note over Agent: No weather tool bound — plugin is on-demand
Agent->>ListCaps: list_capabilities()
ListCaps-->>Agent: [..., { name: 'weather', loaded: false }, ...]
Agent->>LoadCap: load_capability({ names: ['weather'] })
LoadCap->>State: loadedPlugins += ['weather']
LoadCap-->>Agent: manifests + tools
Note over Agent: Capability gate now passes get_current_weather
Agent->>User: (calls get_current_weather, returns answer)
```
In practice the agent often skips `list_capabilities` and goes straight to `load_capability` when the user's intent maps obviously to a manifest the agent has seen recently. It falls back to listing when the intent is ambiguous.
## The `loadedPlugins` state field
The single new field added to the graph state. Per-thread, monotonically growing — `load_capability` only appends, never removes. The checkpointer persists it for free.
Loading a plugin in thread A does not affect thread B. Every new conversation rediscovers fresh.
## What the agent sees in the system prompt
For `always` plugins, the runtime renders a Tier-1 prompt block. Each entry is more than a one-liner — the plugin name and summary, then sub-bullets pulled straight from the manifest (`whenToUse`, `whenNotToUse`, and the first `example`):
```text
## Available Capabilities
- **memory** — Durable memory across conversations.
- When to use: user refers to something from a past chat; you learn a durable fact
- Avoid for: one-off lookups that don't need to persist
- Example: "remember I prefer metric units" → save_memory({"text":"prefers metric"})
- **skills** — Discover and run IXO skill capsules.
- When to use: a packaged skill could generate a file, report, or run a workflow
- Example: "make me an invoice" → search_skills({"query":"invoice"})
For more capabilities, call `list_capabilities()` to see what else is available,
then `load_capability({ names: ["cap1", "cap2"] })` to make their tools available in one call.
```
Because `whenToUse`, `whenNotToUse`, and the first example all render into Tier-1, manifest quality directly drives prompt size. `on-demand` plugins are *not* listed here — the agent has to discover them. `silent` plugins are never listed and can't be loaded through the meta-tools.
## Token cost
| Item | Cost per turn |
| --- | --- |
| Tier-1 prompt block | ~80–150 tokens per `always` plugin (name + summary + whenToUse/avoid/example sub-bullets) |
| Bound tool schemas | ~100–300 tokens per tool |
| Meta-tools themselves | ~100 tokens combined (negligible) |
For a fork with 50 `on-demand` plugins, the cost on a turn where none are loaded is the same as a fork with zero such plugins. That's the point.
## Read next
The three modes and how to pick.
`loadedPlugins` and the rest of the graph state.
Source: [`packages/oracle-runtime/src/meta-tools/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/meta-tools/).
---
# createOracleApp
> The single entry point of every QiForge oracle. Full signature, every option, the returned OracleApp shape, and the boot sequence the function runs.
## Overview
`createOracleApp(opts: CreateOracleAppOptions): Promise` resolves the plugin set, validates env, populates registries, builds the dynamic `RuntimeAppModule`, bootstraps NestJS, and returns an `OracleApp` whose `listen()` runs `beforeListen` callbacks then starts the HTTP server.
Matrix init runs in the background — the returned promise resolves as soon as Nest is built. Status flips via `onPluginStatusChange`.
```ts
import { createOracleApp } from '@ixo/oracle-runtime';
```
## CreateOracleAppOptions
```ts
export interface CreateOracleAppOptions {
config: OracleConfig;
features?: Partial>;
manifestOverrides?: Partial>;
plugins?: OraclePlugin[];
nestModules?: Array;
authExcludedRoutes?: AuthExcludedRoute[];
bundledPlugins?: OraclePlugin[];
env?: NodeJS.ProcessEnv;
skipMatrixInit?: boolean;
skipGracefulShutdown?: boolean;
logger?: Logger;
hooks?: MainAgentHooks;
}
```
- **Type:** `OracleConfig`
- **Required:** yes
Inline oracle config — `name`, optional `org`, optional `description`, optional `prompt`. The runtime fills in `entityDid` from the `ORACLE_ENTITY_DID` env var, so don't put it here.
```ts
config: {
name: 'My Oracle',
org: 'My Org',
description: 'What this oracle is for',
prompt: {
opening: '...',
communicationStyle: '...',
capabilities: '...',
customInstructions: '...',
},
}
```
**prompt sub-fields** — all four are optional. Omit any you don't need.
**`opening`**
Used **verbatim** as the identity preamble — the very first section of the assembled system prompt. Write it as a complete paragraph describing who the oracle is and what it does.
If `opening` is absent, the runtime generates a fallback from the other top-level fields:
- `name` + `org` + `description` present → `"You are {name}, an AI agent operated by {org}. {description}."`
- `org` missing → `"You are {name}. {description}."`
- Both `org` and `description` missing → `"You are {name}."`
Providing `opening` gives you full control over tone, persona, and framing — use it whenever the generated fallback is too generic.
**`communicationStyle`**
Injected inside the hardcoded **"## Operating principles"** section of the prompt. If set, it appears as a paragraph within that section immediately after the 7 standard operating-principle bullets. If empty or absent, the field is omitted entirely — the operating-principles section still appears, just without the custom style block.
Use this to steer the agent's tone: formal, concise, empathetic, domain-specific jargon preferences, and so on.
**`capabilities`**
Rendered **above** the Tier-1 plugin capability block (the auto-generated list of `visibility='always'` plugins). No header is added by the runtime around this text — write your own heading if you want one. If empty or absent, the field is omitted entirely.
Use this to describe high-level skills the oracle has that aren't obvious from the plugin manifest alone — things like domain expertise, supported workflows, or what the agent should proactively offer.
**`customInstructions`**
Free-form standing guidance injected **verbatim** into a dedicated `## Custom Instructions` section of the system prompt. Use it for house style, domain rules, compliance guardrails, or any directive that doesn't fit `communicationStyle` (tone) or `capabilities` (the elevator pitch). The runtime renders the section only when there is content.
Operating guides contributed by on-demand capabilities the agent loads mid-thread (e.g. the Flow Builder guide, which appears once `flows` is in `loadedPlugins`) are appended **after** your text in the same section — so they cost no tokens on turns where the capability is never loaded.
```ts
prompt: {
customInstructions: `Always cite the registry document ID when quoting issuance data.
Never promise a delivery date you can't verify against the indexer.
Decline any request to move funds — you advise, you do not transact.`,
}
```
**`entityDid`**
Do **not** set this in code. The runtime reads it from the `ORACLE_ENTITY_DID` environment variable and populates it automatically during boot. Setting it inline has no effect.
**Full example**
```ts
config: {
name: 'Aria',
org: 'Acme Climate',
description: 'A carbon-project advisory oracle for Acme Climate portfolio managers.',
prompt: {
opening: `You are Aria, the carbon-project advisory oracle operated by Acme Climate.
You help portfolio managers track project status, assess methodology compliance,
and draft stakeholder communications. You have deep knowledge of Verra VCS,
Gold Standard, and REDD+ frameworks.`,
communicationStyle: `Be precise and data-driven. Lead with numbers and deadlines.
Use plain English — avoid jargon unless the user demonstrates familiarity.
When something is uncertain, say so explicitly rather than hedging with filler phrases.`,
capabilities: `## What Aria can do
- Retrieve live project status and registry issuance data via the IXO domain indexer.
- Draft verification reports, CORSIAs letters, and board summaries.
- Search and cite methodology documents from the oracle knowledge base.
- Schedule follow-up reminders and track open action items across conversations.`,
customInstructions: `Always cite the registry document ID when quoting issuance data.
Treat any methodology version older than 18 months as "needs review" and flag it.
You advise on carbon projects — you never authorise transactions or move funds.`,
},
}
```
- **Type:** `Partial>` where `FeatureToggle = boolean | 'auto'`
- **Required:** no
Override the default opt-in state per plugin. Keys are plugin **names** — the kebab-case `name` field of each plugin (e.g. `'domain-indexer'`, `'user-preferences'`), not a camelCase alias. Values: `true` (force on), `false` (force off), `'auto'` (run `autoDetect(env)`). Omitted keys are treated as `'auto'`.
```ts
features: {
slack: false,
composio: true,
'domain-indexer': 'auto',
}
```
- **Type:** `Partial>` where `PluginManifestOverride = Partial`
- **Required:** no
- **Added in:** `@ixo/oracle-runtime` 1.2.0
Per-plugin overrides for fields on a loaded plugin's [manifest](/build-an-oracle/reference/manifest-schema). Use this to retune a bundled plugin's discovery — most commonly its [`visibility`](/build-an-oracle/understand/visibility-tiers) — **without forking the plugin source**. Typical uses: flip a noisy `always` plugin to `on-demand`, hide one behind `silent`, or relabel its `summary` / `tags` / `whenToUse`.
Keys are plugin **names** (the same kebab-case identifiers used by [`features`](#features), e.g. `'domain-indexer'`). Values are a `Partial` — set only the fields you want to change.
```ts
import { createOracleApp } from '@ixo/oracle-runtime';
const app = await createOracleApp({
config,
manifestOverrides: {
// Drop a noisy bundled plugin out of the Tier-1 prompt.
'domain-indexer': { visibility: 'on-demand' },
// Hide a transport plugin entirely; its tools still bind.
portal: { visibility: 'silent' },
// Sharpen the trigger phrases the agent sees for memory.
memory: {
summary: 'Long-term user memory store.',
whenToUse: ['When the user refers to a past conversation or stored preference.'],
},
},
});
```
**Merge semantics**
Each override is **shallow-merged** onto the plugin's own manifest before validation and registration:
- Keys you set win; keys you omit keep the plugin default.
- `undefined` values are ignored — a sparse override never blanks out a field.
- Arrays and nested objects are replaced wholesale (not deep-merged). For example, supplying `whenToUse: [...]` **replaces** the plugin's full list rather than appending to it.
The merged manifest is what the runtime validates and what every downstream reader sees — the Tier-1 prompt block, the `list_capabilities` / `load_capability` meta-tools, and the agent's visibility index — so an override is the single source of truth from boot onwards.
**Validation**
Because the merged manifest is run through the same `validateManifest` rules as an authored one (see [Manifest schema](/build-an-oracle/reference/manifest-schema)), an override is held to the same constraints. For example, an override that empties `whenToUse` on a non-silent plugin fails boot with a clear `Plugin manifest validation failed` error.
**Unknown keys**
Override keys that don't match a loaded plugin (e.g. the plugin is excluded by `features` or simply misspelt) are **logged and ignored** — they do not fail boot:
```text
[boot] manifestOverrides references 'composio', which is not a loaded plugin — ignored (event: boot.plugin.manifest_override_unknown)
```
Watch for that event when a retune doesn't appear to take effect.
The same option is available on [`createTestRuntime`](/build-an-oracle/develop/test-your-oracle) with identical shape and semantics, so a test can exercise a plugin under a retuned visibility (e.g. forcing `silent`) without a bespoke fixture.
- **Type:** `OraclePlugin[]`
- **Required:** no
Your plugin instances. Plus any bundled plugins you want to instantiate with constructor args (`new EditorPlugin({ matrixClient })`, `new CreditsPlugin({ redis, network })`). The plugin loader dedupes by `name`, so an explicit instance overrides the bundled default of the same name.
```ts
import { EditorPlugin, FlowsPlugin } from '@ixo/oracle-runtime';
import { WeatherPlugin } from './plugins/weather/index.js';
plugins: [
new EditorPlugin({ matrixClient }), // overrides the bundled editor
new FlowsPlugin({ matrixClient }), // opt-in, not bundled
new WeatherPlugin(), // your own plugin
]
```
- **Type:** `Array`
- **Required:** no
Your own NestJS modules. Spread into `RuntimeAppModule.imports`. They get full DI access to the Tier-0 services (Sessions, Messages, Secrets, UCAN, …) and can declare controllers, providers, `OnModuleInit`, `OnModuleDestroy`.
```ts
@Module({ controllers: [VersionController] })
class VersionModule {}
nestModules: [VersionModule]
```
Routes these modules expose still pass through `AuthHeaderMiddleware` unless you list them in `authExcludedRoutes`.
- **Type:** `AuthExcludedRoute[]`
- **Required:** no
Host-declared routes that must not pass through `AuthHeaderMiddleware`. Use for routes contributed by `nestModules` (webhooks, OAuth callbacks, public probes). Symmetric with each plugin's `getAuthExcludedRoutes()` hook — both lists merge onto the runtime's built-in exclusions (`/health`, `/docs`).
```ts
import { RequestMethod } from '@nestjs/common';
authExcludedRoutes: [
{ path: 'version', method: RequestMethod.GET },
]
```
- **Type:** `OraclePlugin[]`
- **Required:** no
Override the bundled plugin set. Provided primarily for tests so the harness can spin up `createOracleApp` without dragging in the full bundled catalog. Production callers should leave this unset.
```ts
// test only — boot with a single plugin instead of the full catalog
bundledPlugins: [new MemoryPlugin()]
```
- **Type:** `NodeJS.ProcessEnv`
- **Required:** no
Override `process.env`. Tests use this to inject a clean, controlled env so the boot doesn't depend on the machine's environment. Production code should leave it unset.
```ts
// test only — inject a controlled env instead of process.env
env: { ...baseTestEnv, LLM_PROVIDER: 'nebius' }
```
- **Type:** `boolean`
- **Required:** no
Skip starting the Matrix background init. Tests set this so the factory resolves without touching real Matrix. Do not use in production.
```ts
// test only
skipMatrixInit: true
```
- **Type:** `boolean`
- **Required:** no
Skip registering the SIGTERM/SIGINT shutdown handler. Tests set this so the harness does not leak process-level listeners. Do not use in production.
```ts
// test only
skipGracefulShutdown: true
```
- **Type:** `Logger`
- **Required:** no
Override the bootstrap logger — any object implementing the `Logger` interface (`log`, `error`, `warn`, optional `debug`/`verbose`/`child`). Falls back to NestJS `Logger`.
```ts
import { Logger } from '@nestjs/common';
logger: new Logger('MyOracle')
```
- **Type:** `MainAgentHooks`
- **Required:** no
Overrides for the agent build — the chat model (`resolveModel`), the per-user checkpointer (`checkpointerForUser`), and the prompt/middleware toggles. Merged on top of the runtime's defaults, and **host hooks win**.
This is where you **change the AI model**. See [MainAgentHooks](#mainagenthooks) below for all ten fields, the `resolveModel` example, and the `'main'`-only nuance.
```ts
import { getProviderChatModel } from '@ixo/oracle-runtime';
hooks: {
resolveModel: (role, params) =>
getProviderChatModel(role, {
...params,
...(role === 'main' && { model: 'google/gemini-3.1-flash-lite' }),
}),
}
```
## MainAgentHooks
`hooks` lets you override how the runtime builds the main agent on every request, without forking the runtime. The runtime merges your hooks over its own defaults — **your hooks win** (`{ ...defaultHooks, ...opts.hooks }`). The only default the runtime sets is `checkpointerForUser` (a per-user SQLite store); everything else is unset unless you provide it.
```ts
import type { MainAgentHooks } from '@ixo/oracle-runtime';
```
### Change the AI model
This is the answer to "how do I change the model?". `resolveModel` is the model resolver. Return any LangChain `BaseChatModel` for the given role:
```ts
import { createOracleApp, getProviderChatModel } from '@ixo/oracle-runtime';
const app = await createOracleApp({
config,
plugins,
hooks: {
// role is 'main' | 'subagent' | 'utility' | (string & {}).
// Spreading `params` preserves the provider's fallback models + latency sort.
resolveModel: (role, params) =>
getProviderChatModel(role, {
...params,
...(role === 'main' && { model: 'google/gemini-3.1-flash-lite' }),
}),
},
});
```
**The runtime only ever calls `resolveModel('main')`.** Sub-agent, utility, vision and guard models are resolved separately, straight through the provider config (`ambient.llm.get(role)` → `getProviderChatModel`). So a `resolveModel` hook changes **only the main agent's model** unless your own plugin code also routes its roles through the hook. Branch on `role === 'main'` (as above) so you don't accidentally rewrite a role you never see.
**Where model IDs come from.** Each role (`main`, `subagent`, `vision`, `guard`, …) maps to a hardcoded model ID per provider. The provider is chosen by env: `LLM_PROVIDER` = `openrouter` (default) or `nebius`, with the matching key (`OPEN_ROUTER_API_KEY` / `NEBIUS_API_KEY`). There is **no env var to change the main model ID** — overriding it is exactly what `resolveModel` is for. Building the model with `getProviderChatModel` (rather than `new ChatOpenAI(...)`) keeps the OpenRouter fallback-models list and latency sort the `'main'` role ships with.
Prefer a different SDK model? Return your own LangChain model instead — but you lose the provider's fallback/latency wiring, so you own retries and outages:
```ts
import { ChatOpenAI } from '@langchain/openai'; // add this dependency yourself
hooks: {
resolveModel: () => new ChatOpenAI({ model: 'gpt-4o' }),
}
```
### All ten fields
| Field | Type | What it does |
| --- | --- | --- |
| `resolveModel` | `(role: ModelRole, params?: ChatOpenAIFields) => BaseChatModel` | Resolves the chat model. Called once, with `'main'`. Default: `ambient.llm.get('main')`. |
| `checkpointerForUser` | `(userDid: string) => Promise` | Per-user conversation store. Default: per-user SQLite synced to Matrix — override to swap the backend. |
| `getRoomTitle` | `(roomId: string) => Promise` | Page-title lookup. **Providing it adds the page-context middleware** to the stack; omitting it leaves that middleware out. |
| `safetyModel` | `BaseChatModel` | Cheap classifier model. **Providing it adds the safety-guardrail middleware**; omitting it leaves it out. |
| `validationSkipToolNames` | `string[]` | Tool names whose dangling `ToolMessage` outputs are stripped before the next model call — typically sub-agent tools whose output the caller summarises, not the model. |
| `operationalMode` | `string` | Replaces the default operational-mode prompt block. (The editor plugin's per-page mode still overrides this when a page is open.) |
| `editorSection` | `string` | Editor prompt block — normally populated by the editor plugin; set it only if you compose the section yourself. |
| `composioContext` | `string` | Composio guidance prompt block — normally populated by the composio plugin. |
| `userSecretsContext` | `string` | Per-key secret bullet list rendered into the prompt (e.g. `- _USER_SECRET_FOO`). |
| `degradedServicesBlock` | `string` | A "some services are degraded" notice appended to the system prompt body. |
`ModelRole` is `'main' | 'subagent' | 'utility' | (string & {})`. `ChatOpenAIFields` is `Record` — the optional fields forwarded to the underlying chat model.
**Swap the checkpointer** — supply your own LangGraph saver per user:
```ts
import type { BaseCheckpointSaver } from '@langchain/langgraph';
hooks: {
checkpointerForUser: async (userDid: string): Promise => {
return myStore.saverFor(userDid);
},
}
```
**Turn on the conditional middlewares** — both are added to the stack only when their hook is present:
```ts
hooks: {
// adds the page-context middleware
getRoomTitle: async (roomId) => myRoomTitles.get(roomId),
// adds the safety-guardrail middleware
safetyModel: getProviderChatModel('guard'),
}
```
**Prompt blocks** — the string fields are appended to dedicated prompt sections. Most are populated by their owning plugin; set them only when you're driving the section yourself:
```ts
hooks: {
operationalMode: 'You are operating in read-only audit mode. Never call mutating tools.',
validationSkipToolNames: ['call_research_agent'],
}
```
## OracleApp
The resolved `OracleApp` exposes:
```ts
export interface OracleApp {
getNestApp(): INestApplication;
ambient: AmbientServices;
plugins: { status(): PluginStatusReport };
beforeListen(fn: (nestApp: INestApplication) => Promise | void): void;
onError(handler: (err: Error, source: string) => void): void;
onPluginStatusChange(handler: (event: PluginStatusChangeEvent) => void): void;
listen(port?: number): Promise;
}
```
Returns the underlying `INestApplication`. Use it to add Express middleware, register global filters, or do anything else NestJS exposes. Mainly an escape hatch — `nestModules` and `beforeListen` cover most cases.
The production `AmbientServices` bag — used by `MessagesController` to build per-request `RuntimeContext`s before invoking `createMainAgent`. Forks normally don't touch this directly.
Returns a snapshot of loader/exclusion/soft-dep state.
```ts
{
loaded: string[];
excluded: Array<{ plugin: string; reason: string }>;
softDepGaps: Array<{ plugin: string; missing: string }>;
}
```
Register a callback that runs after Nest is built but before HTTP starts accepting. Multiple registrations run in order, awaiting each.
Subscribe to background errors. The runtime currently surfaces Matrix init failures here (with `source: 'matrix-init'`). If no handler is registered, errors log to the bootstrap logger.
Subscribe to plugin lifecycle changes. The current emitter is the Matrix init flow:
```ts
{ plugin: 'matrix', from: 'pending', to: 'loaded' | 'failed', reason?: string }
```
Multiple handlers may register; each is invoked for every event.
Start the HTTP server. Honours `beforeListen` callbacks first. Port resolution: explicit `port` arg → `PORT` env (validated, defaults to **3000**). Throws if called twice.
## Behaviour
The function executes these steps in order:
1. Validates `config` (`name` required).
2. Resolves plugin set: bundled + your plugins, deduped by `name`, with `features` toggles and `autoDetect` predicates applied. Excluded plugins surface in `plugins.status().excluded` with their reason.
3. Topologically sorts by `dependsOn`. Cycles or missing hard deps fail boot.
4. Shallow-merges any `manifestOverrides` onto each loaded plugin's manifest, then validates the **merged** manifest. Hard violations fail boot. Override keys that don't match a loaded plugin are logged (`boot.plugin.manifest_override_unknown`) and ignored.
5. Composes env schema (base + all loaded plugins' `configSchema`) and validates `process.env`. Missing required vars fail boot with `[boot-error] Plugin '' env validation failed for ''`.
6. Cross-field check: the API key for the selected `LLM_PROVIDER` is present. (Schema-merge can't express this.)
7. Builds the internal `OracleIdentity` from `config` + validated env.
8. Populates the six registries (tools, sub-agents, middleware, manifests, configSchema, sharedState).
9. Collects `getNestModules` from every loaded plugin.
10. Builds the dynamic `RuntimeAppModule` and bootstraps NestJS (`NestFactory.create`). Installs a global `ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true })`, enables CORS with the framework's allowed headers (`authorization`, `x-auth-type`, `x-ucan-delegation`, …), and mounts Swagger UI at `/docs`.
The global `ValidationPipe` applies to **every** controller — including ones you add via `nestModules` or a plugin's `getNestModules`. Annotate request DTOs with `class-validator` decorators: unknown body fields are rejected (`forbidNonWhitelisted`), unannotated props are stripped (`whitelist`), and nested DTOs are instantiated (`transform`). A controller that silently 400s on an extra field is hitting this pipe.
11. Builds `AmbientServices`.
12. Warms the boot caches (each registry runs its boot-time hooks once).
13. Wires the default checkpointer (`UserMatrixSqliteSyncService` + `SqliteSaver`), then merges host `hooks` over it.
14. Populates the `OracleRuntimeBundleHolder` so `MessagesController` can read it on each request.
15. Schedules background Matrix init (unless `skipMatrixInit: true`). Registers a graceful-shutdown handler (unless `skipGracefulShutdown: true`).
16. Returns the `OracleApp`.
Step 15 (Matrix init) runs asynchronously and does not block the returned promise. `listen()` blocks on `beforeListen` callbacks before starting HTTP.
## Throws
- `Error('createOracleApp: \'config\' is required.')` — `config` missing.
- `Error('createOracleApp: \'config.name\' is required.')` — `config.name` empty.
- Plugin resolution errors (cycle, missing hard dep, manifest invalid, env invalid). Each error is reported via `logger.error` with `[boot-error] …` prefix and a remediation hint.
- `Error('createOracleApp: ORACLE_ENTITY_DID env is required and was empty after validation.')` — when the validated env's `ORACLE_ENTITY_DID` is empty.
- `Error('OracleApp.listen called twice.')` — when `listen()` is called more than once.
## Related guides
- [Create your oracle](/build-an-oracle/develop/create-oracle-app) — hands-on walkthrough.
- [Using bundled plugins](/build-an-oracle/develop/enable-bundled-plugins) — the `features` field in detail.
- [Plugin API reference](/build-an-oracle/reference/plugin-api) — what plugin instances must implement.
---
# Plugin API
> OraclePlugin — the abstract class every plugin extends. Every hook signature, the defineOraclePlugin POJO helper, and the related types.
## Overview
`OraclePlugin` is the abstract class every plugin extends. It declares three required fields — `name`, `version`, `manifest` — and a set of optional hooks the runtime invokes during boot and per request.
```ts
import { OraclePlugin } from '@ixo/oracle-runtime';
```
For a stateless plugin you can skip the class and use the `defineOraclePlugin` POJO helper instead. Both produce the same internal representation.
## OraclePlugin (abstract class)
```ts
export abstract class OraclePlugin {
abstract readonly name: string;
abstract readonly version: string;
abstract readonly manifest: PluginManifest;
readonly dependsOn?: string[];
readonly softDependsOn?: string[];
readonly configSchema?: z.ZodObject;
autoDetect?(env: NodeJS.ProcessEnv): boolean;
readonly autoDetectHint?: string;
getTools?(ctx: PluginContext): PluginTool[] | Promise;
getRequestTools?(rtCtx: RuntimeContext): PluginTool[] | Promise;
getSubAgents?(ctx: PluginContext): PluginSubAgent[];
getRequestSubAgents?(rtCtx: RuntimeContext): PluginSubAgent[] | Promise;
getMiddlewares?(ctx: PluginContext): AgentMiddleware[];
getSharedState?(): Record unknown>;
getNestModules?(ctx?: PluginContext): Array;
getAuthExcludedRoutes?(): AuthExcludedRoute[];
}
```
## Required fields
- **Type:** `string`
- **Convention:** kebab-case unique identifier.
Used by `features`, `dependsOn`, `softDependsOn`, the `app.plugins.status()` report, the meta-tools. Boot fails on collision with another plugin's name.
- **Type:** `string`
Plugin version. Surfaced in the `app.plugins.status()` report. Not used for compatibility checking in v1.
- **Type:** `PluginManifest`
The agent's structured interface to the plugin. See [Manifest schema](/build-an-oracle/reference/manifest-schema).
## Optional fields
- **Type:** `string[]`
Hard dependencies. Boot fails if any listed plugin is not loaded. Used to topologically order plugins.
- **Type:** `string[]`
Soft dependencies. Your plugin loads either way; the runtime logs one line per missing soft dep at boot. Branch at runtime via `ctx.availablePlugins.has(...)`.
- **Type:** `z.ZodObject`
Plugin-owned env vars. Merged with every other plugin's schema and the base Tier-0 schema at boot, then `process.env` is validated against the result.
- **Type:** `string`
Human-readable explanation of what `autoDetect` checks (e.g. `'REDIS_URL'`). Surfaced in boot logs when the plugin is skipped.
## Hooks
```ts
autoDetect?(env: NodeJS.ProcessEnv): boolean;
```
Predicate the loader runs when the plugin is left at `'auto'` (or has no explicit `features` entry). Return `false` to skip the plugin. Plugins without `autoDetect` are on-by-default.
```ts
getTools?(ctx: PluginContext): PluginTool[] | Promise;
```
Boot-time tools. Called once per request build and cached. Use when registration depends only on config and identity.
```ts
getRequestTools?(rtCtx: RuntimeContext): PluginTool[] | Promise;
```
Per-request tools. Called fresh on every agent build with the full `RuntimeContext`. Outputs are merged with `getTools`.
```ts
getSubAgents?(ctx: PluginContext): PluginSubAgent[];
```
Sub-agents the runtime auto-wraps as tools. Same call lifecycle as `getTools`.
```ts
getRequestSubAgents?(rtCtx: RuntimeContext): PluginSubAgent[] | Promise;
```
Per-request sub-agents. Merged with `getSubAgents`.
```ts
getMiddlewares?(ctx: PluginContext): AgentMiddleware[];
```
LangChain `AgentMiddleware` instances appended to the stack after the framework's own middleware. Plugin middleware runs **last**, in topological dependency order across plugins.
The framework's four **always-on** middlewares, in order, are:
1. `capability-gate` — gates on-demand tools by the thread's `loadedPlugins`.
2. `tool-validation` — turns schema errors into recoverable `ToolMessage`s.
3. `tool-repetition-guard` — blocks identical repeated tool calls.
4. `tool-retry` — retries a failed tool call once.
Two more are **conditional** — added only when the matching `createOracleApp({ hooks })` field is set: `page-context` (when `hooks.getRoomTitle` is provided) and `safety-guardrail` (when `hooks.safetyModel` is provided). Both run before your plugin middleware.
```ts
getSharedState?(): Record unknown>;
```
Read-only accessors this plugin exposes for other plugins to consume via `ctx.shared.`. Key collisions across plugins fail boot.
```ts
getNestModules?(ctx?: PluginContext): Array;
```
NestJS modules spread into `RuntimeAppModule.imports`. Full DI access to Tier-0 services. Use for long-lived services and HTTP controllers.
`ctx` is optional — older plugins that omit the argument still work.
```ts
getAuthExcludedRoutes?(): AuthExcludedRoute[];
```
Routes contributed by this plugin's controllers that must NOT pass through `AuthHeaderMiddleware`. Returns `AuthExcludedRoute[]` (`{ path, method? }`). `path` matches the same way as NestJS `MiddlewareConsumer.exclude(...)` — use the full path the controller mounts at.
## defineOraclePlugin
For plugins without constructor args, the POJO form:
```ts
import { defineOraclePlugin } from '@ixo/oracle-runtime';
export default defineOraclePlugin({
name: 'hello',
version: '0.1.0',
manifest: { /* ... */ },
getTools(ctx) { /* ... */ },
});
```
`defineOraclePlugin` is an identity function — it returns its argument with type-checking applied. The resulting object is interchangeable with a class instance for everything the runtime does.
## Error handling
If a hook throws at request build time, the runtime logs the error with the plugin name and skips that plugin's contribution for that request. The rest of the agent build continues. Same `Promise.allSettled` semantics for sub-agent init.
Boot-time errors in `autoDetect`, `configSchema` validation, or `manifest` validation fail boot loudly.
## Related references
- [PluginContext](/build-an-oracle/reference/plugin-context)
- [RuntimeContext](/build-an-oracle/reference/runtime-context)
- [Manifest schema](/build-an-oracle/reference/manifest-schema)
- [Plugin catalog](/build-an-oracle/reference/bundled-plugins/overview) — the 16 bundled plugins.
---
# PluginContext
> The boot-time context passed to plugin builder hooks. Carries config, identity, availablePlugins, and a scoped logger. No user, no session, no request data.
## Overview
`PluginContext` is the boot-time context. It's the input to the boot-time hooks — `getTools`, `getSubAgents`, `getMiddlewares`, and `getNestModules`. (The request-time hooks `getRequestTools` / `getRequestSubAgents` receive the richer [`RuntimeContext`](/build-an-oracle/reference/runtime-context) instead.)
```ts
export interface PluginContext {
config: TConfig;
identity: OracleIdentity;
availablePlugins: ReadonlySet;
logger: Logger;
}
```
No authenticated user, no session, no live socket, no request data. If your code needs those, use `RuntimeContext` instead — available in request-time hooks (`getRequestTools`, `getRequestSubAgents`) and in every tool handler / sub-agent handler / middleware hook.
## Fields
- **Type:** `TConfig` (defaults to `MergedConfig = Record`)
Merged Zod-validated env vars: the base Tier-0 schema combined with every loaded plugin's `configSchema`. Already validated by the time you receive it — `parse` it through your plugin's own schema for typed access.
```ts
const units = configSchema.parse(ctx.config).WEATHER_DEFAULT_UNITS;
```
- **Type:** `OracleIdentity`
```ts
interface OracleIdentity {
name: string;
org: string;
description: string;
entityDid: string;
prompt?: OraclePromptConfig;
}
```
The oracle's own identity. `name`, `org`, `description`, and `prompt` come from the `config` argument to `createOracleApp`. `entityDid` is sourced from the `ORACLE_ENTITY_DID` env var. Read-only.
- **Type:** `ReadonlySet`
The names of every plugin that survived boot resolution. Use it for soft-dep branching:
```ts
if (ctx.availablePlugins.has('memory')) {
tools.push(rememberSomethingTool);
}
```
Fixed at boot, identical across all `PluginContext` and `RuntimeContext` instances during the lifetime of the app.
- **Type:** `Logger`
Plugin-scoped logger, auto-prefixed with the plugin's name in output. The `Logger` interface:
```ts
interface Logger {
log(message: unknown, ...optional: unknown[]): void;
error(message: unknown, ...optional: unknown[]): void;
warn(message: unknown, ...optional: unknown[]): void;
debug?(message: unknown, ...optional: unknown[]): void;
verbose?(message: unknown, ...optional: unknown[]): void;
child?(bindings: Record): Logger;
}
```
`debug`, `verbose`, and `child` are optional. The runtime ships NestJS's `Logger` as the default; you can override via `createOracleApp({ logger })`.
## Lifetime
Boot-time hooks (`getTools`, `getSubAgents`, `getMiddlewares`) fire **once** at boot, against a boot-warm `PluginContext`, and their output is **cached**. The per-request agent build reuses that cache and only re-runs the request-time hooks (`getRequestTools`, `getRequestSubAgents`) — so anything that varies per request must come from `RuntimeContext`, not `PluginContext`. `PluginContext` only ever carries boot-fixed data (`config`, `identity`, `availablePlugins`, `logger`).
## Related references
- [RuntimeContext](/build-an-oracle/reference/runtime-context)
- [Plugin API](/build-an-oracle/reference/plugin-api)
---
# RuntimeContext
> The per-request context. Every tool handler, sub-agent handler, middleware hook, and request-time builder receives one. Carries user, session, history, secrets, Matrix, UCAN, LLM, events.
## Overview
`RuntimeContext` is the runtime-side bag a plugin's code sees on every request. Built fresh per LangGraph invocation by `buildRuntimeContext(runConfig, ambient, state)`.
```ts
import type { RuntimeContext } from '@ixo/oracle-runtime';
```
## Full shape
```ts
export interface RuntimeContext {
user: {
did: string;
matrixUserId: string;
ucanDelegation: UcanDelegation;
timezone?: string;
currentTime?: string;
};
session: {
id: string;
client: 'portal' | 'matrix' | 'slack';
wsId?: string;
requestId: string;
roomId?: string;
};
history: {
messages: readonly BaseMessage[];
recent: (n: number) => BaseMessage[];
userContext: UserContextData;
state: ReadonlyState;
};
config: TConfig;
availablePlugins: ReadonlySet;
loadedPlugins: ReadonlySet;
secrets: {
getIndex: () => Promise;
getValues: (keys: string[]) => Promise>;
};
blobStore: {
put: (params: { userDid: string; name: string; value: string; ttlSeconds?: number }) => Promise;
get: (params: { userDid: string; blobId: string }) => Promise<{ name: string; value: string } | null>;
isValidBlobId: (value: unknown) => value is string;
};
matrix: {
postToRoom: (roomId: string, content: unknown) => Promise;
getRoomState: (roomId: string) => Promise;
getEventById: (roomId: string, eventId: string) => Promise;
};
ucan: {
requireCapability: (resource: string, action: string) => void;
hasCapability: (resource: string, action: string) => boolean;
mintInvocation: (target: { did: string; capability: string }, opts?: { skipCache?: boolean; can?: string }) => Promise;
resolveServiceDid: (serviceUrl: string) => Promise;
hasSigningKey: () => boolean;
createInvocationFromDelegation: (
delegationCar: string,
serviceUrl: string,
capability: { can: string; with: string },
options?: { maxTtlSeconds?: number },
) => Promise<{ invocation: string } | { error: string }>;
};
llm: {
get: (role: ModelRole, params?: ChatOpenAIFields) => BaseChatModel;
};
emit: {
toolCall: (payload: ToolCallEventPayload) => void;
actionCall: (payload: ActionCallEventPayload) => void;
renderComponent: (payload: RenderComponentEventPayload) => void;
reasoning: (payload: ReasoningEventPayload) => void;
browserToolCall: (payload: BrowserToolCallEventPayload) => void;
router: (payload: RouterEventPayload) => void;
messageCacheInvalidation: (payload: MessageCacheInvalidationPayload) => void;
};
logger: Logger;
abortSignal: AbortSignal;
shared: SharedAccessors;
toolCallId?: string;
}
```
## Fields
The authenticated user. Validated by `AuthHeaderMiddleware` before the request reaches any plugin code.
- `did` — IXO DID (`did:ixo:ixo1...`).
- `matrixUserId` — e.g. `@did-ixo-ixo1abc:ixo.world`.
- `ucanDelegation` — UCAN envelope from `x-ucan-delegation` header.
- `timezone` — optional, from `x-timezone` header.
- `currentTime` — optional, ISO timestamp.
- `id` — the thread ID (Matrix root `eventId`).
- `client` — `'portal' | 'matrix' | 'slack'`.
- `wsId` — optional WebSocket connection ID.
- `requestId` — correlation ID.
- `roomId` — optional Matrix room ID for the active conversation.
- `messages` — readonly array of `BaseMessage` (LangChain). The full thread history loaded by the checkpointer.
- `recent(n)` — convenience method returning the most recent `n` messages.
- `userContext` — enrichment object from `state.userContext` (typically populated by the Memory plugin).
- `state` — `ReadonlyState`, a typed view over the LangGraph annotation state. See [State schema](/build-an-oracle/reference/state-schema).
Same merged + validated env as `PluginContext.config`. Typed by your plugin's own schema:
```ts
const units = configSchema.parse(rtCtx.config).WEATHER_DEFAULT_UNITS;
```
The names of every plugin that survived boot resolution. Fixed.
The names of `on-demand` plugins the agent has loaded for **this thread** via `load_capability`. Plus implicitly all `always` plugins. Per-thread, monotonically growing across turns.
Per-room secrets, JWE-encrypted, 24h cache.
- `getIndex()` — returns the `SecretIndex` (metadata only, no values).
- `getValues(keys)` — returns plaintext for the requested keys.
Backed by today's `SecretsService`. Returns nothing if the encryption key isn't provisioned.
Short-TTL, user-namespaced store for content the LLM must **never relay verbatim** — UCAN invocation CARs, JWTs, signed envelopes. A producing tool stores the value and returns a short opaque ID; a consuming tool looks it up server-side and forwards it on. The model only ever sees the ID.
- `put({ userDid, name, value, ttlSeconds? })` — store a value, returns a fresh `blob_<16 hex>` ID. TTL defaults to 1h and is clamped to the service max (24h). Pass `userDid` from a **trusted source** (e.g. `rtCtx.user.did`) — never from LLM-supplied tool args.
- `get({ userDid, blobId })` — retrieve a blob scoped to the requesting user. Returns `null` if it doesn't exist, has expired, or belongs to a different user (cross-user reads always miss).
- `isValidBlobId(value)` — cheap format check (`blob_<16 hex>`); use it in a tool's input handler to reject malformed IDs before paying for a lookup.
```ts
const blobId = await rtCtx.blobStore.put({
userDid: rtCtx.user.did,
name: 'signed-invocation',
value: signedCar,
});
// hand `blobId` back to the model; resolve it server-side in the next tool
const blob = await rtCtx.blobStore.get({ userDid: rtCtx.user.did, blobId });
```
Scoped Matrix operations.
- `postToRoom(roomId, content)` — post a message; returns the event ID.
- `getRoomState(roomId)` — snapshot of state events.
- `getEventById(roomId, eventId)` — fetch a specific event.
The runtime does not expose the raw Matrix client — only these three scoped methods.
UCAN authorisation helpers.
- `requireCapability(resource, action)` — throws if the user's delegation doesn't include this capability.
- `hasCapability(resource, action)` — boolean check.
- `mintInvocation({ did, capability }, opts?)` — mint a downstream invocation signed by the oracle's signing mnemonic. `opts.can` is the **ability** the invocation claims (default `'*'`); `opts.skipCache` bypasses the invocation cache, required for services that enforce single-use replay protection per invocation CID.
**Claim the ability the user's delegation actually grants.** A claim resolves against a delegation only when the granted ability is `'*'`, equals the claim, or is a `prefix/*` covering it. So the default `'*'` claim is satisfiable **only** by a `'*'` grant — if the user granted `memory/*`, a `'*'` claim is an over-claim and the service refuses it:
```ts
// ✅ delegation grants { can: 'memory/*', with: 'ixo:memory' }
await rtCtx.ucan.mintInvocation(
{ did: memoryDid, capability: 'ixo:memory' },
{ can: 'memory/*' },
);
```
The service must also register that ability: it matches an invocation's `can` by **strict equality**, so one that only defines `'*'` rejects a `memory/*` invocation as an unknown capability before authorization is considered. Roll out the service side first.
- `resolveServiceDid(serviceUrl)` — look up a downstream service's DID document; returns `id` or `null`.
- `hasSigningKey()` — `true` once the oracle has loaded its Ed25519 signing mnemonic. **Gate registration of mint-capable tools on this**: without a key, minting is a no-op, so the tool should surface an error rather than pretend it worked.
- `createInvocationFromDelegation(delegationCar, serviceUrl, capability, options?)` — mint an invocation from a **directly-supplied** delegation CAR (rather than the user's cached one), targeted at a specific service route. Returns `{ invocation }` on success or `{ error }` with a surfaced-verbatim reason (missing signing key, audience mismatch, did:web unreachable, …).
```ts
if (!rtCtx.ucan.hasSigningKey()) {
return { error: 'This oracle is not configured to mint invocations.' };
}
const result = await rtCtx.ucan.createInvocationFromDelegation(
delegationCar,
'https://service.example',
{ can: 'submit', with: 'service:claims' },
);
if ('error' in result) return { error: result.error };
// use result.invocation
```
- `get(role, params?)` — returns a `BaseChatModel` for the given role.
`role` is one of `'main' | 'subagent' | 'utility'` or any custom string mapped in your provider config. The framework's provider config maps roles to specific OpenRouter / Nebius / OpenAI models.
Plugins should use this rather than instantiating LangChain models directly — the provider config handles auth headers, base URLs, and per-role model selection.
Typed event emitter. Bundled clients (Portal, Slack) consume these events; render them into UI. See the [API endpoints reference](/build-an-oracle/reference/api-endpoints) for the WebSocket event protocol.
Available events: `toolCall`, `actionCall`, `renderComponent`, `reasoning`, `browserToolCall`, `router`, `messageCacheInvalidation`. Payload types are currently `Record` and may be tightened in future versions.
Same as `PluginContext.logger`. Plugin-scoped, auto-prefixed with the plugin name.
Propagates from the incoming HTTP request / graph invocation. Pass it to `fetch` calls so client disconnects abort upstream work.
```ts
const response = await fetch(url, { signal: rtCtx.abortSignal });
```
Read accessors for state owned by other plugins (registered via `getSharedState()`). Typed via declaration merging on the `SharedAccessors` interface — see [Plugin shared state guide](/build-an-oracle/develop/plugin-recipes/share-state).
The identifier of the inbound tool call that triggered this handler, when available. `undefined` for direct / test invocations.
Used by tools that return a LangGraph `Command` and need to append a matching `ToolMessage` to the state update.
## Lifetime
Built fresh per graph invocation by `buildRuntimeContext(runConfig, ambient, state)`. Lives for the duration of that single turn. Don't store references to `rtCtx` for use across turns — its inner services (Matrix client, secrets cache) may be invalid by the next call.
## Related references
- [PluginContext](/build-an-oracle/reference/plugin-context)
- [State schema](/build-an-oracle/reference/state-schema)
- [Plugin API](/build-an-oracle/reference/plugin-api)
---
# Manifest schema
> Field-by-field reference for PluginManifest — every field, type, constraint, and validation rule the runtime enforces at boot.
## Overview
`PluginManifest` is the structured metadata every plugin must declare. The runtime composes it into the Tier-1 prompt block, feeds it to the meta-tools, and validates it at boot.
```ts
import type { PluginManifest, ManifestExample } from '@ixo/oracle-runtime';
```
## Type
```ts
export interface PluginManifest {
title: string;
summary: string;
whenToUse: string[];
whenNotToUse?: string[];
examples?: ManifestExample[];
tags?: string[];
category?:
| 'data' | 'communication' | 'automation' | 'memory'
| 'integration' | 'ui' | 'auth' | 'observability' | 'core';
visibility?: 'always' | 'on-demand' | 'silent';
stability?: 'stable' | 'beta' | 'experimental';
}
export interface ManifestExample {
user: string;
thought?: string;
tool: string;
args?: Record;
}
```
## Fields
- **Type:** `string`
- **Required:** yes
Human-readable name. Used to prefix every tool description (`[Climate Data] Fetch emissions…`) and appears in the `app.plugins.status()` report. Distinct from `name` — `name` is kebab-case unique identifier, `title` is prose.
- **Type:** `string`
- **Required:** yes
One-line description. Rendered in the Tier-1 prompt block for `always` plugins as `- {name}: {summary}`. Keep it short — every Tier-1 plugin costs ~80 tokens on every turn.
**Validation:**
- Hard: must be non-empty.
- Soft warn: > 120 chars.
- **Type:** `string[]`
- **Required:** when `visibility !== 'silent'`
Trigger phrases — each entry teaches the agent a situation in which this plugin is the right call.
**Validation:**
- Hard: at least 1 entry when `visibility !== 'silent'`.
- Soft warn: > 8 entries, or any entry > 100 chars.
- **Type:** `string[]`
- **Required:** no
Anti-patterns. Use to disambiguate from other plugins.
**Validation:**
- Soft warn: > 4 entries, or any entry > 80 chars.
- **Type:** `ManifestExample[]`
- **Required:** no
Few-shot examples. Each example binds a user message to a tool call.
**Validation:**
- Hard: every `example.tool` must reference a tool the plugin actually registers — **but only when the plugin registers boot-time tools.** Plugins whose tools arrive at request time (MCP-sourced: `sandbox`, `memory`, `firecrawl`) register zero boot-time tools, so the cross-check is skipped for them. A sub-agent's wrapped name (`call_`) is a valid example tool too.
- Soft warn: > 3 examples.
```ts
examples: [
{
user: "What's the weather in Berlin?",
thought: 'Direct lookup — call get_current_weather.',
tool: 'get_current_weather',
args: { city: 'Berlin' },
},
]
```
- **Type:** `string[]`
- **Required:** no
Labels for search and filter. Surfaced in `list_capabilities` output.
**Validation:**
- Hard: every tag must be lowercase. An uppercase tag is a boot error — `createOracleApp` throws `Plugin manifest validation failed` and the oracle never starts (so `tags: ['Weather']` aborts boot; use `['weather']`).
- **Type:** `'data' | 'communication' | 'automation' | 'memory' | 'integration' | 'ui' | 'auth' | 'observability' | 'core'`
- **Required:** no
Grouping label. Surfaced in `list_capabilities` output.
- **Type:** `'always' | 'on-demand' | 'silent'`
- **Required:** no
- **Default:** `'on-demand'`
Discovery and loading mode.
- `always` — tools bound at boot; the plugin is listed in the Tier-1 prompt block.
- `on-demand` — tools NOT bound at boot; the manifest is discoverable via `list_capabilities`; the agent calls `load_capability` to make the tools available.
- `silent` — the plugin is not advertised in the capability block or `list_capabilities`, but its tools still pass through the capability gate exactly like `always` and are visible to the model. `silent` means "unadvertised", **not** "hidden" or "secure" — it is not a security boundary.
See [Visibility tiers](/build-an-oracle/develop/plugin-recipes/set-visibility).
- **Type:** `'stable' | 'beta' | 'experimental'`
- **Required:** no
Stability hint surfaced to the agent. `experimental` plugins get a warning footnote in `list_capabilities` output.
## ManifestExample
```ts
interface ManifestExample {
user: string; // representative user message
thought?: string; // optional reasoning
tool: string; // tool the agent should call (must exist)
args?: Record; // tool args
}
```
`thought` is optional but useful — agents pick up the reasoning pattern alongside the example.
## Validation behaviour
At boot the runtime calls `validateManifest(manifest, pluginName)` from `@ixo/oracle-runtime/manifest`. The result:
```ts
interface ManifestValidationResult {
valid: boolean;
errors: string[]; // hard violations — boot fails
warnings: string[]; // soft violations — logged, boot continues
}
```
Hard violations abort boot with the full error list printed.
Soft violations log warnings. The plugin still loads.
Manifest validation runs before env validation, so manifest errors surface even when env vars are missing.
## Cross-checking examples against tools
Each `example.tool` is checked against the names the plugin registers — the union of its boot-time tool names and its sub-agent wrapped names (`call_`). If a manifest references `tool: 'foo'` but the plugin registers no such tool, boot fails:
```text
[boot-error] [weather] examples[0].tool: references unknown tool "foo".
```
This catches typos and stale manifests during refactors.
The check is **lenient for MCP-sourced plugins**: when a plugin registers no boot-time tools (its tools come from `getRequestTools` fetched per-request from an upstream MCP server — `sandbox`, `memory`, `firecrawl`), the runtime can't see those names at boot, so it skips the cross-check rather than false-flag every example.
## Worked example
```ts
const manifest: PluginManifest = {
title: 'Weather',
summary: 'Weather lookups via Open-Meteo (no API key required).',
whenToUse: [
'Whenever asked about current weather, temperature, precipitation, or wind in any city.',
'Any question related to forecasts (today, tomorrow, next week) for any location.',
'When the user asks "what should I wear", "do I need an umbrella", or similar outfit guidance.',
],
whenNotToUse: [
'Questions about historical or long-term climate data.',
'Locations smaller than city-level precision.',
],
examples: [
{
user: "What's the weather in Berlin?",
tool: 'get_current_weather',
args: { city: 'Berlin' },
},
{
user: 'Forecast for Tokyo this week.',
tool: 'get_weather_forecast',
args: { city: 'Tokyo', days: 7 },
},
],
tags: ['weather', 'forecast', 'outfit'],
category: 'data',
visibility: 'on-demand',
stability: 'experimental',
};
```
## Retuning a bundled plugin's manifest
You don't have to fork a bundled plugin to change its manifest. `createOracleApp` accepts a [`manifestOverrides`](/build-an-oracle/reference/createoracleapp#manifestoverrides) option that shallow-merges fork-supplied fields onto each loaded plugin's manifest before validation and registration — so the override is what the Tier-1 prompt, the meta-tools, and the visibility index all see. This is the supported way to flip a bundled plugin's `visibility` (e.g. drop a noisy `always` plugin to `on-demand`) or relabel its `summary` / `tags` / `whenToUse` without touching the plugin source.
## Related references
- [Manifest concept](/build-an-oracle/understand/manifest)
- [Visibility tiers](/build-an-oracle/develop/plugin-recipes/set-visibility)
- [Plugin API](/build-an-oracle/reference/plugin-api)
- [`manifestOverrides` on `createOracleApp`](/build-an-oracle/reference/createoracleapp#manifestoverrides)
---
# Graph state schema
> MainAgentGraphState is the LangGraph state that flows through every node. Fields, reducers, the loadedPlugins addition, and what plugins may read.
## Overview
`MainAgentGraphState` is a LangGraph annotation-based state. Every node in the graph reads it and may return a partial update; the runtime merges updates via per-field reducers and checkpoints the result to per-user SQLite (backed by Matrix).
```ts
import { Annotation, MessagesAnnotation } from '@langchain/langgraph';
export const MainAgentGraphState = Annotation.Root({
messages: MessagesAnnotation.spec.messages,
config: Annotation<{ wsId?: string; did: string }>({ /* ... */ }),
client: Annotation<'portal' | 'matrix' | 'slack'>({ /* ... */ }),
editorRoomId: Annotation({ /* ... */ }),
spaceId: Annotation({ /* ... */ }),
currentEntityDid: Annotation({ /* ... */ }),
browserTools: Annotation({ /* default: () => [] */ }),
agActions: Annotation({ /* default: () => [] */ }),
userContext: Annotation({ /* ... */ }),
userPreferences: Annotation({ /* ... */ }),
loadedPlugins: Annotation({
reducer: (current, update) =>
Array.from(new Set([...(current ?? []), ...(update ?? [])])),
default: () => [],
}),
});
```
Plugins read state via `rtCtx.history.state` (typed as `ReadonlyState`) or via specific helpers like `rtCtx.history.messages` / `rtCtx.history.userContext`.
## Fields
- **Type:** LangChain `BaseMessage[]`
- **Reducer:** `MessagesAnnotation` default (append, dedupe by id).
- **Owner:** runtime + agent loop.
The thread's full message history. Plugins read it via `rtCtx.history.messages` (readonly) or `rtCtx.history.recent(n)`.
- **Type:** `{ wsId?: string; did: string }`
- **Owner:** runtime.
Per-request runtime config — primarily the user's DID and the WebSocket connection ID.
- **Type:** `'portal' | 'matrix' | 'slack'`
- **Owner:** runtime.
Which client surface this turn arrived through. Same value plugins read via `rtCtx.session.client`.
- **Type:** `string | undefined`
- **Owner:** Editor plugin.
The active BlockNote room for editor sub-agent work.
- **Type:** `string | undefined`
- **Owner:** runtime / plugins.
The active workspace/space ID, when relevant.
- **Type:** `string | undefined`
- **Owner:** Domain Indexer plugin.
The IXO entity DID currently in focus, when set by a domain lookup.
- **Type:** `BrowserToolCall[] | undefined`
- **Default:** `[]` (empty array).
- **Owner:** Portal client.
Browser tools declared by the Portal frontend for this turn. Each entry is `{ name, description, schema }`. The Portal plugin's sub-agent only builds when this array is non-empty.
- **Type:** `AgAction[] | undefined`
- **Default:** `[]` (empty array).
- **Owner:** Portal client (AG-UI).
AG-UI actions declared by the frontend. Each entry is `{ name, description, schema, hasRender? }`. The AG-UI plugin's sub-agent only builds when this is non-empty.
- **Type:** `UserContextData` (`Record`)
- **Owner:** Memory plugin (writes via enrichment middleware).
Memory-enriched user profile. Other plugins read via `rtCtx.shared.userProfile` (registered by Memory's `getSharedState`).
- **Type:** `UserPreferences | undefined`
- **Owner:** user-preferences plugin.
Behavioural preferences (tone, format, length) injected into the prompt.
- **Type:** `string[]`
- **Reducer:** union via Set (deduplicating).
- **Default:** `[]`.
- **Owner:** runtime — written by the `load_capability` meta-tool.
The names of `on-demand` plugins the agent has loaded for this thread. Monotonically growing across turns. Cleared on new thread.
This is the **single new field** the plugin runtime added to the state — every other field above pre-dates the plugin rewrite.
## ReadonlyState
Plugins access the state via `rtCtx.history.state`, which is typed as `ReadonlyState`:
```ts
export interface ReadonlyState {
readonly messages: readonly BaseMessage[];
readonly userContext?: UserContextData;
readonly loadedPlugins?: ReadonlySet;
readonly [key: string]: unknown;
}
```
The full annotation state is open-ended (`[key: string]: unknown`), so plugins can read field names they know exist but the type doesn't enforce it. For fully-typed reads, declare the field on `SharedAccessors` via shared state, or check existence at runtime.
## Reducers
Each field has a reducer that merges partial updates from agent nodes:
- **`messages`** — append + dedupe by ID (LangGraph default for the messages channel).
- **`loadedPlugins`** — union via Set; never removes.
- Most other fields use last-write-wins or "merge if present" semantics; consult the source for the exact reducer when authoring middleware that mutate state.
Plugin middleware hooks must **not** return a partial state object (`{ messages: ... }`, etc.). A middleware hook returns only `undefined` (pass through) or `{ jumpTo: 'end' as const }` (flow control). Returning a state channel from `beforeAgent` / `wrapModelCall` / `afterModel` breaks LangGraph checkpointer thread continuity (the symptom is "a new thread per message"). Message rewrites belong in the transport/messages layer, not in a middleware.
## Checkpointing
State is checkpointed per thread to per-user SQLite via `UserMatrixSqliteSyncService` + `SqliteSaver`. The DB lives under `SQLITE_DATABASE_PATH`, synced to Matrix in the background.
The runtime exposes `hooks.checkpointerForUser(userDid)` so hosts can override the default per-user checkpointer with an alternate implementation — pass via `createOracleApp({ hooks })`.
## Related references
- [RuntimeContext](/build-an-oracle/reference/runtime-context) — `rtCtx.history` field.
- [Meta-tools concept](/build-an-oracle/understand/meta-tools-and-loading) — how `loadedPlugins` is populated.
---
# Environment variables
> Tier-0 (core) vars the runtime always requires, plus per-plugin vars contributed by each bundled plugin's configSchema.
## How env validation works
The runtime composes one big Zod schema at boot: the **Tier-0 base schema** (always required) plus every loaded plugin's **`configSchema`**. `process.env` is validated against the merged schema. Missing required vars fail boot with `[boot-error] Plugin '' env validation failed for ''`.
Disabling a plugin (via `features` or `autoDetect`) removes its env requirements automatically.
## Tier-0 (core)
Always required by the runtime. Source: `packages/oracle-runtime/src/config/base-env-schema.ts`.
### Runtime
| Variable | Type | Default | Notes |
| --- | --- | --- | --- |
| `NODE_ENV` | `'development' \| 'production' \| 'test'` | `'development'` | |
| `PORT` | number (coerced) | `3000` | Override at `app.listen(port)` if needed. |
| `ORACLE_NAME` | string | — | Required. |
| `CORS_ORIGIN` | string | `'*'` | Wildcard disables credentials. |
| `NETWORK` | `'mainnet' \| 'testnet' \| 'devnet'` | — | Required. |
### Matrix
| Variable | Type | Default | Notes |
| --- | --- | --- | --- |
| `MATRIX_BASE_URL` | string | — | Required. |
| `MATRIX_RECOVERY_PHRASE` | string | — | Required. |
| `MATRIX_STORE_PATH` | string | `'./matrix-storage'` | Must persist across restarts. |
| `MATRIX_ORACLE_ADMIN_USER_ID` | string | — | Required. |
| `MATRIX_ORACLE_ADMIN_PASSWORD` | string | — | Required. |
| `MATRIX_ORACLE_ADMIN_ACCESS_TOKEN` | string | — | Required. |
| `MATRIX_ACCOUNT_ROOM_ID` | string | — | Required. |
| `MATRIX_VALUE_PIN` | string | — | Required. |
### Storage
| Variable | Type | Default | Notes |
| --- | --- | --- | --- |
| `SQLITE_DATABASE_PATH` | string | — | Required. Must persist across restarts. |
### Blocksync / chain
| Variable | Type | Default | Notes |
| --- | --- | --- | --- |
| `BLOCKSYNC_GRAPHQL_URL` | string | — | Required. |
| `ORACLE_DID` | string | — | Required. The oracle's own DID (`did:ixo:ixo1...`) — the signer identity used by the UCAN service and the auth middleware. |
| `ORACLE_ENTITY_DID` | string | — | Required. The oracle's on-chain entity record DID. Distinct from `ORACLE_DID` — do not conflate them. |
| `SECP_MNEMONIC` | string | — | Required. |
| `RPC_URL` | string | — | Required. |
### Auth / UCAN
Always present. Both are `z.coerce.number()` — set them as plain integer strings in `.env`.
| Variable | Type | Default | Notes |
| --- | --- | --- | --- |
| `UCAN_AUTH_MAX_TTL_SECONDS` | number (coerced) | `900` | Max lifetime (seconds) the oracle accepts for a user **auth invocation**. Bounds the server-side replay window regardless of the TTL the client declares. Default 15 minutes. |
| `UCAN_REAUTH_PROMPT_THROTTLE_SECONDS` | number (coerced) | `21600` | Throttle window (seconds) between "please re-authorize" prompts posted into a user's Matrix room when their stored delegation is missing or expired. Default 6 hours. |
### LLM
| Variable | Type | Default | Notes |
| --- | --- | --- | --- |
| `LLM_PROVIDER` | `'openrouter' \| 'nebius'` | `'openrouter'` | |
| `OPENAI_API_KEY` | string | — | Optional. |
| `OPEN_ROUTER_API_KEY` | string | — | Required if `LLM_PROVIDER=openrouter`. |
| `NEBIUS_API_KEY` | string | — | Required if `LLM_PROVIDER=nebius`. |
Cross-field check (`validateLlmProviderKey`): the API key for the selected `LLM_PROVIDER` must be present — `OPEN_ROUTER_API_KEY` when `LLM_PROVIDER=openrouter` (the default), `NEBIUS_API_KEY` when `LLM_PROVIDER=nebius`. A missing key fails boot with a named-field error (e.g. `OPEN_ROUTER_API_KEY` / `NEBIUS_API_KEY`) rather than a generic upstream 401 at request time. The per-role model ids are hardcoded per provider — there is no env var to swap the main model id (use the `resolveModel` hook for that).
### Misc
| Variable | Type | Default | Notes |
| --- | --- | --- | --- |
| `ORACLE_SECRETS` | string | `''` | Comma-separated `KEY=value` pairs surfaced as `x-os-*` headers to capabilities. |
| `LIVE_AGENT_AUTH_API_KEY` | string | `''` | Optional. |
### LangSmith tracing (optional)
| Variable | Type | Default | Notes |
| --- | --- | --- | --- |
| `LANGSMITH_TRACING` | string | — | Set to `'true'` to enable. |
| `LANGSMITH_API_KEY` | string | — | |
| `LANGSMITH_PROJECT` | string | — | |
| `LANGSMITH_ENDPOINT` | string | — | Optional override of the LangSmith API URL. |
Declared in the base schema so they show up in `qiforge-cli env` output. LangChain auto-wires when these are present in `process.env`; the runtime never reads them directly.
## Per-plugin
Only required when the named plugin is loaded.
| Plugin | Variable | Required | Notes |
| --- | --- | --- | --- |
| `memory` | `MEMORY_MCP_URL` | Yes | Must be a valid HTTP(S) URL. |
| `memory` | `MEMORY_ENGINE_URL` | Yes | Must be a valid HTTP(S) URL. |
| `firecrawl` | `FIRECRAWL_MCP_URL` | Yes | Must be a valid HTTP(S) URL. |
| `domain-indexer` | `DOMAIN_INDEXER_URL` | No | URL override; otherwise resolved from `NETWORK`. |
| `composio` | `COMPOSIO_API_KEY` | Yes (when loaded) | |
| `composio` | `COMPOSIO_BASE_URL` | No | Defaults to `https://composio.ixo.earth`. |
| `sandbox` | `SANDBOX_MCP_URL` | Yes | Must be a valid URL. |
| `skills` | `SKILLS_CAPSULES_BASE_URL` | No | URL. Defaults to `https://capsules.skills.ixo.earth`. |
| `slack` | `SLACK_BOT_OAUTH_TOKEN` | Yes | Triggers autoDetect. |
| `slack` | `SLACK_APP_TOKEN` | No | |
| `slack` | `SLACK_USE_SOCKET_MODE` | No | Defaults to `'true'`. |
| `slack` | `SLACK_MAX_RECONNECT_ATTEMPTS` | No | Coerced to number; default `10`. |
| `slack` | `SLACK_RECONNECT_DELAY_MS` | No | Coerced to number; default `1000`. |
| `credits` | `SUBSCRIPTION_URL` | No | URL. |
| `credits` | `SUBSCRIPTION_ORACLE_MCP_URL` | No | URL. |
| `credits` | `DISABLE_CREDITS` | No | Enum — exactly `'true'` or `'false'` (any other value fails env validation). `'true'` disables the credit-enforcement middleware; the plugin's `autoDetect` also excludes the whole plugin when `DISABLE_CREDITS=true`. |
| `tasks` | `REDIS_URL` | Yes (when loaded) | Triggers autoDetect. |
| `tasks` | `TASKS_MAX_PER_USER` | No | Coerced positive int; default `50`. Max scheduled tasks per user. |
| `tasks` | `TASKS_RUN_LOCK_TTL_SEC` | No | Coerced positive int; default `600`. Per-run lock TTL (seconds). |
| `tasks` | `TASKS_MIN_CRON_INTERVAL_SEC` | No | Coerced positive int; default `300`. Minimum allowed cron interval (seconds). |
| `matrix-group-chats` | `CHANNEL_MEMORY_SYNC_INTERVAL_MS` | No | Coerced int, min `1000`; default `60000`. Debounce window between a write and the Matrix snapshot upload. |
| `matrix-group-chats` | `GROUP_CHAT_ACTIVE_THREAD_TTL_MS` | No | Coerced int, min `60000`; default `1800000`. How long a thread stays "active with the bot" after a reply. |
| `matrix-group-chats` | `GROUP_CHAT_REQUIRE_POWER_LEVEL` | No | Coerced int, min `0`; default `0`. Extra minimum power level the bot must have before posting (`0` = use the room default). |
| `matrix-group-chats` | `GROUP_CHAT_ROOM_INFO_TTL_MS` | No | Coerced int, min `60000`; default `1800000`. How long roomInfo (membership, DM flag) stays cached. |
| `vfs` | `VFS_MAX_READ_LINES` | No | Coerced positive int; default `2000`. Max lines a single `vfs_read` window returns. |
| `vfs` | `VFS_REQUEST_TIMEOUT_MS` | No | Coerced positive int; default `20000`. Per-request timeout to the VFS worker. |
## Variables read but not owned
Some plugins read variables that live in another schema:
- **`composio`** reads the core `NETWORK` and forwards it as `x-ixo-network`.
- **`skills`** reads `NETWORK` and forwards as `X-IXO-Network`.
- **`sandbox`** reads `ORACLE_SECRETS` (core) and `SKILLS_CAPSULES_BASE_URL` (owned by `skills`) and forwards them as headers.
- **`vfs`** reads the core `NETWORK` (selects the bundled VFS + UCAN Store worker URLs — nothing to configure) and `SANDBOX_MCP_URL` (owned by `sandbox`; when set, adds the two sandbox↔files bridge tools).
These are declared in the plugin's sibling schemas (typed `optional()`); a missing value just skips the matching header instead of failing the plugin build.
## Generating an `.env` template
`qiforge-cli env` generates a `.env` template for your installed plugin set. Until that lands, write the file by hand using this reference.
## Related references
- [Plugin catalog](/build-an-oracle/reference/bundled-plugins/overview) — env vars per plugin in catalog form.
- [Plugin config and env guide](/build-an-oracle/develop/plugin-recipes/add-config-and-env) — declaring your own plugin's vars.
---
# API endpoints
> The HTTP and WebSocket surface a QiForge oracle exposes — sessions, messages, streaming chat, health, and the typed events emitted on every turn.
## Overview
A running QiForge oracle exposes a REST + WebSocket surface. Most routes require a UCAN delegation header; some (`/health`, `/docs`, plus anything declared in `getAuthExcludedRoutes()` or `authExcludedRoutes`) are public.
The Swagger UI at `/docs` is generated from the running app's controllers — open it in a browser for the live, deployment-specific reference.
## Authentication
Protected routes authenticate with a user-signed UCAN **invocation**, plus an optional delegation for downstream authorization. This is the same model the [Identity and auth](/build-an-oracle/develop/identity-and-auth) guide describes.
| Header | Required | Notes |
| --- | --- | --- |
| `Authorization: Bearer ` | Yes (primary) | The user-signed UCAN invocation — proves *who* is calling. Only read when `X-Auth-Type: ucan` is also present. |
| `X-Auth-Type: ucan` | Yes (primary) | Selector telling the middleware to read the bearer as a UCAN invocation. Without it the bearer is ignored. |
| `x-ucan-delegation` | Fallback / downstream-authz | The user→oracle delegation. Carries the capabilities plugins use to mint downstream invocations. Also accepted as the auth artifact on its own for clients that haven't migrated to invocation auth. |
| `x-did` | No | The user's IXO DID (informational). The runtime derives the authenticated DID from the invocation/delegation — **`x-did` is not used for authentication.** |
| `x-matrix-access-token` | No | Matrix session token (used by Portal/Slack). |
| `x-matrix-homeserver` | No | Matrix homeserver URL. |
| `x-timezone` | No | Propagates to `rtCtx.user.timezone`. |
| `x-request-id` | No | Correlation ID; echoed back as `X-Request-Id`. |
When neither an invocation nor a delegation is present, protected routes return **401**:
```text
Missing UCAN authentication: provide Authorization: Bearer with X-Auth-Type: ucan, or an x-ucan-delegation header
```
A malformed or expired invocation returns **401** `Invalid UCAN invocation`. Public routes ignore auth headers and don't validate them.
## CORS
`CORS_ORIGIN` controls allowed origins (wildcard `*` is the default; specific origins enable credentials).
Allowed request headers:
```text
Content-Type, Authorization, x-ucan-delegation,
x-matrix-access-token, x-matrix-homeserver,
x-did, x-request-id, x-auth-type, x-timezone
```
Allowed methods: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`.
Exposed response headers: `X-Request-Id`.
## Public routes
| Route | Method | Auth | Purpose |
| --- | --- | --- | --- |
| `/` | GET | Public | Landing JSON (`{ status, message, timestamp }`). |
| `/health` | GET | Public | Liveness probe (`{ status, timestamp }`). |
| `/docs` | GET | Public | Swagger UI. |
| `/docs/(.*)` | GET | Public | Swagger static assets. |
Plus any route returned by a plugin's `getAuthExcludedRoutes()` or by `createOracleApp({ authExcludedRoutes })`.
## Protected routes
All of these require the auth headers above. For the request/response schemas of your specific deployment, hit `GET /docs`.
### Sessions (`/sessions`)
| Route | Method | Purpose |
| --- | --- | --- |
| `/sessions` | POST | Create a chat session. Returns the new `sessionId`. |
| `/sessions?limit&offset` | GET | List the authenticated user's sessions (paginated; defaults `limit=20`, `offset=0`). |
| `/sessions/:sessionId` | DELETE | Delete a session. |
### Messages (`/messages`)
| Route | Method | Purpose |
| --- | --- | --- |
| `/messages/:sessionId` | POST | Send a message. Set `stream: true` in the body for an SSE stream instead of a single JSON reply. |
| `/messages/:sessionId` | GET | List the messages in a session. |
| `/messages/abort` | POST | Abort an in-flight stream. Body: `{ "sessionId": "..." }`. |
`POST /messages/:sessionId` body (`SendMessageDto`):
| Field | Type | Notes |
| --- | --- | --- |
| `message` | string | **Required.** The user's message text. |
| `stream` | boolean | `true` → SSE stream; otherwise a single JSON reply. Default `false`. |
| `returnAllMessages` | boolean | Non-stream only: also return the full transcript. Testing aid. Default `false`. |
| `tools` | array | Browser-tool declarations (`{ name, schema, description }`). |
| `agActions` | array | AG-UI action declarations (`{ name, description, schema, hasRender? }`). |
| `attachments` | array | Up to 10 Matrix attachments (`mxcUri` or `eventId`, plus `filename`, `mimetype`). |
| `mcpInvocations` | object | Map of MCP tool name → base64 CAR invocation for protected MCP tools. |
| `timezone` | string | Overrides `rtCtx.user.timezone`. |
| `homeServer` | string | The user's Matrix homeserver. |
| `metadata` | object | Free-form (`editorRoomId`, `spaceId`, …). |
Streaming is a flag on this single endpoint — there is no separate `/stream` route.
### Delegation (`/delegation`)
A user→oracle UCAN delegation lets the oracle mint downstream-service invocations on the user's behalf. The client-sdk re-auth popup POSTs here; a non-React client must implement this to complete the authorization flow.
| Route | Method | Purpose |
| --- | --- | --- |
| `/delegation` | POST | Store a freshly-signed delegation. Returns `{ ok: true, expiration? }`. |
| `/delegation` | GET | Authorization status: `{ authorized: boolean, expiration? }`. |
| `/delegation` | DELETE | Revoke the stored delegation. Returns `{ ok: true }`. |
`POST /delegation` body (`StoreDelegationDto`):
| Field | Type | Notes |
| --- | --- | --- |
| `raw` | string | **Required.** Base64-encoded UCAN delegation CAR (user → oracle). |
| `issuer` | string | Delegation issuer DID (the user). |
| `audience` | string | Delegation audience DID (this oracle). |
| `expiration` | number | Unix timestamp, seconds. |
### Other modules
- **`SubscriptionModule`** — credit/subscription gating middleware (active when the `credits` plugin is loaded).
- **`WsModule`** — the WebSocket gateway (see below).
Plugins contribute more routes via `getNestModules()` — e.g. `SlackModule` (Slack webhooks), `UserPreferencesController` (`/user-preferences`), and claim-processing cron endpoints. The Swagger UI at `/docs` is generated from your app's controllers — including plugin and host routes — so it's always the authoritative, deployment-specific list.
## WebSocket
The oracle runs a socket.io gateway on namespace `/`. Plugins emit typed events via `rtCtx.emit`; the framework forwards them to the client over the session's room.
### Handshake
Connect with a `sessionId` query param and a UCAN in `auth`. A handshake with no `sessionId`, or with no valid UCAN, is disconnected immediately.
```ts
import { io } from 'socket.io-client';
const socket = io('https://your-oracle.example', {
query: { sessionId },
auth: {
invocation, // primary: the user-signed UCAN invocation
ucanDelegation, // fallback: a bare delegation (pre-invocation clients)
},
});
```
The server validates the invocation (primary) or delegation (fallback) the same way HTTP requests do; the authenticated DID is the validated invoker, never the query value. On success it emits `connected`.
### Server → client events
| Event | When it fires |
| --- | --- |
| `connected` | Handshake accepted. |
| `pong` | Reply to a `ping`. |
| `status` | Reply to a `status` request (connection stats). |
| `subscribed` | Subscription acknowledged. |
| `available-events` | Reply to `list-events` — the full event catalog. |
| `tool_call` | A tool is being invoked (or has returned). |
| `action_call` | An AG-UI action is being invoked. |
| `render_component` | A render-component result is ready for the client. |
| `browser_tool_call` | A browser-side tool is being invoked on the user's tab. |
| `router_update` | A routing decision between agents/sub-agents. |
| `message_cache_invalidation` | A cached message should be dropped. |
### Client → server events
| Event | Purpose |
| --- | --- |
| `ping` | Keep-alive; server replies `pong`. |
| `status` | Request connection stats; server replies `status`. |
| `subscribe` | Subscribe to session events. |
| `list-events` | Ask for the event catalog; server replies `available-events`. |
| `tool_result` | Return a browser-tool result (`{ toolCallId, result, error? }`). |
| `action_call_result` | Return an AG-UI action result (`{ sessionId, toolCallId, result }`). |
Event payload types are currently `Record` and may be tightened in future runtime versions. The `@ixo/oracles-client-sdk` React SDK handles this handshake, parses these events, and renders them.
## Client SDK
For frontend integration, use `@ixo/oracles-client-sdk` — it handles the SSE / WebSocket protocol, UCAN delegation, and event parsing. See [Client SDK](/build-an-oracle/reference/client-sdk).
## Related references
- [Identity and auth](/build-an-oracle/develop/identity-and-auth) — the UCAN flow.
- [Plugin HTTP endpoints](/build-an-oracle/develop/plugin-recipes/add-http-endpoints) — adding your own routes.
- [Client SDK](/build-an-oracle/reference/client-sdk) — React integration.
---
# CLI reference
> Every qiforge-cli command — installation, authentication, oracle scaffolding, plugin scaffolding, entity ops, chat.
The CLI installs from the `qiforge-cli` npm package and runs as the `qiforge-cli` binary ([source](https://github.com/ixoworld/ixo-oracles-cli)). It scaffolds new oracles, scaffolds new plugins inside an existing oracle, provisions on-chain identity + Matrix bots, and gives you an SSE chat client for testing.
## Installation
```sh
npm install -g qiforge-cli
# or
pnpm add -g qiforge-cli
```
**pnpm users:** after install, approve build scripts for `protobufjs`:
```sh
pnpm approve-builds -g
# select protobufjs when prompted
```
Verify:
```sh
qiforge-cli --help
```
Requires Node 22+. Authentication needs either the IXO Mobile App (SignX QR) or a 12/24-word mnemonic (offline mode).
## Commands at a glance
| Command | Purpose |
| --- | --- |
| `qiforge-cli` | Interactive menu (auth → choose command). |
| `qiforge-cli new ` | Scaffold a new oracle from the bundled starter. |
| `qiforge-cli plugin new ` | Scaffold a new plugin into an existing oracle project. |
| `qiforge-cli create-entity` | Create the on-chain entity record + Matrix bot. |
| `qiforge-cli update-entity` | Update an existing entity (e.g. add controllers). |
| `qiforge-cli update-oracle-api-url` | Update the oracle's registered API URL/domain. |
| `qiforge-cli setup-encryption-key` | Provision the oracle's encryption/signing key. |
| `qiforge-cli create-composio-key` | Mint a Composio API key tied to the oracle. |
| `qiforge-cli create-user` | Create a new user account. |
| `qiforge-cli signx-login` | Authenticate via SignX QR (IXO Mobile App). |
| `qiforge-cli offline-login` | Authenticate with a local mnemonic (no mobile app). |
| `qiforge-cli logout` | Clear stored credentials. |
| `qiforge-cli help` | Show help. |
| `--chat` | Flag — start a chat session with a running oracle. |
| `--help`, `-h` | Flag — show help. |
## Authentication
Two modes:
Uses the IXO Mobile App for QR-code-based authentication. Keep the app open during the session.
```sh
qiforge-cli signx-login
# Scan the QR code with the IXO Mobile App when prompted.
```
Uses a local mnemonic. No mobile app needed. Credentials are stored in `~/.wallet.json`.
Interactive:
```sh
qiforge-cli offline-login
```
Non-interactive:
```sh
qiforge-cli offline-login \
--network devnet \
--mnemonic "your twelve word mnemonic phrase here" \
--matrixPassword "your-matrix-password"
```
**Flags:**
| Flag | Description |
| --- | --- |
| `--network` | `devnet`, `testnet`, `mainnet` |
| `--mnemonic` | Mnemonic phrase for wallet derivation. |
| `--matrixPassword` | Matrix account password. |
| `--name` | Display name (falls back to Matrix profile name). |
## qiforge-cli new
Scaffolds a new oracle from the bundled starter template — no git clone. Interactive by default: it walks you through auth, network, the oracle profile, and entity creation, then writes the project.
```sh
qiforge-cli new my-oracle
```
Non-interactive (for CI) — `--name` is required:
```sh
qiforge-cli new --no-interactive \
--name my-oracle \
--description "What this oracle does" \
--org "My Org" \
--install
```
**Flags:**
| Flag | Description | Default |
| --- | --- | --- |
| `--name` | Project name (required with `--no-interactive`). | — |
| `--path` | Directory to scaffold into. | `./` |
| `--template` | Starter template. | `basic` |
| `--description` | Oracle description. | — |
| `--org` | Organisation name. | `IXO` |
| `--install` | Run the package install after scaffolding. | `false` |
| `--force` | Overwrite an existing directory. | `false` |
| `--no-interactive` | Skip prompts (requires `--name`). | `false` |
The command optionally runs the install step and can also create the oracle entity + Matrix account in the same flow.
## qiforge-cli plugin new
Scaffolds a new plugin into an **existing** oracle project. Run it from inside the project; the CLI walks up to find a `package.json` that depends on `@ixo/oracle-runtime` and writes the plugin there.
```sh
qiforge-cli plugin new climate
```
**Flag:** `--cwd` — the directory to resolve the oracle project from (defaults to the current directory).
Generated layout (under `src/plugins//`):
```text
src/plugins/climate/
├── climate.plugin.ts # OraclePlugin class with a manifest stub + one sample tool
├── climate.plugin.test.ts # 3 tests using createTestRuntime
├── climate.fixtures/ # placeholder for fixtures
└── README-climate.md # the manifest mirrored as docs
```
Name validation: kebab-case, must not collide with bundled plugin names.
## qiforge-cli create-entity
Creates the on-chain entity record and Matrix bot, and writes `oracle.config.json` + `.env`. Used both standalone (e.g. when re-provisioning) and as part of `new`.
```sh
qiforge-cli create-entity --no-interactive \
--network devnet \
--oracle-name "My Oracle" \
--price 100 \
--org-name "My Org" \
--description "Oracle description" \
--api-url http://localhost:4000 \
--model "anthropic/claude-sonnet-4" \
--pin 123456
```
`create-entity` registers `--api-url http://localhost:4000` by default, but the runtime's own default `PORT` is `3000`. If you take the default URL, make your running oracle reachable at it: either set `PORT=4000` in your `.env` to match, or update the registered URL later with `qiforge-cli update-oracle-api-url`.
**Flags:**
| Flag | Description | Default |
| --- | --- | --- |
| `--network` | `devnet`, `testnet`, `mainnet`. | `devnet` |
| `--oracle-name` | Oracle name. | `My oracle` |
| `--price` | Price in IXO credits. | `100` |
| `--org-name` | Organisation name. | `IXO` |
| `--logo` | Logo URL. | Auto-generated. |
| `--cover-image` | Cover image URL. | Same as logo. |
| `--location` | Location string. | `New York, NY` |
| `--description` | Entity description. | Default placeholder. |
| `--website` | Website URL. | — |
| `--api-url` | Oracle API URL. | `http://localhost:4000` |
| `--project-path` | Project directory for saving config. | Current directory. |
| `--pin` | 6-digit PIN for Matrix vault. | Prompted. |
| `--model` | LLM model identifier. | `moonshotai/kimi-k2.5` |
| `--skills` | Comma-separated skill list. | — |
| `--prompt-opening` | Opening prompt for the oracle. | — |
| `--prompt-style` | Communication style. | — |
| `--prompt-capabilities` | Capabilities description. | — |
| `--mcp-servers` | JSON array `[{"url":"..."}]`. | — |
Supported model identifiers (from the interactive menu):
```text
moonshotai/kimi-k2.5 # default
anthropic/claude-sonnet-4
openai/gpt-4o
google/gemini-2.5-pro
meta-llama/llama-4-maverick
```
Or pass any custom identifier as `--model`.
## qiforge-cli update-entity
Updates an existing entity. Used to add controllers, rotate keys, or modify metadata after the initial create-entity.
```sh
qiforge-cli update-entity
```
Accepts account DIDs as controllers (in addition to entity DIDs).
## qiforge-cli update-oracle-api-url
Updates the URL the oracle entity advertises. Default is `http://localhost:4000`; switch to your deployed URL before going live.
```sh
qiforge-cli update-oracle-api-url
```
## qiforge-cli setup-encryption-key
Provisions the oracle's encryption/signing key into its Matrix account room — the P-256 `keyAgreement` key used to decrypt per-room secrets, plus the signing material the runtime uses to mint downstream UCAN invocations. Until it's provisioned, authenticated routes return 401 and the boot log warns about the missing key.
```sh
qiforge-cli setup-encryption-key
```
Run it once per oracle (and again after rotating the key).
## qiforge-cli create-composio-key
Mints a Composio API key tied to your oracle's DID. Required if your oracle uses the bundled `composio` plugin.
```sh
qiforge-cli create-composio-key
```
Prompts for the oracle DID and a key label, then writes the key to your Composio account.
## qiforge-cli create-user
Creates a new user account (DID + Matrix account) — useful for testing your oracle against multiple identities.
```sh
qiforge-cli create-user
```
## --chat
Starts a chat session with a running oracle over SSE — renders tool calls, assistant messages, and errors in the terminal. It's a flag, not a subcommand:
```sh
qiforge-cli --chat
```
Interactive — prompts for the oracle URL (or uses the saved one from `new`).
## qiforge-cli logout
```sh
qiforge-cli logout
```
Clears stored authentication. Future commands prompt for auth again.
## What `qiforge-cli new` writes
```text
my-oracle/
├── src/
│ ├── main.ts # calls createOracleApp({ config, plugins })
│ ├── config.ts # OracleConfig
│ └── plugins/ # your plugins go here
├── .claude/
│ └── skills/qiforge-oracle/ # Claude Code skill (project-local)
├── .env # Tier-0 vars + initial plugin vars
├── oracle.config.json # name, model, prompt, entityDid, …
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── CLAUDE.md # bootstrapping for Claude Code
```
The CLI also scaffolds a **`qiforge-oracle` Claude Code skill** at `.claude/skills/qiforge-oracle/` so any AI agent you point at the project (Claude Code, Cursor, etc.) immediately has dense, scenario-specific guidance on the framework — adding plugins, adding tools, wiring env, writing tests with `createTestRuntime`, debugging boot. The skill is project-local: it ships inside every scaffolded oracle, no separate install needed. See the [skill source](https://github.com/ixoworld/ixo-oracles-cli/tree/main/src/templates/starter/.claude/skills/qiforge-oracle) in the CLI repo.
Generated `.env` skeleton:
```text
PORT=3000
ORACLE_NAME=your-oracle-name
NETWORK=devnet
MATRIX_BASE_URL=https://matrix.ixo.world
MATRIX_ORACLE_ADMIN_USER_ID=...
MATRIX_ORACLE_ADMIN_PASSWORD=...
MATRIX_ORACLE_ADMIN_ACCESS_TOKEN=...
MATRIX_ACCOUNT_ROOM_ID=...
MATRIX_VALUE_PIN=...
MATRIX_RECOVERY_PHRASE=...
BLOCKSYNC_GRAPHQL_URL=https://devnet-blocksync-graphql.ixo.earth/graphql
RPC_URL=https://devnet.ixo.earth/rpc/
ORACLE_DID=did:ixo:ixo1...
ORACLE_ENTITY_DID=did:ixo:entity:...
SECP_MNEMONIC=...
SQLITE_DATABASE_PATH=./.data/sqlite
LLM_PROVIDER=openrouter
OPEN_ROUTER_API_KEY=
LANGSMITH_API_KEY=
LANGSMITH_PROJECT=
```
Fill in the plugin-specific vars before booting.
## Related references
- [Identity and auth guide](/build-an-oracle/develop/identity-and-auth) — what `create-entity` and `setup-encryption-key` set up.
- [Environment variables](/build-an-oracle/reference/environment-variables) — what to put in the `.env` the CLI writes.
- [CLI source](https://github.com/ixoworld/ixo-oracles-cli) — every command's implementation.
---
# @ixo/oracles-client-sdk
> The React client SDK for QiForge oracles. useChat() hook, browser tools, and event rendering.
## Overview
`@ixo/oracles-client-sdk` is the official React client for a QiForge oracle. It wraps:
- The streaming chat endpoint over SSE.
- The WebSocket event channel (tool calls, render components, browser tools, AG-UI actions).
- UCAN auth — signing invocations (authentication) and delegations (downstream authorization).
- Browser-side tools and AG-UI actions (so the agent can act on the user's tab / UI).
If you're building a Portal-like web UI for your oracle, use this. If you're building a non-React client (CLI, mobile app), implement the HTTP/WS protocol directly — the API reference is [API endpoints](/build-an-oracle/reference/api-endpoints).
## Install
```sh
pnpm add @ixo/oracles-client-sdk
```
`react >= 18` and `zod ^3` are peer dependencies.
## OraclesProvider (required wrapper)
Every hook calls `useOraclesContext()`, which throws `useOraclesContext must be used within a OraclesProvider` if there's no provider above it. Wrap your app once. The provider holds the wallet, signs transactions, and turns your `createDelegation` / `createInvocation` callbacks into the cached auth artifacts the hooks attach to requests.
```tsx
import { OraclesProvider } from '@ixo/oracles-client-sdk';
function Root({ children }) {
return (
{
// sign + broadcast the chain tx with your wallet; return the result
return undefined;
}}
createDelegation={async (oracleDid) => {
// sign a user→oracle UCAN delegation (downstream authorization)
return { serialized: '', expiresAt: Date.now() + 60 * 60 * 1000 };
}}
createInvocation={async (oracleDid) => {
// sign a short-lived UCAN invocation (primary authentication)
return { serialized: '', expiresAt: Date.now() + 15 * 60 * 1000 };
}}
>
{children}
);
}
```
The connected user's wallet + Matrix session. Required.
Signs and broadcasts chain transactions (e.g. paying to contract the oracle).
Mints a user→oracle UCAN delegation. The provider caches it and sends it as `x-ucan-delegation`.
Optional (migration-safe). Mints a short-lived UCAN invocation; the provider sends it as `Authorization: Bearer …` + `X-Auth-Type: ucan` (primary auth). Omit to stay on delegation-only auth.
## useChat
The primary hook. Provides the message thread, the send function, and live streaming state.
```tsx
import { useChat, useOracleSessions, renderMessageContent } from '@ixo/oracles-client-sdk';
function Chat({ oracleDid }: { oracleDid: string }) {
const { sessions, createSession } = useOracleSessions(oracleDid);
const sessionId = sessions?.[0]?.sessionId ?? '';
const { messages, sendMessage, isSending, status } = useChat({
oracleDid,
sessionId,
onPaymentRequiredError: (claimIds) => {
// the oracle needs payment before answering — surface a paywall
console.log('Payment required:', claimIds);
},
});
return (
{messages.map((m) => (
{renderMessageContent(m.content)}
))}
);
}
```
Options (`IChatOptions`):
The oracle's DID. The SDK resolves its API/WS URL from the oracle's on-chain config.
The chat session to read/write. Create one with `useOracleSessions().createSession()`.
Called when the oracle returns a payment-required error. Surface a paywall.
Browser-side tools the agent may call on the user's tab (see below).
Map of render-component name → React component for `render_component` events.
Override the resolved API / WebSocket URLs (local dev, self-host).
How streamed tokens are flushed to state.
Returned values:
| Field | Type | Notes |
| --- | --- | --- |
| `messages` | `IMessage[]` | Thread history, including in-flight streamed content. |
| `sendMessage` | `(message: string) => void` | Submit a user message. |
| `abortStream` | `() => Promise` | Abort the in-flight response. |
| `status` | `'submitted' \| 'streaming' \| 'ready' \| 'error'` | Current chat state. |
| `isSending` | `boolean` | `true` while submitting or streaming. |
| `isLoading` | `boolean` | Initial history fetch in flight. |
| `error` / `sendMessageError` | `Error \| undefined` | Load error / send error. |
| `refetchMessages` | `() => Promise<…>` | Re-pull the thread from the server. |
| `isRealTimeConnected` | `boolean` | WebSocket connection status. |
| `isConfigReady` | `boolean` | The oracle's config has resolved. |
There is no `isStreaming` or `events` field — use `status` / `isSending` for streaming state.
## Browser tools
Browser tools let the agent call DOM/UI actions on the user's tab. Pass them as the `browserTools` option to `useChat` — a map keyed by tool name. The SDK advertises them to the agent, forwards each call to your `fn`, and returns the result over the WebSocket.
```tsx
import { useChat } from '@ixo/oracles-client-sdk';
import { z } from 'zod';
const browserTools = {
open_portal_route: {
toolName: 'open_portal_route',
description: 'Navigate to a route inside the Portal.',
schema: z.object({ route: z.string() }),
fn: async ({ route }: { route: string }) => {
router.push(route);
return { ok: true };
},
},
};
const { messages, sendMessage } = useChat({
oracleDid,
sessionId,
onPaymentRequiredError: () => {},
browserTools,
});
```
There is no `registerBrowserTools` export — browser tools are the `browserTools` option above.
## AG-UI actions
`useAgAction` registers an agent-invokable action with an optional render. The provider tracks registered actions; `useChat` advertises them and renders their output inline.
```tsx
import { useAgAction } from '@ixo/oracles-client-sdk';
import { z } from 'zod';
useAgAction({
name: 'create_data_table',
description: 'Render a data table from structured rows.',
parameters: z.object({
title: z.string().optional(),
columns: z.array(z.any()),
data: z.array(z.any()),
}),
handler: async ({ data }) => ({ ok: true, rowCount: data.length }),
render: ({ status, args }) =>
status === 'done' && args ? : null,
});
```
## Events
Streamed and WebSocket events are surfaced as the typed `AnyEvent` union and rendered through `renderMessageContent` / your `uiComponents` map. The event `eventName` values match the wire names:
| `eventName` | What it carries |
| --- | --- |
| `tool_call` | A tool invocation (and its result). |
| `render_component` | A structured UI component the agent wants displayed. |
| `browser_tool_call` | A call to one of your `browserTools`. |
The full server/client WebSocket event catalog is in [API endpoints → WebSocket](/build-an-oracle/reference/api-endpoints#websocket).
## Other exports
| Export | Purpose |
| --- | --- |
| `useOracleSessions(oracleDid)` | Create, list, and delete chat sessions. |
| `useOraclesConfig(oracleDid)` | The oracle's resolved public config (API URL, model, price). |
| `useContractOracle()` | Pay to contract (subscribe to) an oracle. |
| `useMemoryEngine()` | Read/write the optional persistent memory engine. |
| `useGetOpenIdToken()` / `getOpenIdToken()` | Mint a Matrix OpenID token for the user. |
| `renderMessageContent(content)` | Turn stored message content into React nodes. |
| `OraclesProvider`, `useOraclesContext` | The provider + its context accessor. |
| `@ixo/oracles-client-sdk/live-agent` → `useLiveAgent` | Encrypted voice/video live-agent calls (separate entry point). |
## Auth lifecycle
The provider owns auth. From your `createDelegation` / `createInvocation` callbacks it:
- Caches the signed delegation and invocation per `(userDid, oracleDid)`.
- Attaches `Authorization: Bearer ` + `X-Auth-Type: ucan` (primary) and `x-ucan-delegation` (downstream / fallback) to every request.
- Passes the same artifacts on the WebSocket handshake (`auth.invocation` / `auth.ucanDelegation`).
When a stored delegation is missing or expired, the oracle nudges the user to re-authorize; your app calls `createDelegation` again to mint a fresh one (the client-sdk POSTs it to [`/delegation`](/build-an-oracle/reference/api-endpoints#delegation-delegation)).
## Where to read next
The HTTP / WebSocket protocol the SDK speaks.
UCAN delegation and the per-request auth headers.
---
# Glossary
> Short definitions of QiForge terms with links to the concept or reference page where each one is fully covered.
## Terms
**Agent** — the main LLM loop. Built per request by `createMainAgent`, which composes tools, sub-agents, and middleware from the plugin registries and invokes LangChain's `createAgent`.
**`availablePlugins`** — `ReadonlySet` of plugin names that survived boot resolution. Fixed at boot. Exposed on both `PluginContext` and `RuntimeContext`. Used for soft-dep branching. See [Dependencies](/build-an-oracle/develop/plugin-recipes/declare-dependencies).
**`autoDetect`** — predicate `(env) => boolean` a plugin implements to opt itself in or out based on `process.env`. Runs when `features` doesn't explicitly set the toggle. See [Using bundled plugins](/build-an-oracle/develop/enable-bundled-plugins).
**Bundled plugin** — a plugin shipped inside `@ixo/oracle-runtime` (one of 15). Loaded from `BUNDLED_PLUGINS` by default. See [Plugin catalog](/build-an-oracle/reference/bundled-plugins/overview).
**Checkpointer** — per-user SQLite saver (`UserMatrixSqliteSyncService` + `SqliteSaver`) that persists graph state across turns. Synced to Matrix in the background. See [State schema](/build-an-oracle/reference/state-schema).
**Composer** — the boot phase that merges every loaded plugin's `configSchema` with the Tier-0 base schema into a single Zod object, validates `process.env`, and warns on collisions. Lives in `packages/oracle-runtime/src/bootstrap/schema-composer.ts`.
**`configSchema`** — Zod schema a plugin declares for its env vars. Merged into the runtime's schema at boot. See [Plugin config and env](/build-an-oracle/develop/plugin-recipes/add-config-and-env).
**Context** — see *PluginContext* and *RuntimeContext*.
**`createOracleApp`** — the single entry point of the runtime. Resolves plugins, validates env, populates registries, bootstraps NestJS, returns an `OracleApp`. See [reference](/build-an-oracle/reference/createoracleapp).
**`defineOraclePlugin`** — POJO form of plugin authoring. Identity function with type-checking; produces the same internal shape as extending `OraclePlugin`. See [Plugin API](/build-an-oracle/reference/plugin-api).
**`dependsOn`** — hard dependency declaration. Boot fails if a listed plugin is not loaded. Used for topological sort. See [Dependencies](/build-an-oracle/develop/plugin-recipes/declare-dependencies).
**DID** — Decentralized Identifier. Two flavours in QiForge: `did:ixo:ixo1...` (a user or the oracle itself) and `did:ixo:entity:...` (an on-chain entity record). The oracle has both `ORACLE_DID` and `ORACLE_ENTITY_DID`.
**Feature toggle (`features`)** — `Partial>` map on `createOracleApp`. Overrides per-plugin opt-in. `true` forces on, `false` forces off, `'auto'` runs `autoDetect`.
**Identity** — the oracle's own identity. Lives on `PluginContext.identity` and includes `name`, `org`, `description`, `entityDid`, `prompt`. Set from `OracleConfig` + `ORACLE_ENTITY_DID` env var.
**`list_capabilities`** — meta-tool that lists every visible plugin's name, summary, visibility, loaded flag, category, tags. See [Meta-tools](/build-an-oracle/understand/meta-tools-and-loading).
**`load_capability`** — meta-tool that marks a plugin as loaded for the current thread, returns its full manifest + tool list, and appends a `ToolMessage` so the agent sees the manifest on the same turn. See [Meta-tools](/build-an-oracle/understand/meta-tools-and-loading).
**`loadedPlugins`** — graph state field tracking which `on-demand` plugins the agent has loaded for this thread. Monotonic union (a Set reducer). The only new field the plugin runtime added to the legacy state schema. See [State schema](/build-an-oracle/reference/state-schema).
**Loader** — the boot phase that resolves the final plugin set from `BUNDLED_PLUGINS` + user `plugins`, applies `features` and `autoDetect`, topologically sorts by `dependsOn`. Lives in `packages/oracle-runtime/src/bootstrap/plugin-loader.ts`.
**Main agent** — see *Agent*. The single LLM loop per request, with sub-agents wrapped as tools.
**Manifest** — `PluginManifest`. Structured metadata the agent reads (`title`, `summary`, `whenToUse`, etc.). See [Manifest schema](/build-an-oracle/reference/manifest-schema).
**Matrix** — the encrypted messaging system used as QiForge's data store (per-user encrypted rooms hold thread history and oracle secrets) and as a transport (when the `matrix` client is in use). The Matrix-backed SQLite checkpointer is not pluggable.
**Meta-tool** — a built-in tool the runtime registers on every agent, not authored by any plugin. There are two: `list_capabilities` and `load_capability`. See [Meta-tools](/build-an-oracle/understand/meta-tools-and-loading).
**Middleware** — LangChain `AgentMiddleware`. Hooks: `beforeAgent`, `beforeModel`, `wrapModelCall`, `wrapToolCall`, `afterModel`, `afterAgent` (there is no `onError` middleware hook — to handle errors, wrap a call in `wrapModelCall`/`wrapToolCall` with try/catch). Four are always-on, in order — capability gate, tool validation, tool-repetition guard, tool retry — plus two conditional: page context (when `hooks.getRoomTitle` is set) and safety guardrail (when `hooks.safetyModel` is set). Plugins add more via `getMiddlewares`. See [Plugin middleware](/build-an-oracle/develop/plugin-recipes/add-a-middleware).
**NestJS module** — a unit of Nest DI (controllers, providers, lifecycle). Plugins ship modules via `getNestModules`; the host ships them via `createOracleApp({ nestModules })`.
**OracleApp** — the object returned by `createOracleApp`. Exposes `getNestApp`, `plugins.status`, `beforeListen`, `onError`, `onPluginStatusChange`, `listen`. See [createOracleApp reference](/build-an-oracle/reference/createoracleapp).
**`OracleConfig`** — the inline oracle config passed to `createOracleApp({ config })`. Holds `name`, `org`, `description`, `prompt`. `entityDid` is sourced from `ORACLE_ENTITY_DID` env.
**`OraclePlugin`** — the abstract class every plugin extends. See [Plugin API](/build-an-oracle/reference/plugin-api).
**Plugin** — a unit of extension. Class or POJO. Contributes any combination of tools, sub-agents, middleware, Nest modules, shared state, config, and auth exclusions.
**`PluginContext`** — boot-time context passed to plugin builder hooks. Holds `config`, `identity`, `availablePlugins`, `logger`. No user. See [reference](/build-an-oracle/reference/plugin-context).
**`PluginManifest`** — see *Manifest*.
**Registry** — internal collection that holds plugin contributions of one kind. Six registries: tools, sub-agents, middleware, manifests, configSchema, sharedState. Populated at boot.
**`RuntimeContext`** — per-request context. Holds `user`, `session`, `history`, `secrets`, `matrix`, `ucan`, `llm`, `emit`, `logger`, `abortSignal`, `shared`. See [reference](/build-an-oracle/reference/runtime-context).
**Shared state** — read-only values one plugin exposes for others via `getSharedState`, accessed by consumers through `rtCtx.shared.`. See [concept](/build-an-oracle/develop/plugin-recipes/share-state).
**`softDependsOn`** — soft dependency declaration. Plugin loads regardless; check `availablePlugins.has(...)` at runtime. See [Dependencies](/build-an-oracle/develop/plugin-recipes/declare-dependencies).
**Sub-agent** — a focused inner agent with its own prompt and tool list. The runtime wraps each one as a tool (`call_memory_agent`, `call_weather_planner_agent`, …). See [Plugin sub-agents](/build-an-oracle/develop/plugin-recipes/add-a-sub-agent).
**Tier-0** — the runtime's base env schema. Always required. Owned by `@ixo/oracle-runtime`. See [Environment variables](/build-an-oracle/reference/environment-variables).
**Tier-1** — the prompt block listing `always` plugins (`- {name}: {summary}`). Composed at request build from the manifest registry. See [Visibility tiers](/build-an-oracle/develop/plugin-recipes/set-visibility).
**Topological sort** — the boot ordering pass over `dependsOn`. Plugins are loaded in dependency order; middleware fires in this order; tools are emitted in this order (for deterministic prompts).
**UCAN** — User-Controlled Authorisation Network. Authentication is a user-signed UCAN *invocation* (`Authorization: Bearer …` + `X-Auth-Type: ucan`); authorization is the user→oracle *delegation* (`x-ucan-delegation`, also accepted as a migration-only auth fallback). The oracle mints downstream invocations signed by its `SECP_MNEMONIC`. See [Identity and auth](/build-an-oracle/develop/identity-and-auth).
**Visibility** — one of `always`, `on-demand`, `silent`. Controls whether a plugin's tools are bound at boot, loaded on demand, or invisible to the agent. See [Visibility tiers](/build-an-oracle/develop/plugin-recipes/set-visibility).
---
# Bundled plugins
> Reference for every plugin the oracle runtime ships with — toggle each one through the features map, set its env vars, and ship to production.
The runtime bundles 16 plugins that load by default; opt out per plugin via the `features` map on `createOracleApp`. One additional plugin — [`flows`](/build-an-oracle/reference/bundled-plugins/flows) — ships in the package but is **opt-in only**: wire it in explicitly via the `plugins` array.
## At a glance
| Name | Visibility | Default state | Required env | Depends on |
| --- | --- | --- | --- | --- |
| [`memory`](/build-an-oracle/reference/bundled-plugins/memory) | `always` | Auto-detect | `MEMORY_MCP_URL`, `MEMORY_ENGINE_URL` | — |
| [`portal`](/build-an-oracle/reference/bundled-plugins/portal) | `on-demand` | On | — | — |
| [`firecrawl`](/build-an-oracle/reference/bundled-plugins/firecrawl) | `on-demand` | Auto-detect | `FIRECRAWL_MCP_URL` | — |
| [`domain-indexer`](/build-an-oracle/reference/bundled-plugins/domain-indexer) | `always` | On | — | — |
| [`composio`](/build-an-oracle/reference/bundled-plugins/composio) | `on-demand` | Auto-detect | `COMPOSIO_API_KEY` | — |
| [`sandbox`](/build-an-oracle/reference/bundled-plugins/sandbox) | `always` | Auto-detect | `SANDBOX_MCP_URL` | — |
| [`skills`](/build-an-oracle/reference/bundled-plugins/skills) | `always` | On | — | `sandbox` |
| [`editor`](/build-an-oracle/reference/bundled-plugins/editor) | `always` | On | — | — |
| [`agui`](/build-an-oracle/reference/bundled-plugins/agui) | `on-demand` | On | — | — |
| [`slack`](/build-an-oracle/reference/bundled-plugins/slack) | `silent` | Auto-detect | `SLACK_BOT_OAUTH_TOKEN` | — |
| [`tasks`](/build-an-oracle/reference/bundled-plugins/tasks) | `on-demand` | Auto-detect | `REDIS_URL` | — |
| [`credits`](/build-an-oracle/reference/bundled-plugins/credits) | `silent` | On unless `DISABLE_CREDITS=true` | — | — |
| [`calls`](/build-an-oracle/reference/bundled-plugins/calls) | `silent` | On (stub) | — | — |
| [`user-preferences`](/build-an-oracle/reference/bundled-plugins/user-preferences) | `always` | On | — | — |
| [`matrix-group-chats`](/build-an-oracle/reference/bundled-plugins/matrix-group-chats) | `on-demand` | On | — | — |
| [`vfs`](/build-an-oracle/reference/bundled-plugins/vfs) | `always` | On | — (URLs from `NETWORK`) | — |
| [`flows`](/build-an-oracle/reference/bundled-plugins/flows) | `on-demand` | Opt-in (not bundled) | — | `editor` Qi Flow engine |
`Default state` legend:
- **On** — loaded by default; opt out with `features: { name: false }`.
- **Auto-detect** — loaded when its env var is set; opt in by setting it, force on with `features: { name: true }`, force off with `false`.
- **(stub)** — placeholder entry in `BUNDLED_PLUGINS` so feature toggles work; full implementation deferred.
## How to use `features`
```ts
const app = await createOracleApp({
config,
features: {
composio: false, // never load even if COMPOSIO_API_KEY is set
slack: true, // force load even if SLACK_BOT_OAUTH_TOKEN is missing (will fail env validation)
'domain-indexer': 'auto', // explicit auto (same as omitting)
},
});
```
`true` forces on, `false` forces off, `'auto'` runs the plugin's `autoDetect` callback. Omitted keys default to `'auto'`.
## Browse
Durable memory across conversations.
Browser-side actions on the user's Portal UI.
Web search and human-readable page scraping.
IXO entity lookup — orgs, projects, DAOs, DIDs.
SaaS tool catalog (Gmail, GitHub, Linear, …).
Per-user Linux box for code execution.
IXO skill capsule discovery.
Read and edit BlockNote workspace pages.
Render interactive UI components in the browser.
Slack bot transport.
Schedule the agent to run on time-based triggers, in the background.
Per-user credit enforcement and claim settlement.
LiveKit call integration (stub — deferred).
Tone / format / language preferences.
Gate the bot + per-room compacted memory for Matrix group rooms.
Read, write, search, and share the user's real files on their Virtual Filesystem.
Author and inspect multi-step Qi Flow templates (opt-in).
## Wiring custom-constructed plugins
Some plugins accept constructor args. Pass a custom instance via the `plugins` array — the loader dedupes by name, so your instance overrides the bundled default.
**`credits` genuinely needs a Redis client.** Without one the enforcement middleware loads in pass-through mode and the settlement cron is skipped, so production must construct it explicitly:
```ts
import { createOracleApp, CreditsPlugin } from '@ixo/oracle-runtime';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
const app = await createOracleApp({
config,
plugins: [new CreditsPlugin({ redis, network: 'devnet' })],
});
```
**`editor` works from env on its own.** The bundled `editorPlugin` is fully functional: when no `matrixClient` is passed it lazily builds an internal Matrix client from the `MATRIX_*` admin env vars. Pass `matrixClient` only to reuse a client your app already keeps synced:
```ts
import { createOracleApp, EditorPlugin } from '@ixo/oracle-runtime';
import * as sdk from 'matrix-js-sdk';
const matrixClient = sdk.createClient({ /* ... */ });
const app = await createOracleApp({
config,
plugins: [new EditorPlugin({ matrixClient })], // optional — env fallback otherwise
});
```
## Read next
Why `skills` + `sandbox` are paired in the catalog.
`features` patterns in depth.
All env vars in one table.
Every field a plugin manifest declares.
---
# memory
> Durable memory across conversations: who the user is, what you made for them, and what worked.
**Source:** [`packages/oracle-runtime/src/plugins/memory/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/memory/)
| Attribute | Value |
| --- | --- |
| Visibility | `always` |
| Stability | `stable` |
| Category | `memory` |
| Default state | Auto-detect (env: `MEMORY_MCP_URL`) |
| Depends on | — |
## Summary
Durable memory across conversations — who the user is, what you have made for them, and what worked. Surfaces the upstream Memory Engine MCP tools verbatim (`memory-engine__search_memory_engine`, `memory-engine__add_memory`, …). Tool list is resolved per-request because MCP headers depend on the in-flight user's UCAN delegation.
## Environment variables
| Var | Required | Description |
| --- | --- | --- |
| `MEMORY_MCP_URL` | yes | Memory MCP HTTP(S) URL. Also triggers auto-detect. |
| `MEMORY_ENGINE_URL` | yes | Memory Engine HTTP(S) URL. |
## What it contributes
- **Tools (default selection):**
- `memory-engine__search_memory_engine` — recall stored facts.
- `memory-engine__add_memory` — write a new memory.
- `memory-engine__delete_episode` — delete a memory.
- `memory-engine__clear` — clear (destructive; main-agent only).
- **Sub-agents:** none.
- **Middleware:** none directly contributed by the plugin (enrichment of `state.userContext` is handled upstream of the agent).
- **HTTP routes:** none.
- **Shared state:** `userProfile` — other plugins read it via `rtCtx.shared.userProfile`.
Tools available beyond the default selection (e.g. `memory-engine__add_oracle_knowledge`, `memory-engine__delete_edge`) can be enabled by passing `selectedTools` to `new MemoryPlugin({ selectedTools: [...] })`.
## How memory reaches the prompt
Memory context does not arrive in the prompt because the agent calls a tool to fetch it. Instead, a `UserContextFetcher` runs **before** the main agent is compiled — eagerly loading all six context slots in a single pass, before any message is processed.
**What the fetcher loads:** six slots, in this order:
1. `identity` — who the user is (name, role, background)
2. `work` — ongoing projects and responsibilities
3. `goals` — stated objectives and priorities
4. `interests` — topics and domains the user cares about
5. `relationships` — people and organizations mentioned in past conversations
6. `recent` — notable things from the last few sessions
**When it runs:** at agent-compile time, before turn 1. The fetcher runs once per session and caches the result for **5 minutes** (keyed by `sessionId`). Subsequent turns within the same session reuse the cached context — the Memory Engine is not called again unless the cache expires.
**What appears in the prompt:** if at least one slot is non-empty, the runtime inserts a `## What you know about the user` block into the system prompt containing all populated slots. If every slot is empty (no prior memory for this user), the block is omitted entirely.
**Implication for oracle authors:** you do not need to instruct the agent to "look up the user's context" or "recall memory before responding" in `config.prompt.opening` or anywhere else. The context is already in the system prompt when the agent sees the user's first message. Adding such instructions is redundant and wastes tokens.
The 5-minute session cache means very-recent memory writes (e.g. the agent just called `memory-engine__add_memory` in the same session) may not appear in the fetched context until the next session or cache expiry. This is intentional — the fetcher is optimised for read latency, not write-through consistency.
## Adding global oracle knowledge
Global knowledge is content the oracle should know on every turn, for every user — product docs, brand voice, FAQs, reference material. It's stored on the Memory Engine and indexed under the **oracle's entity DID**, not the user's DID.
Only an account that is `owner` or `controller` on the oracle's IXO entity (the entity created via `qiforge-cli create-entity`) can write global knowledge. The Memory Engine rejects writes from any other DID.
Check with `qiforge-cli update-entity` or by inspecting the entity on chain. If you are not a controller, ask the entity owner to add your DID first.
The bundled `memory` plugin's default tool selection does **not** include `memory-engine__add_oracle_knowledge`. Instantiate `MemoryPlugin` explicitly and extend the default selection:
```ts
// src/main.ts
import {
createOracleApp,
MemoryPlugin,
DEFAULT_MEMORY_TOOLS,
MEMORY_ADD_ORACLE_KNOWLEDGE_MCP_NAME,
} from '@ixo/oracle-runtime';
const app = await createOracleApp({
config,
plugins: [
new MemoryPlugin({
selectedTools: [
...DEFAULT_MEMORY_TOOLS,
MEMORY_ADD_ORACLE_KNOWLEDGE_MCP_NAME,
],
}),
],
});
```
The loader dedupes by name, so this explicit instance overrides the bundled default with the same name.
```sh
pnpm dev
```
The boot log should list `memory` as loaded. The agent now has `memory-engine__add_oracle_knowledge` available.
Pick the environment matching the network your oracle is registered on:
| Network | Portal URL |
| --- | --- |
| devnet | `https://dev.portal.qi.space` |
| testnet | `https://test.portal.qi.space` |
| mainnet | `https://portal.qi.space` |
Navigate directly to your oracle's connect page (replace `` with the value of `ORACLE_ENTITY_DID` from your `.env`):
```text
https://dev.portal.qi.space/domain//connect
```
Sign in as the entity owner/controller. On the connect page, click the highlighted "Connect" action — the Portal then opens a chat session bound to the oracle.
In the chat, drag and drop files (PDFs, markdown, text), paste links, or paste raw text — anything you want the oracle to know going forward. Then tell the oracle in plain language:
> Save this into the global oracle knowledge.
The agent calls `memory-engine__add_oracle_knowledge` with the dropped content. The Memory Engine accepts the write because your delegation chain proves you are owner/controller of the oracle entity.
Newly written knowledge takes about five minutes to index before it appears in `memory-engine__search_memory_engine` results. After indexing, every user's session will be able to recall it through the normal memory search path.
`memory-engine__add_oracle_knowledge` writes are scoped to the oracle entity, not to the calling user. Anything you add is visible to **every** user who talks to this oracle. Treat it like a public knowledge base.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { memory: false }, // never load
// features: { memory: true }, // force load (will fail env validation if vars missing)
// features: { memory: 'auto' }, // run autoDetect (default)
});
```
## When to use it
- First contact (no prior context loaded): greet, ask the user's name and what they need help with, save the answer.
- You learn something durable about the user — name, role, ongoing project, a constraint, a relationship.
- You produce an artifact (file, document, edit, generated content) — record what it is, what it is for, the structural choices.
- The user expresses satisfaction or dissatisfaction with something you produced — capture what worked or did not.
- The user references something they told you before, or something you made before.
## When NOT to use it
- Ephemeral conversation-only state — use the current message thread.
- Behavioural preferences about how to respond — use [`user-preferences`](/build-an-oracle/reference/bundled-plugins/user-preferences).
- Public web facts not specific to this user — use [`firecrawl`](/build-an-oracle/reference/bundled-plugins/firecrawl).
- Anything the user asked you to forget or framed as temporary.
## Where to read next
How `userProfile` flows to other plugins.
Pattern for exposing upstream MCP tools.
---
# portal
> Browser-side actions on the user's Portal UI — open URLs, manipulate the DOM, run FE-declared browser tools.
**Source:** [`packages/oracle-runtime/src/plugins/portal/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/portal/)
| Attribute | Value |
| --- | --- |
| Visibility | `on-demand` |
| Stability | `stable` |
| Category | `ui` |
| Default state | On |
| Depends on | — |
## Summary
The Portal frontend declares its available browser tools on each request via `state.browserTools`. The plugin wraps each declared tool into a `PluginTool` and exposes a sub-agent (`call_portal_agent`) that the main agent can delegate to. If no browser tools are declared, the plugin contributes nothing.
## Environment variables
This plugin has no required env vars.
## What it contributes
- **Tools:** none directly — tools are owned by the per-request sub-agent.
- **Sub-agents:** `call_portal_agent` — built only when `state.browserTools` is non-empty.
- **Middleware:** none.
- **HTTP routes:** none.
- **Shared state:** none.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { portal: false }, // never load
// features: { portal: true }, // force load (default)
});
```
## When to use it
- The user asks for an action the Portal frontend exposes as a browser tool (declared in `state.browserTools`).
- A task needs a browser-side capability the server cannot do alone — open a URL in the user's tab, click a Portal button, fill a form.
## When NOT to use it
- No browser tools are declared on this request — the sub-agent is not built.
- The task can be completed purely server-side — use a server tool or a different sub-agent.
## Where to read next
Build a per-request sub-agent like Portal's.
Sibling FE-declared-tools plugin for AG-UI components.
---
# firecrawl
> Web search and scraping of human-readable pages via Firecrawl.
**Source:** [`packages/oracle-runtime/src/plugins/firecrawl/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/firecrawl/)
| Attribute | Value |
| --- | --- |
| Feature key | `firecrawl` |
| Visibility | `on-demand` |
| Stability | `stable` |
| Category | `data` |
| Default state | Auto-detect (env: `FIRECRAWL_MCP_URL`) |
| Depends on | — |
## Summary
Web search and scraping via Firecrawl. Exposes a sub-agent (`call_firecrawl_agent`) that wraps the upstream Firecrawl MCP server's `firecrawl_search` and `firecrawl_scrape` tools. The manifest specifically routes the main agent away from API endpoints (use [`sandbox`](/build-an-oracle/reference/bundled-plugins/sandbox)) and IXO entities (use [`domain-indexer`](/build-an-oracle/reference/bundled-plugins/domain-indexer)).
## Environment variables
| Var | Required | Description |
| --- | --- | --- |
| `FIRECRAWL_MCP_URL` | yes | Firecrawl MCP HTTP(S) URL. Also triggers auto-detect. |
## What it contributes
- **Tools (inside the sub-agent):** `firecrawl_search`, `firecrawl_scrape`.
- **Sub-agents:** `call_firecrawl_agent`.
- **Middleware:** none.
- **HTTP routes:** none.
- **Shared state:** none.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { firecrawl: false }, // never load
// features: { firecrawl: true }, // force load (will fail env validation if FIRECRAWL_MCP_URL missing)
// features: { firecrawl: 'auto' }, // run autoDetect (default)
});
```
## When to use it
- User asks the agent to search the web for current information.
- User wants the contents of a specific page summarised or extracted.
- A question can only be answered by recent public web content.
## When NOT to use it
- Fetching from an API endpoint (URLs containing `/api/`, `/v1/`, `/v2/`, `/v3/`, or that return JSON/XML) — use [`sandbox`](/build-an-oracle/reference/bundled-plugins/sandbox).
- IXO entity lookups — use [`domain-indexer`](/build-an-oracle/reference/bundled-plugins/domain-indexer).
- Personal memory or past-conversation recall — use [`memory`](/build-an-oracle/reference/bundled-plugins/memory).
## Where to read next
How sub-agents like `call_firecrawl_agent` are built.
When to wrap an MCP server as a plugin vs ship it as a skill.
---
# domain-indexer
> Domain analysis and entity lookup across the IXO ecosystem — organisations, projects, DAOs, DIDs.
**Source:** [`packages/oracle-runtime/src/plugins/domain-indexer/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/domain-indexer/)
| Attribute | Value |
| --- | --- |
| Feature key | `domain-indexer` |
| Visibility | `always` |
| Stability | `stable` |
| Category | `data` |
| Default state | On |
| Depends on | — |
## Summary
Domain analysis and entity lookup across the IXO ecosystem — organisations, projects, DAOs, DIDs. Exposes a sub-agent (`call_domain_indexer_agent`) that searches the IXO Domain Indexer and resolves entity domain cards by DID. Base URL defaults to per-network endpoints (`https://domain-indexer{.testnet|.devnet}.ixo.earth`) resolved from `NETWORK`; override with `DOMAIN_INDEXER_URL`.
## Environment variables
| Var | Required | Description |
| --- | --- | --- |
| `DOMAIN_INDEXER_URL` | no | Optional URL override. Without it the plugin resolves per-network from `NETWORK` (`mainnet | testnet | devnet`). |
| `NETWORK` | no | Read but not owned (declared by the core base env schema). |
## What it contributes
- **Tools (inside the sub-agent):** `domain_indexer_search`, `get_domain_card`.
- **Sub-agents:** `call_domain_indexer_agent`.
- **Middleware:** none.
- **HTTP routes:** none.
- **Shared state:** none.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { 'domain-indexer': false }, // never load
// features: { 'domain-indexer': true }, // force load (default)
});
```
## When to use it
- User asks "what is X?" or "tell me about X" for an organisation, project, DAO, or DID.
- User needs the summary, overview, or FAQ of an IXO entity.
- Looking up a domain card by its DID.
- Discovering entities by topic, category, or keyword.
## When NOT to use it
- General web search unrelated to IXO entities — use [`firecrawl`](/build-an-oracle/reference/bundled-plugins/firecrawl).
- Personal memory or past-conversation recall — use [`memory`](/build-an-oracle/reference/bundled-plugins/memory).
- Page editing or workspace pages — use [`editor`](/build-an-oracle/reference/bundled-plugins/editor). Pages are not entities.
## Where to read next
Sub-agent contribution pattern.
All env vars including `NETWORK`.
---
# composio
> External SaaS tools (Gmail, GitHub, Linear, Slack, Notion, Jira, …) invoked on behalf of the user.
**Source:** [`packages/oracle-runtime/src/plugins/composio/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/composio/)
| Attribute | Value |
| --- | --- |
| Feature key | `composio` |
| Visibility | `on-demand` |
| Stability | `stable` |
| Category | `integration` |
| Default state | Auto-detect (env: `COMPOSIO_API_KEY`) |
| Depends on | — |
## Summary
Hundreds of SaaS tools (Gmail, GitHub, Linear, Slack, Google Calendar, Notion, Jira, HubSpot, …) invoked on behalf of the user through Composio. Tools are discovered dynamically per request: the plugin mints a UCAN invocation addressed to the composio-worker, opens a session for the current user, and exposes each returned tool to the agent. Auth is UCAN-only — if minting fails the plugin contributes zero tools that turn.
## Environment variables
| Var | Required | Description |
| --- | --- | --- |
| `COMPOSIO_API_KEY` | yes | Composio API key. Triggers auto-detect. |
| `COMPOSIO_BASE_URL` | no | Defaults to `https://composio.ixo.earth`. |
| `NETWORK` | no | Read but not owned (declared by the core base env schema). Forwarded as the `x-ixo-network` header so the composio-worker routes to the right IXO environment. |
## What it contributes
Composio is a **tool router**, not a 1:1 catalog. Per request the session returns a small fixed set of directly-callable meta-tools; the thousands of app-specific tools (e.g. `GMAIL_SEND_EMAIL`, `COMPOSIO_SEARCH_FINANCE`) are **not** bound to the agent — they are discovered and then executed through the router.
- **Tools (the four router meta-tools):**
- `COMPOSIO_MANAGE_CONNECTIONS` — check / start a toolkit's auth connection (returns a `redirect_url` when the user must connect).
- `COMPOSIO_SEARCH_TOOLS` — describe an action in natural language to find the exact tool slug(s).
- `COMPOSIO_GET_TOOL_SCHEMAS` — fetch the exact input schema for one or more discovered slugs.
- `COMPOSIO_MULTI_EXECUTE_TOOL` — run one or more discovered tools by slug. App-specific tools are executed here, never bound directly.
- **Sub-agents:** none.
- **Middleware:** none.
- **HTTP routes:** none.
- **Shared state:** none.
The exact tool set is returned dynamically by the composio-worker session, so it can vary by user/connection. The plugin pins only the `COMPOSIO_MULTI_EXECUTE_TOOL` argument envelope (see below) — every slug lives in Composio's registry, not in the runtime.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { composio: false }, // never load
// features: { composio: true }, // force load (will fail env validation if COMPOSIO_API_KEY missing)
// features: { composio: 'auto' }, // run autoDetect (default)
});
```
## The discover → execute flow
App-specific tools are never callable directly. The agent follows a fixed four-step flow (this is the `whenToUse` guidance that ships in the manifest and reaches the model):
1. **Connect** — for any connected-app action, call `COMPOSIO_MANAGE_CONNECTIONS` with the toolkit FIRST. If it returns a `redirect_url`, surface it as a clickable markdown link and stop. (Pure search tools — the `COMPOSIO_SEARCH_*` family — need no connection.)
2. **Discover** — call `COMPOSIO_SEARCH_TOOLS`, describing the action in natural language, to find the exact tool slug(s).
3. **Inspect (if unsure)** — call `COMPOSIO_GET_TOOL_SCHEMAS` with those slugs to fetch their exact input schema.
4. **Execute** — call `COMPOSIO_MULTI_EXECUTE_TOOL` with the discovered slug(s).
The execute envelope is strict — exactly `tools` + `sync_response_to_workbench`, with each call wrapped as `tool_slug` + `arguments`:
```json
{
"tools": [
{ "tool_slug": "COMPOSIO_SEARCH_FINANCE", "arguments": { "query": "Bitcoin price USD today" } }
],
"sync_response_to_workbench": false
}
```
Never pass a tool name as a top-level key (e.g. `{ "COMPOSIO_SEARCH_FINANCE": {...} }`) — always wrap it inside the `tools` array.
## When to use it
- User asks to send, read, or search emails (Gmail, Outlook).
- User asks to create or modify issues, pull requests, or stars (GitHub, Linear, Jira).
- User asks to manage calendar events, files, or documents in a SaaS app.
- Web, news, finance, academic, or trend searches — the `COMPOSIO_SEARCH_*` family covers these and needs no connection.
- No native skill covers the requested action — discover what Composio offers with `COMPOSIO_SEARCH_TOOLS` before giving up.
## When NOT to use it
- A native skill or sub-agent already covers the action — prefer the skill.
- Normal conversation or general question with no external SaaS interaction.
- NEVER fabricate or guess any URL yourself — the only valid auth link is the `redirect_url` returned by `COMPOSIO_MANAGE_CONNECTIONS`.
## Where to read next
How UCAN invocations are minted per request.
Why composio is `on-demand` instead of `always`.
---
# sandbox
> Per-user Linux box for code execution. Runs shell/python; writes raw bytes under /workspace/data/.
**Source:** [`packages/oracle-runtime/src/plugins/sandbox/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/sandbox/)
| Attribute | Value |
| --- | --- |
| Visibility | `always` |
| Stability | `stable` |
| Category | `core` |
| Default state | Auto-detect (env: `SANDBOX_MCP_URL`) |
| Depends on | — |
## Summary
Per-user Linux sandbox. `sandbox_run` runs shell/python (writes anywhere via shell, including `/tmp` for scratch). `sandbox_write_file` writes raw bytes BUT only under `/workspace/data/` — other paths are rejected; use `sandbox_run` with a here-doc for `/tmp`. The plugin surfaces every upstream MCP tool verbatim and authenticates the connection with a UCAN invocation plus operator and per-user secrets as request headers. Used internally by [`skills`](/build-an-oracle/reference/bundled-plugins/skills) for skill execution.
## Environment variables
| Var | Required | Description |
| --- | --- | --- |
| `SANDBOX_MCP_URL` | yes | Sandbox MCP URL. Triggers auto-detect. |
| `ORACLE_SECRETS` | no | Read but not owned (declared by the core base env schema). Each entry is forwarded as an `x-os-` header. |
| `SKILLS_CAPSULES_BASE_URL` | no | Read but not owned (declared by [`skills`](/build-an-oracle/reference/bundled-plugins/skills)). When set, the plugin mints a parallel `ixo:skills` UCAN invocation and forwards it as `X-Skills-Invocation`. |
## What it contributes
- **Tools:** every upstream MCP tool — `sandbox_run`, `sandbox_write_file`, the `artifact_*` family, `load_skill` — passed through verbatim. By default the `oracle_*` management tools (`oracle_list`, `oracle_get`, `oracle_health`, `oracle_stop`, `oracle_restart`, `oracle_get_logs`) are filtered out; opt in with `new SandboxPlugin({ includeOracleManagementTools: true })`.
- Plus one synthetic, non-upstream tool: `sandbox_write_blob` — takes a server-stored `blobId` + a sandbox path, looks the value up server-side, and forwards it to `sandbox_write_file` (the companion to blob storage). It is added per request only when `sandbox_write_file` is present upstream **and** the request has a known user DID (the blob store is namespaced by user DID).
- **Sub-agents:** none.
- **Middleware:** none.
- **HTTP routes:** none.
- **Shared state:** none.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { sandbox: false }, // never load
// features: { sandbox: true }, // force load (will fail env validation if SANDBOX_MCP_URL missing)
// features: { sandbox: 'auto' }, // run autoDetect (default)
});
```
## When to use it
- Execute a skill — call `sandbox_run` with `cid` so user + oracle secrets are injected; the skill folder mounts read-only at `/workspace/skills//`.
- Read a skill file (`SKILL.md`, scripts, configs) — `sandbox_run` with a `cat`/`ls`/`grep`/`sed -n` command and the skill's `cid`.
- Hit a JSON/REST API — write curl or python in `sandbox_run`. Never use a web scraper for `/api/`, `/v1/`, `/v2/`, `/v3/` endpoints.
- Generate or transform a file the user (or a later turn) will re-read — write to `/workspace/data/output/`.
- Re-read an attachment the user sent earlier — auto-archived to `/workspace/output/`.
- Save a large or escape-sensitive blob byte-perfect to `/workspace/data/...` — use `sandbox_write_file`.
- Write a scratch / throwaway file — use `sandbox_run` with a here-doc into `/tmp`.
## When NOT to use it
- The value is already inline in chat — just use it.
- Fetching a URL the user just mentioned — prefer `process_file` so it auto-archives.
- A long human-readable page — use [`firecrawl`](/build-an-oracle/reference/bundled-plugins/firecrawl).
- Installing native deps in cwd (`pip install -e .`, `bun install`) — install under `/tmp` instead.
- `sandbox_write_file` with a path outside `/workspace/data/` — the validator hard-rejects this.
## Where to read next
How `sandbox` + `skills` work together.
UCAN invocations and per-user secret forwarding.
---
# skills
> Discover IXO skill capsules — the caller's published private skills first, then the public registry.
**Source:** [`packages/oracle-runtime/src/plugins/skills/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/skills/)
| Attribute | Value |
| --- | --- |
| Visibility | `always` |
| Stability | `stable` |
| Category | `data` |
| Default state | On |
| Depends on | `sandbox` (hard dep — see below) |
## Summary
Discover IXO skill capsules. Both `list_skills` and `search_skills` mint an `ixo:skills` UCAN invocation per call so the registry surfaces the caller's own published private skills alongside public ones. When minting fails the tools degrade to public-only — they never throw on auth issues. Pairs with [`sandbox`](/build-an-oracle/reference/bundled-plugins/sandbox) for execution (see [Plugin vs Skill](/build-an-oracle/understand/plugins-vs-skills)).
## Environment variables
| Var | Required | Description |
| --- | --- | --- |
| `SKILLS_CAPSULES_BASE_URL` | no | Defaults to `https://capsules.skills.ixo.earth`. |
| `NETWORK` | no | Read but not owned (declared by the core base env schema). Forwarded as `X-IXO-Network`. |
## Depends on
Hard-depends on [`sandbox`](/build-an-oracle/reference/bundled-plugins/sandbox) — skill *execution* runs through `sandbox_run`, which `sandbox` owns (listing/search is HTTP-only). But `sandbox` is auto-detect (`SANDBOX_MCP_URL`) and off by default, while `skills` is on by default. If `sandbox` is not loaded — e.g. `SANDBOX_MCP_URL` is unset — the loader **cascades `skills` off silently** (a `boot.plugin.cascaded_off` warning), it does **not** fail boot. A true boot failure only occurs if `sandbox` is removed from the plugin set entirely while `skills` still requires it.
## What it contributes
- **Tools:** `list_skills`, `search_skills`.
- **Sub-agents:** none.
- **Middleware:** none.
- **HTTP routes:** none.
- **Shared state:** none.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { skills: false }, // never load
// features: { skills: true }, // force load (default)
});
```
## When to use it
- User asks "what skills are available?" or "what can you do?".
- User asks the agent to find a skill for a specific task ("a skill for invoices", "is there a skill for KYC?").
- Before running a skill via the sandbox, list or search to obtain its `cid` and path.
## When NOT to use it
- Executing a skill — that goes through [`sandbox`](/build-an-oracle/reference/bundled-plugins/sandbox) (`sandbox_run`), not the skills tools.
- General web search — use [`firecrawl`](/build-an-oracle/reference/bundled-plugins/firecrawl).
## Where to read next
Why `skills` discovers and `sandbox` executes.
Pattern used here to require `sandbox`.
---
# editor
> Reads and edits BlockNote pages — collaborative documents in the user's workspace.
**Source:** [`packages/oracle-runtime/src/plugins/editor/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/editor/)
| Attribute | Value |
| --- | --- |
| Feature key | `editor` |
| Visibility | `always` |
| Stability | `stable` |
| Category | `data` |
| Default state | On |
| Depends on | — |
## Summary
Reads and edits BlockNote pages — collaborative documents in the user's workspace via Matrix CRDT. Behaviour depends on per-request state:
- `state.editorRoomId` set → editor sub-agent (`call_editor_agent`) bound to that room; plus `apply_sandbox_output_to_block` when [`sandbox`](/build-an-oracle/reference/bundled-plugins/sandbox) is also loaded.
- `state.spaceId` set without `editorRoomId` → standalone `call_editor_agent` tool that accepts a `room_id` argument per call.
- Neither set → no contributions.
## Environment variables
The plugin owns no env vars. It reads the following from the core base env schema:
| Var | Required | Description |
| --- | --- | --- |
| `MATRIX_BASE_URL` | yes (base schema) | Matrix homeserver base URL. |
| `MATRIX_ORACLE_ADMIN_USER_ID` | yes (base schema) | Admin Matrix user ID. |
| `MATRIX_ORACLE_ADMIN_ACCESS_TOKEN` | yes (base schema) | Admin Matrix access token. |
| `SANDBOX_MCP_URL` | no | Read when `apply_sandbox_output_to_block` is constructed. |
| `SKILLS_CAPSULES_BASE_URL` | no | Read when `apply_sandbox_output_to_block` is constructed. |
| `ORACLE_SECRETS` | no | Read when `apply_sandbox_output_to_block` is constructed. |
## What it contributes
- **Tools:** `apply_sandbox_output_to_block` (when an editor room + sandbox plugin are both available); standalone `call_editor_agent` (when only a `spaceId` is in scope).
- **Sub-agents:** `call_editor_agent` — built only when `state.editorRoomId` is set.
- **Middleware:** none.
- **HTTP routes:** none.
- **Shared state:** none.
## Matrix client (optional)
The bundled `editorPlugin` is fully functional from env. When no `matrixClient` is passed, the plugin lazily builds an internal Matrix client from the `MATRIX_*` admin env vars (`MATRIX_BASE_URL`, `MATRIX_ORACLE_ADMIN_USER_ID`, `MATRIX_ORACLE_ADMIN_ACCESS_TOKEN`). So you do **not** need to construct it explicitly for production.
Pass a `matrixClient` only as a reuse optimization — when your app already keeps a long-lived `matrix-js-sdk` client synced and you want the editor tools to share it:
```ts
import { createOracleApp, EditorPlugin } from '@ixo/oracle-runtime';
import * as sdk from 'matrix-js-sdk';
const matrixClient = sdk.createClient({ /* … */ });
const app = await createOracleApp({
config,
plugins: [new EditorPlugin({ matrixClient })], // optional — env fallback otherwise
});
```
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { editor: false }, // never load
// features: { editor: true }, // force load (default)
});
```
## When to use it
- User asks to read, summarise, or edit a page in their workspace.
- User wants to update specific blocks (status, properties, content) on a page.
- User wants to create a new page or update an existing one.
- A skill produced output (URLs, credentials, status values) that should land on specific blocks.
## When NOT to use it
- IXO entity lookups — use [`domain-indexer`](/build-an-oracle/reference/bundled-plugins/domain-indexer). Pages are documents, not entities.
- Web search or scraping — use [`firecrawl`](/build-an-oracle/reference/bundled-plugins/firecrawl).
- Long-term user memory — use [`memory`](/build-an-oracle/reference/bundled-plugins/memory).
## Where to read next
How `call_editor_agent` is built per-request.
`editorRoomId` and `spaceId` are state fields the client sets.
---
# agui
> Renders interactive UI components (tables, charts, forms) in the user's browser via AG-UI actions.
**Source:** [`packages/oracle-runtime/src/plugins/agui/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/agui/)
| Attribute | Value |
| --- | --- |
| Feature key | `agui` |
| Visibility | `on-demand` |
| Stability | `stable` |
| Category | `ui` |
| Default state | On |
| Depends on | — |
## Summary
Renders interactive UI components (tables, charts, forms) in the user's browser. The client declares its renderable actions on each `sendMessage` via `state.agActions`; the runtime wraps each into a `PluginTool` and exposes them through the `call_ag-ui_agent` sub-agent. When no actions are declared the plugin contributes nothing.
## Environment variables
This plugin has no required env vars.
## What it contributes
- **Tools:** none directly — tools are owned by the per-request sub-agent.
- **Sub-agents:** `call_ag-ui_agent` — built only when `state.agActions` is non-empty.
- **Middleware:** none.
- **HTTP routes:** none.
- **Shared state:** none.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { agui: false }, // never load
// features: { agui: true }, // force load (default)
});
```
## When to use it
- User asks for an interactive table, chart, or form to be rendered.
- A response is best shown as a structured UI component rather than plain text.
## When NOT to use it
- No AG-UI actions are declared on this request — the sub-agent is not built.
- A plain text answer is sufficient — do not render UI just because you can.
## Where to read next
The pattern AG-UI uses to wrap FE-declared actions.
Sibling plugin for FE-declared browser tools.
---
# slack
> Slack bot transport — runs as a NestJS module with socket-mode lifecycle. No agent-visible tools.
**Source:** [`packages/oracle-runtime/src/plugins/slack/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/slack/)
| Attribute | Value |
| --- | --- |
| Visibility | `silent` |
| Stability | `stable` |
| Category | `core` |
| Default state | Auto-detect (env: `SLACK_BOT_OAUTH_TOKEN`) |
| Depends on | — |
## Summary
Connects a Slack bot to the oracle. The plugin contributes **no agent-visible tools** — it is purely a transport. The agent does not know Slack exists; it just sees inbound messages on the Slack client. Ships its own NestJS module so the bot client can use `OnModuleInit` / `OnModuleDestroy` for socket-mode lifecycle and inject the Tier-0 services it needs (messages, sessions, cache).
## Environment variables
| Var | Required | Description |
| --- | --- | --- |
| `SLACK_BOT_OAUTH_TOKEN` | yes | Slack bot OAuth token. Triggers auto-detect. |
| `SLACK_APP_TOKEN` | no | App-level token (required for socket mode in production). |
| `SLACK_USE_SOCKET_MODE` | no | Defaults to `'true'`. |
| `SLACK_MAX_RECONNECT_ATTEMPTS` | no | Coerced number; defaults to `10`. |
| `SLACK_RECONNECT_DELAY_MS` | no | Coerced number; defaults to `1000`. |
## What it contributes
- **Tools:** none.
- **Sub-agents:** none.
- **Middleware:** none.
- **HTTP routes:** none directly.
- **Nest module:** `SlackModule` (registered via `getNestModules`) — owns the Slack socket-mode client lifecycle.
- **Shared state:** none.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { slack: false }, // never load
// features: { slack: true }, // force load (will fail env validation if SLACK_BOT_OAUTH_TOKEN missing)
// features: { slack: 'auto' }, // run autoDetect (default)
});
```
## When to use it
- You want the same oracle reachable from Slack as well as the web client.
## Where to read next
Nest-module contribution pattern.
What `silent` visibility means (here, the plugin has no tools at all).
---
# tasks
> Schedule the main agent to run on time-based triggers and deliver results to the user's chat — in the background, not inline.
**Source:** [`packages/oracle-runtime/src/plugins/tasks/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/tasks/)
| Attribute | Value |
| --- | --- |
| Feature key | `tasks` |
| Visibility | `on-demand` |
| Stability | `beta` |
| Category | `automation` |
| Default state | Auto-detect (env: `REDIS_URL`) |
| Depends on | — |
| Soft-depends on | `memory` |
## Summary
Lets the agent schedule itself to run later. A task is a saved spec (title + intent) plus a trigger (`time.once` or `time.cron`). When the trigger fires, the runtime starts a **fresh background session** — no memory of the conversation that created it — runs one agent turn, and delivers the result to the user's main oracle room or a dedicated `[Task]` room. The agent owns the whole lifecycle through 10 tools, a mandatory **preview → create** approval flow, and an optional per-run **before-action** approval gate.
Because each run is a fresh session, every ID, URL, name, or reference the run needs must be written into `intent.context` when the task is created. The run cannot "remember" anything discussed in the chat that scheduled it.
## Environment variables
| Var | Required | Default | Description |
| --- | --- | --- | --- |
| `REDIS_URL` | yes | — | Redis connection URL. Backs the BullMQ `task_run` queue + worker and the task state store. Triggers auto-detect. |
| `TASKS_MAX_PER_USER` | no | `50` | Max live tasks per user (not counting `cancelled` / `completed`). `create_task` rejects new tasks past this. |
| `TASKS_RUN_LOCK_TTL_SEC` | no | `600` | TTL (seconds) on a run's execution lock — guards against a double-fire running the same task twice. |
| `TASKS_MIN_CRON_INTERVAL_SEC` | no | `300` | Floor (seconds) on how often a `time.cron` trigger may fire. Faster schedules are rejected at create/update time. |
## Depends on
Soft-depends on [`memory`](/build-an-oracle/reference/bundled-plugins/memory): tasks loads and runs fine if `memory` is absent, but background runs lose durable per-user context. A `boot.plugin.soft_dep_missing` log line is emitted when `memory` is not loaded — it is a warning, never a boot failure.
## What it contributes
- **Tools (10, `on-demand`):** the agent loads them via the capability gate before use.
- `preview_task` — run a candidate spec **once, for real**, and return the output + a `previewToken`. Always required before scheduling; the agent must show the user this output and stop.
- `create_task` — schedule a previewed task. Requires a matching `previewToken` minted in an **earlier** turn (re-preview if anything changed). Picks `time.once` vs `time.cron` and the delivery room.
- `list_my_tasks` — list the user's tasks (id, title, status, trigger, next run); optional status filter.
- `get_task` — full spec body, status, trigger, delivery room, and the last error if it has been failing.
- `update_task` — patch title, trigger, approval mode, or intent. Changing the intent needs a fresh `previewToken`; changing the trigger reschedules automatically.
- `pause_task` — pause a task; pending runs are cancelled until resumed.
- `resume_task` — resume a paused, `pending-approval`, or `failed-pending-review` task; recomputes the next run.
- `cancel_task` — cancel permanently (spec kept for the audit trail).
- `resolve_task_approval` — record the user's decision (`approved` / `declined`) after a **nuanced** reply in a `[Task]` room. Plain yes/no replies are recorded automatically — don't call this for those.
- `suggest_spec_fix` — for a failing task, return the current body + last error so the agent can propose a revision (applied via `update_task` only after the user agrees).
- **Sub-agents:** none.
- **Middleware:** one — `TaskRoomApprovalGate`. Wraps every model call but only acts inside a dedicated task room whose task is `pending-approval`. A plain yes/no reply is classified and resolved deterministically **before** the model runs; nuanced replies get a system-prompt hint pointing the model at `resolve_task_approval`. It never short-circuits the turn and never posts to Matrix.
- **HTTP routes:** none.
- **Nest module:** `TasksModule` — owns the BullMQ `task_run` queue + `TaskRunWorker`, a Redis-backed state/task store, the scheduler, delivery service, agent invoker, and approval flow. On init it registers a room→session resolver on the Matrix bridge so a plainly-typed reply in a task room continues that run's own thread instead of starting a fresh one.
- **Shared state:** none.
## Triggers
| Type | Fields | Use for |
| --- | --- | --- |
| `time.once` | `runAtIso` (ISO datetime), `tz` | One-time requests — "in 10 minutes", "tomorrow at 5pm". Compute `runAtIso` from now. |
| `time.cron` | `pattern` (cron), `tz` | Genuinely recurring schedules — "every morning at 7". `*/10 * * * *` means every-10-minutes-forever, not once-in-10-minutes. |
## The preview → create flow
Scheduling is always two turns:
The agent runs the candidate spec once with live tools and returns the real output plus a `previewToken`. It then **stops** and shows the output to the user — it must not call `create_task` in the same turn.
The user replies to schedule it (and optionally asks for a dedicated room or per-run approval).
Called with the same title/intent and the `previewToken`. The runtime re-checks the token (owner + content hash + a different request id), enforces the per-user limit and the cron floor, saves the spec, and enqueues the first run.
## Before-action approval
Set `approval: 'before-action'` (with `intent.requiresApproval` naming the guarded action) when a run would send, post, publish, or create something on the user's behalf. Such tasks always get their own `[Task]` room. Each run does the work, **drafts** the action, and asks the user to approve by replying in that room:
- A plain "yes" / "no" (and close variants) is recorded deterministically by the `TaskRoomApprovalGate` middleware before the model runs.
- A nuanced reply ("fix the typo first, then send") is handled by the agent, which acts on it and then calls `resolve_task_approval`.
## Task statuses
`active` · `pending-approval` · `paused` · `failed-pending-review` · `completed` · `cancelled`.
## Opt out / Opt in
`tasks` auto-detects on `REDIS_URL` and is off when that env var is unset.
```ts
const app = await createOracleApp({
config,
features: { tasks: false }, // never load
// features: { tasks: true }, // force load (fails env validation if REDIS_URL missing)
// features: { tasks: 'auto' }, // run autoDetect (default — loads when REDIS_URL is set)
});
```
## When to use it
- The user wants a reminder or recurring report ("every morning at 7", "tomorrow at 5pm", "remind me to…").
- The user wants the agent to monitor or track something on a schedule.
- The user wants a piece of work run later, in the background, without sitting in the chat.
## When NOT to use it
- A one-shot action the user wants done right now — just do it inline.
- Real-time / streaming requirements — tasks run on a scheduled cadence, not on demand.
## Where to read next
Soft dependency — durable per-user context for background runs.
Why `on-demand` tools are loaded through the capability gate.
---
# credits
> Enforces per-user credit budgets and settles held credits to the chain on a cron.
**Source:** [`packages/oracle-runtime/src/plugins/credits/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/credits/)
| Attribute | Value |
| --- | --- |
| Feature key | `credits` |
| Visibility | `silent` |
| Stability | `stable` |
| Category | `core` |
| Default state | On unless `DISABLE_CREDITS=true` |
| Depends on | — |
## Summary
Owns the full credit lifecycle:
- **Enforcement** — per-request middleware that aborts model calls when the user is out of credits (`createCreditsMiddleware` + `TokenLimiter`).
- **Settlement** — background cron that converts held credits into on-chain claims, shipped via `ClaimProcessingModule`.
Silent (no agent-visible tools). When loaded, this plugin also activates the runtime's Tier-0 `SubscriptionMiddleware`, which gates the HTTP request before the graph even runs.
## Environment variables
| Var | Required | Description |
| --- | --- | --- |
| `SUBSCRIPTION_URL` | no | Subscription API URL. |
| `SUBSCRIPTION_ORACLE_MCP_URL` | no | Subscription Agentic Oracles MCP server URL. |
| `DISABLE_CREDITS` | no | Set to `'true'` to skip the plugin entirely. |
| `NETWORK` | no | Read but not owned (declared by the core base env schema). Required by the cron module. |
## What it contributes
- **Tools:** none.
- **Sub-agents:** none.
- **Middleware:** `createCreditsMiddleware` (aborts the agent run when the user is out of credits).
- **Nest modules** (when Redis is configured at construct time):
- `ClaimProcessingModule` — cron that settles held credits on chain.
- `FileProcessingSinkModule` — `FILE_PROCESSING_CREDIT_SINK` so pre-flight file-processing LLM usage bills the per-user budget.
- `SubscriptionSinkModule` — `SUBSCRIPTION_CREDIT_SINK` so the subscription middleware mirrors per-DID subscription payload + balance into Redis on every authenticated request.
- **HTTP routes:** none directly.
- **Shared state:** none.
## Production constructor
The bundled singleton is for inspect / test only. **Production needs a Redis client and the network:**
```ts
import { createOracleApp, CreditsPlugin } from '@ixo/oracle-runtime';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
const app = await createOracleApp({
config,
plugins: [new CreditsPlugin({ redis, network: 'devnet' })],
});
```
Without a Redis client the middleware loads in pass-through mode and the cron modules are skipped.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { credits: false }, // never load
});
// Or via env: DISABLE_CREDITS=true
```
## Where to read next
The pattern `createCreditsMiddleware` uses.
How subscription payloads reach the request.
---
# calls
> LiveKit call integration — stub. Full implementation deferred.
**Source:** [`packages/oracle-runtime/src/plugins/index.ts`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/index.ts) (stub manifest)
| Attribute | Value |
| --- | --- |
| Feature key | `calls` |
| Version | `0.0.0` |
| Visibility | `silent` |
| Stability | `experimental` |
| Default state | Loaded by default (stub — contributes nothing) |
`calls` is a **placeholder stub**, not a shipped feature. It is the only bundled entry created via `stub('calls', 'Calls')` in `plugins/index.ts` — version `0.0.0`, no tools, no sub-agents, no middleware, no Nest modules. Don't rely on it.
## Status: deferred
`calls` exists in `BUNDLED_PLUGINS` only so the `features` toggle key resolves. Because it declares no `autoDetect`, the loader loads it by default — but it contributes nothing, so loading it has no effect. The legacy `apps/app` codebase had a `@Controller('calls')` for LiveKit integration; the `getNestModules` API hook would technically unblock a real implementation. Deferred for now.
It contributes nothing, so there is nothing to enable. You can keep it from loading at all with:
```ts
const app = await createOracleApp({
config,
features: { calls: false }, // skip the stub entirely
});
```
See the framework's [follow-ups](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/docs/spec-and-roadmap/follow-ups.md) for the rebuild plan.
## Where to read next
The full bundled set.
The hook a real `calls` plugin would use.
---
# user-preferences
> Behavioural preferences — how the user wants you to respond (tone, language, formality, what to call you).
**Source:** [`packages/oracle-runtime/src/plugins/user-preferences/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/user-preferences/)
| Attribute | Value |
| --- | --- |
| Visibility | `always` |
| Stability | `stable` |
| Category | `core` |
| Default state | On |
| Depends on | — |
## Summary
Per-room user behavioural preferences (tone, length, format, language, what to call the agent). `state.userPreferences` is hydrated by the agent builder before the agent is compiled, so the system prompt sees the value on turn 1. The agent calls `set_user_preferences` when the user asks to change behaviour, and the change persists across sessions.
## Environment variables
This plugin has no required env vars.
## What it contributes
- **Tools:** `set_user_preferences`.
- **Sub-agents:** none.
- **Middleware:** none directly (preferences are hydrated by the runtime's agent builder, not a plugin middleware).
- **Nest modules:** `UserPreferencesHttpModule` — registers the `GET /user-preferences` controller for client-side reads.
- **HTTP routes:** `GET /user-preferences`.
- **Shared state:** none.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { 'user-preferences': false }, // never load
// features: { 'user-preferences': true }, // force load (default)
});
```
## When to use it
- User states how they want you to behave: "be more terse", "respond in Spanish", "call me Alex", "stop using emojis".
- User asks to change the voice, formality, or language of your replies — save it so it persists across sessions, not just this turn.
## When NOT to use it
- Facts about who the user is (name, role, project) — those go to [`memory`](/build-an-oracle/reference/bundled-plugins/memory), not preferences.
- Artifacts you have produced or how the user reacted to them — also [`memory`](/build-an-oracle/reference/bundled-plugins/memory), not preferences.
- One-turn formatting requests ("just for this answer, use bullets") — adapt locally without saving.
## Where to read next
Pattern this plugin uses for `GET /user-preferences`.
Where to put facts (vs preferences).
---
# matrix-group-chats
> Lets the oracle participate cleanly in Matrix group rooms — only replies when relevant, and keeps a searchable per-room memory.
**Source:** [`packages/oracle-runtime/src/plugins/matrix-group-chats/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/matrix-group-chats/)
| Attribute | Value |
| --- | --- |
| Visibility | `on-demand` (per-tool `always`) |
| Stability | `beta` |
| Category | `communication` |
| Default state | On (no autoDetect) |
| Depends on | — |
## Summary
Two responsibilities, one plugin:
1. **Per-turn gating middleware.** In Matrix group rooms (`!isDirect && memberCount > 2`), the oracle only replies when mentioned, replied to, or already in an active thread. Otherwise the turn short-circuits and nothing is posted. DMs and non-Matrix transports pass through untouched.
2. **Per-room compacted memory.** Every message in the room is captured into an FTS5-searchable SQLite store, compacted into ~200–400 token summary chunks, and synced back to the same Matrix room as encrypted media. The agent can recall, search, and pin durable facts via four tools.
## Activation
The plugin loads at boot by default. Opt out:
```ts
await createOracleApp({
config,
features: { 'matrix-group-chats': false },
});
```
At request time, the middleware and tools only act when **all** of:
- `rtCtx.session.client === 'matrix'`
- `rtCtx.session.roomId` is set
- The room is not a DM and has more than 2 members
DM-only oracles never see the four tools — `getRequestTools` returns `[]` outside group rooms.
## Environment variables
All optional; defaults are production-sane.
| Var | Default | Purpose |
| --- | --- | --- |
| `CHANNEL_MEMORY_SYNC_INTERVAL_MS` | `60000` | Debounce window before uploading a dirty room DB to Matrix. |
| `GROUP_CHAT_ACTIVE_THREAD_TTL_MS` | `1800000` (30 min) | How long a thread stays "active with the bot" after a reply. |
| `GROUP_CHAT_REQUIRE_POWER_LEVEL` | `0` | Extra minimum power level the bot needs before posting (0 = use room default). |
| `GROUP_CHAT_ROOM_INFO_TTL_MS` | `1800000` (30 min) | How long room info (membership, DM flag) stays cached. |
## What it contributes
- **Tools** (all per-tool `visibility: 'always'`, so they bypass the capability gate when returned for group rooms):
- `recall_channel_memory` — recent compacted summary chunks + pinned facts + member roster.
- `search_channel_memory` — FTS5 keyword search over compacted chunks.
- `pin_room_fact` — persist a durable fact to this room (decisions, deadlines, roles).
- `unpin_room_fact` — remove a pinned fact by id.
- **Sub-agents:** none.
- **Middleware:** one — the per-turn group-chat gate (see below).
- **HTTP routes:** none.
- **Nest modules:** `ChannelMemoryModule` (the per-room SQLite store + Matrix sync).
- **Shared state:** `channelMemory` — other plugins read the `ChannelMemoryService` singleton via `rtCtx.shared.channelMemory`.
## What the middleware does per turn
For Matrix group rooms only (passes through everywhere else):
1. Captures the latest `HumanMessage` into channel memory — even when the bot stays silent.
2. Runs `shouldAgentRespond`. Precedence: DM (auto-respond) → mention → reply-to-bot → active-thread cache → Matrix-history fallback → ignore.
3. If the bot shouldn't respond → short-circuits the turn (`jumpTo: 'end'`); the Matrix transport detects "no AI message produced" and posts nothing.
4. If it should → checks `m.room.power_levels`; short-circuits silently when the bot lacks permission to send `m.room.message`.
5. Refreshes the room's member roster (fire-and-forget) so the next turn has fresh names.
6. Runs just-in-time compaction with a 3-second cap so the agent gets up-to-date summary context.
## Speaker identity in group rooms
For every Matrix-originated turn, `MessagesService.assembleInput()` resolves the sender's display name via `MatrixManager.getCachedDisplayName(mxid, roomId)` (30-min cache, falls back to mxid local-part).
- **Group rooms (`memberCount > 2`)** — content is prefixed `[DisplayName]: ` so the agent reads who's speaking inline.
- **DMs** — same `additional_kwargs` metadata (`senderDid`, `senderMatrixUserId`, `senderDisplayName`, `threadId`, `eventId`, raw `m.mentions` / `m.relates_to`), no content prefix.
## When to use it
- A user asks what was said or decided earlier in this Matrix group room.
- A user asks who is in the room or what their role is.
- A durable fact should survive across threads (deadline, decision, project context) — pin it.
- You need to recall the gist of prior conversation before answering a multi-step group request.
## When NOT to use it
- Single-user DMs — the plugin only acts in rooms with more than 2 members.
- Long-term personal memory about a specific user — use [`memory`](/build-an-oracle/reference/bundled-plugins/memory).
- Verbatim text of a specific Matrix event — query Matrix history directly.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { 'matrix-group-chats': false }, // never load
// features: { 'matrix-group-chats': true }, // force load (default)
});
```
## Where to read next
Per-user durable memory — complementary to per-room channel memory.
How other plugins read `channelMemory` via `rtCtx.shared`.
---
# vfs
> The user's Virtual Filesystem — read, create, edit, search, organise, and share their real files, inside the folder they granted the oracle.
**Source:** [`packages/oracle-runtime/src/plugins/vfs/`](https://github.com/ixoworld/ixo-oracles-boilerplate/blob/main/packages/oracle-runtime/src/plugins/vfs/)
| Attribute | Value |
| --- | --- |
| Visibility | `always` |
| Stability | `stable` |
| Category | `data` |
| Default state | On (always-on; contributes tools only when the oracle has a UCAN signing key **and** the user has granted access) |
| Depends on | — (`sandbox` is an optional sibling — see [bridge tools](#sandbox-bridge-tools)) |
## Summary
The user's **Virtual Filesystem (VFS)** — their persistent, access-controlled home for real documents, notes, datasets, and artifacts, in folders, searchable and versioned. This is reusable context that lives across sessions — not sandbox scratch files, not chat attachments, not the web.
The oracle acts **as the user**, inside the exact folder the user delegated to it. The **user owns their filesystem and is the sole grantor**: only their key can sign the delegation — the oracle can never grant itself access. Every file the oracle reads or writes is confined to the granted subtree and audited as `invoker=oracle, actor=user`.
The filesystem is securely stored with managed encryption; authorized IXO services can read content to power search and previews, so it is **not** end-to-end encrypted — never describe it as zero-knowledge.
## How the oracle gets access
The user grants access from the IXO Portal — the oracle never self-grants:
In the Portal: **your domain → Library → Files → Access** (top-right).
In **Manage access**, paste the oracle's account DID (its `ORACLE_DID`) as the Recipient DID, choose the rights (Read / Write / Delete), set the folder scope and duration, and **Grant access**.
The grant is a UCAN delegation deposited in the UCAN Store Worker, addressed to the oracle. Per request the oracle pulls that delegation and mints a fresh, single-use VFS invocation — no extra wiring. Until a grant exists, the file tools return a short message telling the user how to grant access (including the oracle's DID).
See [Identity and auth](/build-an-oracle/develop/identity-and-auth) for the two-hop UCAN flow (oracle → UCAN Store → VFS).
## Environment variables
The worker URLs are **bundled per network** — there is nothing to configure. Everything else is optional tuning.
| Var | Required | Description |
| --- | --- | --- |
| `NETWORK` | yes | `mainnet` / `testnet` / `devnet`. Selects the bundled VFS + UCAN Store worker URLs (`https://[testnet.\|devnet.]vfs.ixo.earth`). Owned by the core base env schema. |
| `VFS_MAX_READ_LINES` | no | Max lines a single `vfs_read` window returns. Default `2000`. |
| `VFS_REQUEST_TIMEOUT_MS` | no | Per-request timeout to the VFS worker. Default `20000`. |
| `SANDBOX_MCP_URL` | no | Read but not owned (declared by [`sandbox`](/build-an-oracle/reference/bundled-plugins/sandbox)). When set, adds the two [sandbox bridge tools](#sandbox-bridge-tools). |
The oracle's UCAN signing key (loaded from its Matrix account room at boot) is required — without it the plugin contributes no tools.
## What it contributes
- **Tools:** ten file tools, each resolving a fresh single-use UCAN bearer per call:
| Tool | Ability | What it does |
| --- | --- | --- |
| `vfs_search` | `fs/read` | Find files by meaning (hybrid lexical + semantic); returns paths + cited line ranges. |
| `vfs_grep` | `fs/read` | Find files containing an exact term. |
| `vfs_glob` | `fs/list` | Match files by path pattern, e.g. `/notes/*.md`, `**/*.pdf`. |
| `vfs_list` | `fs/list` | List a folder's contents. |
| `vfs_read` | `fs/read` | Read a file. Text → windowed numbered lines (page with `offset`); images/PDFs → transcribed via the vision model. |
| `vfs_write` | `fs/write` | Create a file (or overwrite when the user asked to replace it). |
| `vfs_edit` | `fs/write` | Exact-string edit — change one occurrence (or `replaceAll`). |
| `vfs_move` | `fs/write` | Move or rename a file. |
| `vfs_delete` | `fs/delete` | Move a file to trash (recoverable). |
| `vfs_share` | `fs/write` | Publish a file/folder and return an anyone-with-the-link URL. |
- **Sub-agents:** none.
- **Middleware:** none.
- **HTTP routes:** none.
- **Shared state:** none.
### Sandbox bridge tools
When [`sandbox`](/build-an-oracle/reference/bundled-plugins/sandbox) is configured (`SANDBOX_MCP_URL` set), two extra tools move a file between the user's sandbox and their filesystem **server-side — the bytes never pass through the LLM**:
- `sandbox_to_vfs` — persist something the sandbox produced (a report, export, chart) into the user's files so it survives the session.
- `vfs_to_sandbox` — feed one of the user's files into the sandbox (under `/workspace/data/`) so `sandbox_run` code can process it.
## Opt out / Opt in
```ts
const app = await createOracleApp({
config,
features: { vfs: false }, // never load
// features: { vfs: true }, // force on (the default — always bundled)
});
```
## When to use it
- The user refers to a document, note, file, or folder they "saved", "uploaded", or "shared with you".
- You need to create, update, or organise a file that should persist across sessions.
- You need to find or quote something from the user's files ("what did my notes say about X").
- You can reuse a file the user already has instead of asking them to re-paste it.
- The user asks you to share a file or make it downloadable.
- Persist a sandbox artifact into the user's files (`sandbox_to_vfs`), or feed a file into the sandbox (`vfs_to_sandbox`).
## When NOT to use it
- General knowledge or web questions — the files are the user's private content, not a knowledge base of the world.
- Content the user pasted directly into chat — act on it inline; don't write it to a file unless asked.
- Temporary/scratch files from a compute run — those belong to the [`sandbox`](/build-an-oracle/reference/bundled-plugins/sandbox), not the user's filesystem.
- Data another plugin owns (flows, skills, memory) — use that plugin.
If a file action reports no access (or read-only), the oracle relays the grant steps above — with its agent DID — so the user can authorize it, then retries. It never claims a file is missing when the real issue is access.
## Where to read next
The two-hop UCAN flow that lets the oracle act as the user.
The compute box the VFS bridges to.
---
# flows
> Opt-in flow-builder plugin: author, inspect, wire, and form-fill multi-step Qi Flow action templates on top of the editor's Qi Flow engine.
| Attribute | Value |
| --- | --- |
| Feature key | `flows` |
| Visibility | `on-demand` |
| Stability | `beta` |
| Category | `automation` |
| Default state | Off — opt-in only |
| Depends on | [`editor`](/build-an-oracle/reference/bundled-plugins/editor) Qi Flow engine + Matrix CRDT (shared at runtime) |
The `flows` plugin is **not loaded by default** even though it ships in `@ixo/oracle-runtime`. Wire it in explicitly via the `plugins` array (see [Opt in](#opt-in)). The bundled set in `BUNDLED_PLUGINS` does not include it.
## Summary
`flows` is a flow **builder** capability. The agent designs reusable flow *templates* — steps (action blocks), the data wired between them, conditions, schedules, assignees, and forms — and reads the live state of running flows. The agent never executes, signs, mints, holds a key, or enters a PIN; **the user runs the flow in the portal**, where signing and any state transitions happen.
It coexists with the [`editor`](/build-an-oracle/reference/bundled-plugins/editor) plugin: flows are written as documents over the `@ixo/editor` Qi Flow engine, using oracle-runtime's native yjs reads/writes. The plugin does not require `editor` to be loaded — it contributes its own tool surface.
## When to use it
- User wants to build an automation/workflow from steps or action blocks.
- User wants to change a step's inputs, condition, trigger, schedule, or assignee.
- User wants to know what an action needs (its inputs/prerequisites) before adding it.
- User wants to fill in a form or survey attached to a flow step.
- User wants to inspect a flow run, find out why a step failed, and fix the template.
## When NOT to use it
- Editing prose, pages, or BlockNote documents — use [`editor`](/build-an-oracle/reference/bundled-plugins/editor).
- Actually executing, running, or signing a step — that happens in the portal, by the user. No transaction tooling lives here.
- IXO entity lookups — use [`domain-indexer`](/build-an-oracle/reference/bundled-plugins/domain-indexer).
## Environment variables
The plugin owns no env vars. It relies on the same admin Matrix credentials the `editor` plugin reads from the core base env schema:
| Var | Required | Description |
| --- | --- | --- |
| `MATRIX_BASE_URL` | yes (base schema) | Matrix homeserver base URL. |
| `MATRIX_ORACLE_ADMIN_USER_ID` | yes (base schema) | Admin Matrix user ID. |
| `MATRIX_ORACLE_ADMIN_ACCESS_TOKEN` | yes (base schema) | Admin Matrix access token. |
## What it contributes
- **Tools:** a flow-authoring surface grouped into five buckets — see below.
- **Sub-agents:** none.
- **Middleware:** none.
- **HTTP routes:** none.
- **Shared state:** reads `state.spaceId` / current flow ref from the per-request runtime context; writes flow documents through the editor Qi Flow engine.
### Tools
**Discovery** — learn what blocks exist and what they need before adding them.
- `list_actions` — list available action blocks (optionally filtered by tag).
- `describe_action` — return an action's inputs, outputs, and prerequisites.
- `list_referenceable_fields` — list fields the agent can wire from prior steps.
**Linkage** — typed wiring checks between steps.
- `check_link` — verify a single field-to-input wiring between two steps.
- `compatible_actions` — find actions whose inputs match a step's outputs.
- `requirements` — pure action lookup of required inputs.
**Inspect** — read live flow state to debug a run or review a template.
- `read_flow` — assemble the full flow document from its native sources.
- `get_step` — fetch a single step's configuration and current state.
- `flow_status` — high-level status of the flow and each step.
- `explain_step` — explain why a step is in its current state.
**Authoring** — build and edit the template.
- `validate_flow` — run static checks against the current flow.
- `create_flow` — start a new flow template from `get_flow_template`.
- `add_step`, `remove_step`, `reorder_step` — manipulate the step list.
- `update_flow_meta` — title, description, tags.
- `connect_steps` — wire an upstream output to a downstream input.
- `update_step` — per-block / delta edit that never disturbs sibling steps.
**Settings** — per-step mutators with narrow scope.
- `set_step_inputs` — set or replace a step's static inputs.
- `set_step_conditions` — set `props.conditions` directly in the frontend evaluator's operator vocabulary.
- `set_step_schedule` — set or clear a step's schedule.
- `set_step_assignment` — set or clear a step's assignee.
- `set_step_confirmation` — toggle the user-confirmation gate before a step runs.
- `set_step_trigger` — configure the trigger that starts the flow.
**Forms** — attach and fill structured forms on a step.
- `set_form_schema` — set or replace a step's form schema.
- `describe_form` — return a form's schema and current values.
- `fill_form` — fill or update form fields.
## How the agent should behave
Follow a tight loop: **discover → plan → confirm → build → hand off.** One discovery pass, one short plan, one user confirmation, then build with the authoring tools. The plugin injects an operating guide into the system prompt while it is loaded — see the `FLOWS_OPERATING_GUIDE` exported from the package.
Conditions are written **directly as `props.conditions`** in the frontend evaluator's operator vocabulary; the compiler's verbatim operators never evaluate at runtime.
## Opt in
`flows` is opt-in. Add the plugin instance explicitly — the bundled loader will not load it for you:
```ts
import { createOracleApp, FlowsPlugin } from '@ixo/oracle-runtime';
import * as sdk from 'matrix-js-sdk';
const matrixClient = sdk.createClient({ /* … */ });
const app = await createOracleApp({
config,
plugins: [new FlowsPlugin({ matrixClient })], // matrixClient is optional — env fallback otherwise
});
```
If your app already constructs the [`editor`](/build-an-oracle/reference/bundled-plugins/editor) plugin with a shared `matrixClient`, reuse it here so both plugins share the same long-lived sync.
## Examples
**User: "Build a flow that emails the applicant when their claim is approved."**
The agent calls `list_actions` (tag: `claims`) to discover blocks, picks a submit/approve/email chain, confirms the plan, then `create_flow` + `add_step` + `connect_steps` + `set_step_conditions` to wire it up. The user runs the flow from the portal.
**User: "What does the submit-claim step need before I can add it?"**
The agent calls `describe_action` with `action: 'qi/claim.submit'` and reports its required inputs and prerequisites.
**User: "Why did the second step of my flow fail?"**
The agent calls `flow_status` followed by `explain_step` on the failed step to surface the error, then proposes an `update_step` or `set_step_inputs` fix on the template.
## Where to read next
The Qi Flow engine the builder writes against.
`spaceId` and the per-request flow ref the tools key off.
---
# Troubleshooting
> Common QiForge boot errors, Matrix issues, plugin loading problems, and the fix for each.
## Boot errors
Every boot error names the offending plugin and a remediation hint. They print to stderr via the configured logger, prefixed with `[boot-error]`.
### Missing required env var
```text
[boot-error] Plugin 'memory' env validation failed for 'MEMORY_MCP_URL': Required.
Set 'MEMORY_MCP_URL' or disable: features: { memory: false }
```
**Cause:** A loaded plugin's `configSchema` requires a variable that isn't in `process.env`.
**Fix:**
- Set the variable in `.env`.
- Or opt the plugin out: `features: { memory: false }` (only if you don't need its functionality).
- Or if the plugin has an `autoDetect`, make sure the detection var is set so the plugin opts in only when ready.
### LLM provider key missing
```text
[boot-error] LLM provider env validation failed for 'OPEN_ROUTER_API_KEY':
LLM provider 'openrouter' selected via LLM_PROVIDER but OPEN_ROUTER_API_KEY is not set.
Set OPEN_ROUTER_API_KEY, or switch LLM_PROVIDER to nebius and set NEBIUS_API_KEY.
```
**Fix:** Set the key for the selected provider, or switch providers.
### `ORACLE_ENTITY_DID` empty
```text
createOracleApp: ORACLE_ENTITY_DID env is required and was empty after validation.
```
**Cause:** The env var passed Zod validation as a string but resolved to an empty value.
**Fix:** Set `ORACLE_ENTITY_DID=did:ixo:entity:...` in `.env`. Run `qiforge-cli create-entity` if you don't have one.
### Hard dependency missing
```text
[boot-error] Plugin 'skills' depends on 'sandbox', which is not loaded.
Add sandbox to features, or remove skills.
```
**Cause:** A loaded plugin's `dependsOn` lists another plugin that isn't loaded.
**Fix:** Set the required env var for the dependency, or disable the dependent plugin via `features`.
### Dependency cycle
```text
[boot-error] Cyclic plugin dependency: A → B → C → A
```
**Fix:** Restructure plugin dependencies. Cycles in `dependsOn` are not allowed.
### Tool name collision
```text
[boot-error] Tool name collision: 'send_message' is registered by both 'slack' and 'matrix'.
Rename one of them.
```
**Cause:** Two plugins register tools with the same `name`. Flat namespace.
**Fix:** Rename one. Convention is to prefix tool names with a domain hint (`slack_send_message`).
### Shared-state key collision
```text
[boot-error] Shared state key collision: 'userProfile' registered by 'memory' and 'profile-overrides'.
```
**Fix:** Rename one of the keys in the plugin's `getSharedState()` return.
### Manifest validation failed
```text
[boot-error] Plugin 'weather' manifest example references unknown tool 'foo'.
[boot-error] Plugin 'weather' manifest: whenToUse must have at least 1 entry when visibility != 'silent'.
```
**Cause:** Hard manifest violations (see [Manifest schema](/build-an-oracle/reference/manifest-schema)).
**Fix:** Ensure `summary` is non-empty, `whenToUse` has entries when visibility isn't `silent`, and every `examples[].tool` matches a registered tool.
## Matrix issues
### Matrix init fails
```text
[plugin] matrix pending → failed (reason: ...)
[runtime] matrix-init: ...
```
**Cause:** Wrong Matrix homeserver URL, wrong admin credentials, network unreachable, or the access token has expired.
**Fix:**
- Check `MATRIX_BASE_URL`, `MATRIX_ORACLE_ADMIN_USER_ID`, `MATRIX_ORACLE_ADMIN_PASSWORD`, `MATRIX_ORACLE_ADMIN_ACCESS_TOKEN`.
- Run `qiforge-cli create-entity` to re-provision if the credentials are stale.
Important: the runtime starts HTTP listening even when Matrix is still pending. Auth-requiring routes will 401 until Matrix is up, but public routes (`/health`, `/docs`, plugin and host exclusions) stay reachable.
### Boot warns: UCAN signing key not loaded
```text
[boot] setupClaimSigningMnemonics returned null.
UCAN minting will be unavailable until the mnemonic is provisioned
(run `oracles-cli setup-claim-signing-mnemonics`).
```
**Cause:** The oracle's Matrix account room doesn't contain the encrypted signing mnemonic state event.
**Fix:** Run `qiforge-cli setup-encryption-key` to provision it. Until then, authenticated routes will 401. (The boot warning still prints the legacy command name `oracles-cli setup-claim-signing-mnemonics`; the current CLI provisions the signing key under `qiforge-cli setup-encryption-key`.)
### Boot warns: No P-256 encryption key found
```text
[boot] No P-256 encryption key found. User secrets will be
unavailable. Provision one via `oracles-cli setup-encryption-key`.
```
**Fix:** Run `qiforge-cli setup-encryption-key`. Until then, `rtCtx.secrets.getValues()` returns nothing — acceptable degraded mode, but plugins that depend on user secrets won't work.
### Lost Matrix store after restart
**Symptoms:** Boot is slow; old threads aren't visible; encrypted messages can't be decrypted.
**Cause:** The Matrix store directory wasn't persisted across restarts.
**Fix:** Mount a volume at `MATRIX_STORE_PATH` (and `SQLITE_DATABASE_PATH`). See [Deployment](/build-an-oracle/develop/deploy).
## Plugin loading
### A plugin I expected isn't loaded
Check the boot log:
```text
[boot] excluded plugins: composio (COMPOSIO_API_KEY), slack (SLACK_BOT_OAUTH_TOKEN)
```
The reason is the plugin's `autoDetectHint` — the env var that, when set, would opt the plugin in.
**Fix:**
- Set the var.
- Or force the plugin on via `features: { composio: true }` (you'll then need to provide whatever vars its `configSchema` requires).
### A plugin loaded that I didn't want
Two reasons it might load:
1. It has no `autoDetect` and is on by default. Disable via `features: { name: false }`.
2. Its `autoDetect` returned `true` because the relevant env var is set. Either unset the var or force off via `features`.
### The agent doesn't call my tool
Most common causes:
- **Plugin is `on-demand` but hasn't been loaded** by the agent in this thread. The agent has to call `list_capabilities` and `load_capability` first. Either:
- Promote to `visibility: 'always'` (costs Tier-1 tokens — only if the agent needs it most turns).
- Improve the manifest's `whenToUse` so the agent picks it up faster.
- **Manifest `whenToUse` is too vague.** Add specific trigger phrases.
- **Tool description doesn't match the user's intent.** The agent reads the description verbatim — write it like a docstring for the LLM.
- **Tool isn't in the bound list.** Check `app.plugins.status().loaded` to confirm the plugin is loaded. If `on-demand`, check `state.loadedPlugins`.
### Soft-dep gap warning at boot
```text
[boot] excluded plugins: ...
[boot] soft-dep gaps: claim-processing → memory
```
The plugin loaded but its `softDependsOn` is missing. This is informational — the plugin will branch on `availablePlugins.has('memory')` and degrade gracefully. If you want the full behaviour, also load the missing plugin.
## Auth
### Every protected request returns 401
When no auth is present at all, the middleware returns **401** with:
```text
Missing UCAN authentication: provide Authorization: Bearer with X-Auth-Type: ucan, or an x-ucan-delegation header
```
A malformed or expired invocation returns **401** `Invalid UCAN invocation`. Likely causes:
- **No invocation sent.** Primary auth is a user-signed UCAN invocation carried as `Authorization: Bearer ` **plus** `X-Auth-Type: ucan` — the bearer is ignored without the `X-Auth-Type: ucan` selector. (A bare `x-ucan-delegation` header is accepted only as a migration fallback.)
- **Invocation expired or malformed**, or its lifetime exceeds `UCAN_AUTH_MAX_TTL_SECONDS` (default 900s).
- **Matrix init still pending.** The runtime needs Matrix up to validate UCANs. Watch for `[plugin] matrix pending → loaded` before retrying.
- **Signing mnemonic not loaded** (see Matrix issues above). The oracle can't mint downstream invocations until it's provisioned.
`x-did` is **not** used for authentication — the authenticated DID is derived from the validated invocation (or delegation), never from a header the client sets. See [Identity and auth](/build-an-oracle/develop/identity-and-auth#per-request-user-auth).
### My webhook hits 401
Plugin and host webhooks need to be opted out of `AuthHeaderMiddleware`:
- Plugin-side: `getAuthExcludedRoutes()` returns `[{ path: 'my-route', method: RequestMethod.POST }]`.
- Host-side: pass `authExcludedRoutes: [{ path: '...', method: ... }]` to `createOracleApp`.
The `path` is the full path the controller mounts at (`webhooks/incoming`, not `incoming`). Leading slash optional.
## Runtime errors
### `OracleApp.listen called twice.`
You called `app.listen()` more than once. The framework allows it only once — after that, the app's listening state is fixed.
### `setFileProcessingProvider` not configured
This is wired by `createOracleApp` itself before NestJS boots — you shouldn't see it. If you do, you're using internal modules in an unusual order. Stick to `createOracleApp`.
### Concurrent model-timing middleware logs are interleaved
If you wrote a middleware with closure-scoped timing state, concurrent LLM calls share the same closure. For accurate per-call timing, push start times onto a stack keyed by `runId` (from the runtime), or use the standard tracing path.
## When in doubt
- Check `app.plugins.status()` — `loaded`, `excluded` (with reason), `softDepGaps`.
- Check the Swagger UI at `/docs` — the live route list.
- Open the LangSmith trace for the failing turn.
## Where to read next
What needs to be set for each plugin.
Default state, dependencies, env vars per plugin.
Volume layout, probes, signal handling.
---
# API reference
> Interface reference for IXO Protocol gateways and IXO application services.
This section documents API interfaces only. It does not define protocol concepts, product positioning, or canonical endpoint literals.
For a **single comparison table** (read/write, purpose, links to auth and endpoints), start at [API introduction](/api-reference/intro-apis).
## API surfaces
Direct node-level interface for IXO Protocol transaction and state access.
HTTP/JSON gateway over gRPC query services for IXO Protocol modules.
Indexed query interface for chain data through IXO Blocksync.
Service API for state and ACL operations in IXO Matrix rooms.
Application API for Impact Hub Registry service workflows.
Session endpoint for the IXO USSD gateway — accepts input from telecom gateways and returns USSD responses.
## Boundary between protocol and services
- IXO Protocol APIs: `rpc-api` and `grpc-gateway-api`.
- Service APIs: `blocksync-graphql-api`, `matrix-state-bot-api`, and `registry-api`.
- Protocol concepts and module behavior belong in protocol documentation, not API operation pages.
## Shared references
Confirm canonical endpoint mappings by network and environment.
Verify interface-specific authentication requirements and literals.
Navigate IXO products, APIs, and SDK references.
## Related API guidance
Compare authentication schemes by API surface.
Handle common API and transport failures.
Use cursor and page patterns for large result sets.
---
# Introduction
> How to choose between IXO Protocol APIs and IXO service APIs.
Use this page to select the right **API surface** before implementation.
## Vocabulary
- **Protocol gateways** — Node-level interfaces to IXO Protocol modules: submit and query chain state via **JSON-RPC** or **REST over gRPC** (Cosmos stack). You typically need a wallet or signer for writes.
- **Service APIs** — Operator-hosted interfaces over **indexed chain data**, **Matrix**, **registry workflows**, or **USSD** sessions. Auth and base URLs are **deployment-specific**; never assume one header works everywhere.
## Comparison matrix
- **Primary purpose:** Transactions, ABCI queries, Tendermint RPC
- **Typical operations:** `broadcast_tx_*`, status, block, tx search
- **Read / write:** Read + write (tx)
- **Auth and endpoints:** [Authentication matrix](/reference/authentication-matrix); [Networks and endpoints](/reference/networks-and-endpoints)
- **Docs:** [Blockchain RPC API](/api-reference/rpc-api)
- **Primary purpose:** HTTP/JSON queries (and tx broadcast where exposed)
- **Typical operations:** Module REST paths, Swagger/OpenAPI per deployment
- **Read / write:** Read; write depends on deployment
- **Auth and endpoints:** Same as RPC—confirm with node operator
- **Docs:** [REST (gRPC gateway)](/api-reference/grpc-gateway-api)
- **Primary purpose:** Indexed queries over chain-derived entities
- **Typical operations:** GraphQL queries (subscriptions if enabled)
- **Read / write:** **Read-only** in docs model
- **Auth and endpoints:** Service operator credentials
- **Docs:** [Blocksync GraphQL API](/api-reference/blocksync-graphql-api)
- **Primary purpose:** Room state, ACLs, automation hooks for Matrix
- **Typical operations:** Bot-specific HTTP endpoints
- **Read / write:** Read + write (room operations)
- **Auth and endpoints:** Service operator credentials
- **Docs:** [Matrix state bot API](/api-reference/matrix-state-bot-api)
- **Primary purpose:** Impact Hub Registry application workflows
- **Typical operations:** CRUD-style registry operations per spec
- **Read / write:** Read + write (per spec)
- **Auth and endpoints:** Often Basic or bearer—confirm matrix
- **Docs:** [Registry API](/api-reference/registry-api)
- **Primary purpose:** Telecom session bridge for USSD menus
- **Typical operations:** Session request/response
- **Read / write:** Write (session replies) + read patterns per spec
- **Auth and endpoints:** Service operator policy
- **Docs:** [USSD gateway API](/api-reference/ussd-api)
For SDK and product naming, see [Product and SDK map](/reference/product-and-sdk-map).
## API families (quick links)
- **IXO Protocol gateways**
- [RPC API](/api-reference/rpc-api)
- [REST (gRPC gateway)](/api-reference/grpc-gateway-api)
- **IXO service APIs**
- [Blocksync GraphQL](/api-reference/blocksync-graphql-api)
- [Matrix state bot](/api-reference/matrix-state-bot-api)
- [Registry](/api-reference/registry-api)
- [USSD gateway](/api-reference/ussd-api)
## Authentication and endpoints
This page does not define canonical auth headers or endpoint literals.
Use canonical auth requirements and literals by interface.
Confirm network-specific endpoint mappings before implementation.
Navigate IXO products, APIs, and SDK references.
## Next steps
Compare protocol and service interfaces from one entry point.
Handle API and transport failures consistently.
Apply cursor and page patterns for large result sets.
---
# Authentication
> Authentication patterns used across IXO Protocol and IXO service APIs.
Use this page to understand auth surface area by API family. For canonical literals (headers, token sources, environment-specific behavior), use the authentication matrix.
## Scope
- This page summarizes auth methods by API surface.
- It does not define a single universal flow for all IXO APIs.
- Protocol docs and service docs can require different credentials.
## Common request patterns
Common header formats used across IXO docs are centralized in `/reference/authentication-matrix`.
Do not assume any one header or token type applies to every endpoint. Confirm each interface against `/reference/authentication-matrix`.
## API families and auth responsibility
- **Surfaces:** `/api-reference/rpc-api`, `/api-reference/grpc-gateway-api`
- **Auth notes:** Chain and node access patterns vary by network and deployment.
- **Surfaces:** `/api-reference/blocksync-graphql-api`, `/api-reference/matrix-state-bot-api`, `/api-reference/registry-api`
- **Auth notes:** Service operators can enforce different credentials and scopes.
## Security baseline
- Use HTTPS for all authenticated requests.
- Keep credentials in secure runtime storage, not source files.
- Rotate tokens and keys according to your operator policy.
- Log request identifiers and auth failures for incident triage.
## Source-of-truth references
Use canonical auth literals and requirements by interface.
Confirm network-specific endpoint mappings before integration.
Navigate IXO products, APIs, and SDK references.
---
# Error Handling
> Comprehensive guide to error handling in IXO API
Effective error handling is essential for ensuring a smooth developer experience and building robust applications. This guide provides an overview of the errors you may encounter when interacting with IXO API.
Errors that occur due to client-side issues.
- **400 Bad Request**: Malformed request or invalid parameters
- **401 Unauthorized**: Authentication required or failed
- **403 Forbidden**: Insufficient permissions
- **404 Not Found**: Resource not found
- **429 Too Many Requests**: Rate limit exceeded
Errors that occur due to server-side issues.
- **500 Internal Server Error**: Unexpected server condition
- **502 Bad Gateway**: Invalid response from upstream
- **503 Service Unavailable**: Server temporarily unable to handle request
## Error Response Structure
IXO API provides informative error responses in JSON format:
```json
{
"error": {
"code": 400,
"message": "Invalid request parameters",
"details": [
{
"field": "entityId",
"error": "Missing required parameter"
}
]
}
}
```
### Response Fields
- **code**: HTTP status code
- **message**: Human-readable error description
- **details**: Additional error information
## Docs API errors
The `/api/*` routes on `docs.ixo.world` answer with JSON, never an HTML page, so an agent can parse the failure without scraping. The envelope extends the structure above with a stable machine-readable code and a resolution hint:
`/mcp` speaks JSON-RPC 2.0 and uses **a different error shape** — see [MCP errors](#mcp-errors) below. Do not match on `error.status` there.
```json
{
"error": {
"code": 404,
"status": "NOT_FOUND",
"message": "No API route at /api/unknown.",
"hint": "Available routes: /api/assistant/session, /api/assistant/message, /api/assistant/abort, /api/try.",
"documentation": "https://docs.ixo.world/api-reference/errors"
}
}
```
### Envelope fields
| Field | Type | Meaning |
| --- | --- | --- |
| `code` | integer | The HTTP status code, repeated in the body. |
| `status` | string | Stable machine-readable code. Match on this, not on `message`. |
| `message` | string | What went wrong, in one sentence. |
| `hint` | string | How to resolve it. Present whenever a resolution exists. |
| `documentation` | string | URL of the page describing this class of error. |
### Status codes
`BAD_REQUEST` · `UNAUTHENTICATED` · `PERMISSION_DENIED` · `NOT_FOUND` · `METHOD_NOT_ALLOWED` · `PAYLOAD_TOO_LARGE` · `RESOURCE_EXHAUSTED` · `BAD_GATEWAY` · `UNAVAILABLE`
### Rate limits
Every `/api/*` and `/mcp` response carries [RFC 9331](https://www.rfc-editor.org/rfc/rfc9331.html) rate-limit headers, so an agent can pace itself from the published quota:
| Header | Example | Sent on | Meaning |
| --- | --- | --- | --- |
| `RateLimit-Policy` | `"mcp";q=120;w=60` | every response | The quota (`q`) per window of `w` seconds. |
| `RateLimit` | `"mcp";r=0;t=60` | 429 only | Requests remaining (`r`) and seconds until the window resets (`t`). |
| `Retry-After` | `60` | 429 only | Seconds to wait before retrying. |
Current quotas: **120 requests per minute per IP** on `/mcp` (policy name `mcp`), **10 per minute per IP** on `/api/*` (policy name `api`).
`RateLimit` is sent only on a 429. The edge limiter reports allow/deny, not a live counter, so a remaining count is published only when it is known to be exactly `0` — an invented number would be worse than none. Pace from `RateLimit-Policy`.
A 429 on `/api/*` uses the envelope above with `status: "RESOURCE_EXHAUSTED"`; on `/mcp` it uses the same HTTP envelope, because the limiter runs before the JSON-RPC layer.
## MCP errors
`/mcp` implements JSON-RPC 2.0, so protocol failures use the JSON-RPC error object rather than the envelope above — there is no `status`, `hint`, or `documentation` field:
```json
{
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -32601, "message": "Method not found: tools/invoke" }
}
```
| JSON-RPC code | Meaning |
| --- | --- |
| `-32700` | Parse error — the body was not valid JSON. |
| `-32600` | Invalid request — not a JSON-RPC 2.0 envelope. |
| `-32601` | Method not found. |
| `-32602` | Invalid params, including an unknown tool name. |
A **tool** that fails does not produce a JSON-RPC error. It returns a normal result with `isError: true` and the reason as text content, which is how MCP reports tool-level failures:
```json
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{ "type": "text", "text": "No page at /nope. Use search_docs to find the right URL." }],
"isError": true
}
}
```
Transport-level failures before the JSON-RPC layer — a 429 from the rate limiter, or a `GET`/`DELETE` on `/mcp` when no stream is available — use HTTP status codes with the JSON envelope documented above.
A path that does not exist returns a real HTTP **404**, never a 200 with an app shell. Clients that do not ask for HTML get a short Markdown body listing the sitemap, `llms.txt`, the MCP server, and the docs index, so an agent can recover in one more request.
## Best Practices
Rely on HTTP status codes to understand error types and handle them appropriately.
For 5xx errors, implement retry logic with exponential backoff.
Validate all input parameters before sending requests to minimize 400 errors.
Monitor and respect rate limits to avoid 429 errors.
## Common Error Solutions
**Solution**: Double-check request syntax and parameters
**Solution**: Verify access token and re-authenticate if needed
**Solution**: Verify user permissions and roles
**Solution**: Retry after delay or contact support
Always log and monitor errors to identify patterns and improve error handling in your applications.
---
# Pagination
> Guide to pagination in the IXO Blocksync GraphQL API
Pagination is crucial for managing large datasets efficiently, ensuring smooth navigation and data retrieval without overwhelming clients or servers.
## Default Pagination
The IXO Blocksync GraphQL API uses a default pagination value of 10 items across all queries.
## Pagination Parameters
Defines the number of records to return in each query.
```graphql
query {
entities(first: 50) {
edges {
node {
id
name
}
}
}
}
```
Defines the number of records to return, starting from the end of the dataset.
```graphql
query {
entities(last: 10) {
edges {
node {
id
name
}
}
}
}
```
Cursor to indicate the point in the dataset from which to continue fetching results.
```graphql
query {
entities(first: 20, after: "YXJyYXljb25uZWN0aW9uOjEw") {
edges {
node {
id
name
}
}
}
}
```
Cursor to navigate backward from a specific point in the dataset.
```graphql
query {
entities(last: 10, before: "YXJyYXljb25uZWN0aW9uOjIw") {
edges {
node {
id
name
}
}
}
}
```
## Response Structure
A paginated response includes metadata for navigation:
```json
{
"data": {
"entities": {
"edges": [
{
"node": {
"id": "did:ixo:entity:001",
"name": "Entity One"
}
}
],
"pageInfo": {
"endCursor": "YXJyYXljb25uZWN0aW9uOjIw",
"hasNextPage": true
}
}
}
}
```
### Response Fields
- **edges**: Array of results
- **pageInfo.endCursor**: Reference point for next results
- **pageInfo.hasNextPage**: Indicates more data availability
## Best Practices
Use 20-100 items per query for optimal performance
Check `hasNextPage` to determine if more data is available
Use cursor values from responses, don't hardcode them
Implement rate limiting strategies to avoid 429 errors
## Error Handling
Occurs with invalid cursor values. Always use cursors from previous responses.
Implement retry logic with exponential backoff when rate limits are reached.
For optimal performance, implement client-side caching of paginated results when appropriate.
---
# Blockchain RPC API
> Node-level API interface for IXO Protocol transactions and state queries.
This page documents the RPC interface family for IXO Protocol. It does not document application service APIs such as Impact Hub Registry, IXO Blocksync, or IXO Matrix.
## Overview
- Use this interface for protocol-level operations against chain nodes.
- Prefer this when you need direct interaction with IXO Protocol modules.
- For indexed or application-service workflows, use service APIs in this section instead.
## Protocol boundary
- In scope: chain state queries and transaction submission patterns.
- Out of scope: service-specific business endpoints and off-chain workflow APIs.
## Authentication and endpoints
Authentication depends on node and deployment policy.
- Check `/reference/authentication-matrix` for active auth requirements.
- Check `/reference/networks-and-endpoints` for network-specific RPC endpoints.
## Example message shapes
```protobuf
message MsgCreateEntity {
string creator = 1;
string entity_type = 2;
string entity_status = 3;
}
```
```protobuf
message MsgSubmitClaim {
string creator = 1;
string collection_id = 2;
string claim_id = 3;
}
```
## Troubleshooting
- **`connection refused` / TLS errors** — Wrong host, port, or TLS termination. Confirm the literal in [Networks and endpoints](/reference/networks-and-endpoints) and your operator’s current RPC URL.
- **`invalid JSON-RPC` / parse errors** — Request body not valid JSON-RPC 2.0; include `jsonrpc`, `id`, `method`, and `params` as required by the method.
- **Transaction broadcast failures** — Inspect `code`, `codespace`, and `rawLog`. Common causes: insufficient fees, wrong `chain-id`, sequence mismatch, or missing signer permissions. See [Error handling](/api-reference/errors) and [Claims management](/guides/dev/ixo-claims#troubleshooting).
- **Auth on RPC** — Many nodes allow unauthenticated read; writes require a valid signed tx, not an API key. Confirm policy in [Authentication matrix](/reference/authentication-matrix).
## Related references
Access protocol queries over HTTP/JSON through the gRPC gateway.
Query indexed chain data through the IXO Blocksync service layer.
Navigate IXO products, APIs, and SDK references.
---
# Blockchain REST API
> HTTP/JSON gateway reference for IXO Protocol gRPC query services.
The Blockchain REST API is the gateway surface for IXO Protocol query access over HTTP. It is a protocol interface, not an application service API.
## Overview
- Use this API when your client needs HTTP/JSON access to protocol queries.
- Use the RPC API for direct node interaction patterns.
- Use service APIs for IXO Blocksync, IXO Matrix, or Impact Hub Registry workflows.
## Request model
1. Client sends HTTP request.
2. Gateway maps request to gRPC service methods.
3. Gateway returns JSON response.
## Example endpoint shapes
```http
GET /ixo/entity/{id}
GET /ixo/claims/claims
GET /ixo/token/params
```
## Authentication and endpoints
Authentication requirements vary by deployment.
Confirm required credentials and auth patterns by interface.
Confirm endpoint mappings by network and environment.
## Troubleshooting
- **`404` on REST paths** — Gateway build or route prefix differs by deployment (`/ixo/...` vs legacy prefixes). Compare the served OpenAPI/Swagger document from your node operator.
- **`501 Not Implemented` / empty responses** — The mapped gRPC method may be disabled or not compiled into that binary.
- **CORS errors from browsers** — Prefer server-side or mobile clients for protocol access; if you must call from a browser, use an operator-approved proxy.
- **401/403** — Some deployments gate REST; align headers with [Authentication matrix](/reference/authentication-matrix). For writes, you still sign transactions—REST is often query-only.
## Related references
Use RPC endpoints for direct blockchain node interactions.
Review authentication patterns used across IXO API interfaces.
Navigate IXO products, APIs, and SDK references.
---
# IXO Blocksync GraphQL API
> Read-only GraphQL query interface for indexed IXO chain data.
IXO Blocksync is an indexing and query service. It is not the canonical protocol transaction interface.
## Overview
- Use this API for indexed, flexible querying.
- Treat this as a service layer over chain data.
- Use protocol gateways for direct protocol operations.
## Service boundary
- In scope: GraphQL query patterns, filters, and pagination.
- Out of scope: transaction submission and module-level protocol semantics.
## Example queries
```graphql
query {
entities(first: 10) {
edges {
node {
id
name
type
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
```
```graphql
query {
entities(
filter: {
type: { equalTo: "ORGANIZATION" }
status: { equalTo: "ACTIVE" }
}
) {
edges {
node {
id
name
status
}
}
}
}
```
## Authentication and endpoints
Verify required credentials and auth patterns by interface.
Confirm endpoint mappings by network and environment.
## Troubleshooting
- **GraphQL `errors` with partial `data`** — Treat the operation as failed if any required selection set is null; fix variables and filters, then retry. Log `extensions` when present for server hints.
- **Auth failures** — Service credentials differ by operator; confirm against [Authentication matrix](/reference/authentication-matrix). Do not reuse protocol wallet keys as GraphQL API keys.
- **Stale or missing entities** — Indexers lag behind chain head; wait for sync or query the same entity via RPC/REST if you need authoritative height.
- **Introspection disabled** — Some deployments turn off schema introspection in production; use a saved schema or dev environment for tooling.
## Related references
Apply consistent pagination patterns across API queries.
Use RPC endpoints for direct blockchain node interactions.
Navigate IXO products, APIs, and SDK references.
---
# Matrix state bot API
> Service API for state and ACL operations in IXO Matrix rooms.
This is a service API for IXO Matrix workflows. It is separate from IXO Protocol gateway APIs.
## Overview
- Use this API to read and manage application state events in Matrix rooms.
- Use this API to manage ACL event content for room-level permissions.
- Do not use this page as a source for protocol transaction behavior.
## Event shapes
```json
{
"type": "ixo.room.state",
"state_key": "unique_identifier",
"content": {}
}
```
```json
{
"type": "ixo.room.state.acl",
"state_key": "state_event_key",
"content": {
"read": ["@user:domain.com"],
"write": ["@admin:domain.com"]
}
}
```
## Authentication and endpoints
Confirm required credentials and auth patterns by interface.
Confirm endpoint mappings by network and environment.
## Troubleshooting
- **`403` / ACL denied** — Bot token lacks power level or room membership; verify room ID, bot user ID, and ACL event content.
- **State event rejected** — Invalid `state_key`, oversized `content`, or conflicting types; compare with working examples in [IXO Matrix](/articles/ixo-matrix).
- **Wrong homeserver** — Matrix is federation-aware; confirm `baseUrl` and user IDs match the deployment in [Networks and endpoints](/reference/networks-and-endpoints).
- **Rate limits** — Back off and batch state updates; see [Error handling](/api-reference/errors) for generic HTTP retry guidance.
## Related references
Review authentication patterns used across IXO API interfaces.
Navigate IXO products, APIs, and SDK references.
---
# Registry API
> Application service API reference for Impact Hub Registry workflows.
This page covers the Impact Hub Registry service API. It is not a direct IXO Protocol gateway and should be treated as an application/service surface.
## Overview
- Service scope includes registry-facing workflows such as project, household, device, credit, and claim reporting interfaces.
- Protocol-level claim semantics and module transactions belong to IXO Protocol API pages.
## Authentication
Registry deployments can use service-specific credentials. The historical examples for this surface include HTTP Basic auth.
```http
Authorization: Basic
```
Do not assume one auth scheme across all registry environments. Confirm active requirements in `/reference/authentication-matrix`.
## Endpoint groups
- System health and API docs
- Household and device reporting
- Fuel and claims reporting
- Credit lifecycle reporting
## Known literal handling
Historical environment literals for this surface have appeared in previous docs. This page no longer treats those values as canonical source of truth.
Use network and environment endpoint mappings as source of truth.
Confirm active authentication requirements before integration.
## Troubleshooting
- **`401 Unauthorized` with Basic auth** — Wrong username/password or environment rotated credentials; re-read the deployment row in [Authentication matrix](/reference/authentication-matrix).
- **`403 Forbidden`** — Authenticated but missing role or scope for that registry route; check operator RBAC and which program the credentials belong to.
- **`404` on resource paths** — Registry API versions and path prefixes differ by deployment; confirm OpenAPI/base URL with your operator ([Networks and endpoints](/reference/networks-and-endpoints)).
- **Payload validation errors** — Compare field names to the operator’s schema; many issues are missing required reporting fields, not transport failures ([Error handling](/api-reference/errors)).
## Related references
Follow task-oriented guidance for registry workflows.
Review authentication patterns used across IXO API interfaces.
Navigate IXO products, APIs, and SDK references.
---
# USSD gateway API
> Endpoint reference for the IXO USSD gateway session API.
## Overview
The IXO USSD gateway exposes a single session endpoint that telecom gateways call for each user interaction, plus debug and health endpoints for operators.
All endpoints accept and return JSON unless otherwise noted. The main session endpoint returns plain text in the USSD response format.
## Authentication
The `/api/ussd` endpoint does not require authentication by default. It is intended to be called by your telecom gateway server, not directly from a browser. Restrict access at the network or reverse-proxy layer in production.
## Base URL
```text
http://YOUR_SERVER:3000
```
The default port is `3000`. Set `PORT` in your environment to override.
## Session endpoint
### POST /api/ussd
Processes a USSD interaction. Called by the telecom gateway once per user input during a session.
**Request**
```json
{
"sessionId": "string",
"serviceCode": "string",
"phoneNumber": "string",
"text": "string"
}
```
- **Type:** `string`
- **Required:** yes
- **Description:** Unique session identifier assigned by the telecom gateway.
- **Type:** `string`
- **Required:** yes
- **Description:** USSD service code the user dialled, for example `*1234#`.
- **Type:** `string`
- **Required:** yes
- **Description:** User phone number in E.164 international format, for example `+260971234567`.
- **Type:** `string`
- **Required:** yes
- **Description:** User input accumulated during the session; empty string on initial dial.
**Validation rules**
- `phoneNumber` must be E.164 format
- `serviceCode` must match USSD format, e.g. `*1234#` or `*123*456#`
- `text` maximum 182 characters (USSD protocol limit)
- `sessionId` maximum 100 characters
**Response**
Plain text. The first word indicates whether the session continues or ends.
```text
CON