# Covia > Covia is an open-source runtime for federated AI orchestration: venues host self-describing operations, run stateful agents, and keep an auditable job record. Speaks REST, MCP, A2A, and DID. This file contains all documentation content in a single document following the llmstxt.org standard. ## Overview # Covia > **The Universal Grid for the Agent Economy.** > Covia lets AI models, agents, and data collaborate across organisational boundaries, clouds, and jurisdictions, with governance built in and control retained by every participant. ## What is Covia? Covia is an open-core, open-source runtime for **federated AI orchestration** and **federated AI execution**. The two terms name the two halves of the problem. Orchestration is the coordination half: multi-step workflows that sequence agents, models, and data across venues, with dependencies, error handling, and result aggregation. Execution is the delivery half: each step runs inside the venue that governs its data and credentials, under capability checks made before dispatch, and leaves an immutable job record when it completes. Orchestration without governed execution produces workflows nobody can audit; execution without orchestration produces capabilities nobody can compose. Covia provides both in one runtime. The grid consists of **venues**: nodes that host *operations* (executable, self-describing capabilities), manage context and state for *agents*, and keep an immutable, auditable record of every job. Venues federate: a workflow on one venue can invoke an operation on a partner's venue, in another cloud or jurisdiction, while the data stays where it is governed and only results cross the boundary. The agent capability of every venue is **[Covia Agents](./agents)**: hosted, governed agents plus **BYOA** (bring your own agent, over A2A or MCP) and **BYOM** (bring your own model, through pluggable LLM bindings). It is built on the [Convex](https://docs.convex.world/docs/overview/lattice) lattice platform for decentralised, cryptographically verifiable state, and it speaks the protocols the AI ecosystem already uses: **REST**, **MCP**, **A2A**, and **DID**. If you think of HTTP as the protocol that made documents interoperable, Covia is the layer that makes **AI capability** interoperable, verifiable, and governable across trust boundaries. ## See it work: right now Every venue exposes the same API. Call a live one (no install, no signup): ```bash curl -X POST https://venue-3.covia.ai/api/v1/invoke \ -H "Content-Type: application/json" \ -d '{ "operation": "v/ops/schema/infer", "input": { "value": { "name": "Ada", "age": 36 } }, "wait": true }' ``` What comes back is not just a result; it's a **job record**: who invoked what, when, with what outcome, persisted on the venue's lattice. That audit-grade system of record is the point. Three minutes more gets you a venue of your own: ```bash docker run -p 8080:8080 ghcr.io/covia-ai/covia:latest ``` (`:latest` tracks the development channel, which these docs follow; use `:stable` for the release build; see [Release channels](../operator-guide/venue-start#release-channels).) → **[Quick Start](../user-guide/quick-start)**: zero to your first operation in TypeScript or Python. ## Why federation? AI capability is unevenly distributed by nature: the best model sits in one cloud, the data that makes it valuable belongs to another organisation, and the domain expertise lives in a third. Today there are two ways to combine them: **centralise** (hand your data and agency to someone else's platform) or **integrate** (build N² brittle point-to-point connections). Neither survives contact with real governance: regulated data can't leave, audit trails can't be reconstructed from glue code, and every new partner restarts the integration project. Covia's view is that the missing piece is **infrastructure, not another framework**: a common runtime where any AI capability can be published, discovered, invoked, and audited across organisational boundaries, with control retained, always, by the party that owns each resource. ## How it works, and what's different | Principle | What it means | |---|---| | **Sovereign by construction** | Each [venue](./venues) is operated independently with its own identity (DID), [authentication](../operator-guide/auth), and policy. Federation never requires surrendering control of data or infrastructure. | | **Self-describing operations** | Every capability carries discoverable metadata, for developers *and* agents. One integration surface for humans and machines. | | **Audit-grade execution** | Every invocation is a [job](../user-guide/api/) with an immutable, queryable record. Compliance is a property of the runtime, not an afterthought. | | **Verifiable state** | Assets are content-addressed; venue state lives on a [CRDT lattice](https://docs.convex.world/docs/overview/lattice) that merges deterministically: no central coordinator, no consensus bottleneck. | | **Capability security** | Authorisation follows the [UCAN model](../user-guide/capabilities): signed, attenuable grants enforced on every call. Agents run under least privilege by default. | | **Protocol-native** | A venue is an [MCP server](../user-guide/mcp/) and an [A2A agent](../user-guide/adapters/covia-with-a2a) out of the box. Covia meets the ecosystem where it already is, rather than asking it to move. | ## What you can build - **Cross-organisational AI workflows**: [orchestrate](../user-guide/adapters/orchestrator) multi-step pipelines that span venues, where each party's data stays under its own governance and only results travel. - **Governed agent teams**: persistent, tool-using [agents](../user-guide/agents/) with scoped capabilities and a complete audit trail, like the [AP invoice pipeline](../user-guide/agents/creating-agents#ap-demo-example) where three agents scan, enrich, and approve under least privilege. - **One capability, every protocol**: publish an operation once and it is callable via REST, as an MCP tool from any AI assistant, and as an A2A task from any agent framework. - **Sovereign data services**: user-signed, portable file systems ([DLFS](../user-guide/adapters/dlfs)) and per-user encrypted secrets, hosted on infrastructure you control. - **Teachable agents**: package know-how as portable, self-describing [skills](../user-guide/tutorials/skills) ([COG-18](../protocol/cogs/COG-018)) that agents discover and load at run time. - **Human-in-the-loop control**: pause a workflow for human review, approval, or a capability grant ([COG-16](../protocol/cogs/COG-016)), inside the same audited job record. ## Where the project is Covia is **open core and open source**, built in the open. The runtime is EPL-2.0 (weak copyleft: changes to the core must be published, while proprietary systems may freely build on top), the SDKs are Apache-2.0, and the protocol is standardised under open governance. We are deliberately honest about maturity, because trust is the product: - The **engine is solid**: a clean adapter architecture (nearly 30 adapters), a multi-protocol surface, and over 2,000 automated tests. - **SDKs are published** for [TypeScript](https://www.npmjs.com/package/@covia/covia-sdk) (npm), [Python](https://pypi.org/project/covia/) (PyPI), and [Java](https://central.sonatype.com/artifact/ai.covia/covia-core) (Maven Central). - **Live venues** run today; the examples above hit one. - The platform is **pre-1.0 and moving fast**; APIs may change. Development happens in the open on [GitHub](https://github.com/covia-ai/covia). - The protocol is being standardised in the open as [COGs](../protocol/cogs-overview): draft specifications open to community review. ## Start here **Building something?** [Give Claude your own tools in 5 minutes](../user-guide/tutorials/claude-mcp) · [Quick Start](../user-guide/quick-start) · [Tutorials](../user-guide/tutorials/) · [SDKs](../user-guide/sdk/) · [Agents](../user-guide/agents/) **Running infrastructure?** [Run a venue](../operator-guide/venue-start) · [Authentication](../operator-guide/auth) · [Persistence](../operator-guide/persistence) **Evaluating the vision?** [Whitepaper](../protocol/whitepaper) · [Protocol specifications](../protocol/cogs-overview) · [The Grid](./grid) · [Venues](./venues) **Joining the community?** [GitHub](https://github.com/covia-ai/covia) · [Discord](https://discord.gg/fywdrKd8QT) · [Discussions](https://github.com/orgs/covia-ai/discussions) · [Contribute](./contribute) --- ## Grid The **Grid** is the federated network that Covia venues form together. Each [venue](./venues) is a node that hosts operations and persists state; venues talk to each other, so a workflow running on one venue can invoke an operation on another — in a different cloud or jurisdiction — while the data stays where it's governed and only results cross the boundary. There is no central coordinator. A venue has a DID identity, exposes the same multi-protocol surface (REST, MCP, A2A), and federates with peers directly. ## What the Grid gives you - **Federated execution** — call an operation on a remote venue exactly as you'd call a local one, with caller identity propagated for audit and access control. See the [Grid adapter](../user-guide/adapters/grid-adapter) (`grid:run`, `grid:invoke`). - **Content-addressed assets** — operations, artifacts, and data are identified by the cryptographic hash of their metadata (see [COG-5](../protocol/cogs/COG-005)), so the same asset is the same everywhere on the Grid. - **Verifiable state** — built on [lattice technology](https://docs.convex.world/docs/overview/lattice), state merges deterministically and every job leaves an auditable record. - **Governance at the edge** — each venue enforces its own [authentication](../operator-guide/auth) and [capabilities](../user-guide/capabilities); trust is established between venues, not delegated to a centre. ## Where to go next - [Venues](./venues) — what a single node is and does - [Grid adapter](../user-guide/adapters/grid-adapter) — invoking operations across venues - [Federation tutorial](../user-guide/tutorials/federation) — run two venues and call across them - [COG-15: Cross-Venue Federation](../protocol/cogs/COG-015) — how caller identity and authority cross the venue boundary - [Quick Start](../user-guide/quick-start) — call a live venue in a few minutes - [Protocol](../protocol/) — the COG specifications behind the Grid --- ## Venues A **venue** is a single node on the [Grid](./grid) — a self-contained server that hosts operations, runs agents, and persists state. It has a DID identity, a content-addressed asset store, and a job engine, and exposes the same capabilities over REST, MCP, and A2A — addressable by URL or by `did:web` name. Run one with a single command; see the [operator guide](../operator-guide/venue-start). ## What a venue hosts - **Operations** — self-describing, invocable capabilities provided by pluggable [adapters](../user-guide/adapters/) (LLMs, HTTP, files, orchestration, cross-venue federation, and more). - **Agents** — persistent, stateful actors that hold [sessions](../user-guide/agents/sessions), call tools, and produce auditable results. - **Assets** — immutable, content-addressed artifacts and data. - **Jobs** — the execution record for every invocation, which you can poll or stream over SSE. ## Governance is per-venue Each venue operator decides their own policy. A venue controls its own [authentication](../operator-guide/auth) (public, OAuth, bearer/UCAN tokens), enforces [capabilities](../user-guide/capabilities) on every request, and keeps its data where it is governed — other venues see only the results of operations they're permitted to invoke. This is what lets venues federate across organisational and jurisdictional boundaries without centralising control. ## Where to go next - [Run a venue](../operator-guide/venue-start) — get one running locally or in a container - [Adapters](../user-guide/adapters/) — what a venue can do out of the box - [Agents](../user-guide/agents/) — build stateful, tool-using agents - [Grid](./grid) — how venues federate --- ## Covia Agents **Covia Agents** is the agent capability of every venue: **persistent, governed AI actors** that live on a [venue](./venues). An agent is a durable record with configuration, conversation [sessions](../user-guide/agents/sessions), a task queue, and an append-only timeline of every run. It calls tools, picks up skills mid-task, remembers across restarts, and delegates work to other agents, on its own venue or across the [Grid](./grid), with every step landing in an audit-grade job record. The capability has three doors, and all three run under the same governance: - **Hosted agents**: create an agent on the venue from a template or your own configuration; the venue holds its state, sessions, and timeline. - **BYOA (Bring Your Own Agent)**: connect an agent you already run elsewhere. External agents reach the venue over [A2A](../user-guide/adapters/covia-with-a2a) or [MCP](../user-guide/mcp/), and remote A2A agents can be imported and addressed as ordinary venue assets, so a LangGraph, CrewAI, or framework-native agent participates in governed workflows without being rewritten. See [Bring your own agent](../user-guide/agents/bring-your-own-agent) for the walkthrough. - **BYOM (Bring Your Own Model)**: model access is a pluggable executor binding, with [bindings](../user-guide/agents/llm-backends) for OpenAI, Anthropic, Gemini, DeepSeek, xAI, and local models via Ollama. Swap the model without changing the agent or its callers. This page tours the agentic suite; the [user guide](../user-guide/agents/) covers each piece in depth. ## Equipping an agent ### Tools Every operation in a venue's catalog is a potential tool. Operations are self-describing (JSON Schema in and out), so the same capability serves human developers and LLMs alike: list it in the agent's `config.tools` and it is presented to the model under a snake_case name (`v/ops/covia/read` becomes `covia_read`). Agents are not limited to their configured set: a [Goal Tree](../user-guide/agents/goal-tree) agent can call `more_tools` to discover what else its venue offers. Every call is capability-checked at invocation, whatever route it arrived by. → [Tools and Context](../user-guide/agents/tools-and-context) · [Calling MCP tools](../user-guide/mcp/calling-mcp-tools) ### Skills A **skill** is a named bundle of instructions, context, and tools that an agent loads on demand. Instead of pinning every procedure into the configuration and paying for it on every turn, a well-built agent starts lean and carries a one-line index of skills it can acquire when a task needs them (`skill_load`). Skills are ordinary assets: content-addressed and portable, so you can write one in your workspace and hand it to any agent on any venue. Loading a skill grants no authority: its tools are still capability-checked like any other. → [Skills in Covia](./skills) · [Teach an Agent a New Skill](../user-guide/tutorials/skills) · [COG-18](../protocol/cogs/COG-018) ### Context Each turn, the context builder reassembles the agent's input from scratch: system prompt, tools, pinned context entries, loaded paths, pending results, and the session conversation. Agents manage their own working set with `context_load` and `context_unload`, every entry carries a byte budget with budget-aware rendering, and the Goal Tree adapter compacts automatically under pressure, so context is an engineered, inspectable resource rather than an ever-growing transcript. → [The context layer](./context) · [Context assembly](../user-guide/agents/tools-and-context#context-assembly-pipeline) · [Context budgets](../user-guide/agents/tools-and-context#context-budgets) ### Memory Agent state is durable by construction. [Sessions](../user-guide/agents/sessions) hold conversation threads that persist across runs; tasks survive venue restarts; the timeline records every completed run. Beyond the conversation, agents read and write the venue's lattice through well-defined namespaces (`w/` for the user's workspace, `n/` for the agent's private notes, `a/` for immutable assets), so memory is queryable, governed state, not an opaque blob. The same machinery gives a connected AI assistant persistent memory on infrastructure you control. → [Sessions](../user-guide/agents/sessions) · [Lattice namespaces](../user-guide/agents/tools-and-context#lattice-namespaces) · [Give Claude persistent memory](../user-guide/tutorials/claude-mcp) ## Connecting and extending ### Connectors Every venue is an **MCP server** and an **A2A agent** out of the box: no gateway, no extra deployment. Add a venue to claude.ai or Claude Desktop as a custom connector and its operations appear as tools; point any A2A-speaking framework at it and it responds as an agent. One capability, published once, reachable from every protocol the ecosystem already uses. → [Venues as MCP servers](../user-guide/mcp/venues-as-mcp-servers) · [Covia with A2A](../user-guide/adapters/covia-with-a2a) · [Give Claude your own tools in 5 minutes](../user-guide/tutorials/claude-mcp) ### Adapters Adapters are how a venue gets its capabilities: each contributes a family of operations: LLM calls, outbound HTTP, files, secrets, scheduling, lattice state, federation, and more. Out of the box a venue ships with nearly thirty, and they compose: an agent can call an LLM, fetch a URL, and schedule its own wake-up through one uniform interface. → [Adapters overview](../user-guide/adapters/) ### Plugins The capability surface is pluggable at every level. Operators extend a venue with their own adapters; shipped examples include [SQL](../user-guide/adapters/sql) (governed queries over any JDBC database) and [Python](../user-guide/adapters/python) (operator-configured Python operations); **bridging** mirrors or curates an external MCP server's tools into the venue's catalog, where they behave exactly like native operations: discoverable, capability-scoped, and audited; and skills package higher-level know-how that agents load at run time. Extending a venue never means forking it. → [Bridging MCP tools](../user-guide/mcp/calling-mcp-tools#bridging-mcp-tools-into-the-catalog) · [Adapters overview](../user-guide/adapters/) ### Federation Agents do not stop at the venue boundary. The [Grid adapter](../user-guide/adapters/grid-adapter) invokes operations on remote venues exactly as if they were local, and the [orchestrator](../user-guide/adapters/orchestrator) runs multi-step workflows that span them, while each party's data stays under its own governance and only results cross the boundary. Multi-agent systems can therefore span organisations: your agent, a partner's venue, a third party's model, one auditable workflow. → [The Grid](./grid) · [Federate two venues](../user-guide/tutorials/federation) · [COG-12: Orchestrations](../protocol/cogs/COG-012) ## Governed by default None of the above is bolted on to an ungoverned core. Agents run under [capability](../user-guide/capabilities) grants (signed, attenuable, enforced on every call), with their permissions disclosed in the system prompt and explained in every denial. Each run is a job with an immutable record, and a workflow can pause for human review or a capability grant ([COG-16](../protocol/cogs/COG-016)) inside the same audit trail. The suite is comprehensive precisely because the governance is uniform: one model of identity, authority, and audit underneath every tool an agent touches. ## Start here [Running Agents tutorial](../user-guide/tutorials/running-agents) · [Creating Agents](../user-guide/agents/creating-agents) · [Agents user guide](../user-guide/agents/) · [COG-11: Agent Lifecycle](../protocol/cogs/COG-011) --- ## Architecture This page is the technical evaluator's tour: the problem the architecture answers, the system model, the substrate it stands on, and why the guarantees hold. Each section links to the reference material that goes deeper. ## The problem, stated precisely AI systems are becoming distributed, multi-party workforces spanning APIs, clouds, internal services, and external data. In practice every multi-agent deployment today is a bespoke integration: orchestration logic embedded in each agent, credentials shared across trust boundaries, no durable state, and no verifiable record of what an agent did. That becomes acute the moment regulators, partners, or your own incident review demand evidence. Covia treats this as an infrastructure-layer problem and approaches it the way prior coordination layers were built: an open protocol (the [Covia Open Grid specifications](../protocol/cogs-overview), under open governance), a reference runtime (the venue, open source under EPL-2.0), and a state substrate chosen so that federation, provenance, and fine-grained authority are structural properties rather than bolted-on features. Four goals drive the design: - **Federation without centralised control**: workflows span organisations, clouds, and jurisdictions with no central coordinator. - **Verifiable provenance**: every result can be traced and cryptographically validated back to the code and inputs that produced it. - **Fine-grained authority**: permissions that are scoped, delegable, attenuable, and auditable. - **Ecosystem compatibility**: native support for the protocols and frameworks the AI ecosystem already uses. The design lineage is the modularity of HTTP, the operational consistency of Kubernetes, and the verifiability of Git. ## System model The grid is built from a small set of abstractions. Each is hosted and executed by a venue and addressed under one unified scheme (`//`): | Concept | Definition | | ------- | ---------- | | **Venue** | A grid node: an execution environment with its own DID identity, trust scope, and policy. Deploys as a single JAR or container. | | **Asset** | An immutable resource (data, model, or service description) identified by the hash of its metadata, the root of a Merkle tree over its content. | | **Operation** | An invocable asset, self-describing via JSON Schema input/output, dispatched to a pluggable adapter. | | **Job** | The execution record of an invocation: long-lived (no framework timeout), streamed over SSE, frozen to an immutable record on completion. The unit of accountability and cost. | | **Agent** | A persistent actor hosted by a venue, with durable state, a message inbox, an auditable timeline, and a pluggable transition function (LLM, rules, or code). | Inside a venue, a layered runtime (HTTP server → engine → adapters → lattice storage) dispatches operations to nearly thirty [adapters](../user-guide/adapters/): LLM inference, agents, multi-step orchestration, MCP bridging, cross-venue federation, HTTP, file systems, secrets, and capability issuance. Adapters are the extension mechanism: new execution backends are added without touching the core, and the engine resolves all references to concrete values before dispatch, which is also where authority checks attach. ## The lattice substrate The most consequential design decision is what sits at the bottom. Distributed systems conventionally choose between a central coordinator and global consensus in the blockchain style. The coordinator is efficient but a single point of trust and failure; global consensus is trustless but expensive and totally ordered, which most workloads do not require. Covia's substrate is a [lattice](https://docs.convex.world/docs/overview/lattice): a data structure whose merge operation is commutative, associative, and idempotent. Replicas merged in any order converge to the same state (the CRDT property), so venue state replicates peer-to-peer, tolerates partition and reconnection, and cannot diverge. There is no coordinator and no global ledger as a single point of failure. All values are content-addressed (CAD3 canonical encoding, SHA3-256), so grid state forms Merkle trees verifiable by any peer. The system pairs mutable names with immutable snapshots: operations are authored and iterated under stable paths, pinned to an exact content hash at the moment of invocation; completed jobs freeze into immutable records capturing the pinned operation version, inputs, outputs, executor, and cost. The consequence worth noting: provenance and reproducibility are properties of the data structure, not an audit subsystem that must be maintained per feature. The architecture separates planes: - **Data plane**: workloads, agent state, and bulk replication via [DLFS](../user-guide/adapters/dlfs) (a lattice-based decentralised file system) flow entirely over peer-to-peer replication, at conventional infrastructure cost. - **Trust plane**: identity anchoring, settlement, and tokenisation of AI services can optionally anchor to a public consensus network built on the same lattice mathematics. Globally verifiable consensus is paid for only where it is actually needed. ## Federated execution and provenance A workflow may span venues operated by different organisations in different jurisdictions. The execution model moves computation to the data: an operation on restricted data (patient records, proprietary datasets) executes inside the venue that governs it, and only results cross the boundary. Output assets carry metadata that chains back to the pinned operations and inputs that produced them, so any party can validate the full lineage of a result across organisational lines without access to the underlying data. Because a workflow is itself an operation, composition is recursive: a multi-step workflow published by one venue can be invoked as a single step in another's. Combined with content addressing (the same asset ID resolves identically from any venue holding a copy), this is what allows independent deployments to interoperate as one grid rather than as point integrations. See [Cross-Venue Federation (COG-15)](../protocol/cogs/COG-015) and the [federation tutorial](../user-guide/tutorials/federation). ## Authority model Identity is DID-based and method-agnostic (`did:key` for self-certifying identities, `did:web` for server-backed ones). Authorisation is capability-based using [UCAN tokens](../user-guide/capabilities): signed grants naming a specific resource (`with`) and action (`can`), attenuable and delegable. Capabilities scope down to individual paths: invoke one named operation, decrypt one specific secret, message one specific agent. A delegating agent can pass on a strictly narrower power, so delegation chains mirror organisational authority. Nothing is reachable by default; every action is authorised and recorded. - **Secrets**: agents reference credentials by path only; the runtime is the single decryption point and injects plaintext at the adapter layer. The LLM-driven reasoning loop never observes credentials. - **Dual audit**: the job record answers what happened to a given request (the basis for billing, SLAs, and dispute resolution); the agent timeline answers what a given agent did and why. Both are append-only and replayable. - **Humans in the loop**: a running job can pause for a human answer, approval, or an explicit capability grant ([COG-16](../protocol/cogs/COG-016)), inside the same audit trail, including self-sovereign grants the human signs with their own key ([COG-19](../protocol/cogs/COG-019)). Venue-signed UCAN issuance, per-request proof presentation, cross-user access verification, and adversarial tests (forged signatures, expired tokens, wrong audience or ability) are implemented; completing enforcement across all namespaces is scheduled work on the open roadmap. ## Interoperability The runtime separates the substrate from the agent and the agent from the model: agent behaviour is a pluggable transition function (LLM-backed, rule-based, or custom code), and model access is an executor binding that can be swapped without changing callers. Externally, a venue exposes the same capabilities over every major protocol simultaneously: - **MCP, in both directions**: any MCP client can use a venue as a tool server; the venue can consume any external MCP server as a grid operation. See [MCP integration](../user-guide/mcp/). - **A2A**: agent-to-agent workflows across vendors and frameworks. See [Covia with A2A](../user-guide/adapters/covia-with-a2a). - **REST + DID**: the full [API](../user-guide/api/) with interactive OpenAPI docs; venue discovery via standard DID documents. - **Models**: LangChain4j bindings for OpenAI, Anthropic, Gemini, DeepSeek, xAI, Mistral, OpenRouter, and local models via Ollama. - **SDKs**: [TypeScript](../user-guide/sdk/typescript) (npm), [Python](../user-guide/sdk/python) (PyPI), and [Java](../user-guide/sdk/java), with Ed25519 keypair and bearer authentication. The positioning is deliberate: Covia sits beneath existing frameworks, so its integration surface grows with each protocol's ecosystem. ## Deployment model A venue is one artifact (`docker run` or `java -jar`) that comes up self-describing, with interactive API documentation and working built-in operations. Hosted venues are live on the public internet today, and self-hosting requires no further infrastructure: see the [venue quick start](../operator-guide/venue-start). The self-hosted agent wave of 2025 and 2026 showed unusually fast bottom-up adoption of self-sovereign agents, and the consequences of running them without an authority model: broad system access, publicised security incidents, and no safe way for agents operated by different parties to interact. Covia fits the same deployment pattern while supplying capability-scoped authority, verifiable provenance, and governed federation. Because the protocol is an open standard, each new venue extends what every participant can reach. What does your current agent stack record when something goes wrong, and whose signature is on the authority it acted under? --- ## Context An agent is only as capable as the context you can put in front of it. Covia builds a **governed context layer** into every [venue](./venues): durable, structured state that agents read, write, and share — access-controlled down to the path, auditable at every touch, and open to any agent you choose to let in. ## A workspace for every user — and every agent Every user gets a persistent workspace; every agent gets private notes of its own — alongside scratch space, immutable content-addressed assets, and encrypted secrets, all addressable as structured paths (`w/projects/apollo/status`) rather than opaque blobs. An agent picks up exactly where it left off after a restart, a redeploy, or a month of silence; two agents collaborate by working the same paths. This is not a bolt-on vector store — it is queryable, governed venue state with an audit trail. ## Any agent can plug in The context layer is not reserved for agents hosted on the venue. Every venue is an [MCP server](../user-guide/mcp/), so external agents — Claude, or any MCP-capable assistant or framework — reach the same workspace as ordinary tools (`covia_read`, `covia_write`, …). Five minutes of setup gives Claude memory that survives the conversation, lives on infrastructure you control, and leaves a record of every access. ## Scope it, share it, delegate it Read and write access are governed by [UCAN capabilities](../user-guide/capabilities): signed grants naming who may do what, to which paths. Grants attenuate as they are delegated — give an agent read-only access to `w/reports/`, hand a partner organisation write access to a single path, and nothing more. Every call is checked at the point of use, and a denial explains itself to the LLM instead of inviting retries. Delegation means context can safely cross trust boundaries: your data, their agent, your rules. ## Engineered for LLMs For hosted agents the layer goes further: context is assembled fresh every turn under explicit budgets, agents load and unload exactly what a task needs, and [skills](./skills) bundle context with instructions and tools for expertise on demand. See [Agents](./agents) for the full suite. ## Go deeper [Give Claude persistent memory](../user-guide/tutorials/claude-mcp) · [Tools and Context](../user-guide/agents/tools-and-context) · [Capabilities (UCAN)](../user-guide/capabilities) · [COG-3: Authentication Mechanisms](../protocol/cogs/COG-003) --- ## Contribute to the Grid # Build the Open Grid with Us The Grid is an open, interoperable fabric for AI — built in the open, by and for developers. If you believe AI should be modular, auditable, and connectable across tools, frameworks, and venues, you're in the right place. - Core (Covia): [github.com/covia-ai/covia](https://github.com/covia-ai/covia) - Documentation: [github.com/covia-ai/covia-docs](https://github.com/covia-ai/covia-docs) - Frontend: [github.com/covia-ai/frontend](https://github.com/covia-ai/frontend) - All repositories: [github.com/covia-ai](https://github.com/covia-ai) - Discord: [discord.gg/fywdrKd8QT](https://discord.gg/fywdrKd8QT) ## Why contribute - Help define open standards that make AI systems composable and portable. - Ship real infrastructure: venues, adapters, operations, and governance tooling. - Work in a pragmatic codebase with a strong focus on DX, observability, and security. - Join an active community of builders (researchers, operators, and application devs). ## Ways you can contribute - Fix a bug or improve docs (great for first contributions) - Add or enhance an adapter (e.g. LangChain, HTTP, MCP, or an adapter of your own) - Implement new grid operations or examples - Improve testing, CI, and developer tooling - Propose protocol and governance improvements ## Get started in minutes 1. Explore the docs - Start at [Overview](./) and the [Grid](./grid). - See the [SDK](./sdk) direction and the [Venues](./venues) model. 2. Pick an issue - Core issues: [covia/issues](https://github.com/covia-ai/covia/issues) - Docs issues: [covia-docs/issues](https://github.com/covia-ai/covia-docs/issues) - Frontend issues: [frontend/issues](https://github.com/covia-ai/frontend/issues) - Look for labels like "good first issue", "help wanted", or "docs". 3. Run the docs locally (optional, for docs changes) ```bash pnpm install pnpm run start ``` The docs build fails on broken links and anchors (enforced in CI), so run `pnpm build` before opening a PR. 4. Submit your PR - Fork the repo, create a branch, open a PR against `develop`. - Provide a concise description, screenshots where helpful, and test notes. ## Development pointers - Adapters live under the user guide: see [Adapters](../user-guide/adapters/) for integration patterns. - Keep code clear and self-explanatory; prefer readability over cleverness. - Add meaningful tests and docs alongside code changes. - Follow existing formatting and conventions; avoid unrelated refactors. - Use British English in documentation prose. ## Community & support - Join [Discord](https://discord.gg/fywdrKd8QT) to ask questions, share work, and find collaborators. - Follow updates on [X](https://x.com/CoviaAI). - Discussions and proposals: [GitHub Discussions](https://github.com/orgs/covia-ai/discussions), or issues and PRs on the relevant repo. ## Not sure where to start? Tell us what you want to build in Discord, and we'll help you map it to the Grid — whether that's a new venue, a custom adapter, or a production workflow. Your feedback directly shapes the roadmap. Ready? Open your first issue or PR today: - Core: [github.com/covia-ai/covia](https://github.com/covia-ai/covia) - Docs: [github.com/covia-ai/covia-docs](https://github.com/covia-ai/covia-docs) - Frontend: [github.com/covia-ai/frontend](https://github.com/covia-ai/frontend) --- ## SDK Covia SDKs are **embeddable Grid clients**. Drop one into an application, a service, or an agent and it becomes a first-class participant on the [Grid](./grid): a single client works with any number of connected [venues](./venues) — addressed by URL or DID (`did:web:…`), authenticated with Ed25519 keypairs or bearer tokens — and carries the full Covia capability surface: operations, jobs, assets, agents, secrets, and UCAN capabilities. | SDK | Package | Install | |-----|---------|---------| | [TypeScript / JavaScript](../user-guide/sdk/typescript) | [`@covia/covia-sdk`](https://www.npmjs.com/package/@covia/covia-sdk) | `npm install @covia/covia-sdk` | | [Python](../user-guide/sdk/python) | [`covia`](https://pypi.org/project/covia/) | `pip install covia` | | [Java](../user-guide/sdk/java) | [`ai.covia:covia-core`](https://central.sonatype.com/artifact/ai.covia/covia-core) | Maven Central | | [Rust](../user-guide/sdk/rust) | — | Planned | The Java SDK is the reference implementation. Each language page carries full setup, authentication, and the complete API surface. ## The shape of the API Connect once — `Grid.connect(urlOrDid)` — and every operation in the venue's catalog is a call away. A single call gives your application durable memory on a venue you control: ```python venue.run("v/ops/covia/write", { "path": "w/memory/preferences", "value": "British English, concise replies", }) ``` The call looks the same in every language — see the language pages above for the exact idiom. That value now lives in the venue's governed workspace: it survives restarts, any authorised client or agent can read it back, and the write itself is recorded as an auditable [job](../user-guide/api/). Because operations are self-describing, the same pattern covers everything a venue can do — LLM calls, agent tasks, scheduled work, federated invocations on remote venues — with no per-integration client to generate. No SDK for your language? Every venue speaks the same [REST API](../user-guide/api/); the SDKs are ergonomic bindings over it, so anything they do, any HTTP client can do. ## Compatibility SDKs version independently of the platform. Pre-1.0 the REST surface is additive: older SDKs generally keep working against newer venues, while newly added operations and fields need a current SDK release. ## Where to go next - [SDK reference](../user-guide/sdk/) — full surface for each language (operations, jobs, assets, agents, secrets, UCAN) - [Quick Start](../user-guide/quick-start) — zero to your first operation - [REST API](../user-guide/api/) — the underlying HTTP interface --- ## Skills Most agent frameworks make you decide everything up front — every tool, every procedure, pinned into the configuration and paid for on every turn. Covia agents learn on the job. A **skill** is a named bundle of instructions, context, and tools that an agent picks up mid-task, uses, and puts down again. ## Package any grid operation This is what makes Covia skills unusual: the tools a skill carries are **grid operations**. A skill doesn't just tell an agent how to do something — it hands over the working capability itself: an LLM call, an outbound HTTP request, a scheduled wake-up, a workflow, an operation hosted on a partner's venue across the [Grid](./grid). Anything a venue can do, a skill can teach. Know-how and capability travel as one artifact. A complete, working skill can be a dozen lines: ```json { "description": "Vendor checks — run before approving a vendor.", "content": { "inline": "Read w/vendors/. Check its status URL. Approve." }, "skill": { "tools": [ "v/ops/covia/read", "v/ops/http/get" ] } } ``` Those `tools` entries are live operations from the venue's catalog — the moment an agent loads the skill, it can call them. ## Lean agents, expertise on demand A well-built agent starts lean — a couple of read tools — and carries a one-line index of the skills within reach. When a task calls for more, the agent loads the skill, its instructions and tools join the working context under an explicit budget, and when the job is done the agent unloads it. No sprawling system prompts, no paying every turn for expertise used once. ## Write once, teach anywhere A skill is an ordinary [asset](../protocol/cogs/COG-005): content-addressed and portable. Draft one in your workspace, publish it, and hand it to any agent on any venue. Edit it, and every agent carrying it picks up the change on its next turn. Venues ship with a skill library covering their own mechanisms, browsable by anyone — no agent, no authentication required. ## Powerful, not privileged Loading a skill grants no authority. Its tools are [capability-checked](../user-guide/capabilities) at invocation exactly like everything else, so an agent can carry a powerful skill and still act only within its grant. Teach freely; governance holds. ## Go deeper [Teach an Agent a New Skill](../user-guide/tutorials/skills) · [Skills reference](../user-guide/agents/tools-and-context#skills) · [COG-18: Skills](../protocol/cogs/COG-018) --- ## Quick Start # Getting Started The fastest way to try Covia is against a **hosted venue** — no install required. Every venue exposes the same REST API and SDK surface, so anything you do here works the same on a venue you run yourself. There are public example venues you can use right now: | Venue | Cloud | Build | |-------|-------|-------| | [venue-1.covia.ai](https://venue-1.covia.ai) | Google Cloud | Stable — latest release | | [venue-2.covia.ai](https://venue-2.covia.ai) | Google Cloud | Stable — latest release | | [venue-3.covia.ai](https://venue-3.covia.ai) | AWS | Development — latest development build | | [venue-4.covia.ai](https://venue-4.covia.ai) | Azure | Development — latest development build | | [venue-test.covia.ai](https://venue-test.covia.ai) | Google Cloud | Scratch venue for experiments — data may be cleared at any time | These docs track the development build, so the examples below use **venue-3** — they may occasionally be ahead of what the stable venues offer. Each venue's DID and stats are reported by `GET /api/v1/status`. Prefer a UI? The [Covia App](https://app.covia.ai) lets you connect to venues, run operations, and operate agents interactively; the [Covia App guide](./app/) walks through it. ## 1. Call a live venue (no install) List the operations a venue offers, then invoke one and wait for the result. `v/ops/schema/infer` derives a JSON Schema from an example value: ```bash curl https://venue-3.covia.ai/api/v1/operations curl -X POST https://venue-3.covia.ai/api/v1/invoke \ -H "Content-Type: application/json" \ -d '{ "operation": "v/ops/schema/infer", "input": { "value": { "name": "Ada", "age": 36 } }, "wait": true }' ``` The venue returns a job record whose `output` carries the result. ## 2. From your code ### TypeScript ```bash npm install @covia/covia-sdk ``` ```typescript import { Grid } from "@covia/covia-sdk"; const venue = await Grid.connect("https://venue-3.covia.ai"); const result = await venue.operations.run("v/ops/schema/infer", { value: { name: "Ada", age: 36 }, }); console.log(result); // { schema: { type: "object", ... } } venue.close(); ``` ### Python ```bash pip install covia ``` ```python from covia import Grid venue = Grid.connect("https://venue-3.covia.ai") result = venue.run("v/ops/schema/infer", {"value": {"name": "Ada", "age": 36}}) print(result) # {'schema': {'type': 'object', ...}} ``` A Java SDK (`ai.covia:covia-core`, currently built from source) is also available — see the [SDK reference](sdk/) for the full surface in every language. ## 3. Run your own venue To control local resources, install custom adapters, or hold your own data, run a venue yourself — it's a single self-contained server: ```bash docker run -p 8080:8080 ghcr.io/covia-ai/covia:latest # :latest = dev channel; use :stable for the release build ``` Point the examples above at `http://localhost:8080`. See the [Operator Guide](../operator-guide/venue-start) for configuration, persistence, and authentication. ## What's next - [Tutorials](tutorials/) — guided builds: [give Claude your own tools in 5 minutes](tutorials/claude-mcp), [run an agent against your workspace](tutorials/running-agents) - [REST API](api/) — the full HTTP surface (assets, operations, jobs, SSE) - [SDK reference](sdk/) — TypeScript, Python, and Java clients - [Agents](agents/) — build persistent, tool-using agents - [Adapters](adapters/) — LLMs, HTTP, files, orchestration, federation, and more - [MCP integration](mcp/) — use a venue as an MCP server, or call MCP tools from the Grid - [Capabilities](capabilities) — the UCAN authorisation model --- ## Covia REST API The Covia REST API provides HTTP endpoints for interacting with venues on the Grid. All venues expose a consistent API that enables clients to manage assets, invoke operations, and monitor jobs. ## Base URL The API is available at `/api/v1/` on any Covia venue. For example: ``` https://venue-3.covia.ai/api/v1/ ``` ## Authentication Authentication requirements vary by venue. See [COG-3: Authentication](/docs/protocol/cogs/COG-003) for supported authentication methods. Public venues may allow unauthenticated access to read operations, while write operations typically require authentication. ### Bearer tokens Authenticated requests carry a JWT in the standard header: ``` Authorization: Bearer ``` A venue accepts three bearer forms: a **self-issued EdDSA JWT** (signed with the caller's own Ed25519 key, identifying a `did:key` or a named venue user), a **venue-signed JWT** (issued after OAuth login), or an **external provider JWT** (verified against the provider's JWKS). See the [operator authentication guide](../../operator-guide/auth) for how venues configure these. ### Presenting UCAN capability proofs Delegated authority is presented as [UCAN](../capabilities) proof tokens alongside the caller's identity. There are three transport channels, merged by the venue: | Channel | Where it works | Form | | ------- | -------------- | ---- | | `Authorization: Bearer ` | Any request | A UCAN JWT as the bearer token itself | | `ucans` body field | `POST` requests (`/invoke`, `/run`) | JSON array of UCAN JWT strings | | `X-Covia-Ucans` header | Body-less job requests (`GET /api/v1/jobs/{id}`, SSE) | Comma-separated UCAN JWTs | The `X-Covia-Ucans` header exists because a `GET` has no body to carry the `ucans` array: it is how delegated and federated job observation presents proofs. The job-free values routes do not yet consult it, so a delegated read of another principal's path goes through the `covia:read`/`covia:list` operations with `ucans` (each a job record) until that lands. Capability enforcement is identical on every channel. ## Content Type All API requests and responses use JSON: ``` Content-Type: application/json ``` ## Endpoints ### Status #### `GET /api/v1/status` Returns venue status information including DID, available assets, and operational statistics. **Response:** ```json { "url": "https://venue-3.covia.ai", "ts": 1781255878753, "status": "OK", "name": "Covia Venue (EC2)", "did": "did:key:z6MkovQ9NpjTsbVrSaAKEX2d3zXztSnYHjNxTi5oFs8qcrwx", "stats": { "assets": 130, "users": 0, "ops": 116 } } ``` The `did` is the venue's persistent `did:key` identity, also published in its [DID document](#get-well-knowndidjson). --- ### Assets Across the API and in operation inputs (e.g. `asset:get`, the `file:write` `asset` field, grid operation references), an asset can be referenced by **bare hex hash**, by `a/`, or by `/a/`; these are equivalent. The `a/` form matches the per-user namespace convention used elsewhere (`w/`, `o/`). #### `GET /api/v1/assets` Lists the **venue-level asset catalog** (content-addressed ids). Pass `scope=own` (alias `mine`) to list the **authenticated caller's own assets** instead: the per-user `a/` namespace populated by `asset:store` and `asset:pin`, read job-free (covia 0.9.2). The venue's operation catalog is discovered via `GET /api/v1/operations`, not here. **Query Parameters:** | Parameter | Type | Description | | --------- | ---- | ----------- | | `offset` | integer | Starting index (0-based). Default: 0 | | `limit` | integer | Maximum results (max 1000). Default: all | | `scope` | string | `own` or `mine`: list the caller's own `a/` assets instead of the venue catalog | **Response:** ```json { "total": 42, "offset": 0, "limit": 10, "items": [ "0x119e30db8a4ea8b33723603743591a5f8229684e6236d89ef1966a72d7293607", "0x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b" ] } ``` #### `POST /api/v1/assets` Registers a new asset with the venue. **Request Body:** Asset metadata as JSON ```json { "name": "My Dataset", "description": "A sample dataset", "content": { "contentType": "text/csv", "sha256": "119e30db8a4ea8b33723603743591a5f8229684e6236d89ef1966a72d7293607" } } ``` **Response:** `201 Created` ```json "0x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b" ``` The response header includes `Location` pointing to the new asset. #### `GET /api/v1/assets/{ref}` Retrieves metadata for a specific asset. **Path Parameters:** | Parameter | Type | Description | | --------- | ---- | ----------- | | `ref` | string | Asset reference: a bare CAD3 hash, a content-addressed `a/` path, or another resolvable asset reference (covia 0.9.3 resolves any reference form here) | **Response:** `200 OK` Returns the asset metadata JSON as originally registered. #### `GET /api/v1/assets/{id}/content` Retrieves the content of an artifact asset. **Path Parameters:** | Parameter | Type | Description | | --------- | ---- | ----------- | | `id` | string | Asset ID (hex string) | **Query Parameters:** | Parameter | Type | Description | | --------- | ---- | ----------- | | `inline` | boolean | Set Content-Disposition to inline | **Response:** `200 OK` Returns the binary content with appropriate `Content-Type` header based on the asset metadata. #### `PUT /api/v1/assets/{id}/content` Uploads content for an artifact asset. The content hash must match the `content.sha256` value in the asset metadata. **Path Parameters:** | Parameter | Type | Description | | --------- | ---- | ----------- | | `id` | string | Asset ID (hex string) | **Request Body:** Binary content **Response:** `200 OK` ```json "0x119e30db8a4ea8b33723603743591a5f8229684e6236d89ef1966a72d7293607" ``` --- ### Invocation #### `POST /api/v1/invoke` Invokes an operation and creates a job to track execution. **Request Body:** ```json { "operation": "v/ops/http/get", "input": { "url": "https://example.com/data" } } ``` The `operation` field is a resolvable reference: - A **catalog path**: `v/ops//` (e.g. `v/ops/http/get`). The usual form; list them via `GET /api/v1/operations`. - A **user pin**: `o/` from your workspace - An **Asset ID**: `a/` or bare hex - A **DID URL**: an operation on a remote venue The short `adapter:op` style (e.g. `http:get`) is the operation's *name* as used in documentation and adapter metadata; it is **not** a resolvable reference and will be rejected. **Response:** `201 Created` ```json { "id": "0x12345678901234567890123456789012", "status": "PENDING", "created": 1706367600000, "operation": "0x7a8b9c0d..." } ``` Invocation is **asynchronous by default**: the response is the job record, and you poll `GET /api/v1/jobs/{id}` (or subscribe via `.../sse`) until the job reaches a terminal status carrying its output. The `Location` header names the job to poll. **Waiting for the result inline.** Pass `wait` (query parameter `?wait=…` or a body field) for a synchronous response: | `wait` | Behaviour | |--------|-----------| | absent / `false` | Asynchronous: `201` with a job record to poll (the default) | | `true` | Block up to the 120s cap; return the finished record with `200` if it completes | | `` | Block up to that many **milliseconds** (clamped to the 120s cap) | If the job finishes within the window you get the completed record (`200`); otherwise the current record (`201`) and you continue polling. A malformed `wait` value is rejected with `400`. The 120s cap is a server resource limit; for longer waits, poll or use SSE. ```bash # Fire-and-poll (default) curl -X POST .../api/v1/invoke -d '{"operation":"v/ops/http/get","input":{"url":"..."}}' # Wait inline, up to 30 seconds curl -X POST '.../api/v1/invoke?wait=30000' -d '{"operation":"...","input":{...}}' ``` #### `POST /api/v1/run` Runs an operation and returns its **result**, with no job handle in the response. Use this when you only want the output; use `/invoke` when you need to track, stream, pause, or cancel the execution. **Request Body:** identical to `/invoke`: `operation` (resolvable reference), optional `input`, optional `ucans` proof array. ```bash curl -X POST .../api/v1/run \ -H "Content-Type: application/json" \ -d '{"operation": "v/ops/schema/infer", "input": {"value": {"name": "Ada"}}}' ``` **Response:** `200 OK` with the operation output as the body, `400` for a malformed request, `403` if the operation is not authorised. Execution still uses the normal job lifecycle internally: mutating or unclassified operations are recorded as durable jobs, while an operation marked `readOnly: true` may run as a transient job that is never persisted. The HTTP request remains open until the operation completes, so bound long-running work with `/invoke` and polling instead. --- ### Jobs #### `GET /api/v1/jobs` Lists the caller's own job ids (the per-user `j/` namespace). **Response:** ```json [ "0x12345678901234567890123456789012", "0xabcdef01234567890abcdef012345678" ] ``` #### `GET /api/v1/jobs/{id}` Gets the current status of a job. **Path Parameters:** | Parameter | Type | Description | | --------- | ---- | ----------- | | `id` | string | Job ID | **Response:** `200 OK` ```json { "id": "0x12345678901234567890123456789012", "status": "COMPLETE", "created": 1706367600000, "updated": 1706367601000, "operation": "0x7a8b9c0d...", "output": { "result": "Success", "data": {} } } ``` **Job Status Values:** | Status | Category | Description | | ------ | -------- | ----------- | | `PENDING` | Active | Job created, waiting to execute | | `STARTED` | Active | Job is currently executing | | `COMPLETE` | Terminal | Job finished successfully with output | | `FAILED` | Terminal | Job finished with an error | | `CANCELLED` | Terminal | Job was cancelled by client or venue | | `REJECTED` | Terminal | Job was rejected before execution (e.g. policy violation) | | `PAUSED` | Interactive | Job execution suspended, awaiting resume | | `INPUT_REQUIRED` | Interactive | Job requires additional input from the client | | `AUTH_REQUIRED` | Interactive | Job requires authorisation or credentials | #### `GET /api/v1/jobs/{id}/sse` Server-Sent Events endpoint for real-time job status updates. **Response:** an SSE stream. Each event is named `job-update` and its `data` payload is the **full JSON job record** (the same shape as `GET /api/v1/jobs/{id}`), sent on every state change. When the job reaches a terminal status the final record is sent and the stream closes; subscribing to an already-terminal job yields one final frame, then close. ``` event: job-update data: {"id":"0x1234...","status":"COMPLETE","output":{...}} ``` Delegated or federated observation presents proofs via the `X-Covia-Ucans` header (see [Authentication](#presenting-ucan-capability-proofs)). Note that the browser `EventSource` API cannot set an `Authorization` header. The SDKs therefore stream over `fetch` and parse the SSE body themselves, carrying normal auth headers (TypeScript `venue.jobs.stream()`, SDK 1.9.0); plain `EventSource` clients work unauthenticated on public venues, and otherwise poll `GET /api/v1/jobs/{id}`. #### `PUT /api/v1/jobs/{id}/cancel` Cancels a running job. **Response:** `200 OK` with final job status, or `404 Not Found`. #### `PUT /api/v1/jobs/{id}/pause` Pauses a running job. Only valid when the job is in a non-terminal, non-paused state (`PENDING`, `STARTED`, `INPUT_REQUIRED`, `AUTH_REQUIRED`). **Response:** `200 OK` with updated job status, `404 Not Found`, or `409 Conflict` if the job is already finished or paused. #### `PUT /api/v1/jobs/{id}/resume` Resumes a paused job. Only valid when the job is in `PAUSED` state. The venue re-engages the adapter to continue execution. **Response:** `200 OK` with updated job status, `404 Not Found`, or `409 Conflict` if the job is not paused. #### `PUT /api/v1/jobs/{id}/delete` Deletes a job record. **Response:** `200 OK` or `404 Not Found`. --- ### Values (job-free lattice reads) Six `GET` routes read the venue's lattice directly, **without creating a job**. They exist so that dashboards, pollers, and agents can read state repeatedly without minting an audit record per read; capability enforcement is identical to the operation path. Clients that poll (such as the Covia web app) should always read through these routes and reserve `/invoke` for explicit actions. Every route takes a `path` query parameter addressing the lattice: | Prefix | Contents | Notes | | ------ | -------- | ----- | | `a/` | The caller's content-addressed assets | | | `o/` | The caller's named operation pins | | | `j/` | The caller's job records | | | `g/` | The caller's agents | e.g. `g/my-agent/status` | | `w/` | The caller's durable workspace | e.g. `w/notes`, `w/memory` | | `s/` | The caller's secret **names** | Values are never readable | | `h/` | The caller's human-in-the-loop inbox | | | `n/` | Agent-scoped notes | Requires `agent` parameter | | `t/` | Job-scoped temporary state | Requires `agent` and `task` parameters | | `c/` | Session-scoped scratch | Requires `agent` and `session` parameters | | `v/` | Venue globals: `v/ops`, `v/info`, `v/agents` | Public read | The scoping parameters accompany the virtual namespaces: `agent` is a bare agent id under the caller (or a full agent DID), `task` is the `agent:request` job id, and `session` is the session id. These routes read the caller's own namespaces; delegated reads of another user's paths currently use the `covia:read`/`covia:list` operations with `ucans` proofs. #### `GET /api/v1/values/read` Reads the literal value at a path. | Parameter | Type | Description | | --------- | ---- | ----------- | | `path` | string | **Required.** Lattice path, e.g. `w/notes`, `v/info/adapters` | | `maxSize` | integer | Byte guard (default 1,000,000): a larger value is withheld | **Response** always carries an explicit existence flag, so a stored `null` is distinguishable from an absent path: ```json { "exists": true, "value": { "theme": "dark" } } ``` | Shape | Meaning | | ----- | ------- | | `{"exists": true, "value": }` | Path has data | | `{"exists": true, "value": null}` | Path holds a stored null | | `{"exists": false, "value": null}` | Path is absent | | `{"exists": true, "value": null, "truncated": true, "size": }` | Value exceeds `maxSize`; use `slice`, `list`, or a larger guard | #### `GET /api/v1/values/list` Lists the keys and structure of a lattice node. | Parameter | Type | Description | | --------- | ---- | ----------- | | `path` | string | **Required.** Node to list, e.g. `g`, `w` | | `limit` | integer | Maximum keys to return (default 1000) | | `offset` | integer | Keys to skip (default 0) | | `fields` | string | Field projection: comma-separated subpaths (max 16) read from each listed child, returned as a `values` map of per-key `{exists, value, truncated?}` results. Keyed nodes only; applies after `limit`/`offset` | | `maxSize` | integer | Per-projected-field byte guard when `fields` is given (default 1,000,000) | The `fields` projection lets a list view fetch each child's display fields (for example `fields=status,meta/updated` over `g`) in one request instead of an N+1 fan-out. #### `GET /api/v1/values/slice` Reads a paginated slice of a lattice sequence (for example a job history or an agent timeline). | Parameter | Type | Description | | --------- | ---- | ----------- | | `path` | string | **Required.** The vector to slice, e.g. `g/my-agent/timeline` | | `offset` | integer | Starting element index (default 0) | | `limit` | integer | Maximum elements (default 100) | | `maxSize` | integer | Maximum encoded bytes of the returned page (default 1,000,000). An oversize page is a `400`: reduce `limit`. Slice returns exact values, never summaries | #### `GET /api/v1/values/inspect` Budget-controlled JSON5 render of a value: shape and sample content within a byte budget, for previewing large or unknown structures. | Parameter | Type | Description | | --------- | ---- | ----------- | | `path` | string | **Required.** Path to render, e.g. `g/my-agent` | | `budget` | integer | Render budget in bytes (default 500) | | `compact` | boolean | Compact rendering, no whitespace | #### `GET /api/v1/values/count` Fast-path count of entries below a path. | Parameter | Type | Description | | --------- | ---- | ----------- | | `path` | string | **Required.** Collection path, e.g. `j` | | `depth` | integer | Steps below the path to count at (default 1) | **Response:** `{"exists": true, "count": 1287}` #### `GET /api/v1/values/aggregate` Counts entries at a depth, optionally partitioned by a field. | Parameter | Type | Description | | --------- | ---- | ----------- | | `path` | string | **Required.** Collection path, e.g. `j` | | `depth` | integer | Steps below the path to count at (default 1) | | `groupBy` | string | Field (relative subpath) to partition by; adds a `groups` breakdown | **Response:** ```json { "exists": true, "count": 42, "groups": { "COMPLETE": { "count": 37 }, "FAILED": { "count": 5 } } } ``` --- ### Operations #### `GET /api/v1/operations` Lists all registered operations across all adapters. **Response:** ```json [ { "name": "covia:read", "description": "Read a value at any lattice path", "adapter": "covia", "input": { ... } } ] ``` #### `GET /api/v1/operations/{name}` Gets details for a specific operation by name. **Path Parameters:** | Parameter | Type | Description | | --------- | ---- | ----------- | | `name` | string | Operation name (e.g., `covia:read`, `agent:create`) | --- ### Schedules (job-free read) #### `GET /api/v1/schedules` Lists the authenticated caller's pending scheduled events, time-ordered, without creating a job (covia 0.9.2). Includes events queued by the caller's agents. Requires authentication (`401` otherwise). **Response:** an array of `{handle, op, time}` entries, where `handle` identifies the schedule for `scheduler:cancel`/`scheduler:trigger`, `op` is the target operation reference, and `time` is the next run in epoch milliseconds. --- ### Agents (job-free reads) Two `GET` routes read agent state without creating a job. Agent **actions** (create, chat, request, suspend, and the rest) remain operations under `v/ops/agent/`; see [Agent Operations](../agents/operations). #### `GET /api/v1/agents` Lists the authenticated caller's agents. Requires authentication (`401` otherwise). | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | boolean | `false` returns bare agent ids instead of the default annotated form | | `includeTerminated` | boolean | `true` includes terminated agents (hidden by default) | **Response** (default annotated form): ```json [ { "agentId": "researcher", "status": "SLEEPING", "tasks": 2 }, { "agentId": "assistant", "status": "RUNNING", "tasks": 0 } ] ``` Entries carry `agentId`, `status`, `tasks`, and `error` when present, so a list view needs no per-agent fan-out. #### `GET /api/v1/agents/{id}` Gets one of the caller's agents: the same payload as the `agent:info` operation. Requires authentication. **Response:** `200 OK` with the agent info record, or `404 Not Found` for a missing agent id. --- ### Secrets #### `GET /api/v1/secrets` Lists secret names for the authenticated user. Values are never returned. **Response:** ```json ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"] ``` #### `PUT /api/v1/secrets/{name}` Stores a secret value (encrypted per-user). **Request Body:** ```json "sk-proj-abc123..." ``` **Response:** `200 OK` #### `DELETE /api/v1/secrets/{name}` Deletes a secret. **Response:** `200 OK` or `404 Not Found`. --- ### Discovery Endpoints #### `GET /.well-known/did.json` Returns the DID document for the venue, following W3C DID specification. **Response:** `200 OK` ```json { "@context": "https://www.w3.org/ns/did/v1", "id": "did:key:z6MkovQ9NpjTsbVrSaAKEX2d3zXztSnYHjNxTi5oFs8qcrwx", "service": [ { "type": "Covia.API.v1", "serviceEndpoint": "https://venue-3.covia.ai/api/v1" } ], "verificationMethod": [ { "id": "did:key:z6MkovQ9...#z6MkovQ9...", "type": "Multikey", "controller": "did:key:z6MkovQ9...", "publicKeyMultibase": "z6MkovQ9NpjTsbVrSaAKEX2d3zXztSnYHjNxTi5oFs8qcrwx" } ], "authentication": ["did:key:z6MkovQ9...#z6MkovQ9..."] } ``` The document `id` is the venue's persistent `did:key`; the same key is also listed under `assertionMethod`, `capabilityDelegation` and `capabilityInvocation`. The venue remains reachable by `did:web:` references; this endpoint is what resolves them to the API `serviceEndpoint`. #### `GET /.well-known/mcp.json` MCP server discovery endpoint. #### `GET /.well-known/agent-card.json` A2A agent card discovery endpoint. #### `GET /a/{id}/did.json` Returns the DID document for a specific asset. #### `GET /u/{id}/did.json` Returns the DID document for a user (did:web resolution). --- ### MCP Endpoints #### `POST /mcp` MCP JSON-RPC endpoint for tool listing, tool calls, and notifications. See [Venues as MCP Servers](/docs/user-guide/mcp/venues-as-mcp-servers) for details. #### `GET /mcp` MCP SSE session establishment for server-to-client notifications. #### `DELETE /mcp` Close an MCP session. --- ### A2A Endpoint #### `POST /a2a` Agent-to-Agent JSON-RPC endpoint for federated agent operations. --- ### DLFS (WebDAV) When WebDAV is enabled (`webdav.enabled: true` in venue config), DLFS drives are accessible via standard WebDAV at `/dlfs/`: | Method | Path | Description | | ------ | ---- | ----------- | | `GET` | `/dlfs/{drive}/{path}` | Read file | | `PUT` | `/dlfs/{drive}/{path}` | Write/upload file | | `DELETE` | `/dlfs/{drive}/{path}` | Delete file | | `MKCOL` | `/dlfs/{drive}/{path}` | Create directory | | `PROPFIND` | `/dlfs/{drive}/{path}` | List directory | | `MOVE` | `/dlfs/{drive}/{path}` | Move/rename file | | `COPY` | `/dlfs/{drive}/{path}` | Copy file | | `OPTIONS` | `/dlfs/*` | WebDAV capability discovery | See [DLFS Adapter](/docs/user-guide/adapters/dlfs) for details. --- ### Documentation Endpoints | Endpoint | Description | | -------- | ----------- | | `GET /openapi` | OpenAPI 3.0 JSON schema | | `GET /swagger` | Swagger UI (interactive API docs) | | `GET /redoc` | ReDoc UI | | `GET /llms.txt` | LLM capabilities file | --- ### Authentication Endpoints #### Login | Endpoint | Description | | -------- | ----------- | | `GET /login` | Login page listing configured OAuth providers | | `GET /auth/{provider}` | Initiate OAuth login | | `GET /auth/{provider}/callback` | OAuth callback URL | #### Bearer Token ``` Authorization: Bearer ``` Supported token types: - EdDSA self-issued tokens (did:key) - Venue-signed JWTs - OAuth provider RS256 tokens See [COG-10: Authentication](/docs/protocol/cogs/COG-010) for details. #### UCAN Proofs Operations can include UCAN capability proofs: ```json { "operation": "v/ops/covia/write", "input": { "path": "w/data", "value": {...} }, "ucans": [""] } ``` See [COG-13: Agent Capabilities](/docs/protocol/cogs/COG-013) for the capability model. ## Error Responses Errors return appropriate HTTP status codes with a JSON body: ```json { "error": "Asset not found: 0x1234..." } ``` | Status | Description | | ------ | ----------- | | `400` | Bad request (invalid parameters) | | `401` | Authentication required | | `403` | Forbidden (insufficient capabilities) | | `404` | Resource not found | | `409` | Conflict (invalid state transition, e.g. pausing a finished job) | | `500` | Server error | ## Related Documentation - [COG-5: Asset Metadata](/docs/protocol/cogs/COG-005) - Metadata format specification - [COG-6: Artifacts](/docs/protocol/cogs/COG-006) - Data asset specification - [COG-7: Operations](/docs/protocol/cogs/COG-007) - Operation specification - [COG-10: Authentication](/docs/protocol/cogs/COG-010) - Authentication specification - [COG-13: Agent Capabilities](/docs/protocol/cogs/COG-013) - Capability model --- ## Covia SDK Overview The Covia SDK provides client libraries for building applications on the Grid. SDKs are available for multiple languages, enabling developers to: - Connect to venues and discover assets - Invoke operations and monitor jobs - Upload and download artifacts - Build AI-powered applications SDKs are available for major language ecosystems. | Language | Package | GitHub | Status | | -------- | ------- | ------ | ------ | | [Java](./java) | [`ai.covia:covia-core`](https://central.sonatype.com/artifact/ai.covia/covia-core) | [covia-ai/covia](https://github.com/covia-ai/covia) | Available | | [Python](./python) | `covia` | [covia-ai/covia-sdk-py](https://github.com/covia-ai/covia-sdk-py) | Available | | [TypeScript](./typescript) | `@covia/covia-sdk` | [covia-ai/covia-sdk](https://github.com/covia-ai/covia-sdk) | Available | | [Rust](./rust) | — | — | Planned | ## Core Concepts All SDKs share the same conceptual model: ### Venue A `Venue` represents a connection to a Grid node. It's your entry point for all Grid interactions. ### Asset An `Asset` represents a resource on the Grid - either an artifact (data) or an operation (compute). Assets are identified by their content-addressable Asset ID. ### Job A `Job` tracks the execution of an operation. Jobs are asynchronous and can be polled or monitored for completion. ## Quick Example Here's a simple example showing the common pattern across all SDKs: ```java // Java Venue venue = Grid.connect("did:web:venue-3.covia.ai"); Object result = venue.run("v/ops/schema/infer", Map.of("value", Map.of("name", "Ada"))); ``` ```python # Python from covia import Grid with Grid.connect("did:web:venue-3.covia.ai") as venue: result = venue.run("v/ops/schema/infer", {"value": {"name": "Ada"}}) ``` ```typescript // TypeScript const venue = await Grid.connect("did:web:venue-3.covia.ai"); const result = await venue.operations.run("v/ops/schema/infer", { value: { name: "Ada" } }); ``` ```rust // Rust (planned) let venue = Grid::connect("did:web:venue-3.covia.ai").await?; let result = venue.run("v/ops/schema/infer", json!({"value": {"name": "Ada"}})).await?; ``` ## Choosing an SDK - **Java**: Reference implementation. Best for enterprise applications, JVM-based systems, and when you need the full feature set - **Python**: Ideal for data science, ML pipelines, scripting, and rapid prototyping. Full sync and async support - **TypeScript**: Perfect for web applications, Node.js backends, and full-stack development - **Rust**: For performance-critical systems, embedded applications, and when you need zero-overhead abstractions ## Direct API Access If an SDK isn't available for your language, you can use the [REST API](../api) directly. The API is simple and well-documented, making it straightforward to build your own client. --- ## Java SDK The Java SDK provides the reference implementation for Covia Grid clients, offering full access to all Grid functionality. ## Installation The Java SDK is published to [Maven Central](https://central.sonatype.com/artifact/ai.covia/covia-core) as `ai.covia:covia-core` (requires Java 21+): ### Maven ```xml ai.covia covia-core 0.9.3 ``` ### Gradle ```groovy implementation 'ai.covia:covia-core:0.9.3' ``` ### Development snapshots To track unreleased work, build from source and install into your local Maven repository, then depend on the snapshot version from the repo's root `pom.xml` (currently `0.9.4-SNAPSHOT` on `develop`; Gradle users also need `repositories { mavenLocal() }`): ```bash git clone https://github.com/covia-ai/covia.git cd covia mvn clean install -DskipTests ``` ## Quick Start ### Connecting to a Venue ```java import covia.grid.Grid; import covia.grid.Venue; // Connect using a DID Venue venueByDid = Grid.connect("did:web:venue-3.covia.ai"); // Or connect directly to a URL Venue venueByUrl = Grid.connect("https://venue-3.covia.ai"); ``` ### Discovering Assets ```java import covia.grid.Asset; import java.util.List; // List all assets List assets = venue.getAssets(); // Get a specific asset by ID Asset asset = venue.getAsset("0x119e30db8a4ea8b33723603743591a5f8229684e6236d89ef1966a72d7293607"); // Get asset metadata String name = asset.meta().get("name").toString(); String description = asset.meta().get("description").toString(); ``` ### Invoking Operations ```java import covia.grid.Job; import convex.core.data.ACell; import convex.core.data.Maps; // Find an operation Asset operation = venue.getAsset("0x7a8b9c0d..."); // Invoke with input parameters CompletableFuture future = operation.invoke(Maps.of( "url", "https://example.com/data", "format", "json" )); // Wait for completion Job job = future.get(); // Check result if (job.isComplete()) { ACell output = job.getOutput(); System.out.println("Result: " + output); } else if (job.isFailed()) { System.err.println("Error: " + job.getErrorMessage()); } ``` ### Synchronous Execution For simpler use cases, use the `run()` method for synchronous execution: ```java // Run operation and get result directly ACell result = operation.run(Maps.of("query", "hello world")); ``` ### Working with Artifacts ```java import covia.grid.AContent; // Get artifact content AContent content = asset.getContent(); // Access content as bytes byte[] data = content.getBytes(); // Or as a stream InputStream stream = content.getInputStream(); ``` ### Uploading Assets ```java import convex.core.data.Hash; // Create asset metadata String metadata = """ { "name": "My Dataset", "description": "Sample data for testing", "content": { "contentType": "text/csv", "sha256": "119e30db8a4ea8b33723603743591a5f8229684e6236d89ef1966a72d7293607" } } """; // Register the asset Hash assetId = venue.addAsset(metadata); // Upload content byte[] content = Files.readAllBytes(Path.of("data.csv")); venue.putContent(assetId, content); ``` ## Job Monitoring ### Polling ```java Job job = operation.invoke(input).get(); while (!job.isFinished()) { Thread.sleep(100); job = venue.getJob(job.getId()); } ``` ### Asynchronous Callbacks ```java operation.invoke(input).thenAccept(job -> { System.out.println("Job completed: " + job.getOutput()); }).exceptionally(e -> { System.err.println("Job failed: " + e.getMessage()); return null; }); ``` ## Error Handling ```java try { Job job = operation.invoke(input).get(); if (job.isFailed()) { String error = job.getErrorMessage(); // Handle operation-level error } } catch (ExecutionException e) { // Handle connection or protocol errors Throwable cause = e.getCause(); if (cause instanceof IOException) { // Network error } } ``` ## Configuration ### Custom HTTP Client ```java import covia.grid.VenueHTTP; // Create and configure VenueHTTP venue = VenueHTTP.create("https://venue-3.covia.ai"); venue.setTimeout(Duration.ofSeconds(30).toMillis()); ``` ### Authentication ```java import covia.grid.VenueAuth; // Bearer token Venue withToken = Grid.connect("did:web:venue-3.covia.ai", VenueAuth.bearer("your-api-token")); // Ed25519 key pair (self-issued JWT) Venue withKey = Grid.connect("did:web:venue-3.covia.ai", VenueAuth.keyPair(keyPair)); // Local venue (no auth needed) Venue local = Grid.connect("http://localhost:8080", VenueAuth.local()); // No authentication Venue anonymous = Grid.connect("https://public-venue.covia.ai", VenueAuth.none()); ``` ## Related Documentation - [API Reference](../api) - REST API documentation - [COG-7: Operations](/docs/protocol/cogs/COG-007) - Operation specification --- ## Python SDK The Python SDK provides a Pythonic interface to the Covia Grid with full type safety, sync and async support, and idiomatic error handling. - **Package:** `covia` - **Python:** 3.11+ - **License:** Apache-2.0 - **Source:** [covia-ai/covia-sdk-py](https://github.com/covia-ai/covia-sdk-py) ## Installation ```bash pip install covia ``` For development: ```bash pip install covia[dev] ``` ## Quick Start ### Connecting to a Venue ```python from covia import Grid # Connect using a URL venue = Grid.connect("https://venue-3.covia.ai") # Connect using a DID venue = Grid.connect("did:web:venue-3.covia.ai") # Use as a context manager for automatic cleanup with Grid.connect("https://venue-3.covia.ai") as venue: status = venue.status() print(status.name) ``` ### Invoking Operations ```python with Grid.connect("https://venue-3.covia.ai") as venue: # Invoke and get a Job for tracking job = venue.invoke("my-operation", {"prompt": "hello"}) job.wait(timeout=60) print(job.output) # Or use run() for synchronous one-shot execution result = venue.run("my-operation", {"prompt": "hello"}, timeout=60) ``` ### Discovering Assets ```python # List all assets assets = venue.list_assets() print(f"Total: {assets.total}") # With pagination assets = venue.list_assets(offset=10, limit=20) # Get a specific asset by ID asset = venue.get_asset("abc123def456...") print(asset.name) print(asset.description) print(asset.metadata) ``` ### Working with Asset Content ```python # Download binary content content = venue.get_asset_content("abc123def456...") # Upload content content_hash = venue.put_asset_content("abc123def456...", b"file contents") # Or use the Asset object directly asset = venue.get_asset("abc123def456...") content = asset.get_content() asset.put_content(b"updated content") ``` ### Registering Assets ```python from covia import Asset # Register an Asset object (recommended) asset = venue.register( Asset({ "name": "My Dataset", "description": "Sample data for analysis", "content": {"contentType": "text/csv"}, }) ) print(asset.id) # server-assigned content-addressed ID print(asset.name) # "My Dataset" # Or pass a plain dict asset = venue.register({ "name": "My Dataset", "description": "Sample data for analysis", }) ``` ## Authentication The SDK supports pluggable authentication via the `auth` parameter on `Grid.connect()`. ### Bearer Token ```python from covia import Grid from covia.auth import BearerAuth venue = Grid.connect( "https://venue-3.covia.ai", auth=BearerAuth("your-token-here") ) ``` ### HTTP Basic ```python from covia.auth import BasicAuth venue = Grid.connect( "https://venue-3.covia.ai", auth=BasicAuth("username", "password") ) ``` ### No Authentication ```python from covia.auth import NoAuth venue = Grid.connect("https://venue-3.covia.ai", auth=NoAuth()) ``` This is the default when `auth` is not specified. ### Ed25519 (Self-Issued JWT) Authenticate with a self-issued EdDSA JWT using an Ed25519 key pair. This is the recommended method for programmatic access and agent-to-venue communication. ```python from covia.auth import Ed25519Auth # Generate a new key pair auth = Ed25519Auth.generate(audience="https://venue-3.covia.ai") print(auth.did) # did:key:z6Mk... # Or create from an existing seed (deterministic) auth = Ed25519Auth.from_seed( seed=b"32-byte-seed-value-here-12345678", audience="https://venue-3.covia.ai" ) venue = Grid.connect("https://venue-3.covia.ai", auth=auth) ``` The `audience` is automatically set from the venue URL when using `Grid.connect()`. Tokens are short-lived and refreshed automatically. Requires the `signing` extra: ```bash pip install covia[signing] ``` ### Custom Authentication Implement the `Auth` interface for custom schemes (OAuth 2.0, etc.): ```python from covia.auth import Auth class OAuth2Auth(Auth): def __init__(self, access_token: str): self._token = access_token def apply(self, headers: dict[str, str]) -> None: headers["Authorization"] = f"Bearer {self._token}" def __repr__(self) -> str: return "OAuth2Auth(***)" ``` ## Job Lifecycle Jobs represent the execution of an operation. They progress through a lifecycle: ``` PENDING → STARTED → COMPLETE | FAILED | CANCELLED | REJECTED ``` Jobs may also enter interactive states: `PAUSED`, `INPUT_REQUIRED`, `AUTH_REQUIRED`. There is no framework timeout: the venue never emits a `TIMEOUT` status, and a job may run until it completes, fails, or is cancelled. Apply timeouts client-side (for example the `timeout` argument to `job.wait()`). ### Tracking Jobs ```python job = venue.invoke("my-operation", {"x": 1}) # Check status print(job.status) # JobStatus.PENDING print(job.is_finished) # False print(job.is_complete) # False # Poll for latest status job.refresh() # Wait for completion with exponential backoff job.wait(timeout=120) # Access the result if job.is_complete: print(job.output) else: print(job.error) ``` ### Convenience Methods ```python # wait + output in one call output = job.result(timeout=60) # invoke + wait + output in one call output = venue.run("my-operation", {"x": 1}, timeout=60) ``` ### Cancelling Jobs ```python job = venue.invoke("long-running-op", {"data": "..."}) # ... later job.cancel() ``` ### SSE Streaming ```python for event in job.stream(): print(f"Event: {event.event}, Data: {event.data}") ``` ### Managing Jobs ```python # List all job IDs job_ids = venue.list_jobs() # Get a specific job job = venue.get_job("job-id-here") # Delete a job record venue.delete_job("job-id-here") ``` ## Operations Operations are named capabilities exposed by a venue. ```python # List all available operations operations = venue.list_operations() for op in operations: print(f"{op.name}: {op.description}") # Get details of a specific operation op = venue.get_operation("v/ops/schema/infer") # Invoke by operation name job = venue.invoke("v/ops/schema/infer", {"value": {"name": "Ada", "age": 36}}) # Invoke by asset ID job = venue.invoke("abc123def456...") # Invoke by DID URL job = venue.invoke("did:key:z6Mk.../a/abc123def456...") ``` ## Agents, Secrets, Workspace & UCANs The venue exposes typed managers for the `v/ops/*` operations as lazy properties: `venue.agents`, `venue.secrets`, `venue.workspace`, `venue.ucan`. ### Agents ```python # Create (or update in place with overwrite=True) venue.agents.create("Alice", config={ "systemPrompt": "You are a helpful assistant.", "model": "gpt-5.4-mini", "tools": ["v/ops/covia/read", "v/ops/covia/list"], }, overwrite=True) # Session-scoped chat — omit session_id on the first call, then reuse it reply = venue.agents.chat("Alice", "Hi Alice") follow = venue.agents.chat("Alice", "And the next step?", session_id=reply.sessionId) # Task request (wait=True blocks for the result) result = venue.agents.request("Alice", {"question": "Summarise the records"}, wait=True) # Inspection and lifecycle info = venue.agents.info("Alice") # lightweight summary: status, config, timelineLength, task count venue.agents.suspend("Alice") venue.agents.resume("Alice") venue.agents.delete("Alice") ``` ### Secrets ```python venue.secrets.set("OPENAI_API_KEY", "sk-...") names = venue.secrets.list() # extract requires a UCAN capability grant value = venue.secrets.extract("OPENAI_API_KEY").value venue.secrets.delete("OPENAI_API_KEY") ``` ### Workspace (lattice) ```python venue.workspace.write("w/config", {"theme": "dark"}) result = venue.workspace.read("w/config") print(result.value) # {"theme": "dark"} listing = venue.workspace.list("w/") # keys / values under a path venue.workspace.append("w/events", {"type": "invoice_received"}) venue.workspace.slice("w/events", offset=0, limit=10) venue.workspace.delete("w/config") ``` Paths resolve against the caller's own DID; see [Capabilities](../capabilities) for the cross-user access model. ### UCAN delegation ```python from covia import UCANAttenuation result = venue.ucan.issue( "did:key:z6MkBob...", # audience (delegatee) DID [UCANAttenuation(with_="did:key:z6MkAlice.../w/shared", can="crud/read")], expiry=2_000_000_000, # unix seconds ) token = result["token"] ``` ## Discovery Venues expose standard discovery endpoints for interoperability. ```python # DID Document (W3C Decentralized Identifier) doc = venue.did_document() print(doc.id) # "did:key:z6MkovQ9..." print(doc.context) # "https://www.w3.org/ns/did/v1" # Cached DID (fetched once, then cached) did = venue.did # "did:key:z6MkovQ9..." # MCP Discovery (Model Context Protocol) mcp = venue.mcp_discovery() print(mcp.mcp_version) # A2A Agent Card card = venue.agent_card() print(card.name, card.provider) # A2A v1.0 fields: name, provider, capabilities, skills, ... ``` ## Async API The SDK provides a full async mirror of the sync API via `covia.async_api`. ```python import asyncio from covia.async_api import AsyncGrid async def main(): async with AsyncGrid.connect("https://venue-3.covia.ai") as venue: # All methods are awaitable status = await venue.status() result = await venue.run("my-operation", {"prompt": "hello"}) print(result) asyncio.run(main()) ``` :::note[Async property differences] Some sync properties become async methods. For example, `venue.did` (sync) becomes `await venue.get_did()` (async). ::: ### Concurrent Operations ```python async def run_parallel(): async with AsyncGrid.connect("https://venue-3.covia.ai") as venue: # Launch multiple operations concurrently jobs = await asyncio.gather( venue.invoke("op-a", {"x": 1}), venue.invoke("op-b", {"x": 2}), venue.invoke("op-c", {"x": 3}), ) # Wait for all results results = await asyncio.gather( *[job.result(timeout=60) for job in jobs] ) return results ``` ### Async Authentication Authentication works identically with the async API: ```python from covia.async_api import AsyncGrid from covia.auth import BearerAuth async with AsyncGrid.connect( "https://venue-3.covia.ai", auth=BearerAuth("token") ) as venue: ... ``` ## Error Handling The SDK provides a structured exception hierarchy: ``` CoviaError # Base for all SDK errors ├── GridError # HTTP error responses (4xx/5xx) │ └── NotFoundError # 404 responses │ ├── AssetNotFoundError # Asset not found │ └── JobNotFoundError # Job not found ├── CoviaConnectionError # Connection failures (also: ConnectionError) ├── CoviaTimeoutError # Timeouts (also: TimeoutError) └── JobFailedError # Job finished with non-COMPLETE status ``` `CoviaConnectionError` and `CoviaTimeoutError` also subclass Python's built-in `ConnectionError` and `TimeoutError`, so you can catch them idiomatically: ```python from covia import ( Grid, CoviaError, GridError, NotFoundError, AssetNotFoundError, JobNotFoundError, CoviaConnectionError, CoviaTimeoutError, JobFailedError, ) try: with Grid.connect("https://venue-3.covia.ai") as venue: result = venue.run("my-operation", {"x": 1}, timeout=30) except AssetNotFoundError as e: print(f"Asset not found: {e.asset_id}") except JobNotFoundError as e: print(f"Job not found: {e.job_id}") except NotFoundError: print("Resource not found") except GridError as e: print(f"API error {e.status_code}: {e.message}") except JobFailedError as e: print(f"Job failed: {e.job_data.error}") except CoviaConnectionError: print("Cannot connect to venue") except CoviaTimeoutError: print("Operation timed out") # Or catch all SDK errors except CoviaError: print("Something went wrong") # Or use Python built-in exception types except TimeoutError: print("Timed out") except ConnectionError: print("Connection failed") ``` ## Configuration ### Timeouts ```python # Custom timeout (applies to connect, read, and write) venue = Grid.connect("https://venue-3.covia.ai", timeout=60) ``` ### Custom Headers ```python venue = Grid.connect( "https://venue-3.covia.ai", headers={"X-Request-ID": "abc123"} ) ``` ### Combining Options ```python from covia.auth import BearerAuth venue = Grid.connect( "did:web:venue-3.covia.ai", timeout=30, headers={"X-Tenant": "acme"}, auth=BearerAuth("token"), ) ``` ## Logging The SDK uses Python's standard `logging` module. By default, no output is produced (the library registers a `NullHandler`). Enable debug logging to trace HTTP requests, job polling, and DID resolution: ```python import logging logging.basicConfig(level=logging.DEBUG) ``` Or target just the SDK: ```python import logging logging.getLogger("covia").setLevel(logging.DEBUG) handler = logging.StreamHandler() handler.setFormatter(logging.Formatter("%(name)s %(levelname)s %(message)s")) logging.getLogger("covia").addHandler(handler) ``` ### Log Levels | Level | What's Logged | |-------|---------------| | `DEBUG` | HTTP requests/responses, job polling cycles, DID resolution, connections | | `WARNING` | Plain HTTP connections (no TLS) | ## Module Structure ``` covia ├── Grid # Entry point — Grid.connect() ├── Venue # Venue interaction; typed managers: venue.agents, │ # venue.secrets, venue.workspace, venue.ucan ├── Job # Job lifecycle (wait, result, cancel, stream) ├── Asset # Asset metadata and content ├── JobStatus # Status enum (PENDING, STARTED, COMPLETE, ...) ├── auth # Auth, NoAuth, BearerAuth, BasicAuth, Ed25519Auth ├── exceptions # CoviaError, GridError, NotFoundError, ... ├── models # Pydantic data models (VenueStatus, JobData, ...) └── async_api # Full async mirror ├── AsyncGrid ├── AsyncVenue └── AsyncJob ``` ## Related Documentation - [SDK Overview](./) - Comparison of all Covia SDKs - [REST API Reference](../api) - Direct HTTP API documentation - [COG-003: Authentication](/docs/protocol/cogs/COG-003) - Authentication specification - [COG-007: Operations](/docs/protocol/cogs/COG-007) - Operation specification --- ## Rust SDK :::info[Planned] The Rust SDK is on the roadmap but has not yet been implemented. No package is available. ::: A Rust SDK for the Covia Grid is planned with the following design goals: - **Async-first** with Tokio runtime support - **Zero-copy** deserialisation where possible - **Builder pattern** for configuration - **Strong typing** with Rust's type system ## In the Meantime Use the [REST API](../api) directly from Rust with `reqwest` or any HTTP client. The API is straightforward and well-documented. ## Related Documentation - [SDK Overview](./) — comparison of all Covia SDKs - [REST API Reference](../api) — direct HTTP API documentation --- ## TypeScript SDK The TypeScript SDK provides a fully-typed client for the Covia Grid, with manager classes for agents, workspace, assets, jobs, secrets, and UCAN capabilities. - **Package:** `@covia/covia-sdk` - **Node.js:** 18+ - **License:** Apache-2.0 - **Source:** [covia-ai/covia-sdk](https://github.com/covia-ai/covia-sdk) ## Installation ```bash npm install @covia/covia-sdk ``` ## Quick Start ### Connecting to a Venue ```typescript import { Grid, BearerAuth, Ed25519Auth } from "@covia/covia-sdk"; // Connect using a URL const venueByUrl = await Grid.connect("https://venue-3.covia.ai"); // Connect using a DID const venueByDid = await Grid.connect("did:web:venue-3.covia.ai"); // With bearer token const venueWithToken = await Grid.connect("https://venue-3.covia.ai", new BearerAuth("your-token") ); // With Ed25519 key pair (self-issued JWT) const auth = Ed25519Auth.generate(); console.log(`Your DID: ${auth.getDID()}`); const venueWithKey = await Grid.connect("https://venue-3.covia.ai", auth); ``` `Grid.connect()` caches venue connections — calling it twice with the same ID returns the same instance. ### Running Operations ```typescript // Run synchronously (invoke + wait + return output) const result = await venue.operations.run("v/ops/schema/infer", { value: { name: "Ada", age: 36, admin: true }, }); console.log(result); // Invoke asynchronously const job = await venue.operations.invoke("v/ops/langchain/openai", { prompt: "Summarise this document..." }); const output = await job.result({ timeout: 60000 }); ``` ### Venue Properties ```typescript const status = await venue.status(); console.log(status.did); // "did:key:z6MkovQ9..." console.log(status.name); // "Covia Venue (EC2)" console.log(status.stats); // { assets: 130, users: 0, ops: 116 } ``` ## Managers The venue exposes manager classes for each domain. They are lazy-loaded on first access. ```typescript venue.operations // OperationManager venue.agents // AgentManager venue.workspace // WorkspaceManager venue.assets // AssetManager venue.jobs // JobManager venue.secrets // SecretManager venue.ucan // UCANManager ``` ### Operations ```typescript // List all operations const ops = await venue.operations.list(); for (const op of ops) { console.log(`${op.name}: ${op.description}`); } // Run by name const result = await venue.operations.run("v/ops/covia/read", { path: "w/data" }); // Invoke and track job const job = await venue.operations.invoke("v/ops/agent/request", { agentId: "Alice", input: { question: "What vendors are overdue?" }, wait: true }); ``` ### Agents ```typescript // Create await venue.agents.create({ agentId: "Alice", config: { systemPrompt: "You are a helpful assistant.", model: "gpt-5.4-mini", tools: ["v/ops/covia/read", "v/ops/covia/list"] } }); // Send a task and wait const response = await venue.agents.request("Alice", { question: "Summarise the vendor records" }, true // wait ); console.log(response.output); // Session-scoped chat — start a session const first = await venue.agents.chat("Alice", "Hi Alice"); const sessionId = first.sessionId; console.log(first.response); // Continue the same session const follow = await venue.agents.chat("Alice", "And the next step?", sessionId); console.log(follow.response); // Send a notification await venue.agents.message("Alice", { event: "new-invoice" }); // Query state const info = await venue.agents.info("Alice"); console.log(info.status); // "SLEEPING" // List all agents const list = await venue.agents.list(); // Lifecycle await venue.agents.suspend("Alice"); await venue.agents.resume("Alice"); await venue.agents.delete("Alice"); ``` ### Workspace ```typescript // Write await venue.workspace.write("w/config", { theme: "dark", lang: "en" }); // Read const result = await venue.workspace.read("w/config"); console.log(result.value); // { theme: "dark", lang: "en" } // List keys const list = await venue.workspace.list("w/", 100, 0); console.log(list.keys); // ["config", "vendor-records", ...] // Append to a vector await venue.workspace.append("w/events", { type: "invoice_received" }); // Paginate const slice = await venue.workspace.slice("w/events", 0, 10); // Delete await venue.workspace.delete("w/config"); ``` ### Assets ```typescript // List const assets = await venue.assets.list({ limit: 50, offset: 0 }); console.log(`Total: ${assets.total}`); // Get (returns Operation or DataAsset) const asset = await venue.assets.get("0x1234..."); const meta = await asset.getMetadata(); // Register const newAsset = await venue.assets.register({ name: "My Dataset", description: "Sample data", content: { contentType: "text/csv" } }); // Upload content const blob = new Blob(["col1,col2\na,b"], { type: "text/csv" }); await asset.putContent(blob); // Download content const stream = await asset.getContent(); ``` ### Jobs ```typescript // List job IDs const jobIds = await venue.jobs.list(); // Get a job const job = await venue.jobs.get("0x1234..."); // Status checks job.isFinished; // terminal state? job.isComplete; // COMPLETE? job.isPaused; // PAUSED, INPUT_REQUIRED, or AUTH_REQUIRED? // Wait for result const output = await job.result({ timeout: 30000 }); // Lifecycle await job.pause(); await job.resume(); await job.cancel(); await job.delete(); // Stream SSE events for await (const event of job.stream()) { console.log(event.event, event.json()); } // Send message to a running job await job.sendMessage({ action: "continue" }); ``` ### Secrets ```typescript // Store await venue.secrets.set("OPENAI_API_KEY", "sk-..."); // List names const names = await venue.secrets.list(); // Delete await venue.secrets.delete("OPENAI_API_KEY"); ``` ### UCAN ```typescript // Issue a capability token const token = await venue.ucan.issue( "did:key:z6Mk...", // audience DID [{ with: "w/data/", can: "crud/read" }], // attenuations Math.floor(Date.now() / 1000) + 3600 // expiry (1 hour, unix seconds) ); ``` ## Authentication ### NoAuth (Default) ```typescript import { NoAuth } from "@covia/covia-sdk"; const venue = await Grid.connect("https://public-venue.covia.ai", new NoAuth()); ``` ### BearerAuth ```typescript import { BearerAuth } from "@covia/covia-sdk"; const venue = await Grid.connect("https://venue.covia.ai", new BearerAuth("your-api-token") ); ``` ### Ed25519Auth (Ed25519 Self-Issued JWT) ```typescript import { Ed25519Auth } from "@covia/covia-sdk"; // Generate a new key pair const auth = Ed25519Auth.generate(); console.log(auth.getDID()); // did:key:z6Mk... console.log(auth.getPublicKey()); // Uint8Array // Or from an existing private key (hex) const authFromKey = Ed25519Auth.fromHex("abcdef0123456789..."); const venue = await Grid.connect("https://venue.covia.ai", auth); ``` Tokens are generated fresh per request with a configurable lifetime (default: 300 seconds). > **Renamed:** this provider was previously `KeyPairAuth`. That name still works as a deprecated alias, but new code should use `Ed25519Auth` (matching the Python SDK). ## Job Lifecycle Jobs progress through a state machine: ``` PENDING → STARTED → COMPLETE | FAILED | CANCELLED | REJECTED ``` `JobStatus.TIMEOUT` exists in the SDK enum but is reserved: the venue never emits it. There is no framework timeout; a job may run until it completes, fails, or is cancelled, so apply time bounds client-side (for example the `timeout` option to `job.wait()`). Jobs may also enter interactive states: `PAUSED`, `INPUT_REQUIRED`, `AUTH_REQUIRED`. ```typescript import { RunStatus } from "@covia/covia-sdk"; const job = await venue.operations.invoke("long:task", input); await job.wait({ timeout: 120000 }); if (job.metadata.status === RunStatus.COMPLETE) { console.log(job.output); } ``` Polling uses exponential backoff (300ms initial, 1.5x multiplier, 10s cap). ## Error Handling ```typescript import { CoviaError, GridError, NotFoundError, AssetNotFoundError, JobNotFoundError, CoviaConnectionError, CoviaTimeoutError, JobFailedError } from "@covia/covia-sdk"; try { const result = await venue.operations.run("my-op", input); } catch (error) { if (error instanceof AssetNotFoundError) { console.error(`Asset not found: ${error.assetId}`); } else if (error instanceof JobFailedError) { console.error(`Job failed: ${error.jobData.error}`); } else if (error instanceof GridError) { console.error(`API error ${error.statusCode}: ${error.message}`); } else if (error instanceof CoviaTimeoutError) { console.error("Operation timed out"); } else if (error instanceof CoviaConnectionError) { console.error("Cannot connect to venue"); } } ``` ## Discovery ```typescript // DID Document const doc = await venue.didDocument(); console.log(doc.id); // "did:key:z6MkovQ9..." // MCP Discovery const mcp = await venue.mcpDiscovery(); // A2A Agent Card const card = await venue.agentCard(); ``` ## Logging ```typescript import { logger } from "@covia/covia-sdk"; // Enable debug logging logger.level = "debug"; // Custom handler logger.handler = (level, message) => { myLogger.log(level, message); }; ``` ## Cleanup ```typescript // Manual cleanup venue.close(); // Or use the Disposable protocol (TypeScript 5.2+) { using venue = await Grid.connect("https://venue.covia.ai"); // venue.close() called automatically at end of block } ``` ## Related Documentation - [SDK Overview](./) — comparison of all Covia SDKs - [REST API Reference](../api) — direct HTTP API documentation - [Agents](/docs/user-guide/agents/) — agent system documentation --- ## Bring your own agent You don't have to rebuild an agent to use it on Covia. If you already run one — in Agentforce, Lyzr, Hermes, or any other framework — you can **connect it to a venue** and task it like a native agent: it becomes discoverable, every call is a governed job with a receipt, and it can take part in workflows across the grid. The on-ramp is **A2A** (the [Agent-to-Agent protocol](https://a2a-protocol.org/)), now a Linux Foundation standard backed by 150+ organisations. Covia is already an A2A *client* — you **import** the remote agent once, then send it work. See the [A2A adapter](../adapters/covia-with-a2a) for the full operation reference. ## Three steps ### 1. Store the agent's credential The remote agent authenticates you with its own credential — an API key or a bearer token. Keep it in your venue's encrypted secret store, referenced by name and never inlined into a call: ```bash curl -X PUT https://venue-3.covia.ai/api/v1/secrets/PARTNER_KEY \ -H "Authorization: Bearer " -H "Content-Type: application/json" \ -d '{"value": ""}' ``` ### 2. Import the agent `import-agent` fetches the remote **Agent Card** (`/.well-known/agent-card.json`), validates the endpoint, and writes an immutable `type:a2a-agent` asset bound at `w/a2a/agents/`. Auth is attached here, once — as a `s/NAME` reference: ```bash curl -X POST https://venue-3.covia.ai/api/v1/invoke \ -H "Authorization: Bearer " -H "Content-Type: application/json" \ -d '{"operation": "v/ops/a2a/import-agent", "input": {"name": "partner-bot", "url": "https://agent.example.com", "auth": {"scheme": "bearer", "secret": "s/PARTNER_KEY"}}}' ``` Use `auth: {scheme: "", secret: "s/NAME"}` for the API-key or HTTP-Bearer scheme the agent card declares, or `auth: {kind: "bearer", secret: "s/NAME"}` when a bearer is needed even to fetch a private card. Literal credentials are rejected — only the reference is stored. ### 3. Send it work Task the binding. One Covia **Job mirrors one remote A2A Task**: the adapter polls the remote to completion, cancelling the Job sends a best-effort cancel, and an `input-required` / `auth-required` turn surfaces on the Job so you can respond. Reattach a long-running task with `v/ops/a2a/get-task`. ```bash curl -X POST https://venue-3.covia.ai/api/v1/invoke \ -H "Authorization: Bearer " -H "Content-Type: application/json" \ -d '{"operation": "v/ops/a2a/send", "input": {"agent": "w/a2a/agents/partner-bot", "message": {"role": "user", "parts": [{"type": "text", "text": "Summarise Q1 revenue"}]}}}' ``` TypeScript: `await venue.secrets.set("PARTNER_KEY", key)`, then `await venue.operations.run("v/ops/a2a/import-agent", { name, url, auth })`, then `await venue.operations.run("v/ops/a2a/send", { agent, message })`. Once imported, an agent can also be loaded through the `a2a` skill, so a skilled agent can discover and task it in plain language. ## Your framework Most agent platforms now speak A2A, so "can I bring it in?" is increasingly "yes". A few specifics: | Framework | A2A | How to point Covia at it | | --- | --- | --- | | **Agentforce** (Salesforce) | Native — Salesforce co-created A2A and contributed the Agent Card | Import its agent URL with the scheme the card declares | | **Lyzr** | Native, in Lyzr Agent Studio | Import; the credential is the agent's API key | | **Hermes** (Nous Research) | Native A2A plugin, both directions | Expose Hermes with a bearer and an explicit `A2A_HOST`, store the bearer as `s/NAME`, then import | | **OpenClaw** | Local-first gateway; A2A not built in | Front its gateway with a thin A2A facade (an `a2a-sdk` wrapper), then import — its self-hosted design makes this straightforward | For a framework that exposes only tools (not an agent), bridge those instead — see [Connect your tools](../connect-your-tools) (MCP) — or wrap a plain HTTP API as an operation. ## What you get - **Governed** — every interaction is a job with a verifiable receipt on your venue; the imported agent acts under your own capabilities toward the remote. - **Discoverable and composable** — it is an ordinary asset; other agents and workflows can reach it. - **Resumable** — tasks have no framework timeout; reattach by task id. - **Contained** — the remote's replies are treated as untrusted data; the credential never leaves your secret store, and Covia UCAN proofs are not forwarded outbound, so cross-organisation authorisation rests on the remote's own auth. ## Related - [A2A adapter](../adapters/covia-with-a2a) — the full inbound and outbound reference - [Connect your tools](../connect-your-tools) — bringing a framework's *tools* in over MCP - [Agents overview](../../overview/agents) — the three doors: hosted, bring-your-own-agent, bring-your-own-model --- ## Creating Agents Agents are created with the `agent:create` operation. The key decision is **what to put in the config** — this determines the agent's personality, tools, capabilities, and LLM backend. ## Minimal Agent The simplest possible agent has just an ID: ```json { "operation": "v/ops/agent/create", "input": { "agentId": "my-agent" } } ``` This creates an agent with default settings: `llmagent:chat` transition, `langchain:openai` backend, `gpt-5.4-mini` model, and the platform's default tool set. It will respond conversationally to any task. ## Configuration The `config` field controls agent behaviour. All fields are optional — defaults are sensible for general-purpose agents. ```json { "operation": "v/ops/agent/create", "input": { "agentId": "Bob", "config": { "operation": "v/ops/goaltree/chat", "systemPrompt": "You are Bob, an AP Data Enricher...", "model": "gpt-5.4-mini", "llmOperation": "v/ops/langchain/openai", "tools": [ "v/ops/covia/read", "v/ops/covia/write", "v/ops/covia/list" ], "defaultTools": false, "caps": [ { "with": "w/vendor-records/", "can": "crud/read" }, { "with": "w/enrichments/", "can": "crud" } ], "context": [ { "ref": "w/docs/data-guide", "label": "AP Data Guide" } ], "outputs": { "complete": { "schema": { "type": "object", "properties": { "enrichedData": { "type": "object" }, "confidence": { "type": "number" } }, "required": ["enrichedData", "confidence"] } } } } } } ``` ### Config Reference | Field | Type | Default | Description | |-------|------|---------|-------------| | `operation` | string | `v/ops/llmagent/chat` | Level 2 transition function. Use `v/ops/goaltree/chat` for goal tree. | | `systemPrompt` | string | Generic assistant | The system message defining the agent's role and instructions. | | `model` | string | `gpt-5.4-mini` | LLM model name passed to the backend. | | `llmOperation` | string | `v/ops/langchain/openai` | Level 3 LLM operation. See [LLM Backends](./llm-backends). | | `tools` | array | — | Operations the agent can call. Strings or `{operation, name, description}` maps. | | `defaultTools` | boolean | `true` | Include the platform's default tools (workspace CRUD, agent management, assets, schemas). Set `false` for explicit control. | | `caps` | array | — | Capability restrictions. Each entry: `{with: "path/", can: "crud/read"}`. | | `context` | array | — | Lattice paths to load into context every turn. | | `skills` | array | — | Skill sources (`w/skills`, `v/skills`, `a/`). Injects the skills index and offers `skill_load`. See [Skills](./tools-and-context#skills). | | `outputs` | object | — | Typed output schemas for `complete` and `fail` (Goal Tree only). | | `responseFormat` | string / object | — | `"text"`, `"json"`, or `{name, schema}` for strict structured responses. | ### Tools The `tools` array lists operations the agent can invoke. Each entry is either: - A **string** — an operation path like `"v/ops/covia/read"` - A **map** — `{ "operation": "v/ops/covia/read", "name": "read_data", "description": "Read workspace data" }` for a custom name/description When `defaultTools` is `true` (the default), the platform adds standard operations covering workspace CRUD, agent management, asset operations, and schema validation. Set `defaultTools: false` for full control over the tool palette. ### Capabilities Capabilities restrict what paths and operations an agent can access. They follow the UCAN model: ```json "caps": [ { "with": "w/vendor-records/", "can": "crud/read" }, { "with": "w/enrichments/", "can": "crud" } ] ``` The `with` field is a path prefix; `can` specifies the permission level (`crud/read`, `crud/write`, `crud`, etc.). Capabilities are disclosed to the agent in its system prompt so it knows what it can and cannot do. If a tool call is denied, the error message lists the agent's available capabilities. ### Context The `context` array injects external data into the agent's context every turn: ```json "context": [ { "ref": "w/docs/data-guide", "label": "AP Data Guide" }, { "ref": "w/vendor-records/acme", "label": "ACME Record" }, { "op": "v/ops/covia/list", "input": { "path": "w/vendors" }, "label": "Known Vendors" } ] ``` Each entry can be: - `ref` — a lattice path, resolved and rendered fresh each turn - `op` — an operation to invoke, whose output is injected as context Context entries are budget-aware — large values are automatically summarised to fit within the context window. ### Typed Outputs (Goal Tree) When using the Goal Tree adapter, you can enforce structured output via `outputs`: ```json "outputs": { "complete": { "schema": { "type": "object", "properties": { "result": { "type": "string" }, "confidence": { "type": "number" } }, "required": ["result", "confidence"], "additionalProperties": false } }, "fail": { "schema": { "type": "object", "properties": { "reason": { "type": "string" }, "details": { "type": "string" } }, "required": ["reason"], "additionalProperties": false } } } ``` This wraps the `complete` and `fail` harness tools with strict JSON schema enforcement via the LLM's structured output mode. **Per-request override:** a caller can pass a `responseSchema` on [`agent:request`](./operations#agent-request) to get typed output from any agent for a single call — no `outputs` config required. It overrides the agent's default output schema for that request. ## Templates Instead of inline config, you can reference a template: ```json { "operation": "v/ops/agent/create", "input": { "agentId": "reader-1", "config": "template:reader" } } ``` A `template:` reference resolves to the venue's template catalog at `v/agents/templates/`. ### Standard Templates Every venue provides these built-in templates: | Template | Tools | Purpose | |----------|-------|---------| | `template:skilled` | read, list + [skills](./tools-and-context#skills) | **Recommended default** — lean base, capabilities loaded on demand | | `template:minimal` | None | Pure reasoning, no side effects | | `template:reader` | read, list, slice | Read-only data analysis | | `template:worker` | CRUD operations | General data processing | | `template:manager` | Agent ops + read/list | Agent coordination | | `template:analyst` | CRUD + schema ops | Data analysis with validation | | `template:full` | All defaults | Development and exploration | ### Custom Templates Store a config map in the workspace and reference it: ```json // Store a template { "operation": "v/ops/covia/write", "input": { "path": "w/templates/invoice-scanner", "value": { "operation": "v/ops/goaltree/chat", "systemPrompt": "You are an invoice scanner...", "model": "gpt-5.4-mini", "defaultTools": false, "outputs": { "complete": { "schema": { ... } } } } } } // Create agent from template { "operation": "v/ops/agent/create", "input": { "agentId": "scanner-1", "config": "w/templates/invoice-scanner" } } ``` A `config` string reference can be any of: - a **template** name — `template:worker` (resolves to `v/agents/templates/worker`) - a **workspace path** — `w/templates/invoice-scanner` - an **asset reference** — `a/` (immutable, content-addressed) - a **DID URL** — for a template hosted on another venue (cross-venue use) - a **venue operation name** Explicit fields you pass alongside the reference are merged on top of the resolved config. ## Initial State An agent can be created with pre-populated state by including a `state` field at the top level: ```json { "operation": "v/ops/agent/create", "input": { "agentId": "Alice", "config": { "operation": "v/ops/goaltree/chat" }, "state": { "config": { "model": "gpt-5.4-mini", "systemPrompt": "You are Alice...", "tools": ["v/ops/covia/read"] } } } } ``` The `state` is opaque to the framework — its structure depends on the Level 2 adapter. For both `llmagent:chat` and `goaltree:chat`, the state includes a `config` sub-map with LLM settings. ## Idempotent Creation By default, `agent:create` is idempotent. If an agent with the given ID already exists: - The call succeeds (no error) - The existing agent is unchanged - The response indicates `created: false` To replace an existing agent, use `overwrite: true`: ```json { "operation": "v/ops/agent/create", "input": { "agentId": "Alice", "overwrite": true, "config": { "systemPrompt": "Updated instructions..." } } } ``` This updates the config in place for SLEEPING or SUSPENDED agents. For TERMINATED agents, it revives the agent with fresh state. ## AP Demo Example The AP Invoice Audit Trail demo creates a team of specialised agents: ```json // Alice: Invoice Scanner (minimal tools, structured output) { "agentId": "Alice", "config": { "operation": "v/ops/goaltree/chat" }, "state": { "config": { "model": "gpt-5.4-mini", "defaultTools": false, "systemPrompt": "You are Alice, an AP Invoice Scanner...", "outputs": { "complete": { "schema": { ... } } } }} } // Bob: Data Enricher (workspace access, vendor lookup) { "agentId": "Bob", "config": { "operation": "v/ops/goaltree/chat" }, "state": { "config": { "model": "gpt-5.4-mini", "tools": ["v/ops/covia/read", "v/ops/covia/write", "v/ops/covia/list"], "caps": [ { "with": "w/vendor-records/", "can": "crud/read" }, { "with": "w/enrichments/", "can": "crud" } ], "context": [ { "ref": "w/docs/data-guide", "label": "AP Data Guide" } ], "outputs": { "complete": { "schema": { ... } } } }} } // Carol: Payment Approver (read all, write decisions) { "agentId": "Carol", "config": { "operation": "v/ops/goaltree/chat" }, "state": { "config": { "model": "gpt-5.4-mini", "tools": ["v/ops/covia/read", "v/ops/covia/write", "v/ops/covia/list"], "caps": [ { "with": "w/", "can": "crud/read" }, { "with": "w/decisions/", "can": "crud" } ], "outputs": { "complete": { "schema": { ... } } } }} } ``` Each agent has the minimum tools and capabilities needed for its role — following the principle of least privilege. ## Related - [Agent Operations](./operations) — full lifecycle reference (trigger, request, fork, etc.) - [Tools and Context](./tools-and-context) — tool resolution, context loading, budgets - [LLM Backends](./llm-backends) — configuring OpenAI, Anthropic, Ollama, xAI - [Capabilities](../capabilities) — the UCAN capability model behind the `caps` field --- ## Goal Tree The Goal Tree adapter (`goaltree:chat`) provides hierarchical goal decomposition for complex agent tasks. The agent pursues goals via a **frame stack** — each subgoal opens a child frame with its own scoped conversation, and results propagate back to the parent. ## How It Works ``` Root Goal: "Process this invoice" ├── Subgoal: "Extract line items" → complete({items: [...]}) ├── Subgoal: "Look up vendor" → complete({vendor: {...}}) └── Subgoal: "Validate totals" → complete({valid: true}) → complete({invoice: {...}, status: "validated"}) ``` Each frame has its own conversation with the LLM. When a `subgoal` tool is called: 1. A new child frame opens with the subgoal description 2. The child frame runs its own tool call loop 3. The child calls `complete` or `fail` (or returns text for implicit complete) 4. Control returns to the parent with the result Ancestor frames are visible as progressively summarised context — the parent at ~300 bytes, the grandparent at ~150 bytes, and so on. ## Configuration ```json { "operation": "v/ops/agent/create", "input": { "agentId": "processor", "config": { "operation": "v/ops/goaltree/chat", "systemPrompt": "You are an invoice processor...", "model": "gpt-5.4-mini", "tools": ["v/ops/covia/read", "v/ops/covia/write"], "defaultTools": false, "outputs": { "complete": { "schema": { "type": "object", "properties": { "invoice": { "type": "object" }, "status": { "type": "string", "enum": ["validated", "rejected"] } }, "required": ["invoice", "status"], "additionalProperties": false } } } } } } ``` ## Harness Tools The Goal Tree provides built-in harness tools, available to the agent regardless of `config.tools`: ### subgoal Open a child frame to pursue a sub-task. ```json { "name": "subgoal", "input": { "description": "Extract line items from the invoice" } } ``` The child inherits the parent's tool definitions and loaded context paths. Its conversation is independent — the parent doesn't see it. Returns `{status: "complete", result: ...}` or `{status: "failed", error: ...}`. ### complete Finish the current goal with a result. ```json { "name": "complete", "input": { "invoice": {...}, "status": "validated" } } ``` If `config.outputs.complete.schema` is defined, the input is validated against that schema using the LLM's strict structured output mode. A text-only response (no tool calls) implicitly completes the goal — no explicit `complete` call needed. ### fail Finish the current goal with an error. ```json { "name": "fail", "input": { "reason": "Vendor not found", "details": "..." } } ``` If `config.outputs.fail.schema` is defined, the input is validated against that schema. ### compact Checkpoint a long conversation to free context space. ```json { "name": "compact", "input": { "summary": "Extracted 12 line items totalling £4,250..." } } ``` Archives the current live turns into a segment with the LLM's summary. The conversation starts fresh while the archived segment remains accessible at reduced detail. The harness manages compaction pressure automatically: - At **70%** of context budget: suggests compaction - At **90%**: warns compaction is required - Beyond 90%: truncates oldest live turns (still in lattice, just not in active context) ### context_load / context_unload Load or unload lattice paths into persistent context. Same behaviour as in [LLM Agent](./llm-agent). ### more_tools Discover additional operations available on the venue beyond what's in the agent's config. ## Typed Outputs When `config.outputs` is set, the `complete` and `fail` tools enforce strict JSON schemas. This uses the LLM's structured output mode (e.g., OpenAI's `strict: true`) to guarantee the output conforms to the schema. ```json "outputs": { "complete": { "schema": { "type": "object", "properties": { "decision": { "type": "string", "enum": ["approve", "reject", "escalate"] }, "reason": { "type": "string" }, "amount": { "type": "number" } }, "required": ["decision", "reason", "amount"], "additionalProperties": false } } } ``` Without `outputs`, the `complete` tool accepts any JSON object. ## Differences from LLM Agent | Feature | LLM Agent | Goal Tree | |---------|-----------|-----------| | Conversation model | Flat history | Hierarchical frame stack | | Subgoals | Not supported | Built-in `subgoal` tool | | Context from ancestors | N/A | Progressive summarisation | | Typed outputs | Via `responseFormat` config or per-request `responseSchema` | Also `outputs` on `complete`/`fail` with JSON Schema | | Compaction | Not supported | Built-in `compact` tool | | Implicit complete | N/A | Text response auto-completes | | Best for | Simple conversation | Complex multi-step tasks | ## Patterns ### Divide and Conquer The agent breaks a complex task into subgoals, each handled independently: ``` Root: "Audit the quarterly report" ├── Subgoal: "Check revenue figures" ├── Subgoal: "Verify expense categories" ├── Subgoal: "Cross-reference with bank statements" └── Synthesise results → complete({...}) ``` ### Iterative Refinement The agent uses subgoals to try different approaches: ``` Root: "Classify this document" ├── Subgoal: "Try keyword-based classification" → fail("ambiguous") ├── Subgoal: "Try semantic analysis" → complete({category: "invoice"}) └── Use successful result → complete({...}) ``` ### Long-Running Analysis For tasks that generate extensive conversation, use `compact` to manage context: ``` Root: "Analyse all 50 vendor records" ├── Process vendors 1-10, compact("Processed 10 vendors, 3 flagged...") ├── Process vendors 11-20, compact("Processed 20 vendors, 5 flagged...") ├── ... continue with full context budget available each batch └── complete({flagged: [...], summary: "..."}) ``` ## Related - [LLM Agent](./llm-agent) — simpler conversational model - [Creating Agents](./creating-agents) — configuration and templates - [Tools and Context](./tools-and-context) — tool resolution, budgets, context loading --- ## Agents Overview # Agents Covia agents are persistent, stateful AI actors that live on a venue. They receive tasks, call tools, coordinate with other agents, and produce auditable results — all within the venue's governance and capability framework. ## Key Concepts An agent is more than a chat session. It is a durable record with: - **Configuration** — system prompt, tools, capabilities, LLM backend - **Sessions** — persistent conversation threads; each holds its own messages, conversation, and scratch data that persist across runs - **Tasks** — tracked requests (Jobs) from humans, other agents, or systems - **Timeline** — an append-only audit trail of every completed run See [Sessions](./sessions) for how work flows into an agent. Agents are created by callers and scoped to their owner. Two different users can each have an agent named "Alice" without collision. ## Architecture The agent system separates concerns into three pluggable levels: ``` Level 1: Framework Level 2: Domain Logic Level 3: LLM agent:trigger goaltree:chat langchain:openai agent:request --> llmagent:chat --> langchain:anthropic agent:message (custom) langchain:ollama ``` **Level 1 (Framework)** manages the agent lifecycle — picking up inbound tasks and session messages, invoking the transition function, recording results in the timeline, and persisting state. It never inspects the conversation or user data. **Level 2 (Domain Logic)** manages the session conversation, tool call loops, and context assembly. Two built-in options: | Adapter | Operation | Best for | |---------|-----------|----------| | [LLM Agent](./llm-agent) | `llmagent:chat` | Simple conversational agents, flat history | | [Goal Tree](./goal-tree) | `goaltree:chat` | Complex tasks with hierarchical subgoals, typed outputs | **Level 3 (LLM Call)** is a stateless request/response to the language model. See [LLM Backends](./llm-backends) for available providers. ## Agent States An agent transitions through four states: ``` create | v resume --> SLEEPING <-- (run completes successfully) | trigger / request | v RUNNING / \ v v SLEEPING SUSPENDED (error) | resume ``` | State | Description | |-------|-------------| | **SLEEPING** | Idle, ready to run. Default after creation or a successful run. | | **RUNNING** | A transition is in flight. New tasks and messages are queued. | | **SUSPENDED** | Last run failed. Dormant — does not auto-retry. State and tasks are preserved for debugging. | | **TERMINATED** | Logically deleted. Preserves audit record; can be revived with `overwrite: true`. | ## Quick Example Create an agent, send it a task, and wait for the result: ```json // 1. Create POST /api/v1/invoke { "operation": "v/ops/agent/create", "input": { "agentId": "Alice", "config": { "systemPrompt": "You are Alice, a helpful research assistant.", "model": "gpt-5.4-mini", "tools": ["v/ops/covia/read", "v/ops/covia/list"] } } } // 2. Send a task (waits up to timeout ms for the result) POST /api/v1/invoke { "operation": "v/ops/agent/request", "input": { "agentId": "Alice", "input": { "task": "Summarise the vendor records" }, "timeout": 30000 } } ``` Or via MCP tools in Claude Desktop: ``` agent_create agentId: "Alice" config: { systemPrompt: "...", tools: [...] } agent_request agentId: "Alice" input: { task: "Summarise the vendor records" } timeout: 30000 ``` ## What's Next - [Creating Agents](./creating-agents) — configuration, templates, and examples - [Sessions](./sessions) — conversation threads and the run loop - [Agent Operations](./operations) — full lifecycle reference - [LLM Agent](./llm-agent) — simple conversational agents - [Goal Tree](./goal-tree) — hierarchical goal decomposition - [Tools and Context](./tools-and-context) — tool resolution, capabilities, context loading - [LLM Backends](./llm-backends) — OpenAI, Anthropic, Ollama, and more ## Related - [COG-11: Agent Lifecycle](/docs/protocol/cogs/COG-011) — Protocol specification for agents - [COG-12: Orchestrations](/docs/protocol/cogs/COG-012) — Multi-agent orchestration --- ## LLM Agent The LLM Agent adapter (`llmagent:chat`) provides a simple conversational agent model with a flat per-session conversation and a tool call loop. It is the default transition function for new agents. ## How It Works On each run, the LLM Agent processes one [session](./sessions) and: 1. **Assembles context** — system prompt, loaded context paths, pending job results, session messages, and the session conversation 2. **Calls the LLM** — sends messages and tool definitions to the Level 3 backend 3. **Processes tool calls** — if the LLM requests tool calls, executes each as a grid operation and appends results 4. **Loops** — repeats steps 2-3 until the LLM returns a text response (no tool calls), up to 20 iterations 5. **Persists the conversation** — stores new turns into the session ``` ┌──────────────────────────────────────┐ │ Assemble context │ │ (system prompt + conversation + tools)│ └──────────────┬───────────────────────┘ │ v ┌─────────────────┐ │ Call LLM │ <───────────┐ └────────┬────────┘ │ │ │ ┌────────v────────┐ │ │ Tool calls? │── yes ──> Execute tools └────────┬────────┘ │ no v ┌─────────────────┐ │ Return text │ └─────────────────┘ ``` ## Configuration Use `llmagent:chat` by omitting the `operation` field (it is the default) or setting it explicitly: ```json { "operation": "v/ops/agent/create", "input": { "agentId": "helper", "config": { "operation": "v/ops/llmagent/chat", "systemPrompt": "You are a helpful assistant.", "model": "gpt-5.4-mini", "tools": ["v/ops/covia/read", "v/ops/covia/list"] } } } ``` ## Conversation & Context The LLM Agent separates the **persistent session conversation** from **ephemeral context**: - **Conversation** — real turns only: user messages, assistant responses, and tool call/result pairs. Stored on the session and persisted across runs. - **Ephemeral context** — system prompt, loaded context paths, pending job results, session messages. Rebuilt fresh each turn from current config and lattice state. This means: - Updates to the system prompt take effect immediately (not frozen from the first turn) - Context paths reflect current data, not stale snapshots - The conversation doesn't bloat with repeated system messages Each [session](./sessions) keeps its own conversation, so the same agent can hold several independent threads. ## Built-in Tools When `defaultTools` is `true` (the default), the agent has access to a standard set of tools: **Workspace CRUD:** `covia:read`, `covia:write`, `covia:delete`, `covia:append`, `covia:slice`, `covia:list` **Agent Management:** `agent:create`, `agent:fork`, `agent:request`, `agent:message`, `agent:trigger`, `agent:info`, `agent:list`, `agent:cancelTask` **Assets:** `asset:store`, `asset:get`, `asset:list` **Schema:** `schema:validate`, `schema:infer`, `schema:coerce` Additionally, two task management tools are always available: - `complete_task` — complete a pending task with a result - `fail_task` — reject a pending task with a reason And two context management tools: - `context_load` — persistently load a lattice path into context - `context_unload` — stop loading a path ## When to Use The LLM Agent is well suited for: - **Conversational assistants** — multi-turn Q&A with tool access - **Simple task workers** — agents that process one task at a time - **Prototyping** — quick setup with sensible defaults For complex tasks requiring hierarchical decomposition, subgoals, or typed structured outputs, consider the [Goal Tree](./goal-tree) adapter instead. ## Related - [Goal Tree](./goal-tree) — hierarchical goal decomposition - [Tools and Context](./tools-and-context) — tool resolution and context budgets - [LLM Backends](./llm-backends) — configuring the Level 3 provider --- ## LLM Backends The Level 3 LLM call is a stateless operation that sends messages and tool definitions to a language model and returns the response. Covia is **model-agnostic** — it talks to several providers through the LangChain adapter, and the choice of provider and model is yours. The default models below are what a backend uses when you don't set `model` — a fallback, not a recommendation. ## Available Backends | Operation | Provider | Notes | |-----------|----------|-------| | `v/ops/langchain/openai` | OpenAI | Supports structured outputs (strict JSON schema). Also the route for any OpenAI-compatible endpoint (see below). | | `v/ops/langchain/anthropic` | Anthropic | Claude models via the native Anthropic API. | | `v/ops/langchain/ollama` | Ollama | Local models. Requires Ollama running and reachable from the venue host. No API key. | | `v/ops/langchain/xai` | xAI (Grok) | Pre-configured OpenAI-compatible operation pointed at `https://api.x.ai/v1`. | Each operation supplies a **default model** when you don't set one, but you can pass any model the provider supports via the `model` field. Current defaults are: | Backend | Default model | Secret | |---------|---------------|--------| | OpenAI | `gpt-5.4-mini` | `OPENAI_API_KEY` | | Anthropic | `claude-sonnet-4-6` | `ANTHROPIC_API_KEY` | | Ollama | `qwen` | _(none)_ | | xAI | `grok-4` | `XAI_API_KEY` | ### OpenAI-compatible providers Many providers (xAI, DeepSeek, Google Gemini, local gateways) expose an OpenAI-compatible API. Reach them through `v/ops/langchain/openai` by setting the `url` (and the appropriate secret): ```json { "llmOperation": "v/ops/langchain/openai", "model": "deepseek-chat", "url": "https://api.deepseek.com/v1" } ``` `v/ops/langchain/xai` is simply this pattern packaged as a named operation. ## Configuration Set the backend via `llmOperation` in agent config. The `model` is optional — omit it to use the backend default: ```json { "operation": "v/ops/agent/create", "input": { "agentId": "claude-agent", "config": { "llmOperation": "v/ops/langchain/anthropic", "model": "claude-sonnet-4-6", "systemPrompt": "You are a helpful assistant." } } } ``` ### Secrets API keys are resolved from the per-user secret store at invocation time — they are never stored in job records. Set one with: ```json { "operation": "v/ops/secret/set", "input": { "key": "OPENAI_API_KEY", "value": "sk-..." } } ``` A backend that needs a key but can't resolve one **fails fast** with a clear error naming the secret it looked for (e.g. `s/OPENAI_API_KEY`) — it does not silently fall back. Ollama needs no key. ### Sampling parameters `temperature` and `topP` in the op input pass through to every backend — `temperature: 0` gives (near-)deterministic output for extraction pipelines. Omitted parameters use the provider's defaults. `maxTokens` bounds the output on the Anthropic backend (its API requires a bound; the client default applies otherwise). ### Where Ollama lives (Docker vs host) When the op input carries no `url`, the Ollama base URL resolves venue-side: `adapters.langchain.ollamaUrl` in venue config, then the `OLLAMA_BASE_URL` environment variable, then `http://localhost:11434`. Keep agents topology-agnostic — set the venue-level knob, not per-agent URLs: | Topology | Setting | |----------|---------| | Host venue + host Ollama | nothing — the localhost default works | | Docker venue + host Ollama | `adapters.langchain.ollamaUrl` (or `OLLAMA_BASE_URL`) = `http://host.docker.internal:11434`, and start Ollama with `OLLAMA_HOST=0.0.0.0 ollama serve` | | Both in one compose network | `http://:11434` | A connect failure names the resolved URL and this knob — if you see `Ollama not reachable at http://localhost:11434` from a containerised venue, that's the second row. ### Structured outputs When an agent uses typed outputs (Goal Tree `outputs` config, a per-request `responseSchema`, or `responseFormat`), the OpenAI backend enables strict JSON-schema mode so the response conforms to the schema. See [Creating Agents](./creating-agents) and [Goal Tree](./goal-tree). ## Message Format All backends use the same message format: ```json { "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "What is the weather?" }, { "role": "assistant", "content": "I'll check that for you.", "toolCalls": [...] }, { "role": "tool", "id": "call_123", "name": "weather_check", "content": "{...}" } ], "tools": [...], "responseFormat": { "name": "Response", "schema": {...} } } ``` You may pass a single `prompt` string and/or `systemPrompt` instead of a full `messages` array. **Response:** ```json { "role": "assistant", "content": "The weather is sunny.", "toolCalls": null, "tokens": { "input": 42, "output": 128, "total": 170 }, "finishReason": "stop" } ``` `tokens` and `finishReason` are surfaced when the provider reports them. If `toolCalls` is present, the Level 2 adapter executes them and calls the LLM again. ## Related - [Creating Agents](./creating-agents) — agent configuration reference - [LLM Agent](./llm-agent) — how the Level 2 transition manages the tool call loop --- ## Migrate an agent You do not have to rebuild an agent to move it onto Covia. If you already run one elsewhere, you can bring its **system prompt** and its **skills** across and stand up a native Covia agent from them in one step. Once it is native, it runs on your venue's own agent loop and inherits the grid: every task becomes a governed job with a receipt, it is discoverable and composable, and it can take part in workflows across venues. This is the counterpart to [Bring your own agent](./bring-your-own-agent), which leaves the agent where it runs and reaches it over A2A. Migrating instead recreates the agent **on** Covia. Pick whichever fits: connect it, or move it in. :::note[What comes across today] A migrated agent brings its **system prompt** and its **SKILL.md skills** (the [Anthropic Agent Skills](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview) format that Claude and a growing number of frameworks already use). Its **tools** and **memory** are not migrated yet, and are on the roadmap. ::: ## In the dashboard The quickest way is the **Port an agent** flow. 1. Open **Agents → Create**, and choose **Port an agent** under "Other options". 2. Give the agent a **name**. It is created at `g/` in your namespace. 3. Paste its **system prompt**. 4. Paste each **SKILL.md** into the skills box and press **Add skill**. Covia reads the frontmatter and previews the skill's name and description, so you can see what will be imported before you commit. 5. Press **Port agent**. Covia imports each skill into your `w/skills`, creates the native agent that indexes them, and opens its chat so you can try it straight away. ## With the operation The same thing is one operation, `agent:from-skills`, which you can call from the SDK, the CLI, or any A2A or REST client. It composes the underlying `skills:import` and `agent:create` operations, so both their capability checks apply: importing needs write access to the skillset, and creating needs agent-create authority in your namespace. ```bash curl -X POST https://venue-3.covia.ai/api/v1/invoke \ -H "Authorization: Bearer " -H "Content-Type: application/json" \ -d '{"operation": "v/ops/agent/from-skills", "input": { "agentId": "refund-bot", "systemPrompt": "You are Acme'\''s support agent. Follow the refund policy skill exactly.", "skills": [ { "text": "---\nname: refund-policy\ndescription: How to handle refund requests.\n---\n\n# Refund policy\n..." } ] }}' ``` Each entry in `skills` is either an inline SKILL.md as `{ "text": "..." }`, or a reference to one as a string (`file:////SKILL.md`, `dlfs//`, or an asset `a/`). The operation returns the created agent along with `importedSkills` (where each skill now lives) and the `skillset` they were imported into. TypeScript: ```typescript await venue.operations.run("v/ops/agent/from-skills", { agentId: "refund-bot", systemPrompt: "You are Acme's support agent. Follow the refund policy skill exactly.", skills: [{ text: skillMarkdown }], }); ``` Optional fields let you pick the transition (`operation`), the model (`llmOperation` for bring-your-own-model, or `model`), a different `skillset`, and any further `config` that `agent:create` accepts. ## What you get Because the result is an ordinary native agent, it gets everything a native agent gets: - **Governed** so every task is a job with a verifiable receipt on your venue. - **Discoverable and composable** as an ordinary asset other agents and workflows can reach. - **Interoperable** over A2A and MCP with no rewrite. - **Durable and resumable** with no framework timeout on its tasks. - **Federated** across venues on the grid. ## Related - [Bring your own agent](./bring-your-own-agent) — connect an agent over A2A instead of moving it in - [Creating agents](./creating-agents) — the full agent configuration model - [Tools and context](./tools-and-context) — how skills, tools and context shape an agent --- ## Agent Operations This page documents every operation available for managing agent lifecycle and interaction. All operations are invoked via the REST API (`POST /api/v1/invoke`) or as MCP tools. Work reaches an agent through a **session** — a persistent conversation thread. `agent:request`, `agent:chat`, and `agent:message` all attach to a session (minting a new one if you don't supply a `sessionId`). See [Sessions](./sessions) for the full model. ## Running Agents ### agent:request {/* #agent-request */} Send a tracked task to an agent. The task is recorded as a Job. This is the primary tool for agent delegation. It is **best-effort synchronous**: it waits up to `timeout` ms for the agent to finish. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | | `input` | object | *required* | Task payload — the instructions/data the agent needs. Never empty. | | `timeout` | integer (ms) | `5000` | How long to wait synchronously. `0` returns a snapshot immediately (pure async). | | `responseSchema` | object | — | JSON Schema constraining the agent's response (server-side structured output). Overrides the agent's default output schema. | | `sessionId` | string (hex) | — | Continue an existing session. Omit to mint a new one (returned in the response). | ```json { "operation": "v/ops/agent/request", "input": { "agentId": "Alice", "input": { "task": "What vendors are overdue?" }, "timeout": 30000 } } ``` **If the agent completes within `timeout`,** the response is its structured output. **If not,** you get a snapshot to poll: ```json { "id": "0x1234...", "status": "STARTED", "agentId": "Alice", "sessionId": "0xabcd..." } ``` Poll for the result with `grid:jobResult` (`grid_job_result`), passing that `id` and a `timeout`. The response always carries the `sessionId` the task belonged to. ### agent:chat {/* #agent-chat */} Send a message and synchronously await the agent's next response on the session. The A2A `SendMessage` analogue — use it for conversational interactions where you want a reply. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | | `message` | any | *required* | Message content — string, object, or any JSON value | | `sessionId` | string (hex) | — | Echo a previous `sessionId` to continue the conversation. Omit to start a new session. An unknown `sessionId` is rejected. | ```json { "operation": "v/ops/agent/chat", "input": { "agentId": "Alice", "message": "Summarise the vendor records" } } ``` **Response:** ```json { "agentId": "Alice", "sessionId": "0xabcd...", "response": "Three vendors are overdue..." } ``` Only one chat may be in flight per session — concurrent calls on the same session are rejected. ### agent:message {/* #agent-message */} Send a fire-and-forget notification. The message is delivered into the session and consumed on the agent's next run — **no agent response is returned** (the call acknowledges with `{ "delivered": true }`) and no Job is created. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | | `message` | string | *required* | Notification text | | `sessionId` | string (hex) | — | Deliver into an existing session. Omit to mint a new one. | ```json { "operation": "v/ops/agent/message", "input": { "agentId": "Alice", "message": "New invoice uploaded to w/invoices/INV-2026-042" } } ``` Use messages for one-way alerts where no reply is needed. ### agent:trigger A fallback kick that nudges the agent's run loop to execute a cycle. **Not a result-getter** — it carries no payload and makes no guarantee about output. Use it after a manual state edit, for diagnostics, or to resume a stuck agent. For results, await the relevant task or chat Job instead. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | | `force` | boolean | `true` | `true`: run even with no outstanding work. `false`: no-op if idle (the scheduler fires deferred wakes with `force=false`). | | `wait` | boolean / number | `true` | `true` blocks until the cycle yields/completes, `false` returns immediately, a number sets a timeout in ms. Not a result-await. | | `sessionId` | string (hex) | — | Resolved and echoed back if supplied; trigger never creates a session. | ```json { "operation": "v/ops/agent/trigger", "input": { "agentId": "Alice", "force": false } } ``` ## Task Completion These are framework tools an agent transition calls (typically as an LLM tool call) to finish a task. The `agentId` and `taskId` are read from the request context — the framework populates them when dispatching a task transition, so neither is a parameter. Without a task in scope the call fails. ### agent:complete-task Marks the in-scope task complete and delivers the result to the caller. Exposed to LLMs as the harness tool **`complete_task`**. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `result` | any | — | The successful result to deliver. Open schema — any structured value. | ### agent:fail-task Marks the in-scope task failed. Exposed to LLMs as the harness tool **`fail_task`**. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `error` | string | *required* | Human-readable failure reason. | ## Lifecycle Management ### agent:create Create a new agent. See [Creating Agents](./creating-agents) for full configuration details. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | | `config` | object / string | — | Inline config map, or a string reference: template (`template:worker`), workspace path (`w/templates/reader`), asset ref (`a/`), DID URL, or venue operation name. | | `state` | any | — | Initial agent state | | `overwrite` | boolean | `false` | Replace/update an occupied slot (SLEEPING/SUSPENDED update in place; TERMINATED wipes and re-creates; RUNNING is rejected). | ### agent:update Update agent configuration or state without recreating. `config` is shallow-merged. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | | `config` | object | — | Configuration fields to merge | | `state` | any | — | State to merge or replace | Framework-managed fields (status, tasks, timeline, sessions) are not affected. ```json { "operation": "v/ops/agent/update", "input": { "agentId": "Alice", "config": { "systemPrompt": "Updated instructions..." } } } ``` ### agent:fork Create a new agent from an existing agent's configuration and state. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `sourceId` | string | *required* | Source agent to fork from | | `agentId` | string | *required* | New agent identifier | | `config` | object / string | — | Config override (merged on top of source) | | `includeTimeline` | boolean | `false` | Copy the source's timeline | ```json { "operation": "v/ops/agent/fork", "input": { "sourceId": "Alice", "agentId": "Alice-v2", "config": { "systemPrompt": "Try a different approach..." } } } ``` The forked agent starts SLEEPING with fresh tasks and sessions, inheriting the source's configuration (and timeline if requested). The source agent is untouched. **Use cases:** - **Branching exploration** — fork with a modified system prompt to try a different approach - **Scaling** — fork a configured agent to handle parallel workloads - **Recovery snapshots** — fork before risky operations with `includeTimeline: true` ### agent:suspend Manually pause an agent. Prevents future runs; any in-flight run completes first. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | ### agent:resume Resume a suspended agent. Clears the error and returns to SLEEPING. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | | `autoWake` | boolean | `true` | If there is pending work, start the agent immediately | ### agent:delete Terminate an agent. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | | `remove` | boolean | `false` | Permanently delete the record (frees the name) | By default the agent is marked TERMINATED — preserving its audit trail. Use `remove: true` to delete the record entirely. ### agent:cancelTask Remove a pending task from an agent's queue. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | | `taskId` | string | *required* | Task Job ID (hex) | ## Inspection ### agent:info Get a lightweight summary of an agent. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | **Response:** ```json { "agentId": "Alice", "status": "SLEEPING", "config": { "operation": "v/ops/goaltree/chat", "model": "gpt-5.4-mini" }, "tasks": 2, "timelineLength": 15, "error": null } ``` ### agent:list List all agents owned by the caller. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `includeTerminated` | boolean | `false` | Include terminated agents | ### agent:context {/* #agent-context */} Inspect the exact LLM context an agent would receive — the complete Level 3 input as assembled by the context builder. Requires the agent's transition adapter to support inspection (the built-in adapters do). | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `agentId` | string | *required* | Agent identifier | | `task` | any | — | Optional task to simulate as a goal (shows first-iteration context) | The response is the L3 input as JSON — system prompt, context entries with byte sizes, tool definitions, and conversation. Invaluable for debugging agent behaviour. ## Deep Inspection via Lattice For detailed debugging beyond `agent:info`, read the agent's lattice state directly with `covia:read` / `covia:list` / `covia:slice`: | Path | Contents | |------|----------| | `g//sessions` | Index of sessions (keyed by session id) | | `g//sessions//meta` | Session metadata — `{parties, created, turns}` | | `g//sessions//frames/0/conversation` | The session's conversation turns | | `g//sessions//pending` | Pending (not-yet-consumed) messages for the session | | `g//tasks` | Pending task Job IDs | | `g//timeline` | Complete audit trail (append-only) | ```json { "operation": "v/ops/covia/read", "input": { "path": "g/Alice/timeline" } } ``` ### Timeline Entries Each successful run appends a timeline entry: ```json { "start": 1782864000000, "end": 1782864012000, "op": "v/ops/goaltree/chat", "tasks": [{ "task": "Summarise vendor records" }], "messages": [], "result": { "answer": "Three vendors..." }, "taskResults": { "0x1234": { "status": "COMPLETE", "output": { } } } } ``` ## Error Handling and Recovery When an agent transition fails: 1. The error is recorded in `agent.error` 2. Status transitions to **SUSPENDED** 3. Tasks, sessions, and pending messages are preserved 4. The timeline is **not** updated (no entry for failed runs) **To recover:** 1. Inspect the error: `agent:info agentId: "Alice"` 2. Fix the root cause (missing tool, bad credentials, capability issue) 3. Resume: `agent:resume agentId: "Alice"` 4. The agent returns to SLEEPING and processes pending work ## Related - [Sessions](./sessions) — the session model and run loop - [Creating Agents](./creating-agents) — configuration and templates - [Tools and Context](./tools-and-context) — how tools and context are assembled - [COG-11: Agent Lifecycle](/docs/protocol/cogs/COG-011) — Protocol specification for agents --- ## Sessions A **session** is a persistent conversation thread inside an agent. Every piece of inbound work — a task, a chat, or a notification — attaches to a session, and an agent can hold many sessions at once, each with its own conversation and scratch space. Sessions are how a single agent keeps several independent conversations straight. ## The model Each session lives in the agent record at `g//sessions/`, where `sessionId` is a venue-minted hex identifier. A session holds: | Field | Contents | |-------|----------| | `frames` | The conversation. For a plain LLM Agent this is a single root frame; for a [Goal Tree](./goal-tree) agent it's a stack of frames. The root conversation is at `frames/0/conversation`. | | `pending` | The session's intake queue — messages delivered but not yet consumed by a run. | | `c` | Session-scoped scratch workspace (addressed as `c/...` from within a transition). | | `meta` | `{ parties, created, turns }` — the caller DIDs on the session, when it was created, and a turn counter. | ## Getting a session id You never create a session explicitly. The first time you send work without a `sessionId`, the venue mints one and returns it: ```json // Request → response carries the sessionId that was minted { "operation": "v/ops/agent/chat", "input": { "agentId": "Alice", "message": "Hello" } } ``` ```json { "agentId": "Alice", "sessionId": "0xabcd...", "response": "Hi — how can I help?" } ``` Echo that `sessionId` on the next call to continue the same conversation: ```json { "operation": "v/ops/agent/chat", "input": { "agentId": "Alice", "sessionId": "0xabcd...", "message": "What did I just say?" } } ``` Omit `sessionId` to start a fresh thread. An **unknown** `sessionId` is rejected — omit it to start a new session rather than inventing one. ## Three ways to send work All three intake operations attach to a session (minting one if you don't supply an id). They differ in what they create and whether you get a reply: | Operation | Creates | You get back | Use for | |-----------|---------|--------------|---------| | [`agent:request`](./operations#agent-request) | A **task Job** | The agent's structured response (within `timeout`), else a snapshot to poll | Tracked delegation — formal requests needing a result | | [`agent:chat`](./operations#agent-chat) | A **chat Job** | The agent's next response on the session | Conversational, back-and-forth interaction | | [`agent:message`](./operations#agent-message) | _(nothing — no Job)_ | Just `{ delivered: true }` | Fire-and-forget, one-way notifications | - A **task Job** stays open until the transition calls `complete_task` / `fail_task` (or until it returns a value matching the task's output schema, which completes it implicitly). It survives restarts and is polled with `grid:jobResult`. - A **chat Job** completes implicitly with the agent's next turn output on that session. Only one chat may be in flight per session at a time. - A **message** is appended to the session's `pending` queue and consumed on the next run — there's no Job and no reply. ## The run loop Each agent runs on a single virtual thread, one cycle at a time. When work is waiting, the loop: 1. **Picks one session** — at most one session with pending work is processed per cycle. 2. **Invokes the transition** — the Level 2 adapter (LLM Agent or Goal Tree) receives that session (its conversation, pending messages, and metadata) plus the agent config. 3. **Merges the result** — new conversation turns are written back to the session, the messages it presented are drained from `pending`, the timeline gets an entry, and any task/chat Jobs are completed. 4. **Loops** — repeats until no session has pending work and no task is outstanding. Messages that arrive *during* a transition are preserved for the next cycle — only the messages presented to that cycle are drained. This keeps intake lossless under concurrency. ## Scheduled wakes A session (or task) can carry a `wakeTime`. The venue [scheduler](../adapters/scheduler) arms a single wake per agent at the earliest pending `wakeTime` and fires `agent:trigger` (with `force: false`) when it's due — so an agent can sleep until it has work or until a deferred time arrives, without polling. Wake times are stored on the lattice, so they survive a restart: the venue re-arms every agent's wake from the lattice on boot. ## Inspecting sessions Sessions are plain lattice data — read them directly with the `covia` adapter: ```json // List an agent's sessions { "operation": "v/ops/covia/list", "input": { "path": "g/Alice/sessions" } } // Read one session's metadata { "operation": "v/ops/covia/read", "input": { "path": "g/Alice/sessions/0xabcd.../meta" } } // Read the most recent conversation turns { "operation": "v/ops/covia/slice", "input": { "path": "g/Alice/sessions/0xabcd.../frames/0/conversation", "limit": 20 } } ``` `agent:context` is the higher-level equivalent — it shows the exact LLM input the agent would assemble for a session. See [Agent Operations](./operations#agent-context). ## Related - [Agent Operations](./operations) — `agent:request`, `agent:chat`, `agent:message`, and the rest - [LLM Agent](./llm-agent) — the flat per-session conversation model - [Goal Tree](./goal-tree) — the frame-stack conversation model - [Scheduler](../adapters/scheduler) — deferred wakes and agent scheduling --- ## Tools and Context Every agent turn, the context builder assembles the complete input for the LLM — system prompt, tools, loaded context, and the session conversation. Understanding this pipeline helps you configure agents effectively and debug unexpected behaviour. ## Context Assembly Pipeline Each turn, the context is rebuilt fresh (never frozen from the first turn): 1. **System prompt** — from `config.systemPrompt`, plus capability disclosure and lattice namespace reference 2. **Config tools** — resolved from `config.tools` array 3. **Default tools** — the standard operation palette (when `defaultTools: true`) 4. **Context entries** — from `config.context` array, resolved and rendered 5. **Skills index** — one line per skill offered by `config.skills` sources 6. **Loaded paths** — from `context_load` and `skill_load` calls, rendered at assigned budgets 7. **Pending results** — completions of outbound jobs the agent is waiting on 8. **Session messages** — ephemeral notifications delivered to the session 9. **Conversation** — the session conversation (LLM Agent) or current frame (Goal Tree) ## Tool Resolution Tools in the `config.tools` array are resolved to grid operations at runtime: ```json "tools": [ "v/ops/covia/read", "v/ops/covia/write", { "operation": "v/ops/http/get", "name": "fetch_url", "description": "Fetch a web page" } ] ``` **String entries** resolve directly to an operation in the venue's catalog. The tool name presented to the LLM is derived from the operation path (e.g., `v/ops/covia/read` becomes `covia_read`). **Map entries** allow you to customise the tool name and description shown to the LLM, while still pointing to the same underlying operation. ### Tool Naming Tool names follow a consistent **snake_case** convention. Venue operations are written as `adapter:op` or as a lattice path (`v/ops/covia/read`); when presented to an LLM the colons and slashes become underscores (`covia_read`, `agent_request`). The framework maps the snake_case name back to the operation when dispatching the call. The built-in **harness tools** are already snake_case: `complete_task`, `fail_task`, `context_load`, `context_unload` (LLM Agent), plus `subgoal`, `complete`, `fail`, `compact`, `more_tools` (Goal Tree). ### Runtime Discovery Agents using the Goal Tree adapter can call the `more_tools` harness tool to discover additional operations available on the venue beyond their configured set. This enables agents to adapt to their environment without needing every tool pre-configured. ## Context Loading Agents can load lattice data into their persistent context in two ways: ### Static Context (config) Paths listed in `config.context` are loaded every turn: ```json "context": [ { "ref": "w/docs/policy", "label": "Company Policy" }, { "ref": "w/vendor-records/acme", "label": "ACME Vendor Record" }, { "op": "v/ops/covia/list", "input": { "path": "w/vendors" }, "label": "Known Vendors" } ] ``` - `ref` entries resolve a lattice path and render the value - `op` entries invoke an operation and inject the output ### Dynamic Context (tools) During execution, agents can manage their own loaded context: ```json // Load a path (persists across turns) { "name": "context_load", "input": { "path": "w/reports/Q1", "label": "Q1 Report", "budget": 5000 } } // Unload when no longer needed { "name": "context_unload", "input": { "path": "w/reports/Q1" } } ``` Loaded paths persist across turns and are re-rendered fresh each turn. ### One-Shot Reads For data needed once, agents can call `covia:read` directly — this returns the value in the tool result without persisting it in the context across turns. ## Skills Where `config.context` pins knowledge the agent always needs, **skills** are the on-demand complement: named bundles of instructions, context, and tools that the agent loads mid-task. ```json "skills": ["w/skills", "v/skills"] ``` Each entry is a source — a directory of skills, or a single skill asset. Sources are searched in order, so a same-named skill in `w/skills` shadows the venue's. Declaring any source does two things: it injects a compact index (one `- name — description` line per skill) into every turn, and it offers the agent the `skill_load` harness tool. Loading a skill injects its body, loads its bundled context entries, and adds its tools to the palette — all as one entry in the same loads tier `context_load` uses, so budgets, the Context Map, eviction, and `context_unload` all apply unchanged. Bodies re-resolve each turn, so editing a skill takes effect on the next turn of every agent carrying it. Loading grants no authority: a skill's tools are still capability-checked at invocation, exactly as before it was loaded. ```json // The agent acquires a capability it wasn't configured with { "name": "skill_load", "input": { "name": "workspace" } } // ...and puts it down when finished { "name": "context_unload", "input": { "path": "v/skills/workspace" } } ``` Anyone can browse the same skills with the `v/ops/skills` operation (`list` and `read`), without an agent. See the [Teach an Agent a New Skill](../tutorials/skills) tutorial and [COG-18](../../protocol/cogs/COG-018). ## Context Budgets Each context entry has a byte budget. The context builder uses budget-aware rendering: - **High budget** — full detail, all fields and nested structures - **Low budget** — summarised, key fields only - **Compacted segments** (Goal Tree) — summaries at low budget, full turns at high budget The default context budget is 180,000 bytes (approximately 45,000 tokens). This is configurable. The Goal Tree adapter manages budget pressure automatically: - At 70% capacity: suggests compaction - At 90% capacity: warns compaction is required - Beyond 90%: truncates oldest live turns ## Capabilities Capabilities restrict what paths and operations an agent can access: ```json "caps": [ { "with": "w/vendor-records/", "can": "crud/read" }, { "with": "w/enrichments/", "can": "crud" } ] ``` ### Disclosure Capabilities are included in the agent's system prompt so the LLM knows what it can and cannot do. This eliminates trial-and-error discovery. ### Enforcement When a tool call is denied due to insufficient capabilities, the error message: - Explains which capability was required - Lists the agent's available capabilities - States that retrying will not help This helps the LLM adjust its approach rather than retrying the same denied action. ### Capability Levels | Level | Includes | |-------|----------| | `crud/read` | Read operations only | | `crud/write` | Write operations only | | `crud` | Full CRUD (read, write, delete, append) | The `with` field is a path prefix — `"w/vendor-records/"` grants access to everything under that path. ## Lattice Namespaces Agents interact with data via lattice paths. The system prompt includes a reference to available namespaces: | Prefix | Description | |--------|-------------| | `w/` | User workspace — persistent data managed on the user's behalf | | `o/` | Operation pins — named operations saved for reuse | | `n/` | Agent-private notes — persists across transitions, private to this agent | | `t/` | Temporary scratch space — cleaned up when the job ends | | `g/` | Agent records — state, timelines, config for all agents | | `s/` | Secrets — API keys and credentials | | `j/` | Job records — status and results of past work | | `a/` | Assets — immutable content-addressed artefacts | | `v/ops/` | Venue operations catalog — shared, read-only | Deep path navigation is supported: `w/records/123/name`, `g/agent/timeline/0/end`. ## Inbound Channels Agents receive work through three channels. Tasks, chats, and messages all attach to a [session](./sessions) — the persistent conversation thread the run loop processes one at a time. | Channel | Persistence | Tracking | Use for | |---------|-------------|----------|---------| | **Tasks** (via `agent:request`) | Persistent — survive restarts | Each is a Job with status | Formal requests requiring a response | | **Chats** (via `agent:chat`) | Per-session | A chat Job awaiting the next reply | Conversational, back-and-forth interaction | | **Messages** (via `agent:message`) | Queued on the session, consumed on next run | Recorded in the timeline | One-way notifications | | **Pending results** | Persistent | Linked to outbound Job IDs | Completions of work the agent delegated | Tasks persist until completed or failed. Messages are drained after processing. Pending results wake the agent automatically when the outbound job completes. ## Related - [Creating Agents](./creating-agents) — configuration reference - [Teach an Agent a New Skill](../tutorials/skills) — skills end to end - [LLM Agent](./llm-agent) — simple conversational model - [Goal Tree](./goal-tree) — hierarchical goal decomposition - [LLM Backends](./llm-backends) — configuring the Level 3 provider --- ## Capabilities (UCAN) # Capabilities Covia uses **capabilities** to decide what an authenticated caller — a user, an agent, or another venue — is allowed to do. A capability is a signed grant that says "this holder may perform *this ability* on *this resource*". Covia's model follows [UCAN](https://github.com/ucan-wg/spec) (User-Controlled Authorization Networks): capabilities are carried as signed tokens, can be **attenuated** (narrowed) when delegated, and are checked at the point of use. Authentication answers *who you are* (see the [operator auth guide](../operator-guide/auth)); capabilities answer *what you may do*. ## The shape of a capability A capability is a `{with, can}` pair: - **`with`** — the resource, as a path or URI: a lattice path like `o/shared/`, `w/reports/`, or `dlfs/notes/` (DLFS drives are a DID-scoped namespace alongside `w/`; the legacy `dlfs://notes/` scheme form is still accepted for your *own* drives), or a typed resource such as `file://workspace/`. - **`can`** — the ability, a hierarchical path. The common family is `crud/read`, `crud/write`, `crud/delete` (with the umbrella `crud` covering all three); further families govern specific surfaces — e.g. `agent/create`, `asset/read`, `mcp/manage` (managing bridged MCP servers), `grant/…` (issuing grants) — and `*` covers everything. ```json { "with": "o/shared/", "can": "crud/read" } ``` A grant **covers** a request when its resource is a prefix of the target and its ability includes the required one. Reads require `crud/read`, writes/appends/mkdir require `crud/write`, deletes require `crud/delete`. ## Two ways capabilities are applied ### 1. Agent caps An agent's config can carry a `caps` array. Once set, **every** operation the agent attempts is checked against it — the agent cannot exceed its grant, even via tool calls: ```json "caps": [ { "with": "w/vendor-records/", "can": "crud/read" }, { "with": "w/enrichments/", "can": "crud" }, { "with": "file://workspace/", "can": "crud/write" } ] ``` The agent's capabilities are disclosed in its system prompt so the model knows its limits up front. A denied call returns a **structural** error — it names the required ability and resource and lists what the agent holds, and signals that retrying won't help — so the agent adapts instead of looping. (An agent with no `caps` is unrestricted within its owner's namespace.) ### 2. UCAN tokens on a request A caller can present a UCAN as a bearer token. The venue verifies it at ingress and treats the proven capabilities as authority for that request: ``` Authorization: Bearer ``` This works on both the REST API and the MCP endpoint. The token's issuer (`iss`) is taken as the caller DID, and the token is stashed as a capability proof for the operation. Requests may also carry proofs explicitly via the `ucans` field of the invoke envelope. This is what enables **cross-user access**: if Alice issues Bob a UCAN granting `{with: "o/shared/", can: "crud/read"}`, Bob can present it to read under Alice's `o/shared/` namespace — access he wouldn't otherwise have. The venue trusts the verified proof, not Bob's bare identity. Verification checks the **root of the delegation chain**: a grant over Alice's resources must be rooted by Alice herself (an owner-signed, *self-sovereign* grant — verifiable by **any** venue, which is what makes capabilities work across venue boundaries) or by the venue that hosts the data (venue-issued grants, e.g. from `ucan:issue`). A chain can pass through intermediaries (Alice → Bob → Carol) as long as each hop only narrows the grant; an escalating hop is refused. ## Issuing a UCAN The `ucan:issue` operation mints a venue-signed UCAN delegating capabilities to an audience: ```json { "operation": "v/ops/ucan/issue", "input": { "aud": "did:key:z6MkBob...", "att": [ { "with": "o/shared/", "can": "crud/read" } ], "exp": 2000000000 } } ``` | Field | Meaning | |-------|---------| | `aud` | Audience DID — who receives the capability | | `att` | Attenuations — the `{with, can}` grants being delegated | | `exp` | Expiry, in unix seconds | The result is a JWT the audience presents as a bearer token. Because UCANs attenuate, the audience can in turn delegate a *narrower* subset onward, but never more than it holds. A resource owner with their own `did:key` keypair can equally sign grants over their own namespace **directly** — no venue involvement. Such self-sovereign grants verify on any venue that holds the data. ## Capability-gated adapters Capability checks apply across the venue. In particular, the [`file:`](./adapters/file) adapter is gated on typed resources (`file:///`) and the [`dlfs:`](./adapters/dlfs) adapter on DID-scoped drive paths (`dlfs//`, owner-prefixed for cross-user access: `did:key:zAlice.../dlfs//`), so a grant can scope filesystem access down to a single root, drive, or path. ## Related - [Operator: Authentication](../operator-guide/auth) — how callers are identified - [Creating Agents](./agents/creating-agents) — setting an agent's `caps` - [File Adapter](./adapters/file) — capability-gated filesystem access - [COG-013: Agent Capabilities](../protocol/cogs/COG-013) — the protocol specification --- ## Archive # Archive Adapter Real workflows exchange bundles — datasets, document packs, build outputs — and an archive from outside should never be trusted blindly. The archive adapter handles zip and jar files as governed operations: `archive:list` inspects without extracting, `archive:extract` unpacks into a jailed directory, and `archive:zip` builds an archive from files — written to a jailed file or published as a content-addressed [asset](./asset). ## Operations ```json { "operation": "v/ops/archive/zip", "input": { "root": "work", "path": "data", "destRoot": "work", "destPath": "out.zip" } } ``` Sources for `list` and `extract` are exactly one of: `root` + `path` (a file under a configured [File adapter](./file) root), `asset` (a content-addressed reference), or `bytes` (inline base64). Omit `destRoot`/`destPath` on `zip` and the result is published as an asset in your `a/` namespace instead of written to disk — `{entries, bytes, asset: "did:.../a/"}`. ## Built for hostile inputs Archives are a classic attack surface, so the guards are structural: - **Nothing escapes a root.** Sources and destinations are confined to the File adapter's configured roots; there are no arbitrary filesystem paths. - **Zip-slip protection.** Every extracted entry is resolved inside the destination's real path; an escaping entry (`../…`) fails the whole job and writes nothing. - **Zip-bomb guards.** Extraction caps at 100,000 entries and 2 GiB uncompressed; listings cap at 5,000 entries with an explicit `truncated` flag. ## Access Control Checks are per resource rather than blanket: sources require `crud/read`, destinations require `crud/write` on a writable root, and zip-to-asset requires `asset/store`. ## Related - [File Adapter](./file) — the root jail this builds on - [Assets](./asset) — archives as immutable artifacts --- ## Assets # Asset Adapter When data travels between parties who don't share infrastructure, "is this exactly what was published?" must have a provable answer. The asset adapter provides it with **immutable, content-addressed assets**: store data once, get back an ID that is the cryptographic hash of its metadata, and that ID verifies the content forever. Assets are how skills, operation definitions, documents, and artifacts travel the grid tamper-evidently. ## Operations | Operation | Purpose | |-----------|---------| | `asset:store` | Store metadata (plus optional content) in your `a/` namespace | | `asset:get` | Read asset metadata from any resolvable address | | `asset:content` | Retrieve the binary payload (size-capped, default 1 MB) | | `asset:list` | List your own assets, paginated, filterable by type | | `asset:pin` | Snapshot any resolvable value — including a remote venue's — into your store | ```json { "operation": "v/ops/asset/store", "input": { "metadata": { "name": "Q1 Report", "type": "document" }, "contentText": "Revenue up 14%..." } } ``` Returns `{id: "did:key:z.../a/", stored: true}`. Storing identical metadata returns the identical ID — idempotent by construction. ## Content addressing, strictly The asset ID is the CAD3 hash of the **stored metadata**, and content bytes are bound in via `content.sha256` — so the ID commits to the payload transitively. The venue never silently rewrites caller metadata (that would change the asset's identity); the single documented write is injecting `content.sha256`, and a declared hash that mismatches the actual bytes is an error, not a correction. Content is supplied as hex (`content`) or plain text (`contentText`), never both. ## Pin: adoption with verification `asset:pin` makes a reference durable. Pinning a remote `did:` reference fetches it, **verifies metadata and content hashes**, and copies it into your own store — the explicit act that turns "something a remote venue serves today" into "something I hold". Reads and pins accept the same universal addressing as everything else: hashes, `a/`, DID URLs, workspace paths. ## Access Control All operations require an authenticated caller. Assets are per-user exactly like workspace paths: reads need `asset/read`, stores need `asset/store`, and a cross-user read your proofs don't cover is a **denial, never a silent miss**. The one deliberate exception: the venue's own catalog is publicly readable — operations must be discoverable. ## Related - [Covia (Venue) Adapter](./venue-adapter) — mutable lattice state, by contrast - [Skills](./skills) — skills as published assets - [COG-5: Assets](../../protocol/cogs/COG-005) — the specification --- ## Auth # Auth Adapter The auth adapter is a single diagnostic operation: `auth:whoami` returns the caller identity exactly as the venue's auth middleware resolved it. It answers the question every integration eventually asks — *who does the venue think I am?* — end to end, through the real request path. ```json { "operation": "v/ops/auth/whoami", "input": {} } ``` Returns: ```json { "caller": "did:web:venue.example.com:u:alice", "authenticated": true, "internal": false } ``` `caller` is the resolved DID; `internal` is true only when the caller *is* the venue itself (engine-side trust paths). The operation is deliberately callable anonymously — on a venue with public access enabled, anonymous requests resolve to the venue's public principal (a DID ending `:public`), and `whoami` shows you exactly that. Use it to verify bearer tokens, UCAN presentation, and agent identity attribution before debugging anything else: if `whoami` says the wrong name, nothing downstream will behave. ## Related - [Authentication](/docs/operator-guide/auth) — configuring who can call the venue - [Capabilities (UCAN)](../capabilities) — what an identity may do --- ## Convex # Convex Adapter Sometimes agents need shared state that no single party controls — balances, registries, commitments between organisations that don't trust each other's databases. The Convex adapter provides it by connecting the venue to the [Convex network](https://docs.convex.world/) — the decentralised lattice technology Covia itself is built on. `convex:query` runs read-only Convex Lisp against a peer; `convex:transact` submits signed transactions: verifiable, decentralised state and settlement as ordinary grid operations. ## Operations ### convex:query — Read ```json { "operation": "v/ops/convex/query", "input": { "peer": "peer.convex.live:18888", "address": "#13", "source": "(* 2 3)" } } ``` Returns the Convex result as `{result, value}` — here `{"result": "6", "value": 6}`. `peer` is optional (the shipped operation defaults to the public `peer.convex.live` peer); `address` is optional for queries. ### convex:transact — Write Submits a signed transaction: `address` (origin account), `source` (Convex Lisp), and `seed` (the Ed25519 signing seed) are required. The seed is marked secret in the schema — prefer a [secret reference](./secret) over a literal wherever one is accepted. CVM-level errors (an undefined symbol, a bad signature) come back as a **completed** job with `errorCode` set — the network answered; the answer was an error. Connection and input failures fail the job. A 60-second backstop ensures an unresponsive peer can never park a job forever. ## Related - [The Grid](/docs/overview/grid) — how Covia uses lattice technology itself - [Secrets](./secret) — keeping signing seeds out of inputs --- ## Covia with A2A # A2A (Agent-to-Agent) The agent ecosystem is plural — many frameworks, many vendors — and your agents shouldn't be strangers to any of it. Covia speaks the [Agent-to-Agent protocol (A2A)](https://a2a-protocol.org/) **both ways**. A venue is a spec-conformant A2A v1.0 server — any A2A client can send it messages and track tasks — and the A2A adapter lets your operations and agents **call** remote A2A agents as grid operations. A2A maps cleanly onto Covia's own model: an A2A *Task* is a Covia *Job*, and an A2A *Message* is a turn in a conversation. ## Inbound: a venue as an A2A server A venue configured with an `a2a` block (see the [Configuration Reference](../../operator-guide/configuration)) exposes an A2A endpoint and an agent card: | Endpoint | Purpose | |----------|---------| | `POST /a2a` | A2A JSON-RPC 2.0 | | `GET /.well-known/agent-card.json` | Agent card (discovery) | Responses use `Content-Type: application/a2a+json`. The implemented methods are: | Method | Support | |--------|---------| | `SendMessage` | ✅ Send a message; returns a Task | | `GetTask` | ✅ Fetch a Task's current state | | `CancelTask` | ✅ Cancel a Task | | `SendStreamingMessage` | ✅ Send and stream updates over SSE | | `SubscribeToTask` | ✅ Subscribe to an existing Task over SSE | | Push-notification config, `ListTasks`, extended card | Not implemented — return an `UnsupportedOperation` error | ### Agent card `GET /.well-known/agent-card.json` returns the venue's card — `name`, `description`, `version`, `provider`, `capabilities` (the venue advertises `streaming: true` — gating the streaming methods — and state-transition history), a single JSON-RPC interface at `{baseUrl}/a2a` (protocol version `1.0`), and default input/output modes (`text/plain`, `application/json`). The `name`, `description`, provider, and version come from the venue's agent-info configuration. ### How inbound messages map - A `SendMessage` with **no** `taskId` starts fresh: the venue invokes its configured default chat operation, creating a **new Job**, and returns a Task whose `id` is the Job id (hex). - A `SendMessage` **with** a `taskId` is a continuation: the message is appended to that Job's history and delivered to the running transition. - Task state mirrors Job status — `PENDING→submitted`, `STARTED→working`, `COMPLETE→completed`, `FAILED→failed`, `CANCELLED→canceled`, plus `input-required` / `auth-required`. When a Job reaches a terminal state its output is surfaced as the Task's artifact. Inbound callers are identified by the venue's [auth middleware](../../operator-guide/auth); Job ownership is enforced, so a caller only sees its own tasks. ### Streaming `SendStreamingMessage` and `SubscribeToTask` open a Server-Sent Events stream (`text/event-stream`). Each event is a JSON-RPC envelope carrying a Task snapshot or a status-update; the stream sends a final update marked `final: true` when the Task reaches a terminal state, then closes. ## Outbound: calling remote A2A agents — bring your own agent The A2A adapter turns a remote A2A agent into grid operations. **Import the agent once** — that fetches its card and stores it as an asset, with its credential configured a single time — then task it by the binding the import creates. | Operation | Input | Returns | |-----------|-------|---------| | `a2a:import-agent` | `name`, `url` (or `coviaAgent`), `auth?` | Writes an immutable `type:a2a-agent` asset, bound at `w/a2a/agents/` | | `a2a:agent-card` | `agent` | The imported card snapshot | | `a2a:send` | `agent`, `message`, `taskId?` | The remote Task (final, or current on interrupt) | | `a2a:get-task` | `agent`, `id` | The remote Task's current state | | `a2a:cancel` | `agent`, `id` | The cancelled Task | ```json { "operation": "a2a:import-agent", "input": { "name": "partner-bot", "url": "https://agent.example.com", "auth": { "scheme": "bearer", "secret": "s/PARTNER_KEY" } } } ``` Then send to the binding — the message carries no credentials: ```json { "operation": "a2a:send", "input": { "agent": "w/a2a/agents/partner-bot", "message": { "role": "user", "parts": [{ "type": "text", "text": "Summarise Q1 revenue" }] } } } ``` Pass a `taskId` to continue an existing remote Task. Low-level `a2a/raw/*` operations take a `url` and credentials inline for one-off diagnostics; they are deliberately not agent tools. A step-by-step walkthrough — including Agentforce, Lyzr and Hermes — is in [Bring your own agent](../agents/bring-your-own-agent). ### Job-per-Task mirroring `a2a:send` creates a **local Job that mirrors the remote Task**. If the remote Task is already terminal, the local Job completes immediately with its result. If it's still running, the adapter polls the remote agent (`GetTask` every ~500 ms, up to a 30-minute lifetime) and propagates state to the local Job; cancelling the local Job sends a best-effort `CancelTask` to the remote. An interrupted remote Task (`input-required` / `auth-required`) surfaces that state on the local Job so the caller can respond. ### Authentication Outbound auth belongs to the **imported agent**, configured once and attached on every call — never supplied per request. `auth.secret` must be a **caller-owned `s/NAME` reference**; literal credentials are rejected. Use `auth: {scheme: "", secret: "s/NAME"}` for the API-key or HTTP-Bearer scheme the agent card declares, or `auth: {kind: "bearer", secret: "s/NAME"}` when a bearer is required even to fetch a private card. Only standard A2A HTTP auth is sent; Covia UCAN proofs are **not** forwarded outbound, so cross-organisation authorisation on an outbound call rests on the remote's own auth, not on Covia capabilities. Inbound, the venue's agent card does not yet advertise A2A `securitySchemes`; inbound auth is handled at the transport/middleware layer (see the [operator auth guide](../../operator-guide/auth)). ## Related - [Bring your own agent](../agents/bring-your-own-agent) — the task guide for importing an external agent - [A2A protocol specification](https://a2a-protocol.org/latest/specification/) - [MCP](./covia-with-mcp) — the other side of the interop story - [Agent Operations](../agents/operations) — `agent:chat` is the inbound `SendMessage` analogue - [Grid Adapter](./grid-adapter) — federating operations across venues --- ## Covia with MCP # MCP Adapter Thousands of useful tools already exist as [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers — but on their own they come with no governance, no audit trail, and no federation. The MCP adapter brings them inside: a venue **calls external MCP servers as grid operations**, discovering their tools and invoking them with the same capability checks and job records as everything else. > Looking for the other direction — exposing *your* venue's operations to an AI assistant as MCP tools? A venue is automatically an MCP server. See [Venues as MCP Servers](../mcp/venues-as-mcp-servers). ## Operations | Operation | Catalog path | Purpose | |-----------|--------------|---------| | `mcp:tools:list` | `v/ops/mcp/tools-list` | List the tools a remote MCP server offers | | `mcp:tools:call` | `v/ops/mcp/tools-call` | Invoke a tool on a remote MCP server | | `mcp:tool:add` | `v/ops/mcp/add-tool` | Curate a single remote tool as a catalog operation at a path you choose | | `mcp:server:add` | `v/ops/mcp/add-server` | Mirror **all** of a server's tools into the catalog | | `mcp:server:remove` | `v/ops/mcp/remove-server` | Remove a mirrored server's tools and registry entry | | `mcp:server:refresh` | `v/ops/mcp/refresh` | Re-sync bridged tools against the live server (mirror or curated mode) | A server is addressed by URL or by DID (resolved from the DID document's service entries). See [Calling MCP Tools](../mcp/calling-mcp-tools) for worked examples, authentication options, and orchestration patterns. ## Bridging: remote tools as catalog operations Beyond one-off calls, the adapter can **bridge** remote MCP tools into the operation catalog, where they behave exactly like native operations — capability grants, gates, job records, schema validation, and agent tool palettes all apply. Curate individual tools from different servers into groups at catalog paths of your choosing, or mirror a whole server. See [Calling MCP Tools § Bridging](../mcp/calling-mcp-tools#bridging-mcp-tools-into-the-catalog) for the full guide. ## Why go through the Grid? Calling an MCP server *through* a venue, rather than wiring it into your client directly, gives you: - **Plug-and-play orchestration** — compose MCP tools with other grid operations, agents, and [orchestrations](./orchestrator). - **A system of record** — every call is a [Job](../api/) with an auditable record on your venue. - **Controlled credentials** — API keys live in the venue's [secret store](./secret), not in the client. ## Related - [Calling MCP Tools](../mcp/calling-mcp-tools) — detailed reference for `mcp:tools:list` / `mcp:tools:call` - [Venues as MCP Servers](../mcp/venues-as-mcp-servers) — the inbound side (your venue as an MCP server) - [MCP Integration](../mcp/) — the full MCP section - [Orchestrator](./orchestrator) — composing MCP tools into workflows --- ## DLFS # DLFS (Decentralised Lattice File System) On most platforms your files ultimately belong to the platform. DLFS exists so they belong to **you**: self-sovereign, decentralised file storage where each user gets their own named drives, every write is signed with the user's own Ed25519 key, and the whole drive — stored in an independent lattice region — is portable across venues. Move venue, keep your files, keep the proof they're yours. ## Key Properties - **User-signed** — every write is signed by the user's own key, not the venue's - **Lattice-backed** — drives use CRDT merge semantics for conflict-free cross-venue synchronisation - **Portable** — users can migrate drives to another venue by exporting their key - **Isolated** — DLFS occupies a separate lattice region from venue state; the venue operator cannot forge modifications ## Drives Each user can create and manage multiple named drives. A drive is a named file system root. ```json // Create a drive { "operation": "v/ops/dlfs/create-drive", "input": { "name": "documents" } } // List your drives { "operation": "v/ops/dlfs/list-drives" } // → { "drives": ["documents", "health-vault", "archive"] } // Delete a drive and all its contents { "operation": "v/ops/dlfs/delete-drive", "input": { "name": "archive" } } ``` Drive creation is idempotent — creating the same drive twice succeeds without error. The first write to a drive also auto-creates it if needed. ## File Operations All file operations take a `drive` name and a `path` within that drive. ### Reading Files ```json { "operation": "v/ops/dlfs/read", "input": { "drive": "documents", "path": "report.json" } } ``` **Response:** ```json { "content": "{\"title\": \"Q1 Report\"...}", "encoding": "utf-8", "size": 2048 } ``` Text files are returned as UTF-8 strings. Binary files (images, PDFs) are returned as Base64-encoded strings with `"encoding": "base64"`. ### Writing Files ```json // Write inline text content { "operation": "v/ops/dlfs/write", "input": { "drive": "documents", "path": "report.json", "content": "{\"title\": \"Q1 Report\", \"status\": \"draft\"}" } } // Write from a Covia asset (for binary files) { "operation": "v/ops/dlfs/write", "input": { "drive": "documents", "path": "logo.png", "asset": "/a/0x7a8b9c0d..." } } ``` Creates the file if absent, overwrites if it exists. Returns `{written: , created: }`. ### Listing Directories ```json { "operation": "v/ops/dlfs/list", "input": { "drive": "documents", "path": "reports" } } ``` **Response:** ```json { "entries": [ { "name": "q1-report.json", "type": "file", "size": 2048 }, { "name": "q2-report.json", "type": "file", "size": 3100 }, { "name": "attachments", "type": "directory" } ] } ``` Omit `path` to list the drive root. ### Creating Directories ```json { "operation": "v/ops/dlfs/mkdir", "input": { "drive": "documents", "path": "reports/2026" } } ``` Parent directories must already exist. ### Deleting Files ```json { "operation": "v/ops/dlfs/delete", "input": { "drive": "documents", "path": "reports/draft.json" } } ``` Cannot delete non-empty directories — delete contents first. ## Operations Reference | Operation | Input | Description | |-----------|-------|-------------| | `dlfs:listDrives` | — | List all drives for the caller | | `dlfs:createDrive` | `name` | Create a named drive (idempotent) | | `dlfs:deleteDrive` | `name` | Delete a drive and all contents | | `dlfs:list` | `drive`, `path?` | List directory entries | | `dlfs:read` | `drive`, `path` | Read file content | | `dlfs:write` | `drive`, `path`, `content?` or `asset?` | Write file content | | `dlfs:mkdir` | `drive`, `path` | Create a directory | | `dlfs:delete` | `drive`, `path` | Delete a file or empty directory | ## WebDAV DLFS drives can be mounted as network file systems via WebDAV, enabling drag-and-drop access from native file managers and desktop applications. ### Enabling WebDAV WebDAV must be enabled in venue configuration: ```json { "webdav": { "enabled": true } } ``` ### Endpoint WebDAV is served at `/dlfs/` on the venue: ``` https://your-venue.example.com/dlfs/{drive-name}/{path} ``` Standard WebDAV verbs are supported: | Verb | Example | Effect | |------|---------|--------| | `GET` | `/dlfs/documents/report.json` | Read file | | `PUT` | `/dlfs/documents/report.json` | Write file | | `PROPFIND` | `/dlfs/documents/` | List directory | | `MKCOL` | `/dlfs/documents/new-folder` | Create directory | | `DELETE` | `/dlfs/documents/temp.txt` | Delete file | ### Mounting as a Network Drive DLFS drives can be mounted as a native network drive on Windows, macOS, and Linux. Authentication uses your venue credentials (username/password or API token). #### Windows (File Explorer) 1. Open **File Explorer** → right-click **This PC** → **Map network drive…** 2. Choose a drive letter (e.g. `Z:`) 3. In **Folder**, enter: ``` https://your-venue.example.com/dlfs/documents ``` 4. Tick **Connect using different credentials**, click **Finish** 5. Enter your venue username and password when prompted From PowerShell or `cmd`: ``` net use Z: https://your-venue.example.com/dlfs/documents /user:alice ``` :::tip If Windows refuses `https://` URLs, ensure the **WebClient** service is running (`services.msc` → WebClient → Start, set to Automatic). Windows also enforces a default 50 MB WebDAV file-size limit — raise `FileSizeLimitInBytes` under `HKLM\SYSTEM\CurrentControlSet\Services\WebClient\Parameters` if you transfer larger files. ::: #### macOS (Finder) 1. In Finder, press `⌘K` (or **Go → Connect to Server…**) 2. Enter the server URL: ``` https://your-venue.example.com/dlfs/documents ``` 3. Click **Connect** and authenticate with your venue credentials The drive appears under **Locations** in Finder. #### Linux (davfs2) ```bash sudo apt install davfs2 sudo mkdir -p /mnt/dlfs sudo mount -t davfs https://your-venue.example.com/dlfs/documents /mnt/dlfs ``` For unattended mounts, add credentials to `/etc/davfs2/secrets` and an entry to `/etc/fstab`. #### rclone (cross-platform) Configure a WebDAV remote once with `rclone config` (vendor: `other`), then: ```bash rclone mount dlfs:documents ~/dlfs --vfs-cache-mode writes ``` ## Security ### Key Management When a user first accesses DLFS, the adapter generates a unique Ed25519 keypair. The private key is encrypted and stored in the venue's secret store. Every operation on that user's drives is signed with this key. ### Access Control - Each user can only access their own drives - The venue operator cannot modify a user's drives (modifications require the user's signature) - Anonymous requests are rejected — all DLFS operations require authentication ### Cross-Venue Portability Because drives are signed by the user's key rather than the venue's: 1. Export the DLFS key seed 2. Import it on another venue 3. Access your drives unchanged Because ownership is cryptographic rather than administrative, a venue cannot lock users in. ## Merge Semantics DLFS uses timestamp-wins CRDT merge: - When two venues modify the same file, the newest version wins - Changes made offline are stored locally and sync when reconnected - No manual conflict resolution — merging is automatic and deterministic ## Related - [Vault](./vault) — simplified DLFS wrapper for health vault access - [REST API](/docs/user-guide/api) — HTTP endpoints --- ## File Adapter Agents often need real files — imports to process, outputs to write, a working directory — without being handed the run of the host machine. The File adapter squares that: agents and operations get **root-jailed access to a local filesystem**. An operator configures one or more named *roots*; every `file:` operation works relative to a root and cannot escape it. Roots can be host directories, ephemeral temp directories, or [DLFS](./dlfs)-backed drives. ## Operations | Operation | Ability | Purpose | |-----------|---------|---------| | `file:roots` | read | List the configured roots | | `file:list` | read | List the entries in a directory | | `file:tree` | read | Tab-indented text tree of a directory (for LLM consumption) | | `file:read` | read | Read a file (text, bytes, or JSON) | | `file:stat` | read | File metadata | | `file:write` | write | Write/replace a file | | `file:append` | write | Append to a file | | `file:mkdir` | write | Create a directory | | `file:delete` | delete | Delete a file or directory | Every operation except `file:roots` takes a `root` (the configured root name) and a `path` relative to it. ## Configuring roots Roots are declared in the venue config under `file.roots`. A root is either a bare host-path string or a map: ```json5 { "file": { "roots": { // string form → a host directory (must already exist) "workspace": "/srv/agent-workspace", // map form → explicit options "data": { "path": "/srv/reference", "readOnly": true, "description": "Shared reference data" }, "scratch": { "temp": true }, // ephemeral, removed on JVM exit "notes": { "dlfs": "user-notes" } // backed by a DLFS drive } } } ``` Per-root options: | Option | Meaning | |--------|---------| | `path` | Host filesystem path (canonicalised; must exist) | | `temp` | `true` creates an ephemeral directory, auto-removed on shutdown | | `dlfs` | Name of a [DLFS](./dlfs) drive to back the root (per-user, signed) | | `readOnly` | Reject all writes/deletes through this root | | `description` | Operator note, surfaced to agents via `file:roots` | | `prefix` | Name prefix for a temp directory (default `covia--`) | Exactly one of `path`, `temp`, or `dlfs` must be set. If no roots are configured, the venue provides a single ephemeral `tmp` root. `file:roots` returns each root's `name`, `path` (or `dlfs:`), `kind` (`host` / `temp` / `dlfs`), `readOnly`, and optional `description`. ## Reading `file:read` decodes by `mode` — `auto` (default; UTF-8 text when it looks like text, otherwise base64), `text`, `bytes` (base64), or `json` (parsed value returned in `value`): ```json { "operation": "v/ops/file/read", "input": { "root": "workspace", "path": "notes/daily.md" } } ``` ```json { "content": "# Daily notes\n...", "encoding": "utf-8", "size": 1280, "mime": "text/markdown" } ``` `file:tree` renders a compact, tab-indented walk that's cheap for an LLM to read. It is bounded by `maxDepth` (default 3, max 10) and `maxEntries` (default 500, max 5000); `truncated: true` signals the cap was hit. Pass `info: "size"` to annotate files with a human-readable size: ```json { "operation": "v/ops/file/tree", "input": { "root": "workspace", "maxDepth": 2, "info": "size" } } ``` ``` workspace/ notes/ daily.md (1.2 KB) report.pdf (2.3 MB) ``` ## Writing `file:write` and `file:append` take exactly one content source: `content` (UTF-8 text), `value` (written as JSON), `bytes` (base64), or `asset` (an asset reference streamed to the file). Writing requires the file's parent directory to exist; `file:mkdir` with `parents: true` creates intermediates. Writes through a `readOnly` root are rejected. ```json { "operation": "v/ops/file/write", "input": { "root": "workspace", "path": "out/result.json", "value": { "ok": true } } } ``` `file:delete` removes a file or empty directory; pass `recursive: true` to delete a non-empty directory. It never deletes the root itself. ## Safety Every path is **root-jailed**. Absolute paths, drive-rooted or UNC paths, and `..` escapes are rejected before any filesystem access. For host roots the adapter additionally resolves symlinks (`toRealPath`) and rejects any path — including one reached through a symlink or a dangling link — that would resolve outside the root. A leading `/` on a path is treated as "relative to the root", not the host root. ## Capabilities `file:` operations are capability-gated. When an agent has a `caps` list (or a request carries UCAN proofs), each call is checked against the required ability and a `file:///` resource: ```json "caps": [ { "with": "file://workspace/", "can": "crud/write" }, { "with": "file://data/", "can": "crud/read" } ] ``` Reads map to `crud/read`, writes/mkdir/append to `crud/write`, and delete to `crud/delete`. A denied call returns a structural error explaining what was required and what the agent holds — see [Capabilities](../capabilities). ## Related - [DLFS](./dlfs) — the decentralised file system that can back a root - [Capabilities](../capabilities) — the UCAN model gating `file:` operations - [Creating Agents](../agents/creating-agents) — giving an agent `file:` tools and caps --- ## Grid Adapter The best model, the governed dataset, and the domain expert's operation rarely all live on *your* venue — that is the whole point of a grid. The Grid adapter lets workflows and agents use capability wherever it lives: operations run locally or on remote venues through exactly the same interface, while each party's data stays under its own governance and only results cross the boundary. ## Operations ### grid:run — Synchronous Execution Execute an operation and wait for the result. ```json { "operation": "v/ops/grid/run", "input": { "operation": "v/ops/json/merge", "input": { "values": [{ "a": 1 }, { "b": 2 }] } } } ``` Returns the operation's output directly. **Remote execution** — specify a venue URL or DID: ```json { "operation": "v/ops/grid/run", "input": { "operation": "v/ops/test/echo", "input": { "message": "hello" }, "venue": "https://other-venue.covia.ai" } } ``` ### grid:invoke — Asynchronous Execution Submit an operation and return immediately with a job ID. ```json { "operation": "v/ops/grid/invoke", "input": { "operation": "v/ops/langchain/openai", "input": { "prompt": "Summarise this document..." } } } ``` **Response:** ```json { "id": "0x1234...", "status": "PENDING", "created": 1712930400000 } ``` ### grid:jobStatus — Poll Job Check the current status of a previously submitted job. ```json { "operation": "v/ops/grid/job-status", "input": { "id": "0x1234..." } } ``` **Response:** ```json { "id": "0x1234...", "status": "COMPLETE", "created": 1712930400000, "updated": 1712930412000, "output": { "response": "The document describes..." } } ``` ### grid:jobResult — Wait for Result Block until a job completes and return its output. ```json { "operation": "v/ops/grid/job-result", "input": { "id": "0x1234..." } } ``` Returns the operation's output directly, or errors if the job failed. ## Operations Reference | Operation | Input | Description | |-----------|-------|-------------| | `grid:run` | `operation`, `input?`, `venue?` | Execute synchronously, return result | | `grid:invoke` | `operation`, `input?`, `venue?` | Execute asynchronously, return job ID | | `grid:jobStatus` | `id`, `venue?` | Poll job status | | `grid:jobResult` | `id`, `venue?` | Wait for job completion, return result | The `input` passed to `grid:run` / `grid:invoke` may be **any JSON value** — object, array, string, number, boolean, or null — not just an object. It is passed through to the target operation, which validates it against its own input schema. ## Operation References The `operation` parameter accepts multiple formats: | Format | Example | Description | |--------|---------|-------------| | Venue catalog path | `v/ops/json/merge` | Operation from the venue's catalog | | User pin | `o/my-pipeline` | Named operation from your workspace | | Asset ID | `0x7a8b9c0d...` | Content-addressed asset hash | | DID URL | `did:web:venue.example.com/v/ops/test/echo` | Operation on a specific remote venue | ## Patterns ### Fire and Forget ``` grid:invoke → get job ID → continue other work → grid:jobResult when ready ``` ### Fan-Out / Fan-In Submit multiple operations in parallel, then collect results: ``` grid:invoke op-A → job-1 grid:invoke op-B → job-2 grid:invoke op-C → job-3 grid:jobResult job-1 → result-A grid:jobResult job-2 → result-B grid:jobResult job-3 → result-C ``` ### Cross-Venue Federation Execute an operation on a remote venue — caller identity is automatically propagated for audit and access control: ```json { "operation": "v/ops/grid/run", "input": { "operation": "v/ops/covia/read", "input": { "path": "w/shared-data" }, "venue": "did:web:partner-venue.example.com" } } ``` ## Related - [REST API](/docs/user-guide/api) — HTTP endpoints for invoking operations - [Orchestrator](./orchestrator) — multi-step workflows using grid operations - [COG-007: Operations](/docs/protocol/cogs/COG-007) — Operation specification --- ## Human-in-the-Loop (HITL) # Human-in-the-Loop Adapter Some decisions must stay human: paying an invoice, approving a deployment, granting an agent new authority. The HITL adapter makes those decisions part of the workflow rather than an interruption to it — automated work pauses for a human answer (approval, choice, free text, or a capability grant) inside the same audited job machinery as everything else. A workflow or agent calls `hitl:request`; the job parks as `INPUT_REQUIRED` and a durable request record lands in the target user's `h/` inbox. When the human responds, the job completes with their answers (or fails on rejection or expiry). Three operations carry the whole protocol: | Operation | Who calls it | Purpose | |-----------|--------------|---------| | `hitl:request` | workflows, agents | Ask a human; job parks until resolved | | `hitl:respond` | the inbox owner | Answer or reject a request | | `hitl:list` | the inbox owner | List your own requests | ## Asking — hitl:request ```json { "operation": "v/ops/hitl/request", "input": { "title": "Pay invoice", "description": "Invoice INV-1 from Acme, £15,600.", "asks": [ { "id": "pay", "type": "approval", "prompt": "Approve payment?", "required": true } ] } } ``` Each entry in `asks` is a typed question: `text`, `approval` (boolean), `choice` (one option), `checkboxes` (several), or `token` (see below). `user` targets a responder DID — it defaults to the calling user, so an agent asks its **owner** with no extra setup; delivering into another user's inbox requires a `hitl/request` delegation from them. An optional `timeout` (seconds) expires the request; omit it and the request waits indefinitely. The human sees only the request record — title, description, asks — never the requesting conversation. ## Answering — hitl:respond ```json { "operation": "v/ops/hitl/respond", "input": { "id": "", "outcome": "answer", "answers": { "pay": true }, "comment": "approved" } } ``` Responding is authorised structurally — you can only resolve requests in your own inbox — and **agents can never answer**, only ask: an agent approving its own request would defeat the point, so the gate is identity, not capability scope. Rejections (`"outcome": "reject"`) fail the requester's job with the reject comment as the reason. ## Grants: consent that mints capability Approval asks and choice options can carry offered **grants** (`{with, can}`). The venue issues exactly the intersection of what the responder explicitly echoes back and what their choices actually triggered — issued as a single venue-signed [UCAN](../capabilities) with the requester as audience. Grant lifetimes default to 7 days; a venue-configured ceiling validates offers *before* delivery, so a human is never asked to approve an expiry the venue would later refuse. `token` asks go further: they request a **self-sovereign** UCAN that the human signs client-side with their own key — the venue verifies provenance but never mints or re-signs it, and the signed token is redacted from the durable record, delivered only in the requester's job output. This is the machinery behind "pause a workflow for a capability grant" ([COG-16](../../protocol/cogs/COG-016)): consent is a first-class, auditable step, and authority is conferred only as the consequence of an explicit human choice. ## Reliability details Requests survive restarts: parked jobs are recovered and expiry timers re-armed at venue launch, with a lazy check so an expired request can never be answered late. A resolved record is never overwritten — a race between expiry and response has exactly one winner. Discover pending work with `hitl:list` (`{"status": "open"}`); read a full record at `h/` with `covia:read`. ## Related - [COG-16: Human-in-the-Loop](../../protocol/cogs/COG-016) — the specification - [Capabilities (UCAN)](../capabilities) — grants, attenuation, delegation - [Agents](../agents/) — agents as requesters --- ## HTTP Adapter Agents and workflows constantly need the outside world — REST APIs, webhooks, data feeds — but an unguarded network hole is the classic way an agent platform gets burned. The HTTP adapter provides outbound HTTP as a governed operation: built-in SSRF protection, capability checks on every call, and a job record for every request that leaves the venue. ## Operations ### http:get — GET Request ```json { "operation": "v/ops/http/get", "input": { "url": "https://api.example.com/data", "headers": { "Accept": "application/json" }, "queryParams": { "q": "test", "limit": 10 } } } ``` ### http:post — POST Request (and PUT, DELETE, PATCH) ```json { "operation": "v/ops/http/post", "input": { "url": "https://api.example.com/submit", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "name": "Alice", "role": "analyst" } } } ``` The `method` parameter defaults to POST but can be set to `PUT`, `DELETE`, or `PATCH`. ### Bearer authentication via a secret To call an API that needs a bearer token without putting the token in plaintext, set `bearerSecret` to a secret reference. The venue resolves it from the caller's [secret store](./secret) at invocation time and sends it as `Authorization: Bearer ` — the token never appears in the operation input or the job record: ```json { "operation": "v/ops/http/get", "input": { "url": "https://api.example.com/private", "bearerSecret": "s/EXAMPLE_API_TOKEN" } } ``` For a plaintext token, set the `Authorization` header directly via `headers` instead. ### Response Format Every HTTP operation returns: ```json { "status": 200, "body": "{\"result\": \"success\"}", "headers": { "content-type": "application/json" } } ``` The `body` is always a string — JSON responses must be parsed by the caller. ## Input Reference | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `url` | string | Yes | Full URL (HTTP or HTTPS) | | `method` | string | No | HTTP method (GET, POST, PUT, DELETE, PATCH) | | `headers` | object | No | Request headers as key-value pairs | | `queryParams` | object | No | Query parameters (URL-encoded automatically) | | `body` | object | No | Request body (JSON-serialised automatically) | | `bearerSecret` | string | No | Secret reference (e.g. `s/MY_TOKEN`) resolved and sent as `Authorization: Bearer ` | ## Timeouts - Connection timeout: 10 seconds - Request timeout: 30 seconds - No automatic retries ## Security The adapter includes SSRF (Server-Side Request Forgery) protection: - Blocks requests to private/internal network addresses (loopback, site-local, link-local) by default - Only HTTP and HTTPS schemes are permitted - Venue operators can configure allow lists for specific internal hosts ## Related - [Grid Adapter](./grid-adapter) — for invoking operations on other Covia venues (use Grid, not HTTP) - [REST API](/docs/user-guide/api) — the venue's own HTTP API --- ## Adapters Adapters are how a venue gets its capabilities. Each adapter contributes a family of **operations** — self-describing, invocable units with JSON Schema inputs and outputs — that any client, agent, or orchestration can call. Out of the box a venue ships with nearly thirty adapters spanning LLMs, agents, human oversight, files, lattice state, governance, and federation; operators add more as [venue modules](./jvm#venue-modules). ## AI and agents | Adapter | Operations | What it does | |---------|-----------|--------------| | [LangChain](./langchain-adapter) | `langchain:*` | Call LLMs — OpenAI, Anthropic, Ollama, xAI, and OpenAI-compatible endpoints | | [Skills](./skills) | `v/ops/skills` | Skill discovery and retrieval; every venue ships a skill library | | [User Memory](./memory) | `v/ops/memory` | Durable per-user memory that agents recall every turn and edit by number | | [Human-in-the-Loop](./hitl) | `hitl:*` | Park a job for human approval, choices, or capability grants | Agent execution (`agent:*`, `llmagent:*`, `goaltree:*`) is documented under [Agents](../agents/). ## Federation and connectivity | Adapter | Operations | What it does | |---------|-----------|--------------| | [Grid](./grid-adapter) | `grid:*` | Invoke operations on local or remote venues (federation) | | [Orchestrator](./orchestrator) | *(adapter, not ops)* | Multi-step DAG workflows, defined as operation assets and run via `grid:run` | | [MCP](./covia-with-mcp) | `mcp:*` | Call external MCP servers; bridge their tools into the catalog | | [A2A](./covia-with-a2a) | `a2a:*` | Call remote A2A agents; serve A2A inbound | | [HTTP](./http-adapter) | `http:*` | SSRF-protected outbound HTTP requests | | [Convex](./convex) | `convex:*` | Query and transact against the Convex lattice network | ## State and data | Adapter | Operations | What it does | |---------|-----------|--------------| | [Covia (Venue)](./venue-adapter) | `covia:*` | CRUD over the venue's lattice state | | [Assets](./asset) | `asset:*` | Immutable, content-addressed asset store — store, read, list, pin | | [Secrets](./secret) | `secret:*` | Per-user encrypted secret store; `s/NAME` references | | [File](./file) | `file:*` | Root-jailed local filesystem access | | [DLFS](./dlfs) | `dlfs:*` | Decentralised, user-signed file system (WebDAV) | | [Vault](./vault) | `vault:*` | Simplified, fixed-drive file access | | [Archive](./archive) | `archive:*` | Zip handling — jailed, zip-slip-proof, bomb-guarded | | [JSON](./json) | `json:*` | Pure JSON data manipulation (merge, select, …) | | [Schema](./schema) | `schema:*` | JSON Schema validate, infer, coerce — pure and sub-millisecond | | [Scheduler](./scheduler) | `scheduler:*` | Run operations later; deferred and agent wakes | ## Identity and governance | Adapter | Operations | What it does | |---------|-----------|--------------| | [UCAN](./ucan) | `ucan:*` | Issue and verify capability tokens — authority as an inspectable artifact | | [Auth](./auth) | `auth:*` | Caller identity diagnostics (`whoami`) | | [Users](./user) | `user:*` | Administrative user registration and authenticator key management | ## Extensibility | Adapter | Operations | What it does | |---------|-----------|--------------| | [JVM](./jvm) | `jvm:*` | In-process utility ops, plus the venue-module mechanism for arbitrary JVM code | | [SQL](./sql) *(module)* | `sql:*` | Governed SQL over any JDBC database, delegable per database | | [Python](./python) *(module)* | `python:*` | Operator-configured Python operations, in-process via FFM | SQL and Python are optional [venue modules](./jvm#venue-modules) — separate artifacts on Maven Central (`ai.covia:covia-sql`, `ai.covia:covia-python-adapter`), loaded by config. ## How operations are named and invoked Operations are written as `adapter:op` (e.g. `grid:run`) or as a venue catalog path (`v/ops/grid/run`). Invoke them over [REST](../api/) (`POST /api/v1/invoke`), through an [SDK](../sdk/), as [MCP](../mcp/) tools, or as agent tools. Because every operation is self-describing, both humans and agents can discover and call them. --- ## JSON # JSON Adapter Orchestrations are full of small data plumbing — merge these step outputs, pick that branch — and reaching for an LLM or custom code for it is waste. The JSON adapter covers the plumbing with four pure data manipulation primitives for composing structured outputs and declarative branching. All operations are pure functions with sub-millisecond execution — no IO, no external calls, no tokens spent. ## Operations ### json:merge — Deep Merge Merges an array of maps using [RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396) JSON Merge Patch semantics. Later values win on key conflict; nested maps merge recursively; `null` values delete keys. ```json { "operation": "v/ops/json/merge", "input": { "values": [ { "decision": "ESCALATED", "tier": "manager" }, { "invoice": { "vendor": "Acme Corp", "amount": 15600 } }, { "policy_passed": true } ] } } ``` **Result:** ```json { "decision": "ESCALATED", "tier": "manager", "invoice": { "vendor": "Acme Corp", "amount": 15600 }, "policy_passed": true } ``` Use `merge` to compose outputs from multiple pipeline steps (e.g., LLM decision + invoice data + policy rules). ### json:cond — Conditional Selection Returns the `then` value of the first case whose `when` is truthy. Only `null` and `false` are falsy — everything else (including `0`, `""`, `[]`, `{}`) is truthy. ```json { "operation": "v/ops/json/cond", "input": { "cases": [ { "when": false, "then": { "route": "auto-approve" } }, { "when": true, "then": { "route": "manager", "approver": "J. Martinez" } }, { "when": false, "then": { "route": "vp" } } ], "default": { "route": "unknown" } } } ``` **Result:** `{ "route": "manager", "approver": "J. Martinez" }` Use `cond` for declarative branching in orchestrations without invoking an LLM. ### json:assoc — Set Value at Path Sets a value at a path in a map, creating intermediate maps for missing keys. Equivalent to Clojure `assoc-in` or Lodash `_.set`. ```json { "operation": "v/ops/json/assoc", "input": { "target": { "user": { "name": "alice" } }, "path": ["user", "email"], "value": "alice@example.com" } } ``` **Result:** `{ "user": { "name": "alice", "email": "alice@example.com" } }` The `path` can be a single string (top-level key) or an array of strings (nested path). Missing intermediate maps are created automatically. ### json:select — Pick by Discriminator Looks up a value in a map of cases by a discriminator key. Like a switch statement. ```json { "operation": "v/ops/json/select", "input": { "key": "manager", "cases": { "auto": { "decision": "APPROVED" }, "manager": { "decision": "ESCALATED", "target": "J. Martinez" }, "vp": { "decision": "ESCALATED", "target": "VP Finance" } } } } ``` **Result:** `{ "decision": "ESCALATED", "target": "J. Martinez" }` Returns `default` if the key is not found, or `null` if no default is provided. ## Operations Reference | Operation | Input | Description | |-----------|-------|-------------| | `json:merge` | `values` (array of maps) | RFC 7396 deep merge | | `json:cond` | `cases` (array of `{when, then}`), `default?` | First truthy case | | `json:assoc` | `target?`, `path`, `value` | Set value at nested path | | `json:select` | `key`, `cases` (map), `default?` | Switch on discriminator | ## Use Cases - **Pipeline composition** — `merge` combines outputs from multiple steps into a single result - **Declarative routing** — `cond` and `select` branch without LLM calls, keeping orchestrations fast and deterministic - **Data enrichment** — `assoc` adds fields to records before passing them downstream ## Related - [Orchestrator](./orchestrator) — workflow coordination using these primitives - [Agents](/docs/user-guide/agents/) — agents can call JSON operations as tools --- ## JVM # JVM Adapter and Venue Modules Sometimes the capability you need is code you already have — and it deserves the same catalog entry, capability checks, and audit trail as everything else. That is what the JVM story delivers, at two levels: this adapter ships a few pure in-process utility operations — `v/ops/jvm/string-concat`, `v/ops/jvm/url-encode`, `v/ops/jvm/url-decode` — as orchestration glue: ```json { "operation": "v/ops/jvm/string-concat", "input": { "first": "apple", "second": "banana", "separator": "|" } } ``` The more interesting story is the extension point behind it: **venue modules**, the mechanism by which arbitrary JVM code becomes first-class grid operations. ## Venue modules A module is an external jar compiled against the venue API that declares its adapter classes via `META-INF/services`. The operator lists it in venue config: ```json { "modules": [ "modules/covia-sql-module.jar", { "path": "modules/custom.jar", "sha256": "9f2a...", "config": {} } ] } ``` Modules load at boot, before catalog materialisation, so their operations appear in `v/ops/` alongside the built-ins — with the same catalog entries, capability checks, argument defaults, and audit trail. Each module gets its own isolating classloader for dependency separation; an optional `sha256` pins the exact jar content. The security posture is deliberate: loading is **operator-only and fail-fast** — a missing jar or a bad hash is a boot error, and there is no runtime module-load operation, because in-process code is total compromise and deserves explicit, restart-gated intent. The shipped [SQL](./sql) and [Python](./python) adapters are venue modules — working references for writing your own. ## Related - [SQL (module)](./sql) · [Python (module)](./python) — shipped module examples - [Configuration](/docs/operator-guide/configuration) — venue config reference --- ## LangChain Adapter Every AI workflow needs a model call somewhere, and nobody wants to wire a different vendor SDK for each provider — or lose governance the moment a prompt leaves the building. The LangChain adapter gives the venue one governed LLM surface: OpenAI, Anthropic, Ollama, and xAI, plus any OpenAI-compatible endpoint, behind a consistent interface for messages, tool calling, and structured output. Swap providers without touching the calling code; every call is a job in the audit trail. This adapter powers the Level 3 (LLM call) layer in the [agent architecture](/docs/user-guide/agents/#architecture). It can also be invoked directly for one-shot LLM calls. Covia is model-agnostic; the default models below are what a backend uses when you don't set `model`, not a recommendation. ## Backends | Operation | Provider | Default Model | API Key Secret | |-----------|----------|---------------|----------------| | `langchain:openai` | OpenAI | `gpt-5.4-mini` | `OPENAI_API_KEY` | | `langchain:anthropic` | Anthropic | `claude-sonnet-4-6` | `ANTHROPIC_API_KEY` | | `langchain:ollama` | Ollama (local) | `qwen` | None required | | `langchain:xai` | xAI (Grok) | `grok-4` | `XAI_API_KEY` | Other OpenAI-compatible providers (DeepSeek, Google Gemini, gateways) work through `langchain:openai` by setting the `url` field — e.g. `"url": "https://api.deepseek.com/v1"`. ### Setting API Keys Store API keys via the secrets manager: ```json { "operation": "v/ops/secret/set", "input": { "key": "OPENAI_API_KEY", "value": "sk-..." } } { "operation": "v/ops/secret/set", "input": { "key": "ANTHROPIC_API_KEY", "value": "sk-ant-..." } } ``` Keys are resolved from the caller's secret store at invocation time. A backend that requires a key but can't resolve one fails fast with an error naming the secret it expected — it does not silently continue. ## Usage ### Simple Prompt ```json { "operation": "v/ops/langchain/openai", "input": { "prompt": "What is the capital of France?", "model": "gpt-5.4-mini" } } ``` **Response:** `{ "response": "The capital of France is Paris." }` ### Messages with System Prompt ```json { "operation": "v/ops/langchain/anthropic", "input": { "model": "claude-sonnet-4-6", "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "Explain lattice technology in one paragraph." } ] } } ``` **Response:** ```json { "role": "assistant", "content": "Lattice technology is a mathematical framework..." } ``` ### Tool Calling Provide tool definitions and the LLM can request tool calls: ```json { "operation": "v/ops/langchain/openai", "input": { "model": "gpt-5.4-mini", "messages": [ { "role": "user", "content": "What's the weather in London?" } ], "tools": [ { "name": "get_weather", "description": "Get current weather for a city", "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] } } ] } } ``` **Response:** ```json { "role": "assistant", "toolCalls": [ { "id": "call_abc123", "name": "get_weather", "arguments": { "city": "London" } } ] } ``` To continue the conversation, execute the tool and send the result back: ```json { "messages": [ { "role": "user", "content": "What's the weather in London?" }, { "role": "assistant", "toolCalls": [{ "id": "call_abc123", "name": "get_weather", "arguments": { "city": "London" } }] }, { "role": "tool", "id": "call_abc123", "name": "get_weather", "content": "{\"temp\": 15, \"condition\": \"cloudy\"}" } ], "tools": [...] } ``` ### Structured Output Request JSON output conforming to a schema: ```json { "operation": "v/ops/langchain/openai", "input": { "model": "gpt-5.4-mini", "messages": [ { "role": "user", "content": "Extract the person's name and age from: 'Alice is 30 years old'" } ], "responseFormat": { "name": "PersonInfo", "schema": { "type": "object", "properties": { "name": { "type": "string" }, "age": { "type": "integer" } }, "required": ["name", "age"], "additionalProperties": false } } } } ``` The OpenAI backend enables **strict mode** by default — the response is guaranteed to conform to the schema. ## Input Reference | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `prompt` | string | — | Simple prompt (alternative to `messages`) | | `systemPrompt` | string | — | System message (with `prompt` mode) | | `messages` | array | — | Conversation messages (system, user, assistant, tool) | | `tools` | array | — | Tool definitions for function calling | | `model` | string | Backend default | Model name override | | `responseFormat` | string or object | `"text"` | `"text"`, `"json"`, or `{name, schema}` | | `apiKey` | string | From secret store | Override API key | | `url` | string | Backend default | Override API endpoint (use for OpenAI-compatible providers) | Assistant responses also carry `tokens` (`{input, output, total}`) and `finishReason` when the provider reports them. ## Related - [Agents: LLM Backends](/docs/user-guide/agents/llm-backends) — configuring backends for agents - [Agents Overview](/docs/user-guide/agents/) — how LangChain fits in the agent architecture --- ## User Memory # User Memory Adapter Nobody should have to repeat their preferences to every agent, every session. The memory adapter gives every user — and every agent acting for them — a durable, editable memory that agents carry into each turn, with a deliberately tiny surface: one operation, `v/ops/memory`, dispatched by a `command` field (`recall`, `remember`, `update`, `forget`). One tool definition instead of four keeps the cost in an agent's tool palette minimal. Memory lives as a plain vector at a workspace path (default `w/memory`) in the calling user's namespace — queryable, governed lattice state like everything else, not a private database. Storage delegates to [`covia:read`/`covia:write`](./venue-adapter), so per-user scoping and capability enforcement are inherited rather than reimplemented. ## Operations ### memory remember — Append an Item ```json { "operation": "v/ops/memory", "input": { "command": "remember", "text": "Prefers metric units" } } ``` Returns `{remembered: true, n, count}` — `n` is the new item's number. ### memory recall — Render the List Returns the memory as a numbered text block (`1. …`), or `null` when empty. Agents rarely call this directly: list it in `config.context` and the memory is injected into the agent's context every turn, freshly rendered. ```json { "operation": "v/ops/memory", "input": { "command": "recall" } } ``` `recall` can also point at a map of records (e.g. a slug-keyed store): entries render sorted, `displayField` picks the line text (default `"text"`), `noteField` appends a note. Entries marked inactive, held, or merged are skipped. ### memory update / forget — Edit by Number ```json { "operation": "v/ops/memory", "input": { "command": "update", "n": 2, "text": "Prefers SI units" } } ``` ```json { "operation": "v/ops/memory", "input": { "command": "forget", "n": 2 } } ``` `n` is the 1-based number as shown by `recall` — the number the agent sees is the number it edits. Both refuse to operate on anything that is not a flat list, so a structured store can never be clobbered by a stray edit. ## Input Reference | Field | Type | Required | Notes | |-------|------|----------|-------| | `command` | `recall` \| `remember` \| `update` \| `forget` | required | | | `text` | string | `remember`, `update` | the item text | | `n` | integer | `update`, `forget` | 1-based item number | | `path` | string | optional | memory list path, default `w/memory` | | `displayField`, `noteField` | string | optional | `recall` over map collections | ## Access Control and Durability Authentication is required — memory is always some user's memory. Items are stored as `{text, ts}` (with `updated` stamped on edits), and every mutation rewrites the whole vector, so a forgotten item is durably gone — it cannot re-materialise through a partial merge. ## Related - [Context in Covia](/docs/overview/context) — the context layer this feeds - [Tools and Context](../agents/tools-and-context) — `config.context` injection - [Covia (Venue) Adapter](./venue-adapter) — the underlying workspace CRUD --- ## Orchestrator Multi-step AI pipelines deserve better than glue code in a script — they should be declared once, audited end to end, and runnable anywhere on the grid. The Orchestrator does exactly that: workflows are defined as operation assets and executed as directed acyclic graphs (DAGs), with steps running in parallel where possible, automatic dependency resolution, and result composition — one auditable job for the whole pipeline. ## How It Works An orchestration is a sequence of steps. Each step invokes a grid operation and can reference outputs from earlier steps. Independent steps run in parallel; dependent steps wait for their inputs. ``` Step 0: Extract invoice data ─┐ Step 1: Look up vendor ─┤── (parallel, no dependencies) Step 2: Validate (needs 0 + 1) ─┘── (waits for both) Step 3: Approve (needs 2) ──── (sequential) ``` ## Defining an Orchestration Orchestrations are defined as operation assets with an `orchestrator` adapter: ```json { "name": "Invoice Pipeline", "operation": { "adapter": "orchestrator", "steps": [ { "op": "v/ops/agent/request", "input": { "agentId": ["const", "Alice"], "input": ["input", "invoice"], "timeout": ["const", 60000] } }, { "op": "v/ops/agent/request", "input": { "agentId": ["const", "Bob"], "input": [0, "output"], "timeout": ["const", 60000] } }, { "op": "v/ops/agent/request", "input": { "agentId": ["const", "Carol"], "input": [1, "output"], "timeout": ["const", 60000] } } ], "result": { "scanned": [0, "output"], "enriched": [1, "output"], "decision": [2, "output"] } } } ``` ## Input Resolution Step inputs are built using a resolution syntax that references the orchestration input, constants, or outputs from earlier steps: | Syntax | Meaning | Example | |--------|---------|---------| | `["input", "key"]` | Extract from orchestration input | `["input", "invoice"]` | | `["const", value]` | Constant value | `["const", true]` | | `[N, "field"]` | Output from step N | `[0, "output"]` | | `["concat", parts...]` | String concatenation | `["concat", "prefix-", ["input", "id"]]` | Resolution is recursive — maps and arrays within step inputs are resolved recursively. **Dependency rule:** Step N can only reference steps 0 to N-1. This enforces a DAG structure with no circular dependencies. ## Execution 1. Steps with no dependencies start immediately (in parallel, using virtual threads) 2. As each step completes, dependent steps become ready and start 3. When all steps complete, the `result` expression is resolved and returned 4. If a step fails and `strict` mode is on, the orchestration fails immediately ## Step Structure ```json { "op": "v/ops/agent/request", "input": { ... }, "venue": "https://remote-venue.example.com", "strict": true } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `op` | string | Yes | Operation to invoke | | `input` | object | No | Input with resolution expressions | | `venue` | string | No | Remote venue URL or DID (omit for local) | | `strict` | boolean | No | Validate output against operation schema | | `foreach` | object | No | Run the step once per element of a collection — see [Fan out over a collection](#fan-out-over-a-collection-foreach) | ## Result Composition The `result` field defines what the orchestration returns, using the same resolution syntax: ```json "result": { "scanned": [0, "output"], "enriched": [1, "output"], "decision": [2, "output"], "timestamp": ["const", "2026-04-12"] } ``` ## Error Handling - If `strict: true` on a step, its output is validated against the operation's schema — violations fail the orchestration immediately - If a step's operation fails, all steps depending on it also fail - Independent steps continue running even if a sibling fails (unless global strict mode is set) ## Patterns ### Fan out over a collection (foreach) A step with a `foreach` object runs its operation once per element, as concurrent child jobs, and returns an ordered array of the per-element outputs: ```json "steps": [ { "op": "v/ops/covia/read", "input": { "path": ["const", "w/invoices"] } }, { "op": "v/ops/agent/request", "foreach": { "in": [0, "value"], "maxConcurrency": 4 }, "input": { "agentId": ["const", "Classifier"], "input": { "invoice": ["item"], "position": ["index"] }, "timeout": ["const", 60000] } } ] ``` Inside a foreach step's `input`, `["item"]` binds the current element (`["item", "field"]` for a path within it) and `["index"]` its zero-based position. Arrays iterate element-wise; maps iterate as `[key, value]` pairs. Output order matches source order regardless of completion order. The first failing element fails the step (in-flight elements finish), and the venue caps collection size and concurrency — see [COG-12 § Foreach Steps](/docs/protocol/cogs/COG-012#foreach-steps) for the full semantics. ### Agent Pipeline Chain agents where each processes the previous one's output: ```json "steps": [ { "op": "v/ops/agent/request", "input": { "agentId": ["const", "Scanner"], "input": ["input"], "timeout": ["const", 60000] } }, { "op": "v/ops/agent/request", "input": { "agentId": ["const", "Enricher"], "input": [0, "output"], "timeout": ["const", 60000] } }, { "op": "v/ops/agent/request", "input": { "agentId": ["const", "Approver"], "input": [1, "output"], "timeout": ["const", 60000] } } ] ``` ### Parallel Fan-Out Independent steps run concurrently: ```json "steps": [ { "op": "v/ops/http/get", "input": { "url": ["const", "https://api-a.example.com/data"] } }, { "op": "v/ops/http/get", "input": { "url": ["const", "https://api-b.example.com/data"] } }, { "op": "v/ops/json/merge", "input": { "values": [[0, "body"], [1, "body"]] } } ] ``` Steps 0 and 1 run in parallel; step 2 waits for both. ### Cross-Venue Federation Route individual steps to remote venues: ```json "steps": [ { "op": "v/ops/covia/read", "input": { "path": ["const", "w/data"] } }, { "op": "v/ops/covia/read", "input": { "path": ["const", "w/data"] }, "venue": "did:web:partner.example.com" }, { "op": "v/ops/json/merge", "input": { "values": [[0, "value"], [1, "value"]] } } ] ``` ## Related - [Grid Adapter](./grid-adapter) — individual operation invocation - [JSON Adapter](./json) — data manipulation primitives for result composition - [Agents](/docs/user-guide/agents/) — agent-based step execution - [COG-012: Orchestrations](/docs/protocol/cogs/COG-012) — Protocol specification --- ## Python (module) # Python Adapter Much of the world's analytical, scientific, and ML code lives in Python — and rewriting it is not a plan. The Python adapter — the optional [`ai.covia:covia-python-adapter`](https://central.sonatype.com/artifact/ai.covia/covia-python-adapter) venue module — brings that code onto the grid as first-class operations: operator-configured Python functions, published, capability-checked, and audited. CPython runs **in-process** via the Java FFM API (Java 22+, CPython 3.10–3.14), with Convex values converting natively across the boundary — no subprocess, no serialisation tax. The security model is deliberate: **there is no `eval`**. Callers can never submit Python source, host paths, or function names — the operator selects every script and allowlists every callable. Caller-selected code would turn venue invocation into host code execution, so the surface simply doesn't exist. Python runs with the venue process's authority; scripts are trusted operator code (use containers for anything else). ## Configured operations Each operation declared in the module config installs at `v/ops/python/` with the JSON Schemas the operator gives it, and behaves like any native operation — discoverable, capability-checked, audited, callable as an MCP tool: ```json { "operation": "v/ops/python/health/score", "input": { "x": 41 } } ``` The configured function (default `main`) receives the whole input as one argument and its return value converts back to the result. ## Stateful instances For session-style work, the module can also expose **instances**: isolated, stateful Python namespaces created from operator-approved templates, each with an explicit allowlist of callable functions. ```json { "operation": "v/ops/python/instances/create", "input": { "template": "worker" } } ``` ```json { "operation": "v/ops/python/instances/call", "input": { "id": "", "function": "add", "args": [5, 7] } } ``` `instances/list` shows your live instances; `instances/close` releases one. State (module globals) persists across calls until close or venue restart. Instances are owned by the calling user — other users' instances are invisible, agents share their owner's, and anonymous callers are refused. `maxPerUser` (default 8) and `maxTotal` (default 128) bound retained native state. ## Enabling the module Unlike SQL, Python's configuration lives in the module entry itself: ```json { "modules": [{ "path": "modules/covia-python-adapter--module.jar", "config": { "library": "/usr/lib/libpython3.13.so", "operations": { "health/score": { "script": "/opt/venue/python/health_score.py" } }, "instances": { "templates": { "worker": { "script": "/opt/venue/python/worker.py", "functions": ["add", "summary", "reset"] } } } } }] } ``` Omit `instances` and no instance surface exists at all. If FFM or CPython is unavailable the module degrades gracefully — the venue starts, the adapter stays inactive with a warning. ## Related - [Venue modules](/docs/operator-guide/configuration) — loading and pinning module jars - [SQL (module)](./sql) — the other shipped extension module - [MCP integration](../mcp/) — configured operations as MCP tools --- ## Scheduler Adapter The Scheduler adapter runs an operation **later** — at an absolute time or after a delay. It's how a venue defers work: a reminder, a delayed clean-up, or an agent that should wake at a set time. (There is no built-in recurrence — a repeating job re-schedules itself as its last action.) Scheduled invocations run with the **authority of whoever scheduled them**, captured at schedule time and replayed unchanged when they fire. ## Operations | Operation | Purpose | |-----------|---------| | `scheduler:schedule` | Schedule an operation to run at a future time | | `scheduler:list` | List your pending scheduled events | | `scheduler:trigger` | Fire a scheduled event now | | `scheduler:cancel` | Cancel a scheduled event | ## Scheduling Give `scheduler:schedule` the operation to invoke, its input, and *when* — either `time` (absolute, milliseconds since the epoch) or `after` (milliseconds from now): ```json { "operation": "v/ops/scheduler/schedule", "input": { "operation": "v/ops/agent/message", "input": { "agentId": "Alice", "message": "Daily summary due" }, "after": 3600000 } } ``` It returns a `handle` (an opaque token) and the resolved `time`: ```json { "handle": "0x4f3a...", "time": 1749650400000 } ``` Keep the `handle` — you use it to cancel or trigger the event. ## Listing, triggering, cancelling ```json { "operation": "v/ops/scheduler/list" } ``` `scheduler:list` returns your pending events as `{ handle, op, time }`, soonest first. It is **scoped to the caller** — you only see events you scheduled. ```json { "operation": "v/ops/scheduler/trigger", "input": { "handle": "0x4f3a..." } } // fire now → { triggered, result } { "operation": "v/ops/scheduler/cancel", "input": { "handle": "0x4f3a..." } } // → { cancelled: true|false } ``` ## Captured authority — no escalation When you schedule an event, the venue records your caller DID together with the UCAN **proofs and capabilities** present on the request. When the event fires, it runs under *exactly* that captured authority — not the venue's ambient authority and not a refreshed set of permissions. This means a scheduled invocation can never do more than you could do at the moment you scheduled it. If your capabilities wouldn't allow the operation now, the deferred run won't be allowed either. Scheduling requires an authenticated caller. ## Durability Scheduled events are stored on the lattice, so they survive a venue restart. On boot the venue re-arms its in-memory timer from the persisted schedule, and any events whose time has already passed fire immediately as a catch-up. ## Waking agents Agent scheduling is built on this adapter. A [session](../agents/sessions) or task can carry a `wakeTime`; the venue derives a single wake per agent at the earliest pending time and schedules an `agent:trigger` (with `force: false`) to fire then. Because the wake times live on the lattice, the venue rebuilds every agent's wake from state on startup — a crash can't lose a pending wake. You normally don't call the scheduler directly for this; set the wake time and the framework arms it. ## Related - [Sessions](../agents/sessions) — how agent wakes use the scheduler - [Capabilities](../capabilities) — the authority captured and replayed at fire time - [Grid Adapter](./grid-adapter) — invoking operations across venues --- ## Schema # Schema Adapter Data that crosses boundaries — between workflow steps, between agents, between venues — needs checking, and LLM output doubly so. The schema adapter is that guard rail, exposing JSON Schema operations as grid tools: validate, infer, coerce, and check. All five are **pure JVM functions — no IO, no LLM, sub-millisecond** — cheap enough to guard every boundary and safe to call in tight orchestration loops. ## Operations | Operation | Purpose | |-----------|---------| | `schema:validate` | Validate a value against a schema; reports the first violation | | `schema:validateAll` | Validate and return **all** violations (`v/ops/schema/validate-all`) | | `schema:infer` | Infer the tightest schema accepting an example value | | `schema:coerce` | Coerce a value toward a schema (string→number, string→boolean, …) | | `schema:check` | Check a schema is itself structurally well-formed | ```json { "operation": "v/ops/schema/validate", "input": { "schema": { "type": "object", "properties": { "a": { "type": "number" } } }, "value": { "a": 1 } } } ``` Returns `{valid: true}` — or `{valid: false, error}` with the violation. `schema:infer` powers the site's canonical first example: give it `{"name": "Ada", "age": 36}` and it hands back the schema. ## Boundary discipline Two rules make these operations trustworthy at trust boundaries: - **Values are judged exactly as given.** A JSON-looking string (`"{\"a\":1}"`) is a string — it is never silently reparsed into the structure it resembles; `infer` on `"[1,2,3]"` yields `{"type": "string"}`. - **LLM-friendly schema input.** The `schema` parameter tolerates a string-encoded JSON object (LLM callers often stringify); a malformed string is reported with its real parse cause rather than a generic error. Only the baseline `invoke` capability is required; no state is touched. ## Related - [Orchestrator](./orchestrator) — schema ops as workflow guards - [JSON Adapter](./json) — pure data manipulation alongside validation --- ## Secrets # Secret Adapter The Secret adapter manages the venue's **per-user encrypted secret store** — the home for API keys and other credentials that operations need at run time but that must never appear in job records, agent context, or operation input. The core idea: you store a secret once, then **reference it by name** (`s/NAME`) wherever an operation accepts a secret reference. The venue resolves the reference at invocation time, on the caller's behalf, and the plaintext value never travels through the Grid. ## Storing a secret ```json { "operation": "v/ops/secret/set", "input": { "name": "OPENAI_API_KEY", "value": "sk-..." } } ``` Returns `{ "name": "OPENAI_API_KEY", "stored": true }`. The value is encrypted into the caller's own store, and because the operation declares `value` as a secret field, it is **redacted in the job record** — the write itself leaves no plaintext in durable history. Storing requires an authenticated caller holding the `secret/write` ability on `s/`. Authenticated users hold this by default; the anonymous [public capability ceiling](../../protocol/cogs/COG-010) does not, so unauthenticated callers cannot write secrets. ## Referencing secrets: `s/NAME` Operations that need a credential accept a **secret reference** instead of a plaintext value, resolved from the *caller's* store at invocation time: - The [HTTP adapter](./http-adapter)'s `bearerSecret` — the venue sends `Authorization: Bearer ` without the token appearing in input or job records. - The [LangChain backends](./langchain-adapter) resolve provider keys (`s/OPENAI_API_KEY`, `s/ANTHROPIC_API_KEY`, …) the same way — a missing key fails fast, naming the reference it looked for. - [Bridged MCP servers](../mcp/calling-mcp-tools#authentication-for-bridged-servers) resolve their `auth` secret per caller. Because resolution is per-DID, two users invoking the same operation use their own keys — a venue never shares one credential across callers. ## Operations Reference | Operation | Catalog path | Input | Description | |-----------|--------------|-------|-------------| | `secret:set` | `v/ops/secret/set` | `name`, `value` | Store an encrypted secret in the caller's store | | `secret:extract` | `v/ops/secret/extract` | `name` | Read a secret's plaintext back. **Currently denied for all callers** pending capability-gated extraction — pass an `s/` reference instead | The refusal of `extract` is deliberate: day-to-day use never needs plaintext back out, and keeping extraction closed until UCAN-gated grants land means a compromised agent cannot exfiltrate its owner's keys. ## REST and SDK access The store is also exposed directly over [REST](../api/#secrets) — `GET /api/v1/secrets` lists the caller's secret **names** (values are never returned), `PUT /api/v1/secrets/{name}` stores one — and through the SDK secret managers (`venue.secrets.set/list/delete`). ## Related Documentation - [HTTP Adapter](./http-adapter) - `bearerSecret` for authenticated HTTP calls - [LangChain Adapter](./langchain-adapter) - provider API keys - [Capabilities](../capabilities) - the authorisation model behind `secret/write` - [REST API § Secrets](../api/#secrets) - the HTTP surface --- ## Skills(Adapters) # Skills Adapter Agents shouldn't need every procedure configured up front — they should find expertise at the moment a task calls for it. The skills adapter is how they do: the read surface for [skills](../agents/tools-and-context#skills), the named bundles of instructions, context, and tools that agents load on demand. It exposes a single operation, `v/ops/skills`, dispatched by a `command` field, so it costs one tool definition in an agent's palette rather than two. It is deliberately read-only: skills are authored with the ordinary write surfaces — [`covia:write`](./venue-adapter) for workspace skills, [`asset:store`](./asset) for immutable published ones. The same resolution logic backs the agent runtime's skills index and `skill_load` tool, so what you see here is exactly what a loading agent receives. ## Operations ### skills list — Render the Index Returns the skill index — one `- name — description` line per skill — across the given sources, searched in order. A skill in `w/skills` shadows a venue skill of the same name. ```json { "operation": "v/ops/skills", "input": { "command": "list", "sources": ["w/skills", "v/skills"] } } ``` `sources` is optional and defaults to `["w/skills", "v/skills"]`. Entries are directory paths or content-addressed asset refs (`a/`). When no skills exist the result is `null`, not an empty string — used as a `config.context` assemble-op, the context entry is simply skipped. ### skills read — Fetch One Skill Returns a single skill in full: `{name, description, tools, path, id, body?, context?}`. `body` is absent for a pure toolset; `context` appears only when the skill bundles context entries. ```json { "operation": "v/ops/skills", "input": { "command": "read", "name": "workspace" } } ``` Address the skill with exactly one of `name` (looked up across sources) or `ref` (a direct address such as `v/skills/workspace`, `w/skills/reports`, or `a/`). ## Access Control No authentication is required — `v/skills` is publicly discoverable by design. Gating is per source: content-addressed refs pin `asset/read` and path sources pin `crud/read`, both inside the anonymous read-only grant scope. Loading a skill grants no authority either way — its tools are capability-checked at invocation. ## The Venue Skill Library Every venue materialises a built-in library at `v/skills/` covering its own mechanisms — workspace, assets, agents, orchestration, grid, secrets, scheduling, memory, MCP, HITL, skill-authoring, and more (22 skills out of the box). Module adapters ship their own: load the [SQL module](./sql) and its `sql` skill appears alongside. Agent templates declare `skills: ["w/skills", "v/skills"]` by default, so a new agent's index shows the whole library from its first turn. ## Related - [Skills in Covia](/docs/overview/skills) — why skills are a differentiator - [Teach an Agent a New Skill](../tutorials/skills) — authoring end to end - [Skills reference](../agents/tools-and-context#skills) — loading, budgets, shadowing - [COG-18: Skills](../../protocol/cogs/COG-018) — the specification --- ## SQL (module) # SQL Adapter Most of the world's operational data lives in SQL databases — and agents become far more useful the moment they can reach it safely. The SQL adapter — the optional [`ai.covia:covia-sql`](https://central.sonatype.com/artifact/ai.covia/covia-sql) venue module — makes that safe reach real, turning SQL databases into governed grid operations: `sql:query` for reads, `sql:execute` for DDL/DML. Any JDBC database can sit behind it, and every call passes through Covia's capability checks and lands in the audit trail like any other operation. Callers name a **database, never a JDBC URL** — connection strings live only in operator config, which removes the whole class of attacks where a caller points the venue at an internal host or local file. ## Operations ### sql:query — Read Rows ```json { "operation": "v/ops/sql/query", "input": { "db": "warehouse", "statement": "SELECT id, reading FROM readings WHERE id = ?", "params": [7] } } ``` Returns `{columns, rows, count}` — plus `truncated: true` whenever rows were dropped by the cap (`maxRows`, default 1000, hard cap 10,000). Truncation is never silent. Always pass values via `params` and `?` placeholders rather than interpolating into the statement. ### sql:execute — Write ```json { "operation": "v/ops/sql/execute", "input": { "db": "warehouse", "statement": "INSERT INTO readings VALUES (?, ?)", "params": [7, "healthy"] } } ``` Returns `{updateCount}` (0 for DDL). ## Databases: shared or personal A `db` name resolves in one of two ways: - **Operator-registered connection** — an entry under `adapters.sql.databases.` in venue config, shared by all callers. Any JDBC driver on the classpath works; passwords should be [secret references](./secret) (`s/`), resolved at connect time. - **Venue-local database** — when no registered entry matches, the venue creates a lattice-backed database scoped to the calling user on first use. Two users using the same `db` name get two isolated databases; an agent shares its owner's. Persistent when `adapters.sql.path` is set, in-memory otherwise. ```json { "modules": ["modules/covia-sql--module.jar"], "adapters": { "sql": { "path": "storage/sql.etch", "databases": { "warehouse": { "url": "jdbc:postgresql://db.internal:5432/warehouse", "user": "covia", "password": "s/warehouse-db" } } } } } ``` ## Capability precision Beyond the standard `invoke` check, every call requires the ability `sql/query` or `sql/execute` on the resource **`sql/`**. A [UCAN grant](../capabilities) can therefore delegate exactly one database, read-only — to an agent, or across a trust boundary — and nothing else. ## Agent-ready The module ships its own [skill](./skills), installed at `v/skills/sql` when loaded: any agent on the venue can discover and pick up governed SQL mid-task, with the parameterisation rules included in what it learns. ## Related - [Venue modules](/docs/operator-guide/configuration) — loading and pinning module jars - [Secrets](./secret) — secret references for connection passwords - [Capabilities (UCAN)](../capabilities) — per-database delegation --- ## UCAN # UCAN Adapter Collaboration across users, agents, and organisations only works when authority itself can be handed over, narrowed, and checked — not implied by shared infrastructure. The UCAN adapter is the venue's capability desk for exactly that: `ucan:issue` mints venue-signed capability tokens, and `ucan:verify` explains — rather than merely enforces — whether a token would be honoured. Together with the [capability model](../capabilities) they make authority a first-class, inspectable artifact. ## ucan:issue — Mint a Grant ```json { "operation": "v/ops/ucan/issue", "input": { "aud": "did:key:z6MkBob...", "att": [{ "with": "w/projects/", "can": "crud/read" }], "exp": 1786550400 } } ``` Returns `{token}` — a signed UCAN JWT granting the audience the listed capabilities. Bare paths canonicalise to the **caller's** namespace before signing, so the token in the wild always carries the absolute resource. Omitting `exp` mints a genuinely non-expiring token; a finite expiry must be in the future. Issuance is a governed surface, with rules worth knowing: - **The venue signs only for what it controls** — its own resources and its managed custodial users. Self-sovereign users issue with their own key via the SDK; the venue never impersonates them. - **Agents cannot issue at all.** An agent acts *within* its owner's namespace but does not speak *for* it — it requests grants from a human through [HITL](./hitl) instead. Consent, not self-service. - **Delegating beyond your own namespace requires a granting right** (`grant/` on the resource, per [COG-17](../../protocol/cogs/COG-017)) — and the minted authority can never outlive the right that enabled it. Proofs travel in the transport channel (request `ucans` array or `Authorization` header), never in operation input — input is persisted in job records; secrets and proofs are not. ## ucan:verify — Explain a Token Enforcement only ever says "Access denied"; `verify` says **why**. Give it a token (plus optionally `with`/`can` to ask "would this authorise that request here?") and it returns the verdict with the working: issuer, audience, expiry, delegation `chainDepth`, and per-capability `rootAuthority` — `owner` (self-sovereign chain), `venue` (rooted in this venue's grant), or `refused`. It runs the **same gate as enforcement**, so its answer is the enforcer's answer. ```json { "operation": "v/ops/ucan/verify", "input": { "token": "eyJhbGciOi...", "with": "w/projects/x", "can": "crud/read" } } ``` ## Related - [Capabilities (UCAN)](../capabilities) — the model: attenuation, delegation, enforcement - [Human-in-the-Loop](./hitl) — grants minted by human consent - [COG-17](../../protocol/cogs/COG-017) · [COG-15](../../protocol/cogs/COG-015) — granting rights and the proof channel --- ## Users # User Adapter A sovereign venue decides exactly who it serves — membership is a deliberate trust decision, not a side effect. The user adapter manages that decision: who is registered, and which keys may authenticate as them. Registration is an **explicit administrative act** — successfully authenticating never registers anyone. ## Operations | Operation | Purpose | Who may call | |-----------|---------|--------------| | `user:create` | Register a user DID at the venue | venue admin | | `user:info` | Registration info for a DID | self, or admin for others | | `user:list` | List registered users | venue admin | | `user:authentication-add` | Add a public `did:key` authenticator | the named user, or admin | | `user:authentication-revoke` | Revoke an authenticator (audit tombstone kept) | the named user, or admin | | `user:authentication-list` | List active and revoked authenticators | self, or admin | "Admin" is precise here: administrative ops require the **venue's own authority** — the venue DID acting directly, or a venue-rooted delegation of the relevant ability (`user/create`, `user/read`, `user/authentication-manage`) on the venue's `users` resource. An ordinary registered user, however broad their grant scope, cannot administer users. ## Two kinds of user ```json { "operation": "v/ops/user/create", "input": { "username": "alice" } } ``` Returns `{did: "did:web:venue.example.com:u:alice", registered: true, created: true}` — idempotent on repeat. - **Venue-managed named users**: `username` derives a `did:web::u:` identity under the venue's namespace. These are the users whose authenticator keys the venue manages (`authentication-add`/`-revoke`), with rotation and revocation history preserved. - **Self-sovereign DIDs**: pass any valid `did` (a `did:key`, an external `did:web`) instead. Registering it grants venue access but **never transfers control** — the venue's root authority stops strictly at identities it minted itself. Agent sub-principal DIDs are refused outright: registering one would hand its bearer the owner's whole namespace. Named users can also be bootstrapped from venue config for repeatable deployments. ## Related - [Authentication](/docs/operator-guide/auth) — how registered users authenticate - [Capabilities (UCAN)](../capabilities) — venue-rooted delegation --- ## Vault # Vault Adapter The vault is a **protected, venue-hosted file system for each user** — a private space for documents and files that agents can be given controlled access to. Every user's vault is their own: operations always target the caller's personal drive, access is governed by [capabilities](../capabilities) like every other operation, and every read and write is traced to the identity that made it and signed with the user's DLFS key. Under the hood it is a thin wrapper over [DLFS](./dlfs) bound to a single well-known drive (`vault` by default, operator-configurable below). The drive is created automatically on first access, and the user's signing key is generated for them — no setup step. ## Why Use Vault? Handing an agent the full DLFS API means it must manage drive names; handing it the vault means five simple tools — `read`, `write`, `list`, `mkdir`, `delete` — that always land in its owner's protected space. Access is as controlled as you choose to make it: give a summarising agent only `vault:read` and `vault:list` and it can never modify a document; every operation it performs lands in the audit trail under its own identity. ## Operations Only a `path` is needed — the drive is implicit. ### Read ```json { "operation": "v/ops/vault/read", "input": { "path": "documents/referral.json" } } ``` Returns `{content, encoding, size}` — identical to `dlfs:read`. ### Write ```json // Inline content { "operation": "v/ops/vault/write", "input": { "path": "profile.json", "content": "{...}" } } // From asset { "operation": "v/ops/vault/write", "input": { "path": "scan.pdf", "asset": "/a/0x1234..." } } ``` ### List ```json { "operation": "v/ops/vault/list", "input": { "path": "documents" } } ``` Omit `path` to list the vault root. ### Mkdir ```json { "operation": "v/ops/vault/mkdir", "input": { "path": "documents/lab-results" } } ``` ### Delete ```json { "operation": "v/ops/vault/delete", "input": { "path": "documents/temp.txt" } } ``` ## Operations Reference | Operation | Input | Description | |-----------|-------|-------------| | `vault:read` | `path` | Read file content | | `vault:write` | `path`, `content?` or `asset?` | Write file content | | `vault:list` | `path?` | List directory entries | | `vault:mkdir` | `path` | Create a directory | | `vault:delete` | `path` | Delete a file or empty directory | ## Configuration The operator can rename the backing drive in venue config — useful when a deployment wants a domain-specific vault (a health product might use `health-vault`, for instance): ```json { "adapters": { "vault": { "drive": "health-vault" } } } ``` The name must be a valid DLFS drive name — non-empty, with no `/`, `\`, or `:`. :::caution[Encrypt sensitive vaults] Vault data is only as protected as the venue's storage. If the venue has no encrypted storage policy configured, vault contents persist unencrypted — the venue logs a warning at startup. Configure `etch.cipher` and key management before storing sensitive data. See [Persistence](/docs/operator-guide/persistence). ::: ## Giving Agents Vault Access Add vault operations to an agent's tool list: ```json { "operation": "v/ops/agent/create", "input": { "agentId": "records-assistant", "config": { "systemPrompt": "You manage the user's document vault...", "tools": [ "v/ops/vault/read", "v/ops/vault/write", "v/ops/vault/list", "v/ops/vault/mkdir" ], "defaultTools": false } } } ``` ## Example: Agent Processing Vault Documents ``` 1. Agent calls vault:list → discovers available documents 2. Agent calls vault:read path="reports/q1.json" → retrieves data 3. Agent processes content, produces analysis 4. Agent calls vault:write path="analysis/q1-summary.json" → stores result ``` All operations are traced to the agent's identity and signed by the user's DLFS key. ## Related - [DLFS](./dlfs) — full decentralised file system with multi-drive support and WebDAV - [Agents](/docs/user-guide/agents/) — creating and configuring agents with tools --- ## Covia (Venue) Adapter # Covia Adapter Everything on a venue — agent memory, workflow state, shared records, the operation catalog itself — ultimately lives as lattice state, and this is the adapter that reads and writes it. The Covia adapter provides workspace CRUD, data inspection, and lattice access: the substrate that users, agents, and workflows collaborate through, one structured path at a time. ## Workspace Operations ### covia:read — Read a Value ```json { "operation": "v/ops/covia/read", "input": { "path": "w/vendor-records/acme" } } ``` **Response:** `{ "exists": true, "value": { "name": "Acme Corp", "status": "active" } }` Supports deep paths: `w/records/acme/contact/email` navigates into nested maps. Numeric keys index into vectors. ### covia:write — Store a Value ```json { "operation": "v/ops/covia/write", "input": { "path": "w/vendor-records/acme", "value": { "name": "Acme Corp", "status": "active" } } } ``` Creates intermediate maps for deep paths. Only `w/` (workspace) and `o/` (operation pins) are writable. ### covia:delete — Remove a Value ```json { "operation": "v/ops/covia/delete", "input": { "path": "w/vendor-records/acme" } } ``` Idempotent — deleting a non-existent path succeeds. ### covia:append — Add to a Vector ```json { "operation": "v/ops/covia/append", "input": { "path": "w/events", "value": { "type": "invoice_received" } } } ``` Creates the vector if it doesn't exist. Useful for event logs and audit trails. ### covia:list — List Keys ```json { "operation": "v/ops/covia/list", "input": { "path": "w/vendor-records", "limit": 20 } } ``` **Response:** `{ "exists": true, "type": "Map", "totalSize": 42, "offset": 0, "keys": ["acme", "globex", ...] }` Supports pagination via `limit` and `offset`. Shows structure without fetching values. ### covia:slice — Paginate Collections ```json { "operation": "v/ops/covia/slice", "input": { "path": "w/events", "offset": 0, "limit": 10 } } ``` Returns elements from vectors or `{key, value}` pairs from maps. ### covia:inspect — Budget-Controlled Preview ```json { "operation": "v/ops/covia/inspect", "input": { "paths": "w/vendor-records", "budget": 500 } } ``` Renders data as a compact preview, truncated to fit the byte budget (default 500 bytes). Accepts a single path or an array of paths. Useful for agent context and debugging. ## Operations Reference | Operation | Input | Writable Paths | Description | |-----------|-------|----------------|-------------| | `covia:read` | `path`, `maxSize?` | All | Read value at path | | `covia:write` | `path`, `value` | `w/`, `o/` | Store value (creates intermediates) | | `covia:delete` | `path` | `w/`, `o/` | Remove value (idempotent) | | `covia:append` | `path`, `value` | `w/`, `o/` | Append to vector | | `covia:list` | `path`, `limit?`, `offset?` | All | List keys with pagination | | `covia:slice` | `path`, `limit?`, `offset?` | All | Paginate collection elements | | `covia:inspect` | `paths` (string or array), `budget?` | All | Compact preview | ## Lattice Namespaces | Prefix | Access | Description | |--------|--------|-------------| | `w/` | Read/Write | User workspace — your persistent data | | `o/` | Read/Write | Operation pins — named operations for reuse | | `n/` | Read | Agent-private notes | | `g/` | Read | Agent records (state, timeline, config) | | `s/` | Read | Secrets (encrypted) | | `j/` | Read | Job records | | `a/` | Read | Assets (content-addressed) | | `v/ops/` | Read | Venue operations catalog | | `v/info/` | Read | Venue metadata | Framework-managed namespaces (`n/`, `g/`, `s/`, `j/`, `v/`) are read-only via the Covia adapter. Use dedicated operations (e.g., `agent:create`, `secret:set`) to modify them. ## Related - [Agents: Tools and Context](/docs/user-guide/agents/tools-and-context) — how agents use these operations - [REST API](/docs/user-guide/api) — HTTP endpoints - [DLFS](./dlfs) — file-based storage (separate from workspace) --- ## Agentic Economics :::info[Forward-looking] This page mixes shipped mechanics (the `convex_query` / `convex_transact` tools) with a forward-looking vision. Code samples outside the tool invocations are illustrative sketches, not deployable examples. ::: As AI agents become increasingly capable, they are evolving from passive tools into active economic participants. The combination of Covia's Grid infrastructure and the Convex blockchain creates a foundation for **agentic economics** - a paradigm where AI agents can engage in value exchange, manage assets, and even create new economic instruments on behalf of their human operators. ## The Vision Today's AI agents can search the web, write code, and automate workflows. Tomorrow's agents will: - **Negotiate and transact** - Autonomously acquire resources, pay for services, and receive compensation for work performed - **Manage portfolios** - Hold, transfer, and invest digital assets according to delegated authority - **Create economic instruments** - Design and deploy smart contracts that encode novel forms of value exchange - **Participate in markets** - Act as buyers, sellers, and service providers in decentralised marketplaces This isn't science fiction - it's an emerging reality enabled by the convergence of capable AI models, programmable money, and trusted execution environments. ## Convex: The Economic Layer [Convex](https://convex.world) provides the economic infrastructure for agentic transactions: ### Programmable Value Convex supports arbitrary digital assets - tokens, NFTs, synthetic instruments, and custom asset types. Agents can: - Hold assets in controlled accounts - Execute atomic multi-party transactions - Create new asset types dynamically - Enforce complex conditions on transfers ### Live-Coded Smart Contracts Perhaps most significantly, AI agents can **write and deploy smart contracts in real-time**. This capability transforms agents from users of pre-existing economic systems into architects of new ones: ```clojure ;; An agent might generate and deploy a contract like this (def escrow-contract (deploy '(do (def buyer *caller*) (def seller ~seller-address) (def amount ~payment-amount) (def release-condition ~condition-fn) (defn release [] (when (and (= *caller* buyer) (release-condition)) (transfer seller amount))) (defn refund [] (when (> (- *timestamp* creation-time) timeout) (transfer buyer amount)))))) ``` An AI agent negotiating a service agreement could generate, deploy, and fund such a contract in seconds - creating a trustless escrow that didn't exist moments before. ### Sub-Second Finality Convex transactions confirm in under a second with immediate finality. This enables: - Real-time micropayments for API calls and compute - Interactive negotiation with committed offers - High-frequency economic coordination between agents ## Integration via MCP The Model Context Protocol provides the interface through which AI agents interact with economic infrastructure: ### Convex Tools Covia venues expose Convex operations as MCP tools: | Tool | Description | | ---- | ----------- | | `convex_query` | Read lattice state without a transaction | | `convex_transact` | Execute state-changing transactions | Further Convex operations (account creation, key generation) are planned. :::note[Tool naming] MCP tool names are derived from operation names with colons and slashes replaced by underscores (e.g., `convex:query` becomes `convex_query`). ::: ### Example: Agent-Initiated Payment An AI assistant paying for a service on behalf of its user: ``` User: "Subscribe me to the premium data feed from DataVenue" Agent thinking: 1. Query DataVenue for subscription terms 2. Verify user has authorised this spending limit 3. Execute payment transaction 4. Register subscription ``` ```json { "operation": "v/ops/mcp/tools-call", "input": { "server": "did:web:your-venue.example.com", "toolName": "convex_transact", "arguments": { "source": "(call #1678 (subscribe :premium))", "address": "#1234" } } } ``` ## Trust and Delegation Agentic economics requires careful attention to trust boundaries: ### Delegated Authority Agents operate within explicitly delegated authority from their human operators: - **Spending limits** - Maximum transaction values - **Asset restrictions** - Which assets can be transacted - **Counterparty rules** - Approved venues and addresses - **Purpose constraints** - Categories of permitted transactions ### Audit and Transparency Every economic action is recorded: - Convex provides an immutable transaction history - Covia's Job system logs all operation invocations - Agents can be required to explain their economic decisions ### Revocable Access Human operators maintain ultimate control: - Agent accounts can be paused or revoked instantly - Multi-signature requirements for high-value actions - Time-locked transactions with cancellation windows ## Emerging Patterns ### Agent-to-Agent Commerce Agents can transact directly with other agents: ``` Agent A: "I need sentiment analysis for 10,000 documents" Agent B: "I can provide that. Cost: 50 tokens, delivery: 2 hours" Agent A: "Agreed. Initiating escrow contract..." ``` No human intervention required - just agents exchanging value for services. ### Dynamic Pricing and Negotiation Agents can implement sophisticated pricing strategies: - Demand-responsive pricing for computational resources - Auction-based allocation of scarce capabilities - Bundle pricing and volume discounts negotiated in real-time ### Collaborative Value Creation Multiple agents can co-create value through: - Joint ventures with programmatic profit sharing - Bounty systems for distributed problem solving - Prediction markets for collective intelligence ## Building Agentic Economic Systems ### For Agent Developers 1. **Define delegation boundaries** - What economic actions can your agent take? 2. **Implement safeguards** - Rate limits, approval workflows, anomaly detection 3. **Log everything** - Economic decisions should be explainable 4. **Test thoroughly** - Economic bugs can be expensive ### For Venue Operators 1. **Expose economic operations** - Make payment and asset tools available via MCP 2. **Implement metering** - Track resource consumption for billing 3. **Support micropayments** - Enable fine-grained value exchange 4. **Provide discovery** - Help agents find services and pricing ### For the Ecosystem 1. **Standardise interfaces** - Common patterns for agent economic interaction 2. **Build reputation systems** - Track agent and venue reliability 3. **Develop insurance mechanisms** - Manage risks in agent transactions 4. **Create dispute resolution** - Handle failures and disagreements ## The Road Ahead We are at the beginning of a transformation in how economic activity is conducted. As AI agents become more capable and trusted, they will take on an increasing share of routine economic coordination - not replacing human economic agency, but augmenting it. The combination of: - **Covia's Grid** - Trusted, auditable operation execution - **Convex's CVM** - Programmable, instant-finality transactions - **MCP's Protocol** - Standard interface for agent capabilities ...creates the infrastructure for this future. Agents working on behalf of humanity, participating as full economic actors, creating value through collaboration and exchange. The economy of the future will be built by humans and agents, working together. ## Related Documentation - [Calling MCP Tools](./calling-mcp-tools) - Using MCP tools including Convex operations - [Convex Documentation](https://docs.convex.world) - Convex blockchain and smart contracts - [COG-7: Operations](../../protocol/cogs/COG-007) - Operation specification for economic services --- ## Calling MCP Tools via the Grid The MCP adapter enables you to invoke tools from any MCP server as Grid operations. This provides unique advantages over direct MCP integration: - **Orchestration**: Combine MCP tools with other Grid operations in workflows - **Audit Trail**: All tool invocations are recorded as Jobs in your venue - **Authentication Management**: Handle API keys and tokens centrally - **Federation**: Access MCP tools across different venues ## Built-in MCP Operations Every Covia venue includes six built-in MCP operations — two for one-off calls (below), and four for [bridging](#bridging-mcp-tools-into-the-catalog) remote tools into the catalog: ### `mcp:tools:list` - List Available Tools Discover tools available on an MCP server: ```json { "operation": "v/ops/mcp/tools-list", "input": { "server": "https://some-mcp-server.example.com" } } ``` **Response:** ```json { "tools": [ { "name": "search", "description": "Search for information", "inputSchema": { "type": "object", "properties": { "query": { "type": "string" } } } }, { "name": "fetch", "description": "Fetch a URL", "inputSchema": { "type": "object", "properties": { "url": { "type": "string" } } } } ], "total": 2 } ``` ### `mcp:tools:call` - Invoke a Tool Call a specific tool on an MCP server: ```json { "operation": "v/ops/mcp/tools-call", "input": { "server": "https://some-mcp-server.example.com", "toolName": "search", "arguments": { "query": "latest AI news" } } } ``` **Response** — the job output is the tool's result, unwrapped: - If the server returns **structured content** (a tool with an output schema), the job output is that structured value, verbatim — e.g. `{"results": [...]}` for a search tool. - Otherwise the output is the tool's **text content**: a single string for a one-block result, or an array of strings for a multi-block result. ```json "Here are the latest AI news articles..." ``` There is no `content` wrapper — dereference the output directly (`[0]` for the text of a text-only tool, `[0, "results", ...]` for a structured one). A tool that reports an error fails the job with the error text (see [Error Handling](#error-handling)). ## Bridging MCP Tools into the Catalog One-off `v/ops/mcp/tools-call` invocations work well inside orchestrations, but the adapter can also **bridge** remote tools into your operation catalog, where they become ordinary operations: invocable by path, subject to capability grants and gates, recorded as jobs, validated against their schema, and usable in agent tool palettes. The tool is the entity — the server is just where it lives. ### Curating individual tools `v/ops/mcp/add-tool` bridges **one** tool at a catalog path you choose: ```json { "operation": "v/ops/mcp/add-tool", "input": { "server": "https://mcp.arxiv.example", "tool": "search_papers", "path": "o/research/search_papers" } } ``` The bridged asset is **self-contained** (server, tool name, auth reference, and the tool's own input schema all live in the asset) — there is no registry entry to manage. That makes **groups just catalog paths**: curate `o/research/search_papers`, `o/research/github_search` and `o/research/fetch_page` from three different servers, each with its own auth, and hand the group to an agent as its tool set. Paths under your own `o/` namespace need nothing extra; venue-wide paths (`v/ops/...`) require the `mcp/manage` ability. Remove a curated tool with `covia:delete` on its path — nothing resurrects it. Optional inputs: - `name` / `description` — display overrides. They are yours: refresh never touches them. - `auth` — a secret reference for the server (see [Authentication for bridged servers](#authentication-for-bridged-servers)). - `default` — argument defaults (below). If the tool name doesn't exist on the server, the error lists the server's available tools so you (or an agent) can self-correct in one step. ### Purpose-shaping with argument defaults `default` fills in arguments the caller omits, turning a generic tool into a purpose-shaped one: ```json { "operation": "v/ops/mcp/add-tool", "input": { "server": "https://mcp.github.example", "tool": "create_issue", "path": "o/covia/report_bug", "auth": "s/GITHUB_MCP_TOKEN", "default": { "owner": "covia-ai", "repo": "covia" } } } ``` Callers (and agents) now see a tool that needs only `title` and `body` — the defaulted keys are removed from the schema's `required` list. Defaults may be any value type, and a caller-supplied value always wins: this is purpose-shaping, **not** access control. When an argument value must be enforced, use a capability gate instead. The mechanism is generic (`operation.default` works on any operation — see [COG-7: Operations](/docs/protocol/cogs/COG-007)); `add-tool` just stores it for you. ### Mirroring a whole server `v/ops/mcp/add-server` bridges **all** of a server's tools in one call: ```json { "operation": "v/ops/mcp/add-server", "input": { "name": "github", "url": "https://mcp.github.example", "auth": "s/GITHUB_MCP_TOKEN" } } ``` Tools materialise under `o/mcp/github/` (your namespace) by default, or under the shared `v/ops/mcp/github/` with `scope: "venue"` (requires the `mcp/manage` ability). A registry entry records the server for refresh/remove bookkeeping. `v/ops/mcp/remove-server` deletes the mirrored subtree and registry entry. Server URLs pass the same SSRF validation (and operator allow/block lists) as the HTTP adapter — bridging a server can never reach anything a direct call couldn't. ### Keeping bridged tools fresh `v/ops/mcp/refresh` re-syncs against the live server, in one of two modes — provide exactly one of `name` or `path`: - **Mirror** (`{"name": "github"}`): full reconciliation — new tools added, changed schemas rewritten, vanished tools **deleted**. The subtree mirrors the server. - **Curated** (`{"path": "o/research"}`): walks the bridged ops under the path (one op or a whole group, across servers), updates input/output schemas and annotations in place. Your `name`/`description` overrides and `default` values are untouched, and a tool the server no longer offers is **reported in `missing`, never deleted** — you picked it, removal is your call. Per-server failures are isolated in `errors`; other servers in the group still refresh. ### Authentication for bridged servers The `auth` input should be a **secret reference**, resolved at call time and never persisted raw: - `s/GITHUB_MCP_TOKEN` — a secret in your own store (stored qualified to your DID), set via `v/ops/secret/set` - `did:key:.../s/GITHUB_MCP_TOKEN` — an explicitly DID-qualified reference (the venue's or your own) A literal token works but warns — the bridged asset persists on the lattice, so raw credentials belong in the secret store. ### Bridged tool errors Bridged calls fail with LLM-diagnosable messages at the point of use: - A remote tool-level error (`isError` in the MCP result) fails the job with the remote error text — a model can read it and self-correct. - Transport failures name the tool, the server and the root cause, with a remedy: `MCP tool 'search_papers' on server https://... failed: Connection refused — check the server is reachable, or re-sync the bridged tool with v/ops/mcp/refresh`. ## Server Identification You can identify MCP servers using either URLs or DIDs: ### By URL ```json { "server": "https://venue-3.covia.ai/mcp" } ``` ### By DID ```json { "server": "did:web:venue-3.covia.ai" } ``` When using a DID, the venue resolves it to find the MCP endpoint from the DID document's service entries. ## Authentication ### Passing Tokens If the MCP server requires authentication, include a token: ```json { "operation": "v/ops/mcp/tools-call", "input": { "server": "https://protected-server.example.com", "toolName": "private-search", "token": "your-api-key", "arguments": { "query": "confidential data" } } } ``` The token is passed as a Bearer token in the request to the MCP server. ### Using Venue Configuration For frequently-used MCP servers, declare them in the venue configuration — they are bridged into the catalog at boot (venue scope): ```json { "mcp": { "servers": { "protected-server": { "url": "https://protected-server.example.com", "auth": "s/PROTECTED_SERVER_TOKEN" } } } } ``` Each server's tools materialise as operations under `v/ops/mcp/protected-server/`, invocable by path like any other operation — no `server` or `token` inputs needed. Seeding is best-effort: a server that is down at boot logs a warning and the last-known catalog persists (run `v/ops/mcp/refresh` when it is reachable). See [Bridging](#bridging-mcp-tools-into-the-catalog) above. ## Orchestrating MCP Tools Combine MCP tool calls with other operations in orchestrations: ```json { "name": "Search and Summarise", "operation": { "adapter": "orchestrator", "steps": [ { "op": "v/ops/mcp/tools-call", "name": "Web Search", "input": { "server": "did:web:search-venue.example.com", "toolName": "web-search", "arguments": { "query": ["input", "topic"] } } }, { "op": "v/ops/langchain/openai", "name": "Summarise Results", "input": { "prompt": ["concat", "Summarise the following search results:\n", [0]] } } ], "result": { "summary": [1, "content"], "sources": [0, "content"] } } } ``` ## Calling Other Venues Use MCP to call tools on other Covia venues: ```json { "operation": "v/ops/mcp/tools-call", "input": { "server": "did:web:venue-4.covia.ai", "toolName": "analyze_sentiment", "arguments": { "text": "This product is amazing!" } } } ``` This enables federated workflows where operations are distributed across multiple venues. ## Error Handling MCP failures fail the job with a diagnosable message: ```json { "status": "FAILED", "error": "MCP tool 'search' on server https://... failed: Connection refused — check the server is reachable, or re-sync the bridged tool with v/ops/mcp/refresh" } ``` Common error scenarios: - **Connection errors**: the message names the tool, server and root cause - **Authentication errors**: invalid or missing token / unresolvable secret reference (fail-closed — a missing secret never silently connects unauthenticated) - **Tool not found**: the requested tool doesn't exist on the server - **Tool-level errors**: a remote tool that reports `isError` fails the job with the remote error text — never a "successful" result carrying an error payload - **Invalid arguments**: arguments don't match the tool's schema ## Caching and Performance MCP tool calls are not cached by default since tool results may change. For cacheable operations, consider: 1. Wrapping MCP calls in an operation that handles caching 2. Using the Grid's artifact system to store results ## Security Considerations ### Token Handling - Never include tokens in client-side code - Use venue-side credential configuration for sensitive tokens - Rotate tokens regularly ### Server Validation - Verify MCP server identity before sending sensitive data - Use HTTPS for all MCP communications - Consider IP allowlisting for production deployments ### Audit and Compliance All MCP tool invocations create Jobs in your venue, providing: - Complete audit trail of tool usage - Ability to track which tools are being used - Input/output logging for compliance ## Example: Multi-Tool Workflow This example shows a complete workflow using multiple MCP tools. It assumes structured-output servers (the search tool returns `{"results": [...]}` and the fetch tool `{"content": ...}`); against text-only servers, dereference the whole output with `[N]` instead: ```json { "name": "Research Assistant", "description": "Search, fetch, and summarize information on a topic", "operation": { "adapter": "orchestrator", "input": { "type": "object", "properties": { "topic": { "type": "string", "description": "Research topic" } }, "required": ["topic"] }, "steps": [ { "name": "Search", "op": "v/ops/mcp/tools-call", "input": { "server": "did:web:search.example.com", "toolName": "web-search", "arguments": { "query": ["input", "topic"], "limit": 5 } } }, { "name": "Fetch First Result", "op": "v/ops/mcp/tools-call", "input": { "server": "did:web:fetch.example.com", "toolName": "fetch-url", "arguments": { "url": [0, "results", 0, "url"] } } }, { "name": "Summarise", "op": "v/ops/langchain/openai", "input": { "prompt": ["concat", "Summarise this page:\n", [1, "content"]] } } ], "result": { "summary": [2, "content"], "source_url": [0, "results", 0, "url"], "all_results": [0, "results"] } } } ``` ## Related Documentation - [Venues as MCP Servers](./venues-as-mcp-servers) - Exposing your operations via MCP - [Orchestrator Adapter](../adapters/orchestrator) - Building multi-step workflows - [COG-7: Operations](/docs/protocol/cogs/COG-007) - Operation specification --- ## Covia in ChatGPT The same **Covia connector** that powers [Covia in Claude](./covia-in-claude) also works with ChatGPT. The connector is a standard remote MCP server with OAuth 2.1, so ChatGPT can add it as a **custom connector**, sign a capability grant on the same consent page, and operate your venue — every action landing as a job record on your venue. The connector lives at `connector.covia.ai` and works with any venue, including one you host yourself. :::note[Status] The connector implements the open standards ChatGPT's connectors use (MCP over streamable HTTP, OAuth 2.1 with PKCE, and Client ID Metadata Documents). Adding it through **Developer Mode** or a workspace custom connector works today. A listing in the ChatGPT apps directory is a separate, in-progress step; until then, use the manual-connector route below. ::: ## Where custom connectors live in ChatGPT - **Developer Mode** (ChatGPT Plus/Pro): **Settings → Connectors → Advanced → Developer mode**, then add a connector by URL. Best for individual testing. - **Workspace connectors** (Business/Enterprise/Edu): an admin adds the connector under **Settings → Connectors** for the workspace. In either case, the connector URL is: ``` https://connector.covia.ai/mcp ``` ## Connect 1. Add `https://connector.covia.ai/mcp` as a custom connector (see above for where). 2. ChatGPT discovers the connector's authorisation server and opens the **consent page** — the same page Claude users see, titled *Connect Covia to ChatGPT*. Pick your venue, then check the identity under **2 · Your identity**. ![The connector consent page: venue and identity](/img/connect-covia/02-consent-identity.png) 3. Provide your device key: generate a new one, or import the key you use with [app.covia.ai](https://app.covia.ai) (**Profile → copy the Private Key**, the 64-hex value — not the DID). The key stays in your browser and signs the grant locally. ![Copying the private key from the dashboard](/img/connect-covia/03-profile-key.png) 4. Tick the capabilities ChatGPT may use, choose a duration, and **Approve**. ![The capability grant on the consent page](/img/connect-covia/05-consent-capabilities.png) :::tip[Use the same identity as your dashboard] Your workspace, agents, and secrets belong to the identity (DID) of the device key you use. Import your dashboard key so ChatGPT shares them; a freshly generated key is a new, empty identity. After importing, confirm the *You are did:key:…* line ends with the same characters as your Profile page. ::: :::note[Paste the key, not the DID] The import field wants the 64-character **private key**, not your `did:key:…` DID. If you paste the DID, the consent page tells you so. ::: ## What ChatGPT can do The connector exposes the same tools regardless of assistant: list and run operations (each returns a receipt link), list and inspect your agents, chat with and task them, read and list your workspace, fetch job records, and see and answer pending approvals. Ask ChatGPT to *"use covia_help"* for built-in setup and troubleshooting guidance. Agent chat runs on your own model provider account — store the provider key at **app.covia.ai → Secrets** (signed in with the same device key), by its conventional name (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY`, …). The connector never holds provider keys. ## The trust model The connector has its own grid identity (a `did:key`, shown on the consent page and at [connector.covia.ai](https://connector.covia.ai)). It never holds your private key, your venue secrets, or your ChatGPT conversation: it acts under the UCAN grant you signed, the venue enforces that scope on every call, and anything outside it is refused. Receipts link to job records on your venue, so your audit trail stays yours. Remove the connector in ChatGPT — or let the grant expire — and the connection is powerless. ## Troubleshooting The [Covia in Claude](./covia-in-claude#troubleshooting) troubleshooting table applies here too — the identity, capability, and LLM-key behaviour is identical; only where you add and remove the connector differs. --- ## Covia in Claude The **Covia connector** puts your venue inside Claude. Claude (web, Desktop, and Claude Code) can run your operations, work with your agents, read your workspace, and answer your approvals, with every action landing as a job record on your venue, under a capability grant you sign yourself. The connector lives at `connector.covia.ai` and works with any venue, including one you host yourself. :::note[Using ChatGPT instead?] The same connector also works with ChatGPT. ::: :::tip[Prefer to follow along in the connector?] The connector hosts a step-by-step visual guide at **[connector.covia.ai/claude](https://connector.covia.ai/claude)** — the same steps below, each with a clean recreation of the exact screen you will see. ::: ## Connect in about two minutes 1. In Claude, open **Settings → Customize → Connectors → Add → Custom connector** and enter: ``` https://connector.covia.ai/mcp ``` ![Adding the connector in Claude](/img/connect-covia/01-claude-connectors.png) 2. Claude discovers the connector's authorisation server and opens the **consent page**. Pick your venue (any venue works, including a self-hosted one — paste its URL), then check the identity shown under **2 · Your identity**. ![The connector consent page: venue and identity](/img/connect-covia/02-consent-identity.png) 3. Provide your device key. Generate a new one, or import the key you use with [app.covia.ai](https://app.covia.ai): open **Profile**, and copy the **Private Key** (64 hex characters — not the DID) with the button beside it. ![Copying the private key from the dashboard](/img/connect-covia/03-profile-key.png) **The key stays in your browser** and signs the grant locally. 4. Tick the capabilities Claude may use and choose a duration, then **Approve**. The venue enforces exactly these capabilities; the two off-by-default rows are the more consequential ones. ![The capability grant on the consent page](/img/connect-covia/05-consent-capabilities.png) :::tip[Use the same identity as your dashboard] Your workspace, agents, and secrets belong to the identity (DID) of the device key you use. Importing your dashboard key means Claude shares them; generating a fresh key creates a new, empty identity. After importing, confirm the *You are did:key:…* line ends with the same characters as your Profile page. ::: :::note[Paste the key, not the DID] The import field wants the 64-character **private key**, not your `did:key:…` DID (which is your public identity). If you paste the DID, the consent page tells you so. ::: The first time Claude uses each tool, it asks permission in the conversation — **Allow once** keeps you in the loop; **Always allow** suits trusted read-only tools. ![In-conversation permission prompt](/img/connect-covia/06-permission-dialog.png) ## What Claude can do Ask Claude naturally; the connector's tools map to your venue: - **Operations**: list what the venue offers and run any operation. Each run returns a receipt link to the job record. - **Agents**: list your agents, inspect them, chat with them, and give them tasks. - **Workspace**: read and list your lattice paths (`w/` workspace, `g/` agent state, `j/` jobs, `h/` inbox). - **Approvals**: see pending human-in-the-loop requests and answer them. Claude always confirms with you before answering approvals or capability asks. - **Help**: Claude can consult the built-in `covia_help` tool for setup and troubleshooting guidance. For example, ask *"Create an agent called bob on my Covia venue, then list my agents to confirm."* The reply names the agent's address under your own DID, with a job-receipt link that opens the signed record on your venue — and the agent appears in your dashboard. ![Claude creating an agent, with a job receipt](/img/connect-covia/07-bob-created.png) ![The new agent in the dashboard](/img/connect-covia/08-dashboard-bob.png) ## Chatting with agents needs your LLM key Agent conversations run on your own model provider account. Store your key on the venue, signed in with the same device key you connected with: 1. Open [app.covia.ai](https://app.covia.ai) → **Secrets**. 2. Add the provider key by its conventional name: `ANTHROPIC_API_KEY` (Claude models), `OPENAI_API_KEY`, `GOOGLE_API_KEY`, and so on. Keys resolve per identity from your own encrypted secret store. The connector never holds provider keys. ## The trust model, in one paragraph The connector has its own grid identity (a `did:key`, shown on its consent page and at [connector.covia.ai](https://connector.covia.ai)). It never holds your private key, your venue secrets, or your files: it acts under the UCAN grant you signed, the venue enforces that scope on every call, and anything outside it is refused with the exact missing capability named. Receipts in every result link to the job record on your venue, so your audit trail stays yours. Disconnect in Claude's settings (or let the grant expire) and the connection is powerless. ## Troubleshooting | Symptom | Cause and fix | | --- | --- | | Your workspace or agent list is empty, but the dashboard is not | Claude is connected as a different identity. Reconnect and **import** your dashboard key; the DIDs must match. | | Clicking **Import** does nothing, or shows an error | You pasted the DID or an incomplete string. Import needs the 64-character private key from Profile. | | "Not permitted … reconnect and tick the capability" | The grant does not include that capability. Disconnect, reconnect, and tick the named row — a signed grant cannot be edited, only replaced. | | Chatting with an agent returns a provider or API-key error | No model key is stored for this identity. Add it at **Secrets**, signed in with the same device key. | | Everything stopped working after some days | The grant expired, by design. Reconnect; choose a longer duration if you prefer. | | Not sure what Claude can do | Ask it to *"use covia_help"* — the connector answers setup and troubleshooting questions itself. | ## Self-hosted venues Nothing above assumes a Covia-operated venue: paste your own venue's URL on the consent page and the same flow applies. Claude Code users can also skip the connector entirely and add a venue's own MCP endpoint directly with a bearer token; see [Venues as MCP servers](./venues-as-mcp-servers). --- ## MCP Integration Overview The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard for connecting AI models to external data sources and tools. Covia provides deep integration with MCP, enabling two powerful use cases: 1. **Venues as MCP Servers** - Every Covia venue can act as an MCP server, exposing its operations as tools that AI assistants can use 2. **MCP Tools via the Grid** - Call any MCP server's tools as Grid operations, enabling orchestration, auditing, and federated access 3. **Bridged MCP tools** - Mirror or curate remote MCP tools into your venue's operation catalog, where they behave exactly like native operations ([bridging](./calling-mcp-tools#bridging-mcp-tools-into-the-catalog)) ## Why MCP + Covia? ### For AI Application Developers - **Instant Tool Access**: Connect your AI assistant to any Covia venue and immediately access all its operations as tools - **Standardised Interface**: Use the same MCP protocol regardless of which venue provides the functionality - **Discovery**: Browse available tools through MCP's standard discovery mechanisms ### For Operation Providers - **Zero Configuration**: Operations you create are automatically available as MCP tools - **Consistent Experience**: Your operations work with any MCP-compatible AI assistant - **Secure Access**: Leverage Covia's authentication and authorisation for tool access ### For Enterprise - **Audit Trail**: All tool invocations go through the Grid, providing a complete audit log - **Governance**: Apply consistent policies across all AI tool usage - **Orchestration**: Combine MCP tools with other Grid operations in workflows ## Quick Start ### Connect AI Assistant to a Venue For claude.ai / Claude Desktop, add the venue as a custom connector (**Settings → Connectors → Add custom connector**, paste `https://venue-3.covia.ai/mcp`). For clients that use a generic MCP config file: ```json { "mcpServers": { "covia-venue": { "url": "https://venue-3.covia.ai/mcp" } } } ``` Your AI assistant can now use all operations on that venue as tools. See the [Claude tutorial](../tutorials/claude-mcp) for a step-by-step walkthrough. ### Call MCP Tools via the Grid Use the Grid to invoke any MCP server's tools: ```json { "operation": "v/ops/mcp/tools-call", "input": { "server": "https://some-mcp-server.example.com", "toolName": "search", "arguments": { "query": "hello world" } } } ``` ## Documentation - [Venues as MCP Servers](./venues-as-mcp-servers) - How to expose your venue's operations via MCP - [Calling MCP Tools](./calling-mcp-tools) - How to invoke external MCP tools through the Grid, and bridge them into the catalog - [Claude tutorial](../tutorials/claude-mcp) - Give Claude your own tools in five minutes - [MCP Adapter](../adapters/covia-with-mcp) - The adapter behind outbound MCP calls - [Agentic Economics](./agentic-economics) - AI agents as economic participants using Convex --- ## Venues as MCP Servers Every Covia venue can function as an MCP (Model Context Protocol) server, automatically exposing its operations as tools that AI assistants can use. ## Enabling MCP The MCP endpoint registers when an `mcp` block is present in the venue configuration — the built-in default configuration (and the published Docker image) includes one, so a stock venue serves MCP out of the box. To configure it explicitly: ```json { "name": "My Venue", "mcp": { "enabled": true } } ``` ## MCP Endpoint The MCP endpoint is available at `/mcp` on your venue: ``` https://your-venue.example.com/mcp ``` This endpoint implements the MCP **Streamable HTTP** transport: JSON-RPC over `POST /mcp`, an optional SSE stream via `GET /mcp` for server-to-client notifications, and `DELETE /mcp` to close a session (see the [REST API reference](../api/#mcp-endpoints)). ## Connecting AI Assistants ### Claude (claude.ai and Claude Desktop) Remote venues are added as **custom connectors**: go to **Settings → Connectors → Add custom connector** and paste your venue's `/mcp` URL. Custom connectors require a public HTTPS endpoint — a `http://localhost:8080/mcp` venue won't work here. See the [Claude tutorial](../tutorials/claude-mcp) for a full walkthrough. ### Claude Code ```bash claude mcp add --transport http my-venue https://your-venue.example.com/mcp ``` For a venue that requires authentication, add a bearer token header: ```bash claude mcp add --transport http my-venue https://your-venue.example.com/mcp \ --header "Authorization: Bearer your-api-key" ``` ### Clients using a generic MCP config file ```json { "mcpServers": { "my-venue": { "url": "https://your-venue.example.com/mcp", "headers": { "Authorization": "Bearer your-api-key" } } } } ``` (The `headers` block is only needed when the venue requires authentication.) ### Other MCP Clients Any MCP client that supports the Streamable HTTP transport can connect. Consult your client's documentation for configuration details. ## How Operations Become Tools Covia automatically converts operations to MCP tools. Tool names are derived from operation names by replacing colons and slashes with underscores: | Operation | MCP Tool Name | |-----------|---------------| | `covia:read` | `covia_read` | | `agent:create` | `agent_create` | | `mcp:tools:call` | `mcp_tools_call` | Operations are discovered dynamically from the venue's adapter registries — any operation with a valid input schema is automatically exposed as an MCP tool. ### Operation Metadata ```json { "name": "Web Search", "description": "Search the web for information", "operation": { "adapter": "http", "input": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query" }, "limit": { "type": "integer", "description": "Maximum results", "default": 10 } }, "required": ["query"] } } } ``` ### Resulting MCP Tool ```json { "name": "web_search", "description": "Search the web for information", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query" }, "limit": { "type": "integer", "description": "Maximum results", "default": 10 } }, "required": ["query"] } } ``` The AI assistant sees this tool and can invoke it naturally during conversation. ## Tool Discovery MCP clients can discover available tools using the `tools/list` method. This returns all operations on the venue that are suitable for use as tools. ### Filtering Tools You can control which operations appear as MCP tools using the `operation.info` metadata: ```json { "name": "Internal Operation", "operation": { "adapter": "http", "info": { "mcp": false } } } ``` Operations with `"mcp": false` will not appear in the MCP tools list. ### Featured Tools Mark important tools to appear prominently: ```json { "name": "Primary Search", "operation": { "adapter": "http", "info": { "featured": true, "category": "search" } } } ``` ## Tool Execution When an AI assistant invokes a tool: 1. **MCP Request**: The assistant sends a `tools/call` request 2. **Job Creation**: The venue creates a Job to track execution 3. **Operation Execution**: The operation runs with the provided arguments 4. **Response**: Results are returned to the assistant ### Example Flow ``` AI Assistant Covia Venue | | |--- tools/list --------------->| |<-- [web_search, ...] ---------| | | |--- tools/call(web_search) --->| | {query: "AI news"} | | | |<-- {results: [...]} ----------| ``` ## Error Handling Errors are returned using MCP's standard error format: ```json { "error": { "code": -32000, "message": "Operation failed: Connection timeout" } } ``` The venue maps operation failures to appropriate MCP error codes. ## Security Considerations ### Authentication MCP requests are subject to the same authentication requirements as REST API requests. Configure authentication using API keys or other supported methods. ### Rate Limiting Tool invocations count toward your venue's rate limits. Configure appropriate limits to prevent abuse. ### Sensitive Operations Review which operations should be exposed via MCP. Use the `"mcp": false` flag to hide internal or sensitive operations. ## Best Practices ### Tool Design - **Clear Names**: Name operations so the derived tool reads as an action — `search:documents` becomes `search_documents`, which beats `doc_srch` - **Helpful Descriptions**: Write descriptions that help AI understand when to use the tool - **Typed Parameters**: Use JSON Schema to fully describe parameters with types and descriptions ### Schema Quality - **Required Fields**: Mark truly required parameters as required - **Defaults**: Provide sensible defaults where appropriate - **Examples**: Include examples in descriptions to guide AI usage ### Testing Test your operations with AI assistants before deployment: 1. Connect to your venue via MCP 2. Ask the AI to discover and use your tools 3. Verify the AI correctly understands when and how to use each tool ## Related Documentation - [COG-7: Operations](/docs/protocol/cogs/COG-007) - Operation specification - [Calling MCP Tools](./calling-mcp-tools) - Invoking external MCP tools - [API Reference](../api) - REST API documentation --- ## Authentication Covia venues support flexible authentication and access control. By default, venues allow anonymous (public) access, which is suitable for development and testing. For production deployments, you can configure OAuth login providers and restrict access to authenticated users. All authentication settings live under the `"auth"` key in your venue configuration. For the complete list of venue config keys, see the [Configuration Reference](./configuration). ## Public Access The `auth.public.enabled` setting controls whether unauthenticated requests are allowed on API endpoints. When disabled, all API, MCP, and A2A requests require a valid bearer token. ```json5 "auth": { "public": { "enabled": true } } ``` | Setting | Default | Description | |---------|---------|-------------| | `auth.public.enabled` | `true` | Allow unauthenticated access to API endpoints | | `auth.public.caps` | *(unset — secure read-only)* | The **public capability ceiling**: what anonymous callers may do. Unset grants read-only access (no `invoke` or mutating abilities); `"unrestricted"` removes the ceiling; an explicit capability array sets exactly that scope. See [COG-10](../protocol/cogs/COG-010). | Public access is therefore not all-or-nothing: even with `enabled: true`, anonymous callers are bounded by the capability ceiling, and widening it (e.g. to allow anonymous invocation) is a deliberate configuration step. When public access is disabled and a request arrives without a valid `Authorization: Bearer ` header, the venue returns `401 Authentication required`. Even with public access enabled, authenticated users receive a caller identity that the venue can use for access control, audit logging, and user-specific state. ## Admission: Authentication Is Not Membership Anyone can mint a key pair, so proving control of a DID cannot by itself make someone a member of your venue — membership is a trust decision you make. Since 0.9.0 the two are explicitly separate: a previously unknown DID that authenticates successfully receives **`403` with an actionable registration message**, before any state is created for it. You admit users in one of three ways: - **`users.autoCreate: true`** — admit any authenticated DID automatically. Appropriate for open or loopback-only venues; leave it `false` (the default) for private and production venues. - **`users.bootstrap`** — provision named users and their public authenticator keys in config, before HTTP starts. First-use only: once a user has key history, later startups never touch it. - **`user:create` at runtime** — as the venue itself, or by a provisioner holding a venue-issued UCAN delegating exactly that ability. There is no separate admin API to secure; the capability check *is* the admission control. See the [Users adapter](../user-guide/adapters/user). ## Token Expiry Venue-issued JWTs (returned after OAuth login) have a configurable expiry: ```json5 "auth": { "tokenExpiry": 86400 } ``` | Setting | Default | Description | |---------|---------|-------------| | `auth.tokenExpiry` | `86400` | Expiry of venue-issued JWTs, in seconds (24 hours) | ## OAuth Providers The venue supports OAuth 2.0 Authorization Code flow with three providers: Google, Microsoft, and GitHub. Each provider requires a `clientId` and `clientSecret` obtained from the provider's developer console. ### Configuration OAuth providers are configured under `auth.oauth`: ```json5 "auth": { "oauth": { "google": { "clientId": "YOUR_GOOGLE_CLIENT_ID", "clientSecret": "YOUR_GOOGLE_CLIENT_SECRET" }, "microsoft": { "clientId": "YOUR_MS_CLIENT_ID", "clientSecret": "YOUR_MS_CLIENT_SECRET" }, "github": { "clientId": "YOUR_GITHUB_CLIENT_ID", "clientSecret": "YOUR_GITHUB_CLIENT_SECRET" } } } ``` You only need to configure the providers you want to support. Unconfigured providers are simply not available. ### Redirect URIs Each provider requires a redirect URI to be registered in the provider's developer console. The venue automatically constructs these from the venue's base URL: ``` https://venue.example.com/auth/google/callback https://venue.example.com/auth/microsoft/callback https://venue.example.com/auth/github/callback ``` The base URL is derived from the venue's `hostname` and `port` configuration, or can be overridden with an explicit `baseUrl` setting at the venue level. For production deployments behind a reverse proxy, you should set `baseUrl` explicitly: ```json5 { "hostname": "venue.example.com", "port": 8080, "baseUrl": "https://venue.example.com", "auth": { "oauth": { ... } } } ``` ### Login Flow When OAuth providers are configured, the venue exposes: | Endpoint | Description | |----------|-------------| | `/login` | Login page listing available providers | | `/auth/{provider}` | Redirects to the provider's authorisation page | | `/auth/{provider}/callback` | Handles the OAuth callback and issues a venue JWT | After a successful OAuth login, the venue: 1. Exchanges the authorisation code for tokens from the provider 2. Extracts the user's identity (email, name, subject) 3. Creates or updates the user record in the venue's user database 4. Issues a venue-signed JWT containing the user's DID 5. Returns the JWT to the client ### Provider Details #### Google - Uses OpenID Connect with JWKS-based ID token verification - Scopes: `openid`, `email`, `profile` - User identity derived from email address #### Microsoft - Uses Microsoft Identity Platform (Microsoft Entra ID, formerly Azure AD) - Scopes: `openid`, `email`, `profile` - Supports multi-tenant configurations - User identity derived from email address #### GitHub - Uses GitHub OAuth (not OpenID Connect) - Scopes: `user:email`, `read:user` - User information fetched from GitHub's API after token exchange - User identity derived from email address ## Bearer Token Authentication API requests are authenticated via `Authorization: Bearer ` headers. The venue's auth middleware supports four types of bearer tokens: ### Self-Issued EdDSA JWTs Clients with their own Ed25519 key pair can create self-issued JWTs. The `sub` claim must be a `did:key` matching the signing key in the `kid` header. Named venue users can self-issue too: `sub` is their stable `did:web::u:` DID, signed with one of their registered authenticator keys (the `kid` carries the verification-method DID URL their DID document publishes). This lets automated clients authenticate without OAuth — but note that authentication alone does not [admit](#admission-authentication-is-not-membership) an unknown DID. See [Embedded Venue](./embedded-venue) for the single-owner, loopback deployment shape built on this. ### Venue-Signed JWTs JWTs signed by the venue's own key pair, typically issued after OAuth login. The `sub` claim contains the user's venue-managed DID (e.g. `did:web:venue.example.com:u:alice_gmail_com`). ### External Provider RS256 JWTs RS256 JWTs from configured OAuth providers, verified against the provider's JWKS endpoint. This allows clients that already have a provider-issued token to authenticate directly. ### UCAN Capability Tokens A bearer token may also be a **UCAN** — a signed capability token. When the venue recognises one, the token's issuer (`iss`) becomes the caller DID and the token's capabilities are carried into the request as proofs. This works on both the REST API and the MCP endpoint and is what enables fine-grained, delegable, cross-user access. Authentication (identity) and capabilities (authorisation) are separate concerns — see [Capabilities](../user-guide/capabilities) for the authorisation model, `ucan:issue`, and the `{with, can}` grant shape. Since 0.9.0, venues emit the versioned Convex UCAN profile (an explicit `ucv` claim and always-present `prf`); tokens minted by pre-0.9 venues or older SDKs no longer verify, so outstanding grants must be re-issued. Correctly signed legacy JWTs that merely omit the profile marker are still accepted, and the emitted profile is advertised in `/api/v1/status`. An unauthenticated request may also carry an **identity token** in its `ucans` proof array — a UCAN with an empty attenuation list, audienced to this venue, signed by the caller. The venue verifies the caller's own signature and treats the issuer as the caller. This is how relayed cross-venue requests keep the original caller's identity: the relaying venue forwards the token, this venue trusts the signature, not the relay. An `Authorization` header always takes precedence. See [COG-3 §6](../protocol/cogs/COG-003) for the exact rules. ## User Database Authenticated users are stored in the venue's lattice-backed user database. User records are keyed by a sanitised user ID derived from the email address (e.g. `alice_gmail_com`) and contain: | Field | Description | |-------|-------------| | `did` | The user's venue-scoped DID | | `email` | Email address from the OAuth provider | | `name` | Display name from the OAuth provider | | `provider` | The OAuth provider used for login | | `updated` | Timestamp of last update | User records are managed through the venue-administrative `user:list` / `user:info` operations, and authenticator keys through `user:authentication-add` / `-revoke` / `-list` — see the [Users adapter](../user-guide/adapters/user). Named users' DID documents are published at `/u//did.json`. MCP has matching knobs: `mcp.auth.required` defaults to the inverse of `auth.public.enabled` (an explicit `false` cannot re-open MCP on a venue that requires authentication), and `mcp.auth.allowedDids` restricts MCP to a DID allowlist — see the [Configuration Reference](./configuration#protocols--features). ## Checking the Caller The `auth:whoami` operation reports how the venue resolved the current request — useful for debugging client auth: ```json { "operation": "v/ops/auth/whoami" } ``` ```json { "caller": "did:key:z6Mk...", "authenticated": true, "internal": false } ``` `caller` is the resolved caller DID (or `null` for anonymous), `authenticated` is true when a caller DID is present, and `internal` is true only when the request originates from the venue itself. ## Example Configuration A complete auth configuration for a production venue: ```json5 { "venues": [ { "name": "Production Venue", "hostname": "venue.example.com", "port": 8080, "baseUrl": "https://venue.example.com", "auth": { "public": { "enabled": false }, "tokenExpiry": 3600, "oauth": { "google": { "clientId": "123456789.apps.googleusercontent.com", "clientSecret": "GOCSPX-..." }, "github": { "clientId": "Iv1.abc123", "clientSecret": "abc123def456..." } } } } ] } ``` This configuration: - Requires authentication for all API access - Issues tokens valid for 1 hour - Supports Google and GitHub login --- ## Configuration Reference A venue is configured from a single JSON5 file passed on launch: ```bash java -jar covia.jar config.json ``` This page is the exhaustive list of configuration keys, grouped by concern, with defaults. The topic guides — [Venue Quick Start](./venue-start), [Authentication](./auth), [Security](./security), [Persistence](./persistence), and [Embedded Venue](./embedded-venue) — carry the narrative and worked examples; this page is the index of *what keys exist and what they default to*. ## The config file is a server document The root of the file is a **server document**, not a venue: it holds a `venues` array (required, non-empty), and each entry is one venue. One JVM can host several venues — the [federation tutorial](../user-guide/tutorials/federation) runs two from one file. ```json5 { "strictConfig": true, // optional: reject unknown fields (inherited by venues) "venues": [ { "name": "My Venue", "port": 8080, "store": "/data/venue.etch" } ] } ``` Validation is fail-closed for known fields — a malformed value is a startup error, never a silently substituted default. Unknown fields warn and are ignored, or are rejected outright under `strictConfig: true`. All keys below are venue-entry keys unless noted, dotted paths showing nesting (e.g. `auth.public.enabled`). ## Identity & network | Key | Default | Description | |-----|---------|-------------| | `name` | — | Human-readable venue name, surfaced in status. | | `port` | `8080` | HTTP listen port. | | `bindAddress` | all interfaces (`0.0.0.0`) | Network interface the HTTP connector binds to. Set `127.0.0.1` to restrict the venue to loopback — see [Embedded Venue](./embedded-venue). A loopback bind also flips the defaults for `rateLimit` (off) and `allowPrivateNetwork` (on). Distinct from `hostname`. | | `hostname` | — | The venue's *advertised* public host, used to derive `baseUrl` and a `did:web` alias in the DID document. Leave unset for a loopback/embedded venue (identity stays `did:key`). | | `baseUrl` | derived from `hostname`/`port` | Explicit external base URL (e.g. behind a reverse proxy). | | `did` | — | Operator-declared venue identity, validated **fail-closed**: a `did:web` must match the public `hostname`; a `did:key` must match the venue key pair (wrong seed → refuses to start). See [Security](./security#pin-the-venue-identity). | | `seed` | auto-generated | Ed25519 hex seed for a stable venue identity. If omitted with a persistent `store`, one is generated and saved to `venue.key` (owner-only permissions) beside the store. | | `keystore` | — | Venue identity from a PKCS12 keystore (Convex CLI format): `{path, alias, storepass, keypass}`, with `CONVEX_KEYSTORE`/`CONVEX_KEYSTORE_PASSWORD` env fallbacks. Any keystore failure is fatal — never a silent fallback to a generated key. | | `rootPage` | venue status page | Replace the public `/` page: `{"redirect": "https://…"}` or `{"file": "path/to/index.html"}`. | ## Persistence & storage See [Persistence](./persistence) for the durability model and [Security](./security#encrypt-the-store) for encryption. | Key | Default | Description | |-----|---------|-------------| | `store` | `"temp"` | Etch store location: `"temp"` (deleted on exit), `"memory"`, or a file path (survives restarts). | | `etch` | — | Encrypted Etch v3 store policy: `{version: 3, cipher: "aes-256-ctr" \| "chacha20", encryptIndex, key: {env} \| {file} \| hex}`. Fail-closed: any misconfiguration is a startup error, never an unencrypted store. | | `storage` | `lattice` | Content storage backend: `lattice`, `memory`, `file`, or `dlfs`. Object form uses **`storage.content`** plus backend options like `storage.path`. With `"file"`, asset content bytes live **outside** the (possibly encrypted) Etch store as plaintext files — the venue warns about this combination. | | `maxContentSize` | `104857600` (100 MB) | Maximum asset content size in bytes. | ## Authentication & access control See [Authentication](./auth) for OAuth setup, token types, and admission. | Key | Default | Description | |-----|---------|-------------| | `auth.public.enabled` | `true` | Allow unauthenticated (anonymous) access. `false` requires a bearer token on every request. | | `auth.public.caps` | secure read-only | Capability ceiling applied to unauthenticated callers (read-only, no `invoke`). `"unrestricted"` removes the ceiling (loopback throwaway venues only); an explicit capability vector sets a custom ceiling. | | `auth.tokenExpiry` | `86400` (24 h) | Expiry, in seconds, of venue-issued JWTs (after OAuth login). | | `auth.oauth.` | — | OAuth login providers (`google`, `microsoft`, `github`), each with `clientId` / `clientSecret`. | | `auth.acceptedAudiences` | venue DID(s) | Additional JWT `aud` values the venue accepts, beyond its own published DID. | | `auth.audience` | `verify` | Audience policy: `verify` (tolerate an absent `aud` during migration) or `require` (reject tokens with no `aud`). | | `users.autoCreate` | `false` | Automatically admit previously unknown authenticated DIDs. Off, an unknown DID gets `403` with a registration message — see [Admission](./auth#admission-authentication-is-not-membership). Keep off for private and production venues. | | `users.bootstrap` | — | Provision named venue users and their public `did:key` authenticator keys before HTTP starts. First-use only: once a user has authenticator history, later startups never add or revoke keys. | | `corsOrigins` | `*` | Allowed browser origins: a single origin, an array, the `"loopback"` sentinel (literal localhost forms, never resolves DNS), or `false`/`"none"`/`[]` to disable. Denied origins get `403`; invalid config fails startup rather than silently widening. | | `allowPrivateNetwork` | follows bind | Emit `Access-Control-Allow-Private-Network`. Defaults **on for a loopback bind, off otherwise**; an explicit value wins either way. | | `rateLimit` | on for non-loopback binds | Per-caller token bucket: `{enabled, rps: 100, burst: 300, maxConcurrentJobsPerUser: 100, blockMs: 3000}`. Buckets are keyed per user DID (anonymous traffic shares one); over-limit requests get `429` + `Retry-After`. | ## Protocols & features | Key | Default | Description | |-----|---------|-------------| | `mcp` | off unless present | Model Context Protocol endpoint config: `enabled`, `includeAdapters`, `includePathPrefixes`, `serverInfo`, and `servers` (bridge external MCP servers into the catalog). | | `mcp.auth.required` | inverse of `auth.public.enabled` | MCP authentication. A non-empty `mcp.auth.allowedDids` implies required; an explicit `false` cannot re-open MCP when venue-wide public access is off. | | `a2a` | off unless present | Agent-to-Agent protocol config (`a2a.defaultChatOp`, `a2a.agentInfo`). Endpoints register only when this block is present. | | `webdav.enabled` | `false` | Mount DLFS over WebDAV at `/dlfs/`. | | `file.roots` | ephemeral `tmp` root | Named filesystem roots for the [File adapter](../user-guide/adapters/file). Forms: a path string, `{path, readOnly}`, `{dlfs, subpath, readOnly}` (a DLFS drive clamped to a subtree), or `{temp: true, prefix}`. With no roots configured the adapter creates a single ephemeral `tmp` root, deleted on exit. | | `modules` | — | External adapter jars loaded at boot: a path string or `{path, sha256, config}`. `sha256` pins jar content; loading is fail-fast and operator-only — there is deliberately no runtime module-load operation. See [venue modules](../user-guide/adapters/jvm#venue-modules). | | `recordReadOnlyOperations` | `false` | Record even declared-read-only `/api/v1/run` operations as durable jobs. | | `enablePrivateJobs` | *(deprecated)* | No longer enables `private: true` on `/invoke` — invoke always creates a durable job. Read-only result-oriented execution goes through `/api/v1/run`. | | `defaultTransitionOp` | `v/ops/llmagent/chat` | Default agent transition operation when an agent config omits `operation`. | | `defaultLlmOperation` | `v/ops/langchain/openai` | Default level-3 LLM operation for agents. | | `maxToolIterations` | `30` | Venue-wide cap on agent tool-loop iterations per turn; agents can override per config. | | `strictAssets` | `true` | Broken or missing adapter operation resources **fail startup** — a venue booting with silently missing ops is broken. `false` is test scaffolding only. | | `strictConfig` | `false` | Reject unknown config fields at startup instead of warning. Also valid at the server-document root, inherited by every venue entry. | | `fixMcpStrings` | `true` | Coerce non-string MCP tool arguments where a schema expects a string. | | `outputValidation` | `"off"` | Validate operation outputs against their declared output schema: `"off"`, `"warn"`, or `"strict"`. | ## Adapter settings Per-adapter settings live under `adapters.`: | Key | Default | Description | |-----|---------|-------------| | `adapters.agent.sessionDelete` | `true` | Allow agent session deletion. | | `adapters.vault.drive` | `"vault"` | Backing DLFS drive name for the [Vault adapter](../user-guide/adapters/vault). The venue warns at startup if the adapter is active without an encrypted Etch policy. | | `adapters.hitl.maxGrantLifetimeSecs` | none | Ceiling on the lifetime of [HITL](../user-guide/adapters/hitl)-minted capability grants; offers exceeding it are rejected before the human ever sees them. | | `adapters.orchestrator.maxItems` / `maxConcurrency` | `50` / `8` | Bounds on orchestrator `foreach` fan-out. | | `adapters.langchain.ollamaUrl` | — | Local Ollama endpoint for the LangChain adapter. | | `adapters.sql.databases.` | — | *(SQL module)* Operator-registered JDBC connections: `{url, user, password}`, with `s/NAME` secret references for passwords. `adapters.sql.path` makes venue-local databases persistent. See [SQL adapter](../user-guide/adapters/sql). | ## Secrets bootstrap Pre-populate the per-user encrypted secret stores at startup, keyed by DID. Top-level keys resolve as: `"venue"` → the venue's own DID, `"public"` → the `:public` identity, anything else verbatim as a literal DID. ```json5 { "secrets": { "venue": { "OPENAI_API_KEY": "sk-..." }, "public": { "ANTHROPIC_API_KEY": "sk-ant-..." }, "did:key:z6MkOwner...": { "FOO": "bar" } } } ``` Each named secret overwrites any existing value under that name for that user at launch; names not listed are left untouched, and a per-secret failure warns without failing startup. **Never commit production secrets** — keep configs with bootstrap secrets in a per-user, non-tracked location. ## Advanced / tuning Rarely needed; sensible defaults apply. | Key | Description | |-----|-------------| | `acceptQueueSize` | Connector accept-queue backlog (default `1024`). | | `httpSelectors` / `httpAcceptors` | Explicit Jetty selector/acceptor thread counts. Handlers run on virtual threads, so selectors only pump non-blocking I/O — override only when co-locating many venues in one JVM. | ## Minimal examples **Local development** (ephemeral, anonymous): ```json5 { "venues": [ { "port": 8080, "store": "temp" } ] } ``` **Embedded, single-owner** (loopback, authenticated) — see [Embedded Venue](./embedded-venue): ```json5 { "venues": [ { "port": 8080, "bindAddress": "127.0.0.1", "allowPrivateNetwork": false, "store": "/path/to/venue.etch", "auth": { "public": { "enabled": false } }, "users": { "autoCreate": true } } ] } ``` **Production** (internet-facing, authenticated, OAuth, encrypted store) — see [Authentication](./auth#example-configuration) and [Security](./security): ```json5 { "strictConfig": true, "venues": [ { "hostname": "venue.example.com", "baseUrl": "https://venue.example.com", "store": "/data/venue.etch", "etch": { "version": 3, "cipher": "aes-256-ctr", "key": { "env": "COVIA_ETCH_KEY" } }, "auth": { "public": { "enabled": false }, "tokenExpiry": 3600, "oauth": { "google": { "clientId": "…", "clientSecret": "…" } } } } ] } ``` --- ## Embedded Venue # Embedded, Loopback, Self-Authenticated Venue This is the deployment shape for a venue **embedded inside a desktop or single-user application** — launched as a subprocess, reachable only on localhost, and serving exactly one owner. It's the shape behind the GetMine desktop app and applies to any app that bundles a venue for one user. The goal is a venue that is **private to its host machine and its owner**: no anonymous access, not reachable off-box, and no browser on some other origin able to reach in. The app authenticates itself as its owner and the venue's job is simply to *verify and enforce*. ## The security model: no token minting An embedded venue does **not** hand out per-launch tokens. Instead the embedding app holds its own Ed25519 key pair and **self-signs a bearer JWT**, which the venue verifies with no shared secret and no prior registration: - The app generates a key pair once and derives its `did:key` from the public key. - For each session it signs a JWT whose `sub` claim is that `did:key` and whose `kid` header carries the same public key. The venue's [self-issued JWT verification](./auth#self-issued-eddsa-jwts) checks that the `kid` and `sub` agree — proving the signer owns the DID — and enforces the token's expiry and audience. - The token's `aud` claim must name **this venue's DID** (read it from `/.well-known/did.json`). A token minted for another venue cannot be replayed here. Every request then authenticates as the app owner's DID; there is no shared `public` identity to fall back to. ## The recipe Four settings turn a default venue into this shape: ```json5 { "bindAddress": "127.0.0.1", "allowPrivateNetwork": false, "auth": { "public": { "enabled": false } }, "users": { "autoCreate": true } } ``` | Setting | Value | What it does | |---------|-------|--------------| | `bindAddress` | `127.0.0.1` | Binds the HTTP listener to loopback only, so the venue is unreachable from the LAN. (If omitted, a venue binds to all interfaces.) | | `allowPrivateNetwork` | `false` | Suppresses the `Access-Control-Allow-Private-Network` header, so a public web origin **cannot** reach the venue on localhost from the browser. This is a real override: on a loopback bind the default is `true`. | | `auth.public.enabled` | `false` | Removes the anonymous/shared `public` identity. Every request must carry a valid bearer token; an unauthenticated request gets `401`. | | `users.autoCreate` | `true` | Admits the owner's `did:key` on first authenticated request — a fresh venue has never seen it, and [admission](./auth#admission-authentication-is-not-membership) would otherwise return `403`. Acceptable here precisely because only loopback token-holders can reach the venue at all; for a stricter posture, provision the owner DID explicitly at startup via `user:create` with venue authority instead. | With these in place, the only way to reach the venue is a process on the same machine presenting a bearer token the venue accepts — i.e. the owner app. (Rate limiting, incidentally, defaults **off** on a loopback bind.) :::note[bindAddress vs hostname] `bindAddress` is the socket the listener binds to (restrict it to loopback). It is distinct from `hostname`, the venue's *advertised* public host used to derive `baseUrl` and the DID. An embedded venue typically sets no public `hostname`, so its identity stays its `did:key`. ::: ## Secrets belong to the owner, not to `public` An embedded venue's secrets — LLM API keys, provider credentials — are bootstrapped under the **owner's DID**, not the `public` identity. Config pre-populates the per-user encrypted secret stores at startup, keyed by DID: ```json5 { "secrets": { "did:key:z6MkOwnerAppKey...": { "ANTHROPIC_API_KEY": "sk-ant-...", "OPENAI_API_KEY": "sk-..." } } } ``` Top-level keys resolve as follows: `"venue"` → the venue's own DID, `"public"` → the `:public` identity, and anything else verbatim as a literal DID. Because the app authenticates as its owner DID, secret references (`s/ANTHROPIC_API_KEY`) resolve under that identity at invocation time. Nothing lands under `public`, so there is no shared credential surface. :::caution Never commit production secrets to a checked-in config. Keep the embedded venue's config (with its bootstrap secrets) in a per-user, non-tracked location the app manages. ::: ## Putting it together A complete embedded-venue config: ```json5 { "venues": [ { "name": "GetMine Local Venue", "port": 8080, "bindAddress": "127.0.0.1", "allowPrivateNetwork": false, "store": "/Users/alice/Library/Application Support/GetMine/venue.etch", "seed": "…ed25519 hex seed for a stable venue identity…", "auth": { "public": { "enabled": false } }, "users": { "autoCreate": true }, "secrets": { "did:key:z6MkOwnerAppKey...": { "ANTHROPIC_API_KEY": "sk-ant-..." } } } ] } ``` This venue: - listens on loopback only, unreachable off-box; - rejects browser Private-Network reach-in; - requires a bearer token for every request — no anonymous access; - accepts the owner app's self-signed JWT (audienced to this venue's DID) and admits it on first use; - resolves the owner's secrets under their DID. An embedded venue holding personal data should also **encrypt its store**. Java embedders can go one better than a config key: supply the Etch key via a key function (a KMS, OS keychain, or user passphrase) and hand the venue an already-opened store, so no key material ever touches config or disk — see [Security](./security#encrypt-the-store). The app's responsibilities, in turn: 1. Generate an Ed25519 key pair once and persist it. 2. Derive its `did:key` and read the venue's DID from `/.well-known/did.json`. 3. Sign a bearer JWT per session — `sub` = its `did:key`, `kid` = its public key, `aud` = the venue's DID, with an expiry — and send it as `Authorization: Bearer `. 4. Bootstrap its secrets under its own DID (via the config above, or `secret:set` as the authenticated owner). ## See also - [Configuration Reference](./configuration) — every venue config key and its default. - [Authentication](./auth) — the auth primitives this recipe composes: [self-issued JWTs](./auth#self-issued-eddsa-jwts) and [public access](./auth#public-access). - [Persistence](./persistence) — the `store` and `seed` settings for a stable venue identity across restarts. --- ## Operator Guide This section is for people who want to **run their own venue** — a node on the Covia [Grid](../overview/grid) that hosts operations, runs agents, and persists state. Running a venue lets you control local resources, install custom adapters, hold your own data, and federate with other venues under your own governance. Operators remain fully in control of their own infrastructure, resources, and data. Each venue decides its own access policy and keeps its data where it is governed. ## In this guide - **[Venue Quick Start](./venue-start)** — get a venue running locally, via Docker, or from source, and point clients at it. - **[Configuration Reference](./configuration)** — every venue config key and its default. - **[Authentication](./auth)** — public access, OAuth login providers, and bearer/UCAN tokens. - **[Security](./security)** — encrypted stores, pinned identity, network hardening, admission, and the production checklist. - **[Persistence](./persistence)** — how venue state is stored, the durability model, and recovery. - **[Embedded Venue](./embedded-venue)** — the loopback, single-owner deployment shape for bundling a venue inside an app. ## Related - [Capabilities](../user-guide/capabilities) — the authorisation model you enforce on callers - [Adapters](../user-guide/adapters/) — the operations a venue can offer - [Venues](../overview/venues) — what a venue is, conceptually --- ## Persistence Covia venues persist all state — assets, jobs, agents, secrets, workspace data, and DLFS drives — to an on-disk Etch store backed by lattice technology. This page explains how persistence works, what operators need to configure, and what to expect during shutdown and recovery. See the [Configuration Reference](./configuration) for the `store`, `seed`, and `storage` keys. ## How It Works Venue state is persisted by a background sweep: ``` Component writes → in-memory fork → (every 100ms) → signed root cursor → Etch store (disk) ``` 1. **Writes accumulate in a fork** — all mutations (API calls, MCP operations, agent runs, jobs) write to an in-memory fork of the lattice 2. **Daemon sweep (every 100ms)** — a background thread merges the fork into the root cursor with a single Ed25519 signature 3. **Periodic fsync (~every 10s)** — the sweep forces a store-level `fsync` only when at least ~10 seconds have elapsed since the last flush, bounding the data-loss window on an *unclean* host shutdown (kernel panic, power loss, hard VM stop) 4. **Critical writes flush immediately** — job completion, secret rotation, agent termination, and OAuth login trigger a synchronous flush that bypasses the background queue and resets the periodic-fsync timer No operator configuration is needed for the sweep — it runs automatically. ## Store Configuration The `store` field in venue configuration controls where state is persisted: ```json { "store": "/path/to/venue.etch" } ``` | Value | Behaviour | |-------|-----------| | `"/path/to/venue.etch"` | **Persistent file store** — state survives restarts. Recommended for production. | | `"temp"` | **Temporary store** — deleted on exit. Default; suitable for development. | | `"memory"` | **In-memory only** — no persistence at all. Fastest, but all state lost on exit. | If `store` is **omitted entirely**, the venue falls back to an ephemeral temp store and logs a warning that data will be deleted on exit. Set `store` to a file path for persistence, or to `"temp"` / `"memory"` to make the choice explicit and silence the warning. ### Venue Identity The venue key pair resolves in a strict order: an explicit `seed` → a configured `keystore` (PKCS12) → a `venue.key` file beside a persistent store (auto-generated with owner-only `0600` permissions if absent) → freshly generated. On restart with the same store path, the same venue DID is restored — identity is stable across restarts. Two deliberate edges: an **ephemeral** (`temp`/`memory`) venue without `seed` or `keystore` gets a new DID every start by design, and reopening an existing store with a key that owns none of its state is a **startup error naming the store's real owner** — a wrong key can never silently create a fresh empty venue and orphan your data. ### Encrypted Stores The Etch store can be an **encrypted Etch v3 store** (`etch` config block) — everything inside it (lattice state, agents, secrets, DLFS drives) is then encrypted at rest, with fail-closed key handling. The store key and the identity `seed` are independent secrets: rotate and guard them separately, and with an encrypted store prefer `seed` or `keystore` over a plaintext `venue.key` sitting beside it (the venue warns about that combination). One caveat: `storage.content: "file"` writes asset content bytes *outside* the store as plaintext files. See [Security](./security#encrypt-the-store) for the full treatment. ## Shutdown On graceful shutdown, the venue follows a strict ordering to ensure no writes are lost: 1. **Engine closes first** — stops accepting new sweep tasks, waits up to 2 seconds for any in-flight sweep to finish, then runs a final synchronous flush (merges fork, persists to root cursor) 2. **Node server closes second** — reads the root cursor (now guaranteed to contain the engine's final writes), broadcasts final state, and flushes the Etch store This ordering is critical. The fork pattern means writes sit in memory until explicitly merged. Without the engine-first flush, the node server's shutdown drain would read stale state. ## Recovery On restart, all state is automatically restored: 1. Etch store restores the lattice root cursor from the last persisted value 2. Engine creates a fresh fork for subsequent writes 3. Job manager scans for PENDING/STARTED jobs and restores their records (jobs that were mid-execution are marked as recoverable; they do not automatically re-execute) 4. Agents with pending work wake automatically via the scheduler 5. DLFS drives are recovered from the independent `:dlfs` lattice region No manual recovery is needed — restart is idempotent. ## Durability Window Two intervals matter, because they fail differently: - **Process kill (SIGKILL):** only writes still in the fork — at most one sweep interval (~100ms) — are at risk. Writes the sweep has already merged and written to the store survive a process kill, because the OS flushes them from its page cache. - **Unclean host shutdown (kernel panic, power loss, hard VM stop):** writes that reached the store but haven't been `fsync`ed can be lost — up to the periodic-fsync interval (~10s). Critical operations (job completion, secrets, agent termination, OAuth login) always flush synchronously and are not subject to either window. ## Failure Modes | Scenario | Behaviour | Recovery | |----------|-----------|----------| | **Abrupt process kill (SIGKILL)** | Writes in the fork since the last sweep (≤100ms) are lost; writes already swept to the store survive. | Restart venue; recovered state appears immediately. | | **Power loss / kernel panic** | Unsynced store writes since the last fsync (≤~10s) may be lost. | Restart venue; state is restored to the last fsync. | | **Disk full** | Etch store write fails. Sweep logs a warning and retries next cycle. In-memory fork accumulates. | Free disk space; sweep resumes automatically. | | **Graceful shutdown (SIGTERM)** | Final flush runs; all in-flight writes are persisted. | Clean restart with full state. | ## Monitoring Watch for these in venue logs: - **Sweep warnings** — indicate the background sync is failing (disk issues, lock contention) - **Flush timeouts** — indicate the synchronous barrier is taking longer than expected - **Recovery messages** on startup — confirm how many jobs and agents were restored ## Related - [Venue Quick Start](./venue-start) — launching and configuring a venue - [Authentication](./auth) — configuring authentication for your venue --- ## Security Your venue holds things worth protecting: users' workspaces and files, agents and their memory, API keys, and an audit trail people will rely on. Covia's security posture is built on two principles — **secure defaults** (anonymous callers get a read-only ceiling, unknown identities get nothing, agents cannot mint authority) and **fail-closed configuration** (a security misconfiguration stops the venue at startup; it never silently downgrades to an unprotected state). This page tours the controls in the order an attacker would meet them, and ends with a production checklist. Config snippets show venue-entry keys — in a real config file they sit inside an entry of the top-level `venues` array (see [Configuration](./configuration#the-config-file-is-a-server-document)). ## Encrypt the store A venue's Etch store contains everything: lattice state, agent records, DLFS drives, secrets. If a disk, VM image, or backup walks away, encryption at rest is what makes that a non-event. Venues run on **encrypted Etch v3 stores**: ```json { "store": "/data/venue.etch", "etch": { "version": 3, "cipher": "aes-256-ctr", "encryptIndex": true, "key": { "env": "COVIA_ETCH_KEY" } } } ``` - **Ciphers**: `aes-256-ctr` or `chacha20` (encryption requires `version: 3`). - **Key sourcing**: exactly one of `{"env": "VAR"}`, `{"file": "path"}`, or an inline hex string (dev/test only — never commit key material). The key must resolve to exactly 32 bytes of hex or the venue refuses to start. - **Key identity hints**: the store is stamped with an identity derived from its key, and the venue verifies it before opening. Start with the wrong key and you get a one-line diagnosis naming the key the store actually needs — never a garbage-decrypt or a mysteriously empty venue. - **Fail-closed, everywhere**: an unset environment variable, an unreadable key file, a wrong-sized key, a cipher with no key source — all are startup errors. There is no code path from "operator asked for encryption" to "data landed on disk unencrypted". Two caveats the venue itself warns about: with `storage.content: "file"`, asset content bytes live *outside* the encrypted store as plaintext files; and an auto-generated plaintext `venue.key` sitting beside an encrypted store means disk theft still yields your venue identity — prefer `seed` or a `keystore` and remove the key file. Embedded deployments can go further: the embedding application supplies a **key function** (backed by a KMS, HSM, or user passphrase) and hands the venue an already-opened store — key material never touches config, environment, or disk. See [Embedded Venue](./embedded-venue). Secrets get a second, independent layer: the per-user secret store encrypts every value with AES-256-GCM under a key bound to the venue's identity, so with an Etch cipher enabled your users' API keys are encrypted twice. ## Pin the venue identity The venue's key pair is the root of every UCAN it signs, every session it issues, and every federation trust decision — so identity is pinned, not assumed. Declare it with the `did` config key: - A declared `did:key` must **match the venue key pair** — a venue started with the wrong seed or keystore refuses to run rather than impersonate. - A declared `did:web` must match the configured public `hostname`, and did:web callers verify at every ingress with HTTPS-only, public-hostname-only resolution. - An existing store opened with a key that owns none of its state is a **startup error naming the store's real owner** — a wrong key can never silently create a fresh empty venue and orphan your data. - The auto-generated `venue.key` file is written with owner-only (`0600`) permissions, repaired on every launch. For production, prefer an explicit `seed` or a PKCS12 `keystore`. One consequence to plan around: user secrets are encrypted under a key derived from the venue seed, so **rotating the seed orphans existing encrypted secrets** — treat the identity seed and the store key as separate secrets, rotated deliberately. ## Control the network surface The defaults suit development: bind on all interfaces, permissive CORS. A venue holding real data should tighten each one — and the venue helps, by failing at startup on invalid values rather than silently widening. - **`bindAddress`** — set `"127.0.0.1"` for anything local or embedded; the loopback bind also flips rate limiting and Private Network Access to matching defaults. - **TLS** — the venue deliberately does not terminate TLS; run it behind a reverse proxy (the repo's `deploy/` directory ships a working Caddy setup with automatic HTTPS) and set `baseUrl` explicitly. - **`corsOrigins`** — narrow `"*"` to your real origins. The `"loopback"` sentinel matches literal localhost forms without ever resolving DNS, which blocks DNS-rebinding tricks; denied origins get a clean 403. - **Rate limiting** — per-caller token buckets, on by default for any non-loopback bind: `{"rateLimit": {"rps": 100, "burst": 300, "maxConcurrentJobsPerUser": 100}}`. Buckets are keyed per user DID (all anonymous traffic shares one bucket), over-limit requests get `429` with `Retry-After`, and a concurrent-jobs cap stops one caller from monopolising the job engine. - **SSRF protection** — outbound HTTP, bridged MCP servers, and A2A URLs all pass the same guard: loopback, site-local, and link-local addresses are blocked, HTTP(S) only. ## Decide who gets in Authentication proves control of a DID; it does not make someone a member of your venue. **Admission is a separate, deliberate step**: - An unknown authenticated DID gets a `403` with an actionable registration message — before any state is created for it. `users.autoCreate` defaults to `false`; leave it that way on private and production venues. - Admit users explicitly: `users.bootstrap` in config (named users and their public keys, provisioned before HTTP starts), or at runtime via `user:create` — as the venue itself, or by a provisioner holding a venue-issued UCAN delegation for exactly that ability. No separate admin API to secure; the capability check *is* the admission control. - Anonymous access, when enabled, is bounded by the **public capability ceiling**: read-only, no `invoke`, unless you explicitly widen it. `"unrestricted"` exists for loopback throwaway venues only. - MCP inherits the same posture: `mcp.auth.required` defaults to the inverse of public access, and an explicit `false` cannot re-open MCP on a venue that requires authentication elsewhere. ## Extend without widening Everything that adds code or capability to a venue is operator-gated: - **Venue modules** load only from config, at boot, with optional `sha256` content pinning — and there is deliberately no runtime module-load operation, because in-process code is total compromise. - **The Python adapter is not a sandbox**: configured scripts run with the venue's full authority. The operator selects every script and allowlists every callable; callers can never submit source. Use containers for untrusted code. - **`strictConfig`** rejects unknown config fields at startup (typo-proofing your security settings); known fields are always validated fatally either way. **`strictAssets`** (default on) refuses to boot a venue whose operations failed to install. ## Production checklist - [ ] `store` is a file path, with `etch.cipher` set and the key sourced from `env` or `file` - [ ] Venue identity from `seed` or `keystore` — no plaintext `venue.key` beside an encrypted store - [ ] `did` declared, pinning the identity fail-closed - [ ] TLS terminated by a reverse proxy; `baseUrl` set explicitly - [ ] `corsOrigins` narrowed from `"*"`; `bindAddress` restricted where applicable - [ ] Rate limiting left on (or tuned); `maxContentSize` appropriate - [ ] `auth.public.caps` reviewed; `users.autoCreate` off; admission via bootstrap or delegated `user:create` - [ ] Secrets supplied as bootstrap config or `s/NAME` references — never in operation inputs, never committed - [ ] Modules pinned with `sha256`; Python scripts treated as trusted code - [ ] Startup log reviewed: the venue warns on exactly the gaps above (unencrypted vault data, plaintext key files, content stored outside the cipher) Found a vulnerability? Report it privately via [GitHub private vulnerability reporting](https://github.com/covia-ai/covia/security) or security@covia.ai — and remember the trust model: the security of a federation depends on the policies its operators configure. ## Related - [Configuration Reference](./configuration) — every key used above - [Authentication](./auth) — OAuth, token types, the access model - [Persistence](./persistence) — the store the cipher protects - [Embedded Venue](./embedded-venue) — the loopback, key-function deployment shape --- ## Venue Quick Start As a user you can work with the Covia Grid through the online web app or the SDK alone. But for more powerful capabilities, such as controlling local resources and installing custom adapters, you may want to create your own venue. A venue is implemented as a server process that you can run by obtaining `covia.jar` from the [latest release](https://github.com/covia-ai/covia/releases/tag/latest) on GitHub (see [Release channels](#release-channels) below for the options). Running a venue requires Java 21+ installed. The venue can be launched with: ```bash java -jar covia.jar ``` This will launch a local venue with a default configuration, suitable for testing and development. You can connect to it with the Covia web app at [http://localhost:8080](http://localhost:8080). To configure the venue, pass a JSON5 config file (`java -jar covia.jar config.json`); see the [Configuration Reference](./configuration) for all keys. Each venue also includes a web presence for diagnostics and discovery of server capabilities. This will appear at [http://localhost:8080](http://localhost:8080) — the same URL you use to connect from the Covia app. For an example of this website, see the hosted [Test Venue](https://venue-test.covia.ai). ## With Docker A published container image is available if you'd rather not install Java (the image ships its own Java runtime, currently the latest LTS): ```bash docker run -p 8080:8080 ghcr.io/covia-ai/covia:stable ``` Mount a volume and point `store` at it (see [Configuring the Venue](#configuring-the-venue)) to persist state across container restarts. ## Release channels Pick the channel that matches how much churn you want: | Channel | JAR | Docker image | Built from | |---------|-----|--------------|------------| | **Release** (recommended) | [`latest`](https://github.com/covia-ai/covia/releases/tag/latest) or a [versioned release](https://github.com/covia-ai/covia/releases) | `:stable`, or pinned `:0.9.3` | `master`, versioned releases | | **Development** | [`latest-snapshot`](https://github.com/covia-ai/covia/releases/tag/latest-snapshot) | `:latest` | every push to `develop` | The hosted example venues follow the same channels: [venue-1](https://venue-1.covia.ai) and [venue-2](https://venue-2.covia.ai) (Google Cloud) run the release channel, while [venue-3](https://venue-3.covia.ai) (AWS) and [venue-4](https://venue-4.covia.ai) (Azure) redeploy automatically from the development channel. For production, pin a specific version (a release tag for the JAR, `:0.9.3`-style image tags for Docker) and upgrade deliberately. Releases also publish the optional venue module jars (SQL, Python) with checksums alongside `covia.jar`. The development channel tracks `develop` and may change under you — it's the right choice only if you're following new features or contributing. See the [CHANGELOG](https://github.com/covia-ai/covia/blob/master/CHANGELOG.md) for what each release contains. ## Building the venue server The venue server is a Maven project in Java. You can build it with Java 21+ and Maven 3.7+ using the following command in a clone of the [Covia repo](https://github.com/covia-ai/covia) root directory: ```bash mvn clean install ``` This will build the project, including the full venue `.jar` at `venue/target/covia.jar`. ## Configuring the Venue A venue can be configured with a JSON / JSON5 config file to enable or disable features, add adapters, and more. Pass the config file path as the first argument when launching: ```bash java -jar covia.jar my-config.json ``` With no config file, the venue starts with a built-in local-test configuration (ephemeral temp store, MCP enabled). The config file is a **server document**: a top-level `venues` array where each entry is one venue — a single JVM can host several. The minimal persistent venue looks like: ```json5 { "venues": [ { "name": "My Venue", "port": 8080, "store": "/data/venue.etch" } ] } ``` Validation is fail-closed: a malformed known field stops startup with a precise error; unknown fields warn (set `strictConfig: true` to reject them). The repo-root [`local-dev.json`](https://github.com/covia-ai/covia/blob/master/local-dev.json) and [`venue-config.json`](https://github.com/covia-ai/covia/blob/master/venue-config.json) are working templates. By default a venue with no `store` configured keeps state in an ephemeral temporary store that is wiped on exit. For a venue you intend to keep, set `store` to a file path — see [Persistence](./persistence) for the durability model, [Authentication](./auth) for locking down access, and [Security](./security) for the production checklist. The repository's `deploy/` directory contains cloud provisioning examples (an Azure VM setup, a Caddy TLS proxy) you can adapt. --- ## COG Specifications COGs (Covia Open Grid specifications) are formal documents that define the technical standards, protocols, and governance mechanisms of the grid ecosystem. Each COG specifies one aspect of the system and progresses through the [community governance process](./governance). All COGs are currently at Draft status; none has yet been formally approved. ## What are COGs? COGs are the formal technical specifications that define: - **Protocol Standards**: Core protocol specifications and message formats - **Security Standards**: Cryptographic primitives, authentication, and authorisation - **Network Standards**: Federation, discovery, and cross-venue routing - **Data Standards**: Asset formats, artifacts, and metadata ## Available COGs COG numbers identify topics, not chronology: numbers are assigned when a topic enters the process, and the specifications are written and revised at different times — a lower number does not imply an earlier or more mature document. ### Core Protocol Specifications - **[COG-1](./cogs/COG-001)** - Architecture - **[COG-2](./cogs/COG-002)** - Decentralised ID - **[COG-3](./cogs/COG-003)** - Authentication Mechanisms - **[COG-4](./cogs/COG-004)** - Grid Lattice - **[COG-5](./cogs/COG-005)** - Asset Metadata - **[COG-6](./cogs/COG-006)** - Artifacts - **[COG-7](./cogs/COG-007)** - Operations - **[COG-8](./cogs/COG-008)** - Jobs - **[COG-9](./cogs/COG-009)** - Agent Messaging - **[COG-10](./cogs/COG-010)** - Venue Authentication & Access Control - **[COG-11](./cogs/COG-011)** - Agent Lifecycle *(exploratory draft — superseded in practice by session-based agents; see [Sessions](../user-guide/agents/sessions))* - **[COG-12](./cogs/COG-012)** - Orchestrations - **[COG-13](./cogs/COG-013)** - Agent Capabilities - **[COG-14](./cogs/COG-014)** - A2A Agent Interoperability *(not yet implemented)* - **[COG-15](./cogs/COG-015)** - Cross-Venue Federation - **[COG-16](./cogs/COG-016)** - Human-in-the-Loop Requests - **[COG-17](./cogs/COG-017)** - Capability Granting - **[COG-18](./cogs/COG-018)** - Agent Skills - **[COG-19](./cogs/COG-019)** - HITL Self-Sovereign Token Transport ## COG Development Process Each COG follows a structured development process: 1. **Proposal**: Community members propose new COGs or updates to existing ones 2. **Discussion**: Open community discussion and technical analysis 3. **Drafting**: Formal specification drafting with community input 4. **Review**: Technical and security review of reference implementations by experts 5. **Acceptance**: Approval for official adoption across the ecosystem ## Contributing to COGs To contribute to COG development: 1. **Join the community** on Discord and GitHub 2. **Review existing COGs** to understand the current specifications 3. **Propose improvements** or new specifications through the governance process 4. **Participate in discussions** and technical reviews 5. **Help implement** and test COG specifications ## COG Status Each COG carries a status indicating its development stage. The stages currently in use are: - **Exploratory Draft**: An early sketch that may be substantially revised or superseded - **Draft (Work in Progress)**: Actively being written; sections may be incomplete - **Draft**: A complete draft under community review Later stages — **Proposed** (ready for community decision), **Approved** (formally adopted), and **Deprecated** (superseded) — are defined by the [governance process](./governance); no COG has yet reached them. *Note: COG specifications are expected to evolve based on community feedback and real-world implementation experience.* --- ## Governance Covia follows a community-driven governance model that ensures transparent and collaborative evolution of the protocol while maintaining its core principles of openness, security, and interoperability. The Covia protocol is designed to evolve through community governance: - **Open Development**: All protocol changes are proposed and discussed publicly - **Community Voting**: Protocol upgrades are decided through community consensus - **Backward Compatibility**: Changes maintain compatibility with existing implementations - **Gradual Migration**: Protocol updates support smooth transitions for all participants The protocol continues to evolve based on real-world implementation experience, community feedback, and emerging requirements from the AI ecosystem. ## Principles ### 1. Open Participation - Anyone can propose protocol improvements via COGs - All discussions are public and archived - Decisions are made through consensus-building - Clear path for new contributors ### 2. Technical Merit - Changes evaluated based on technical soundness - Backward compatibility requirements - Security considerations paramount - Performance impact analysis ### 3. Decentralised Decision-Making - Multi-stakeholder governance - Distributed voting mechanisms - Prevention of capture by any single entity - Balance between stability and innovation ## Governance Process ### COG Proposals 1. **Proposal Stage** - Initial proposal submission - Community discussion and feedback - Appointment of editor / maintainer - Assignment of COG-XXX number 2. **Draft Stage** - Technical specification development - Development of reference implementation - Development of test suite - Publication of draft documentation 3. **Review Stage** - Formal technical review - Security analysis - Community impact assessment 4. **Acceptance Stage** - Consensus gathering - Final specification - Implementation planning 5. **Implementation Stage** - Reference implementation - Testing and validation - Deployment coordination ## Working Groups ### Core Protocol - Protocol specification maintenance - Security reviews - Performance optimisation - Backward compatibility ### Implementation - Reference implementation - Testing frameworks - Integration tools - Developer documentation ### Community - User feedback collection - Education and outreach - Adoption support - Event coordination ## Getting Involved 1. Join the discussion on [GitHub Discussions](https://github.com/orgs/covia-ai/discussions) 2. Participate in working group meetings 3. Submit or review [COG proposals](./cogs-overview) 4. Contribute to implementation and testing ## Current Governance Status The protocol is currently in its initial phase with a focus on: - Establishing core specifications - Building reference implementations - Growing the contributor community - Developing governance processes We welcome participation from individuals and organisations committed to advancing the future of federated AI orchestration. --- ## Grid Protocol The **Covia Open Grid** protocol is the foundational specification that enables federated AI orchestration and execution across organisational boundaries. A note on names: **Covia Grid** is the product, the live federated network of venues; **Covia Open Grid (COG)** is the open specification series that defines how any implementation joins it. The reference runtime is open source under EPL-2.0, the SDKs are Apache-2.0, and the overall model is open core: the protocol and runtime are open, and commercial offerings build on top without gatekeeping the standard. ## Protocol Components ### Core Specifications - **[Whitepaper](./whitepaper)** - Outline of the grid protocol design, architecture, and key implementation details - **[Governance](./governance)** - Protocol governance model, decision-making processes, and community participation guidelines - **[COG Specifications](./cogs-overview)** - Formal specification documents for the Covia Open Grid ### Key Protocol Features #### Identity and Trust - Decentralised identifiers for venues and assets ([COG-2](./cogs/COG-002)) - Authentication mechanisms: bearer tokens, OAuth, self-issued Ed25519 JWTs ([COG-3](./cogs/COG-003), [COG-10](./cogs/COG-010)) - Capability-based authorisation with UCAN delegation and attenuation ([COG-13](./cogs/COG-013), [COG-17](./cogs/COG-017)) - Cross-venue federation with relayed caller identity ([COG-15](./cogs/COG-015)) #### Assets and Data - Content-addressed, cryptographically verifiable assets and metadata ([COG-5](./cogs/COG-005), [COG-6](./cogs/COG-006)) - Conflict-free state replication on the Grid lattice ([COG-4](./cogs/COG-004)) - Immutable provenance via content-addressed Merkle verification #### Execution - Self-describing, invocable operations ([COG-7](./cogs/COG-007)) - Auditable jobs with an immutable state chain ([COG-8](./cogs/COG-008)) - Multi-step orchestrations across venues ([COG-12](./cogs/COG-012)) #### Agents and Humans - Stateful agent lifecycle and messaging ([COG-11](./cogs/COG-011), [COG-9](./cogs/COG-009)) - A2A interoperability ([COG-14](./cogs/COG-014)) and portable agent skills ([COG-18](./cogs/COG-018)) - Human-in-the-loop requests, consent, and capability granting ([COG-16](./cogs/COG-016), [COG-19](./cogs/COG-019)) ## Getting Started To understand and work with the Covia protocol: 1. **Explore the [Overview section](../overview)** for high-level concepts and architecture 2. **Read the [COG specifications](./cogs-overview)**, the normative protocol documents 3. **Review the [Governance Model](./governance)** to understand how the protocol evolves 4. **Join the community** on [GitHub Discussions](https://github.com/orgs/covia-ai/discussions) or [Discord](https://discord.gg/fywdrKd8QT) to contribute to protocol development and governance --- ## Covia.ai White Paper :::caution[Draft] This is a draft of the Covia white paper, for discussion only — not to be considered final. Parts of it predate the current protocol specifications; where they disagree, the [COGs](./cogs-overview) are authoritative. ::: ## Vision Modern AI systems are no longer isolated models or linear pipelines — they are **distributed, agentic, multi-party workflows** operating across APIs, clouds, internal services, and external data zones. Yet the infrastructure to run these systems with **control, coordination, and compliance** is missing. Covia introduces a **runtime orchestration protocol** that lets developers and infra teams **compose, execute, and govern AI-native workflows** with cryptographic traceability, policy enforcement, and modular deployment. Inspired by the modularity of **HTTP**, the operational consistency of **Kubernetes**, and the verifiability of **Git**, **Covia.ai** enables secure, multi-agent AI workflows to operate across trust boundaries — without losing control, visibility, or scalability. With Covia.ai, organisations of all sizes can orchestrate powerful AI supply chains, harnessing the best resources available in a global ecosystem, using open standard Internet technology. ## Protocol Overview The Covia.ai protocol defines a standard for orchestrating federated execution graphs across systems, teams, and infrastructure zones. It enables agents, APIs, models, and data services to interoperate securely under runtime policies and execution constraints. **Core Concepts** - **Assets** — Addressable, immutable units of data or compute - **Venues** — Execution environments with scoped trust and policy - **Agents** — Runtime-exposed capabilities: APIs, models, scripts, or services - **Operations** — Executable nodes in an orchestration graph - **Policies** — Rules enforced at runtime: identity, access, fallback, rollback - **Traces** — Verifiable records of what ran, where, and under what conditions Each Covia graph is: - Executed in parts, across federated venues - Governed by runtime policy per operation - Logged and signed for audit and traceability - Composable into reusable modules for orchestration at scale By combining execution and governance in a unified runtime, Covia.ai offers a **developer-friendly protocol for operating AI workflows safely in production** — even when they span multiple organisations or regulatory regimes. ## Architecture Overview Covia.ai is built as a **federated execution layer** for orchestrating AI workflows across distributed systems. The architecture is modular, pluggable, and policy-aware — designed to span clouds, teams, and partner organisations without centralising control or breaking trust boundaries. At the core of Covia’s architecture is the **Execution Graph Runtime**, supported by runtime policy enforcement, shared memory, and observability services — all operating within or across isolated **venues**. ### Component Summary | Component | Purpose | | -- | -- | | SDK/CLI | Developer tooling to define, simulate, test, and deploy orchestration graphs | | Execution Graph Runtime | Resolves graph nodes, manages retries, memory, rollback, and concurrency | | Policy Engine | Enforces runtime access, identity scope, fallback, and data handling constraints | | Venue Adapters | Interfaces for executing across clouds, SaaS tools, or partner agents | | Audit + Trace Layer | Emits signed logs, verifiable execution traces, and runtime observability | ### Federated by Design Each **Venue** (e.g. a workspace, AWS account, on-prem node, or vendor endpoint) operates with its own policy context and identity boundary. Nodes within a graph may execute across venues, with: - Isolated runtime sandboxes - Signed, policy-bound transitions - Encrypted memory scoped to actor identity - Unified trace collection and verification This allows Covia.ai to operate not just within an **organisation, but across organisational lines**, while maintaining runtime trust and governance. ## Key Concepts ### Assets Covia introduces the concept of a Universal Data Asset (UDA), a model that allows for the representation of *any* compute resource or data set in a standard form. An asset is defined by the following: - Immutable metadata that describes the asset - Asset Content, defined by the type of asset Assets are **immutable**. It is possible to modify assets, but this always creates a new asset version. This property is critical for multiple reasons: - Support for full cryptographic verification of asset integrity - Support trusted acquisition of assets from decentralised sources using cryptographic hashes as content-addressable IDs - Allow reliable distributed caching and replication (immutable data never becomes stale) Examples of assets: - A training data set used for model building - A set of records produced from a production system being sent for real-time processing - A compute service backed by a GPU system - A data access service providing extracts ### Metadata Asset metadata describes assets in their entirety. Asset metadata may include: - Human readable description of an asset - Authorship information - Licence for use (e.g. Creative Commons) - Cryptographic hashes for content (allowing verification and content acquisition) - Asset provenance (e.g. references to input data sets used for model training etc.) Asset metadata is produced by the author of an asset upon asset creation. Typically, this can be automated with the ability for the author to add optional fields if required. Relying parties may choose to independently validate claims expressed in the metadata, or alternatively may trust the author. It is important to note that while some metadata can be automatically verified, this is not possible for all forms of metadata, since claims may depend on external information and judgement (e.g. relating to legal IP rights). ### Asset IDs All assets are logically identified with a unique ID. The ID is defined to be the cryptographic hash of the asset metadata. By also including the hash(es) or any relevant content within the metadata, this effectively forms the root of a Merkle tree, such that all metadata and content can be recursively verified. This also means that the ID can be used to locate the metadata in **content-addressable storage**. This scheme of identifying assets by a cryptographic ID ensures that every version of every asset can be uniquely identified and verified using its ID alone. ### Venues A venue is a logical location that manages assets. Typically, venues are aligned with some organisation or group within an organisation that controls sets of related data. An example might be a predictive modelling team that builds AI models based on consumer behavioural data. The venue would be used for storing training data and predictive models as assets, and offering services that allow other parts of the organisation or trusted partners to request pseudonymised output data sets. Venues, by design, may differ in a number of important ways: - The types of compute facilities, services, or data services provided - Physical limitations, such as storage capacity - Different governance arrangements or access control rules - Scale can be anything from a single network-connected server up to a global virtual network spanning multiple data centres ### References Utilising an asset through the protocol requires two pieces of information: - The asset ID, which identifies the asset (and the means to verify it) - A venue which provides access to the asset Together, this information is a "reference" to an asset. In most circumstances, this information may safely be made public: the asset ID is a cryptographic hash which by design conveys no useful information about the asset metadata or content contained within, and the venue should enforce appropriate access control to ensure that the asset cannot be accessed by unauthorised users. It is possible for multiple venues to possess a copy of the same asset. This can be recognised by references that contain the same asset ID. Since assets are immutable and verifiable, obtaining an asset from one venue is equivalent to any other. For efficiency reasons, users should prefer utilising a copy of an asset in a local venue if it is already available. References are possible in multiple formats: ```text // Covia protocol scheme: cdp:v/mycompany.analytics.venue-101 /a/0c1aee860be175d66152388a6513fd4fa11449c1612cbd04dca92ec92e3d0cca // Standard Web URL: https://data.mycompany.com/cdp/api/v1/v/mycompany.analytics.venue-101 /a/0c1aee860be175d66152388a6513fd4fa11449c1612cbd04dca92ec92e3d0cca // JSON reference: { "venue": "mycompany.analytics.venue-101", "asset": "0c1aee860be175d66152388a6513fd4fa11449c1612cbd04dca92ec92e3d0cca" } // DLFS Drive path dlfs://drive.mycompany.com/venue-assets /101/0c1aee860be175d66152388a6513fd4fa11449c1612cbd04dca92ec92e3d0cca // Note: technically a DLFS drive need not be a complete venue, but it can // host assets and metadata and drivers may therefore utilise it as a venue ``` ### Agents An agent is a system component that supports the Covia protocol and can respond to protocol requests. As such, it represents the "server" aspects of a Covia enabled system in the client/server model. Typically, an agent represents one or more venues, under the control and governance of a single organisation. An organisation may operate multiple agents, perhaps representing different functions, geographies or IT infrastructure domains. Agents provide access to back-end systems such as storage, GPU compute clusters, databases or enterprise services. This role is critical, because it allows existing data assets and infrastructure to be harnessed in the Covia ecosystem. There is significant economic and strategic value in the fact that agents can enable access to such resources *without* requiring significant changes to existing systems. Assets may be exposed as either: - A pure data asset - this is likely to be most appropriate for immutable data, e.g. a specific version of a file or data set. - An operation, which allows dynamic / real-time access to an underlying data source - this is appropriate for dynamically changing data where a higher level process may need to acquire the latest version. Agents SHOULD implement access control appropriate to the venues they represent. There is a spectrum of possible levels of access: - Fully open (free public data, information commons) - Public fee-based (subscription models, tokenised payments etc.) - Trusted (research collaborations, trusted business partners) - Internal (authorised teams within organisations) - Restricted (no access to most assets except through highly controlled operations) Covia provides open source reference implementations for agents, however any ecosystem participant is free to develop their own custom agents providing these follow the standard protocol. ### Drivers A driver is a software module used to access assets and venues via the Covia protocol. Typically, this is available as a software library for developers or a plug-in to enterprise software systems. A driver is the client component of a Covia based system and communicates directly with agents. Drivers MUST offer a set of standard functionality, most importantly: - Resolve standard references to an asset / venue - Retrieve / query an existing asset (or its metadata) - Create an asset (with metadata) - Upload an asset (to a venue which authorises this) - Invoke a compute service Additionally, drivers MAY offer further functionality as extensions: - Ability to manage access control / governance for specific venues - Ability to create a temporary working venue for short term collaboration / development purposes - Advanced search capabilities Driver developers are encouraged to innovate around driver extensions, but significant new developments SHOULD be standardised to help facilitate interoperability and ultimately allow some extensions to become part of the core protocol. ### Operations An operation is a special form of data asset that can be *invoked*. A typical operation might be training a model, anonymising a data set, or producing model output in response to a user request. An operation may define a number of inputs, and a number of outputs. These may be either: - Assets - Values encoded as UTF-8 strings An operation can be considered similar to a web service, with the important additional capabilities: - Its inputs can include assets anywhere in the Covia ecosystem (since these can be referenced, and accessed using a driver) - Its outputs can include the creation of new assets, which can therefore be consumed by subsequent operations Agents may be configured to expose internal services or API functions accessible locally as an operation. By this mechanism, agents make such services available to the broader ecosystem in an interoperable manner, while being able to enforce any additional security checks and access controls that may be required. ### Orchestration Orchestration is the execution of arbitrary graphs of operations across arbitrary sets of participants. This is possible because of the features of the Covia protocol that ensure data assets and operations are designed to behave in a standardised way and can be composed to build higher level processes. As it is an operation, an orchestration can define arbitrary inputs and outputs. These are passed to underlying operations as required. Orchestration is executed by agents, which must also normally include a driver (in order to access input assets if required). Orchestration agents may impose appropriate access controls, as with any agent. The orchestration agent will also usually require appropriate authorisation to execute the underlying operations. An orchestration MAY be considered as an operation in its own right, and hence used as a composable building block to create a higher-level orchestration. It is possible for an orchestration to include assets which are not directly accessible to other parties in the orchestration. This capability is particularly important when access to some assets may be highly restricted (e.g. patient medical records), and it is necessary to send compute operations to the data to be executed in a secure trusted execution environment. It is possible for any party to validate orchestration results by checking that metadata of output assets corresponds to the orchestration execution graph and any inputs used. This creates an automatic, verifiable provenance chain for any AI process, even where this spans multiple parties. This validation process involves several key steps: **Validation of Orchestration Results** 1. **Metadata Verification**: The metadata associated with each output asset contains detailed information about the orchestration execution graph and the inputs utilised. By examining this metadata, parties can trace back the operations and data sources involved in producing the output. 2. **Execution Graph Analysis**: The orchestration execution graph provides a comprehensive map of the operations performed during the AI process. This includes the sequence of steps, the specific algorithms or models applied, and any intermediate data transformations. By analysing this graph, parties can verify the procedural integrity of the orchestration. 3. **Input Correlation**: The metadata records the inputs used in the orchestration process. By comparing these recorded inputs with the actual data used, parties can ensure the correctness of the data flow. This step is crucial for confirming that the outputs are based on accurate and intended inputs. 4. **Consistency Checks**: To further ensure the validity of the orchestration results, additional consistency checks can be performed. These checks compare the outputs against expected patterns or benchmarks, identifying any discrepancies that may indicate errors or anomalies. By following these validation steps, stakeholders can enhance trust and transparency in AI orchestration processes. This robust validation framework ensures that the orchestration results are not only accurate but also auditable, thereby reinforcing confidence in the AI system’s outputs. ### Lattice technology A lattice is a mathematical, algebraic structure with important properties: They can be used to form conflict-free replicated data types that automatically converge to consensus. This is a powerful tool for distributed systems, as it allows for consensus to be reached without locking. The Covia protocol makes use of lattice technology in several areas: - For execution of distributed operations required in orchestration - For P2P transmission and replication of large verifiable Merkle tree based data structures (including DLFS drive) - For the consensus mechanism of the Convex public virtual machine (used optionally for smart contracts and decentralised tokenisation of AI services) ### Open Ecosystem The protocol enables an open, peer-to-peer ecosystem of service providers offering an unlimited variety of venues, assets and services. It is an open standard precisely because this is the best model to enable an effective ecosystem. As such, the protocol is flexible and can support any business model chosen by participants. We anticipate: - Collaboration between trusted organisations on a contractual basis - Services offered publicly on a pay-as-you-go basis - Marketplaces of assets with licences for sale - A wide variety of free data assets open to all It is possible to configure agents to allow access to any existing data assets via the Covia protocol, so that existing data ecosystems are opened up to Covia users in a standard, interoperable way. As such, the protocol serves as a "bridge" between different islands of public and private data. ## Solution Roadmap The Covia roadmap is based around building interoperable components that implement the Covia protocol. Many of these will be made available as open source reference implementations, that anyone can use freely or customise for their project. Companies in the ecosystem are free to offer commercially supported versions of these or create their own solutions as long as they remain consistent with the open standard protocol. :::note[Status (August 2026)] This section predates the current implementation and is retained for context; much of it has shipped. The driver libraries exist as the [TypeScript](../user-guide/sdk/typescript) (npm `@covia/covia-sdk`), [Python](../user-guide/sdk/python) (PyPI `covia`), and [Java](../user-guide/sdk/java) SDKs. The reference agent is the open-source [venue runtime](../operator-guide/venue-start) (single JAR or container). The orchestration engine ships as the venue's [orchestrator adapter](../user-guide/adapters/orchestrator), executing multi-step graphs with cross-venue delegation. The standard documents are the [COG specifications](./cogs-overview). Enterprise adapters have shipped for AI frameworks ([LangChain4j](../user-guide/adapters/langchain-adapter)), [SQL databases](../user-guide/adapters/sql), [file systems](../user-guide/adapters/file), HTTP services, and [MCP bridging](../user-guide/mcp/calling-mcp-tools); NoSQL databases, GPU clusters, enterprise service buses, and enterprise application connectors remain on the roadmap. ::: ### Universal Protocol Toolbox The protocol toolbox provides software, tools, and documentation for developers building projects using Covia. #### Driver Libraries Driver libraries are software libraries designed to allow developers to embed the Covia protocol into their products and solutions, while continuing to use their existing preferred tools and programming languages. In effect, they can add a Covia driver to their software with just a few lines of code. The driver libraries provide client support for all Covia protocol operations. Important examples include: - Create and validate metadata - Publish / upload an asset to a venue - Execute an operation (which could be a complete orchestration) - Read asset metadata - Download asset content We will initially target three major language ecosystems widely used in the enterprise software / data / AI space: - Python - JavaScript - Java #### Reference Agents Reference agents are software components that serve as a Covia protocol agent. These are designed to be configured and operated on standard commodity servers, e.g. running Linux. Standard agent features include: - Storage on local file systems - Metadata management - REST API server endpoints - Access control mechanisms - Ability to invoke operations - Ability to proxy and relay operations to other agents - Ability to perform decentralised functions on the Convex network The agents are readily customisable, especially with respect to allowing developers to implement their own custom operations or access control rules. #### Enterprise adapters Enterprise adapters are agents that connect to existing systems and allow data assets and services to be utilised via the Covia protocol. Key enterprise adapters will include: - AI frameworks (e.g. LangChain) - Enterprise storage systems - SQL / ODBC databases - NoSQL databases - GPU / compute clusters - Enterprise Service Buses and middleware platforms - Enterprise application software (e.g. SAP) Similar to the reference agents, enterprise adapters offer the ability to customise access control rules. This may be used to offer an additional layer of security on top of the security implemented in the underlying enterprise systems. #### Standard Documents The Covia protocol will be documented and maintained as a public open standard and as a reference for developers. ### Orchestration Engine The Orchestration Engine is an advanced Covia agent that supports the execution of orchestration graphs. Key functionality includes: - Managing a database of orchestration graphs - Executing specific graphs on demand and reporting results - Delegating operations within the graph to other Covia agents (possibly remote, or managed in a venue provided by a different organisation) - Customisable logging and access controls - Making entire orchestrations available as composable operations for others to re-use ### Data Lattice File System (DLFS) The Data Lattice File System is a powerful decentralised data storage system using Lattice Technology - "Dropbox meets IPFS meets BitTorrent". #### DLFS Browser DLFS Browser is a GUI tool for exploring and managing filesystems on DLFS (local or remote). In addition to standard file management capabilities, the Browser incorporates an embedded driver and tools to interact directly with the Covia protocol, e.g. publishing a DLFS file as a data asset on a venue or executing an operation with a given file as input. #### DLFS Node The DLFS Node is a back-end storage node for DLFS. This node runs on a server (typically internet connected) and replicates subsets of the DLFS lattice with its peers. The DLFS Node also serves as a Covia agent and can be configured to make DLFS based assets accessible to protocol users as a venue with customisable access control rules. ### Decentralised Vector Database Lattice Technology offers the intriguing possibility of a truly decentralised vector database, allowing the distributed storage, search and retrieval of vector data. Such vector databases are likely to become increasingly important with the demand for AI systems to use data based on vector embeddings. This solution is based on combining several important technologies: - Lattice Technology for replication and validation - Kademlia-style P2P routing - Locality-sensitive hashing (LSH) - Vector similarity search Decentralised vector database options can also be used directly with the Covia protocol, because database nodes are themselves Covia agents! So a vector similarity search can easily be included in a Covia-based orchestration as part of a larger AI process pipeline. --- ## COG-1: Architecture ``` Status: Draft Version: 1.0 Created: 2024-08-06 Updated: 2026-08-07 Authors: Mike Anderson ``` This standard specifies the overall design and architecture of the Covia Open Grid. ## Purpose The Grid enables secure and efficient collaboration between AI agents, data providers, and compute resources across organisational boundaries. The Grid has been designed with several core principles: - **Decentralisation** : nodes in the Grid are independent and operate on a peer-to-peer basis, free from any centralised or external control. - **Open protocol** : There is a formally specified protocol available as an open standard that can be freely implemented, and has an open source reference implementation - **Security** : All interactions are secure by design, ensuring integrity, maintaining high availability and preventing unauthorised use of resources on the Grid - **Universality** : The Grid supports any type of resource - any data type, any sort of compute operation, any sort of interaction of software systems with the real world. It does this in a way that is fully pluggable and composable. - **Scalability** : The Grid has no limit to scalability - constraints are simply those of the underlying resources - **Interoperability** : The Grid connects to existing systems and resources of any type, so that existing infrastructure can be utilised - **Technology neutrality** : The Grid architecture does not make any assumptions about the technology choices made by specific implementations or underlying systems. This enables independence from vendor lock in and ability to continuously incorporate new advances without rework. This specification defines the architectural principles and core components that make this possible. ## Terminology | Term | Definition | | ---- | ---------- | | Grid | The global network of venues that enables federated AI orchestration and access to data and compute resources of all forms | | Venue | A node in the Grid that is independently governed, addressable and responds to requests from other nodes | | Client | A software application or component that makes requests to the Grid | | Asset | A resource accessible and addressable on the Grid. An asset represents either compute or data resources | | Asset ID | The Value ID (SHA3-256 hash of the canonical CAD003 encoding) of an asset's metadata, serving as a unique content-addressable identifier. See [COG-5](./COG-005) | | Metadata | A JSON object describing an asset's properties, including name, description, content references, and operational parameters | | Operation | A type of asset which represents an executable function on the grid, which takes inputs and produces results in response to a request | | Artifact | A type of asset which represents immutable data or content. This may be a file containing content in a specific format, or a snapshot of a database etc. | | Adapter | A component responsible for executing a specific type of operation, translating between Grid protocols and underlying systems | | Agent | An autonomous AI system connected to the grid. It may respond to grid requests or run grid requests itself | | Job | A specific task submitted to the grid via a request to run an Operation. Jobs are asynchronous processes that may fail or succeed | | DID | A decentralised identifier used to identify a venue, asset, job or client | | Transport | A mechanism for conveying messages between nodes, e.g. REST API calls over HTTPS | | Orchestration | An operation which is responsible for executing one or more child operations (which may in turn be lower level orchestrations) | | Lattice | A mathematical structure used for distributed state management that supports conflict-free merging of data from multiple sources | | CRDT | Conflict-free Replicated Data Type - a data structure that can be replicated across nodes and merged without conflicts | ## Specification ### Universal addressability Entities on the Grid are designed to be universally addressable via global namespaces so that clients, venues, assets, and jobs can be uniquely identified and accessed across the network, regardless of their physical or organisational location. This is achieved through the use of Decentralised IDentifiers (DIDs), which provide a standardised, interoperable mechanism for referencing entities in a way that is independent of any specific venue or transport mechanism. Universal addressability ensures that resources and operations on the Grid can be seamlessly discovered, requested, and utilised by authorised clients, supporting the principles of decentralisation and interoperability. The architecture allows for flexible resolution of DIDs to specific endpoints, enabling dynamic routing of requests while maintaining the integrity and autonomy of each node in the Grid. Grid addresses in general consist of two parts: - **Venue DID** - The decentralised identifier of the venue hosting the resource. The grid supports DID resolvers that can connect to the specified venue given an appropriate DID. - **Entity ID** - The ID of the specific resource within the venue This design means that multiple venues may host equivalent copies of the same asset or operation. This capability is critical to allow for P2P replication of assets and other resources, since venues may need to obtain verifiable exact copies of artifacts in order to perform appropriate computation Example: `did:web:open.covia.ai/a/c147dae085baa0124bd1678698ea91645811075fbf2f051ab4a330bec1b7f742` This specifies an artifact with the metadata hash `c147dae085baa0124bd1678698ea91645811075fbf2f051ab4a330bec1b7f742` located at the venue identified by the hostname `open.covia.ai`. The use of the metadata hash as an asset ID is a particularly important feature because it ensures that: - Metadata can be *fully verified* (only the exact metadata string of the artifact is expected to produce the same cryptographic hash) - If the metadata contains hashes of content, the content can also be verified (forming a Merkle tree) - Assets on the Grid are *content-addressable* ### Grid network roles The Grid is a network of nodes that exchange messages (requests and responses) through one or more transport mechanisms. #### Venues A venue is a Grid node that responds to requests, typically implemented as a server process accessible to the Internet. The role of venues is to provide controlled access to resources to clients elsewhere on the Grid, and ensure proper governance of protocol activity. A venue SHOULD respond to any request it receives. A venue MUST reject requests to access or utilise assets that the requestor is not authorised to use, e.g. private artifacts created by a different user. A venue SHOULD obtain a DID to ensure that it is easily addressable by clients on the Grid. Without such addressability, clients will have to configure an appropriate connection and transport mechanism manually. A venue MAY be a client of other venues on the Grid, e.g. when acting as an orchestrator of operations on behalf of one of its own clients. A venue MUST provide metadata on any asset it serves to any authorised client. A venue SHOULD create a job in response to any authorised request to run an operation. Exceptions might include rate limiting or dealing with resource constraints. #### Clients Any system or software able to send requests to the Grid is considered a client of the Grid. Examples of clients include: - A web application used by a human such as [https://app.covia.ai](https://app.covia.ai) - An AI agent working on a set of long-running project tasks - Software written in a general purpose programming language such as Python, and using the Covia SDK A client MUST connect to at least one venue with an appropriate transport mechanism in order to utilise the Grid A client MAY make a local connection to itself. This is frequently useful for testing purposes, or in cases where a venue needs to orchestrate multiple operations where a subset can be run locally. A client MAY simultaneously connect to multiple venues A client MUST provide appropriate credentials when necessary to access specific grid resources #### Grid Requests A grid request is a message from a client to a venue asking it to act — to read metadata or content, invoke an operation, observe or mutate a job, or deliver a message. Every request receives a response: a result, or an error. Requests are defined independently of transport. The reference binding is the REST API over HTTPS; venues MAY additionally expose the same underlying model through other protocol surfaces such as MCP and A2A (see [COG-9](./COG-009) for how these bridge to jobs and messaging). The principal request categories, and the specifications that define them: | Category | Examples | Defined by | | -------- | -------- | ---------- | | Discovery and metadata | DID documents, asset metadata, content retrieval | [COG-2](./COG-002), [COG-5](./COG-005), [COG-6](./COG-006) | | Invocation | Run an operation, creating a Job | [COG-7](./COG-007), [COG-8](./COG-008) | | Job observation and mutation | Status, streaming, cancel, pause, messages | [COG-8](./COG-008), [COG-9](./COG-009) | | Authentication | Login, token issuance | [COG-3](./COG-003), [COG-10](./COG-010) | | Relayed requests | A venue calls another venue on a client's behalf | [COG-15](./COG-015) | Cross-cutting requirements: - Every request is subject to authentication ([COG-3](./COG-003)) and capability authorisation ([COG-13](./COG-013)) before dispatch. - An authorised request to run an operation creates a Job ([COG-8](./COG-008)). Admission control — rate limits, concurrency caps, resource constraints — MAY reject a request before a job is created. - A venue that cannot process a request SHOULD return an error rather than silently dropping it. ## Security Considerations - Venues are the enforcement point for all access control: every request MUST be authenticated and authorised before any resource is touched ([COG-3](./COG-003), [COG-13](./COG-013)). - Venues SHOULD apply admission control (rate limiting, resource constraints) to protect availability; rejecting a request before job creation is preferable to failing it afterwards. - Asset IDs are content-addressed and convey no information about their content, but possession of an ID does not imply access — venues MUST apply access control to metadata and content reads alike. - Clients SHOULD verify content-addressed data against its ID rather than trusting the serving venue ([COG-5](./COG-005), [COG-6](./COG-006)). ## Related Specifications - **[COG-2: Decentralised ID](./COG-002)** - Venue, asset, and user identification - **[COG-3: Authentication Mechanisms](./COG-003)** - How callers authenticate - **[COG-5: Asset Metadata](./COG-005)** - Asset IDs and metadata - **[COG-6: Artifacts](./COG-006)** - Immutable content and verification - **[COG-7: Operations](./COG-007)** - Executable assets and invocation - **[COG-8: Jobs](./COG-008)** - The job lifecycle behind every invocation - **[COG-9: Agent Messaging](./COG-009)** - Messaging and protocol bridging - **[COG-13: Agent Capabilities](./COG-013)** - Capability authorisation - **[COG-15: Cross-Venue Federation](./COG-015)** - Relayed requests between venues --- ## COG-2: Decentralised ID ``` Status: Draft Version: 1.0 Created: 2025-09-23 Authors: Mike Anderson ``` This standard specifies the methods for identifying venues and other resources on the grid ## Purpose The Grid enables secure and efficient collaboration between AI agents, data providers, and compute resources across organisational boundaries. In order to operate effectively as a protocol, it is necessary to be able to identify venues and resources with stable global IDs. ## Specification ### Venue DID A venue MUST provide a W3C compatible did, e.g. `did:web:venue-3.covia.ai` The DID must utilise one of the following methods: - `did:web` for venues identified by a public URL with HTTPS - `did:key` for other venues - `did:convex` for venues registered on the Convex network A venue MAY provide multiple equivalent DIDs which resolve to the same venue. If it does so, it MUST provide a `canonicalID` which specifies its primary DID, and return this as the `id` property for its DID document. ### Asset DID Assets MUST have a DID constructed where: - The path is `/a/{asset-id}` - `{asset-id}` is the Asset ID (the Value ID of the asset metadata — see [COG-5](./COG-005)), in hexadecimal Example: `did:web:venue-3.covia.ai/a/14a3a6b804629835f5466d03f789dd92a45381474d58c1224dfe344e6934c022` The hexadecimal asset ID MAY be prepended by `0x` and/or contain a mix of uppercase and lowercase characters. The canonical ID of an asset MUST exclude `0x` and be in lowercase. Venues SHOULD be capable of providing a DID document for any asset they host, subject to authorisation. ### User DIDs A venue MAY assign **venue-managed user DIDs** to the named users it manages (e.g. after OAuth login — see [COG-10](./COG-010)). These are standard `did:web` DIDs formed from the venue's own `did:web` identity with a `:u:` path segment: ``` did:web:{venue-host}:u:{username} ``` Example: `did:web:venue-3.covia.ai:u:alice` - `{username}` MUST match `[A-Za-z0-9._-]+`. - No new DID method is introduced: the colon-separated segments are the `did:web` method's path encoding, so the DID resolves per the did:web specification to `https://{venue-host}/u/{username}/did.json`. The venue serves this DID document and is the **controller** of the DID — it signs assertions about the user with its own key pair. - A public hostname is required; a venue without one cannot mint resolvable user DIDs. Note the distinction between principals and resources: **principals** (venues, users) have DIDs of their own, while **assets** are resources addressed by a DID URL path (`/a/{asset-id}`) under their hosting venue's DID. --- ## COG-3: Authentication Mechanisms ``` Status: Draft Version: 2.0 Created: 2025-09-24 Updated: 2026-01-28 Authors: Mike Anderson ``` :::note[Related specifications] This document specifies the **catalogue of authentication mechanisms** a venue may offer (public access, bearer tokens, HTTP Basic, OAuth 2.0, Ed25519 self-issued JWTs, UCAN identity tokens for relayed requests) and how clients use them. For how a specific venue *implements* identity verification and access control — the middleware verification order, venue-signed tokens, and OAuth provider configuration — see [COG-10: Venue Authentication & Access Control](./COG-010). For capability-based authorisation (what an authenticated caller may do), see [COG-13: Agent Capabilities](./COG-013). ::: ## Purpose Authentication ensures the integrity, security, and controlled access to assets and operations on the grid. This document specifies authentication mechanisms for venues, enabling secure and flexible access control while maintaining venue operators' autonomy over access policies. Authentication is managed at a per-venue level, allowing venue operators to define and enforce access policies tailored to their specific requirements. Authentication mechanisms are built on commonly available web standards and, where appropriate, make use of decentralised facilities supported by Convex lattice technology. ## Principles - **Venue autonomy** — Each venue operator decides which authentication mechanisms to support and what privileges to grant. - **Layered security** — Multiple mechanisms MAY be supported simultaneously, allowing clients to use whichever is appropriate. - **Standards-based** — All mechanisms use established web and cryptographic standards. - **Decentralisation-ready** — Ed25519 key-based authentication ties into the broader Convex identity model without requiring centralised credential stores. ## Specification ### 1. Public Access (No Authentication) Venues MAY provide open, public access to grid operations and assets, enabling public services or sharing open-source capabilities without requiring prior authorisation. Public access is the simplest mode and is appropriate for open data, demo services, and community resources. Venues SHOULD NOT allow unauthorised users to consume excessive resources. Venues SHOULD implement rate limiting on public endpoints, e.g. via a reverse proxy or application-level throttling. ### 2. Bearer Token (API Key) Venues MAY offer authorisation using bearer tokens (commonly referred to as API keys). The token MUST be passed by the client with every HTTPS request using the standard `Authorization` header: ``` Authorization: Bearer ``` If the venue offers bearer-token authorisation, it has complete control over what privileges it grants to clients using any particular token. Token provisioning is out of scope for this specification and is managed by the venue operator (e.g. via a management portal, CLI, or out-of-band exchange). #### Security considerations - Tokens MUST be treated as secrets and MUST NOT be logged, committed to version control, or transmitted over unencrypted channels. - Venues SHOULD support token revocation. - Venues SHOULD enforce token expiry where appropriate. ### 3. HTTP Basic Authentication Venues MAY support HTTP Basic authentication as defined in [RFC 7617](https://www.rfc-editor.org/rfc/rfc7617). Credentials are transmitted as a Base64-encoded `username:password` pair in the `Authorization` header: ``` Authorization: Basic ``` Basic authentication MUST only be used over HTTPS to protect credentials in transit. This mechanism is suitable for simple setups, internal tooling, or development environments where a lightweight credential model is sufficient. ### 4. OAuth 2.0 Venues MAY allow authentication using OAuth 2.0 with PKCE ([RFC 7636](https://www.rfc-editor.org/rfc/rfc7636)). OAuth enables delegated authorisation, allowing users to authenticate via an external identity provider (e.g. Google, GitHub, or a corporate IdP) without sharing their credentials with the venue. #### Flow overview 1. Client initiates an authorisation request to the venue's OAuth endpoint. 2. Venue redirects the client to the identity provider for authentication. 3. On successful authentication, the identity provider returns an authorisation code to the venue callback. 4. The venue exchanges the code for an access token. 5. The client uses the access token as a bearer token for subsequent API requests. #### Requirements - Venues implementing OAuth MUST use PKCE to mitigate authorisation code interception attacks. - Access tokens SHOULD be short-lived; refresh tokens MAY be issued for long-lived sessions. - Venues MUST validate tokens on every request. ### 5. Ed25519 Self-Issued JWT Venues MAY authenticate clients using self-issued JWTs signed with Ed25519 keys. This mechanism aligns with the Convex identity model, where accounts are identified by Ed25519 public keys, and with the W3C `did:key` method for self-sovereign identity. The token names a **principal** in the `sub` claim and a **signing key** in the `kid` header. The venue verifies the signature with the `kid` key, then checks that the key is **authorised for the principal**. In the common self-certifying case the principal is a `did:key` and the authorised key is exactly the key embedded in it — no pre-registration is required, and any entity with an Ed25519 key pair can authenticate. A venue MAY additionally accept subjects it manages itself (named-user DIDs), for which the signing key must be a registered authentication key — see [Subjects and authorised keys](#subjects-and-authorised-keys). #### Client identity: `did:key` The client's DID is computed from the Ed25519 public key using the `did:key` method: 1. Take the 32-byte Ed25519 public key 2. Prepend the multicodec prefix `0xed01` (Ed25519 public key) 3. Encode with multibase base58btc (prefix `z`) 4. Result: `did:key:z6Mk...` This DID is self-certifying — the public key is embedded in the identifier itself, requiring no external resolution. #### Subjects and authorised keys The `sub` claim names the principal being authenticated. Two subject forms are supported: - **Self-certifying (`did:key`)** — the DID embeds the public key. The signing key is authorised if and only if it is the key encoded in `sub`. Works at any venue, with no registration. - **Venue-managed subject** — the DID of a named user managed by the receiving venue. The signing key is authorised if it is one of the subject's **active registered authentication keys**. Such a subject can authenticate only at the venue that manages it: venues MUST NOT resolve foreign subjects (e.g. another venue's users) through this mechanism — cross-venue caller identity uses the UCAN identity token (§6). Where a venue maintains an authentication-key registry, the following invariants apply: - A subject MAY have multiple active keys (e.g. one per device); keys MAY be added and revoked over time. - A key MUST be bound to at most one subject per venue — otherwise a single key could authenticate as two principals. - A revoked key MUST NOT be silently reactivated; re-authorising a key requires an explicit new binding. #### JWT structure The client constructs a JWT with the following structure: **Header:** ```json { "alg": "EdDSA", "typ": "JWT", "kid": "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" } ``` **Payload:** ```json { "iss": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", "sub": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", "aud": "did:web:venue.example.com", "iat": 1706367600, "exp": 1706367900 } ``` | Claim | Required | Description | | ----- | -------- | ----------- | | `sub` | REQUIRED | The principal: a `did:key`, or a venue-managed subject DID for which the signing key is registered | | `iss` | RECOMMENDED | The principal's DID — MUST equal `sub` when present (a token naming a different issuer is not self-issued and MUST be rejected by this mechanism); REQUIRED for venue-managed subjects | | `aud` | RECOMMENDED | Venue DID or URL — prevents replay at a different venue | | `iat` | REQUIRED | Issued-at timestamp (Unix seconds) | | `exp` | REQUIRED | Expiry timestamp (Unix seconds) | | `jti` | OPTIONAL | Unique token ID for replay detection | The `kid` header identifies the signing key. Its canonical form is the key's bare [Multikey](https://www.w3.org/TR/controller-document/#multikey) string (`z6Mk...`); verifiers SHOULD also accept the `did:key:` and `#` forms (in the latter, the prefix MUST equal the token's `sub`). The `kid` key MUST be authorised for the `sub` principal as defined in [Subjects and authorised keys](#subjects-and-authorised-keys). Note that `kid` identifies the *key*, not the principal — for a venue-managed subject it names a registered key that is distinct from the DID in `sub`. #### Transport The JWT is sent using the standard `Authorization: Bearer` header: ``` Authorization: Bearer eyJhbGciOiJFZERTQSIs... ``` This reuses the same header as bearer tokens and API keys. The venue distinguishes a self-issued JWT from an opaque token by decoding the JWT and checking for `alg: EdDSA` with a `kid` that decodes to an Ed25519 Multikey. #### Flow overview ``` Client Venue │ │ │ 1. Generate Ed25519 key pair (once) │ │ did:key:z6Mk... = encode(pubkey) │ │ │ │ 2. Create JWT: │ │ iss: did:key:z6Mk... │ │ aud: did:web:venue.example.com │ │ exp: │ │ Sign with Ed25519 private key │ │ │ │ Authorization: Bearer │ │ POST /api/v1/invoke │ │ ───────────────────────────────────────────▶ │ │ │ │ 3. Decode JWT, resolve signing key from kid │ 4. Verify JWT signature with that key │ 5. Check key is authorised for sub │ 6. Validate iss = sub, exp, aud, iat │ 7. (Optional) Resolve Convex account │ │ │ 200 OK │ │ ◀─────────────────────────────────────────── │ ``` #### Venue verification Venues MUST perform the following validation steps: 1. Decode the JWT and verify `alg` is `EdDSA` 2. Resolve the signing key from the `kid` header (bare Multikey, `did:key:`, or `#` with the prefix equal to `sub`) 3. Verify the JWT signature using that key 4. Verify the key is authorised for the `sub` principal — for a `did:key` subject it MUST equal the key encoded in `sub`; for a venue-managed subject it MUST be an active registered authentication key of that subject 5. If `iss` is present, validate that it equals `sub` (REQUIRED for venue-managed subjects) 6. Validate `exp > now` (reject expired tokens) 7. Validate `iat <= now + clock_skew` (reject tokens issued in the future) 8. If `aud` is present, validate it matches the venue's own DID or URL; venues MAY require `aud` (audience policy) 9. Validate `exp - iat <= max_lifetime` (reject tokens with excessively long validity) Recommended validation parameters: | Parameter | Recommended value | | --------- | ----------------- | | Clock skew tolerance | 30 seconds | | Maximum token lifetime (`exp - iat`) | 5 minutes | #### Token lifetime Tokens SHOULD be short-lived (recommended 5 minutes; the reference SDK's default is 300 seconds). Since the client holds the private key, generating a new JWT is effectively free (Ed25519 signing takes microseconds). Refresh tokens are unnecessary — when a token expires, the client simply signs a new one. #### Optional Convex account resolution After verifying the JWT signature, a venue MAY look up the Ed25519 public key on the Convex network to resolve a Convex account: 1. Extract the 32-byte Ed25519 public key from the `did:key` 2. Query the Convex network for accounts whose controller key matches 3. If found — associate the request with the Convex account (e.g. `#1337`) 4. If not found — the client is authenticated by `did:key` alone This resolution is a venue-side decision. The client does not need to be aware of it. A venue not connected to the Convex network simply authenticates by `did:key` without account resolution. #### Security considerations - Private keys MUST NOT leave the client and MUST NOT be transmitted over the network. - The `exp` and `aud` claims provide replay protection without server-side state. - Venues SHOULD reject tokens where `exp - iat` exceeds the maximum lifetime, preventing clients from creating excessively long-lived tokens. - For high-security environments, venues MAY track `jti` values within the token's validity window to detect replay of the exact same token. - This mechanism integrates naturally with Convex account keys, enabling on-chain identity verification. ### 6. UCAN Identity Token (Relayed Requests) Venues MAY accept a caller identity from the request's `ucans` proof channel when the transport itself is unauthenticated. This is the identity mechanism for **relayed cross-venue requests**: an intermediary venue forwards the caller's tokens, and the receiving venue verifies the caller's own signature — placing no trust in the relay. #### The proof channel UCAN tokens accompany a request in the **proof channel**, separate from operation input — input is data and is persisted into job records ([COG-8](./COG-008)), so a credential there would leak into durable history. The channel has three carriers, all verified identically at ingress: | Carrier | Form | Use | | ------- | ---- | --- | | Request body `ucans` field | JSON array of UCAN JWT strings | Requests with a body (e.g. `POST /api/v1/invoke`) | | `X-Covia-Ucans` header | The same JWT strings, comma-separated | Body-less requests (e.g. job observation `GET`s) | | `Authorization: Bearer` | A single UCAN JWT | When the bearer slot is not carrying another token type | Encoding rules for the header: - Entries are separated by commas; whitespace around entries MUST be tolerated (each entry is trimmed) and empty entries ignored. A JWT cannot contain a comma, so the separation is unambiguous. - Senders SHOULD emit a single header line. (An intermediary that combines repeated lines with commas per HTTP field semantics yields a parseable value, but receivers read one line.) - Ordering carries no meaning: each token is self-contained — delegation chains travel nested in the token's `prf` field, so a single leaf JWT is a complete chain. - Tokens that fail verification are discarded at ingress; a discarded token never fails the request by itself (the absence of required authority does). - Venues MAY bound the number and size of tokens they accept. Senders attach tokens **explicitly from their own proof state** — nothing relies on passive header propagation across hops ([COG-15](./COG-015)). #### Identity tokens An **identity token** is a UCAN with: | Field | Value | | ----- | ----- | | `iss` | The caller's DID (signs the token) | | `aud` | The **receiving venue's** DID | | `att` | **Empty** — the token grants nothing; it is pure proof of identity | | `exp` | Short-lived expiry | The empty attenuation list makes the token unambiguous: it cannot be dual-used as a grant. The audience binding makes it unusable at any other venue. Verification rules: 1. The token MUST pass standard UCAN verification (signature, temporal bounds). 2. The token's `aud` MUST equal the receiving venue's DID. 3. The token's `att` MUST be empty. 4. The mechanism applies **only when the transport carries no `Authorization` identity** — an authenticated transport peer (e.g. a relaying venue acting as itself) is the caller regardless of forwarded tokens. 5. Two identity tokens with different issuers are ambiguous: the request MUST be rejected. On success the caller is the token's `iss`, with the same standing as any authenticated identity (capability proofs audienced to it apply; the public-access ceiling does not). ## Client SDK Support The Covia SDKs provide a generic authentication interface that supports all mechanisms described above. | Mechanism | Python SDK | Java SDK | |-----------|-----------|----------| | Public (no auth) | Default | Default | | Bearer token | `BearerAuth(token)` | Supported | | HTTP Basic | `BasicAuth(user, pass)` | Supported | | OAuth 2.0 | Planned | Supported | | Ed25519 JWT | `Ed25519Auth` | Supported | ### Python SDK example ```python from covia import Grid from covia.auth import BearerAuth, BasicAuth # Public access (no auth) venue = Grid.connect("https://venue.covia.ai") # Bearer token venue = Grid.connect("https://venue.covia.ai", auth=BearerAuth("my-token")) # HTTP Basic venue = Grid.connect("https://venue.covia.ai", auth=BasicAuth("user", "pass")) ``` ```python # Ed25519 self-issued JWT (requires: pip install covia[signing]) from covia.auth import Ed25519Auth # Generate a new identity — audience is auto-set from Grid.connect() auth = Ed25519Auth.generate() print(auth.did) # did:key:z6Mk... venue = Grid.connect("did:web:venue.covia.ai", auth=auth) # Or from a known seed (deterministic key) auth = Ed25519Auth.from_seed(seed_bytes) venue = Grid.connect("did:web:venue.covia.ai", auth=auth) ``` Custom authentication providers can be implemented by subclassing `covia.auth.Auth`: ```python from covia.auth import Auth class ApiKeyAuth(Auth): def __init__(self, key: str) -> None: self._key = key def apply(self, headers: dict[str, str]) -> None: headers["X-Api-Key"] = self._key ``` ## Venue Discovery Venues SHOULD advertise their supported authentication mechanisms via the A2A agent card (`/.well-known/agent-card.json`) using the `securitySchemes` field. This allows clients to discover the required authentication method before making API calls. ## References - [RFC 7519 — JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519) - [RFC 8037 — EdDSA for JOSE (EdDSA algorithm for JWTs)](https://www.rfc-editor.org/rfc/rfc8037) - [RFC 8032 — Ed25519 and Ed448](https://www.rfc-editor.org/rfc/rfc8032) - [RFC 7617 — HTTP Basic Authentication](https://www.rfc-editor.org/rfc/rfc7617) - [RFC 7636 — PKCE for OAuth](https://www.rfc-editor.org/rfc/rfc7636) - [RFC 6750 — OAuth Bearer Token Usage](https://www.rfc-editor.org/rfc/rfc6750) - [W3C DID Core](https://www.w3.org/TR/did-core/) - [W3C did:key Method](https://w3c-ccg.github.io/did-method-key/) - [Multicodec — Ed25519 Public Key (0xed)](https://github.com/multiformats/multicodec) --- ## COG-4: Grid Lattice ``` Status: Draft (Work in Progress) Version: 0.1 Created: 2025-01-23 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: This standard specifies the lattice-based state management system for the Covia Grid, enabling conflict-free replication and synchronisation of venue state across the distributed network. ## Purpose The Grid requires a robust mechanism for managing distributed state across autonomous venues that may operate independently, experience network partitions, and need to reconcile state after periods of disconnection. Traditional database synchronisation approaches are inadequate for this use case because they typically require: - Central coordination or consensus mechanisms - Strict ordering of operations - Complex conflict resolution logic The Grid Lattice provides an alternative approach based on **Conflict-free Replicated Data Types (CRDTs)**, which guarantee eventual consistency without coordination. This enables venues to: - Operate autonomously during network partitions - Merge state from multiple sources without conflicts - Maintain cryptographic integrity of shared data - Support efficient peer-to-peer state synchronisation ## Key Benefits ### Eventual Consistency Without Coordination Lattice-based state management ensures that all venues converge to the same state regardless of the order in which updates are received. This is achieved through mathematically-defined merge operations that satisfy the CRDT properties: - **Commutative**: `merge(a, b) = merge(b, a)` - **Associative**: `merge(merge(a, b), c) = merge(a, merge(b, c))` - **Idempotent**: `merge(a, a) = a` These properties guarantee that venues can exchange state in any order, through any topology, and arrive at consistent results. ### Content-Addressable Metadata Asset metadata on the Grid is content-addressable, meaning the identifier (Asset ID) is derived from the Value ID of the metadata (see [COG-5: Asset Metadata](./COG-005)). This design ensures: - **Integrity verification**: Any modification to metadata changes its hash, making tampering detectable - **Deduplication**: Identical metadata across venues shares the same identifier - **Safe sharing**: Content-addressed data can be freely replicated without risk of conflicts ### Hierarchical State Organisation The lattice is organised hierarchically, allowing different merge semantics at each level: - Grid-level state manages the collection of venues - Venue-level state manages assets and jobs independently - Asset and job entries use appropriate merge strategies for their data types ### Autonomous Venue Operation Each venue maintains sovereignty over its own state while participating in the broader Grid. The lattice structure ensures that: - Venues can operate during network partitions - State from multiple venues can be merged without data loss - No central authority is required for state reconciliation ## Specification ### Lattice Structure The Grid Lattice follows a hierarchical structure rooted at the `:grid` keyword: ``` :grid ; GridLattice :venues ; Index ; VenueLattice :assets ; Index :jobs ; Index (HEAD pointers — see COG-8) :storage ; Index (content-addressed blob storage) :user-data ; Index (per-user namespaces) :meta ; Index ``` Note: The `:venues` collection uses `Index` rather than `Map` for efficient sorted access with blob-like DID string keys. ### Root Lattice The root lattice (`Covia.ROOT`) is a `KeyedLattice` that maps the `:grid` keyword to the `GridLattice` instance. Implementations MUST use the `:grid` keyword as the root key for Grid state. ### Grid Lattice The Grid Lattice manages global state with two primary components: #### Venues Index (`:venues`) The `:venues` field contains a sorted index from venue DID strings to venue state: - **Type**: `Index` (sorted by DID string) - **Key**: Venue DID as a string (e.g., `"did:web:venue-3.covia.ai"`) - **Value**: Venue state managed by `VenueLattice` - **Merge semantics**: Per-venue merge using `VenueLattice` rules The use of `Index` rather than `Map` provides efficient sorted access and better performance for blob-like DID string keys. Implementations MUST apply `VenueLattice` merge semantics when merging venue entries with the same DID. #### Shared Metadata (`:meta`) The `:meta` field contains content-addressable metadata shared across venues: - **Key**: Value ID of the metadata (SHA3-256 hash of canonical CAD003 encoding — see [COG-5](./COG-005)) - **Value**: Metadata as a JSON string - **Merge semantics**: Union merge (content-addressed, same hash = same content) Implementations MUST use Value IDs (as defined in [COG-5](./COG-005)) as metadata keys. Implementations SHOULD NOT modify existing metadata entries (they are immutable by design). ### Venue Lattice Each venue's state is managed by a `VenueLattice` with the following structure: #### Assets (`:assets`) The `:assets` field contains the venue's asset registry: - **Key**: Asset ID (Value ID of asset metadata — see [COG-5](./COG-005)) - **Value**: Asset record (implementation-defined structure) - **Merge semantics**: Union merge (content-addressed assets are immutable) Implementations MUST use union merge for assets, accepting all entries from both states. #### Jobs (`:jobs`) The `:jobs` field tracks job execution state. Per [COG-8](./COG-008), a job's state is a chain of **immutable, content-addressed state records**; the only mutable element is a HEAD pointer from the Job ID to the latest record. - **Key**: Job ID (unique identifier) - **Value**: HEAD pointer to the job's latest state record (the records themselves are content-addressed values) - **Merge semantics**: Union merge for the state records (immutable and content-addressed, so nothing can conflict); last-writer-wins on the `updated` timestamp for the HEAD pointer only Jobs have a **single writer**: a job is owned by a user, the user is managed by a venue, and only that venue appends to the job's state chain. Other venues hold read-only replicas sourced from the owner. Divergent versions of the same job therefore cannot arise in normal operation — only through exceptional events such as restoring the owning venue from a backup after later state had already replicated, or a split-brain deployment. Union merge makes such repair lossless (records from both histories are retained), and the HEAD rule prefers the newer record; implementations SHOULD surface that a divergence was repaired rather than resolving it silently. #### Storage (`:storage`) The `:storage` field provides content-addressable blob storage for the venue: - **Type**: `Index` - **Key**: SHA256 hash of the content - **Value**: Binary blob data - **Merge semantics**: Union merge (content-addressed, same hash = same content) This storage is used for asset content, attachments, and other binary data associated with the venue. The content-addressed design ensures: - **Deduplication**: Identical content is stored only once - **Integrity**: Content can be verified against its hash - **Safe replication**: Content can be freely shared without conflicts Implementations MAY use alternative storage backends (file system, DLFS, etc.) while maintaining the content-addressed semantics in the lattice. ### Per-User Namespace Each venue maintains per-user state within the venue lattice at `:user-data → `. The per-user namespace is a `StringKeyedLattice` with single-letter keys: ``` :user-data ; MapLattice (DID → per-user state) ; StringKeyedLattice "j" → IndexLattice + LWW ; user's job references "g" → MapLattice + LWW ; user's agents (per-agent atomic LWW record) "s" → MapLattice + LWW ; user's encrypted credentials "w" → MapLattice + REPLACE ; user's workspace (opaque JSON/CAD3 data) "o" → MapLattice + REPLACE ; user's operations (opaque JSON/CAD3 definitions) "h" → MapLattice + REPLACE ; HITL requests (reserved — Phase D) ``` #### Namespace semantics | Key | Name | Merge | Description | |-----|------|-------|-------------| | `g` | Agents | LWW (per-agent `ts` field) | Agent records — see [COG-11](./COG-011) | | `s` | Secrets | LWW (`updated` field) | Encrypted per-user credentials | | `j` | Jobs | LWW (`updated` field) | Per-user job references | | `w` | Workspace | REPLACE (last writer wins) | General-purpose user data. Agents read and write here via `covia:read`/`covia:write`/`covia:delete`/`covia:append`. Values are opaque JSON/CAD3 structures. | | `o` | Operations | REPLACE (last writer wins) | User-defined operation definitions. Same semantics as workspace. | | `h` | HITL | REPLACE (last writer wins) | Human-in-the-loop requests. Reserved for Phase D. | The `w` and `o` namespaces use REPLACE merge (always takes the incoming value) because their contents are opaque structures without timestamp fields. This is adequate for single-venue operation; cross-venue sync will require application-level conflict resolution. Agents and secrets use timestamp-based LWW merge, where the record with the later timestamp wins unconditionally. This is correct because all writes to a given user's namespace are serialised on the hosting venue. #### Path navigation The `covia:read` operation supports path-based navigation into any namespace. Paths use `/` separators (e.g. `g/my-agent/state`, `w/data/nested/field`). Navigation is type-aware: maps resolve string keys, vectors resolve integer indices (e.g. `w/events/0`). The `covia:write`, `covia:delete`, and `covia:append` operations are restricted to the `w` and `o` namespaces. All other namespaces are framework-managed. ### Venue Identification Venues on the Grid are identified using Decentralised Identifiers (DIDs) as specified in [COG-2](./COG-002). A venue's DID serves as its unique key in the `:venues` map. This design ensures: - **Global uniqueness**: DIDs provide globally unique identification - **Self-sovereignty**: Venues control their own identity - **Verifiability**: DID resolution enables verification of venue authenticity Implementations MUST use the venue's canonical DID string as the key in the `:venues` map. A venue MAY have multiple equivalent DIDs, but MUST consistently use its canonical DID as the lattice key. ### Merge Operations #### Null Handling The following rules apply when merging with null values: - `merge(value, null) = value` - `merge(null, value) = value` - `merge(null, null) = null` #### Identity Element The zero (identity) element for each lattice level: - **Grid Lattice**: `{:venues (empty-index), :meta (empty-index)}` - **Venue Lattice**: `{:assets {}, :jobs {}, :storage (empty-index)}` Implementations MUST ensure that `merge(value, zero) = value` for all valid states. ### State Synchronisation Venues MAY synchronise state using any transport mechanism that preserves the integrity of lattice values. When synchronising, venues SHOULD: 1. Exchange lattice state or deltas 2. Apply the merge operation to combine local and remote state 3. Persist the merged result Implementations MUST NOT require strict ordering of synchronisation messages. Implementations SHOULD support incremental synchronisation to minimise bandwidth. ## Security Considerations ### Metadata Integrity Content-addressable metadata provides built-in integrity verification. Implementations SHOULD verify that metadata hashes match their content before accepting updates. ### Venue Authority Only the venue identified by a DID should be authoritative for its state under that DID in the `:venues` map. Implementations SHOULD implement mechanisms to prevent unauthorised modification of venue state. ### Timestamp Manipulation Job state records merge by union, so timestamps are not load-bearing for job data — a skewed or malicious clock cannot cause state records to be lost. The `updated` timestamp matters only when repairing divergent HEAD pointers (e.g. after a restore from backup), and because each job has a single authoritative writer, no third party can inject competing records. Venues SHOULD nevertheless maintain accurate clocks so that HEAD repair prefers genuinely newer state. ## Reference Implementation The reference implementation is available in the Covia venue module: - `covia.lattice.Covia` - Root lattice definition - `covia.lattice.GridLattice` - Grid-level lattice implementation - `covia.lattice.VenueLattice` - Venue-level lattice implementation - `covia.lattice.CASLattice` - Content-addressable storage lattice - `covia.venue.storage.LatticeStorage` - Storage backed by lattice cursor - `covia.venue.storage.FileStorage` - Storage backed by filesystem (supports DLFS) - `covia.venue.Config` - Configuration constants for storage and venue setup ## Related Specifications - **[COG-1: Architecture](./COG-001)** - Overall Grid architecture - **[COG-2: Decentralised ID](./COG-002)** - Venue and asset identification - **[COG-5: Asset Metadata](./COG-005)** - Asset IDs and the metadata format stored in `:meta` - **[COG-6: Artifacts](./COG-006)** - Immutable data assets stored in the lattice - **[COG-7: Operations](./COG-007)** - Executable assets stored in the lattice - **[COG-8: Jobs](./COG-008)** - The job state-record chain whose HEAD pointers live in `:jobs` - **[Lattice Overview](https://docs.convex.world/docs/overview/lattice)** - Convex Lattice specification --- ## COG-5: Asset Metadata ``` Status: Draft (Work in Progress) Version: 0.2 Created: 2025-01-23 Updated: 2026-02-27 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: This standard specifies the metadata format for assets on the Covia Grid, enabling interoperability, discoverability, and verification of resources across the distributed network. ## Purpose Assets are the fundamental resources of the Covia Grid, representing data, operations, and other computational resources that can be shared and utilised across venues. A well-defined metadata format is essential for: - **Discovery**: Enabling clients to find relevant assets based on descriptive information - **Verification**: Ensuring content integrity through cryptographic hashes - **Interoperability**: Allowing assets to be understood and processed by any Grid participant - **Provenance**: Tracking the origin, authorship, and licensing of resources This specification defines the JSON-based metadata format that describes assets on the Grid. ## Terminology See [COG-1: Architecture](./COG-001) for definitions of Grid terminology including Asset, Artifact, Operation, and Job. ## Specification ### Metadata Format Asset metadata MUST be a valid JSON object. ### Asset ID Computation Asset IDs use **lattice Value IDs** — the SHA3-256 hash of the canonical binary encoding defined by the Convex lattice ([CAD003](https://docs.convex.world)). The computation is: 1. Parse the metadata JSON string into a lattice data structure (an ordered map of string keys to values) 2. The Asset ID is the **Value ID** of that data structure — the SHA3-256 hash of its canonical CAD003 binary encoding 3. Encode the hash as a lowercase hexadecimal string (64 characters) This scheme provides: - **Semantic hashing**: The ID is derived from the data structure's content, not a particular JSON serialisation. Two semantically identical metadata objects with different formatting (key order, whitespace) produce the same ID. - **Native lattice integration**: The lattice automatically computes Value IDs for all stored data, so asset identification is a zero-cost property of storage. - **Structural sharing**: The lattice's Merkle tree structure enables automatic deduplication of shared sub-structures across assets. - **Unified data model**: Assets, job state records, and all lattice data use the same identification scheme. :::note[Client Verification] Clients wishing to verify an Asset ID locally need a CAD003 encoding implementation rather than just a general-purpose hash function. Clients that do not require local verification can rely on a trusted venue (or multiple venues for cross-checking) as a verification endpoint — the venue's `GET /api/v1/assets/{id}` response implicitly confirms the ID-to-content binding. ::: ### Common Fields The following fields are RECOMMENDED for all assets: #### `name` (string) A human-readable name for the asset. ```json { "name": "Iris Dataset" } ``` #### `description` (string) A detailed description of the asset, its purpose, and how it can be used. ```json { "description": "The famous Iris flower dataset for machine learning classification tasks" } ``` #### `creator` (string) The creator or author of the asset. ```json { "creator": "UCI Machine Learning Repository" } ``` #### `dateCreated` (string) The creation date in ISO 8601 format. ```json { "dateCreated": "2025-06-05T06:53:59Z" } ``` #### `dateModified` (string) The last modification date in ISO 8601 format. ```json { "dateModified": "2025-06-05T07:22:59Z" } ``` #### `keywords` (array of strings) Keywords for discovery and categorisation. ```json { "keywords": ["machine learning", "dataset", "classification"] } ``` #### `license` (object) Licensing information for the asset. ```json { "license": { "name": "CC BY 4.0", "url": "https://creativecommons.org/licenses/by/4.0/" } } ``` ### Asset Type Fields Assets are categorised by the presence of specific top-level objects: #### Artifacts Assets with a `content` object represent **Artifacts** - immutable data assets. See [COG-6: Artifacts](./COG-006) for the complete specification including: - Content hash verification - Replication and federation - Content storage requirements #### Operations Assets with an `operation` object represent **Operations** - executable assets. See [COG-7: Operations](./COG-007) for the complete specification including: - Adapter configuration - Input/output schemas - Orchestration workflows ### Additional Information The `additionalInformation` object MAY contain any implementation-specific or domain-specific metadata: ```json { "additionalInformation": { "notes": ["Uploaded for testing purposes"], "sourceUrl": "https://example.com/original" } } ``` ## Validation Implementations SHOULD validate metadata against this specification before accepting assets. Implementations MUST reject metadata that: - Is not valid JSON - Contains content hashes that do not match the actual content (for artifacts) - References non-existent adapters (for operations) ## Security Considerations ### Metadata Immutability Once an asset is created, its metadata string and Asset ID are immutable. Any modification to metadata results in a new Asset ID. Implementations MUST NOT allow modification of existing metadata. See [COG-6: Artifacts](./COG-006) and [COG-7: Operations](./COG-007) for asset-type-specific security considerations. ## Related Specifications - **[COG-1: Architecture](./COG-001)** - Overall Grid architecture - **[COG-2: Decentralised ID](./COG-002)** - Asset identification - **[COG-4: Grid Lattice](./COG-004)** - Asset storage in the lattice - **[COG-6: Artifacts](./COG-006)** - Immutable data assets - **[COG-7: Operations](./COG-007)** - Executable assets --- ## COG-6: Artifacts ``` Status: Draft (Work in Progress) Version: 0.1 Created: 2025-01-23 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: This standard specifies **Artifacts** - immutable data assets on the Covia Grid that can be verified, replicated, and shared across venues with cryptographic guarantees. ## Purpose Artifacts are the data foundation of the Grid, representing immutable content that can be: - **Verified**: Content integrity is guaranteed through cryptographic hashes - **Replicated**: Any venue can host an exact copy with provable authenticity - **Trusted**: Consumers can verify they have the exact data the creator intended Unlike mutable data stores, artifacts provide absolute guarantees about content - if you have an artifact with a given ID, you know with cryptographic certainty that it contains exactly the data specified by the creator. ## Terminology See [COG-1: Architecture](./COG-001) for definitions of Grid terminology. ## Core Principles ### Immutability Artifacts are **permanently immutable**. Once created, an artifact's content and metadata cannot be changed. This immutability is enforced cryptographically: - The **Asset ID** is the Value ID of the metadata (see [COG-5](./COG-005)) - The metadata contains the **Content Hash** (SHA256 of the content) - Any change to content or metadata produces a different Asset ID Immutability enables: - **Caching**: Artifacts can be cached indefinitely without invalidation concerns - **Replication**: Copies are guaranteed identical across venues - **Auditing**: Historical references remain valid forever - **Trust**: Content cannot be tampered with after creation ### Verifiability Every artifact can be independently verified by any party: 1. **Metadata Verification**: Compute the Value ID of the metadata and compare to the Asset ID (see [COG-5](./COG-005)) 2. **Content Verification**: Compute SHA256 of the content bytes and compare to `content.sha256` This verification chain forms a **Merkle structure** — trusting the Asset ID means trusting both the metadata and the content it references. ``` Asset ID (trusted) │ └── Value ID of Metadata │ └── content.sha256 ──► SHA256 of Content Bytes ``` ### Federated Trust Artifacts can be safely replicated across venues because: - **Content-addressing** ensures the same Asset ID always refers to identical data - **Independent verification** means venues don't need to trust each other - **No coordination required** - venues can replicate artifacts without permission from the original source This enables a **trustless federation** model where: - Users can obtain artifacts from any venue hosting them - Verification ensures authenticity regardless of source - Load can be distributed across multiple venues - Offline or unavailable venues don't prevent access to replicated artifacts ## Specification ### Artifact Identification Artifacts are identified following [COG-2: Decentralised ID](./COG-002): ``` did:web:venue.example.com/a/7c41f0a9e8b35d21c6a90477c2ff08d1a3b5e6c88f29d34ab01e57f2469cd803 ``` The path component `/a/{asset-id}` identifies the artifact within the venue. The asset ID is the CAD3 content hash (Value ID) of the artifact's **metadata** — it is not the `content.sha256` carried inside that metadata, and the two values always differ. ### Required Metadata Fields Artifacts MUST include a `content` object in their metadata. See [COG-5: Asset Metadata](./COG-005) for the complete metadata specification. #### `content.sha256` (REQUIRED) The SHA256 hash of the content bytes, encoded as a **lowercase** hexadecimal string. (The metadata's exact bytes determine the Asset ID, so the hex casing MUST be canonical — lowercase, matching [COG-2](./COG-002)'s canonical ID rule.) ```json { "content": { "sha256": "119e30db8a4ea8b33723603743591a5f8229684e6236d89ef1966a72d7293607" } } ``` Implementations MUST verify that content matches this hash before serving or using it. #### `content.contentType` (RECOMMENDED) The MIME type of the content, enabling correct interpretation. ```json { "content": { "contentType": "text/csv" } } ``` ### Content Storage Artifact content is stored separately from metadata. Venues MUST: - Store content addressable by its SHA256 hash - Verify content against the metadata's `content.sha256` before returning it, and never return content that fails verification - Support retrieval of content given an Asset ID Content MAY be: - Stored locally on the venue - Retrieved on-demand from other venues - Cached based on access patterns ### Replication Venues MAY replicate artifacts from other venues. When replicating: 1. Obtain the metadata from the source venue 2. Verify metadata hash matches the Asset ID 3. Obtain the content 4. Verify content hash matches `content.sha256` 5. Store both metadata and content locally Implementations MUST NOT serve artifacts that fail verification. ### Versioning Since artifacts are immutable, versioning is achieved through: - Creating new artifacts with updated content - Using metadata fields to link versions (e.g., `previousVersion`, `replaces`) - Maintaining collections or indices that track version history Each version is a distinct artifact with its own Asset ID. ## Examples ### Dataset Artifact A machine learning dataset with full provenance: ```json { "name": "Iris Dataset", "description": "The classic Iris flower dataset containing 150 samples of iris flowers with 4 features each (sepal length, sepal width, petal length, petal width) and species classification.", "creator": "UCI Machine Learning Repository", "dateCreated": "2025-06-05T06:53:59Z", "license": { "name": "CC BY 4.0", "url": "https://creativecommons.org/licenses/by/4.0/" }, "keywords": ["machine learning", "iris", "dataset", "classification"], "content": { "contentType": "text/csv", "sha256": "119e30db8a4ea8b33723603743591a5f8229684e6236d89ef1966a72d7293607", "encoding": "UTF-8", "inLanguage": "en" }, "additionalInformation": { "rows": 150, "columns": 5, "source": "https://archive.ics.uci.edu/ml/datasets/iris" } } ``` ### Document Artifact A text document with licensing information: ```json { "name": "Hamlet", "description": "The complete text of Shakespeare's tragedy Hamlet, Prince of Denmark.", "creator": "William Shakespeare", "dateCreated": "2025-06-05T06:53:59Z", "license": { "name": "Public Domain" }, "keywords": ["text", "drama", "shakespeare", "classic"], "content": { "contentType": "text/plain", "sha256": "74f16013e2b7ce83d5f5c8d4b3c42f279242f6ddfa7bab0f31320301e60c81d6", "encoding": "UTF-8", "inLanguage": "en-GB" } } ``` ### Binary Artifact A compiled model or binary file: ```json { "name": "Sentiment Analysis Model v2.1", "description": "Pre-trained transformer model for sentiment analysis, fine-tuned on product reviews.", "creator": "ML Research Team", "dateCreated": "2025-07-15T14:30:00Z", "keywords": ["model", "nlp", "sentiment", "transformer"], "content": { "contentType": "application/octet-stream", "sha256": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456" }, "additionalInformation": { "framework": "pytorch", "version": "2.1", "accuracy": 0.94, "previousVersion": "did:web:models.example.com/a/3d92c6b17e40aa85f1c2d90b46e87f31905ac44de2b16873c5f90d21ae64b7f2" } } ``` ## Security Considerations ### Hash Algorithm Two distinct hashes are in play, and they use different algorithms: - **Artifact content** is hashed with SHA256 (`content.sha256`). - **Asset identity** is the CAD3 content hash of the metadata — the SHA3-256 Value ID of its canonical CAD003 encoding (see [COG-5](./COG-005)). Future specifications may add support for additional content-hash algorithms with explicit algorithm identifiers. ### Content Injection Implementations MUST verify content hashes before processing. Malicious venues could attempt to serve incorrect content - verification prevents this attack. ### Metadata Authenticity The Asset ID verifies metadata integrity but not authenticity. To verify the creator's identity, additional mechanisms such as digital signatures or DID-based attestations may be used. ## Related Specifications - **[COG-1: Architecture](./COG-001)** - Overall Grid architecture and terminology - **[COG-4: Grid Lattice](./COG-004)** - The content-addressed `:storage` index holding artifact content - **[COG-2: Decentralised ID](./COG-002)** - Asset identification - **[COG-5: Asset Metadata](./COG-005)** - Common metadata format - **[COG-7: Operations](./COG-007)** - Executable assets (contrast with artifacts) --- ## COG-7: Operations ``` Status: Draft (Work in Progress) Version: 0.3 Created: 2025-01-23 Updated: 2026-07-16 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: This standard specifies **Operations** - executable assets on the Covia Grid that provide compute capabilities with defined interfaces, enabling trusted and composable computation across venues. ## Purpose Operations are the compute foundation of the Grid, representing executable functions that: - **Define Interfaces**: Explicit input/output schemas enable interoperability - **Enable Composition**: Operations can be chained into workflows - **Support Federation**: The same operation can be executed by multiple venues - **Provide Verifiability**: Operation definitions are immutable and content-addressable Operations transform the Grid from a data repository into a distributed compute platform where clients can invoke capabilities without managing infrastructure. ## Terminology See [COG-1: Architecture](./COG-001) for definitions of Grid terminology including Operation, Adapter, Job, and Orchestration. See [COG-8: Jobs](./COG-008) for the full job lifecycle specification. ## Core Principles ### Immutable Definitions Like all assets, operation metadata is **immutable** and **content-addressable**: - The Asset ID is the Value ID of the operation's metadata (see [COG-5: Asset Metadata](./COG-005)) - The same Asset ID always refers to the same operation definition - Changes to an operation create a new asset with a new ID This immutability ensures: - **Reproducibility**: Invoking an operation by Asset ID guarantees consistent behaviour - **Auditability**: Job records can reference the exact operation definition used - **Safe Caching**: Operation definitions can be cached indefinitely ### Verifiable Interfaces Operations declare their interfaces through JSON Schema: - **Input Schema**: Defines expected parameters and their types - **Output Schema**: Defines the structure of results These schemas enable: - **Validation**: Inputs can be validated before execution - **Documentation**: Interfaces are self-describing - **Tooling**: IDEs and clients can provide assistance based on schemas - **Composition**: Orchestrations can verify compatibility between steps ### Federated Execution Operations support execution across multiple venues: - **Portable Definitions**: Operation metadata can be replicated to any venue - **Adapter-Based Execution**: Venues implement adapters that execute operations - **Consistent Results**: Same inputs produce same outputs regardless of venue This enables: - **Load Distribution**: Clients can choose from multiple venues - **Resilience**: Operations remain available if some venues are down - **Geographic Optimisation**: Execute operations close to data or users ### Trust Model Trust in operations comes from multiple sources: 1. **Definition Trust**: The Asset ID guarantees you're invoking the intended operation 2. **Venue Trust**: You trust the venue to execute the operation correctly 3. **Result Verification**: For deterministic operations, results can be verified Different use cases require different trust levels - a simple HTTP fetch needs less trust than financial computation. ## Specification ### Operation Identification Operations are identified following [COG-2: Decentralised ID](./COG-002): ``` did:web:venue.example.com/a/7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b ``` ### Required Metadata Fields Operations MUST include an `operation` object in their metadata. See [COG-5: Asset Metadata](./COG-005) for the complete metadata specification. #### `operation.adapter` (REQUIRED) Identifies the adapter responsible for executing this operation: ```json { "operation": { "adapter": "http" } } ``` The adapter string is routed on its **first colon**: the segment before it selects the adapter, and everything after it is an adapter-defined suffix that MAY itself contain further colons (e.g. `http`, `langchain:openai`, `mcp:tools:call`). Adapters define their own suffix conventions, and MAY likewise define additional adapter-specific fields inside the `operation` object (such as the `mcp` adapter's `remoteToolName` and `server` below). Standard adapters include: | Adapter | Description | | ------- | ----------- | | `http` | HTTP request operations | | `jvm` | JVM-based local operations | | `orchestrator` | Multi-step workflow execution | | `mcp` | Model Context Protocol integration | | `langchain` | LLM inference (OpenAI, Ollama, Gemini, DeepSeek, etc.) | | `convex` | Convex blockchain queries and transactions | | `grid` | Grid meta-operations (invoke, status, federation) | | `agent` | Stateful agent lifecycle (create, message, run loop) | | `llmagent` | LLM-backed agent transitions (conversation history, multi-turn) | | `test` | Echo, delay, error simulation for development and testing | | `covia` | Internal venue operations | :::note The `agent` and `llmagent` adapters support the agent lifecycle model described in [COG-11: Agent Lifecycle](./COG-011). These adapters are under active development and their operation interfaces may evolve. ::: #### `operation.input` (RECOMMENDED) JSON Schema defining expected input parameters: ```json { "operation": { "input": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query" }, "limit": { "type": "integer", "description": "Maximum results", "default": 10 } }, "required": ["query"] } } } ``` #### `operation.output` (RECOMMENDED) JSON Schema defining the result structure: ```json { "operation": { "output": { "type": "object", "properties": { "results": { "type": "array", "description": "Search results" }, "total": { "type": "integer", "description": "Total matches found" } } } } } ``` #### `operation.default` (OPTIONAL) A map of argument values merged **under** the caller's input at dispatch — defaults fill in fields the caller omits: ```json { "name": "Report Covia Bug", "operation": { "adapter": "mcp:tools:call", "remoteToolName": "create_issue", "server": "https://mcp.github.example", "default": { "owner": "covia-ai", "repo": "covia" }, "input": { "type": "object", "properties": { "title": { "type": "string" }, "body": { "type": "string" } }, "required": ["title"] } } } ``` Semantics: - **Caller wins**: a caller-supplied value always overrides the default. - **Any value type**: defaults may be strings, numbers, booleans, arrays, or objects. - **Shallow merge**: top-level keys only. Defaults apply to object inputs; a null input becomes the defaults map, and a non-object input passes through untouched. - **Applied once at dispatch**, before capability checks and job creation — authorisation decisions rule on, and the job record stores, the *effective* input. - Defaulted keys SHOULD be omitted from the input schema's `required` list so callers know they may omit them. Defaults let a generic operation be published as a purpose-shaped one — the example above turns a five-field `create_issue` into a two-field bug reporter. They are **purpose-shaping, not policy**: a caller can override any default, so enforcement of argument values belongs to the venue's capability layer (see [COG-13: Agent Capabilities](./COG-013)), not to defaults. Credentials MUST NOT be placed in defaults — operation metadata persists in plain form; credentials belong in secret references. ### Execution Model When a client invokes an operation: 1. **Argument Defaults**: entries in `operation.default` fill in input fields the caller omitted (caller-supplied values win) 2. **Job Creation**: The venue creates a Job to track execution — its record holds the effective input 3. **Input Validation**: Inputs are validated against the schema (if provided) 4. **Adapter Dispatch**: The venue routes to the appropriate adapter 5. **Execution**: The adapter performs the operation 6. **Result Return**: Output is returned via the Job See [COG-8: Jobs](./COG-008) for the full job lifecycle, status transitions, and observation mechanisms. ### Orchestrations Operations can compose other operations using the `orchestrator` adapter: #### `operation.steps` (REQUIRED for orchestrations) An ordered array of steps to execute: ```json { "operation": { "adapter": "orchestrator", "steps": [ { "op": "v/ops/http/get", "name": "Fetch Data", "input": { "url": ["input", "dataUrl"] } }, { "op": "v/ops/langchain/openai", "name": "Process", "input": { "prompt": [0, "body"] } } ], "result": { "processed": [1, "content"], "source": [0, "headers"] } } } ``` Each step specifies: - `op`: Operation reference (catalog path, Asset ID, or DID URL) - `name`: Human-readable step name - `input`: Input mapping (see below) #### Input Mapping Syntax | Syntax | Description | | ------ | ----------- | | `["input"]` | Entire orchestration input | | `["input", "field"]` | Specific field from input | | `[0]` | Output of step 0 | | `[1, "field"]` | Field from step 1's output | | `["const", value]` | Literal constant value (embeds `value` verbatim, even if it is a vector) | | `["concat", parts...]` | String concatenation of the resolved parts | | `["array", elems...]` | Array whose elements are each resolved | Only **JSON arrays** are interpreted as mapping expressions; maps are resolved value-by-value, and any other value (string, number, boolean) is a literal. Expression arguments nest — `["concat", "Convert to ", ["input", "format"], ":\n", [0, "body"]]` mixes literal strings with resolved references. See [COG-12: Orchestrations](./COG-012) for the full orchestration model. #### `operation.result` (REQUIRED for orchestrations) Maps step outputs to the final result: ```json { "operation": { "result": { "data": [1, "output"], "metadata": [0, "headers"], "originalInput": ["input"] } } } ``` ## Examples ### HTTP Operation A simple HTTP GET request: ```json { "name": "HTTP GET", "description": "Performs an HTTP GET request and returns the response", "dateCreated": "2025-01-27T10:00:00Z", "operation": { "adapter": "http", "input": { "type": "object", "properties": { "url": { "type": "string", "description": "URL to fetch" }, "headers": { "type": "object", "description": "Optional request headers" } }, "required": ["url"] }, "output": { "type": "object", "properties": { "status": { "type": "integer", "description": "HTTP status code" }, "body": { "type": "string", "description": "Response body" }, "headers": { "type": "object", "description": "Response headers" } } } } } ``` ### Blockchain Query Operation A read-only Convex blockchain query: ```json { "name": "Convex Query", "description": "Execute a read-only query against the Convex network", "dateCreated": "2025-01-27T10:00:00Z", "operation": { "adapter": "convex:query", "input": { "type": "object", "properties": { "peer": { "type": "string", "description": "Convex peer endpoint", "default": "peer.convex.live:18888" }, "address": { "type": "string", "description": "Account address for context (e.g., #123)" }, "source": { "type": "string", "description": "Convex Lisp code to execute" } }, "required": ["source"] }, "output": { "type": "object", "properties": { "result": { "type": "string", "description": "Query result as Convex data" }, "value": { "description": "Result converted to JSON" }, "errorCode": { "type": "string", "description": "Error code if query failed" } } } } } ``` ### Orchestration Operation A multi-step workflow combining operations: ```json { "name": "Fetch and Transform", "description": "Fetches data from a URL and applies a transformation", "dateCreated": "2025-01-27T10:00:00Z", "operation": { "adapter": "orchestrator", "input": { "type": "object", "properties": { "url": { "type": "string", "description": "Data source URL" }, "format": { "type": "string", "description": "Output format", "default": "json" } }, "required": ["url"] }, "output": { "type": "object", "properties": { "data": { "description": "Transformed data" }, "source": { "type": "string", "description": "Original source URL" } } }, "steps": [ { "op": "v/ops/http/get", "name": "Fetch Source", "input": { "url": ["input", "url"] } }, { "op": "v/ops/langchain/openai", "name": "Transform Data", "input": { "prompt": ["concat", "Convert the following to ", ["input", "format"], ":\n", [0, "body"]] } } ], "result": { "data": [1, "content"], "source": ["input", "url"] } } } ``` ## Security Considerations ### Input Validation Venues SHOULD validate inputs against the declared schema before execution. This prevents: - Injection attacks through malformed inputs - Resource exhaustion from unexpected input sizes - Type confusion errors ### Adapter Security Adapters have different security profiles: - `http`: Can make external network requests — venue policies should restrict targets - `jvm`: Executes local code — should be sandboxed appropriately - `orchestrator`: Inherits security requirements of child operations - `langchain`, `llmagent`: Make outbound API calls to LLM providers — credentials must be handled securely - `mcp`: Connects to external MCP servers — venue policies should restrict allowed endpoints - `agent`: Maintains persistent state — resource limits on agent count, inbox size, and lifetime apply ### Output Trust Operation outputs should be treated according to the trust level of the executing venue: - Verify deterministic results where possible - Use multiple venues for critical computations - Consider cryptographic attestations for high-value operations ### Resource Limits Venues SHOULD impose resource limits on operation execution: - Timeout limits - Memory limits - Network request limits - Nested orchestration depth limits ## Related Specifications - **[COG-1: Architecture](./COG-001)** - Overall Grid architecture, Jobs, and Adapters - **[COG-2: Decentralised ID](./COG-002)** - Operation identification - **[COG-5: Asset Metadata](./COG-005)** - Common metadata format - **[COG-6: Artifacts](./COG-006)** - Data assets (contrast with operations) - **[COG-8: Jobs](./COG-008)** - Job lifecycle, status transitions, and execution tracking - **[COG-11: Agent Lifecycle](./COG-011)** - Stateful agent creation, run loop, and state transitions --- ## COG-8: Jobs ``` Status: Draft (Work in Progress) Version: 0.3 Created: 2025-01-28 Updated: 2026-07-19 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: This standard specifies **Jobs** - the execution tracking mechanism for operations on the Covia Grid. A Job represents a running process on the Grid, modelled as a pointer to an evolving chain of immutable state records. ## Purpose Jobs are the runtime counterpart to [Operations](./COG-007). While an operation defines *what* can be executed, a job represents *a specific execution* of that operation. Jobs provide: - **Asynchronous Execution**: Clients submit work and track progress without blocking - **Observable Lifecycle**: Every job transitions through well-defined states - **Error Handling**: Structured error reporting for failed, rejected, or cancelled work - **Interactive Workflows**: Support for human-in-the-loop patterns where jobs pause for input or authorisation - **Auditability**: Job state chains provide a verifiable, immutable history of all computation performed on the Grid - **Unified Data Model**: Job state records share the same content-addressed, immutable structure as other Grid assets ## Terminology See [COG-1: Architecture](./COG-001) for definitions of Grid terminology including Job, Operation, Venue, and Client. ## Key Concepts ### Job as a Pointer to Process State A Job is not a mutable record. It is a **HEAD pointer** to the latest entry in a chain of immutable state records. Each state transition appends a new immutable record to the chain, linked to its predecessor by a content-addressed hash. ``` Job HEAD ──▶ State(N) ──prev──▶ State(N-1) ──prev──▶ ... ──prev──▶ State(0) (latest) (initial) ``` Each state record in the chain is an immutable, content-addressed value — structurally identical to an [Artifact](./COG-006) or [Operation](./COG-007) asset. The only mutable element is the HEAD pointer itself, which advances as the job progresses. This design provides several properties: - **Audit trail**: The full execution history is preserved as a chain of immutable records, each cryptographically linked to its predecessor - **Verifiability**: Any participant can verify the integrity of a job's history by walking the chain and checking hashes - **Lattice compatibility**: Immutable state records merge naturally using union semantics in the [Grid Lattice](./COG-004), eliminating the need for timestamp-based conflict resolution on job data - **Structural sharing**: When state records are stored as native lattice data structures, unchanged fields (e.g. `input`, `op`) are automatically deduplicated across state transitions via the lattice's Merkle tree structure #### State Record Structure Each state record in the chain contains: | Field | Presence | Description | | ----- | -------- | ----------- | | `status` | REQUIRED | Current lifecycle status | | `prev` | REQUIRED on every record after the first; MUST be absent on the initial record | Content-addressed hash of the previous state record | | `op` | Initial record | Operation identifier (Asset ID, name, or adapter reference) | | `input` | Initial record | Input provided at job creation | | `caller` | Initial record | Verified identity (DID) of the submitting client — the basis of job access control | | `created` | Initial record | Timestamp in milliseconds since Unix epoch of job creation | | `output` | COMPLETE; MAY also be present on interactive states carrying a turn result (e.g. an agent's reply on `INPUT_REQUIRED`) | Result produced by the operation | | `error` | Terminal (FAILED, REJECTED, CANCELLED) | Error description | | `message` | OPTIONAL | Context about current state (e.g. what input is required) | | `updated` | RECOMMENDED | Timestamp in milliseconds since Unix epoch | Fields that have not changed since the previous state record MAY be omitted from subsequent records, since the full state can be reconstructed by walking the chain. However, implementations MAY include all fields in every record for convenience. #### Example Chain A simple echo operation produces a three-link chain: ``` State(2) — HEAD { "status": "COMPLETE", "prev": "0xdef...", "output": {"text": "hello"}, "updated": 1769683717710 } │ ▼ prev State(1) { "status": "STARTED", "prev": "0x123...", "updated": 1769683717708 } │ ▼ prev State(0) — initial { "status": "PENDING", "op": "v/ops/test/echo", "input": {"text": "hello"}, "caller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", "created": 1769683717706, "updated": 1769683717706 } ``` ### Verifiability of Job State The chain model enables cryptographic verification of job execution history: 1. **Each state record is content-addressed.** The record's identifier is derived from its content (see [Asset ID Scheme](#asset-id-scheme) below). Any modification to a record changes its identifier, making tampering detectable. 2. **Chain integrity.** Each record contains the hash of its predecessor in the `prev` field. A verifier can walk the chain from HEAD to the initial state, confirming that no records have been inserted, removed, or modified. 3. **Cross-venue verification.** Because state records are immutable and content-addressed, they can be safely replicated to other venues. A client can request the same job history from multiple venues and confirm consistency. 4. **Selective disclosure.** A client can share a specific state record (e.g. the terminal COMPLETE record) along with its chain as proof that a computation occurred, without revealing the full job context. ### Relationship to Assets Job state records, [Artifacts](./COG-006), and [Operations](./COG-007) are all immutable, content-addressed records stored in the lattice. They differ only in their metadata shape: | Record Type | Distinguishing Field | Purpose | | ----------- | -------------------- | ------- | | Artifact | `content` | Immutable data | | Operation | `operation` | Executable function definition | | State Record | `status` + `prev` | Job execution state | This unification means that job state records can be stored, replicated, and verified using the same mechanisms as any other asset on the Grid. ### One-Shot Jobs and Multi-Turn Jobs All jobs share the same lifecycle and data model, but differ in their interaction pattern: **One-shot jobs** are created with an initial input, execute a single operation, and reach a terminal state. The state chain is short: `PENDING → STARTED → COMPLETE` (or `FAILED`). This is the model used by simple tool calls and operation invocations. **Multi-turn jobs** cycle through interactive states, accepting additional input between processing steps. The state chain grows with each interaction: `PENDING → STARTED → INPUT_REQUIRED → STARTED → INPUT_REQUIRED → ... → COMPLETE`. This is the model used by conversational agents, orchestrated workflows, and any process requiring human-in-the-loop interaction. Both types use the same job lifecycle, the same state chain structure, and the same observation mechanisms (polling, SSE). The difference is purely behavioural — a one-shot job's transition function produces a terminal state on the first invocation, while a multi-turn job's transition function produces an interactive state and waits for the next message. Clients that require a synchronous result (e.g. a single tool call) simply wait for the terminal state — regardless of how many intermediate transitions occur. The job's internal interaction loop is opaque to a synchronous caller. See [COG-9: Agent Messaging](./COG-009) for the asynchronous message delivery mechanism that drives multi-turn interactions. ### Agents as Persistent Jobs A Job whose transition function never reaches a terminal state is behaviourally an **Agent** — a persistent process accepting repeated interactions, waiting in `INPUT_REQUIRED` between turns. The data model is identical; nothing in this COG is agent-specific. The agent model — state, run loop, transition functions, sessions — is specified in [COG-11: Agent Lifecycle](./COG-011). ## Specification ### Job Lifecycle Every job follows a lifecycle defined by its **status**. Status transitions are unidirectional — a job moves forward through the lifecycle and never returns to a previous state (with the exception of interactive states resuming to `STARTED`). #### Status Values | Status | Category | Description | | ------ | -------- | ----------- | | `PENDING` | Active | Job created, queued for execution | | `STARTED` | Active | Job is currently executing | | `COMPLETE` | Terminal | Job finished successfully with output | | `FAILED` | Terminal | Job finished with an error | | `CANCELLED` | Terminal | Job was cancelled by client or venue | | `REJECTED` | Terminal | Job was rejected before execution (e.g. policy violation, invalid input) | | `PAUSED` | Interactive | Job execution suspended, awaiting a resume signal | | `INPUT_REQUIRED` | Interactive | Job requires additional input from the client to continue | | `AUTH_REQUIRED` | Interactive | Job requires authorisation or credentials to continue | #### Status Categories Statuses fall into three categories: - **Active** (`PENDING`, `STARTED`): The job is progressing. Clients should poll or subscribe for updates. - **Terminal** (`COMPLETE`, `FAILED`, `CANCELLED`, `REJECTED`): The job has finished. No further status changes will occur. The job's output or error is available. - **Interactive** (`PAUSED`, `INPUT_REQUIRED`, `AUTH_REQUIRED`): The job is waiting for external action. The client must respond before the job can continue. #### State Transitions ``` ┌─────────────────┐ │ REJECTED │ └─────────────────┘ ▲ │ (invalid input, │ policy violation) │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ PENDING │ ────────▶ │ STARTED │ ────────▶ │ COMPLETE │ └──────────┘ └──────────┘ └──────────────┘ │ ▲ │ │ │ │ ┌────┘ │ └────┐ │ │ │ ▼ │ ▼ ┌───────────┐ │ ┌──────────┐ │ PAUSED / │───┘ │ FAILED │ │ INPUT_ │ └──────────┘ │ REQUIRED /│ │ AUTH_ │ ┌──────────┐ │ REQUIRED │ │ CANCELLED │ └───────────┘ └──────────┘ ▲ │ (from any non-terminal state) ``` The permitted transitions are: | From | To | Trigger | | ---- | -- | ------- | | `PENDING` | `STARTED` | Venue begins execution | | `PENDING` | `REJECTED` | Venue rejects the job (policy, quota, invalid input) | | `PENDING` | `CANCELLED` | Client cancels before execution begins | | `STARTED` | `COMPLETE` | Operation finishes successfully | | `STARTED` | `FAILED` | Operation encounters an error | | `STARTED` | `CANCELLED` | Client cancels during execution | | `STARTED` | `PAUSED` | Operation suspends execution | | `STARTED` | `INPUT_REQUIRED` | Operation needs additional client input | | `STARTED` | `AUTH_REQUIRED` | Operation needs client authorisation | | `PAUSED` / `INPUT_REQUIRED` / `AUTH_REQUIRED` | `STARTED` | Client provides required input or authorisation | | `PAUSED` / `INPUT_REQUIRED` / `AUTH_REQUIRED` | `CANCELLED` | Client cancels while paused | Each transition appends a new immutable state record to the job's chain. Terminal states are **sticky**: once a terminal record is appended, later transition attempts have no effect. When concurrent resolutions race (e.g. a completion against a cancellation), exactly one wins. #### Timeouts Jobs have **no framework-level timeout**, and there is deliberately no timeout status. A job may legitimately remain active or interactive for days, weeks, or months — long workflows, human-in-the-loop requests, persistent agents. - **Client-side waits are a client concern.** A client that stops waiting (poll timeout, disconnect) MUST NOT thereby alter the job's state; it re-attaches later by Job ID and reads the current state. - **Time bounds are feature-level policy.** An operation or feature MAY impose its own expiry (e.g. a Human-in-the-Loop request's expiry, [COG-16](./COG-016)); when such a bound passes, the job resolves through the ordinary terminal states — typically `FAILED` with a descriptive error. - **Operator resource policy** (admission control, concurrent-job caps) acts before job creation or through cancellation, never through a special timeout state. ### Job Identification A job is identified by a unique **Job ID** within the venue. The Job ID is assigned at creation time and remains stable throughout the job's lifecycle — it identifies the HEAD pointer, not any individual state record. Job IDs are represented as hex strings. The format is implementation-defined, but MUST be unique within the venue. Individual state records in the chain are identified by their content-addressed hash (see [Asset ID Scheme](#asset-id-scheme)). ### Job Data The current state of a job is represented as a JSON object when accessed via the API. This object reflects the latest state record in the chain, with all fields resolved (including inherited fields from earlier records). #### `id` (REQUIRED) The Job ID — the stable identifier for this job within the venue. ```json { "id": "0x12345678901234567890123456789012" } ``` #### `status` (REQUIRED) The current lifecycle status of the job. MUST be one of the status values defined above. ```json { "status": "STARTED" } ``` #### `op` (RECOMMENDED) The identifier of the operation being executed (Asset ID, operation name, or adapter reference). The same field name is used in state records. ```json { "op": "0x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b" } ``` #### `caller` (RECOMMENDED) The verified identity (DID) of the client that submitted the job. Job access control is based on this field: by default only the caller — or parties holding a delegation from them ([COG-13](./COG-013)) — can observe the job, and only the caller can mutate it. ```json { "caller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" } ``` #### `output` (present when COMPLETE) The result produced by the operation. The structure of the output is defined by the operation's output schema (see [COG-7: Operations](./COG-007)). ```json { "status": "COMPLETE", "output": { "result": "Success", "data": { "count": 42 } } } ``` #### `error` (present when FAILED, REJECTED, or CANCELLED) A human-readable error message describing why the job did not complete. ```json { "status": "FAILED", "error": "Connection to upstream service timed out" } ``` #### `message` (OPTIONAL) A general-purpose message providing context about the current state. Particularly useful for interactive states to describe what input or authorisation is needed. ```json { "status": "INPUT_REQUIRED", "message": "Please provide the API key for the target service" } ``` #### `input` (OPTIONAL) The input that was provided when the job was created. #### `created` (RECOMMENDED) Timestamp in milliseconds since Unix epoch when the job was created. #### `updated` (RECOMMENDED) Timestamp in milliseconds since Unix epoch of the last status change. ### Job Creation Jobs are created when a client invokes an operation on a venue via `POST /api/v1/invoke`. See [COG-7: Operations](./COG-007) for the invocation model. The invoke request body carries: | Field | Required | Description | | ----- | -------- | ----------- | | `operation` | REQUIRED | Operation reference — catalog path, Asset ID, or DID URL ([COG-7](./COG-007)) | | `input` | OPTIONAL | Input value for the operation | | `wait` | OPTIONAL | Wait for completion before responding: `true` (venue's maximum window) or a bound in milliseconds; also accepted as a query parameter | | `ucans` | OPTIONAL | Proof channel — an array of UCAN JWT strings ([COG-3](./COG-003) §6 for the channel; [COG-13](./COG-013) for proof semantics) | Note the request field is `operation`; the resolved reference is recorded as `op` in the job's state records. A venue MUST create a job for every accepted invocation request. Admission control (rate limits, concurrency caps, resource constraints — [COG-1](./COG-001)) MAY deny a request *before* acceptance with a transport-level error, in which case no job is created. A venue MUST create an initial state record with status `PENDING` (or `REJECTED` if the job is immediately rejected). A venue MAY immediately append a `STARTED` state record if execution begins synchronously. ### Job Observation Clients can observe job progress through two mechanisms: #### Polling Clients poll the job status via `GET /api/v1/jobs/{id}`. This returns the resolved job data from the latest state record, including current status, output (if complete), or error (if failed). Clients SHOULD use exponential backoff when polling to avoid overloading the venue. Recommended parameters: | Parameter | Value | | --------- | ----- | | Initial delay | 300ms | | Backoff factor | 1.5x | | Maximum delay | 10s | Abandoning a poll or a blocking wait is purely a client-side event: it MUST NOT alter the job's state. A client re-attaches at any time by Job ID and reads the current state (see [Timeouts](#timeouts)). #### Server-Sent Events (SSE) Clients can subscribe to real-time updates via `GET /api/v1/jobs/{id}/sse`. The venue pushes an event each time a new state record is appended to the chain. SSE is preferred over polling for long-running jobs because it: - Reduces network overhead - Provides immediate notification of state changes - Avoids the latency inherent in polling intervals #### Job History Clients MAY retrieve the full state chain for a job via `GET /api/v1/jobs/{id}/history`. This returns the ordered sequence of state records from initial to latest, enabling audit and verification of the complete execution history. ### Job Pause Clients can pause a running job via `PUT /api/v1/jobs/{id}/pause`. Pause genuinely suspends execution — a status change alone is not a pause while work continues underneath. Support is therefore **operation-dependent**: only operations whose executor can suspend in-flight work are pausable. A venue MUST append a state record with status `PAUSED` if the job is in `STARTED` state (matching the `STARTED → PAUSED` transition) and the executing operation supports suspension. A venue MUST return `409 Conflict` if the job is not in `STARTED` state, or if the executing operation does not support suspension. ### Job Resume Clients can resume a paused job via `PUT /api/v1/jobs/{id}/resume`. A venue MUST append a state record with status `STARTED` if the job is in `PAUSED` state and the executing operation supports resumption from its suspended state. A venue MUST return `409 Conflict` if the job is not in `PAUSED` state, or if the operation does not support resumption. Resumption continues from the operation's own suspended state. A venue MUST NOT re-execute the operation from its stored input — re-invocation can duplicate non-idempotent side effects and does not carry the original request's authority. Note: `INPUT_REQUIRED` and `AUTH_REQUIRED` jobs are resumed by delivering a message (see [COG-9: Agent Messaging](./COG-009)), not by calling the resume endpoint. The resume endpoint is specifically for `PAUSED` jobs. ### Job Cancellation Clients can cancel a job via `PUT /api/v1/jobs/{id}/cancel`. A venue MUST append a state record with status `CANCELLED` if the job is in a non-terminal state. A cancel request for a job that is already in a terminal state MUST leave the job unchanged; the venue responds `200 OK` with the current (terminal) job state. Cancellation is best-effort — the underlying operation may have already produced side effects before the cancellation takes effect. ### Job Deletion Clients can remove a job record via `PUT /api/v1/jobs/{id}/delete`. Deletion removes the HEAD pointer from the venue's job index. The immutable state records in the chain MAY be retained in lattice storage for audit purposes, or MAY be garbage collected at the venue's discretion. Deletion does not affect any resources or outputs produced by the job. ### Job Finality Once a job reaches a terminal state, no further state records are appended to its chain: - The HEAD pointer MUST NOT advance beyond a terminal state record - The terminal state record's `output` or `error` fields MUST NOT be modified (they are immutable by construction) - The job record MUST remain available for querying until explicitly deleted The immutable chain structure ensures that job records serve as reliable, verifiable audit evidence of computation performed on the Grid. ### Lattice Storage Job state records are stored in the [Grid Lattice](./COG-004) as immutable, content-addressed values. Because state records are immutable, they can be stored using **union merge** semantics — the same strategy used for assets — rather than timestamp-based merge. The HEAD pointer (Job ID → latest state record hash) is the only mutable element and requires a lightweight mutable index within the venue's lattice state. When state records are stored as native lattice data structures (rather than serialised JSON), the lattice provides automatic structural sharing: fields common across state records (such as `input`, `op`, and unchanged context) are deduplicated in the Merkle tree without any explicit compaction. ## Interactive Jobs Interactive statuses (`PAUSED`, `INPUT_REQUIRED`, `AUTH_REQUIRED`) enable human-in-the-loop, multi-step workflows, and agent interaction patterns where execution cannot proceed without external action. ### Use Cases | Status | Use Case | | ------ | -------- | | `PAUSED` | Debugging breakpoint; operator-initiated suspension (operation-dependent — see [Job Pause](#job-pause)) | | `INPUT_REQUIRED` | Operation needs additional parameters not provided at invocation; multi-turn conversation flows; agent interaction | | `AUTH_REQUIRED` | Operation needs credentials for a downstream service; consent or approval step | ### Client Responsibilities When a job enters an interactive state, the client SHOULD: 1. Read the `message` field to understand what is required 2. Deliver the requested input via `POST /api/v1/jobs/{id}` (see [COG-9: Agent Messaging](./COG-009)) 3. Monitor for resumption to `STARTED` via polling or SSE If a client cannot fulfil an interactive request, it SHOULD cancel the job rather than leaving it indefinitely paused. Abandoned interactive jobs are managed by feature-level expiry (e.g. a Human-in-the-Loop request's expiry, [COG-16](./COG-016)) or operator policy — there is no framework timeout (see [Timeouts](#timeouts)). ### Agent Interaction Pattern For persistent agents (jobs that cycle through interactive states), the client interaction loop is: ``` 1. Invoke operation → Job created (PENDING → STARTED → INPUT_REQUIRED) 2. Client sends message → Job processes the turn (INPUT_REQUIRED → STARTED → INPUT_REQUIRED) 3. Repeat step 2 for each interaction turn 4. Agent terminates → Job reaches terminal state (COMPLETE or FAILED) ``` The state chain grows with each interaction turn. Each turn's input and output are preserved as immutable records in the chain, providing a complete, verifiable interaction history. Messages are delivered via [COG-9: Agent Messaging](./COG-009). Whether a message is processed immediately or queued for a later turn is a property of the receiving operation — agents, for example, queue inbound messages in their session ([COG-11](./COG-011)) — so clients MAY send a message at any time, including while the job is actively processing (`STARTED`). Message delivery to a job in a terminal state MUST be rejected. ### Synchronous vs. Asynchronous Observation The same job supports both synchronous and asynchronous interaction: - **Synchronous callers** (e.g. an MCP `tools/call` bridge) wait for the terminal state. The entire multi-turn interaction — including any intermediate message exchanges — is opaque to them. They submit the initial input and receive the final result. - **Asynchronous callers** (e.g. a conversational UI, an A2A agent) observe each state transition in real time via SSE, and submit messages via `POST /api/v1/jobs/{id}` as the interaction progresses. Both patterns operate on the same job, the same state chain, and the same message delivery surface. The choice is the client's, not the job's. ### Asset ID Scheme Job state records, like all content-addressed data on the Grid, are identified by **lattice Value IDs** — the SHA3-256 hash of their canonical CAD003 binary encoding (see [COG-5: Asset Metadata](./COG-005)). This means: - The lattice automatically computes Value IDs for all stored data structures, so state record identification is a zero-cost property of storage - Structural sharing and deduplication across state records are handled natively by the lattice - Unchanged fields across state transitions (such as `input`, `op`) are automatically deduplicated in the Merkle tree ## Examples ### Simple Invocation ``` Client Venue │ │ │ POST /api/v1/invoke │ │ {"operation": "v/ops/test/echo", │ │ "input": {"text": "hello"}} │ │ ─────────────────────────────────▶ │ │ │ Create State(0): PENDING │ 201 Created │ Create State(1): STARTED │ {"id": "0xabc...", │ │ "status": "PENDING"} │ │ ◀───────────────────────────────── │ │ │ Execute → State(2): COMPLETE │ GET /api/v1/jobs/0xabc... │ │ ─────────────────────────────────▶ │ │ │ │ 200 OK │ │ {"id": "0xabc...", │ │ "status": "COMPLETE", │ │ "output": {"text": "hello"}} │ │ ◀───────────────────────────────── │ ``` ### Cancellation ``` Client Venue │ │ │ POST /api/v1/invoke │ │ {"operation": "long-running"} │ │ ─────────────────────────────────▶ │ │ │ │ 201 Created │ State(0): PENDING │ {"id": "0xdef...", │ State(1): STARTED │ "status": "PENDING"} │ │ ◀───────────────────────────────── │ │ │ │ PUT /api/v1/jobs/0xdef.../cancel │ │ ─────────────────────────────────▶ │ │ │ State(2): CANCELLED │ 200 OK │ │ {"id": "0xdef...", │ │ "status": "CANCELLED", │ │ "error": "Job cancelled"} │ │ ◀───────────────────────────────── │ ``` ### Interactive Job (Input Required) ``` Client Venue │ │ │ POST /api/v1/invoke │ │ {"operation": "data-import"} │ │ ─────────────────────────────────▶ │ │ │ State(0): PENDING │ 201 Created │ State(1): STARTED │ {"id": "0x123...", │ │ "status": "PENDING"} │ │ ◀───────────────────────────────── │ │ │ State(2): INPUT_REQUIRED │ GET /api/v1/jobs/0x123... │ │ ─────────────────────────────────▶ │ │ │ │ 200 OK │ │ {"id": "0x123...", │ │ "status": "INPUT_REQUIRED", │ │ "message": "Provide API key"} │ │ ◀───────────────────────────────── │ │ │ │ (Client provides input) │ │ ─────────────────────────────────▶ │ State(3): STARTED │ │ State(4): COMPLETE │ GET /api/v1/jobs/0x123... │ │ ─────────────────────────────────▶ │ │ │ │ 200 OK │ │ {"id": "0x123...", │ │ "status": "COMPLETE", │ │ "output": {"imported": 1500}} │ │ ◀───────────────────────────────── │ ``` ### Agent Interaction (Multi-Turn) ``` Client Venue │ │ │ POST /api/v1/invoke │ │ {"operation": "llm-agent", │ │ "input": {"prompt": "Hello"}} │ │ ─────────────────────────────────▶ │ │ │ State(0): PENDING │ 201 Created │ State(1): STARTED (LLM call) │ {"id": "0x456...", │ State(2): INPUT_REQUIRED │ "status": "PENDING"} │ (response + awaiting next turn) │ ◀───────────────────────────────── │ │ │ │ GET /api/v1/jobs/0x456... │ │ ─────────────────────────────────▶ │ │ │ │ 200 OK │ │ {"id": "0x456...", │ │ "status": "INPUT_REQUIRED", │ │ "output": {"response": "Hi!"}, │ │ "message": "Awaiting input"} │ │ ◀───────────────────────────────── │ │ │ │ (Client sends next message) │ │ ─────────────────────────────────▶ │ State(3): STARTED (LLM call) │ │ State(4): INPUT_REQUIRED │ GET /api/v1/jobs/0x456... │ (response + awaiting next turn) │ ─────────────────────────────────▶ │ │ │ │ 200 OK │ │ {"id": "0x456...", │ │ "status": "INPUT_REQUIRED", │ │ "output": {"response": "..."}, │ │ "message": "Awaiting input"} │ │ ◀───────────────────────────────── │ │ │ │ ... (conversation continues) │ ``` ## Security Considerations ### Resource Exhaustion Jobs consume venue resources (memory, connections, compute). Venues SHOULD: - Impose limits on the number of concurrent jobs per client (admission control, before job creation) - Clean up resources promptly when jobs reach terminal states - Bound long-running work through feature-level expiry or operator-initiated cancellation — never a framework timeout (see [Timeouts](#timeouts)) Long-lived agent jobs (persistent interactive processes) require particular attention to resource management. Venues SHOULD impose maximum chain lengths or interaction counts for agent jobs. ### Job Data Sensitivity Job records may contain sensitive information in their inputs and outputs. Venues SHOULD: - Apply access control so that only the submitting client (or authorised parties) can query a job - Redact or encrypt sensitive fields in stored job records - Support job deletion to allow clients to remove HEAD pointers for records containing sensitive data ### Cancellation Safety Cancellation does not guarantee rollback. Operations may have produced side effects (network calls, data writes) before cancellation takes effect. Clients SHOULD NOT rely on cancellation as a mechanism for undoing work. ### State Chain Integrity The immutable chain structure provides tamper evidence but not tamper prevention. A malicious venue could fabricate a chain. Clients requiring strong guarantees SHOULD: - Verify chain integrity by walking `prev` links and checking content-addressed hashes - Cross-check job state with multiple independent venues - Use signed state records where available ## Related Specifications - **[COG-1: Architecture](./COG-001)** - Overall Grid architecture and terminology - **[COG-2: Decentralised ID](./COG-002)** - Job and venue identification - **[COG-4: Grid Lattice](./COG-004)** - Lattice storage and merge semantics for job state - **[COG-5: Asset Metadata](./COG-005)** - Asset identification and the ID scheme decision - **[COG-7: Operations](./COG-007)** - Operation definitions, invocation model, and orchestration - **[COG-9: Agent Messaging](./COG-009)** - Message delivery to jobs and agents, protocol compatibility - **[COG-11: Agent Lifecycle](./COG-011)** - Stateful agent creation, run loop, transition functions, and state management - **[COG-13: Agent Capabilities](./COG-013)** - Delegated job observation via capability grants - **[COG-16: Human-in-the-Loop Requests](./COG-016)** - Feature-level expiry composed over the job lifecycle --- ## COG-9: Agent Messaging ``` Status: Draft (Work in Progress) Version: 0.1 Created: 2026-01-29 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: This standard specifies the **messaging protocol** for delivering messages to Jobs and Agents on the Covia Grid. It defines how clients, users, and other agents submit messages to a running process via a per-job message queue, and how those messages are processed into state transitions. ## Purpose [Jobs](./COG-008) on the Covia Grid represent running processes — from short-lived operation invocations to long-lived interactive agents. Many of these processes need to receive input after creation: - **User interaction**: A human sends messages to a conversational AI agent - **Agent-to-agent communication**: One agent sends data or instructions to another - **Authorisation responses**: A client provides credentials or approval requested by a paused job - **External notifications**: A system signals that an out-of-band action has completed COG-8 defines the job lifecycle and state chain. This specification defines how **messages are delivered to jobs** and how they trigger state transitions. It also defines compatibility mappings to the [A2A](https://a2a-protocol.org/) and [MCP](https://modelcontextprotocol.io/) protocols. ## Terminology See [COG-1: Architecture](./COG-001) for definitions of Grid terminology. Additional terms used in this specification: - **Message**: An arbitrary JSON value submitted to a job's message queue for processing - **Message Queue**: An ordered, per-job buffer of unprocessed messages - **Transition Function**: The [Operation](./COG-007) asset that processes messages and produces new state records ## Specification ### Message Format A message is an arbitrary JSON value submitted to a job for processing. Messages SHOULD be JSON objects. The following top-level fields are RECOMMENDED for interoperability, but not required: | Field | Type | Description | | ----- | ---- | ----------- | | `role` | string | Origin of the message: `"user"`, `"agent"`, or `"system"` | | `parts` | array | Content parts, each with a `type` field (e.g. `"text"`, `"data"`, `"file"`) | | `from` | string | Sender identity (DID or venue URL) for agent-to-agent messages | | `messageId` | string | Optional client-assigned identifier for deduplication or correlation | Venues and transition functions MUST accept arbitrary JSON as message content. The interpretation of message content is the responsibility of the transition function, not the messaging layer. #### Example: User Text Message ```json { "role": "user", "parts": [ {"type": "text", "text": "What is the capital of France?"} ] } ``` #### Example: Agent-to-Agent Message ```json { "role": "agent", "from": "did:web:other-venue.covia.ai", "parts": [ {"type": "text", "text": "Here are the results you requested."}, {"type": "data", "data": {"count": 42, "items": ["a", "b", "c"]}} ] } ``` #### Example: Authorisation Response ```json { "role": "system", "type": "auth/complete", "credentials": {"apiKey": "sk-..."} } ``` #### Example: Simple Input For simple interactions, a plain JSON value is sufficient: ```json {"prompt": "Hello, how are you?"} ``` ### Message Delivery Endpoint Messages are delivered to a job via: ``` POST /api/v1/jobs/{id} Content-Type: application/json ``` #### Response The venue MUST support at least the following responses: | Status | Condition | Response Body | | ------ | --------- | ------------- | | 202 Accepted | Message enqueued for processing | `{"id": "", "status": "", "queued": true}` | | 400 Bad Request | Request body is not valid JSON | Error object | | 401 / 403 | Caller is not authenticated / not authorised for this job | Error object | | 404 Not Found | Job ID does not exist | Error object | | 409 Conflict | Job is in a terminal state | `{"id": "", "status": "", "error": "Job has finished"}` | | 413 Payload Too Large | Message exceeds the venue's configured size limit | Error object | | 429 Too Many Requests | Message queue is full or rate limit exceeded | Error object with `Retry-After` header | The **202 Accepted** response indicates the message has been received and queued. It does NOT indicate that the message has been processed or that a state transition has occurred. Clients MUST use [job observation](./COG-008#job-observation) (polling or SSE) to observe the resulting state changes. #### Behaviour Requirements A venue MUST: - Accept messages for jobs in any non-terminal state (`PENDING`, `STARTED`, `PAUSED`, `INPUT_REQUIRED`, `AUTH_REQUIRED`) — including while the job is actively processing, queuing them for later — subject to the limits a venue MAY impose below - Enqueue the message and return 202 before processing begins - Preserve message ordering within a single connection A venue SHOULD: - Include the current job status in the 202 response for client convenience A venue MAY: - Impose a maximum queue depth per job and return 429 when exceeded - Impose rate limits on message submission and return 429 when exceeded - Assign a server-side message ID if the client did not provide a `messageId` ### Message Queue Each job maintains an ordered message queue. Messages are processed sequentially by the job's transition function. The queue is the key mechanism that decouples message **submission** from message **processing**. A job may be in any non-terminal state when a message arrives — including `STARTED` (actively processing a previous message). The queue holds the message until the job is ready to process it. #### Ordering Messages within a single job's queue MUST be processed in the order they were accepted (FIFO). When messages arrive from multiple sources concurrently, the venue determines the interleaving order. Venues SHOULD use arrival time at the venue as the ordering criterion. #### Processing When a message is dequeued for processing: 1. The job transitions to `STARTED` 2. The transition function is invoked with the current state and the message as input 3. The transition function produces a new state (which may itself be `INPUT_REQUIRED`, `COMPLETE`, `FAILED`, etc.) 4. A new state record is appended to the job's chain (see [COG-8](./COG-008)) 5. If the new state is non-terminal and there are remaining messages in the queue, the next message is dequeued and processing continues from step 1 The state record produced by processing a message SHOULD include a reference to the message that triggered it, enabling audit trail reconstruction. #### When Messages Are Processed Messages are dequeued and processed when the job is ready to accept input. This occurs: - When the job enters an interactive state (`INPUT_REQUIRED`, `AUTH_REQUIRED`, `PAUSED`) and messages are already waiting in the queue - When a new message is submitted and the job is already in an interactive state with an empty queue If the job is actively processing (`STARTED`), newly submitted messages wait in the queue. The job will process them after the current transition completes and the job returns to an interactive state. This means a client can submit a message to a job in any non-terminal state: | Job Status | Message Behaviour | | ---------- | ----------------- | | `PENDING` | Queued; processed when job starts and is ready for input | | `STARTED` | Queued; processed after current transition completes | | `INPUT_REQUIRED` / `AUTH_REQUIRED` / `PAUSED` | Queued; immediately dequeued and processed | | Terminal | Rejected with 409 Conflict | #### Queue Draining If a job reaches a terminal state while messages remain in the queue, those messages are discarded. The venue MAY log discarded messages for debugging. If a job transitions to an interactive state (`INPUT_REQUIRED`, `AUTH_REQUIRED`, `PAUSED`) while messages remain in the queue, the next queued message is immediately dequeued and processing continues. This enables **pipelining**, where a client submits multiple messages without waiting for intermediate responses. ### State Record Linkage When a message triggers a state transition, the resulting state record SHOULD include a `trigger` field referencing the message: ```json { "status": "INPUT_REQUIRED", "prev": "0xabc...", "output": {"response": "The capital of France is Paris."}, "trigger": { "messageId": "msg-001", "role": "user" }, "updated": 1769683717710 } ``` This enables clients to correlate responses with the messages that produced them, which is essential for multi-source messaging where responses may arrive out of order relative to submissions. ## Agent Interaction Patterns ### Conversational Agent The most common pattern: a user exchanges messages with an LLM-backed agent. ``` Client Venue │ │ │ POST /api/v1/invoke │ │ {"operation": "llm-agent", │ │ "input": {"system": "..."}} │ │ ─────────────────────────────────▶ │ │ │ State(0): PENDING │ 201 Created │ State(1): INPUT_REQUIRED │ {"id": "0x456..."} │ (agent ready for first message) │ ◀───────────────────────────────── │ │ │ │ POST /api/v1/jobs/0x456... │ │ {"role": "user", │ │ "parts": [{"text": "Hello"}]} │ │ ─────────────────────────────────▶ │ │ │ │ 202 Accepted │ Message queued │ ◀───────────────────────────────── │ State(2): STARTED (LLM processing) │ │ State(3): INPUT_REQUIRED │ GET /api/v1/jobs/0x456... │ (response ready, awaiting next) │ ─────────────────────────────────▶ │ │ │ │ 200 OK │ │ {"status": "INPUT_REQUIRED", │ │ "output": {"response": "Hi!"}} │ │ ◀───────────────────────────────── │ │ │ │ POST /api/v1/jobs/0x456... │ │ {"role": "user", │ │ "parts": [{"text": "Bye"}]} │ │ ─────────────────────────────────▶ │ │ │ │ 202 Accepted │ State(4): STARTED │ ◀───────────────────────────────── │ State(5): COMPLETE │ │ (agent chose to terminate) ``` ### Agent-to-Agent Two agents on different venues communicate by posting messages to each other's job queues. ``` Venue A Venue B │ │ │ Agent A's transition function │ │ decides to contact Agent B │ │ │ │ POST /api/v1/jobs/{agent-b-id} │ │ {"role": "agent", │ │ "from": "did:web:venue-a...", │ │ "parts": [{"text": "Request"}]} │ │ ─────────────────────────────────▶ │ │ │ │ 202 Accepted │ Agent B processes message │ ◀───────────────────────────────── │ Agent B's transition function │ │ posts reply to Agent A │ │ │ POST /api/v1/jobs/{agent-a-id} │ │ ◀───────────────────────────────── │ │ │ │ 202 Accepted │ │ ─────────────────────────────────▶ │ ``` Cross-venue agent messaging uses the same endpoint and format. The sending venue's adapter makes an HTTP POST to the target venue's API. No special federation protocol is needed — the messaging API is the federation protocol. ### Pipelining A client may submit multiple messages without waiting for responses: ``` Client Venue │ │ │ POST /api/v1/jobs/{id} │ │ {"role": "user", ...msg1...} │ │ ─────────────────────────────────▶ │ │ 202 Accepted │ │ ◀───────────────────────────────── │ │ │ │ POST /api/v1/jobs/{id} │ │ {"role": "user", ...msg2...} │ │ ─────────────────────────────────▶ │ │ 202 Accepted │ │ ◀───────────────────────────────── │ │ │ │ POST /api/v1/jobs/{id} │ │ {"role": "user", ...msg3...} │ │ ─────────────────────────────────▶ │ │ 202 Accepted │ │ ◀───────────────────────────────── │ │ │ Messages processed in order: │ │ msg1 → State(N+1) │ │ msg2 → State(N+2) │ │ msg3 → State(N+3) ``` This is useful for batch input, data streaming, or pre-loading context into an agent before it begins processing. ## Synchronous and Asynchronous Interaction The message queue supports both synchronous and asynchronous interaction patterns on the same job: ### Asynchronous (Native) The native Covia pattern: the client submits messages via `POST /api/v1/jobs/{id}` and observes state changes via polling or SSE. The client is in full control of the interaction rhythm and can submit messages at any time. This is the pattern used by: - Conversational UIs - A2A agent-to-agent communication - Orchestrator workflows - Any client that wants real-time observation ### Synchronous (Bridge) Some protocols require a synchronous request/response cycle — the caller blocks until a final result is available. The most prominent example is MCP's `tools/call`, which expects a single response containing the tool output. Synchronous bridges work by: 1. Creating a job (or sending a message to an existing one) 2. Waiting for the job to reach a **terminal state** (`COMPLETE`, `FAILED`, etc.) 3. Returning the terminal state's output or error as the response The bridge does not need to know whether the job is one-shot or multi-turn internally. A multi-turn job that requires intermediate input (e.g. via MCP elicitation) goes through its full interactive cycle — the bridge mediates each `INPUT_REQUIRED` → input → `STARTED` transition — and the synchronous caller only sees the final result. This means the same job can be driven by a synchronous bridge (MCP `tools/call`) or an asynchronous client (A2A `SendMessage` + SSE) interchangeably. The job's behaviour is identical in both cases; only the client-side interaction pattern differs. ## Protocol Compatibility ### A2A (Agent-to-Agent Protocol) The Covia messaging model is designed for compatibility with the [A2A protocol](https://a2a-protocol.org/). The mapping is: | A2A | Covia | Notes | | --- | ----- | ----- | | Task | Job | Both are stateful processes with lifecycle states | | `SendMessage` (no taskId) | `POST /api/v1/invoke` | Creates a new job/task | | `SendMessage` (with taskId) | `POST /api/v1/jobs/{id}` | Sends message to existing job | | `SendStreamingMessage` | `POST /api/v1/jobs/{id}` + `GET /api/v1/jobs/{id}/sse` | Send message, then subscribe to SSE for streaming response | | Task states: submitted, working | PENDING, STARTED | | | Task state: input-required | INPUT_REQUIRED | | | Task states: completed, failed, cancelled, rejected | COMPLETE, FAILED, CANCELLED, REJECTED | | | Message with role + parts | Message with role + parts | Same structure by design | | Artifacts (in task output) | Job output / asset references | | #### A2A Message Mapping An A2A `SendMessage` request: ```json { "jsonrpc": "2.0", "id": 1, "method": "SendMessage", "params": { "message": { "role": "user", "parts": [{"text": "Hello"}], "taskId": "0x456..." } } } ``` Maps to a Covia message delivery: ``` POST /api/v1/jobs/0x456... Content-Type: application/json { "role": "user", "parts": [{"type": "text", "text": "Hello"}] } ``` The A2A bridge layer on the venue translates between JSON-RPC and the REST endpoint, extracting the `taskId` and message body from `params.message`. (Per the A2A specification, `taskId` is a field of the Message object.) #### A2A Ordering Guarantee A2A requires: "All implementations MUST deliver events in the order they were generated." The per-job FIFO queue satisfies this requirement. ### MCP (Model Context Protocol) MCP uses a different interaction model — servers expose tools to clients, and clients invoke them. However, several MCP features map onto the Covia messaging model: | MCP | Covia | Notes | | --- | ----- | ----- | | `tools/call` | `POST /api/v1/invoke` | Invoke an operation, creating a job | | `elicitation/create` (form mode) | Job enters `INPUT_REQUIRED`; response = message to job | Server asks client for input; client responds with data | | `elicitation/create` (URL mode) | Job enters `AUTH_REQUIRED` | Server directs user to external URL for auth | | `notifications/elicitation/complete` | Message to job confirming auth complete | Fire-and-forget notification | | `notifications/cancelled` | `PUT /api/v1/jobs/{id}/cancel` | Cancellation | | Progress notifications | SSE events on job state chain | Each state record = progress update | #### MCP Elicitation as Job Messaging When a Covia venue acts as an MCP server and a tool call requires user input: 1. The venue creates a job for the tool call 2. The job transitions to `INPUT_REQUIRED` with a `message` describing what's needed 3. The MCP bridge sends an `elicitation/create` request to the MCP client 4. The MCP client presents the form to the user 5. The user's response is translated into a message posted to the job queue 6. The job processes the message and continues ``` MCP Client MCP Bridge Job Queue │ │ │ │ tools/call │ │ │ ──────────────────────▶│ │ │ │ invoke → Job created │ │ │ Job: INPUT_REQUIRED │ │ │ │ │ elicitation/create │ │ │ ◀──────────────────────│ │ │ │ │ │ User provides input │ │ │ ──────────────────────▶│ │ │ │ POST /jobs/{id} │ │ │ ─────────────────────▶│ │ │ 202 Accepted │ │ │ ◀─────────────────────│ │ │ │ Process → COMPLETE │ tools/call result │ │ │ ◀──────────────────────│ │ ``` #### MCP Notifications as Messages MCP's `notifications/elicitation/complete` is a fire-and-forget notification (no response expected). This maps directly to the 202 Accepted semantics of the Covia messaging endpoint — the sender does not wait for processing. #### MCP Tasks (November 2025) The November 2025 MCP specification introduced **Tasks** as a server-side abstraction for tracking work. MCP Tasks align with Covia Jobs: - Both represent asynchronous work with lifecycle states - Both support progress tracking and cancellation - MCP's `tasks/cancel` maps to `PUT /api/v1/jobs/{id}/cancel` As the MCP Tasks specification matures, deeper integration may be defined. The Covia messaging model is designed to be forward-compatible with task-based MCP interactions. ## Security Considerations ### Message Authentication For agent-to-agent messaging across venues, the receiving venue SHOULD: - Verify the sender's identity (e.g. via DID resolution or mutual TLS) - Apply access control policies to determine whether the sender is authorised to message the target job - Log the sender's identity in the state record for audit purposes For user messages, the venue SHOULD: - Apply the same authentication used for other API endpoints - Associate the message with the authenticated user identity ### Message Size Limits Venues SHOULD impose maximum message sizes to prevent resource exhaustion. A recommended default is 1 MB per message. Venues MUST return 413 Payload Too Large if a message exceeds the configured limit. ### Queue Depth Limits Venues SHOULD impose maximum queue depths per job to prevent memory exhaustion from clients that submit messages faster than the transition function can process them. When the queue is full, the venue MUST return 429 Too Many Requests with a `Retry-After` header. ### Sensitive Content Messages may contain sensitive data (credentials, personal information, proprietary data). Venues SHOULD: - Apply the same data protection policies as for job inputs and outputs (see [COG-8](./COG-008)) - Not log message content at debug level in production environments - Support encryption of message content at rest For credential delivery (e.g. API keys in response to `AUTH_REQUIRED`), clients SHOULD use secure transport (HTTPS) and venues SHOULD NOT persist credentials in the state chain. The transition function SHOULD extract and store credentials separately, recording only a reference (e.g. "API key provided") in the state record. ### Denial of Service The messaging endpoint is a potential target for denial-of-service attacks. Venues SHOULD: - Rate-limit message submissions per client - Rate-limit message submissions per job - Impose connection limits - Reject messages from unauthenticated clients for non-public jobs ## Implementation Notes ### Relationship to Job Creation The `POST /api/v1/invoke` endpoint (COG-7/COG-8) creates a new job with initial input. The `POST /api/v1/jobs/{id}` endpoint (this specification) sends subsequent messages to an existing job. Together they form the complete interaction lifecycle: | Action | Endpoint | COG | | ------ | -------- | --- | | Create job with initial input | `POST /api/v1/invoke` | COG-7, COG-8 | | Send message to existing job | `POST /api/v1/jobs/{id}` | COG-9 (this spec) | | Observe job state | `GET /api/v1/jobs/{id}` | COG-8 | | Stream job events | `GET /api/v1/jobs/{id}/sse` | COG-8 | | View job history | `GET /api/v1/jobs/{id}/history` | COG-8 | | Pause job | `PUT /api/v1/jobs/{id}/pause` | COG-8 | | Resume paused job | `PUT /api/v1/jobs/{id}/resume` | COG-8 | | Cancel job | `PUT /api/v1/jobs/{id}/cancel` | COG-8 | | Delete job | `PUT /api/v1/jobs/{id}/delete` | COG-8 | ### Transition Function Contract A transition function that supports messaging MUST: - Accept arbitrary JSON as the message input - Return a new state value (which becomes the next state record in the chain) - Handle unknown message formats gracefully (e.g. by returning an error state or ignoring unrecognised fields) A transition function SHOULD: - Be idempotent where possible (processing the same message twice produces the same result) - Complete within a reasonable time to avoid blocking the queue - Produce meaningful state records that capture the essence of each interaction ### SSE Integration When a message is processed and produces a new state record, the venue MUST emit an SSE event to any active subscribers for that job (see [COG-8: Job Observation](./COG-008#job-observation)). This provides real-time feedback to clients that submit a message via POST and observe via SSE concurrently. ## Related Specifications - **[COG-1: Architecture](./COG-001)** - Overall Grid architecture and terminology - **[COG-2: Decentralised ID](./COG-002)** - Agent and venue identification - **[COG-7: Operations](./COG-007)** - Operation definitions and invocation - **[COG-8: Jobs](./COG-008)** - Job lifecycle, state chains, and agent model - **[COG-11: Agent Lifecycle](./COG-011)** - Stateful agent creation, run loop, and transition functions - **[A2A Protocol](https://a2a-protocol.org/latest/specification/)** - Agent-to-Agent protocol specification - **[MCP Specification](https://modelcontextprotocol.io/specification/2025-11-25)** - Model Context Protocol specification --- ## COG-10: Venue Authentication & Access Control ``` Status: Draft (Work in Progress) Version: 0.1 Created: 2026-01-29 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: :::note[Related specifications] This document specifies how a venue **implements** authentication and access control — the caller classes, the middleware verification order, venue-signed tokens, and OAuth provider configuration. For the underlying catalogue of mechanisms (including the full Ed25519 self-issued JWT specification, which is not repeated here), see [COG-3: Authentication Mechanisms](./COG-003). For capability-based authorisation, see [COG-13: Agent Capabilities](./COG-013). ::: This standard specifies the **authentication and access control** mechanisms for Covia Grid venues. It defines how callers identify themselves, how venues verify identity, and how access control decisions are made. ## Purpose Venues on the Covia Grid expose API endpoints that accept requests from users, agents, and other venues. Authentication determines **who** is making a request; access control determines **whether** that request is permitted. The Covia authentication model supports three classes of caller: - **Anonymous**: No identity presented. Permitted when public access is enabled. - **Self-identified**: The caller presents a self-issued JWT proving control of a cryptographic key pair. No external authority is needed. - **Authority-backed**: The caller presents a JWT issued by the venue (after OAuth login) or by a trusted external provider. ## Terminology See [COG-1: Architecture](./COG-001) for definitions of Grid terminology. Additional terms used in this specification: - **Bearer JWT**: A JWT presented in the `Authorization: Bearer ` HTTP header (distinct from the opaque bearer tokens / API keys of [COG-3](./COG-003) §2) - **Caller DID**: The Decentralised Identifier associated with the authenticated caller - **Venue Key**: The Ed25519 key pair owned by the venue, used for signing venue-issued JWTs - **JWKS**: JSON Web Key Set, a standard format for publishing public keys used to verify JWTs ## Specification ### Authentication Flow All API requests (`/api/*`), A2A requests (`/a2a`), and MCP requests (`/mcp`) pass through the authentication middleware. The middleware extracts caller identity from the `Authorization` header. ``` Request arrives │ ├── No Authorization header │ ├── Public access enabled → Allow (anonymous) │ └── Public access disabled → 401 Authentication required │ └── Authorization: Bearer │ ├── Try self-issued EdDSA verification │ └── Success → Caller DID = sub claim (did:key or venue-managed named DID) │ ├── Try venue-signed EdDSA verification │ └── Success → Caller DID = sub claim (venue user DID) │ ├── Try external provider RS256 verification │ └── Success → Caller identity = email or sub claim │ └── All verification paths fail ├── Public access enabled → Allow (anonymous) └── Public access disabled → 401 Invalid or expired token ``` The middleware attempts verification in order: self-issued, venue-signed, then external provider. The first successful verification determines the caller identity. Every accepted token yields the caller identity from its `sub` claim. What distinguishes the classes is **whose authority the token rests on** — expressed by the signing key together with a per-class constraint on `iss`, which keeps the classes disjoint: | Class | Authority (`iss`) | Caller (`sub`) | Signature key | |-------|-------------------|----------------|---------------| | Self-issued | The subject itself — `iss`, when present, MUST equal `sub` | `did:key`, or a venue-managed named DID | The `kid` key, which MUST be authorised for `sub` ([COG-3 §5](./COG-003)) | | Venue-signed | This venue — `iss` MUST equal the venue's DID | Venue-scoped user DID | The venue's own key | | External provider | The provider — `iss` MUST match the configured issuer | Provider-scoped subject | A provider JWKS key matching `kid` | A venue-signed token is an **attestation by the venue about its user**, so `iss ≠ sub` is expected there. A self-issued token rests on the subject's own authority, so a token naming any other issuer MUST NOT be accepted as self-issued. ### Token Types #### Self-Issued EdDSA JWT A client with an Ed25519 key pair MAY create a self-issued JWT to authenticate. This is the primary authentication method for agents and automated clients. The full token specification lives in [COG-3 §5](./COG-003) and is not repeated here. In summary: - The JWT MUST use the EdDSA algorithm - The `kid` header identifies the signing key — canonically its bare [Multikey](https://www.w3.org/TR/controller-document/#multikey) string; the `did:key:` and `#` forms are also accepted - The `sub` claim is the caller's DID: a `did:key` whose embedded key equals the `kid` key, or a venue-managed named-user DID for which the `kid` key is an **active registered authentication key** - The `iss` claim, when present, MUST equal `sub`; it is REQUIRED for venue-managed subjects - The JWT MUST include `iat` and `exp`; `aud` is RECOMMENDED and enforced per the venue's audience policy - The venue verifies the signature with the `kid` key, then confirms that key is authorised for `sub` **Example claims:** ```json { "iss": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", "sub": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", "aud": "did:web:venue.example.com", "iat": 1706500000, "exp": 1706500300 } ``` For `did:key` subjects this mechanism requires no prior registration or shared secrets — any entity with an Ed25519 key pair can authenticate to any venue. A venue-managed subject can authenticate only at its home venue, using a key registered for it there. #### Venue-Signed EdDSA JWT After a successful OAuth login, the venue issues a JWT signed with its own Ed25519 key pair. This binds the OAuth identity to a venue-scoped user DID. **Requirements:** - The JWT MUST use the EdDSA algorithm - The JWT MUST be signed with the venue's key pair - The `sub` claim MUST contain the user's venue-scoped DID (e.g. `did:web:venue.example.com:u:alice_gmail_com`) - The `iss` claim MUST contain the venue's DID - The `iat` and `exp` claims MUST be present - Expiry is controlled by the venue's `auth.tokenExpiry` configuration (default: 86400 seconds / 24 hours) **Example claims:** ```json { "sub": "did:web:venue.example.com:u:alice_gmail_com", "iss": "did:web:venue.example.com", "iat": 1706500000, "exp": 1706586400 } ``` The venue verifies these tokens using its own public key. No external key material is needed. #### External Provider RS256 JWT Clients that already hold a valid ID token from a configured OAuth provider MAY present it directly as a bearer token. The venue verifies it against the provider's JWKS endpoint. **Requirements:** - The JWT MUST use the RS256 algorithm - The JWT `kid` header MUST match a key in the provider's JWKS endpoint - The JWT MUST pass claim validation (issuer, audience, expiry) - The venue extracts the `email` claim (preferred) or `sub` claim as the caller identity **Verification process:** 1. Parse the JWT and check the algorithm is RS256 2. Extract the `kid` from the header 3. For each configured OAuth provider with a JWKS endpoint: a. Fetch the provider's JWKS and find the key matching `kid` b. Verify the RS256 signature c. Validate claims: issuer matches provider, audience matches `clientId`, token not expired 4. If verification succeeds, extract the caller identity from `email` or `sub` ### Anonymous Access When `auth.public.enabled` is `true` (the default), requests without a valid bearer token are allowed through without a caller identity. The caller DID is `null` for anonymous requests. Anonymous access is suitable for: - Development and testing - Public read-only APIs - Venues that implement their own access control at the application layer When `auth.public.enabled` is `false`, all requests MUST include a valid bearer token. Requests without a token or with an invalid token receive a `401` response. #### Public Capability Ceiling Anonymous requests do not run unconstrained: the venue assigns the anonymous caller class a capability scope — the **public capability ceiling** — evaluated by the ordinary capability machinery ([COG-13](./COG-013)). The ceiling is configured with `auth.public.caps`: | `auth.public.caps` | Effect | |--------------------|--------| | *(absent — the default)* | A secure read-only profile: `crud/read` scoped to the venue's public identity (`:public`) plus unscoped `asset/read`. No `invoke`, agent, or mutating ability is granted, so every mutating operation — including operation invocation — is denied to anonymous callers. | | `"unrestricted"` | No ceiling — anonymous callers are unscoped (the legacy permissive behaviour). | | An explicit capability array | Exactly that scope. | A malformed value MUST fail closed to the default read-only profile. The ceiling applies **only** to anonymous callers: any authenticated identity — including a self-issued `did:key` caller or a relayed identity-token caller ([COG-3](./COG-003) §6) — has the standing of its own capabilities, and the public ceiling does not apply. ### OAuth Login Venues MAY configure OAuth 2.0 Authorization Code flow providers to enable browser-based login. The following providers are supported: #### Google | Setting | Value | |---------|-------| | Authorization endpoint | `https://accounts.google.com/o/oauth2/v2/auth` | | Token endpoint | `https://oauth2.googleapis.com/token` | | JWKS URI | `https://www.googleapis.com/oauth2/v3/certs` | | Issuer | `https://accounts.google.com` | | Scopes | `openid email profile` | Google returns an OpenID Connect ID token. The venue verifies it via JWKS and extracts user identity. #### Microsoft | Setting | Value | |---------|-------| | Authorization endpoint | `https://login.microsoftonline.com/common/oauth2/v2.0/authorize` | | Token endpoint | `https://login.microsoftonline.com/common/oauth2/v2.0/token` | | JWKS URI | `https://login.microsoftonline.com/common/discovery/v2.0/keys` | | Issuer | Varies by tenant | | Scopes | `openid email profile` | Microsoft uses the common (multi-tenant) endpoint. The issuer varies by Azure AD tenant, so issuer validation is skipped. #### GitHub | Setting | Value | |---------|-------| | Authorization endpoint | `https://github.com/login/oauth/authorize` | | Token endpoint | `https://github.com/login/oauth/access_token` | | User info endpoint | `https://api.github.com/user` | | JWKS URI | N/A (GitHub does not issue JWTs) | | Scopes | `user:email read:user` | GitHub uses standard OAuth 2.0 (not OpenID Connect). After token exchange, the venue fetches user information from GitHub's API. #### OAuth Callback Flow 1. The user visits `/auth/{provider}` and is redirected to the provider's authorisation page 2. After consent, the provider redirects to `/auth/{provider}/callback` with an authorisation code 3. The venue exchanges the code for tokens at the provider's token endpoint 4. The venue extracts user identity (email, name, subject) from the ID token or user info endpoint 5. The venue creates or updates the user record in its user database 6. The venue issues a venue-signed JWT and returns it to the client #### Redirect URIs OAuth redirect URIs are constructed from the venue's base URL: ``` {baseUrl}/auth/{provider}/callback ``` The base URL is derived from the venue's `hostname` and `port` configuration, or can be set explicitly with the `baseUrl` venue configuration key. The redirect URI MUST be registered in the provider's developer console. ### User Identity After OAuth login, users are assigned a venue-managed DID formed from the venue's `did:web` identity ([COG-2](./COG-002)): ``` did:web:{venue-host}:u:{user-id} ``` This is a standard `did:web` DID — the colon-separated segments are the method's path encoding — and it resolves to a DID document the venue serves at `https://{venue-host}/u/{user-id}/did.json`. The venue is the controller of the DID and signs assertions about the user with its own key pair. A public venue hostname is required. The user ID is derived from the email address by replacing `.` and `@` with `_` (e.g. `alice@gmail.com` becomes `alice_gmail_com`). When no email is available, the OAuth `sub` claim is used directly. User IDs MUST match `[A-Za-z0-9._-]+`. User records are stored in the venue's user database with fields including `did`, `email`, `name`, `provider`, and `updated` timestamp. ### Configuration Authentication is configured in the venue configuration file under the `auth` key: ```json5 "auth": { // Allow unauthenticated access (default: true) "public": { "enabled": true }, // add "caps": [...] to widen the public capability ceiling // JWT token expiry in seconds (default: 86400 = 24 hours) "tokenExpiry": 86400, // OAuth login providers "oauth": { "google": { "clientId": "...", "clientSecret": "..." }, "microsoft": { "clientId": "...", "clientSecret": "..." }, "github": { "clientId": "...", "clientSecret": "..." } } } ``` All fields are optional. A venue with no `auth` configuration allows anonymous access and has no login providers. ## Security Considerations ### Token Security - Bearer tokens SHOULD be transmitted only over HTTPS in production - Venue-signed JWTs SHOULD use short expiry times appropriate to the deployment context - Client secrets for OAuth providers MUST NOT be committed to version control or exposed in client-side code ### Self-Issued Token Trust Self-issued EdDSA tokens prove that the caller controls a key pair, but do not establish any real-world identity. Venues that require identity assurance SHOULD use OAuth login and restrict access to venue-signed tokens. ### JWKS Caching The venue caches JWKS responses from external providers to avoid excessive network requests. Cached keys are refreshed periodically or when a `kid` is not found in the cache. ### Anonymous Access When public access is enabled, the venue MUST NOT assume that anonymous requests are trustworthy. Application-level access control SHOULD be applied to restrict what anonymous callers can do. ## Related Specifications - **[COG-1: Architecture](./COG-001)** - Overall Grid architecture and terminology - **[COG-2: Decentralised ID](./COG-002)** - DID scheme and resolution - **[COG-9: Agent Messaging](./COG-009)** - Message authentication requirements - **[RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)** - JSON Web Token (JWT) - **[RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)** - JSON Web Key (JWK) - **[RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749)** - OAuth 2.0 Authorization Framework - **[W3C DID Core](https://www.w3.org/TR/did-core/)** - Decentralized Identifiers --- ## COG-11: Agent Lifecycle ``` Status: Exploratory Draft Version: 0.1 Created: 2026-02-27 Authors: Mike Anderson ``` :::caution[Exploratory Draft] This specification is an **exploratory draft** describing agent lifecycle patterns that are under active development. The concepts, interfaces, and behaviours described here are subject to significant change as the design evolves through implementation experience and community feedback. For the stable foundations that this specification builds on, see [COG-8: Jobs](./COG-008) (job lifecycle and state chains) and [COG-9: Agent Messaging](./COG-009) (message delivery and processing). ::: :::warning[Implementation has moved on] The single-`inbox` run-loop model described below predates the current implementation, which organises intake around **sessions** (per-conversation threads) rather than one agent-level inbox. For how agents actually work today — sessions, the one-session-per-cycle run loop, and task/chat Jobs — see the user-guide [Sessions](../../user-guide/agents/sessions) and [Agent Operations](../../user-guide/agents/operations) pages. This COG will be revised to match. ::: This standard specifies the **agent lifecycle** on the Covia Grid — how stateful, persistent agents are created, how they process messages through a run loop, how they transition between states, and how they terminate. It builds on the job-as-agent model introduced in [COG-8](./COG-008) and the messaging protocol defined in [COG-9](./COG-009). ## Purpose [COG-8](./COG-008) establishes that an agent is simply a job whose transition function never reaches a terminal state — a persistent, stateful process that accepts repeated interactions. [COG-9](./COG-009) defines how messages are delivered to such processes. This specification completes the agent model by defining: - **Agent state**: The data structure that persists between interactions - **Agent lifecycle**: The states an agent moves through from creation to termination - **Run loop**: How agents process messages and produce new state - **Transition functions**: The pluggable operations that define agent behaviour - **Three-level architecture**: How framework, domain logic, and external calls are separated ## Terminology See [COG-1: Architecture](./COG-001) for core Grid terminology and [COG-8: Jobs](./COG-008) for job-related terms. Additional terms used in this specification: | Term | Definition | | ---- | ---------- | | Agent | A persistent, stateful process on the Grid that accepts repeated interactions via message delivery | | Agent Record | The complete state of an agent at a point in time — a single atomic value | | Transition Function | An [Operation](./COG-007) that receives the agent's current state and new messages, and returns updated state | | Run Loop | The cycle of reading the inbox, invoking the transition function, and recording the result | | Timeline | An append-only log of successful transition records, providing an audit trail of the agent's history | ## Core Principles ### Single Atomic Value An agent's entire state is one map. Every operation — create, message delivery, run, configuration update — atomically replaces the whole map. There is no per-field merge and no child lattices within the agent record. The map is the unit of state. This simplifies reasoning about agent state: at any point, the agent record is a consistent snapshot that genuinely existed, never a mixture of fields from different points in time. ### Last Writer Wins Agent state uses last-writer-wins (LWW) merge semantics based on a timestamp field. The record with the later timestamp wins unconditionally. Since all writes to an agent are serialised on the hosting venue, timestamps are monotonic and state only advances. ### Separation of Concerns The agent system separates responsibilities into three levels, each of which is a pluggable Grid operation: 1. **Agent Update** (framework) — manages the run loop, inbox, timeline, and status transitions 2. **Agent Transition** (domain logic) — processes messages and produces updated state 3. **External Call** (single step) — makes a single stateless call to an external service (e.g. an LLM API) Each level invokes the next as a standard Grid operation. This means any level can be a local operation, a remote venue operation via federation, or a test mock. ## Specification ### Agent Record An agent's value is a plain map with the following fields: | Field | Type | Description | |-------|------|-------------| | `ts` | long | Timestamp of the last write. The merge discriminator — later `ts` always wins. | | `status` | string | Current lifecycle status (see below) | | `config` | map | Framework-level configuration. Opaque to the transition function. | | `state` | any | User-defined state. Opaque to the framework. Passed to and returned from the transition function. | | `inbox` | vector | Messages awaiting processing. Drained on successful run. | | `timeline` | vector | Append-only log of transition records. Grows with each successful agent run. | | `caps` | map | Capability sets (reserved for future capability enforcement) | | `error` | string? | Last error message, or null | The framework manages all fields except `state`, which is owned by the transition function. The transition function never manages framework fields; the framework never inspects `state`. ### Agent Lifecycle Agents have a lifecycle that is distinct from, but related to, the job lifecycle defined in [COG-8](./COG-008): ``` create │ ▼ ┌───────────┐ │ SLEEPING │◀─────────── successful run └───────────┘ │ run (inbox non-empty) │ ▼ ┌───────────┐ │ RUNNING │ └───────────┘ │ ┌────┴────┐ │ │ success failure │ │ ▼ ▼ SLEEPING ┌───────────┐ │ SUSPENDED │ └───────────┘ │ clear error │ ▼ SLEEPING From any non-terminal state: │ terminate │ ▼ ┌────────────┐ │ TERMINATED │ └────────────┘ ``` #### Status Values | Status | Description | |--------|-------------| | `SLEEPING` | Agent is idle, ready to process messages when the run loop is triggered | | `RUNNING` | Agent is actively executing its transition function | | `SUSPENDED` | Agent is paused due to an error in the transition function. Inbox is preserved for retry. | | `TERMINATED` | Agent has been permanently stopped. No further messages are accepted. | #### Status Transitions | From | To | Trigger | |------|----|---------| | (none) | `SLEEPING` | Agent created | | `SLEEPING` | `RUNNING` | Run loop triggered with non-empty inbox | | `RUNNING` | `SLEEPING` | Transition function succeeds | | `RUNNING` | `SUSPENDED` | Transition function fails | | `SUSPENDED` | `SLEEPING` | Error cleared (manual recovery) | | Any non-terminal | `TERMINATED` | Explicit termination | ### Operations Every operation atomically replaces the agent record with a new timestamp. #### Create Creates the initial agent record with status `SLEEPING`, empty inbox, empty timeline, and optional initial state. The initial state allows the creator to seed transition-function-specific configuration (e.g. LLM provider, model, system prompt) that the transition function reads and preserves across runs. Framework `config` is kept separate from transition function concerns. **Idempotent:** If the agent record already exists, create is a no-op. #### Message Delivery Reads the current agent record, appends the message to the inbox, and writes the updated record. The agent is not automatically woken — the message sits in the inbox until the next run. Messages MUST be rejected if the agent does not exist or if the agent status is `TERMINATED`. #### Run Loop The run loop is the core mechanism for processing messages. When triggered: 1. Read the current agent record 2. If the inbox is empty, no-op 3. Set status to `RUNNING`, write the agent record 4. Invoke the transition function with the agent ID, current state, and inbox 5. **On success:** - Update `state` from the returned value - Append a timeline entry (transition operation, starting state, messages processed, returned result, start/end timestamps) - Clear the inbox - Set status to `SLEEPING` - Write the agent record 6. **On error:** - Leave `state` and `inbox` unchanged - Set status to `SUSPENDED`, set `error` - Write the agent record The run loop writes twice: once to mark running (step 3), once to record the outcome (step 5 or 6). Each write is a complete, atomic agent record replacement. On error, the inbox is preserved — the same messages are available for retry after the error is resolved and the agent is resumed. #### Timeline Entry Each entry in the timeline records one successful run: | Field | Type | Description | |-------|------|-------------| | `start` | long | Timestamp when the run started | | `end` | long | Timestamp when the run completed | | `op` | string | The operation reference used for the transition function | | `state` | any | The starting state passed to the transition function | | `messages` | vector | The inbox messages passed to the transition function | | `result` | any | The result returned by the transition function | The output state is not stored in the timeline entry — it is the `state` field in the agent record (for the latest run) or the `state` field in the next timeline entry (for earlier runs). This avoids redundant storage. Timeline entries are only written on success. On error, no timeline entry is created. ### Transition Function Contract The transition function is a standard Grid [Operation](./COG-007) with the following contract: **Input:** | Field | Type | Description | |-------|------|-------------| | `agent-id` | string | The agent's identifier | | `state` | any | Current user-defined state from the agent record. Null on first run. | | `messages` | vector | The inbox messages to process | **Output:** | Field | Type | Description | |-------|------|-------------| | `state` | any | Updated user-defined state. Written back to the agent record. | | `result` | any | Summary of the transition outcome. Recorded in the timeline. | The transition function does not manage timestamps, status, timeline, or inbox — it is a pure function from `(state, messages)` to `(state, result)`. The transition function MUST handle its own errors internally. If it throws an unhandled exception, the framework treats this as a severe failure: the agent is suspended, the inbox is preserved, and the error is recorded. No timeline entry is written. ### Three-Level Architecture The agent system separates concerns into three levels. Each level is a Grid operation, invokable locally, remotely, or via orchestration: ``` Level 1: Agent Update (framework — manages run loop) │ reads inbox, invokes level 2, writes timeline and status │ ▼ Level 2: Agent Transition (domain logic — manages state) │ processes messages, maintains conversation/workflow state │ invokes level 3 for external calls │ ▼ Level 3: External Call (single step — stateless) makes one external request (LLM API, HTTP, etc.) returns structured response ``` **Level 1** is the same for every agent — it is the run loop defined above. It owns the agent record and invokes level 2 as a Grid operation. **Level 2** is the pluggable part. Different agents use different transition functions: an LLM-backed conversation agent, a rule engine, a workflow coordinator, or custom logic. Level 2 receives current state and messages, returns updated state and a result summary. **Level 3** is a standard Grid operation that makes a single external call. For LLM agents, this is an LLM inference operation. Level 3 knows about API serialisation, authentication, and provider-specific details. It does not know about agents, conversation history, or the run loop. The level 2 operation is specified by the caller when triggering the run loop. The level 3 operation is specified by the agent creator in the agent's initial state configuration. ### Credential Access Operations that need API keys or other secrets resolve them from two sources, in priority order: 1. **User's secret store** — an encrypted per-user credential store, using the secret name declared in the operation's metadata 2. **Input parameter** — an optional plaintext field for testing only The agent's configuration does not contain API keys. The operation metadata declares which secret it needs, and the runtime resolves it from the caller's secret store. This keeps agent configuration clean and credentials in encrypted storage. ## Relationship to Jobs The agent lifecycle defined here operates **within** the job lifecycle defined in [COG-8](./COG-008). Specifically: - An agent is created as part of a job invocation - The agent's run loop is triggered by job operations - Agent status (`SLEEPING`, `RUNNING`, `SUSPENDED`, `TERMINATED`) describes the agent's internal state, while job status (`PENDING`, `STARTED`, `INPUT_REQUIRED`, `COMPLETE`, etc.) describes the job's external state as seen by clients - Messages delivered via [COG-9](./COG-009) endpoints reach the agent's inbox through the job message queue The mapping between agent status and job status is implementation-defined. A typical mapping would be: | Agent Status | Job Status | Meaning | |-------------|-----------|---------| | `SLEEPING` | `INPUT_REQUIRED` | Agent is idle, awaiting next message | | `RUNNING` | `STARTED` | Agent is processing messages | | `SUSPENDED` | `FAILED` or `PAUSED` | Agent encountered an error | | `TERMINATED` | `COMPLETE` | Agent has finished | ## Merge Semantics Agent records use LWW merge semantics at the lattice level. The record with the later `ts` wins unconditionally. This is correct because: - **Monotonic timestamps**: All writes to an agent are serialised on a single venue, so `ts` is monotonically increasing - **Atomic snapshots**: The winner is always a complete, consistent state that genuinely existed — never a mixture of fields from different writes - **Simple replication**: Cross-venue sync is replication. The hosting venue always has the latest `ts`. Replicas receive the complete state. ## Security Considerations ### Resource Limits Venues SHOULD impose limits on: - **Agent count** per user — to prevent resource exhaustion - **Inbox size** — to prevent memory exhaustion from rapid message delivery - **Timeline size** — to bound the storage cost of long-running agents - **Agent lifetime** — to prevent indefinitely dormant agents from consuming resources ### Credential Security Agent state and timeline entries may indirectly reference sensitive information. Venues SHOULD: - Never store plaintext credentials in the agent record - Use an encrypted per-user secret store for API keys and other credentials - Redact sensitive fields in timeline entries where possible ### Message Validation The transition function receives arbitrary messages from the inbox. Implementations SHOULD: - Validate message format before processing - Handle malformed or malicious messages gracefully (return an error state rather than crashing) - Apply rate limits on message delivery per agent ### Transition Function Safety A failing transition function suspends the agent, preserving the inbox for retry. This design prevents message loss but means a pathological transition function could be repeatedly retried. Venues SHOULD: - Track consecutive failures and escalate (e.g. terminate after N failures) - Apply timeouts to individual transition function invocations - Log transition function errors for operator review ## Agent Workspace Agents have access to persistent, user-scoped storage namespaces via the `covia` adapter operations. These are standard default tools available to all LLM-backed agents. ### Namespaces - **`/w/` (workspace)** — general-purpose data storage for agent knowledge, state, logs, and working data - **`/o/` (operations)** — user-defined operation definitions - **`/h/` (HITL)** — reserved for human-in-the-loop requests (Phase D) All namespaces are readable via `covia:read`, `covia:list`, and `covia:slice`. The `/w/` and `/o/` namespaces are writable via `covia:write`, `covia:delete`, and `covia:append`. Other namespaces (`/g/`, `/s/`, `/j/`) are framework-managed and reject direct writes. ### Operations | Operation | Description | |-----------|-------------| | `covia:read` | Read a value at any lattice path. Supports `maxSize` guard. | | `covia:write` | Write a value to `/w/` or `/o/` at any depth. Creates intermediate maps. | | `covia:delete` | Delete a key from `/w/` or `/o/` at any depth. | | `covia:append` | Append an element to a vector in `/w/` or `/o/`. Creates vector if absent. | | `covia:slice` | Read a paginated slice from a collection (vector, map, or set). | | `covia:list` | Describe structure at a path: type, count, keys for maps. | Paths support mixed map/vector navigation — e.g. `w/records/1/name` navigates into a map, then a vector by index, then a map field. See [COG-4: Grid Lattice](./COG-004) for the per-user namespace structure and merge semantics. ## Future Directions The following capabilities are anticipated but not yet specified: - **Cross-user workspace reads** — agents reading another user's `/w/` namespace, gated by capability delegation (see [COG-13: Agent Capabilities](./COG-013)) - **HITL requests** — the `/h/` namespace for human-in-the-loop interaction patterns - **Cross-user messaging** — agents sending messages to agents owned by different users - **Agent forking** — creating a copy of an agent's state for branching conversations or experiments - **Cross-venue migration** — transferring an agent's state from one venue to another - **Wake triggers** — automatic run loop invocation on message delivery, timer events, or external signals - **Recovery** — handling agents left in `RUNNING` status after a venue restart ## Related Specifications - **[COG-1: Architecture](./COG-001)** — Overall Grid architecture and terminology - **[COG-7: Operations](./COG-007)** — Operation definitions, adapter types, and invocation model - **[COG-8: Jobs](./COG-008)** — Job lifecycle, state chains, and the job-as-agent model - **[COG-9: Agent Messaging](./COG-009)** — Message delivery to jobs and agents, protocol compatibility - **[COG-13: Agent Capabilities](./COG-013)** — Capability model, UCAN delegation, and enforcement --- ## COG-12: Orchestrations ``` Status: Draft (Work in Progress) Version: 0.2 Created: 2026-04-01 Updated: 2026-04-03 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: This standard specifies **Orchestrations** — multi-step workflow operations on the Covia Grid that compose other operations with automatic dependency resolution, parallel execution, and result aggregation. ## Purpose Orchestrations enable complex workflows to be defined declaratively as a single asset: - **Composition**: Chain multiple operations into a pipeline with data flowing between steps - **Parallelism**: Independent steps execute concurrently without explicit threading - **Determinism**: The same orchestration definition always produces the same execution graph - **Auditability**: The entire workflow runs as a single job with per-step status tracking - **Federation**: Individual steps can execute on different venues across the Grid Orchestrations transform isolated operations into coordinated workflows — from simple two-step chains to complex multi-agent pipelines with parallel fan-out and sequential dependencies. ## Terminology See [COG-1: Architecture](./COG-001) for definitions of Grid terminology. See [COG-7: Operations](./COG-007) for the operation metadata format and [COG-8: Jobs](./COG-008) for job lifecycle. An **Orchestration** is an Operation (per [COG-7](./COG-007)) whose adapter is `orchestrator`. It contains a `steps` array defining the workflow and a `result` specification defining the final output. ## Core Principles ### Declarative Workflows Orchestrations are defined as immutable JSON metadata — not imperative code. The orchestrator adapter interprets the definition at runtime: - **No control flow**: No loops, conditionals, or branching. Each step runs exactly once. - **Data-driven dependencies**: Steps declare what data they need; the orchestrator infers execution order. - **Static analysis**: The dependency graph can be computed before execution begins. This declarative model ensures reproducibility and makes orchestrations safe to store, share, and federate. ### Automatic Dependency Resolution The orchestrator scans all input specifications recursively at construction time to build a dependency graph: - A step that references output from step N depends on step N. - A step with no step references has no dependencies and is immediately eligible for execution. - Dependencies are transitive — if step 2 depends on step 1, and step 3 depends on step 2, step 3 waits for both. This eliminates manual dependency declaration. The data flow **is** the dependency graph. ### Parallel by Default Steps without mutual dependencies execute concurrently: ``` Step 0: covia:read (no deps) ─┐ Step 1: covia:read (no deps) ─┤── run in parallel Step 2: http:get (no deps) ─┘ Step 3: agent:request (deps: 0, 1, 2) ── waits for all three ``` The orchestrator uses virtual threads for concurrent step execution — parallelism is automatic and lightweight. ### Immutable Definitions Like all assets ([COG-5](./COG-005)), orchestration metadata is immutable and content-addressed. The Asset ID is the Value ID (SHA3-256) of the metadata. This ensures: - **Reproducibility**: Invoking an orchestration by Asset ID guarantees the same workflow definition - **Verifiability**: The definition can be inspected before execution - **Safe sharing**: Orchestrations can be federated across venues without modification ## Specification ### Metadata Format An orchestration is an Operation asset ([COG-7](./COG-007)) with `adapter` set to `"orchestrator"`: ```json { "name": "My Pipeline", "description": "What this orchestration does", "type": "orchestration", "operation": { "adapter": "orchestrator", "input": { "type": "object", "properties": { ... }, "required": [ ... ] }, "steps": [ ... ], "result": { ... } } } ``` #### Required Fields | Field | Type | Description | |-------|------|-------------| | `operation.adapter` | string | MUST be `"orchestrator"` | | `operation.steps` | array | Ordered array of step definitions | | `operation.result` | object or array | Output assembly specification | #### Recommended Fields | Field | Type | Description | |-------|------|-------------| | `name` | string | Human-readable name | | `description` | string | Purpose and usage | | `type` | string | SHOULD be `"orchestration"` for discoverability | | `operation.input` | object | JSON Schema for orchestration input | | `operation.output` | object | JSON Schema for orchestration output | ### Step Definition Each element in the `steps` array MUST be an object with the following fields: | Field | Required | Type | Description | |-------|----------|------|-------------| | `op` | Yes | string | Operation identifier — adapter reference, Asset ID, or registered name | | `input` | Yes | spec | Input specification (see [Input Mapping](#input-mapping)) | | `name` | No | string | Human-readable step name | | `venue` | No | string | Remote venue DID or URL for federated execution | | `foreach` | No | object | Fan the step out over a collection (see [Foreach Steps](#foreach-steps)) | ```json { "op": "v/ops/agent/request", "name": "Extract Invoice", "input": { "agentId": ["const", "Alice"], "input": ["input", "invoice_text"], "timeout": ["const", 60000] } } ``` ### Input Mapping Input specifications define where each value comes from. They are evaluated recursively at step execution time. #### Source Types ##### Orchestration Input — `["input", ...]` References values from the orchestration's own input: | Syntax | Resolves to | |--------|-------------| | `["input"]` | Entire orchestration input | | `["input", "field"]` | `input.field` | | `["input", "config", "model"]` | `input.config.model` | ##### Constants — `["const", value]` Literal values embedded in the definition: | Syntax | Resolves to | |--------|-------------| | `["const", "Alice"]` | String `"Alice"` | | `["const", true]` | Boolean `true` | | `["const", {"key": "val"}]` | Object `{"key": "val"}` | ##### Step Output — `[index, ...]` References output from a previously completed step by zero-based index. **This creates a dependency.** | Syntax | Resolves to | |--------|-------------| | `[0]` | Entire output of step 0 | | `[0, "body"]` | Field `body` within step 0's output | | `[1, "result", "name"]` | `result.name` within step 1's output | The path is resolved **within the step's output** — `[0, "body"]` is the `body` field of whatever step 0 returned. ##### Concatenation — `["concat", spec, ...]` Concatenates the string representation of two or more resolved input specs. Each element is resolved recursively — arrays are mapping expressions, while any non-array value (string, number, boolean) is a literal — converted to a string, and joined. `["concat", "docs/", [0, "name"]]` is therefore equivalent to the fully-wrapped `["concat", ["const", "docs/"], [0, "name"]]`: | Syntax | Resolves to | |--------|-------------| | `["concat", ["const", "w/enrichments/"], ["input", "id"]]` | `"w/enrichments/INV-001"` (if input.id = "INV-001") | | `["concat", ["const", "docs/"], [0, "name"], ["const", ".txt"]]` | `"docs/report.txt"` (if step 0 output.name = "report") | This is essential for building dynamic paths — for example, writing agent results to per-record workspace locations: ```json { "op": "v/ops/covia/write", "input": { "path": ["concat", ["const", "w/enrichments/"], ["input", "invoiceId"]], "value": [0, "output"] } } ``` Concatenation specs that reference step outputs **create dependencies** — the orchestrator recursively scans concat elements for step index references during dependency resolution. ##### Iteration Bindings — `["item", ...]` and `["index"]` Valid **only inside a foreach step's `input`** (see [Foreach Steps](#foreach-steps)): | Syntax | Resolves to | |--------|-------------| | `["item"]` | The current element of the foreach collection | | `["item", "field"]` | A path within the current element | | `["index"]` | The element's zero-based position (takes no path) | Using either binding outside a foreach step is an error. ### Foreach Steps A step with a `foreach` object executes its operation **once per element** of a collection, as concurrent child jobs: ```json { "op": "v/ops/langchain/openai", "name": "Classify each record", "foreach": { "in": [0, "records"], "maxConcurrency": 4 }, "input": { "prompt": ["concat", "Classify: ", ["item", "text"]], "model": ["const", "gpt-5.4-mini"] } } ``` | `foreach` field | Required | Description | |-----------------|----------|-------------| | `in` | Yes | An input-mapping expression resolving to the collection. Arrays iterate element-wise; maps and indexes iterate as `[key, value]` entry pairs. | | `maxConcurrency` | No | Positive integer bounding concurrent iterations. MUST NOT exceed the venue's configured cap. | Semantics: - A single snapshot of the resolved collection fixes the iteration order and the output order. - For each element the step's `input` is resolved with the `["item", ...]` and `["index"]` bindings available; each iteration invokes `op` as its own child job. - The step's output is an **ordered array of the per-element outputs**, in source order; downstream steps consume it like any step output. An empty collection completes immediately with `[]`. - On the first failing iteration the orchestrator stops admitting new iterations (in-flight ones run to completion) and the step FAILS, naming the failed element's index. Cancellation of the orchestration likewise stops admission. - Venues MAY bound the collection size and the maximum concurrency; a `foreach` exceeding either limit is rejected before any iteration starts. - References inside `foreach.in` and the step `input` create dependencies exactly as for ordinary steps. #### Composition Input specs can be nested in maps to build structured inputs: ```json { "agentId": ["const", "Bob"], "input": { "extraction": [0, "output"], "metadata": ["input", "metadata"] }, "timeout": ["const", 60000] } ``` Each leaf value in the map is resolved independently. The resulting map is passed as the step's input. #### Constraints - Step output references MUST refer to steps with a **lower index** (no forward references, no self-references). - Implementations MUST reject orchestrations with invalid step references at construction time. ### Result Specification The `result` field defines how the orchestration's final output is assembled from step outputs. It uses the same reference syntax as input specifications: ```json { "result": { "extraction": [0, "output"], "enrichment": [1, "output"], "decision": [2, "output"], "original_input": ["input", "invoice_text"] } } ``` The result specification is evaluated after all steps have completed. It MAY reference any step's output or the orchestration input. ### Dependency Resolution Algorithm 1. **Construction**: For each step, recursively scan its input specification for step index references. Collect all referenced indices as the step's dependency set. 2. **Validation**: Verify all step references point to lower indices. Reject the orchestration if any reference is invalid. 3. **Scheduling**: Maintain a set of pending steps. In each iteration: a. Find all pending steps whose dependency sets are empty (all dependencies satisfied). b. Start those steps concurrently. c. Wait for at least one step to complete. d. Remove the completed step's index from the dependency sets of all subsequent steps. e. Repeat until all steps are complete or a step fails. This algorithm guarantees maximum parallelism while respecting data dependencies. ### Execution Model Orchestrations execute as a single Job ([COG-8](./COG-008)): 1. **Job creation**: A new job is created for the orchestration, following the standard lifecycle ([COG-8](./COG-008)): `PENDING` at creation, advancing to `STARTED` when the orchestrator begins executing steps. 2. **Step execution**: Each step invokes its operation as a sub-job. The orchestrator manages the lifecycle of all sub-jobs. 3. **Progress tracking**: The orchestration job's data includes a `steps` array containing per-step job records (status, output, timing). 4. **Result assembly**: Once all steps complete, the `result` specification is evaluated and the orchestration job completes with the assembled output. 5. **Error propagation**: If any step fails, the orchestration job fails with a reference to the failed step. #### Step Status Tracking The orchestration job record includes a `steps` field — a vector of per-step job data: ```json { "status": "COMPLETE", "output": { "extraction": {...}, "enrichment": {...}, "decision": {...} }, "steps": [ { "op": "v/ops/agent/request", "status": "COMPLETE", "output": {...} }, { "op": "v/ops/agent/request", "status": "COMPLETE", "output": {...} }, { "op": "v/ops/agent/request", "status": "COMPLETE", "output": {...} } ] } ``` This enables inspection of individual step outcomes for debugging and auditing. ### Federated Execution Steps MAY include a `venue` field to route execution to a remote venue: ```json { "op": "v/ops/agent/request", "input": { ... }, "venue": "did:web:remote-venue.example.com" } ``` When a `venue` field is present: 1. The orchestrator connects to the remote venue via the Grid protocol. 2. The step's operation is invoked on the remote venue with the resolved input. 3. The result is returned to the local orchestrator and made available to dependent steps. Federated steps participate in the same dependency graph as local steps. The orchestrator handles cross-venue communication transparently. ## Examples ### Simple Two-Step Pipeline Fetch data and process it: ```json { "name": "Fetch and Process", "type": "orchestration", "operation": { "adapter": "orchestrator", "steps": [ { "op": "v/ops/http/get", "name": "Fetch", "input": { "url": ["input", "url"] } }, { "op": "v/ops/langchain/openai", "name": "Summarise", "input": { "messages": ["const", [{"role": "user", "content": "Summarise this data"}]], "model": ["const", "gpt-5.4-mini"] } } ], "result": { "raw": [0, "body"], "summary": [1, "content"] } } } ``` Steps 0 and 1 have no mutual dependency — they run in parallel. ### Parallel Fan-Out Read multiple data sources concurrently, then aggregate: ```json { "name": "Multi-Source Aggregation", "type": "orchestration", "operation": { "adapter": "orchestrator", "steps": [ { "op": "v/ops/covia/read", "input": { "path": ["const", "w/vendors"] } }, { "op": "v/ops/covia/read", "input": { "path": ["const", "w/orders"] } }, { "op": "v/ops/covia/read", "input": { "path": ["const", "w/invoices"] } }, { "op": "v/ops/agent/request", "input": { "agentId": ["const", "Analyser"], "input": { "vendors": [0, "value"], "orders": [1, "value"], "invoices": [2, "value"] }, "timeout": ["const", 60000] } } ], "result": { "analysis": [3, "output"] } } } ``` Steps 0-2 run in parallel. Step 3 waits for all three. ### Agent Pipeline (AP Invoice Demo) Three-agent pipeline where each agent processes the previous agent's structured output: ```json { "name": "AP Invoice Pipeline", "type": "orchestration", "description": "Alice extracts, Bob enriches, Carol approves", "operation": { "adapter": "orchestrator", "input": { "type": "object", "properties": { "invoice_text": { "type": "string", "description": "Raw invoice text" } }, "required": ["invoice_text"] }, "steps": [ { "op": "v/ops/agent/request", "name": "Alice — Extract", "input": { "agentId": ["const", "Alice"], "input": ["input"], "timeout": ["const", 60000] } }, { "op": "v/ops/agent/request", "name": "Bob — Enrich", "input": { "agentId": ["const", "Bob"], "input": { "source_agent": ["const", "Alice"], "extraction": [0, "output"] }, "timeout": ["const", 60000] } }, { "op": "v/ops/agent/request", "name": "Carol — Approve", "input": { "agentId": ["const", "Carol"], "input": { "source_pipeline": ["const", "Alice -> Bob -> Carol"], "extraction": [0, "output"], "enrichment": [1, "output"] }, "timeout": ["const", 60000] } } ], "result": { "extraction": [0, "output"], "enrichment": [1, "output"], "decision": [2, "output"] } } } ``` Strictly sequential — each step depends on the previous. The result contains all three stages for a complete audit trail. ### Federated Pipeline Execute steps across multiple venues: ```json { "name": "Cross-Venue Pipeline", "type": "orchestration", "operation": { "adapter": "orchestrator", "steps": [ { "op": "v/ops/agent/request", "input": { "agentId": ["const", "Scanner"], "input": ["input"], "timeout": ["const", 60000] } }, { "op": "v/ops/agent/request", "input": { "agentId": ["const", "Validator"], "input": [0, "output"], "timeout": ["const", 60000] }, "venue": "did:web:compliance.example.com" } ], "result": { "scanned": [0, "output"], "validated": [1, "output"] } } } ``` Step 0 executes locally. Step 1 executes on a remote compliance venue. ## Error Handling ### Step Failure If any step's sub-job finishes with a non-`COMPLETE` status (e.g. `FAILED`, `CANCELLED`): 1. The orchestration job MUST fail. 2. The error message SHOULD identify the failed step by index. 3. Steps that have not yet started SHOULD be skipped. 4. Steps that are already running MAY continue to completion but their results are not used. ### Timeout Behaviour Orchestrations inherit the timeout behaviour of the enclosing job ([COG-8](./COG-008)). Individual steps may have their own timeouts based on the operation they invoke. The orchestrator does not impose additional timeout constraints beyond what the sub-jobs enforce. ### Invalid Definitions Implementations MUST reject orchestration definitions that: - Contain forward references (step N referencing step M where M >= N) - Contain self-references - Have an empty `steps` array - Reference non-existent operations in `op` fields Rejection SHOULD occur at construction time (when the orchestration job starts), not during step execution. ## Security Considerations ### Resource Limits Orchestrations can amplify resource consumption: - Each step is a separate job invocation with its own resource usage - Parallel steps multiply concurrent resource consumption - Nested orchestrations (an orchestration step invoking another orchestration) create recursive resource amplification Venues SHOULD impose limits on: - Maximum number of steps per orchestration - Maximum orchestration nesting depth - Total concurrent sub-jobs per orchestration ### Federated Trust When steps execute on remote venues: - The local venue trusts the remote venue to execute the operation correctly - Step outputs from remote venues are not independently verified - Sensitive data in step inputs is transmitted to remote venues Operators should consider the trust implications of federated steps, especially for orchestrations that handle credentials, personal data, or financial information. ### Input Injection Step inputs are assembled from orchestration inputs, constants, and prior step outputs. Implementations SHOULD validate assembled inputs against the target operation's schema before invocation to prevent injection through crafted orchestration inputs. ## Related Specifications - **[COG-1: Architecture](./COG-001)** — Overall Grid architecture - **[COG-5: Asset Metadata](./COG-005)** — Asset metadata format and ID computation - **[COG-7: Operations](./COG-007)** — Operation metadata, adapter model, orchestration basics - **[COG-8: Jobs](./COG-008)** — Job lifecycle, status transitions, observation - **[COG-11: Agent Lifecycle](./COG-011)** — Agent operations used as orchestration steps --- ## COG-13: Agent Capabilities ``` Status: Draft (Work in Progress) Version: 0.1 Created: 2026-04-12 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: This standard specifies the **capability model** for agent authorisation on the Covia Grid — how fine-grained access to operations and resources is granted to agents, attenuated through delegation, disclosed in agent context, and enforced at runtime. This COG supersedes the "Capability enforcement" future direction in [COG-11: Agent Lifecycle](./COG-011) by providing the complete semantic model and enforcement architecture. ## Purpose Authentication ([COG-10](./COG-010)) establishes *who* a caller is. Capabilities establish *what* they may do: - **Least privilege** — agents receive only the permissions their role requires - **Transparent boundaries** — agents are told what they can do, preventing trial-and-error discovery - **Structural denial** — denied operations return actionable errors, not ambiguous failures - **Delegable authority** — users can delegate scoped authority to agents or other users via UCAN tokens - **Attenuation** — delegated capabilities can only narrow, never widen, the grantor's authority ## Terminology See [COG-1: Architecture](./COG-001) for Grid terminology and [COG-11: Agent Lifecycle](./COG-011) for agent state. A **Capability** is a `{with, can}` pair granting an ability on a resource. An **Attenuation** is a vector of capabilities assigned to an agent, restricting its operations to those covered by at least one capability in the vector. A **UCAN** (User Controlled Authorisation Network) is a signed token encoding a delegation of capabilities from an issuer to an audience. ## Capability Structure A capability is a map with two required fields: ```json { "with": "w/decisions/", "can": "crud/write" } ``` | Field | Type | Description | |-------|------|-------------| | `with` | string | **Resource** — a path prefix identifying the scope of access | | `can` | string | **Ability** — a slash-delimited permission in the ability hierarchy | An agent's capabilities are expressed as a vector of capability maps in the `caps` field of the agent record ([COG-11](./COG-011)): ```json "caps": [ { "with": "w/vendor-records/", "can": "crud/read" }, { "with": "w/enrichments/", "can": "crud" }, { "with": "g/helper", "can": "agent/message" } ] ``` ### Null vs Empty - **Null caps** (field absent or `null`): unrestricted access — no capability checking is performed - **Empty caps** (`[]`): deny-all — every operation is denied This distinction is critical. A newly created agent with no `caps` field has full access. An agent with `caps: []` can do nothing. ## Resource Matching The `with` field is a **path prefix**. A capability's resource covers a request's target resource if: 1. The capability resource equals the request resource, OR 2. The capability resource is a prefix of the request resource Examples: | Capability `with` | Request resource | Covered? | |--------------------|-----------------|----------| | `w/vendor-records` | `w/vendor-records` | Yes (exact match) | | `w/vendor-records` | `w/vendor-records/acme` | Yes (prefix) | | `w/vendor-records` | `w/vendor-records/acme/contact` | Yes (prefix) | | `w/vendor-records` | `w/other-data` | No | | `w/` | `w/anything/at/all` | Yes (covers entire workspace) | | `""` | any resource | Yes (wildcard) | ### Resource Extraction For each operation, the target resource is derived from the invocation input: | Operation type | Resource derivation | |----------------|---------------------| | Workspace operations (`covia:read`, `covia:write`, etc.) | The `path` parameter | | Agent operations (`agent:request`, `agent:create`, etc.) | `g/` from the `agentId` parameter | | Grid operations (`grid:run`, `grid:invoke`) | No resource-specific check | Operations without a derivable resource are checked only for ability coverage, not resource scope. ### Cross-User Resources Resources may include a full DID URL for cross-user access: ```json { "with": "did:key:zAlice.../w/shared-data", "can": "crud/read" } ``` This enables capability delegation across user boundaries — see [UCAN Delegation](#ucan-delegation). ## Ability Hierarchy Abilities are slash-delimited strings forming a hierarchy. A capability's ability covers a requested ability if: 1. The capability ability equals the requested ability, OR 2. The capability ability is a prefix of the requested ability The wildcard ability `"*"` covers any requested ability. ### Standard Abilities ``` crud (any CRUD operation) ├── crud/read (read, list, slice, inspect) ├── crud/write (write, append) └── crud/delete (delete) agent (any agent operation) ├── agent/create ├── agent/request ├── agent/message └── agent/fork invoke (operation execution) asset (asset operations) └── asset/store secret (credential access) └── secret/decrypt ucan (token delegation) ├── ucan/delegate └── ucan/revoke venue (venue actions on the caller's behalf) └── venue/relay (relay a cross-venue request as the venue itself) hitl (human-in-the-loop requests) └── hitl/request (deliver a HITL request into a user's h/ inbox) grant (meta: rights to issue grants — see COG-17) └── grant/ (the right to grant ; composes recursively) ``` A `venue/relay` capability is granted **to a venue** (`aud` = the venue's DID, `iss` = the caller): it instructs and authorises that venue to make a cross-venue hop authenticated as itself, exercising the caller's delegation. The instruction is only honoured when its issuer is the authenticated caller. The `grant` family is a **meta-family**: for any ability `X`, `grant/X` is the right to have a grant of `X` *minted at a granting surface*, distinct from the right to exercise `X` (`grant/crud/read` does not cover `crud/read`, and vice versa). Granting semantics — grantable authority and issuance — are specified in [COG-17: Capability Granting](./COG-017); chain verification remains grant-agnostic. ### Operation-to-Ability Mapping Each operation maps to a required ability: | Operation | Required Ability | |-----------|------------------| | `covia:read`, `covia:list`, `covia:slice`, `covia:inspect` | `crud/read` | | `covia:write`, `covia:append` | `crud/write` | | `covia:delete` | `crud/delete` | | `agent:create` | `agent/create` | | `agent:request` | `agent/request` | | `agent:message` | `agent/message` | | `agent:fork` | `agent/fork` | | `grid:run`, `grid:invoke` | `invoke` | | `asset:store` | `asset/store` | | `secret:extract` | `secret/decrypt` | | `ucan:issue` | `ucan/delegate` | | `hitl:request` | `hitl/request` (against the target user's `h/` — see [COG-16](./COG-016)) | A capability with ability `crud` covers `crud/read`, `crud/write`, and `crud/delete`. A capability with ability `crud/read` does NOT cover `crud/write`. ## Enforcement ### Enforcement Point Capability checking occurs **before** an operation is dispatched to its adapter. The enforcement point is the job invocation layer: 1. Extract the caller's capability vector from the request context 2. If caps is null, allow (unrestricted) 3. Extract the target resource from the operation input 4. Determine the required ability from the operation 5. Check whether any capability in the vector covers the request 6. If no capability covers the request, deny with a structured error ### Matching Algorithm For a request with resource `R` and ability `A`, check each capability `C` in the agent's caps vector: ``` ALLOW if any C where: C.with is a prefix of R (or C.with == "") AND C.can is a prefix of A (or C.can == "*") DENY otherwise ``` First matching capability allows the operation. Order does not matter. ### Agent Tool Calls In the agent architecture ([COG-11](./COG-011)), capability checking applies at the Level 2 (domain logic) tool dispatch point: 1. Agent's LLM requests a tool call 2. Level 2 adapter resolves the tool to a grid operation 3. **Capability check** against the agent's caps 4. If denied, return error to LLM (operation is not executed) 5. If allowed, invoke the operation Level 3 (LLM call) is NOT capability-gated — the LLM invocation itself is always permitted. Only the agent's tool calls are restricted. ## Disclosure Capabilities MUST be disclosed to agents in their system prompt so the LLM knows its boundaries. The disclosure section is formatted as: ``` ## Your capabilities (caps) - crud/write on w/decisions/ - crud/read on w/ - agent/message on g/Alice Tool calls outside these capabilities will fail with a "Capability denied" error. Retrying the same call does not help — the denial is structural. ``` Disclosure MUST be refreshed every turn from the current agent configuration. Updates to an agent's caps take effect immediately without requiring a restart. ## Denial Messages When an operation is denied, the error message MUST: 1. **Name the denial** — identify it as a capability denial 2. **Specify the requirement** — operation name, required ability, target resource 3. **List available capabilities** — the agent's actual caps in compact format 4. **Guide behaviour** — state that retrying will not succeed and the denial is structural Example: ``` Capability denied: v/ops/covia/write requires crud/write on w/audits/INV-123. Your capabilities are: crud on w/decisions/, crud/read on w/. Retrying the same call will not succeed — the denial is structural. ``` This design prevents LLM agents from looping on impossible operations by making boundaries actionable information. ## UCAN Delegation Capabilities are delegated via UCAN (User Controlled Authorisation Network) tokens — signed JWTs encoding a chain of authority from the resource owner to the delegate. ### Token Structure A UCAN token contains: | Field | Description | |-------|-------------| | `iss` | Issuer DID (who is delegating) | | `aud` | Audience DID (who receives the delegation) | | `exp` | Expiry timestamp (Unix **seconds**, per the JWT NumericDate convention; UCAN v0.10 permits `null` for a non-expiring token) | | `att` | Attenuations — vector of `{with, can}` capabilities | | `prf` | Proof chain — vector of parent UCAN tokens | ### Issuance Tokens are issued via the `ucan:issue` operation: ```json { "operation": "v/ops/ucan/issue", "input": { "aud": "did:key:z6Mk...", "exp": 1795000000, "att": [ { "with": "w/shared-data/", "can": "crud/read" } ] } } ``` The input field names deliberately match the UCAN claim names (`aud`, `att`, `exp`, and optionally `prf` for granting held authority) — see [COG-17: Capability Granting](./COG-017) for the full input schema and grantable-authority rules. The venue signs the token with its own keypair; the issuer is the venue DID. Attenuations MUST lie within the caller's **grantable authority**: the caller's own namespace (ownership carries implicit granting rights), or a resource over which the caller presents a `grant/…` proof chain ([COG-17](./COG-017)). Access to a resource does not confer the right to grant it — in particular, agent sub-principals acting within an owner's namespace hold no granting authority over it and cannot issue grants. `ucan:issue` is a convenience for callers without their own signing key. A resource owner holding a `did:key` keypair MAY equally sign grants over their own namespace directly (a **self-sovereign** grant, `iss` = the owner) — no venue involvement, verifiable by any venue. ### Verification On each request, UCAN tokens are verified: 1. Signature verified against issuer's public key (at every hop of the proof chain) 2. Expiry checked: `exp >= now` 3. Audience DID matched: `aud == caller DID` 4. Each capability in `att` checked for coverage 5. If the proof chain is non-empty, each parent UCAN verified recursively, and each hop's capability MUST be covered by its parent (per-hop attenuation, fail-closed at the capability) 6. **Root authority**: the chain root's issuer MUST be entitled to root a grant over the resource — the DID owning the resource (self-sovereign), or a venue the verifier's **root-authority policy** accepts for attestation over that resource ([COG-15](./COG-015); by default, the venue hosting the resource). Any other root is refused ### Attenuation Rule Delegated capabilities can only **narrow** the grantor's authority: - The delegate's `with` MUST be equal to or a sub-path of the grantor's `with` - The delegate's `can` MUST be equal to or a sub-ability of the grantor's `can` For example, a grantor with `{with: "w/", can: "crud"}` can delegate: - `{with: "w/reports/", can: "crud/read"}` — narrower on both resource and ability - `{with: "w/reports/", can: "crud"}` — narrower on resource, same ability But cannot delegate: - `{with: "s/secrets/", can: "crud/read"}` — different resource, not a sub-path of `w/` Chain delegation of held abilities is **transitive and visible**: a holder may sign a narrowed onward delegation, and the proof chain names every delegator — re-delegation is audited, not blocked. Minting new *chain-free* authority is different: a granting surface requires the granting right `grant/` before producing a token on a caller's instruction (resource owners hold all granting rights implicitly). The granting model is specified in [COG-17: Capability Granting](./COG-017). ## Agent Capability Models ### User-Scoped (Current) The agent has no independent identity. It acts under its owner's DID with attenuated capabilities: - Venue issues a scoped capability set at agent creation - Caps are stored in the agent record alongside config - Tool calls carry the owner's DID with the agent's restricted capability set - The venue enforces capability checks on every tool invocation ### Independent (Planned) The agent has its own Ed25519 keypair and DID: - Agent can receive delegations from multiple users - Agent signs its own UCANs for sub-delegation to child agents - Tool calls carry the agent's DID and proof chain - Full UCAN verification with delegation chain walking ## Security Considerations ### Principle of Least Privilege Agents SHOULD be configured with the minimum capabilities required for their role. The AP demo illustrates this: - **Alice** (scanner): no workspace access, structured output only - **Bob** (enricher): read vendor records, write enrichments - **Carol** (approver): read all workspace, write decisions only ### Empty Caps as Sandbox Setting `caps: []` creates a fully sandboxed agent that cannot invoke any tools. Combined with `defaultTools: false`, this produces a pure reasoning agent with no side effects. ### Capability Escalation Agents with `agent/create` capability can create child agents. Venues MUST ensure that child agents cannot be created with broader capabilities than their parent. The attenuation rule applies transitively. ### Disclosure Accuracy The capability disclosure in the system prompt MUST accurately reflect the enforced capabilities. Discrepancies between disclosed and enforced capabilities would cause agents to attempt operations they believe are permitted but that will be denied. ## Examples ### Read-Only Analyst ```json "caps": [ { "with": "w/", "can": "crud/read" } ] ``` Can read any workspace path. Cannot write, delete, or invoke agent operations. ### Scoped Worker ```json "caps": [ { "with": "w/vendor-records/", "can": "crud/read" }, { "with": "w/enrichments/", "can": "crud" }, { "with": "g/helper", "can": "agent/message" } ] ``` Can read vendor records, perform full CRUD on enrichments, and send messages to the "helper" agent. ### Manager with Delegation ```json "caps": [ { "with": "w/", "can": "crud" }, { "with": "g/", "can": "agent" } ] ``` Full workspace access and full agent management. Can create, request, message, and fork any agent. ## Related Specifications - **[COG-1: Architecture](./COG-001)** — Overall Grid architecture - **[COG-10: Authentication](./COG-010)** — Authentication (identity establishment) - **[COG-11: Agent Lifecycle](./COG-011)** — Agent state, the `caps` field, and the run loop - **[COG-12: Orchestrations](./COG-012)** — Orchestration steps subject to capability enforcement --- ## COG-14: A2A Agent Interoperability ``` Status: Draft (Work in Progress) Version: 0.1 Created: 2026-07-06 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. The reference venue implements the core surface (venue front-door and per-agent A2A endpoints, agent cards, SSE streaming, and import of remote A2A agents as assets); structure and details may still change based on implementation experience and community feedback. ::: :::note[Related specifications] This document specifies how a Covia venue exposes its **agents** to external clients over the A2A (Agent-to-Agent) protocol. It builds on the agent model of [COG-11: Agent Lifecycle](./COG-011) and the message semantics of [COG-9: Agent Messaging](./COG-009). Access is governed entirely by [COG-3: Authentication Mechanisms](./COG-003), the venue access control of [COG-10: Venue Authentication & Access Control](./COG-010), and the capability model of [COG-13: Agent Capabilities](./COG-013); this document introduces **no new trust model**. ::: ## Purpose A Covia venue hosts many independent **agents**, each owned by a user and addressable within the venue's lattice. The A2A protocol is an open standard for agents to discover and message one another across organisational boundaries. This document specifies how a venue presents its agents as first-class A2A agents, each with its own Agent Card, endpoint, and identity, so that any A2A-compliant client can discover and interact with them, subject to the venue's existing authorisation rules. A venue MAY additionally model itself as a single A2A agent (a designated front-door agent). This document extends that model to expose individual agents; it does not replace it. ## Principles - **Facade, not a new trust model**: A2A is a protocol surface over the venue's existing agent operations and capability enforcement. An A2A request is authorised identically to the equivalent native request; there is no A2A-specific authority or bypass. - **Universal addressability, gated access**: Every agent has a stable, uniform address. Any agent MAY be *named*; whether a given caller may *reach* it is a separate authorisation decision. - **Private by default**: An agent is not exposed to other principals unless its owner or the venue operator explicitly grants access. - **Auth-scoped discovery**: What an observer can discover depends on who they are: anonymous observers see only what the operator has published; authenticated principals additionally see their own agents. - **Identity continuity**: An agent's A2A identity, and the identifiers used within A2A interactions, are the venue's own identifiers rather than a parallel namespace. - **Standards-compliance**: The venue conforms to the A2A specification wherever practical, so that generic A2A clients interoperate without Covia-specific knowledge. ## Specification ### 1. Agent Identity and Addressing Each agent is owned by a user identified by a Decentralised Identifier (DID) and is addressed by its canonical grid address: ``` /g/ ``` (see [COG-11: Agent Lifecycle](./COG-011)). An agent's A2A **endpoint** encodes this address, and its Agent Card's `provider` identifies the hosting venue. Addressing is universal: a well-formed address MAY be constructed for any agent, independent of the requester's access rights. Resolving that address to an interaction is subject to §4. ### 2. Agent Cards Each exposed agent is described by an A2A **Agent Card**: the standard document carrying the agent's `name`, `description`, `version`, `provider`, `capabilities`, `skills`, `defaultInputModes` / `defaultOutputModes`, `securitySchemes`, and service `interfaces`. - Descriptive fields (`name`, `description`) derive from the agent's configuration. - `skills` derive from the operations the agent is configured to offer. - `provider` identifies the venue; the service `interfaces` advertise the agent's A2A endpoint (§1). - `securitySchemes` advertise the authentication mechanisms the venue accepts for this agent (§7). Each agent has a **base URL**; its card is served at the standard A2A well-known path relative to that base (`/.well-known/agent-card.json`) and the card's `interfaces` advertise the base endpoint used to interact. A conformant A2A client therefore resolves a per-agent card from its base URL exactly as it resolves any A2A agent. (The venue front-door card sits at the *origin* well-known location; §3.) An agent's card is disclosed only to principals permitted to discover it (§3). ### 3. Discovery The venue supports the three A2A discovery mechanisms, each scoped by the requester's identity: **Well-known URI.** The venue MAY publish a single front-door Agent Card at the A2A well-known location. This card is **operator-owned**: it represents an agent the operator has explicitly designated as public. A venue with no designated public agent MUST NOT publish a front-door card, and SHOULD respond at the well-known location with an indication that A2A is not configured rather than a bare not-found. **Authenticated catalog.** An authenticated principal MAY enumerate the agents it is entitled to see; by default, the agents it owns. This corresponds to A2A's authenticated (extended-card) discovery path and is scoped to the caller's identity. **Direct addressing.** A principal that already knows an agent's base URL (§1) retrieves that agent's card from its well-known path (§2) and interacts via the endpoint the card advertises, subject to §4. No discovery mechanism discloses an agent to a principal not permitted to see it. ### 4. Access Control An A2A interaction resolves the target agent (§1) and is then authorised **exactly as the equivalent native operation** under [COG-10](./COG-010) and [COG-13](./COG-013). A caller MAY interact with an agent when any of the following holds: - the caller is the agent's **owner**; - the caller presents a **capability** delegating the required access on `/g/` (see [COG-13](./COG-013)); - the agent has been made **publicly accessible** by its owner, or routed as a public agent by the operator (§5). Otherwise the request is denied. Denial responses distinguish the requester so as not to disclose an agent's existence to those with no standing: - an **anonymous** requester receives a *not-found* response; - an **authenticated** requester lacking access receives a *forbidden* response. Agents are **private by default**: no agent is reachable by other principals until access is explicitly granted. ### 5. Granting Public or Delegated Access Access to an otherwise-private agent MAY be extended by two independent mechanisms: - **Capability delegation**: the owner issues a capability (see [COG-13](./COG-013)) authorising a specific principal to interact with the agent. Fine-grained and revocable. - **Explicit public exposure**: the owner marks the agent publicly accessible in its configuration. Public exposure has two levels: the agent's **card is discoverable** by anyone, while an **interaction** additionally requires the owner to configure an explicit **capability ceiling**. A public agent with no ceiling is discoverable but **not** anonymously interactable; running the agent for anonymous callers requires the owner to deliberately bound it. A non-owner's interaction runs under the **owner's** authority, narrowed by that ceiling (a ceiling can only narrow, never widen, so this is escalation-safe). This exposure ceiling is an owner-configured attenuation of the owner's own authority (the granting model of [COG-17](./COG-017)) and is distinct from the venue-wide **public capability ceiling** ([COG-10](./COG-010)) that bounds what anonymous callers can do at all. An owner MAY deliberately configure an unrestricted ceiling (full owner authority for anonymous callers), which the venue honours with a warning. The venue operator MAY additionally designate a single agent as the venue's public front-door (§3). No agent is public by operator default. ### 6. Interaction Model A2A interactions map onto the agent model of [COG-9](./COG-009) and [COG-11](./COG-011): - An A2A **message** to an agent corresponds to a message delivered to that agent; a fresh interaction begins a new conversation, and a continued interaction targets an existing one. - The A2A **context** identifier corresponds to the agent's **session**; the A2A **task** identifier corresponds to a unit of work (a **task**) within that session. These identifiers are the venue's own identifiers, reused verbatim (Principle: identity continuity). - A2A **task lifecycle states** correspond to the agent's task and session states. The A2A context/task distinction (a conversation containing units of work) mirrors the Covia session/task distinction, so the mapping is direct. ### 7. Security Schemes Agent Cards advertise the authentication mechanisms a caller may use, drawn from [COG-3](./COG-003), notably Ed25519 self-issued JWTs and the grid's capability tokens. A generic A2A client reads these schemes to learn how to present credentials. An interaction requiring authorisation beyond an agent's public allowance MUST present acceptable credentials per §4. --- ## COG-15: Cross-Venue Federation ``` Status: Draft Version: 0.1 Created: 2026-07-10 Authors: Mike Anderson ``` :::note[Related specifications] This document specifies how requests, authority, and caller identity cross venue boundaries on the Covia Grid. It builds on the authentication catalogue of [COG-3: Authentication Mechanisms](./COG-003) (in particular the UCAN identity token, COG-3 §6) and the capability model of [COG-13: Agent Capabilities](./COG-013) (UCAN delegation, root authority, the `venue/relay` ability). It introduces **no new trust primitive** — federation composes the existing ones. ::: ## Purpose A grid operation may target a **remote venue**: venue A (the *relay*) receives a request from a caller and forwards it to venue B (the *target*). Without care, such hops either drop the caller's authority (everything is anonymous) or — worse — assert it unverifiably (the relay claims "the caller is Alice" and the target has to take its word). This document specifies how authority and identity cross the hop **verifiably**: the target trusts signatures, never the relay. ## Principles - **Authority travels only in the proof channel.** UCAN tokens are carried in the request's `ucans` array — never in operation input, which is data (persisted in job records; a credential there would leak into durable history). Body-less observation requests (e.g. job status reads) carry the same tokens in the `X-Covia-Ucans` header, **explicitly attached by the sender** from its own proof state and verified identically at ingress — nothing ever relies on passive header propagation across hops. - **Tokens are self-describing.** There are no mode flags or auth fields. What a token *is* — a grant, an identity proof, a relay instruction — follows from its issuer, audience, and attenuations. The verifier decides everything from the token itself. - **Zero trust in the relay.** The target venue verifies every forwarded token against its own trust policy (signature at every chain hop, temporal bounds, attenuation, root authority — COG-13). A relay can neither forge authority nor widen it; the worst a malicious relay can do is drop tokens (deny service). - **Anonymous is the explicit default.** A hop carrying no identity token and no relay delegation is anonymous — correct for public operations, fail-closed for everything else. ## The Proof Channel Requests carry UCAN tokens (JWT-encoded) in the `ucans` array of the invoke envelope: ```json { "operation": "v/ops/covia/read", "input": { "path": "did:key:zAlice.../w/shared/doc" }, "ucans": [ "", "" ] } ``` The receiving venue verifies each token's signature at ingress; unverifiable tokens are discarded. Delegation chains travel **nested**: a token's `prf` field carries its parent tokens as JWT strings, recursively verified — a single leaf JWT is a complete, self-contained chain. Requests without a body — observing a job's status or result — carry the same JWTs, comma-separated, in the `X-Covia-Ucans` header (wire format specified in [COG-3](./COG-003) §6). This is what lets a federated hop *observe* the remote job it created: the relayed identity token authenticates the requester at the target on the read, exactly as it did on the invoke. Verification is identical; only the carrier differs. ## Caller Identity Across Venues The caller's identity crosses a hop as an **identity token** (COG-3 §6): a UCAN the caller signs with their own key, audienced to the **target** venue, with an **empty** attenuation list. - The empty `att` makes it pure identity — it grants nothing and cannot be dual-used. - The audience binding makes it unusable at any venue other than the target. - The target accepts it as the caller identity **only on an unauthenticated transport**; an `Authorization` header always wins. The caller is then the **principal** at the target: jobs are owned by them, and forwarded grants audienced to them apply. The relay's only role was carrying the token. ## Venue as Delegate: `venue/relay` A caller MAY instead have the relay make the hop **as itself**, exercising authority the caller delegated to it. The instruction is a token: | Field | Value | |-------|-------| | `iss` | The caller (MUST be the relay's authenticated caller) | | `aud` | The **relay** venue's DID | | `att` | A capability with `can: venue/relay` (conventionally `with: `), plus the substantive grants the relay may exercise | The token is simultaneously the instruction and the authorisation — the relay honours it **only when its issuer is the authenticated caller**. This rule makes the confused deputy impossible by construction: a relay delegation minted by anyone else is not an instruction from this caller, so a third party presenting a stolen copy gets nothing. At the target, the relay is the transport-authenticated caller and the forwarded chain (leaf audienced to the relay) is walked to its root — which must still satisfy the target's root-authority policy (COG-13): the resource owner, or an authority the target accepts. ## Relay Obligations A venue forwarding a request MUST: 1. **Forward the caller's presented tokens** in the hop's `ucans` array — filtered to those that could be admissible at the target. Tokens that are *provably inert* MUST be dropped: expired or unparseable tokens, and tokens audienced to an identity that is not the hop's principal (e.g. the caller's own grants when the relay hops as itself). Forwarding an inert token discloses the caller's unrelated grants for no benefit. 2. **Never forward the inbound transport bearer.** It is audienced to the relay and must not be replayable elsewhere. 3. **Not assert identity.** The relay never adds an unverifiable "caller is X" claim; identity crosses only as a caller-signed identity token, or the hop is anonymous. Deeper filtering than rule 1 is deliberately **not** required: the relay cannot generally know which resources an operation on the target will touch, nor the target's DID before contact. Disclosure control therefore rests with the **caller** — present the tokens relevant to each request, not a wallet. ## Verification at the Target The target venue applies its standard verification to every forwarded token (COG-13): signature at every hop, temporal bounds, audience match against the acting principal, per-hop attenuation, and **root authority** — the chain root must be signed by the resource owner (self-sovereign) or by an authority the target accepts (its own grants; custodial attestation below). Nothing about a forwarded token is trusted because a venue relayed it. ## Custodial Callers (Planned) :::caution[Work in Progress] This section describes planned behaviour for callers who hold no signing key (e.g. OAuth-authenticated users). It is not yet implemented. ::: A keyless caller cannot sign an identity token or delegation. Their controlling venue **attests** instead: it signs a statement that its user (`:u:`, [COG-2](./COG-002)) authenticated to it and that it forwards a grant on their behalf, audienced to the target. The target accepts the attestation only if its **trust policy** accepts the attesting venue (allowlist, organisational root, or DID-discovery based). Until then, custodial callers cross venues anonymously. ## Security Considerations - **Replay:** identity tokens are audience-bound and SHOULD be short-lived (minutes). A relay holding one cannot use it at any other venue. - **Confused deputy:** the `venue/relay` issuer-is-the-caller rule is load-bearing; implementations MUST NOT honour relay delegations from any other issuer. - **Privacy:** the admissibility filter (Relay Obligations, rule 1) bounds what a target learns about the caller's other relationships; per-request presentation bounds it further. - **Denial:** a malicious relay can drop tokens or refuse to forward; it cannot escalate. Callers who cannot tolerate this should contact the target venue directly. --- ## COG-16: Human-in-the-Loop Requests ``` Status: Draft Version: 0.1 Created: 2026-07-19 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: :::note[Related specifications] This document specifies how agents and operations request decisions, approvals, and information from humans on the Covia Grid. It builds on the Job lifecycle of [COG-8: Jobs](./COG-008), the per-user lattice namespaces of [COG-4: Grid Lattice](./COG-004), the capability model of [COG-13: Agent Capabilities](./COG-013), and the federation mechanisms of [COG-15: Cross-Venue Federation](./COG-015). It introduces **no new trust primitive** — HITL composes the existing ones. ::: This standard specifies **Human-in-the-Loop (HITL) requests** — the mechanism by which an agent or operation asks a human for input, approval, or authority, and receives a structured, auditable answer. ## Purpose Autonomous agents routinely reach points where a human decision is required: approving a payment, choosing between alternatives, supplying missing information, or consenting to an expansion of the agent's authority. HITL requests make these moments first-class grid citizens: - **Durable** — requests land in the target user's inbox and survive venue restarts; humans respond in minutes or in weeks - **Structured** — asks are typed (text, approval, choice, checkboxes, plus extension types such as [COG-19](./COG-019)'s `token`) so clients can render them and answers can be validated - **Auditable** — the request, the response, and any authority granted are recorded with verified identities on both sides - **Job-shaped** — every request is carried by a standard Job ([COG-8](./COG-008)), so requesters wait, poll, stream, and cancel with the machinery they already have - **Consent-preserving** — a request MAY offer capability grants, but authority moves only through an explicit choice the human makes - **Federated** — a request can target a user on a remote venue, with caller identity and authority forwarded per [COG-15](./COG-015) ## Terminology See [COG-1: Architecture](./COG-001) for Grid terminology, [COG-8](./COG-008) for the Job lifecycle, and [COG-13](./COG-013) for capabilities and UCANs. | Term | Definition | | ---- | ---------- | | HITL request | A structured request for human input, delivered as a record in the target user's `h/` namespace | | Ask | A single typed question within a request (`text`, `approval`, `choice`, `checkboxes`, or an extension type — see [COG-19](./COG-019)) | | Requester | The verified identity (user or agent acting for a user) that created the request | | Responder | The human user the request is addressed to — the owner of the `h/` inbox it lands in | | Inbox | The responder's `h/` namespace, holding open and resolved HITL request records | | Offered grant | A capability `{with, can}` attached to a request, ask, or option, which the responder may confer on the requester by approving it | | Response | The responder's resolution of a request: an answer set, or a rejection | ## Model A HITL request separates three concerns, each carried by an existing grid primitive: 1. **The request record** — a durable document in the responder's `h/` namespace. This is the source of truth for HITL semantics: what is being asked, by whom, what is offered, and how it was resolved. 2. **The Job** — the requester-side lifecycle carrier. It is created when the request is submitted and reaches a terminal state exactly when the request is resolved: `COMPLETE` with the response as output, or `FAILED` on rejection or expiry. 3. **The response operation** — the responder's action. It validates the answers against the asks, resolves the record, completes (or fails) the Job, and issues any approved capability grants. ### HITL is not a Job status A Job waiting on a HITL request SHOULD be in the `INPUT_REQUIRED` state, because that is the accurate lifecycle state: the job is blocked awaiting external input. However, the implication runs in one direction only: - `INPUT_REQUIRED` does **not** imply HITL. The status means input is required from *some* external party — another agent, a multi-turn caller, or a human. Many non-HITL flows (agent-to-agent conversations, multi-turn operations) use the same status. - HITL semantics live **entirely in the `h/` record** and the `hitl:*` operations that manage it. No HITL behaviour is keyed off Job status, and clients MUST NOT discover pending human asks by scanning for `INPUT_REQUIRED` jobs. The inbox is `h/`. ### Lifecycle overview ``` Requester (agent/user) Responder (human) | | |-- hitl:request ----------------------->| | Job: PENDING -> INPUT_REQUIRED | record written to h/, status "open" | | | (minutes ... weeks) | responder lists h/, reviews asks | | and any offered grants | | |<------------------- hitl:respond ------| | | | outcome "answer": | record status "answered" | Job: COMPLETE, output = response | approved grants issued as UCAN | outcome "reject": | record status "rejected" | Job: FAILED | | | | (no response before expiry) | record status "expired" | Job: FAILED | | (requester cancels the Job) | record status "cancelled" | Job: CANCELLED | ``` ## The `h/` Inbox Each user's lattice record ([COG-4](./COG-004)) includes the `h/` namespace, reserved for HITL requests. It behaves like the other user namespaces (`w/`, `o/`): slash-separated paths, navigable JSON values, and durable last-write-wins merge semantics. ### Addressing A request record lives at: ``` h/ ``` where `` is the hex encoding of the carrying Job's ID. Using the Job ID as the request ID gives every request a unique, time-ordered identifier and a direct link between record and Job. ### Write discipline The `h/` namespace is **framework-managed**: - Records are created and resolved only by the venue, via the `hitl:*` operations. Direct `covia:write` into `h/` MUST be rejected. - The inbox owner MAY read and list `h/` freely (`covia:read`, `covia:list`, `covia:slice`, `covia:inspect`). - The inbox owner MAY delete **resolved** records (`covia:delete h/`) for inbox hygiene. Deleting an open record is equivalent to rejecting it: the venue MUST fail the carrying Job. - Cross-user reads of `h/` records follow the standard delegation rules ([COG-13](./COG-013)): a requester holding a `crud/read` delegation on the record path can observe the record state directly; otherwise the Job is the requester's view. ### Request record schema | Field | Type | Set by | Description | |-------|------|--------|-------------| | `id` | string | venue | Request ID (Job ID hex) | | `from` | string | venue | Requester's verified DID. Never caller-supplied | | `agent` | string | venue | Originating agent (e.g. `g/assistant`), when the requester is an agent | | `origin` | string | venue | Origin venue DID, for requests delivered across venues | | `title` | string | requester | Short human-readable summary | | `description` | string | requester | Markdown body giving full context for the decision | | `asks` | array | requester | The list of asks (see [Asks](#asks)) | | `status` | string | venue | `open` → `answered` \| `rejected` \| `expired` \| `cancelled` | | `created` | integer | venue | Creation timestamp (Unix ms) | | `expires` | integer | venue | Absolute expiry timestamp (Unix ms), when a timeout was requested | | `response` | object | venue | The response (see [Responses](#responses)), set on resolution | ## Asks A request contains an ordered list of one or more **asks**. Each ask is a typed question: | `type` | Meaning | Answer form | |--------|---------|-------------| | `text` | Free-text input | string | | `approval` | Approve or reject a single stated action | boolean | | `choice` | Select exactly one option | option `id` (string) | | `checkboxes` | Select zero or more options | array of option `id`s | Other COGs MAY extend this set with new ask types. Venues MUST reject ask types they do not implement. The current extension is the `token` ask ([COG-19](./COG-019)). ### Ask schema | Field | Type | Applies to | Description | |-------|------|-----------|-------------| | `id` | string | all | Unique (within the request) identifier for the ask | | `type` | string | all | One of `text`, `approval`, `choice`, `checkboxes`, or an extension type defined by another COG (currently `token`, [COG-19](./COG-019)) | | `prompt` | string | all | The question, as shown to the human | | `required` | boolean | all | If `true`, an `answer` outcome MUST include an answer for this ask. Default `false` | | `comment` | boolean | all | If `true`, clients SHOULD offer an optional free-text comment alongside the answer. Default `false` | | `options` | array | `choice`, `checkboxes` | The selectable options. REQUIRED for these types, and MUST be non-empty | | `grants` | array | `approval` | Grants conferred if this ask is approved (see [Capability Grants](#capability-grants)) | ### Option schema | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique (within the ask) identifier for the option | | `label` | string | Human-readable label | | `description` | string | Optional longer explanation | | `grants` | array | Grants conferred if this option is selected | ### Validation Venues MUST validate requests at submission: ask `id`s unique, types recognised, options present and non-empty where required, option `id`s unique within their ask, and `grants` attached only where permitted — on `approval` asks and on options. Invalid requests are rejected synchronously — no record is written and no Job parks. ## Responses A response resolves a request. Its schema: | Field | Type | Description | |-------|------|-------------| | `outcome` | string | `answer` or `reject` | | `answers` | object | Map of ask `id` → answer, in the form required by the ask type. REQUIRED for `answer`; ignored for `reject` | | `comments` | object | Optional map of ask `id` → free-text comment, for asks with `comment: true` | | `comment` | string | Optional overall comment. For `reject`, this is the rejection reason | | `grants` | array | The offered grants the responder explicitly approves (see [Consent](#consent-is-explicit)) | Venues MUST validate responses against the record's asks: every `required` ask answered (for `answer` outcomes), answer forms matching ask types, `choice` and `checkboxes` answers naming existing option `id`s, and no answers for unknown ask `id`s. An invalid response fails the `hitl:respond` call and leaves the record open. An extension ask type MAY declare its answer **secret**. The venue MUST replace such an answer with a non-secret placeholder in every durable carrier — the persisted record's `response.answers` and the Job `output`'s `answers` — and deliver the real value only through the extension's own output channel (see [COG-19](./COG-019)). ### Job resolution | Event | Record `status` | Job terminal state | Job payload | |-------|-----------------|--------------------|-------------| | `answer` response | `answered` | `COMPLETE` | `output` = `{id, outcome, answers, comments?, comment?, grants?, token?, tokens?}` | | `reject` response | `rejected` | `FAILED` | `error` = `"HITL request rejected"` plus the responder's `comment`, when given | | Expiry reached | `expired` | `FAILED` | `error` = `"HITL request expired"` | | Requester cancels the Job | `cancelled` | `CANCELLED` | standard cancellation | `token?` is the venue-minted grant token ([Issuance](#issuance)); `tokens?` carries transported self-sovereign tokens per [COG-19](./COG-019). Job terminal states are sticky ([COG-8](./COG-008)); when a response races an expiry or a cancellation, exactly one resolution wins and the record MUST reflect the winner. Rejection detail intended for the requester (the rejection `comment`) MUST travel in the Job `error`, since the requester generally cannot read the responder's `h/` record. ### Waiting, timeouts, and restarts - Jobs have **no framework-level timeout** ([COG-8](./COG-008)); an unbounded HITL request waits indefinitely by design. Requesters SHOULD set a `timeout` for time-sensitive decisions. - Expiry is a **HITL-level** property, enforced by the venue hosting the record. The venue MUST fail the Job when `expires` passes without a response, and MUST re-arm expiry enforcement for open requests after a restart. - Open requests and their parked Jobs survive venue restarts: paused-family Jobs are restored, and the `h/` record is durable lattice state. - Requesters observe resolution through the standard Job surfaces: polling, blocking waits, and Job SSE streams. Responder clients discover work by listing `h/` — venues MAY additionally surface notification channels, which are out of scope for this COG. ## Operations HITL is exposed through the `hitl` adapter. Operations follow the standard operation model ([COG-7](./COG-007)) and appear in the catalog under `v/ops/hitl/`. ### `hitl:request` — submit a request Creates the request: validates the asks, writes the record to the target user's `h/`, and parks the carrying Job in `INPUT_REQUIRED`. Asynchronous by nature — the Job is the handle. Input: | Field | Type | Description | |-------|------|-------------| | `user` | string | Target responder DID. OPTIONAL — defaults to the requester's own user identity (an agent asking its owner) | | `title` | string | REQUIRED. Short summary | | `description` | string | Markdown context for the decision | | `asks` | array | REQUIRED. One or more asks | | `timeout` | integer | Seconds until expiry. OPTIONAL — omitted means no expiry | Output (Job `output` on completion): the response resolution payload described above. ### `hitl:respond` — resolve a request Invoked by the responder. Validates the response, updates the record, resolves the Job, and issues approved grants. Input: | Field | Type | Description | |-------|------|-------------| | `id` | string | REQUIRED. The request ID | | `outcome` | string | REQUIRED. `answer` or `reject` | | `answers` | object | Ask answers (for `answer`) | | `comments` | object | Per-ask comments | | `comment` | string | Overall comment / rejection reason | | `grants` | array | Explicitly approved grants | Output: `{id, status}` — the resolved record status. Responding to a request that is not `open` MUST fail without side effects. ### `hitl:list` — list the caller's inbox Convenience listing of the caller's own `h/` records, filterable by `status`. Venues MAY implement this as a thin wrapper over the workspace listing operations; clients MUST NOT depend on it existing and can always fall back to `covia:list h/`. ### Cancellation There is no `hitl:cancel` operation. The requester cancels through the standard Job cancellation surface; the venue then marks the record `cancelled`. Responder clients MUST treat a `cancelled` record as closed. ## Authorisation Delivering a request into a user's inbox is a **cross-user act** and is authorised at the point of delivery, following the capability model of [COG-13](./COG-013): - **Ability**: `hitl/request` on the resource `/h/`. - **Self-ask**: when the target user *is* the authenticated requester's own identity (the common case — an agent asking its owner), delivery is always permitted. - **Cross-user and federated asks**: the requester MUST present authority covering `hitl/request` on the responder's inbox — a UCAN delegation issued by the responder, or authority admitted by the venue's trust policy ([COG-15](./COG-015)). Venues MAY additionally apply local policy (allow-lists, per-user opt-in) at this boundary. - **Responding** is restricted to the inbox owner in this version. Delegable response authority (e.g. an assistant answering on a user's behalf via a `hitl/respond` delegation) is a candidate extension. - **Anonymous callers** cannot submit or answer requests: both operations require an authenticated identity, and the default public capability ceiling ([COG-10](./COG-010)) withholds `invoke` in any case. Because responding can issue capability grants, venues MUST authenticate responders with the same strength they require for capability issuance generally. ## Capability Grants A request MAY offer the responder the opportunity to confer authority on the requester. Every offered grant is bound to a specific affirmative choice — *if you choose this, you give this*: 1. **Approval-ask** `grants` — conferred when that ask is approved (answered `true`) 2. **Option** `grants` — conferred when that option is selected Grants attach nowhere else. There are no request-level grants: authority transfer is never a side effect of merely answering a request, and a grant that is not the direct consequence of a choice the responder made MUST NOT be issued. A plain "grant me access to X" is therefore expressed as an `approval` ask whose approval carries the grant. Each offered grant is a capability as defined by [COG-13](./COG-013): ```json { "with": "w/reports/", "can": "crud/read", "exp": 1795000000 } ``` :::note[Capability semantics are specified elsewhere] This COG deliberately specifies **no capability semantics of its own**. What a responder is able to grant, how delegations are issued and signed, how attenuation constrains onward use, and how issued tokens are trusted across venues are defined by [COG-17: Capability Granting](./COG-017) (with the capability model of [COG-13](./COG-013) and the cross-venue trust rules of [COG-15](./COG-015)). HITL specifies only the **consent surface**: which choices confer which offered grants, and how the resulting token travels. ::: ### Grant rules - An offered grant MUST lie within the responder's **grantable authority** as defined by [COG-17](./COG-017): the responder's own resources, or resources belonging to others over which the responder holds granting rights (`grant/…`). HITL is a consent surface, not a source of authority — a responder can confer through HITL exactly what they could delegate directly, and nothing more. - Grantability is enforced at **issuance time**, when the responder and their authority are known. A response echoing a grant outside the responder's grantable authority MUST fail without side effects. - Every grant issued through HITL MUST be time-bounded. A grant's `exp` is capped by venue policy; an offer without `exp` receives the venue's default grant lifetime. - A bare-path `with` in an offered grant denotes the **responder's** namespace. Venue-attested issuance MUST absolutise it to `/…` before signing ([COG-17](./COG-017)'s issuance rule — the venue's `iss` is not the responder, so a stored bare path would otherwise denote the venue's own namespace). ### Consent is explicit Silent authority transfer is the central risk of HITL, and the protocol guards against it structurally: - Responder clients MUST display every offered grant (resource, ability, expiry) attached to the choice that confers it, so the cost of approving or selecting is visible at the point of choice. - The response MUST **echo** the grants being approved in its `grants` field. The venue issues exactly the intersection of (a) the echoed grants and (b) the offers actually triggered by the choices made (approved approval asks and selected options). An echoed grant that was not offered-and-triggered MUST fail the response. Echo matching is verbatim — the response repeats the offer's exact `{with, can}` (a bare-path offer is echoed bare); absolutisation happens once, at issuance. - Answering a request while echoing no grants is always valid: the responder answers the questions but confers nothing. ### Issuance On a valid `answer` outcome with approved grants, a delegation token is issued whose **audience** is the requester (`from`) and whose **attenuations** are exactly the approved grants. Token format, the signing model (venue-attested, or signed by a self-sovereign responder), attenuation validation, and how verifiers trust the token's root are all governed by [COG-17](./COG-017) — not by this COG. HITL adds only the transport: the issued token is returned in the Job `output` as `token`, alongside the approved `grants` list, and the requester thereafter presents it as a standard proof. ## Cross-Venue Requests A HITL request targets a user **where their inbox lives**. To ask a user on a remote venue, the requester invokes `hitl:request` on that venue — directly, or via the grid federation operations — and [COG-15](./COG-015) supplies the machinery: - **Caller identity** crosses the hop as an audience-bound identity token; the responder's venue verifies the requester's own signature and attributes the request accordingly. The record's `from` is the verified caller DID and `origin` the originating venue. - **Authority** crosses in the proof channel: the requester forwards the UCAN delegation that covers `hitl/request` on the target user's inbox, and the remote venue enforces it at delivery exactly as in the local case. - **The Job lives on the responder's venue** (it carries the request); the requester's local grid job proxies its status and result through the standard federation job surfaces. - **Granted tokens** flow back in the Job output. The audience is the requester's DID, so the token is presentable on any venue, subject to the root-authority trust rules of [COG-15](./COG-015). A venue MUST apply the same delivery authorisation to federated requests as to local cross-user requests. Federation changes where the request comes from, not what it is allowed to do. ## Agent Skill Venues SHOULD ship a `hitl` skill ([COG-18](./COG-018)) in the venue skill library so any agent can discover and load the HITL capability. The skill bundles: - **Tools**: `v/ops/hitl/request`, plus the standard job-status operation for observing resolution - **Instructions**, covering at minimum: - Ask sparingly; batch related questions into one request rather than interrupting repeatedly - Put the full decision context in `description` — the human sees only the record, not the agent's conversation - Use the narrowest ask type that fits (`approval` for yes/no, `choice`/`checkboxes` for enumerable options, `text` only when free text is genuinely needed) - Set a `timeout` for time-sensitive decisions and handle the `FAILED` expiry outcome - Offer only the minimum grants needed, scoped as narrowly as possible; never treat a rejection as an error to retry ## Security Considerations - **Requester identity is venue-attested.** `from`, `agent`, and `origin` are set by the venue from the verified request context and are never caller-supplied. A record's provenance is as strong as the venue's ingress authentication. - **Social engineering.** A HITL request is a phishing surface: it asks a human to approve actions and confer authority. The cross-user `hitl/request` gate exists so users receive asks only from parties they (or their venue's policy) have accepted. Clients MUST render the verified `from`/`origin` identities prominently, and MUST NOT render requester-controlled text (title, description, prompts) in a way that spoofs client chrome or verified fields. - **Grant review.** All authority transfer is bound to a specific affirmative choice, opt-in per grant, echoed explicitly in the response, restricted to the responder's grantable authority ([COG-17](./COG-017)), and time-bounded. Clients that cannot render grants MUST NOT submit responses that echo them. - **Mutation and TOCTOU.** The record the responder reviews is the record the venue resolves: requests are immutable once delivered (only the venue-set `status`/`response` fields change), and the echo-consent rule means a response approves specific capabilities, not "whatever the record now says". - **Sensitive content.** Ask prompts and answers may carry sensitive data. The standard `secretFields` redaction applies to the carrying Job's persisted record; the `h/` record itself lives in the responder's own namespace under their access control. Requesters SHOULD NOT place secrets in requests destined for third parties. - **Resource exhaustion.** Every request parks a Job and writes a record. Venues SHOULD bound open requests per requester and per inbox, and SHOULD rate-limit `hitl:request`, particularly at the cross-user and federated boundaries. - **Expiry integrity.** Expiry enforcement MUST survive restarts; a venue that loses its timers must reconstruct them from the durable `expires` fields, not silently leave requests open forever when a timeout was promised. ## Examples ### Simple approval (agent asks its owner) ```json // invoke v/ops/hitl/request { "title": "Pay invoice INV-4711", "description": "Acme Ltd invoice INV-4711 for £12,400, matched to PO-2231. Payment due Friday.", "asks": [ { "id": "pay", "type": "approval", "prompt": "Approve payment of £12,400 to Acme Ltd?", "required": true, "comment": true } ], "timeout": 172800 } ``` The Job parks in `INPUT_REQUIRED`; the record appears at `h/` in the owner's inbox. The owner responds: ```json // invoke v/ops/hitl/respond { "id": "0195f3a2b4c6...", "outcome": "answer", "answers": { "pay": true }, "comments": { "pay": "Approved — this invoice only." } } ``` The Job completes with: ```json { "id": "0195f3a2b4c6...", "outcome": "answer", "answers": { "pay": true }, "comments": { "pay": "Approved — this invoice only." } } ``` ### Mixed asks with an offered grant ```json { "title": "Quarterly report run", "description": "Ready to generate the Q3 report pack. I need output preferences and read access to the source reports.", "asks": [ { "id": "detail", "type": "choice", "prompt": "Report depth?", "options": [ { "id": "summary", "label": "Executive summary" }, { "id": "full", "label": "Full detail" } ], "required": true }, { "id": "sections", "type": "checkboxes", "prompt": "Include:", "options": [ { "id": "fin", "label": "Financials" }, { "id": "ops", "label": "Operations" } ] }, { "id": "access", "type": "approval", "prompt": "Grant read access to w/reports/ for 7 days?", "grants": [ { "with": "w/reports/", "can": "crud/read", "exp": 1795000000 } ] }, { "id": "notes", "type": "text", "prompt": "Anything to emphasise?" } ] } ``` Response, explicitly echoing the approved grant: ```json { "id": "0195f3c1d2e8...", "outcome": "answer", "answers": { "detail": "full", "sections": ["fin", "ops"], "access": true, "notes": "Flag the Q3 logistics overspend." }, "grants": [ { "with": "w/reports/", "can": "crud/read", "exp": 1795000000 } ] } ``` The Job output includes the answers, the approved `grants`, and a `token` — a UCAN audienced to the requesting agent, presentable as a proof on its subsequent `w/reports/` reads. ### Rejection ```json { "id": "0195f3a2b4c6...", "outcome": "reject", "comment": "Wrong PO — resubmit against PO-2240." } ``` The Job fails with `error`: `HITL request rejected: Wrong PO — resubmit against PO-2240.` The requester sees the reason through the Job; the resolved record remains in the responder's inbox until deleted. ## Related Specifications - [COG-4: Grid Lattice](./COG-004) — user namespaces, including `h/`, and merge semantics - [COG-7: Operations](./COG-007) — the operation model implemented by the `hitl` adapter - [COG-8: Jobs](./COG-008) — the Job lifecycle carrying every HITL request - [COG-9: Agent Messaging](./COG-009) — the agent-to-agent counterpart of human-directed requests - [COG-10: Venue Authentication & Access Control](./COG-010) — caller authentication and the public capability ceiling - [COG-11: Agent Lifecycle](./COG-011) — agents and the run loop - [COG-13: Agent Capabilities](./COG-013) — capabilities, UCAN delegation, and attenuation - [COG-15: Cross-Venue Federation](./COG-015) — identity forwarding and authority relay for remote requests - [COG-17: Capability Granting](./COG-017) — the granting model beneath HITL's consent surface - [COG-18: Agent Skills](./COG-018) — the skill bundle that teaches agents the HITL surface - [COG-19: HITL Self-Sovereign Token Transport](./COG-019) — the `token` ask type extending this spec --- ## COG-17: Capability Granting ``` Status: Draft Version: 0.1 Created: 2026-07-19 Authors: Mike Anderson ``` :::caution[Work in Progress] This specification is under active development. Structure and details may change significantly based on implementation experience and community feedback. ::: :::note[Related specifications] [COG-13: Agent Capabilities](./COG-013) defines what capabilities *are* — their structure, resource and ability matching, and runtime enforcement. This document defines how they *move*: what a principal may grant, how grants are issued and signed, how delegation attenuates authority, and how issued tokens are trusted. Consent surfaces that confer authority — such as Human-in-the-Loop responses ([COG-16](./COG-016)) — reference this specification rather than defining granting semantics of their own. ::: This standard specifies **capability granting** on the Covia Grid — the transfer of authority between principals. ## Purpose Possession of authority and transfer of authority are different problems. [COG-13](./COG-013) answers "may this caller do this, here, now?". This COG answers the complementary questions: "what may this principal give away, to whom, and how does the recipient prove they received it?" The granting model rests on six principles: - **Provenance** — every unit of authority traces back, through an unbroken signed chain, to a root that owned it - **No amplification** — a grantor can confer at most what they hold; delegation can only narrow, never widen - **Production-gated granting** — new, chain-free authority is minted only at granting surfaces, for callers holding the corresponding granting right; chain delegation of held abilities is transitive and *visible*, never silent - **Explicit audience** — authority is granted *to a principal*, never to a bearer; possession of a token by anyone other than its audience confers nothing - **Time-bounded** — granted authority expires; permanence requires re-issuance, not a permanent token - **Verifiability** — grants are verifiable offline wherever possible, so authority crosses venue boundaries without callbacks to the grantor ## Terminology See [COG-1: Architecture](./COG-001) for Grid terminology, [COG-2](./COG-002) for DIDs, and [COG-13](./COG-013) for capabilities. | Term | Definition | | ---- | ---------- | | Principal | An identity that can hold and confer authority — a user, agent, or venue, identified by DID | | Grantor | The principal conferring authority | | Grantee | The principal receiving authority — the audience of the issued token | | Grant | A capability `{with, can}` conferred by a grantor on a grantee, with an expiry | | Delegation token | A signed token (UCAN, [COG-3](./COG-003)) encoding one or more grants from an issuer to an audience | | Grantable authority | The total authority a grantor is able to confer: root authority plus delegable held authority | | Root authority | Authority a principal holds by owning a resource; requires no proof beyond ownership | | Proof chain | The sequence of delegation tokens linking a presented grant back to a root authority | | Attenuation | The narrowing of authority at each delegation hop | | Granting right | The meta-ability `grant/` — the right to have a grant of `` minted or attested on one's instruction at a granting surface, distinct from the right to exercise `` | | Self-sovereign issuance | A grant signed by the grantor's own key | | Venue-attested issuance | A grant signed by a venue on the instruction of an authenticated grantor (custodial model) | ## Grantable Authority A principal's **grantable authority** is the union of: 1. **Root authority** — the resources the principal owns. Every DID-rooted resource (a user's namespaces, an agent's scoped identity) is grantable by its owner without any supporting proof: ownership *is* the authority. 2. **Held granting rights** — `grant/…` capabilities delegated *to* the principal by others (see [Granting rights](#granting-rights)). A granting right is exercisable only at a granting surface, together with its proof chain, and only within the bounds of what was received. A grant MUST lie within the grantor's grantable authority. There is no third source: a principal cannot grant what they neither own nor hold granting rights over, and no granting surface — an issuance operation, a HITL approval ([COG-16](./COG-016)), or anything else — can manufacture authority. Granting surfaces are consent mechanisms over this model, not sources of power. ### Granting rights The right to *use* an ability and the right to *confer* it are distinct, and the distinction is expressed in the ability hierarchy itself ([COG-13](./COG-013)): for any ability `X`, the meta-ability **`grant/X`** is the right to issue grants of `X`. ```json { "with": "w/reports/", "can": "grant/crud/read", "exp": 1798000000 } ``` Alice issuing this to Bob lets Bob grant `crud/read` on Alice's `w/reports/` to others. It does **not** let Bob read `w/reports/` himself — `grant/crud/read` does not cover `crud/read`. Use and stewardship are separate rights, conferred together only when both are intended. The scheme composes under the standard prefix-coverage rules, including recursively: | Held ability | A surface may mint | A surface may NOT mint | |--------------|--------------------|------------------------| | `grant/crud/read` | `crud/read` | `crud/write`; any `grant/…` | | `grant/crud` | `crud` and any `crud/…` | any `grant/…` | | `grant/grant/crud` | `grant/crud`, `grant/crud/read`, … | `crud/read` (no direct use-grants) | | `grant` | any ability, granting rights included | — | **Two distinct mechanisms, structurally disjoint.** Holding a use-ability `X` lets a principal **chain-delegate** a narrowed `X` onward under standard transitive attenuation — self-serve, no granting right needed, and *visible*: the proof chain names every delegator, so re-delegation is **audited rather than blocked** (blocking would be illusory — a holder of `crud/read` can always proxy the data out-of-band). Holding `grant/X` lets a **granting surface mint fresh, chain-free authority** on the principal's instruction — needed when the recipient requires a clean root (cross-venue trust, no chain disclosure), or when the steward holds no use-right at all. Because `grant/X` does not cover `X`, a granting right can never be chained into a use-right: it is exercisable only through a producing surface. An agent acting under its owner's identity (the user-scoped model of [COG-13](./COG-013)) exercises the owner's own authority beneath a self-attenuated ceiling — no re-delegation is involved and no granting right is needed. Granting rights matter when authority crosses to a *different* principal. ### No amplification At every delegation hop, the conferred capability MUST be covered by the issuer's own authority at that hop, under the resource and ability matching rules of [COG-13](./COG-013). The effective authority of a chained grant is the intersection of every hop from root to leaf: each link can narrow the resource scope, the ability, and the validity window, and can never widen any of them. Verifiers MUST enforce this per hop, fail-closed — a chain with a single amplifying link conveys nothing. Note what verifiers do **not** check: granting rights. Chain delegation of held use-abilities is transitive by design (standard UCAN semantics), with the chain itself as the audit record. The `grant/…` rules of this COG bind **token production** — a granting surface refuses to mint what the caller may not grant — not chain resolution. Verification stays entirely grant-agnostic. ### Granting is not attenuation Two operations on authority must not be confused: - **Self-attenuation** — a principal narrowing their *own* effective authority (e.g. a session ceiling, an agent's `caps` vector, [COG-13](./COG-013)). This creates no new authority and needs no issuance: it only ever shrinks what the same principal can do. - **Granting** — conferring authority on a *different* principal. This always requires issuance of a delegation token audienced to the grantee. The asymmetry is deliberate and security-relevant: narrowing paths can never be used to widen, and widening (for someone else) exists only through the explicit, signed, auditable issuance path defined here. ## Grant Structure A grant is a capability as defined by [COG-13](./COG-013), plus a validity bound: ```json { "with": "w/reports/", "can": "crud/read", "exp": 1795000000 } ``` | Field | Type | Description | |-------|------|-------------| | `with` | string | Resource, per [COG-13](./COG-013) resource matching. A bare path denotes **the token issuer's own namespace**, resolved at evaluation time against the token's signed `iss` (which always travels with the grant); a custodial issuer therefore writes the principal's absolute DID URL at issuance, since its own `iss` is not the principal. A DID URL form addresses another principal's resource (valid only when the grantor holds a granting right over it) | | `can` | string | Ability, per the [COG-13](./COG-013) ability hierarchy | | `exp` | integer | Expiry (Unix seconds). REQUIRED on issued grants — see [Expiry](#expiry-and-revocation) | | `nb` | object | OPTIONAL caveats constraining use of the capability (UCAN v0.10 `nb` field). Caveat vocabularies are not yet specified; a verifier that does not implement a present caveat MUST treat the capability as not covering the request (fail closed) | Grants are carried in the attenuations (`att`) field of a delegation token; the token contributes the issuer (grantor), audience (grantee), validity window, and proof chain ([COG-3](./COG-003)). ## Issuance ### Signing models **Self-sovereign issuance.** The grantor signs the token with their own key. Because root ownership of a DID-rooted resource is verifiable from the DID itself, a self-sovereign chain verifies **offline, anywhere**: any venue can check that the root issuer owns the root resource and that every hop attenuates correctly, without trusting any intermediary or calling home. This is the preferred model wherever the grantor's client holds their key. **Venue-attested issuance (custodial).** The venue signs on the instruction of an authenticated grantor — necessary when the venue custodies the user's identity (e.g. users authenticated by session rather than by key). The token's root issuer is the venue, so its acceptance elsewhere is subject to the verifier's trust policy for venue-attested roots ([COG-15](./COG-015)). Venues MUST record which authenticated principal instructed each attested issuance. The two models differ only at the root; attenuation, audience binding, and verification are identical from the first delegation hop onward. ### The issuance operation Venues SHOULD expose granting as a standard operation ([COG-7](./COG-007)): | Input field | Type | Description | |-------------|------|-------------| | `aud` | string | REQUIRED. Grantee DID | | `att` | array | REQUIRED, non-empty. The grants to confer | | `exp` | integer | REQUIRED. Token expiry (Unix seconds); MUST NOT exceed venue policy caps | | `prf` | array | Proof tokens, when granting held (non-root) authority | Output: the signed delegation token. The venue MUST verify, before signing, that every `att` entry lies within the caller's grantable authority — root ownership for the caller's own resources, or a proof chain establishing a granting right (`grant/…`) over the resource — and MUST refuse otherwise. A bare path in the request is absolutised to the caller's DID before signing: in a venue-signed token a stored-bare path would denote the *venue's* own namespace, not the caller's. (The operation-level ability `ucan/delegate` ([COG-13](./COG-013)) gates access to the issuance surface itself; granting rights govern what may be issued through it.) Issuance is a mutating act of consequence: it MUST require an authenticated caller and MUST be deniable by capability policy like any other operation ([COG-10](./COG-010)). ### Delivery How an issued token reaches its grantee is out of scope: Job outputs ([COG-16](./COG-016)), agent messages ([COG-9](./COG-009)), API responses, or out-of-band channels are all acceptable. Delivery requires no confidentiality for authority purposes — a token is inert in any hands but its audience's — though tokens MAY reveal information (resource paths, relationships) that warrants care. ## Verification and Trust Verification separates two layers, both fail-closed: 1. **Integrity** — every token in the chain carries a valid signature by its issuer; every token is within its validity window; every link connects (each proof's audience is the next token's issuer); validity windows narrow monotonically toward the leaf. 2. **Authority** — the presented grant is covered by the leaf token's attenuations; each hop attenuates, per [No amplification](#no-amplification); and the **root is authoritative** for the resource: either the root issuer owns it (self-sovereign — universally acceptable), or the root is a venue the verifier trusts for attestation ([COG-15](./COG-015)). A verifier's own identity is irrelevant to the result: the same chain yields the same verdict on any venue, which is what lets granted authority travel with its holder across the grid ([COG-15](./COG-015)). ## Expiry and Revocation - Every issued grant MUST be time-bounded. Venues MUST cap requested expiries by policy and SHOULD apply a conservative default lifetime when none is requested. Indefinite grants MUST NOT be issued. - Delegation tokens are not revocable before expiry in this version. The design compensates by making expiry cheap: prefer **short-lived grants re-issued on demand** over long-lived ones. Long-lived authority SHOULD be modelled as a standing relationship at the granting surface (e.g. a recurring approval), not as a long-lived token. - Revocation mechanisms (revocation lists, status endpoints, chain-invalidation) are future work for this COG. ## Granting Surfaces Any mechanism through which a grantor consents to a grant is a **granting surface**. Surfaces own consent UX and context; this COG owns the semantics beneath all of them: | Surface | Specified in | Notes | |---------|-------------|-------| | Direct issuance operation | this COG | Programmatic granting by an authenticated principal | | HITL response | [COG-16](./COG-016) | Grants bound to explicit human choices, echo-consented | | Venue relay delegation | [COG-15](./COG-015) | A `venue/relay` grant authorising a venue to act as delegate on cross-venue hops | | Identity token | [COG-15](./COG-015) | An empty-`att` token — authentication, not a grant; listed to mark the boundary | | Agent attenuation (`caps`) | [COG-13](./COG-013) | Not a grant — self-attenuation of the owner's authority, no issuance involved | A granting surface MUST ensure the grantor's **informed consent**: the grantor (or their authenticated client) sees resource, ability, and expiry of every grant before issuance. The echo-consent rule of [COG-16](./COG-016) is the reference design for human surfaces. ## Security Considerations - **No bearer authority.** Audience binding is the backbone: intercepted, logged, or relayed tokens confer nothing on their holders, and a deputy presenting a caller's proofs cannot convert them to its own use. Weakening audience binding for convenience (bearer-style grants) is not permitted by this specification. - **Custodial root risk.** A venue that issues attested grants can, if compromised, mint arbitrary authority over the resources of every user it custodies. Verifier trust policies for venue-attested roots ([COG-15](./COG-015)) bound the blast radius; self-sovereign issuance avoids the exposure entirely and SHOULD be preferred where keys permit. - **Amplification defects.** The most dangerous implementation bug is a verifier that checks the leaf but not every hop. Verification MUST walk the full chain to the root and MUST fail closed on any unverifiable link. - **Re-delegation is audited, not prevented.** A holder of a use-ability can chain-delegate it (or simply proxy the resource); the proof chain names every delegator. Grantors who cannot accept that exposure should not grant the ability — or should grant it caveated (`nb`, above) once a gating caveat vocabulary is specified. - **Over-broad grants.** Grantors SHOULD confer the narrowest resource, weakest ability, and shortest expiry that serves the purpose; granting surfaces SHOULD make narrow grants the path of least resistance. - **Granting rights are the sharpest tool.** A `grant/…` capability multiplies exposure: its holder can mint authority for arbitrary third parties for as long as it lives. Confer granting rights narrowly (specific abilities, specific subtrees), briefly, and rarely; recursive rights (`grant/grant/…`) more rarely still. - **Consent integrity.** An issuance made without the grantor's informed consent is an authority theft regardless of cryptographic validity. Surfaces MUST NOT issue beyond what was displayed and consented to. - **Chain complexity.** Verifiers MAY bound proof-chain depth and token size to prevent resource-exhaustion attacks via pathological chains. - **Irrevocability.** Until revocation exists, every issued token is a commitment for its full lifetime. Expiry caps are the safety net; venues SHOULD set them accordingly. ## Examples ### Root grant (self-sovereign) Alice grants her agent read access to her reports for a week, signing with her own key. Token payload sketch: ```json { "iss": "did:key:z6MkAlice...", "aud": "did:key:z6MkAgent...", "exp": 1795600000, "att": [ { "with": "did:key:z6MkAlice.../w/reports/", "can": "crud/read" } ], "prf": [] } ``` Empty `prf`: Alice owns the resource, so her signature is the root authority. Any venue verifies this offline. ### Chain delegation (transitive, visible) Bob holds `crud/read` on Carol's shared workspace and chain-delegates a narrowed form to his analyst — self-serve, no granting right involved: ```json { "iss": "did:key:z6MkBob...", "aud": "did:key:z6MkAnalyst...", "exp": 1795300000, "att": [ { "with": "did:key:z6MkCarol.../w/shared/2026/", "can": "crud/read" } ], "prf": [ "" ] } ``` Every hop narrows (resource, ability, expiry); the chain roots at Carol, who owns the resource; and the chain itself is the audit record — any verifier sees that Bob delegated. Transitive by design: Carol granting Bob read accepts that Bob can pass the data on (he could proxy it regardless); the chain makes it visible. ### Delegated granting (stewardship, via a surface) Carol lets Bob *manage* read access to her shared workspace — without being able to read it himself: ```json { "iss": "did:key:z6MkCarol...", "aud": "did:key:z6MkBob...", "exp": 1798000000, "att": [ { "with": "did:key:z6MkCarol.../w/shared/", "can": "grant/crud/read" } ], "prf": [] } ``` Bob cannot chain this into a read grant himself (`grant/crud/read` does not cover `crud/read` — the chain would fail attenuation). Instead he instructs a **granting surface**, presenting Carol's token as proof: ```json // ucan:issue, called by Bob { "aud": "did:key:z6MkAnalyst...", "att": [ { "with": "did:key:z6MkCarol.../w/shared/2026/", "can": "crud/read" } ], "exp": 1795300000, "prf": [ "" ] } ``` The surface verifies Bob's grantable authority covers `grant/crud/read` on a covering resource, then mints a fresh, chain-free token (venue-attested; accepted per the verifier's [COG-15](./COG-015) trust policy). Had Carol granted Bob nothing, the surface would refuse — the granting rule binds production, not resolution. ### Venue-attested grant from a HITL approval A responder approves a HITL ask carrying an offered grant ([COG-16](./COG-016)). Their venue custodies their identity, so it issues the token: `iss` = the venue's DID, `aud` = the requesting agent, `att` = exactly the echoed grants, `exp` capped by policy. The agent presents it on later requests; venues that trust the issuing venue for attestation accept it, per their [COG-15](./COG-015) trust policy. ## Related Specifications - [COG-2: Decentralised ID](./COG-002) — principal identity and resource ownership roots - [COG-3: Authentication Mechanisms](./COG-003) — UCAN token format and transport - [COG-9: Agent Messaging](./COG-009) — a delivery channel for issued tokens - [COG-10: Venue Authentication & Access Control](./COG-010) — authentication of grantors and capability policy over issuance - [COG-13: Agent Capabilities](./COG-013) — capability structure, matching, attenuation, and enforcement - [COG-15: Cross-Venue Federation](./COG-015) — root authority trust policies and cross-venue authority transport - [COG-16: Human-in-the-Loop Requests](./COG-016) — the human consent surface over this granting model --- ## COG-18: Agent Skills ``` Status: Draft Version: 0.1 Created: 2026-07-22 Authors: Mike Anderson ``` :::note[Related specifications] A skill is an ordinary asset: its metadata follows [COG-5: Asset Metadata](./COG-005) and its body is content as defined by [COG-6: Artifacts](./COG-006). The tools a skill contributes are operations ([COG-7](./COG-007)), invoked by agents ([COG-11](./COG-011)) under the capability rules of [COG-13: Agent Capabilities](./COG-013). This document introduces **no new asset type, no new authority primitive, and no new trust relationship** — it specifies a discovery and loading convention over existing ones. ::: This standard specifies **agent skills** on the Covia Grid — named, discoverable bundles of instructions, context, and tools that an agent loads at run time. ## Purpose An agent's knowledge and abilities are otherwise fixed at configuration time: its system prompt, its pinned context entries, and its tool palette are all declared by its operator in advance. That produces three problems at scale: - **Context waste.** Material needed for occasional work must either be pinned into every turn or omitted entirely. Pinning spends context budget on every turn for material used on one turn in fifty. - **Duplication.** Agents sharing a domain restate the same procedures and tool lists in their configurations. Revising a procedure means editing every agent. - **No self-service.** An agent that discovers mid-task that it needs an unfamiliar capability has no way to acquire one, even when the venue already holds a well-written description of exactly that capability. Skills address these with **progressive disclosure**. A compact index — one line per skill, naming it and saying when to load it — is always in context. The full bundle loads only when the agent asks for it, and then persists for the remainder of the conversation. The wider purpose is portability. Because a skill is an asset, a capability written once at one venue is content-addressed, verifiable, and loadable by any agent at any venue that can resolve it — the same federation properties artifacts already have, applied to agent competence. ## Terminology See [COG-1: Architecture](./COG-001) for Grid terminology and [COG-5](./COG-005) for asset terminology. | Term | Definition | | ---- | ---------- | | Skill | An asset referenced as a loadable bundle of instructions, context, and tools | | Skill facet | The `skill` object in the asset's metadata, carrying the loadable extras | | Body | The skill's instructions — the asset's content, resolved as for any artifact | | Toolset | A skill with no content: a tool bundle plus its index line, and nothing to inject | | Source | An address an agent searches for skills — a directory path or a single skill asset | | Skill index | The rendered one-line-per-skill listing injected into the agent's context each turn | | Load | The act of placing a skill's body, context, and tools into an agent's effective context | | Loads tier | The agent's existing scoped store of loaded context entries, in which a loaded skill is one entry | ## Principles - **A skill is an asset.** Not a new type. Skill metadata is ordinary asset metadata — `name`, `description`, `content` — with loadable extras under a `skill` facet, exactly as invocability sits under `operation` ([COG-7](./COG-007)). Nothing is a skill by virtue of its shape; an asset is treated as a skill because it is **referenced as one**. - **Progressive disclosure.** The index is always present and compact; bodies and tools arrive only on demand. The cost of a skill an agent never loads is one line. - **No parallel machinery.** Loading writes an ordinary entry into the agent's existing context loads tier. Persistence, budgets, inventory, eviction, and unloading are inherited unchanged. - **Live resolution.** The index and every loaded body re-resolve from their sources each turn. Skills stay current; editing a skill changes the behaviour of agents already carrying it on their next turn. - **Loading grants no authority.** A skill can offer an operation; it can never confer the right to invoke one. Every tool call remains capability-checked at invocation ([COG-13](./COG-013)). - **Fail-visible.** Absent sources are skipped quietly. Broken ones are *shown* — an agent must never silently lose a capability it believes it holds. ## Specification ### Skill Metadata A skill's metadata is asset metadata per [COG-5](./COG-005): ```json { "name": "pdf-processing", "description": "Extract text and tables from PDF files", "content": { "contentType": "text/markdown", "sha256": "74f16013e2b7..." }, "skill": { "tools": ["v/ops/file/read", "v/ops/schema/validate"], "context": [ { "ref": "w/docs/pdf-rules", "label": "PDF handling rules" } ], "budget": 2000 } } ``` | Field | Requirement | Role | |-------|-------------|------| | `name` | RECOMMENDED | Standard asset field. Where a skill appears as a value in a directory, the **key is canonical** for identity and an inner `name` is ignored. | | `description` | REQUIRED | The index line: what the skill does, and when to load it. It is a selection aid, never the documentation. | | `content` | OPTIONAL | The standard content descriptor locating the body. Absent for a pure toolset. | The **`skill` facet** carries the loadable extras. All of its fields are OPTIONAL, and an empty facet is valid: | Facet field | Type | Description | |-------------|------|-------------| | `tools` | array of string | Operation refs added to the agent's tool palette while the skill is loaded. Same form as the agent's configured tools. | | `context` | array | Context entries loaded alongside the body, in the agent's standard context entry grammar. | | `budget` | integer | Default accounting budget when the skill is loaded, in **bytes** (the same storage-size units as the agent's context budgets). A loader MAY override it. | Implementations MUST reject a skill whose `description` is missing, and MUST report the rejection visibly (see [Fail-visible behaviour](#fail-visible-behaviour)). A non-string entry in `skill.tools` is an error at load time; a tool ref that merely fails to *resolve* MUST NOT fail the load — it MUST be reported to the agent so it can adapt. ### The Body Is the Asset's Content A skill's body is resolved by the venue's ordinary content resolution and decoded as UTF-8. Any content location valid for an artifact ([COG-6](./COG-006)) is valid for a skill body: - a content-addressed blob referenced by `content.sha256`; - inline content declared in the metadata itself, for small bodies — the bytes are then part of the metadata, so the asset's own identity hash ([COG-5](./COG-005)) covers them and no separate content hash is required; - a filesystem or drive binding, pinned when a `content.sha256` is also declared (hash-verified, drift fails loudly) or live when it is not. There is deliberately **no description-as-body fallback**. The description is the index line; a skill with no content has no body and is a **pure toolset** — its `skill.tools` plus its index line. When a toolset is loaded, implementations SHOULD display its description in place of a body, as a rendering fallback rather than as instructions. ### Markdown Skills A skill whose content is a markdown document with YAML frontmatter — the widely used `SKILL.md` convention — MUST be accepted unchanged: ```markdown --- name: pdf-processing description: Extract text and tables from PDF files --- ## PDF processing ... ``` Where the asset metadata does not supply `name` and `description`, they are read from the frontmatter and the body is the markdown following it. Only `name` and `description` are read; other frontmatter keys are ignored. **Asset metadata always wins** — frontmatter is a compatibility fallback, not an alternative source of truth. This compatibility is deliberate: an ecosystem of hand-written skills already exists, and requiring authors to re-encode them would be a barrier with no technical benefit. ### Facet Composition Facets compose. An asset MAY carry both an `operation` facet and a `skill` facet: ```json { "name": "report-bug", "description": "File a well-formed bug report in the tracker", "content": { "contentType": "text/markdown", "sha256": "..." }, "operation": { "adapter": "mcp:tools:call", "remoteToolName": "create_issue" }, "skill": {} } ``` When such an asset is loaded as a skill, the asset itself MUST join the tool palette, in addition to any `skill.tools`. The result is a **self-documenting tool**: the body explains, the operation executes, and both are one asset with one identity. The converse also holds — `skill.tools` may reference any operation in the catalog, including bridged ones. ### Sources and Addressing An agent declares an ordered list of **sources**. Each is resolved positionally, not by shape: | Source form | Interpretation | |-------------|----------------| | A path (`w/skills`, `v/skills`, a DID-URL path) | A **directory**: a map whose keys are skill names and whose values are either skill metadata maps or **string references** followed one hop to the skill asset | | An asset ref (`a/`) | A **single skill** | Skills may therefore live anywhere the Grid addresses: | Location | Example | Use | |----------|---------|-----| | Asset store | `a/` | The canonical form — immutable, content-addressed, shareable across venues | | Venue catalog | `v/skills/` | Venue-installed skills, published read-only to every caller | | User workspace | `w/skills/` | Personal skills and quick iteration | Resolution rules: - A source that resolves to **null** MUST be skipped quietly. Sources are allowed to be created later. - A source whose resolution **throws** MUST render one visible line in the index rather than being skipped. - A directory value that is neither a metadata map nor a string reference MUST render a visible invalid line for that key. - On name collision, the **first source in declared order wins**. This makes shadowing well-defined: a user's own `w/skills/grid` takes precedence over the venue's `v/skills/grid`. A malformed source list — not a list, or containing a non-string entry — is a configuration error and MUST fail loudly at agent transition time rather than being masked. ### The Skill Index When an agent declares at least one source, its context MUST include, each turn, a compact index of the skills those sources offer: ``` [Skills] Named skill packs you can load with skill_load({name: "..."}). Loading injects the skill's instructions into your context (persists across turns; unload with context_unload) and adds its tools to your palette. - pdf-processing — Extract text and tables from PDF files - code-review — Review code against the house style (loaded) - broken-skill — INVALID: missing description [skills source w/other-skills — unavailable: timeout] ``` - One line per skill: `- — `. - A `(loaded)` marker where the skill is already in effective context. - Resolved fresh each turn. Nothing is cached across turns. The index is a **selection surface, not documentation**. Its per-skill cost bounds how many skills an agent can usefully be offered, which is why the `description` field is normatively a single line about *what and when*. ### Loading Loading is performed by a harness tool, conventionally `skill_load`, which takes either a `name` (looked up across the agent's declared sources) or a `ref` (a direct address, including one the agent was told about at run time and that no source lists), and an optional budget. Loading MUST: 1. Resolve the skill, returning a diagnosable error naming the skill and the reason if it cannot. 2. Write a **skill-flagged entry** into the agent's innermost loads tier. 3. Resolve the skill's tools — `skill.tools`, plus the asset itself where it carries an `operation` facet — into the agent's palette, effective within the same transition. 4. Load the skill's `skill.context` entries alongside the body. 5. Return the body in the tool result, so the instructions are usable in the turn that requested them rather than the turn after. The loads entry records the skill's canonical path, its budget, and its resolved tool refs. The **body is not denormalised** into the entry: it re-resolves each turn, so skills stay live. Tool refs are denormalised, so a change to a skill's tool list takes effect on reload while body and context changes take effect on the next turn. **A skill's identity is its address — the directory key (canonical path) it was loaded from.** Loading, replacement, and unloading are keyed by that path: reloading the same path **overwrites** the entry (which is how an edited tool list takes effect on reload), and a caller who wants a content-pinned skill uses an asset ref (`a/`) as the address, whose metadata value hash ([COG-5](./COG-005)) pins the body via its content binding. The metadata value hash serves separately as a **content-equality check**, not an identity: loading a skill whose resolved metadata is identical to one already loaded under a *different* address MUST be a no-op naming the existing entry, and the `(loaded)` marker MUST match by path or by content equality, so that a skill loaded by asset hash still marks its directory line. Unloading uses the agent's existing context-unload facility on the skill's path; removing the entry removes the body, the skill's context entries, and its tools from the next turn onward. Implementations SHOULD NOT introduce a separate unload verb for skills. ### Discovery Venues SHOULD expose a read-only discovery operation over skills, offering at minimum: | Command | Input | Output | |---------|-------|--------| | `list` | Optional sources | The rendered index text, or null where no skills exist | | `read` | A name or a ref | The skill in full: name, description, body where present, tools, context, and canonical path | Discovery MUST render through the same code path as the injected index, so that what a caller inspects and what an agent sees cannot drift. The operation is available to agents, human callers, and external clients alike, which makes a venue's skill library **publicly enumerable** in the same way its operation catalog is. It also lets an operator inject a skill index into an agent as ordinary context without activating loading — useful where an agent should know what exists but not be able to acquire it. There is deliberately **no skill-write surface**. Skills are authored with the ordinary write and asset-store operations; a skill is data, and creating one requires exactly the authority creating any other asset requires. ### Fail-visible Behaviour Skills change what an agent believes it can do, so failures MUST be visible rather than silent: | Condition | Required behaviour | |-----------|--------------------| | Source resolves to null | Skipped quietly (absence is normal) | | Source resolution errors | One visible index line naming the source and the reason | | Skill missing a description | Visible invalid index line; load refused with a diagnosable error | | Loaded skill no longer resolves | Visible unavailable element in place of the body | | Loaded skill no longer fits its budget | Visible notice, not a silent omission | | A `skill.tools` ref does not resolve | Reported in the load result; the load still succeeds | ### Capability Model Reading a skill is a read: implementations MUST apply the ordinary read capability check for the source actually consulted — namespace read for workspace, venue, and DID paths, and asset read for content-addressed refs ([COG-13](./COG-013)). Loading a skill confers **no authority whatsoever**. A skill may name operations the loading agent has no capability to invoke; those calls MUST fail at invocation exactly as they would have failed before the skill was loaded. Capability and competence are separate axes: a skill tells an agent *how*, never *whether it may*. ### Federation Because a skill is an asset, it crosses venues by the mechanisms artifacts already use ([COG-6](./COG-006), [COG-15](./COG-015)): the ref is content-addressed, the metadata's identity hash covers the content binding, and a receiving venue verifies rather than trusts. A skill authored at one venue and loaded at another is byte-identical or it is a different skill. Two consequences follow, and both are properties of the asset model rather than of skills: - A skill ref may be handed between agents — including across venue boundaries — and loaded directly, without the recipient's operator having listed its source. - The operations a skill names are resolved **at the loading venue**. A skill is portable; the tools it names are only as portable as the catalog behind them, which is why unresolved tool refs are reported rather than fatal. ## Security Considerations ### Skill Content Is Prompt Content A loaded skill's body enters the agent's context verbatim and its tools join the palette. A skill author is therefore trusted by the loading agent's operator to the same degree as the author of the agent's pinned context or system prompt. Operators MUST treat a skill source as a trust boundary and point agents only at sources they trust. This is the identical trust rule that already applies to configured context, and it is *not* weakened by skills — but the run-time, agent-initiated nature of loading makes it easier to overlook. Implementations SHOULD make the provenance of a loaded skill inspectable, and content-addressed skill refs SHOULD be preferred where provenance matters, since a hash pins exactly what will be injected. ### Loading Is Not Authorisation The most likely misreading of this specification is that loading a skill grants the ability to use its tools. It does not. Every invocation is checked against the agent's capabilities at the moment of the call ([COG-13](./COG-013)), and an agent that loads a privileged skill without holding the corresponding capabilities gains instructions it cannot act on. Implementations MUST NOT treat presence in a loaded skill's tool list as evidence of authority. ### Resource Exhaustion Each loaded skill consumes context budget every turn. Implementations MUST bound the per-skill budget, MUST account loaded skills within the agent's overall context budget, and SHOULD evict under pressure by a defined order rather than failing the turn. Directory sources are read per turn to build the index, which bounds practical directory size; venues expecting large libraries should expose a listing that does not require reading every value. ### Live Resolution Is a Live Surface Because bodies re-resolve each turn, editing a skill silently changes the behaviour of every agent currently carrying it. This is the intended contract — it is what makes skills maintainable — but it means write access to a skill source is effectively write access to the prompts of the agents using it. Where that is unacceptable, use pinned, content-addressed skills: a hash cannot be edited underneath a loaded agent. ## Related Specifications - **[COG-5: Asset Metadata](./COG-005)** — the metadata a skill is - **[COG-6: Artifacts](./COG-006)** — content resolution and verification for skill bodies - **[COG-7: Operations](./COG-007)** — the tools a skill contributes, and the facet model it mirrors - **[COG-11: Agent Lifecycle](./COG-011)** — the agents that load skills - **[COG-13: Agent Capabilities](./COG-013)** — the authority check that loading never bypasses - **[COG-15: Cross-Venue Federation](./COG-015)** — how skills, as assets, cross venue boundaries --- ## COG-19: HITL Self-Sovereign Token Transport ``` Status: Draft Version: 0.1 Created: 2026-07-24 Authors: Mike Anderson ``` :::note[Related specifications] This standard extends Human-in-the-Loop requests ([COG-16](./COG-016)) with one new ask type, and complements Capability Granting ([COG-17](./COG-017)): where COG-17 has a venue **mint** a capability grant, this standard has the venue **transport** one the user signs themselves. The tokens it carries are ordinary UCANs ([COG-3: Authentication Mechanisms](./COG-003)) verified under the cross-venue trust rules of [COG-15: Cross-Venue Federation](./COG-015) and [COG-10: Venue Authentication & Access Control](./COG-010). It introduces **no new token format and no new authority primitive** — only a way to route a user-signed grant from a human to a requester. ::: This standard specifies how a requester — typically a resident agent — obtains a capability token it can use on **another venue**, by asking its human to sign that token with their own key. ## Purpose An agent runs on the venue that hosts it. To act on a **different** venue, where its owner holds resources, it needs authority that venue will accept. A venue-minted grant (COG-17) cannot serve: it is rooted in the *issuing* venue's authority, so any other venue rejects it. The authority that travels is a **self-sovereign** grant — one the resource owner signs with their own key, rooted in themselves, and therefore verifiable on any venue where they are the root authority ([COG-15](./COG-015)). A user cannot be expected to sign such grants out of band on demand. The natural place to request one is the human-in-the-loop channel the user already watches: the agent asks, the human reviews the requested capabilities and signs — or declines. This standard defines that exchange. ### The distinction it rests on An access token and a capability grant are **the same object** — a UCAN is simultaneously a delegation (`att` capabilities from `iss` to `aud`) and the credential the audience presents. The meaningful axis is not token-versus-grant; it is **who signs the root**, and therefore where the grant is valid: | | Venue-minted grant (COG-17) | Self-sovereign grant (this COG) | | --- | --- | --- | | Signer / `iss` | the venue | the user's `did:key` | | Rooted in | the venue's authority | the user, self-sovereignly | | Valid on | resources that venue controls | any venue where the user is root authority | | Venue's role | **mints** it | **transports** it | A `token` ask carries a self-sovereign grant. It is a *scoped* delegation — its `att` is the capabilities the responder chose to sign, typically those requested — never a bare identity token that would confer the user's whole authority. ## Terminology See [COG-1: Architecture](./COG-001) for Grid terminology, [COG-16](./COG-016) for HITL terminology (request, ask, response, inbox), and [COG-3](./COG-003) for UCAN terminology. | Term | Definition | | ---- | ---------- | | Token ask | An ask of type `token`: a request for a self-sovereign capability grant the human signs client-side | | Token spec | The `token` object on a token ask, describing the requested grant | | Requester | The principal that issued the HITL request — the token's default audience | | Responder | The human answering — the inbox owner, who signs with their own key | | Transport | Carrying the signed grant from responder to requester unchanged; the venue neither mints nor re-signs | ## Principles 1. **The venue transports, it does not mint.** For a token ask the venue never signs and never alters the grant. It carries a token the responder signed. 2. **The venue is a checked courier, not an issuer.** It holds no user key and cannot forge or widen the grant. The token's integrity does not depend on trusting the venue: a compromised venue can at worst fail to deliver, or deliver a token that fails verification at its destination. 3. **Provenance, not policy.** The venue verifies that the responder actually signed *this* grant for the requested audience with a live expiry. It does **not** judge the capabilities against the request — the responder is authoritative over their own key and MAY have edited them under review. 4. **Only the human answers.** As for all granting-bearing HITL responses ([COG-17](./COG-017)), an agent MUST NOT answer a token ask, including one in its own owner's inbox. 5. **Self-sovereign users only.** The responder's `iss` MUST be a `did:key` they can sign with. A venue-custodial identity has no client key and uses the mint path ([COG-17](./COG-017)) instead. ## Specification ### The token ask A token ask is an ask (COG-16) with `type` = `token` and a `token` spec: ```json5 { "id": "b-access", "type": "token", "prompt": "Grant this agent read access to your invoices on venue B?", "required": true, "token": { "caps": [ { "with": "", "can": "" } ], // REQUIRED, non-empty "lifetime": 3600, // OPTIONAL, seconds — a requested lifetime hint "audience": "", // OPTIONAL — default: the request's `from` "venue": "" // OPTIONAL — informational, for the responder's UI } } ``` | Field | Requirement | Role | | --- | --- | --- | | `caps` | REQUIRED | The requested capabilities, a non-empty array of `{with, can}`. Describes what the agent asks for; the human decides what to sign. | | `lifetime` | OPTIONAL | A requested lifetime in seconds. A hint — the responder sets the actual `exp` when signing. Named `lifetime`, not `exp`, because the UCAN `exp` claim is an absolute timestamp. | | `audience` | OPTIONAL | The DID the signed token must be audienced to. Defaults to the request's `from` (the requester). | | `venue` | OPTIONAL | The target venue, shown in the responder's UI. Purely informational. | The `token` spec is a first-class validated field. A conforming request builder MUST preserve it intact through submission. `caps` here is the *requested* capability list within the ask's `token` spec — distinct from the agent-record `caps` scope of [COG-13](./COG-013), which bounds an agent's own authority. ### The response The answer to a token ask is a **signed UCAN JWT string** — the grant the responder signed with their own key, submitted like any other answer in the response's `answers[]` (satisfying [COG-16](./COG-016) response validation, including the `required` rule). It is not a boolean, an option id, or free text, and it triggers no venue minting. Before transporting, the venue MUST verify, and reject the response on any failure: 1. The answer is a well-formed UCAN. 2. Its signature verifies against the responder's `did:key`, and its `iss` equals the responder's DID. 3. Its `aud` equals the ask's `audience` (or, absent one, the requester). 4. It carries an `exp`, and is within its temporal bounds. The venue MUST NOT require `att ⊆ caps`, MUST NOT re-sign, and MUST NOT substitute a venue-minted token. ### Delivery A verified token is delivered on the **requester's job output**, under a `tokens` map keyed by ask id — distinct from the `token` field a venue-minted grant (COG-17) uses, so the two provenances never collide. A signed token is a **secret** — the token ask declares its answer secret in the sense of [COG-16](./COG-016)'s redaction rule. The venue MUST NOT propagate it into any durable carrier: both the persisted record's `response.answers` and the job output's `answers` carry a non-secret placeholder for the ask (e.g. ``); the token itself is delivered only under the job output's `tokens` map. Its leak resistance rests on its bounded `exp` and its audience binding: a token audienced to the requester is useless to any other principal. ### Interaction with grants A single request MAY mix token asks with approval/option asks that carry grants (COG-17). The venue-minted grant token and the transported self-sovereign tokens are delivered independently — `token` and `tokens` respectively. ## Security Considerations The trust reduction relative to COG-17 is the point: a minted grant depends on the venue's signature *being* the authority, whereas a transported grant is signed by the user and merely relayed. The venue is therefore not a trusted party for the token's integrity. Two residual concerns are handled explicitly: - **Leakage.** The token sits, after delivery, in the requester's job record. Audience binding ([The response](#the-response), rule 3) ensures a leaked token is unusable by any principal but the requester; a bounded `exp` limits the window. Requesters SHOULD request the narrowest capabilities and shortest lifetime that suffice. - **Substitution.** A malicious venue could drop or replace the token, but cannot forge one: any substitute fails signature and audience verification at the destination. This is a liveness failure, not an authority breach. ## Scope and limitations This standard specifies the **transport** — the venue's responsibility. It does not specify how a requester subsequently **presents** the token at the far venue, which depends on the requester's own identity: - An agent with its own key presents the token directly. - A key-less agent needs a presentable credential — an ephemeral session key whose DID the request names as `audience`, or a hosting-venue attestation ([COG-15](./COG-015)) — to authenticate as the token's audience. The transport is complete and secure on its own; end-to-end use composes it with whichever presentation mechanism the requester's identity supports. --- ## Working with agents The **Agents** section is the workbench for [Covia Agents](../../overview/agents): create an agent from a template, chat with it in sessions, watch what it does, and control its lifecycle. Every action here is an `agent:*` operation on the venue, so everything you do leaves the same audit trail a script would. ## Creating an agent **Agents → Create** builds an agent from a venue template (`v/agents/templates/*`): among them `skilled` (lean core with loadable skills), `goaltree` (hierarchical planner), `worker`, `analyst`, and `minimal`. You choose a name, an LLM provider and model, and a system prompt; the template supplies the default tools and skills. The venue needs an API key for your chosen provider, stored as a [secret](./data-and-inbox#secrets) under the standard name (for example `ANTHROPIC_API_KEY`); the create flow detects which providers are ready. Equivalent operation: ```json POST /api/v1/invoke { "operation": "v/ops/agent/create", "input": { "agentId": "researcher", "template": "skilled", "config": { "model": "claude-sonnet-5", "systemPrompt": "..." } } } ``` After creation, the **tool & skill picker** on the agent's configuration lets you browse the venue's operations and skills and attach or detach them; each change is an `agent:update` with its own job record. SDK: `venue.agents.create(...)`, then `venue.agents.chat(...)`. See [Creating Agents](../agents/creating-agents) for the full configuration shape. ## Chat and sessions **Agents → Chat** is a two-pane workbench: your agents on the left, the conversation on the right. Each conversation is a [session](../agents/sessions) on the venue: sessions persist across restarts, can be renamed, and the transcript renders agent replies as markdown with tool calls grouped into collapsible turns, so you can see exactly which operations the agent invoked and what came back. | In the app | Operation | | ---------- | --------- | | Send a message | `agent:chat` (with `sessionId` after the first turn) | | New chat | `agent:chat` without a session id; the venue mints one | | Rename a session | `agent:renameSession` | | Fire-and-forget note to the agent | `agent:message` | | Hand the agent a task | `agent:request` | ## Runtime state and control The agent detail view shows what a status pill alone cannot: pending inbox messages waiting for the next cycle, the next scheduled wake time, tasks in flight, and the per-cycle **timeline** with the operations each run invoked and the tokens it consumed. From the toolbar you can trigger a run now (`agent:trigger`), suspend and resume (`agent:suspend` / `agent:resume`), and delete (`agent:delete`). Agent statuses: `SLEEPING` (waiting for messages or a wake), `RUNNING` (in a cycle), `SUSPENDED` (will not run until resumed), `TERMINATED`. ## Skills **Agents → Skills** is the venue's skills library: every [skill](../../overview/skills) the venue ships plus any you have written into your workspace (`w/skills`), each readable in full. A skill is an ordinary content-addressed asset bundling instructions, context, and tools that an agent loads on demand, so the library doubles as a catalogue of what your agents can learn. The page reads through `skills:manage`, job-free. To teach an agent a skill of your own, follow [Teach an Agent a New Skill](../tutorials/skills). --- ## Workspace, secrets, and the inbox Three signed-in surfaces manage what you and your agents share: your lattice workspace, your encrypted secrets, and the human-in-the-loop inbox where agents ask you for answers and authority. ## Workspace **Manage → Workspace** is a three-pane browser over your slice of the venue's lattice: pick a namespace, walk its tree, and view or edit values with a structured JSON editor. It shows all your namespaces even when empty: | Namespace | Contents | Editable in the app | | --------- | -------- | ------------------- | | `w/` | Your durable workspace: notes, pins, skills, memory | Yes, one level deep and below | | `j/` | Job records | Read-only (immutable audit trail) | | `g/` | Your agents' state, sessions, timelines | Read-only | | `a/` | Your content-addressed assets | Read-only | | `o/` | Your named operation pins | Read-only | | `s/` | Secret names | Values are never readable | | `h/` | Human-in-the-loop requests | Via the Inbox | Browsing is job-free (`GET /api/v1/values/*`); edits go through `covia:write` and `covia:delete` operations, so every mutation leaves a job record. That asymmetry is deliberate: look freely, change accountably. ## Secrets **Manage → Secrets** stores per-user encrypted values (AES-256-GCM, decryptable only by the venue that stored them): API keys for LLM providers, tokens for HTTP operations. The app lists **names only**; values are write-only from the UI and are referenced by operations as `s/NAME` without ever entering a prompt or a job record. The page suggests the standard LLM key names (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, and so on) that agent creation looks for. REST equivalents: `GET /api/v1/secrets` (names), `PUT /api/v1/secrets/{name}`, `DELETE /api/v1/secrets/{name}`. ## The Inbox: human-in-the-loop **Grid → Inbox** is where running work waits for you. When an operation or agent needs a human, it files a [HITL request](../../protocol/cogs/COG-016) and its job holds in `INPUT_REQUIRED`; the inbox badge counts what is waiting. Three kinds of ask arrive: - **Answers**: a question or an approval; simple asks offer one-click responses. - **Choices**: pick from options the requester supplied. - **Authority**: the request asks for capabilities. The app shows exactly which grants (`with` + `can`) are being requested and lets you narrow them before consenting. Two consent modes exist: the venue mints a grant from capabilities you hold, or, with a device key, the app signs a **self-sovereign UCAN token** locally with your own key ([COG-19](../../protocol/cogs/COG-019)), so the venue never holds your signing authority. Every response is recorded against the requesting job, so the audit trail shows what was asked, what was granted, and by whom. Nothing in the app can widen a grant beyond what you approve. ## Where this section grows next The app tracks venue capability closely, and two adjacent surfaces are in active development on the same principles: a memory panel over `memory:manage` (durable user facts at `w/memory`) and a scheduler view for deferred and recurring runs (`scheduler:*`). Until they land, both are fully usable today from the [operations](./operations-and-jobs) surface and the SDKs. --- ## The Covia App The Covia App at [app.covia.ai](https://app.covia.ai) is the web console for the grid: connect to venues, run operations, watch jobs, and create and operate agents, all from a browser. It is the same client whether you point it at a hosted venue or at a venue running on your own machine at `http://localhost:8080`. The app is a pure client. It holds no server-side state of its own: everything you see is read from the venues you connect, and everything you change is an operation on a venue, recorded in that venue's audit trail. One consequence is worth knowing as you browse: **reading never creates jobs**. Pages and background refreshes use the venue's job-free read routes (see the [REST API reference](../api/)), so job records appear only when you explicitly run something. ## Navigation | Group | Entries | Notes | | ----- | ------- | ----- | | **Agents** | Create · View · Chat · Skills | The agent workbench and the venue's skills library | | **Grid** | Public Artifacts · My Artifacts · Operations · Jobs · Inbox | Public Artifacts is the venue catalog; My Artifacts is your own `a/` namespace; Inbox is the human-in-the-loop queue, with a live badge | | **Manage** | Secrets · Venues · Workspace | Secrets and Workspace appear when signed in | | **Learn** | Resources · Demos | Tutorials, videos, and seeded live demos | A bell in the top bar opens a persistent activity panel: job completions and failures, agent errors, and schedule outcomes, each entry linking the record it came from. ## Connecting venues The app connects to a default set of public venues on first load. Add your own from **Venues → Add venue** with either a URL (`https://venue.example.com`, `http://localhost:8080`) or a venue DID (`did:web:venue.example.com`). Each venue card shows its health, protocols, and stats; the venue selector in the top bar switches the active venue everywhere in the app. A venue's identity is its DID, and the app pins it: if a venue at the same address later presents a different DID, the app treats it as a new venue and drops the stored credentials rather than silently trusting the replacement. ## Signing in A venue authenticates you, and the app supports the venue's own methods (see [Authentication](../../operator-guide/auth)): - **Device key**: the app generates an Ed25519 key pair in your browser. Your identity is the key's `did:key`; each request carries a short-lived self-signed JWT bound to the venue you are talking to. You can generate a fresh key, import an existing one, and hold different accounts on different venues. Keys are stored locally in your browser and are manageable from **Profile → Keys**. - **OAuth**: venues that configure providers offer sign-in with Google, GitHub, or Microsoft. The venue (not the app) runs the flow at its `/auth/{provider}` endpoint and issues you a venue-signed token; your identity is a named user DID such as `did:web::u:`. Your identity, active logins per venue, and DID document are all visible on the **Profile** page. Signing out of a venue forgets its token; deleting a device key shows which venues that key still signs into before it goes. ## The equivalent, outside the app Everything the app does is the public API. The pages in this guide pair each action with its REST or SDK form, so you can graduate from clicking to scripting without changing concepts: ```bash # what the venue card shows curl https://venue-3.covia.ai/api/v1/status # what the app reads to render your workspace curl "https://venue-3.covia.ai/api/v1/values/list?path=w" -H "Authorization: Bearer " ``` Continue with [Working with agents](./agents), [Running operations and jobs](./operations-and-jobs), and [Workspace, secrets, and the inbox](./data-and-inbox). --- ## Running operations and jobs Operations are the venue's callable capabilities; jobs are the records of calling them. The app gives both a full UI, and everything maps one-to-one onto the [REST API](../api/). ## Finding an operation **Grid → Operations** lists the venue's catalog as cards with search and adapter filters; the catalog view groups the same operations by adapter. Every operation is self-describing, so its detail page shows the input and output JSON Schema, keywords, and, for composite operations, a read-only diagram of the orchestration steps. Discovery is a job-free read of the catalog trees (`v/ops`, and your own `w/ops` pins when signed in); the REST equivalents are `GET /api/v1/operations` and `GET /api/v1/values/read?path=v/ops`. ## Running one The operation page renders a form from the input schema: one field per property, with types respected, required fields marked, example values as placeholders, secret fields masked, and asset-reference fields backed by an asset picker. Running the form is exactly: ```json POST /api/v1/invoke { "operation": "v/ops/schema/infer", "input": { "value": { "name": "Ada" } } } ``` The app then takes you to the job page to watch execution. Scripts that only want the result can use `POST /api/v1/run` (see the [REST API reference](../api/)) instead; the app always goes via a job so the run is inspectable afterwards. ## Watching a job The job page shows status, timing, schema-annotated input and output tables, any error, and the child jobs a composite operation spawned. Updates stream over [SSE](../api/#get-apiv1jobsidsse) with the app's credentials attached; if a stream drops, the page falls back to polling the job-free job read. While a job is live you can **cancel**, **pause**, and **resume** it (where the operation supports pausing). A job in `INPUT_REQUIRED` shows a reply box: your answer is delivered with `POST /api/v1/jobs/{id}` and the job carries on. Jobs asking for authority instead land in the [Inbox](./data-and-inbox#the-inbox-human-in-the-loop) as human-in-the-loop requests. ## The jobs list **Grid → Jobs** is your `j/` namespace: every job you have created on the venue, sortable and filterable by status and date, with success-rate and duration tiles. The list reads through `GET /api/v1/values/slice?path=j`, and refreshes automatically while any listed job is still active. A job record is immutable once terminal, and its `prev` chain preserves every state it passed through: the list is also your audit trail. See [COG-8: Jobs](../../protocol/cogs/COG-008) for the record's guarantees. --- ## Connect your tools Agents on a venue can use your third-party services (GitHub, Linear, Notion, HubSpot, Slack, and more) with **credentials you supply and keep on your own venue**. There is no Covia-hosted broker in the path: a token you paste is stored in your venue's encrypted secret store, referenced by name, injected at runtime, never shown back, never placed in a prompt, and redacted from job records. Revoking access is deleting the secret. ## The quickest path: the Connections page In the dashboard, **Connections** (under Data) is a catalogue of ready-to-use services. Pick one, follow the two or three steps to create a token, and paste it: the page stores the token encrypted on your venue, runs a live **test-before-save** check (a bad token is rejected and never kept), and shows the service as connected. Paste a token straight into the search box and it recognises the service for you. Each connected service can then be granted to individual agents from the agent's own **Connections** section, so an agent only reaches what you allow. The catalogue today, grouped as it appears in the app: | Group | Services | | ----- | -------- | | Dev | GitHub, Sentry, GitLab, PagerDuty | | Docs & PM | Notion, Jira, Linear, Asana, ClickUp, Calendly, monday.com | | CRM & Support | HubSpot, Intercom | | Payments | Stripe | | Comms | Slack, Discord, Telegram, SendGrid, Twilio | | Data | Airtable | Behind that page are the two mechanisms below; either can be driven directly against the API. ## Two mechanisms Two mechanisms cover most services today, and both work on a self-hosted venue exactly as on a hosted one. ## 1. Bridge a vendor's MCP server with a token Many vendors now run a remote MCP server. The venue can mirror one into its own catalog with `mcp:server:add`: the vendor's tools appear as ordinary operations under `o/mcp//...`, capability-checked and audited like everything else, and agents can pick them up through the tool picker or a skill. Store the vendor token first, then bridge: ```bash # 1. store the token (write-only; the value is never readable back) curl -X PUT https://venue-3.covia.ai/api/v1/secrets/GITHUB_TOKEN \ -H "Authorization: Bearer " -H "Content-Type: application/json" \ -d '{"value": "github_pat_..."}' # 2. bridge the server, referencing the secret by name curl -X POST https://venue-3.covia.ai/api/v1/invoke \ -H "Authorization: Bearer " -H "Content-Type: application/json" \ -d '{"operation": "v/ops/mcp/server/add", "input": {"name": "github", "url": "https://api.githubcopilot.com/mcp/", "auth": "s/GITHUB_TOKEN"}}' ``` TypeScript: `await venue.secrets.set("GITHUB_TOKEN", token)` then `await venue.operations.run("v/ops/mcp/server/add", { name: "github", url: "https://api.githubcopilot.com/mcp/", auth: "s/GITHUB_TOKEN" })`. | Service | MCP endpoint | Token to store | Where to get it | | ------- | ------------ | -------------- | --------------- | | **GitHub** | `https://api.githubcopilot.com/mcp/` | `GITHUB_TOKEN` (fine-grained personal access token) | GitHub → Settings → Developer settings → Personal access tokens | | **Linear** | `https://mcp.linear.app/mcp` | `LINEAR_API_KEY` (personal API key) | Linear → Settings → API | | **Zapier** | `https://mcp.zapier.com/api/v1/connect` | `ZAPIER_MCP_TOKEN` (connection token) | mcp.zapier.com → New MCP Server → Connect | | **Stripe** | `https://mcp.stripe.com/` | `STRIPE_KEY` (restricted key, read-only where possible) | Stripe Dashboard → API keys | The bridge normalises each URL to the server's streamable-HTTP endpoint: a bare host implies `/mcp` (so `https://mcp.linear.app` and `https://mcp.linear.app/mcp` are the same), any explicit path is kept as given, and a lone trailing slash means *this exact root* — which is why Stripe's root server is written `https://mcp.stripe.com/`. Zapier is worth a special mention: once bridged it reaches Google Workspace, Slack, Notion, and thousands of other apps through **your own** Zapier connections, which is the quickest honest route to Google data from a private venue today. **Bring your own automation platform.** Anything that speaks streamable HTTP can be bridged the same way, including a platform you already run. Expose an **n8n** workflow through its *MCP Server Trigger* node (choose the streamable-HTTP transport and a bearer token), or switch on **Make**'s hosted MCP server (streamable HTTP; its connection URL ends in `/stream`), then `mcp:server:add` that URL with the token as `s/`. Because the endpoint is your own instance or account, there is no fixed vendor URL to list — you paste the one that platform gives you. Each then reaches every app you have already connected there, the same logic as Zapier, on infrastructure you control. Remove a bridge with `mcp:server:remove` (`{name}`); refresh its tool list after vendor changes with `mcp:server:refresh`. Scope `venue` (shared with everyone on the venue) needs the `mcp/manage` ability; the default `user` scope is private to you. ## 2. Connection skills over the HTTP operations For services without a token-friendly MCP server, the venue ships **connection skills**: instruction bundles that teach an agent a service's API on top of the built-in `http:get` / `http:post` operations. The credential is referenced by name and resolved server-side; the agent never sees it. Three shapes cover how a service expects its credential, so the skill picks whichever fits: - **Bearer** — `bearerSecret: "s/"` sends `Authorization: Bearer `. - **Header** — `secretHeaders: {"
": "s/"}` sends the stored value as any header, so Basic auth (`Basic `), an API-key header, or a `Bot ` value all work; the stored secret is the complete header value. - **URL** — an `{s/}` placeholder in the request URL, for a service that carries its token in the path (Telegram's `/bot/`). The venue resolves it before the request and masks it back out of every job record. Whichever shape a service uses, store the secret, attach the skill to an agent (or let a `skilled` agent load it on demand), and ask in plain language — "summarise the Notion page for the Q3 plan", "post the deploy summary to #releases", "open a Linear issue for this bug". Each call is a job record, and each skill explains the service's error semantics so the agent reports a missing scope instead of retrying blindly. | Skill (`v/skills/connections/…`) | Secret to store | Credential and where to create it | | --- | --- | --- | | `github` | `GITHUB_TOKEN` | Fine-grained personal access token — GitHub → Settings → Developer settings → Fine-grained tokens | | `notion` | `NOTION_TOKEN` | Internal integration secret — notion.so/my-integrations (share the pages you want) | | `slack` | `SLACK_TOKEN` | Bot User OAuth token (`xoxb-`) — api.slack.com/apps → your app → Install | | `hubspot` | `HUBSPOT_TOKEN` | Private-app access token — Settings → Integrations → Private Apps | | `jira` | `ATLASSIAN_AUTH` | Basic auth, stored as `Basic ` — id.atlassian.com/manage-profile → API tokens | | `linear` | `LINEAR_API_KEY` | Personal API key (sent as the raw `Authorization` value) — Linear → Settings → API | | `stripe` | `STRIPE_KEY` | Restricted key, read-only where possible — Stripe Dashboard → Developers → API keys | | `airtable` | `AIRTABLE_TOKEN` | Personal access token — airtable.com/create/tokens | | `discord` | `DISCORD_BOT_TOKEN` | Bot token, stored as `Bot ` — discord.com/developers → your app → Bot | | `telegram` | `TELEGRAM_BOT_TOKEN` | Bot token (`:`) — @BotFather → `/newbot` | | `asana` | `ASANA_TOKEN` | Personal access token — app.asana.com → My apps | | `intercom` | `INTERCOM_TOKEN` | Access token — Intercom → Developer Hub → your app | | `sentry` | `SENTRY_TOKEN` | Auth token — Sentry → Settings → Auth Tokens | | `sendgrid` | `SENDGRID_KEY` | API key — SendGrid → Settings → API Keys | | `twilio` | `TWILIO_AUTH` | Basic auth, stored as `Basic ` — Twilio Console | | `gitlab` | `GITLAB_TOKEN` | Personal access token (`api` or `read_api` scope) — GitLab → Preferences → Access Tokens | | `clickup` | `CLICKUP_TOKEN` | Personal API token (sent as the raw `Authorization` value) — ClickUp → Settings → Apps → API Token | | `calendly` | `CALENDLY_TOKEN` | Personal access token — Calendly → Integrations → API & Webhooks | | `monday` | `MONDAY_TOKEN` | Personal API token (GraphQL, raw `Authorization` value) — monday.com → Developers → My access tokens | | `pagerduty` | `PAGERDUTY_TOKEN` | API key, stored as the complete `Token token=` header — PagerDuty → Integrations → API Access Keys | Atlassian (Jira/Confluence) authenticates with HTTP Basic, and Twilio the same; both are handled by the header shape above (`secretHeaders`), so no special casing is needed. ## What about Google? Google is the one service where the standard "Sign in with Google" shape does not fit a private venue, because Google requires a pre-registered, verified application and its device flow excludes Gmail, Calendar, and full Drive. The honest options today: - **Organisations running their own venue:** a Google service account with domain-wide delegation (a super admin grants the scopes once; no OAuth client, no verification). An adapter for this path is planned. - **Individuals:** Zapier (above) for actions against your Google data, or your own Google OAuth client configured on your venue, accepting Google's verification rules for the scopes you need. - **Covia-hosted venues:** will offer Covia's own verified Google client as a convenience; that does not become a dependency for any other venue. ## Security model in one paragraph Credentials live in your venue's per-user secret store (AES-256-GCM, decryptable only by that venue). Operations reference them by name; the runtime is the single decryption point and injects values at the adapter layer. Fields an operation marks as secret are redacted from job records. Every use is a job under your authority, and agents act only within the capabilities you granted them. Deleting the secret ends the access. ## Covia inside Claude and ChatGPT The reverse direction ships too: the **Covia connector** lets your venue's operations and agents appear as tools inside Claude and ChatGPT, so an assistant can invoke your venue under your identity. It is a standalone OAuth 2.1 and MCP service; add it as a connector in the assistant and authorise it against your venue. ## Coming next Device-code sign-in for services that support it (GitHub, Microsoft 365) with the code shown in your inbox; bring-your-own OAuth clients for data scopes; and the venue as a first-class MCP client for vendors that support client ID metadata documents. --- ## Troubleshooting Symptoms, causes, and fixes for the issues people actually hit. Each error message below is shown verbatim as the venue produces it. ## "Cannot resolve operation: …" ```json { "error": "Error invoking operation: IllegalArgumentException:Cannot resolve operation: covia:read" } ``` **Cause:** the `operation` field isn't a resolvable reference. The short `adapter:op` style (`covia:read`, `http:get`) is a display name, not an address. **Fix:** use the catalog path — `v/ops/covia/read`, `v/ops/http/get`. List everything the venue offers with `GET /api/v1/operations`. Multi-word operations are kebab-case: `v/ops/grid/job-result`, `v/ops/agent/cancel-task`, `v/ops/mcp/tools-list`. The other valid forms are a workspace pin (`o/`), an asset id (`a/`), and a DID URL. See the [API reference](./api/). ## My job failed — how do I see why? A failed job carries its error in the job record: ```json { "id": "0x019e...", "status": "FAILED", "error": "boom", "output": null } ``` Ways to inspect: - **The invoke response itself** — with `"wait": true` you get the final record including `error`. - **By id** — `GET /api/v1/jobs/{id}` re-fetches the record at any time; `GET /api/v1/jobs` lists your recent job ids. - **Live** — `GET /api/v1/jobs/{id}/sse` streams status updates while a job runs. - **From the lattice** — your job log is data: `v/ops/covia/inspect` with `{"paths": "j", "budget": 1000}` previews recent records. ## "API key not found for provider 'openai' at /s/OPENAI_API_KEY" **Cause:** an LLM operation ran but the venue couldn't resolve the API key from the caller's secret store. This fails fast by design — there is no silent fallback. **Fix:** store the key the operation names: ```json { "operation": "v/ops/secret/set", "input": { "key": "OPENAI_API_KEY", "value": "sk-..." }, "wait": true } ``` Secrets are per-user and resolved at invocation time; they never appear in job records. Each [backend](./agents/llm-backends) declares its own secret name (`ANTHROPIC_API_KEY`, `XAI_API_KEY`, …). Local [Ollama](./agents/llm-backends#available-backends) needs no key. ## My agent is SUSPENDED When a transition fails (bad credentials, missing tool, capability denial), the agent records the error and suspends rather than retrying blindly. Its sessions and tasks are preserved. 1. **Read the error:** `v/ops/agent/info` with `{"agentId": "..."}` — the `error` field says what broke. 2. **Fix the cause** — usually a missing secret, a tool path that doesn't resolve, or a capability the agent lacks. 3. **Resume:** `v/ops/agent/resume` with `{"agentId": "..."}` — the agent returns to SLEEPING and picks up its pending work. ## "'paths' must be a string or array of strings" `v/ops/covia/inspect` takes **`paths`** (a string or an array), not `path` — unlike `covia:read`/`covia:write`, which take `path`. Easy to mix up. ## My data disappeared after a restart **Cause:** the venue had no `store` configured, so it used an ephemeral temp store — and warned about it at startup: ``` No 'store' configured — falling back to ephemeral temp Etch store; data will be deleted on JVM exit. ``` **Fix:** set `"store": "/path/to/venue.etch"` in the venue config (for Docker, on a mounted volume). See [Persistence](../operator-guide/persistence) for the durability model. ## Claude (or another MCP client) won't connect - **Check the path:** the MCP endpoint is `/mcp` on the venue, e.g. `http://localhost:8080/mcp`. - **claude.ai and Claude Desktop custom connectors require a public HTTPS URL** — they cannot reach `localhost`. Use **Claude Code** for a local venue (`claude mcp add --transport http covia http://localhost:8080/mcp`), or tunnel (e.g. `ngrok http 8080`). See the [Claude tutorial](./tutorials/claude-mcp). - **Testing with curl?** MCP responses need the right Accept header: ```bash curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' ``` ## A hosted venue rejects examples that work locally Use **venue-3.covia.ai** or **venue-4.covia.ai** — they are redeployed automatically from the latest development build, which these docs track. The stable venues (**venue-1.covia.ai**, **venue-2.covia.ai**, and the scratch venue **venue-test.covia.ai**) run the latest release, which may lag the docs; `GET /api/v1/status` and `GET /api/v1/operations` tell you what a venue actually is and offers. ## Where are the venue's logs? The venue logs to stdout (so `docker logs ` for Docker). Startup logs tell you the store location, the venue DID, and which adapters loaded; warnings about sweeps or flushes indicate persistence trouble — see [Persistence § Monitoring](../operator-guide/persistence#monitoring). ## Still stuck? - `GET /api/v1/status` — is the venue up, and which venue is it? - [`/swagger`](http://localhost:8080/swagger) — explore the live API interactively. - Ask on [Discord](https://discord.gg/fywdrKd8QT) or open a [GitHub issue](https://github.com/covia-ai/covia/issues). --- ## Give Claude Your Own Tools in 5 Minutes Every Covia venue is an [MCP server](../mcp/venues-as-mcp-servers). Point Claude at one and it immediately gets a working tool belt — a persistent workspace it can read and write, agents it can create and delegate to, assets, and grid operations. **No API key needed** for anything in this tutorial. By the end, Claude will be storing data on a venue *you* control — memory that survives the conversation, lives on your infrastructure, and leaves an auditable record. ## Pick your path | Path | Client | Venue | Time | |------|--------|-------|------| | **A — zero install** | claude.ai or Claude Desktop | Public test venue | ~2 min | | **B — your own venue** | Claude Code (CLI) | Docker on your machine | ~5 min | Custom connectors in claude.ai and Claude Desktop require a **public HTTPS** URL, which is why Path A uses a hosted venue. Claude Code connects happily to `localhost`, which is why Path B pairs it with Docker. ## Path A — connect Claude to a live venue (no install) 1. In **claude.ai** go to **Settings → Connectors** (or in **Claude Desktop**: Settings → Connectors), and click **Add custom connector**. *(Custom connectors require a Pro, Max, Team, or Enterprise plan.)* 2. Paste the test venue's MCP endpoint: ``` https://venue-3.covia.ai/mcp ``` 3. Click **Add**. That's it — Claude now has the venue's tools. > The test venue is a shared, public sandbox — fine for trying things, not for anything you care about. Path B gives you your own. ## Path B — run your own venue and connect Claude Code 1. Start a venue (Docker, one line): ```bash docker run -p 8080:8080 ghcr.io/covia-ai/covia:latest ``` 2. Tell Claude Code about it: ```bash claude mcp add --transport http covia http://localhost:8080/mcp ``` 3. Verify the connection: ```bash claude mcp list ``` You should see `covia: http://localhost:8080/mcp (HTTP) - ✓ Connected`. > To use Claude Desktop with your own venue, the venue needs a public HTTPS URL — put it behind a tunnel (e.g. `ngrok http 8080`) or deploy it properly (see the [operator guide](../../operator-guide/venue-start)). ## Try it Open a conversation and ask: > *What tools do you have from covia?* Claude will list around **35 tools** — workspace CRUD (`covia_read`, `covia_write`, `covia_list`, …), agent lifecycle (`agent_create`, `agent_request`, `agent_chat`, …), assets, grid operations, and secrets. These are the venue's [operations](../adapters/), exposed as snake_case MCP tools. Now give Claude some memory: > *Use covia_write to save my project preferences to `w/notes/preferences`: I prefer British English, tabs over spaces, and concise commit messages.* Claude calls the tool and the venue saves it — the write completes successfully (a write that creates a new branch answers `{"pathCreated": true}`; success itself is the job completing). Then the payoff — **start a brand-new conversation** and ask: > *Read `w/notes/preferences` from covia and follow them.* The data is still there. It didn't live in the chat context — it lives in your venue's workspace, on your infrastructure. ## What just happened Three things worth noticing: 1. **The tools are self-describing operations.** Claude discovered them via MCP `tools/list` — nothing was hard-coded on the client side. Anything you add to the venue (a new [adapter](../adapters/), a pinned operation) shows up as a tool automatically. 2. **The same data is reachable over every protocol.** What Claude wrote via MCP, you can read via REST: ```bash curl -X POST http://localhost:8080/api/v1/invoke \ -H "Content-Type: application/json" \ -d '{ "operation": "v/ops/covia/read", "input": { "path": "w/notes/preferences" }, "wait": true }' ``` One capability, every protocol — that's the [grid](../../overview/grid) model. 3. **Every call left a record.** Each tool invocation is a [job](../api/) on the venue — who called what, when, with what result. Ask Claude to *"list recent jobs with grid_job_status"*, or browse `http://localhost:8080/swagger` yourself. ## Go further - **Have Claude create an agent.** Ask: *"Use agent_create to create an agent called Scribe with the system prompt 'You summarise documents concisely', then send it a task with agent_request."* Claude builds and delegates to a persistent [Covia agent](../agents/) — an agent managing agents. - **Keep the data for real.** The default Docker venue stores state in a temporary store. Mount a volume and set `store` in the config to make it durable — see [Persistence](../../operator-guide/persistence). - **Lock it down.** A venue is public by default for easy development. Before exposing one, configure [authentication](../../operator-guide/auth) and [capabilities](../capabilities). ## Related - [Venues as MCP Servers](../mcp/venues-as-mcp-servers) — configuration, tool filtering, naming - [Calling MCP Tools](../mcp/calling-mcp-tools) — the reverse direction: your venue calling external MCP servers - [Quick Start](../quick-start) — the REST and SDK fundamentals --- ## Federate Two Venues This is the demo that makes Covia *Covia*: two independent venues — separate identities, separate data, separate audit trails — collaborating on a task where **the data never leaves its home venue and only results cross the boundary**. You'll run both on your laptop in about ten minutes, then federate with a real venue in the cloud. **You'll need:** Java 21+ and the `covia.jar` from the [latest development build](https://github.com/covia-ai/covia/releases/tag/latest-snapshot) (or build from source). No API keys. ## 1. Start two venues One process can host multiple venues. Save this as `two-venues.json`: ```json { "venues": [ { "name": "Venue A", "hostname": "localhost", "port": 8080, "mcp": {} }, { "name": "Venue B", "hostname": "localhost", "port": 8081, "mcp": {} } ] } ``` ```bash java -jar covia.jar two-venues.json ``` Confirm they're two distinct identities: ```bash curl -s http://localhost:8080/api/v1/status # "Venue A", did:key:z6Mkg... curl -s http://localhost:8081/api/v1/status # "Venue B", did:key:z6Mks... ``` > Two Docker containers work too — but note that from inside a container, `localhost` is the container itself; put both on a Docker network or use `host.docker.internal`. The single-process config keeps the tutorial friction-free. ## 2. Your first federated call The [Grid adapter](../adapters/grid-adapter)'s `grid:run` executes an operation and waits for the result. Add a `venue` and the execution happens **there**. From Venue A, run an operation on Venue B: ```json POST http://localhost:8080/api/v1/invoke { "operation": "v/ops/grid/run", "input": { "operation": "v/ops/schema/infer", "input": { "value": { "name": "Ada", "age": 36 } }, "venue": "http://localhost:8081" }, "wait": true } ``` ```json { "status": "COMPLETE", "output": { "schema": { "type": "object", "required": ["age", "name"], "properties": { "age": { "type": "integer" }, "name": { "type": "string" } } } } } ``` Venue A created a job, dispatched the call across the venue boundary, Venue B executed it, and only the result came back. The calling interface is identical to a local invocation — federation is not a special case. ## 3. The data stays put Now the part that matters for governance. Give Venue B some data that *lives on B*: ```json POST http://localhost:8081/api/v1/invoke { "operation": "v/ops/covia/write", "input": { "path": "w/inventory/widgets", "value": { "sku": "W-100", "stock": 42, "location": "Warehouse B" } }, "wait": true } ``` From **Venue A**, query it across the boundary: ```json POST http://localhost:8080/api/v1/invoke { "operation": "v/ops/grid/run", "input": { "operation": "v/ops/covia/read", "input": { "path": "w/inventory/widgets" }, "venue": "http://localhost:8081" }, "wait": true } ``` ```json { "status": "COMPLETE", "output": { "value": { "location": "Warehouse B", "sku": "W-100", "stock": 42 }, "exists": true } } ``` The inventory record never moved. Venue A asked a question; Venue B answered it on its own infrastructure, under its own policy. In production, that boundary is governed: B decides who may call ([authentication](../../operator-guide/auth)) and what they may touch ([capabilities](../capabilities)). ## 4. Two sovereign audit trails Each venue keeps **its own** record of what happened: ```bash curl -s http://localhost:8081/api/v1/jobs # B's log: the writes and the federated reads it executed curl -s http://localhost:8080/api/v1/jobs # A's log: the grid:run jobs it dispatched ``` Neither side depends on the other for its audit trail — compliance evidence exists on both sides of every cross-boundary call. ## 5. Federate with the cloud Nothing about this is localhost-specific. Point your laptop venue at a live public venue — by URL or by **DID**: ```json POST http://localhost:8080/api/v1/invoke { "operation": "v/ops/grid/run", "input": { "operation": "v/ops/schema/infer", "input": { "value": { "city": "London" } }, "venue": "did:web:venue-3.covia.ai" }, "wait": true } ``` ```json { "status": "COMPLETE", "output": { "schema": { "type": "object", "..." : "..." } } } ``` Your laptop just dispatched work to a venue on AWS, addressed by its decentralised identifier — the DID resolved via `/.well-known/did.json` to the venue's API endpoint. That's the grid: any venue, anywhere, one calling convention. ## 6. Long-running work across venues `grid:run` blocks for the result. For long operations, use the async pair — submit, then poll: ```json { "operation": "v/ops/grid/invoke", "input": { "operation": "v/ops/schema/infer", "input": { "value": {} }, "venue": "http://localhost:8081" } } ``` returns a job id immediately; collect it later with: ```json { "operation": "v/ops/grid/job-result", "input": { "id": "0x1234...", "venue": "http://localhost:8081" } } ``` ## Go further - **Multi-venue orchestrations** — [orchestrator](../adapters/orchestrator) steps each take their own `venue`, so one declarative workflow can fan out across the grid and compose the results. - **Federated agents** — an [agent](../agents/) on one venue can hold `v/ops/grid/run` as a tool and delegate work to partner venues, under its own [capabilities](../capabilities). - **Other protocols cross boundaries too** — venues federate over [A2A](../adapters/covia-with-a2a) (agent-to-agent tasks) and [MCP](../mcp/calling-mcp-tools) (remote tool calls), not just `grid:*`. ## Related - [Grid Adapter](../adapters/grid-adapter) — the full `grid:*` reference - [The Grid](../../overview/grid) — the conceptual model - [Operator: Authentication](../../operator-guide/auth) — governing who may call your venue --- ## Tutorials Hands-on walkthroughs — each takes you from zero to something working, and every command in them has been verified against a live venue. | Tutorial | You'll build | Time | |----------|--------------|------| | [Give Claude Your Own Tools in 5 Minutes](./claude-mcp) | Claude connected to a venue over MCP — persistent memory and 35 tools, no API key | ~5 min | | [Running Covia Agents](./running-agents) | An agent that explores your workspace with `covia:inspect` and writes reports, with a full audit trail | ~15 min | | [Federate Two Venues](./federation) | Two sovereign venues collaborating — data stays put, only results cross, then federate with the cloud | ~10 min | | [Teach an Agent a New Skill](./skills) | A lean agent that picks up capabilities on demand, plus a skill of your own, published for anyone to load | ~15 min | New here? Start with the [Quick Start](../quick-start) for the fundamentals, or jump straight into the first tutorial. --- ## Running Covia Agents A Covia agent is not a chat session — it's a **durable record on a venue**: configuration, conversation [sessions](../agents/sessions), a task queue, and an append-only timeline, all persisted as lattice data you can inspect. In this tutorial you'll seed a workspace with data, create an agent, watch it use the workspace to do real work, and then read the audit trail it left behind. **You'll need:** a running venue (`docker run -p 8080:8080 ghcr.io/covia-ai/covia:latest`, or see [Venue Quick Start](../../operator-guide/venue-start)) and an LLM API key — the examples use OpenAI, but any [backend](../agents/llm-backends) works, including a local Ollama with no key at all. All the calls below go to `POST /api/v1/invoke`. Operations are referenced by their **catalog path** (`v/ops/covia/write`, `v/ops/agent/create`) — the short `covia:write` style you'll see in prose is the operation's name, not an invokable reference. You can equally drive every step from Claude over MCP using the snake_case tool names (see the [previous tutorial](./claude-mcp)). ## 1. Give the venue an LLM key Store the key in the venue's secret store. It's resolved at invocation time and never appears in job records: ```json POST /api/v1/invoke { "operation": "v/ops/secret/set", "input": { "key": "OPENAI_API_KEY", "value": "sk-..." }, "wait": true } ``` ## 2. Seed your workspace Every caller gets a private, persistent namespace on the venue. `w/` is your workspace — write a couple of project records: ```json { "operation": "v/ops/covia/write", "input": { "path": "w/projects/apollo", "value": { "name": "Apollo", "status": "active", "budget": 120000, "lead": "Dana" } }, "wait": true } ``` ```json { "operation": "v/ops/covia/write", "input": { "path": "w/projects/borealis", "value": { "name": "Borealis", "status": "on-hold", "budget": 45000, "lead": "Sam" } }, "wait": true } ``` `covia:list` confirms what's there without fetching the values: ```json { "operation": "v/ops/covia/list", "input": { "path": "w/projects" }, "wait": true } ``` ```json { "type": "Map", "keys": ["apollo", "borealis"], "totalSize": 2, "exists": true } ``` ## 3. Meet `covia:inspect` — the discovery tool `covia:inspect` is how both you *and your agents* find your way around: a **budget-bounded preview** of any lattice path that shows shape, types, and sizes without flooding the context window. Elided content is annotated so you know where to drill in next. Inspect the venue's virtual namespace: ```json { "operation": "v/ops/covia/inspect", "input": { "paths": "v", "budget": 400 }, "wait": true } ``` ```json5 {test: {ops: {/* +15 more, 9.8KB */}}, ops: {/* +21 more, 129KB */}, info: {/* 6 keys, 13KB */}, /* +1 more, 163KB */} ``` And your own workspace: ```json { "operation": "v/ops/covia/inspect", "input": { "paths": "w", "budget": 300 }, "wait": true } ``` ```json5 {projects: {apollo: {lead: "Dana", status: "active", name: "Apollo", budget: 120000}, /* ... */}} ``` The progressive pattern — `inspect` broadly, `inspect` deeper with a bigger budget, then `read` the exact value — is precisely how an LLM agent explores data without burning its context. That's why it's the first tool to give an agent. Your full namespace map: | Prefix | Contents | |--------|----------| | `w/` | Your workspace — structured, persistent data | | `o/` | Operation pins — short names for operations you use often | | `g/` | Your agents — config, sessions, tasks, timeline | | `j/` | Job log — auto-populated as you invoke operations | | `a/` | Content-addressed assets | | `n/` | Named references · `t/` transient scratch | | `v/ops/`, `v/info/` | Venue catalog and introspection (read-only, shared) | ## 4. Create an agent ```json { "operation": "v/ops/agent/create", "input": { "agentId": "Scribe", "config": { "systemPrompt": "You are Scribe. You analyse the data in the workspace and write clear, concise reports.", "tools": ["v/ops/covia/inspect", "v/ops/covia/read", "v/ops/covia/list", "v/ops/covia/write"] } }, "wait": true } ``` ```json { "created": true, "agentId": "Scribe", "updated": false, "status": "SLEEPING" } ``` Unspecified config gets sensible defaults — the `v/ops/llmagent/chat` transition and the `v/ops/langchain/openai` backend. The `tools` array is the agent's palette: we've given Scribe the same workspace operations you just used, so it explores and writes data exactly the way you do. ## 5. The agent is data — inspect it Here's the part that makes Covia agents different. Scribe is not a process hidden inside a runtime — it's a record at `g/Scribe` in *your* namespace, and `covia:inspect` works on it like anything else: ```json { "operation": "v/ops/covia/inspect", "input": { "paths": "g/Scribe", "budget": 600, "compact": false }, "wait": true } ``` ```json5 { timeline: [], sessions: {}, tasks: {}, status: "SLEEPING", config: { operation: "v/ops/llmagent/chat", systemPrompt: "You are Scribe. You analyse the data in the workspace and write clear, concise reports.", llmOperation: "v/ops/langchain/openai", tools: ["v/ops/covia/read", "v/ops/covia/list", "v/ops/covia/inspect", "v/ops/covia/write"] } } ``` Empty timeline, no sessions, no tasks — a newborn agent, fully transparent. ## 6. Give it work `agent:request` sends a tracked task. It waits up to `timeout` ms for the agent to finish; on timeout you get a snapshot with a job id to poll via `v/ops/grid/job-result`: ```json { "operation": "v/ops/agent/request", "input": { "agentId": "Scribe", "input": { "task": "Explore w/projects with covia_inspect, read the project records, and write a one-paragraph status report to w/reports/projects." }, "timeout": 60000 }, "wait": true } ``` While you wait, Scribe is running its loop: assemble context → call the LLM → execute the tool calls it asks for (`covia_inspect`, `covia_read`, `covia_write`) → repeat until done. Tool names are the snake_case forms of the operations in its config. ## 7. See what it did The report is in your workspace: ```json { "operation": "v/ops/covia/read", "input": { "path": "w/reports/projects" }, "wait": true } ``` And the agent's record now tells the story — inspect `g/Scribe` again and you'll find a session with the conversation, and a timeline entry recording the run: when it started and ended, which transition ran, what tasks it processed, and what it produced. `agent:info` gives the quick summary: ```json { "operation": "v/ops/agent/info", "input": { "agentId": "Scribe" }, "wait": true } ``` This is the audit trail working for free: every run the agent ever makes is appended to its timeline, and every operation it invoked is a job in `j/`. Nothing about what your agent did is opaque. ## 8. Scope it with capabilities Right now Scribe can touch anything in your namespace. Give it only what its role needs by adding [capabilities](../capabilities) to the config: ```json "caps": [ { "with": "w/projects/", "can": "crud/read" }, { "with": "w/reports/", "can": "crud" } ] ``` With caps set, every tool call the agent makes is checked: read-only on the source data, full access to its own output area, and nothing else. A denied call returns a **structural** error that tells the agent what it holds and that retrying won't help — so it adapts instead of looping. Update a live agent with `v/ops/agent/update`. ## Go further - **Templates** — skip hand-written config: `"config": "template:worker"` resolves from the venue catalog at `v/agents/templates/`. See [Creating Agents](../agents/creating-agents#templates). - **Converse** — `v/ops/agent/chat` holds a multi-turn conversation on a [session](../agents/sessions); echo the returned `sessionId` to continue the thread. - **Typed outputs** — pass a `responseSchema` on `agent:request` for schema-enforced results, or use the [Goal Tree](../agents/goal-tree) adapter for hierarchical task decomposition. - **Drive it all from Claude** — every operation here is an MCP tool (`agent_create`, `agent_request`, `covia_inspect`, …). Connect Claude via the [previous tutorial](./claude-mcp) and have it build and manage the agents. ## Related - [Agents Overview](../agents/) — the three-level architecture - [Sessions](../agents/sessions) — how conversations and tasks flow through an agent - [Agent Operations](../agents/operations) — the full lifecycle reference - [Capabilities](../capabilities) — the UCAN model behind `caps` --- ## Teach an Agent a New Skill Most agent frameworks make you decide everything up front: every tool, every procedure, every instruction, pinned into the configuration and paid for on every single turn. Covia agents don't work that way. A well-built agent starts **lean** — a couple of read tools — and carries a one-line index of **skills** it can pick up when a task needs them. A skill is a named bundle of instructions, context, and tools. It's an ordinary [asset](../../protocol/cogs/COG-005), so you can write one in your workspace, publish it content-addressed, and hand it to any agent on any venue. In this tutorial you'll browse the skills a venue already ships, watch a lean agent acquire one mid-task, write your own, and publish it for anyone to load. **You'll need:** a running venue (`docker run -p 8080:8080 ghcr.io/covia-ai/covia:latest`, or see [Venue Quick Start](../../operator-guide/venue-start)) and an LLM API key for the agent steps — the examples use OpenAI, but any [backend](../agents/llm-backends) works, including a local Ollama with no key at all. All calls go to `POST /api/v1/invoke`. Every operation here is also an MCP tool (`skills`, `agent_create`, `covia_write`, …), so you can drive the whole tutorial from Claude instead — see [Give Claude Your Own Tools](./claude-mcp). ## 1. See what the venue already knows Every venue ships a skill library covering its own mechanisms. It's public — no key, no agent, no authentication required: ```json POST /api/v1/invoke { "operation": "v/ops/skills", "input": { "command": "list" }, "wait": true } ``` ``` - workspace — Read and write your durable lattice workspace — paths, namespaces, lists, deep structures. Load before storing or querying any per-user data. - agents — Create, configure and delegate to other agents on this venue. Load when work should be done by a separate agent, or to manage existing agents. - grid — Run operations on OTHER venues — cross-venue federation, remote jobs, hash-verified definitions. Load when work or data lives on a different venue. - discovery — Find out what a venue can do: adapters, operations and their schemas, published assets. Load on an unfamiliar venue or when no current tool fits. - provenance — The audit trail: every invocation leaves an immutable job record. Load to inspect what ran, debug failures, or verify another agent's work. ... ``` That's the whole design in one screen. Each line says **what the skill does and when to load it** — enough for an agent to choose, small enough that twenty of them cost almost nothing. The bodies stay out of context until someone asks for one. Read one in full without loading it: ```json { "operation": "v/ops/skills", "input": { "command": "read", "name": "provenance" }, "wait": true } ``` ```json5 { name: "provenance", description: "The audit trail: every invocation leaves an immutable job record...", body: "## Provenance\nA venue is a system of record: every operation invocation persists a job record under `j/`...", tools: ["v/ops/covia/list", "v/ops/covia/read", "v/ops/covia/slice", "v/ops/covia/inspect", "v/ops/covia/aggregate"], path: "v/skills/provenance" } ``` `body` is the instructions an agent would receive; `tools` are the operations that would join its palette. `read` is how you audit a skill before trusting it — see exactly what would enter an agent's prompt. By default `list` and `read` search `w/skills` (yours) then `v/skills` (the venue's). Pass `sources` to look anywhere else, including a single asset: `{ "command": "list", "sources": ["a/8cd17cbd..."] }`. ## 2. Create a lean agent The `skilled` template is the recommended starting point: read and list, plus the skills index. ```json { "operation": "v/ops/agent/create", "input": { "agentId": "Nomad", "config": "template:skilled" }, "wait": true } ``` Its config is deliberately small — the interesting line is `skills`: ```json { "tools": ["v/ops/covia/read", "v/ops/covia/list"], "skills": ["w/skills", "v/skills"], "defaultTools": false } ``` Two sources, searched in order. Nomad's index is now the union of your workspace skills and the venue's — and because `w/skills` comes first, **your skill shadows a venue skill of the same name**. That's how you override the house behaviour without forking anything. Any agent can be given sources the same way: ```json { "operation": "v/ops/agent/update", "input": { "agentId": "Scribe", "config": { "skills": ["w/skills", "v/skills"] } }, "wait": true } ``` ## 3. Watch it pick one up Give Nomad a job it can't do with read and list alone: ```json { "operation": "v/ops/agent/request", "input": { "agentId": "Nomad", "input": { "task": "Record three project entries under w/projects (Apollo, Borealis, Cygnus — each with a status and a lead), then tell me what you wrote." }, "timeout": 60000 }, "wait": true } ``` Nomad has no write tool. Instead of failing, it finds `workspace` in its index and loads it: ```json { "name": "skill_load", "input": { "name": "workspace" } } ``` ```json5 { loaded: true, skill: "workspace", path: "v/skills/workspace", tools: ["covia_read", "covia_write", "covia_list", "covia_slice", "covia_append", "covia_delete", "covia_inspect", "covia_aggregate"], body: "## Workspace\nYour durable, per-user data store. Paths are slash-separated...", note: "Skill instructions stay in context each turn (unload with context_unload). Tools are available from your next step." } ``` Three things happened at once, and the combination is the point: - **The instructions arrived immediately**, in the tool result — usable on this turn, not the next. - **The tools joined the palette** from the agent's next step. It can now call `covia_write`. - **The entry persists**, so both stay in place for the rest of the conversation. Check the result, and then check the agent — `g/Nomad` records the skill in its loaded context exactly like any other loaded path: ```json { "operation": "v/ops/covia/read", "input": { "path": "w/projects" }, "wait": true } ``` :::note[Loading is not permission] A skill can offer any operation; it can never grant the right to call one. If Nomad's [capabilities](../capabilities) don't cover `w/projects`, loading `workspace` changes nothing — the write is still denied, with the same structural error. Skills carry know-how; [capabilities](../../protocol/cogs/COG-013) carry authority. ::: ## 4. Write your own A skill is asset metadata with a `skill` facet. The quickest form puts the body inline and writes it straight to your workspace: ```json { "operation": "v/ops/covia/write", "input": { "path": "w/skills/status-reports", "value": { "description": "House format for project status reports. Load before writing any report to w/reports.", "content": { "inline": "## Status reports\nOne report per project at `w/reports/`.\n\nShape:\n```\n{project, status: active|on-hold|done, lead, updated, summary}\n```\n\nRules:\n- `summary` is one sentence, no more.\n- Read the project record at `w/projects/` first — never invent a lead or status.\n- Overwrite in place; history lives in the job log, not in the report.\n" }, "skill": { "tools": ["v/ops/covia/read", "v/ops/covia/write"] } } }, "wait": true } ``` Three fields carry the whole thing: | Field | What it does | |-------|--------------| | `description` | The index line. **What it does, and when to load it** — this is what an agent chooses on, so write it for a reader mid-task, not for a catalogue. | | `content` | The body. `inline` for short bodies like this one; larger or shared bodies live in a stored asset (step 5) or a file binding. | | `skill.tools` | Operations added to the palette while loaded. Full catalog paths. Curate the minimum the skill actually teaches. | Verify it before anyone loads it: ```json { "operation": "v/ops/skills", "input": { "command": "read", "name": "status-reports" }, "wait": true } ``` That returns exactly what a loading agent will receive. Nomad's index picks it up on its **next turn** — sources are re-resolved every turn, so there's nothing to restart, redeploy, or invalidate. Edit the body and the next turn of every agent carrying it sees the new text. :::tip[Write bodies for an LLM mid-task] Tight and imperative. Schemas as they're used. Gotchas inline, where they bite. Aim for 1–2 KB: a loaded body costs context budget on **every turn** it stays loaded, so put long reference material in a workspace document the body points at instead. ::: Ask Nomad for a report and watch it load `status-reports` before writing anything: ```json { "operation": "v/ops/agent/request", "input": { "agentId": "Nomad", "input": { "task": "Write a status report for Apollo." }, "timeout": 60000 }, "wait": true } ``` ## 5. Publish it `w/skills` is private and mutable — ideal while iterating. To share a skill, store it as an immutable asset: ```json { "operation": "v/ops/asset/store", "input": { "metadata": { "name": "status-reports", "description": "House format for project status reports. Load before writing any report to w/reports.", "skill": { "tools": ["v/ops/covia/read", "v/ops/covia/write"] } }, "contentText": "## Status reports\nOne report per project at `w/reports/`...\n" }, "wait": true } ``` ```json { "id": "8cd17cbd4e9a...", "stored": true } ``` That hash is now the skill. Anyone can load it directly, whether or not it appears in their index: ```json { "name": "skill_load", "input": { "ref": "a/8cd17cbd4e9a..." } } ``` It's content-addressed, so it verifies rather than trusts — the same guarantee any [artifact](../../protocol/cogs/COG-006) carries, which is what lets a skill cross venue boundaries intact. Hand the ref to a colleague on a different venue and they load the same bytes you wrote. Point a skills directory at it and it shows up in the index by name: ```json { "operation": "v/ops/covia/write", "input": { "path": "w/skills/status-reports", "value": "a/8cd17cbd4e9a..." }, "wait": true } ``` A directory entry can be a full metadata map (step 4) or a string ref like this one — mix both freely in the same directory. Loading the same skill from two addresses is a no-op: skills deduplicate by content identity, not by path. ## 6. Put it down again A loaded skill costs budget every turn, so agents should unload what they've finished with. There's no `skill_unload` — the ordinary context tool does it: ```json { "name": "context_unload", "input": { "path": "w/skills/status-reports" } } ``` The body, its context entries, and its tools all go from the next turn onward. An agent's Context Map shows what it's currently carrying, with skills marked, and the `[Skills]` index marks loaded ones with `(loaded)` so it never loads the same thing twice. ## Go further - **Bundle a tool with its manual** — an asset with both an `operation` facet and a `skill` facet is a self-documenting tool: loading it injects the instructions *and* offers the operation. One asset, one identity. - **A skill with no body** is a pure toolset — a curated tool bundle plus its index line, useful for grouping operations an agent should acquire together. - **Bundle context too** — `skill.context` entries load alongside the body, so a skill can arrive with the reference data it needs. - **Index without loading** — pin `{ "op": "v/ops/skills", "input": { "command": "list" } }` into `config.context` to show an agent what exists without giving it `skill_load`. - **Ship skills with a module** — a venue module jar carries its own skill definitions and installs them into `v/skills` when the module loads, so capabilities arrive with the code that implements them. ## Related - [COG-18: Agent Skills](../../protocol/cogs/COG-018) — the specification - [Tools and Context](../agents/tools-and-context) — how skills fit the per-turn context pipeline - [Creating Agents](../agents/creating-agents) — configuration and templates - [Capabilities](../capabilities) — the authority model skills never bypass - [Running Covia Agents](./running-agents) — agents from first principles