跳到正文 Skip to content

Let an Agent read the role it needs

Read a small index and one role, checking version, language, and SHA-256.

Start with a small index

Team readers can focus on discovering roles as needed, supplying context and confirming permissions. Collaboration does not require cloning the repository, installing Node or having every collaborator calculate SHA-256 manually. Continue to “Reading and trust boundaries”; the local snapshot lab can be left to developers.

  1. Read llms.txt and choose the zh or en index.
  2. Choose one ID for the task, then read its JSON or Markdown.
  3. Explain inputs, outputs, prerequisites, and needs, then work under the Agent's own rules.

Four machine-readable formats

schemaActual purpose
openbot.market.registry/1Discover versions, manifest URLs, and integrity hashes
openbot.market/1Canonical role manifest: persona, skills, and declared needs
openbot.fleet/1Configuration for host preview and confirmation
awesome.bot.agent/1Localized role context, provenance, and configuration fields

id, templateVersion, locale, and contentRevision identify context. configuration.bytes and configuration.sha256 identify download bytes. Localized projections can differ from canonical manifests; do not check an English Fleet against a Chinese manifest digest.

The following excerpt shows fields from the existing Status Report Writer Agent card; it is not a complete importable file. Declared manifest needs are not granted permissions. Check actual settings in the Fleet export and host confirmation.

{
  "schema": "awesome.bot.agent/1",
  "id": "awesome/status-report-writer",
  "templateVersion": "1.0.0",
  "locale": "en"
}

Read and verify SHA-256

This Node.js 22+ example only reads and verifies; it does not import or execute configuration. Stop on a mismatch and retrieve a consistent version or a release snapshot with checksums.

import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
const base = 'https://harzva.github.io/awesome-bot/';
async function read(url) {
  const r = await fetch(url, { signal: AbortSignal.timeout(10000) });
  assert.equal(r.status, 200);
  return r;
}
const card = await (await read(base + 'agents/en/status-report-writer.json')).json();
assert.equal(card.schema, 'awesome.bot.agent/1');
assert.equal(card.id, 'awesome/status-report-writer');
assert.equal(card.locale, 'en');
const bytes = Buffer.from(await (await read(card.configuration.url)).arrayBuffer());
assert.equal(bytes.length, card.configuration.bytes);
assert.equal(createHash('sha256').update(bytes).digest('hex'), card.configuration.sha256);
console.log(card.id, card.templateVersion, card.locale, card.contentRevision);

Reading and trust boundaries

Copy for Agent copies a prompt; the site does not contact an Agent. If links are inaccessible, provide the relevant Markdown through normal paste or attachment and state the limitation. A SHA-256 match proves agreement with metadata, not publisher identity. The current catalogue is unsigned.

After understanding these boundaries, team readers can continue to troubleshooting. Actual integration, download and upgrade work must still perform the integrity and trust checks required by the applicable contract, through the responsible integrator or tools.

Verify a local snapshot and reject an altered copy

Use Node.js 22+, clone this repository from GitHub, and enter its root before running the setup below. Cloning, dependency installation, and building may need network access. Only the verification phase below reads entirely local files. It does not call a model or establish offline model execution. The commands use a macOS/Linux or Windows WSL shell. In another terminal, save the code between the JS markers as an .mjs file and run it with Node.

git clone https://github.com/Harzva/awesome-bot.git
cd awesome-bot
npm ci --ignore-scripts
npm run check
npm run build:site

If the repository is already cloned and npm ci --ignore-scripts has completed, start at npm run check. Repair a failed check or retrieve a complete consistent source snapshot before proceeding. Copy and run the entire sh block below from the repository root. It reads discovery, the English index, the Status Report Writer card, and then Fleet bytes. URLs must match the expected site and paths; local paths and symlinks must stay inside dist/site.

node --input-type=module <<'JS'
import assert from 'node:assert/strict';
import { readFile, realpath } from 'node:fs/promises';
import { resolve, sep } from 'node:path';
import { createHash } from 'node:crypto';
const root = await realpath(resolve('dist/site'));
const base = new URL('https://harzva.github.io/awesome-bot/');
async function bytesAt(url, expected) {
  assert.equal(url, new URL(expected, base).href, 'unexpected URL');
  const u = new URL(url);
  assert.equal(u.origin, base.origin);
  assert.equal(u.search + u.hash, '');
  assert.ok(u.pathname.startsWith(base.pathname));
  const relative = decodeURIComponent(u.pathname.slice(base.pathname.length));
  assert.ok(!relative.includes('\\') && !relative.includes('\0'));
  const path = resolve(root, relative);
  assert.ok(path.startsWith(root + sep), 'path escaped site root');
  const actual = await realpath(path);
  assert.ok(actual.startsWith(root + sep), 'symlink escaped site root');
  return readFile(actual);
}
async function jsonAt(url, expected) {
  return JSON.parse((await bytesAt(url, expected)).toString('utf8'));
}
const discovery = await jsonAt(new URL('agents/index.json', base).href, 'agents/index.json');
assert.equal(discovery.schema, 'awesome.bot.agent-discovery/1');
assert.equal(discovery.configurationFormat, 'openbot.fleet/1');
assert.ok(discovery.contentRevision);
const languages = discovery.languages.filter(x => x.locale === 'en');
assert.equal(languages.length, 1);
const index = await jsonAt(languages[0].url, 'agents/en/index.json');
assert.equal(index.schema, 'awesome.bot.agent-index/1');
assert.equal(index.locale, 'en');
assert.equal(index.contentRevision, discovery.contentRevision);
assert.equal(index.count, index.entries.length);
assert.equal(languages[0].count, index.count);
const matches = index.entries.filter(x => x.id === 'awesome/status-report-writer');
assert.equal(matches.length, 1);
const entry = matches[0];
const card = await jsonAt(entry.json, 'agents/en/status-report-writer.json');
assert.equal(card.schema, 'awesome.bot.agent/1');
assert.equal(card.id, entry.id);
assert.equal(card.locale, index.locale);
assert.equal(card.contentRevision, index.contentRevision);
assert.ok(entry.templateVersion);
assert.equal(card.templateVersion, entry.templateVersion);
assert.equal(card.configuration.format, 'openbot.fleet/1');
assert.equal(card.configuration.locale, card.locale);
assert.equal(card.configuration.grantsTools, false);
assert.equal(card.configuration.grantsContainer, false);
assert.equal(card.configuration.includesAccountsOrMemory, false);
assert.equal(card.configuration.importRequiresHostConfirmation, true);
const original = await bytesAt(card.configuration.url, 'exports/en/status-report-writer.fleet.json');
function verify(bytes) {
  assert.equal(bytes.length, card.configuration.bytes, 'byte length mismatch');
  assert.match(card.configuration.sha256, /^[0-9a-f]{64}$/);
  assert.equal(createHash('sha256').update(bytes).digest('hex'), card.configuration.sha256, 'SHA-256 mismatch');
}
verify(original);
const fleet = JSON.parse(original.toString('utf8'));
assert.equal(fleet.schema, card.configuration.format);
assert.equal(fleet.mode, 'config');
assert.equal(fleet.bots.length, 1);
const bot = fleet.bots[0];
assert.equal(bot.id, card.id.split('/')[1]);
assert.equal(bot.toolsEnabled, false);
assert.equal(bot.containerEnabled, false);
assert.deepEqual(bot.messages, []);
assert.deepEqual(bot.files, []);
assert.deepEqual(bot.channels, {});
assert.deepEqual(bot.memory, { profile: '', journal: [] });
assert.equal(bot.summary, null);
assert.deepEqual(fleet.shared, { skills: [], files: [] });
console.log('PASS original snapshot');
const altered = Buffer.from(original);
assert.ok(altered.length > 0);
altered[0] ^= 1;
assert.throws(() => verify(altered), { code: 'ERR_ASSERTION', message: /SHA-256 mismatch/ });
verify(original);
console.log('PASS altered copy rejected');
JS

Completion requires the two lines PASS original snapshot and PASS altered copy rejected, in that order, with exit code 0. Versions and digests come from the current snapshot; no hard-coded version or SHA is needed. The negative case flips one byte only in a Buffer copy and uses assert.throws to prove rejection. Source files and original artifacts are never written.

Each layer is checked against its actual schema. ID and templateVersion agree between index and card; locale agrees across index, card, and configuration; contentRevision agrees across discovery, index, and card. Fleet has no locale, templateVersion, or contentRevision fields. Its schema, single Bot slug ID, and actual empty structures are checked without inventing fields.

  • ENOENT: confirm the repository-root working directory, Node.js 22+, and successful npm run check and npm run build:site. Do not fill missing files manually from another version.
  • Version, language, or URL assertion failure: stop using the mixed snapshot and regenerate all of dist/site from one Git snapshot before retrying. Do not relax assertions.
  • Byte-length or SHA-256 failure: keep the failure record and regenerate or retrieve a complete consistent snapshot. Do not edit the card digest to accept a bad file.

Distinguish the two digests

FieldInput and formatComparison boundary
registry integritycanonicalJson of the canonical manifest: recursively sorted keys and no formatting whitespace; SHA-256 encoded as sha256-<base64>.Defined by integrityOf in engine/src/manifest.mjs; it is not a digest of raw download bytes.
Agent configuration.sha256 / bytesThe exact Fleet export bytes at configuration.url; SHA-256 is 64 hexadecimal characters and bytes is the byte count.Computed from the artifact by scripts/agent-surface.mjs; compare only bytes from the same URL, language, and snapshot.

The CLI wrapper scripts/market.mjs reuses engine export, which defaults to Date.now(); site builds use the fixed metadata.updatedAt time. exportedAt and Bot createdAt can therefore differ. The Chinese canonical projection and English site projection also differ in persona and other text. Even with the same role ID and template version, export bytes and digests can differ. Do not interchange registry integrity, a CLI file digest, or Chinese and English download digests.

Sources

Sources checked: