Competency System Internals
How competencies are installed, equipped, and enforced at runtime — including multi-competency composition, contract-based integration resolution, tool visibility, and the pre-invocation reshaping that keeps a multi-competency agent's system prompt legible to the model. Grounded in crates/platform/hoziron-core/src/competency/ and ADR-028, ADR-050, ADR-051, ADR-052, ADR-031. This is the lower-level architectural view — for authoring guidance ("how do I write a competency"), see docs/guides/competencies/.
Object Relationships
Key constraint (ADR-028): multiple competencies per agent, with explicit operator-controlled ordering — the historical one-competency-per-agent limit, inherited from OpenFang's 1:1 hand↔agent mapping, is lifted. Hoziron owns its own CompetencyStore (a DashMap of manifests plus a DashMap<AgentId, Vec<EquipBinding>>), persisted at $HOZIRON_HOME/competency_state.json, rather than delegating storage to the kernel's hand registry.
CompetencyStore
struct CompetencyStore {
definitions: DashMap<CompetencyId, CompetencyManifest>,
bindings: DashMap<AgentId, Vec<EquipBinding>>,
}
struct EquipBinding {
competency_id: CompetencyId,
position: u32,
config: HashMap<String, serde_json::Value>, // setting overrides
cron_job_id: Option<CronJobId>,
equipped_at: DateTime<Utc>,
}
Scheduled execution (a competency's schedule field) is delegated to the kernel's CronScheduler rather than a separate platform-level schedule system — one less duplicated subsystem across the boundary.
Equip Operation
Equipping a competency is a multi-phase, all-or-nothing operation:
Phase 1: Skill Dependency Validation
# COMPETENCY.md — field is `skills`, not `required_skills`
skills = ["document-ocr", "postgresql-connector", "email-skill"]
Each listed skill must be installed. If any are missing, the equip fails with a comprehensive error listing all missing skills (not just the first one).
Phase 2: Permission Validation
# COMPETENCY.md — field is `permissions`, holding real Permission values,
# not free-form strings like "database:read"
permissions = ["agent:message:claims-*", "cost:hourly:10"]
The manifest's permissions: Option<Vec<Permission>> is deserialized directly as the real six-variant Permission enum (see permission-model.md) — not an arbitrary string set. Matched by exact equality against the agent's declared Permission grants (validate_equip_permissions, crates/platform/hoziron-core/src/permission/mod.rs). If any required permission is absent, equip fails with an enumeration of all missing permissions.
Phase 3: MCP Access — Two Declaration Mechanisms, Unioned (ADR-050, ADR-051)
A competency's manifest carries two distinct fields for MCP access, both feeding the same resolved mcp_servers union:
# Integration package's MANIFEST.toml (supply side, contract-based)
[contracts.claims-core]
provides = ["claim.search_history", "claim.create", "claim.update",
"claim.set_status", "claim.attach_document", "claim.assign"]
# Competency's COMPETENCY.md (demand side)
contracts = ["claims-core"] # ADR-051: resolved indirection, swappable backend
integrations = ["fixed-universal-mcp"] # ADR-050 legacy path: bound verbatim by server name
contracts(ADR-051, preferred) — names a role, not a package. Resolution finds whichever installed integration package declares that contract in its own manifest (integration_registry.find_by_contract(contract)) and binds to that package's server name. This decouples "what an agent needs" from "which vendor supplies it" — the same competency runs unmodified against Guidewire, Duck Creek, or a test rig, whichever package is installed under that contract. The earlier all-integrationsdesign (equip wrote the literal declared string straight intomcp_serverswith no resolution step) matched nothing in practice — competencies namedmcp-claims-core, installed packages registered astestbed-claims-core— which is what motivated the contract indirection.integrations(ADR-050, retained as a legacy/direct path) — bound verbatim by server name, no resolution step. Appropriate for a fixed, universal MCP server every deployment reaches by the same name.
Union semantics for multi-competency agents: an agent's effective mcp_servers is the union of every currently-equipped competency's resolved contracts entries and verbatim integrations entries.
Missing/disconnected integration: warn-and-skip, not equip failure. A contracts entry with no installed integration package declaring it logs a warning ("install an integration that declares [contracts.{name}] to activate") and does not block equip — competency and integration lifecycle are independent. The allowlist entry stays declaratively present and yields zero tools until the integration connects.
Equipping never triggers integration installation. The operator installs and connects integration packages separately; equip only updates the derived allowlist.
Unequip Operation
- Removes the
EquipBindingat the given position; remaining bindings keep their relative order. - Rebuilds
mcp_serversfrom the remaining equipped competencies' contracts (not simply cleared, unless no competencies remain — an agent with zero equipped competencies reverts tomcp_servers = [], which resolves as open-to-all-connected-servers, the backward-compatible default for never-equipped agents). - Removes that competency's system prompt segment.
- Re-resolves the agent's aggregate skill list from the remaining bindings.
Tool Visibility — Two Positive Per-Class Allow Lists (ADR-052)
Distinct from equip-time contract resolution and from the outbound mediation trust boundary, an agent additionally carries two visibility lists that narrow what's offered to the model (never what's permitted):
allow_skill_tools— narrows in-process skill tools.allow_integration_tools— narrows MCP/contract tools, within the servers already resolved by Phase 3 above.
Empty/absent = full default for that class. Non-empty = offered exactly those named tools. The two lists are independent — naming one class never affects the other. There is no skill-only tool_allowlist or subtractive tool_blocklist. See permission-model.md for the full semantics and the honesty constraint that this is never a security boundary.
Tool Resolution Re-runs Every Message
The effective tool set is not fixed at equip time — it is re-resolved at every message execution, so a skill upgrade that adds tools is visible on the agent's next message without re-equipping:
Pre-Invocation Competency Manager (ADR-031)
Competency order determines system-prompt priority — earlier position gets more LLM attention (primacy effect); mid-prompt content is systematically under-weighted ("lost in the middle"). For multi-competency agents, a kernel primitive — not an agent-authored or operator-configured feature — fires before the system prompt is composed:
| Agent configuration | Action source | Manager fires? |
|---|---|---|
| Single competency | Any | No — nothing to reorder |
| Multi-competency | External action (message/webhook) | Yes |
| Multi-competency | Scheduled cron wake | No — schedule implies a known task |
| Multi-competency | Operator explicitly disabled | No |
The manager's own model selection is a degenerate, always-local, non-gateway-mediated preference resolution ([competency_manager].preference, ADR-054) — the identical PreferenceEntry/resolve_preference mechanism used by the routing gateway, but soft-failing: an empty or unresolvable manager preference degrades to static competency order rather than failing the invocation. This is deliberately asymmetric with the routing gateway's own preference, which is boot-required — a single operator typo in the manager's config must not take down every invocation for every agent.
This is invisible to both the agent and the operator — the agent sees a well-shaped system prompt, not the reshaping machinery, and every multi-competency agent benefits automatically without authoring burden.
Competency Manifest Structure
id = "claims-intake"
name = "Claims Intake"
description = "FNOL processing"
category = "Insurance"
skills = ["document-ocr", "postgresql-connector"]
permissions = ["cost:hourly:10"]
schedule = "0 */6 * * *"
contracts = ["claims-core"] # resolved role, not a package name (ADR-051)
system_prompt = "You are a claims intake specialist..."
[[settings]]
key = "auto_escalate_threshold"
label = "Auto-Escalate Severity"
setting_type = "Select"
default = "4"
[[metrics]]
label = "Claims Processed"
memory_key = "claims_processed_count"
format = "number"
Categories
category is optional. When set, it must be one of: Insurance, Security, Productivity, Development, Communication, Data, Finance, Research, Operations, Other. Taxonomy-backed classification for the catalog comes from [package.metadata] categories in MANIFEST.toml.
Settings at Runtime
Operator-configurable parameters exposed via CLI and API: Select (predefined options), Text (free-text), Toggle (boolean). Agents access their competency's current settings through the memory system (injected as memory KV entries).
Skill ↔ Tool Relationship
Each skill provides one or more tools (must have at least one — validated at install time). Tool collision detection prevents two equipped competencies from providing the same tool name.
Package Lifecycle (ADR-044, ADR-056)
Competency is Installable-only — it has no Runnable/Connectable runtime state of its own (it is inert until equipped to an agent). The sole write route (POST /competencies/install) goes straight through the store-backed install path; there is no side door that registers a competency definition without a corresponding CompetencyStore row (confirmed in the ADR-060 audit of all five package types).
Related:
- ../architecture/object-model.md — the multi-competency relationship at the domain-model level
- permission-model.md — tool visibility (ADR-052) and the real Permission enum
- catalog-packages.md — the lifecycle contract shared with the other four package types
docs/guides/competencies/— task-oriented authoring guidancedocs/decisions/028-competency-store-and-multi-competency.md,029-install-is-instantiation.md,031-pre-invocation-competency-manager.md,050-competency-mcp-access-wiring.md,051-contract-based-integration-resolution.md,052-tool-visibility-gate.md