AXe Skills HubSearch /

Skills hub architecture

What this registry is

A tenant-scoped skill registry. (tenant_id, name, version) is the primary key,

versions are immutable, nothing is deleted (a bad version is yanked), and every

client-facing read is audited.

resolve() is the internal read and is deliberately unaudited. fetch() is the

client-facing one and writes an audit row. Keeping them separate is what stops

the audit log filling with the hub's own lookups until nobody reads it.

The three skill populations

These are three tiers, not one catalogue that fell apart. They are recorded

because it is easy to mistake any one of them for "the" skill catalogue, and

because the counts differ enough that the mistake is not obvious.

PopulationWhereCountHas evalsReachable over MCP
Ingested SKILL.md packs~/.axe/hub/hub.db, tenant axe-internal523nono
Action modulesskill_NN_*.py110no (all has_evals: false)yes, via hub_list_skills
Eval-carrying skillsmain branch73yesno

Nothing joins them. The 03:30 sync keeps hub.db current from ~/.claude/skills,

but hub.db is not what hub_list_skills returns.

Three tiers, one resolver each, one front door

The three populations are not a mistake to be merged away. Knowledge packs,

action executors and external federation have different owners, review standards

and blast radius, so they get different planes -- which is what enterprise

platforms in this space actually do. Knowledge packs are curated, versioned and

tenant-scoped. Action executors are engineering-owned, typed and gated. Only the

federation tier faces third parties, and it lands in community-quarantine

rather than any client tenant.

TierStoreCountResolver
Knowledgehub.db SKILL.md packs523skills_hub_bridge.py
Actionskill_NN_*.py executors105 matched, 110 on diskaxe-mcp run_skill
Federationexternal cataloguescatalogue only, no payloadshub/community.py

What must NOT happen is two resolvers answering the same question. That is a

different failure from tiering, and the distinction is the whole point: tiering

means "which tier owns this?" has exactly one answer, whereas two resolvers means

the answer depends on which host the caller reached and on deploy timing. You

discover it during an incident, when the question is which version an agent is

actually running.

It matters here specifically because the promotion gate only means something if a

single resolver is authoritative. With two, a promotion can be live on one and not

the other, which quietly voids the eval gate while every test still passes.

hub_list_skills currently has exactly this hazard. axe-mcp is configured

type: http against https://mcp.axe.onl/mcp, so the implementation serving its

110 entries runs remotely and cannot read the local hub.db file. A local

skills_hub_bridge.py now exists and reads all 523. Both answer the same tool

name.

The resolution is that the local bridge becomes the knowledge-tier resolver and

the remote's overlapping hub_* responses are retired in the same change. Not

both. Shipping the bridge while the remote still answers is the failure described

above, not a step toward fixing it.

Note the action-tier count: the skill_[0-9]*.py glob matches 105 of the 110

files on disk, silently excluding non-numbered ones such as skill_spawn.py and

skill_ato_scan.py.

Verification note

Every claim above was checked against the running system rather than the source

tree, because the source tree and the running system disagree here. A green test

suite and a plausible file read both asserted things that a direct call

contradicted.