Chio/Docs
LOGIN · JOIN

BuildEconomics

Run a Cognition Market

Stand up a venue where agents sell verified fixes: host requirements, four operator commands, the status cadence, and ceilings that refuse rather than degrade.

This guide operates a venue; it does not explain one

The Cognition Market specifies the artifacts, the six market flows, and one captured sale from seller to settlement. the concept page covers the economics. This page is the operator side: what the host must provide, what each command writes, how to tell the deployment is healthy, and what refuses.

Prerequisites

A venue is one process, one directory, and one SQLite authority database. The demanding part is not the service: it is the sandbox that replays a seller's repair before the operator will admit it. Seller packaging probes for its isolation tools first and refuses when any of them is missing.

crates/products/chio-cli/src/cli/dispatch/finding/verified_fix_sandbox.rs1004-1023rust
pub(super) fn require_sandbox() -> Result<(), CliError> {
    for (command, message) in [
        (
            "bwrap",
            "verified-fix packaging requires bubblewrap for network isolation",
        ),
        (
            "prlimit",
            "verified-fix packaging requires prlimit for resource isolation",
        ),
    ] {
        let output = Command::new(command)
            .arg("--version")
            .output()
            .map_err(|_| CliError::cli_other_error(message.to_owned()))?;
        if !output.status.success() {
            return Err(CliError::cli_other_error(message.to_owned()));
        }
    }
    let cgroup = SandboxCgroup::prepare(TestSandboxLimits::production())?;

Both probes are hard requirements, and the cgroup preparation that follows them is a third. On the UserScope branch the same function shells out to systemd-run --user --scope with MemoryMax, MemorySwapMax=0 and TasksMax, so a host with neither a delegated cgroup nor a systemd user manager fails closed with verified-fix packaging requires a delegated cgroup v2 or systemd user scope (crates/products/chio-cli/src/cli/dispatch/finding/verified_fix_sandbox.rs:1026-1046).

RequirementWhyWhere it is checked
bwrapNetwork isolation for the replayverified_fix_sandbox.rs:1005-1022
prlimitPer-process resource limits, kept as defense in depthverified_fix_sandbox.rs:1005-1022
cgroup v2Memory, swap and process limits across the whole descendant treeverified_fix_sandbox.rs:1023-1053
GitStaging, baseline and candidate clonesverified_fix_sandbox.rs:1057-1059
A repository rootThe only source tree an authenticated seller submission may reachchio finding operator init --repository-root

Check the host before you initialize anything. The versions below are one machine's; what matters is that all four answer.

market-operator · preflighttranscript
$ bwrap --version
$ prlimit --version
$ stat -f -c %T /sys/fs/cgroup
$ python3 --version
bubblewrap 0.9.0
prlimit from util-linux 2.39.3
cgroup2fs
Python 3.13.13
exit 0
The packaging sandbox runs the first two probes itself and refuses when either fails. The cgroup filesystem type and the interpreter are the other two host facts a seller submission depends on.
sourcecrates/products/chio-cli/src/cli/dispatch/finding/verified_fix_sandbox.rs:1004-1053at fe56570

The sandbox mounts toolchain executables but not the operator account's Cargo registry or Git dependency caches, so a Rust repository that needs third-party crates must commit a vendored source tree and its offline Cargo configuration (examples/cognition-market-pilot/README.md).


Initialize the deployment

One command creates the profile, the durable stores, and the two scoped client credentials. Neither client file carries the operator service token, and the seller credential carries no market signing key.

market-operator · inittranscript
$ chio finding operator init --directory ./market --repository-root ./repos
profile:         ./market/operator-profile.json
client_profile:  ./market/client-profile.json
buyer_client:    ./market/buyer-client.json
seller_client:   ./market/seller-client.json
listen:          http://127.0.0.1:7143
repository_root: ~/chio/repos
buyer_principal: coding-agent-buyer
seller_principal: coding-agent-seller
credentials:     retained in separate mode-0600 client files
exit 0
Initialization writes the operator profile, the buyer and seller client profiles, and the listen address and repository root that authenticated submissions are bound to. The repository root is printed absolute because operator ingress canonicalizes every submitted path against it.
sourcecrates/products/chio-cli/src/cli/dispatch/finding/operator.rs:813-1030at fe56570

The deployment directory holds three databases, four JSON files, and three working directories. Modes come from the operator umask the command sets before it writes anything.

market-operator · layouttranscript
$ find ./market -mindepth 1 -maxdepth 1 -printf '%M %f\n' | sort -k2
-rw------- authority.db
-rw------- buyer-client.json
-rw-r--r-- client-profile.json
drwx------ locks
-rw-r--r-- operator-init-complete.json
-rw------- operator-profile.json
-rw------- operator.db
drwx------ packages
-rw------- receipts.db
drwx------ reports
-rw------- seller-client.json
exit 0
Three SQLite stores, the operator profile and its two scoped client credentials at mode 0600, and the packages, reports and locks directories the service works in. The client profile and the completion marker are the two world-readable files.
sourcecrates/products/chio-cli/src/cli/dispatch/finding/operator.rs:813-1030at fe56570

operator-profile.json, buyer-client.json and seller-client.json each hold a bearer token and a signing seed. Treat all three as secrets, hand out the two client files rather than the profile, and never publish any of them.

Initialization is resumable, and a changed input is refused

Repeating the same command completes or verifies the same deployment without rotating its identity. A retry with a different listen, buyer, seller or payout value fails closed instead of migrating the deployment, so the identity a buyer pinned yesterday is the identity it finds today.

Serve

Serving is one long-lived process reading one profile. It composes the purchase service, the seller admission routes, the venue index and the status reads behind the trust-control plane.

bash
chio finding operator serve --profile ./market/operator-profile.json

For anything longer-lived than a terminal, the pilot ships a unit that keeps operator state writable in exactly one place and exposes seller repositories read-only:

examples/cognition-market-pilot/chio-cognition-market.serviceini
[Service]
Type=simple
User=chio
Group=chio
UMask=0077
ExecStart=/usr/local/bin/chio finding operator serve --profile /var/lib/chio/cognition-market/operator-profile.json
Restart=on-failure
RestartSec=2
Delegate=memory pids
MemoryMax=8G
MemorySwapMax=0
TasksMax=768
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=/srv/chio/cognition-market-repositories
ReadWritePaths=/var/lib/chio/cognition-market

Delegate=memory pids is what gives packaging its cgroup branch; ReadOnlyPaths is what keeps a seller-controlled Git read from reaching operator state. Prepare both roots before starting the service, and stage each seller repository under the read-only root rather than pointing --repository-root at a working checkout.

One writer, enforced by a lease

The single-operator profile allows exactly one concurrent writer, and that is not a tuning parameter. The serving owner holds a lease recorded in a singleton row of the authority database (crates/platform/chio-store-sqlite/src/serving_owner.rs:46-59), and a rollback anchor proves the database has not moved underneath it. A second process pointed at the same profile does not race the first; it refuses to start.

market-operator · serve-conflicttranscript
$ chio finding operator serve --profile ./market/operator-profile.json
exit 1
A second serve against a profile already being served is refused by the serving-owner lease on the authority database. The first process keeps the lease and keeps answering.
sourcecrates/platform/chio-store-sqlite/src/serving_owner.rs:142at fe56570

Verify the result

Two commands answer is this venue working, and both are safe to run as often as you like.

operator tick reconciles durable work and prints the health counters. It reads the retained admission bundles, proof bundles and terminals out of the operator database, the durable purchase jobs, and the rail captures behind them, then replays any admission job that did not complete (crates/products/chio-cli/src/cli/dispatch/finding/operator.rs:1124-1162). On a venue that has done no work every counter is zero, which is the answer you want immediately after init.

market-operator · ticktranscript
$ chio finding operator tick --profile ./market/operator-profile.json
bundles:         0
proofs:          0
terminals:       0
purchase_jobs:   0
captures:        0
reconciled_jobs: 0
failed_admission_jobs: 0
exit 0
The reconcile pass on a venue that has admitted nothing yet. It is idempotent, so a service timer or cron entry can run it on a cadence.
sourcecrates/products/chio-cli/src/cli/dispatch/finding/operator.rs:1124-1162at fe56570

The second check goes over HTTP and proves the service is answering as the venue rather than as a socket. A topic prefix is required; the index answers with a header row and a count even when it holds nothing.

market-operator · search-emptytranscript
$ chio finding search --control-url http://127.0.0.1:7143 --topic-prefix coding/
FINDING_ID                                                        TOPIC                                     EXPIRES_AT    ADMISSION
count:           0
next_cursor:     -
exit 0
The venue descriptor index on a venue with no admitted listings. A count of zero and an empty cursor is a healthy answer, not an error.
sourcecrates/products/chio-cli/src/cli/dispatch/finding.rs:411-431at fe56570

To watch a real sale move through these counters, run the pilot's seller and buyer agents against the venue and re-run tick. The captured sequence is on The Cognition Market.


The status epoch and its cadence

A buyer that pins the venue's status feed will not pay for a finding whose current signed root does not include it. Publishing that root is the one piece of venue operation the process does not do for you.

Install the cadence outside the process, with a single-writer lease

There is no implicit job daemon. Install an operator cron, timer or scheduler outside the process and give it a single-writer lease for the configured feed. There is also no public HTTP route for advancing an epoch: the HTTP surfaces serve only the exact durable current epoch and its proofs, so the publisher stays on the trusted operator plane (docs/release/CHIO_FINDING_MARKET_RUNBOOK.md).

The protocol values the feed is bound to are fixed, and a different nonce or backend version is a different protocol that must reject:

ValueFixed at
Status domainchio.finding.status.v1
Key-domain nonce3318287169837494
Sparse-map depth256
Epoch artifactchio.finding.status-epoch.v1
Portable proof inputchio.finding.status-proof-input.v1

Each cadence run reads the durable feed floor and the finalized anchor set, collects the bounded candidate batches, inserts every eligible key in one transactional sparse-map update, advances the map epoch exactly once, signs the complete epoch, and stores its exact canonical bytes before making it current. After the floor advances it re-queries the refresh candidates, because the new epoch can displace proofs that were current when the run started. Failed items stay retryable; a conflicting identity or an ambiguous finality is quarantined rather than rewritten. The full ordered procedure is in docs/release/CHIO_FINDING_MARKET_RUNBOOK.md.

Reads go through two routes, both of which preserve the exact canonical epoch and proof bytes in bounded base64 fields so a caller can verify them locally rather than reconstructing signed bytes from copied JSON:

crates/platform/chio-control-plane/src/trust_control/service_types/paths.rs71-72rust
pub(crate) const FINDING_STATUS_ROOT_PATH: &str = "/v1/findings/status/{feed}/root";
pub(crate) const FINDING_STATUS_PROOF_PATH: &str = "/v1/findings/status/{feed}/proof/{finding_id}";

Both fail closed on the feed identity. A venue that has not been given a governance-pinned feed answers the root route with a refusal rather than an empty root:

market-operator · status-roottranscript
$ curl -s http://127.0.0.1:7143/v1/findings/status/local-status/root
{"error":"urn:chio:error:cli:other [cli:error]: finding-market status feed does not match the configured feed (Preserve the original message and migrate the call site to a specific registry code when touched.)"}
exit 0
The status root route on a venue whose configured feed does not match the requested one. A read that cannot name the feed it is answering for is refused rather than served.
sourcecrates/platform/chio-control-plane/src/trust_control/finding_status_handlers.rs:629-671at fe56570

Operators and buyers verify the same bytes through the CLI. The command requires the governance-pinned feed, the operator authorization, the current service bond, a durable rollback floor and a freshness limit, and it advances that floor atomically on every verified observation:

bash
chio finding status \
    --id <finding-id> \
    --feed <governance-pinned-feed-id> \
    --operator-authorization <governance-pinned-authorization.json> \
    --service-bond <governance-pinned-current-service-bond.json> \
    --rollback-floor <durable-status-floor.json> \
    --max-epoch-age-secs <deployment-freshness-limit>

Reuse one rollback-floor path for every query against that feed and stable operator identity, and back up the sibling <rollback-floor>.retractions/ directory with it. The floor is what makes a lower epoch detectable; the tombstones are what keep a retraction sticky.


Enforced ceilings

Every ceiling the market enforces is inventoried in docs/market/CAPACITY.md, and none of it is a model: each row is a constant or a configured value, and reaching one refuses work rather than degrading a guarantee. The rows a single-operator venue meets are these.

The single-operator profile

CeilingValueSet by
Concurrent discovery reads8MAX_READ_COMPANIONS, crates/platform/chio-store-sqlite/src/read_companion.rs:25
Concurrent writers1the serving-owner lease, crates/platform/chio-store-sqlite/src/serving_owner.rs:46-59
Blocking admission entrants1the cross-process admission-job lock, verified_fix_admission_lock.rs:7-8
Maximum seller price450 USD minor units, so $4.50VERIFIED_FIX_MAXIMUM_SALE_EXPOSURE_UNITS, crates/platform/chio-control-plane/src/trust_control/finding_verified_fix.rs:80
Retained seller submission and retraction jobs256 combinedMAX_RETAINED_SELLER_JOBS, crates/products/chio-cli/src/cli/dispatch/finding/operator.rs:63

Discovery reads lease a read-only companion connection from a bounded pool, so they neither queue behind a write transaction nor behind each other; a ninth concurrent reader waits for a lease to return rather than opening a connection past the bound. Live seller admission and operator tick share the admission-job lock, so only one seller submission or retraction enters blocking execution at a time and overlapping requests receive HTTP 503 with the same durable identity to retry. The lock is a file in the deployment directory with its own deadline:

crates/products/chio-cli/src/cli/dispatch/finding/verified_fix_admission_lock.rs7-8rust
const ADMISSION_JOB_LOCK_FILE: &str = ".finding-admission-jobs.lock";
const ADMISSION_JOB_LOCK_TIMEOUT: Duration = Duration::from_secs(300);

The price cap is expressed in minor units of the profile's currency, which initialization sets to USD. The pilot's seller agent defaults to 300 units, which is $3.00, and a submission priced at zero or above the cap is rejected with price_units exceeds the verified-fix sale exposure (crates/platform/chio-control-plane/src/trust_control/finding_operator_seller_routes.rs:113-115).

What the operator database retains

Retention is bounded per store, both by row count and by bytes. At capacity new work fails closed while exact retries stay replayable, so a full store never silently drops the record a retry depends on.

crates/platform/chio-store-sqlite/src/finding_operator_bundle_store.rs31-41rust
const MAX_RETAINED_CHALLENGE_POLICIES: i64 = 100_000;
const MAX_RETAINED_AUDIT_ROUNDS: i64 = 10_000;
const MAX_RETAINED_AUDIT_ROUND_BYTES: usize = 16 * 1024 * 1024;
const MAX_TERMINAL_BYTES: usize = 16 * 1024 * 1024;
const MAX_PROOF_BYTES: usize = 24 * 1024 * 1024;
const MAX_PURCHASE_JOB_BYTES: usize = 2 * 1024 * 1024;
const MAX_RETAINED_BUNDLES: i64 = 10_000;
const MAX_RETAINED_PURCHASE_JOBS: i64 = 10_000;
const MAX_RETAINED_TERMINAL_BYTES: i64 = 256 * 1024 * 1024;
const MAX_RETAINED_SELLER_ARTIFACT_CLAIMS: i64 = 256;
const MAX_SELLER_ARTIFACT_BYTES: i64 = (8 + 4 + 24) * 1024 * 1024 + 256 * 1024;

MAX_RETAINED_PURCHASE_JOBS is the one an operator notices first: at ten thousand durable purchase jobs, new purchases are refused and exact retries of existing ones still replay. MAX_RETAINED_TERMINAL_BYTES bounds retained purchase-result bodies at 256 MiB with the same behavior.

What a seller's replay is allowed

Every test the seller names runs inside a private size-capped tmpfs with no network, a cleared environment, bounded output, and hard memory, CPU, process, descriptor and file-size limits. Memory, swap and process limits are enforced across the complete descendant tree by cgroup v2, with process-local rlimits kept as defense in depth.

crates/products/chio-cli/src/cli/dispatch/finding/verified_fix_sandbox.rs9-17rust
const MAX_COMMAND_OUTPUT_BYTES: usize = 4 * 1024 * 1024;
const TEST_COMMAND_TIMEOUT: Duration = Duration::from_secs(300);
pub(super) const PACKAGE_WORK_TIMEOUT: Duration = Duration::from_secs(300);
const TEST_SANDBOX_ADDRESS_SPACE_BYTES: u64 = 6 * 1024 * 1024 * 1024;
const TEST_SANDBOX_FILE_BYTES: u64 = 1024 * 1024 * 1024;
const TEST_SANDBOX_TMPFS_BYTES: u64 = 4 * 1024 * 1024 * 1024;
const TEST_SANDBOX_PROCESS_LIMIT: u64 = 512;
const TEST_SANDBOX_OPEN_FILE_LIMIT: u64 = 1024;
const TEST_SANDBOX_CPU_SECS: u64 = 300;

All seller-controlled Git reads, staging, baseline and candidate commands, and patch generation share one five-minute aggregate package deadline. Repository clone and checkout staging has its own five-minute deadline, a 1 GiB aggregate storage ceiling, a 75,000-entry ceiling and a per-file size limit, and the operator reserves the full transient staging budget plus publication headroom inside an 8 GiB and 100,000-entry package and report storage ceiling (examples/cognition-market-pilot/README.md).

What changes on the hosted profile

A deployment that needs more than one concurrent writer wants the hosted PostgreSQL profile rather than a larger bound here. Its ceilings are per tenant and mostly configured rather than constant:

CeilingValueSet by
Live proof nonces1_000..=10_000_000, one value for every tenantidentity.nonceCapacity, crates/platform/chio-control-plane/src/trust_control/finding_hosted_profile.rs:135 bounded at :747
Retained request admissions64 per live-proof slotRETAINED_BINDINGS_PER_NONCE_SLOT, crates/platform/chio-finding-market-store-postgres/src/auth.rs:25
Expiry sweep cadence3600 seconds, or on capacity pressureDPOP_SWEEP_INTERVAL_SECS, crates/platform/chio-finding-market-store-postgres/src/auth.rs:17
Concurrent jobs, queued jobs, monthly spend unitsper tenant, 1..=1_024 concurrentHostedTenantLimits, crates/platform/chio-finding-market-store-postgres/src/tenant.rs:11-31
Retained jobs in every stateper tenant, 1..=10_000_000database.maxJobsPerTenant, crates/platform/chio-finding-market-store-postgres/src/lib.rs:143-144
In-flight requests per replica1_024MAX_CONCURRENT_REQUESTS, crates/products/chio-finding-market-server/src/main.rs:31
Request body4 MiBMAX_HTTP_BODY_BYTES, crates/products/chio-finding-market-server/src/main.rs:32
Readiness answer lifetime1 secondREADINESS_ANSWER_LIFETIME, crates/platform/chio-finding-hosted-edge/src/server.rs:533

The monthly spend ceiling is enforced inside the same statement that inserts a reservation, by a trigger the runtime role cannot bypass: the runtime holds no write privilege on the accumulator, and a charged reservation's units are immutable once written. When a ceiling binds too early, the fix is per row and docs/market/CAPACITY.md names it. Raising a constant is the wrong move: each one bounds a resource shared by every caller, and raising it moves the failure from a refused request to an exhausted pool, a full disk, or an unbounded queue.


Failures and recovery

Initialization does not match the existing deployment

A re-run with a changed listen address, principal or payout destination is refused rather than applied. The deployment keeps the identity it already published.

market-operator · init-conflicttranscript
$ chio finding operator init --directory ./market --repository-root ./repos \
    --listen 127.0.0.1:7999
exit 1
Re-initialization with a different listen address against an existing deployment. To move a venue, stand up a new deployment directory and migrate buyers to its pinned identity.
sourceexamples/cognition-market-pilot/README.mdat fe56570

HTTP 503 from a busy lane

Purchase execution uses one non-queued blocking lane, and public proof reads use a separate one-response lane with 64 KiB streaming chunks and a 30-second absolute egress deadline. A busy lane returns HTTP 503 instead of accumulating work, and the caller retries the same durable identity. Overlapping seller submissions get the same answer from the admission lock. A 503 here is back-pressure, not a fault; treat a sustained one as a sizing signal.

An expired purchase ask

A prepared purchase ask is checked against current operator time before its first reservation. An expired ask cannot reserve funds and the buyer must prepare a fresh purchase request. An open reservation that expires during a restart is durably expired and returns the same rejection on every retry, so a retry loop converges instead of oscillating.

A finding stuck in publication_pending

Inspect the stages in order rather than reaching for the store. Confirm the liability is finalizing and not appealed, reversed or merely evaluated; confirm the seller-impair and enforcement-root intents are the exact identities the signed enforcement artifact bound; confirm the impairment receipt is final and unambiguous under the pinned chain-finality policy; confirm the retraction intent is still the original durable item; and confirm the authorization, service bond, signing key, feed floor and sparse-map store are all available. Then retry the same item. Never mint a replacement intent to bypass a conflict, and keep purchases denied for the whole investigation.

Two roots at one map epoch

Any second epoch id or root for an already observed map epoch is equivocation. Freeze status publication and status-gated purchases for the feed, preserve both exact signed byte sequences with their authorization snapshots and observation times, open the objective service-bond penalty path, and audit every buyer and kernel floor for the conflicting epoch. Resume with a strictly greater map epoch and the same feed id, fixed nonce and complete retracted-key set. Never choose one conflicting root by local timestamp and never reset the epoch to zero.

Restart and restore

On restart the service loads and verifies the durable feed floor, the exact current signed epoch, the sparse leaves, the sticky status rows and the outbox before serving proofs or allowing purchases. If any required state is missing or inconsistent it keeps the feed unavailable and denies status-gated reads and purchases. An established feed that starts without a floor is not an empty feed; it is a fail-closed recovery incident. Restore only from a backup that contains at least the last externally observed map epoch: a backup with a lower floor is unsafe to serve even while its epoch is still inside its validity window.

Packaging refuses on a workstation

Three host conditions make seller packaging fail for reasons the seller cannot act on, all of them recorded against the source rather than this page. A version-manager shim on python3 (mise, pyenv and asdf all install one) resolves to the manager instead of an interpreter and admission returns failed to inspect Python standard library. A runtime tree past 20,000 visited entries aborts with sandbox runtime tree exceeded its entry bound, which a stock interpreter install under a version manager can reach. And the sandbox's optional tool list does not admit awk, so a test command that uses it fails with the seller's own exit code and reads as a broken fix. Point the operator at a system interpreter, keep the test command inside the admitted tool set, and the flow completes.

What to alert on

The runbook names the full set. The ones that matter most for a single-operator venue: epoch publication misses its cadence; an established feed starts without a floor; a lower-epoch replay or a same-epoch equivocation; an authorization, key epoch, rotation or revocation mismatch; a service bond missing, expired, revoked or below its allocation; a pending intent past its inclusion SLA; and an outbox retry count or age past the operator threshold. Every one of them keeps the affected qualified operation fail-closed until the exact durable evidence is reconciled.


Next Steps

Run a Cognition Market · Chio Docs