Author an IDENTITY.md (workspace root or view) for AIP-23
Use this skill when the user asks to draft, extend, or revise an
identity.workspace/v1 manifest under AIP-23. The skill
produces a valid manifest (workspace-root or view), with the right
layer-collection declarations, artifact policy, temporal contract, confidence
semantics, junction policy, lint rules, and cross-AIP refs, ready for
defineIdentityWorkspace to load.
An IDENTITY.md manifest is the machine-readable contract for an
AIP-23 layered identity — which layer kinds it tracks, how those
layers compose, what compression artifacts the host caches, which entities
(operators, companies, personas, users) may bind layer items, and which
workspace-spanning lints run. The same doctype is used in two modes: a
workspace root at the identity root (no extends:), and a view in any
consumer folder (with extends: pointing at a parent). Authoring either is the
same flow, with one branch on step 1.
Critical: AIP-23 delegates ALL per-layer-kind concerns (field schemas,
status state machines, ownership cardinality, item-level lints) to
AIP-18. Do NOT re-specify any of those in IDENTITY.md —
declare layer collections, then let AIP-18 own the schemas.
When to use
- "Set up a new identity workspace — write its
IDENTITY.mdfrom scratch." - "Add a per-locale view on the existing identity — write a view that extends the parent and switches the artifact locales."
- "The eng mentor needs a layer lens — write a view that surfaces only soul and personality layers."
- "Bind an AIP-7 governance policy and an AIP-10 wiki to this identity."
- "Add temporal-entry tracking to the emotional-bond layer."
When NOT to use
- The user wants to author per-layer-kind schemas (which fields are on the
personality layer, the status ladder for emotional-bond) — that's
AIP-18's
author-collectionskill. - The user wants to write individual layer items (a specific operator's soul record) — also AIP-18.
- The user wants to change the AIP-23 spec itself — manifest shape changes are governance, not authoring.
- The user wants to define the persona doctype — that's AIP-25. AIP-23 only carries the binding rules.
- The user wants to edit an existing
IDENTITY.mdin place without considering the chain — read the parent first, run the merge in your head, then edit. Skipping the merge produces views that override fields the parent already provides correctly, or worse, trip a one-way-switch HARD refusal.
Process
Follow these steps in order. Composition and one-way switches are the central mechanics; steps 1-2 set up the right mode, steps 3-11 fill in the body, step 12 validates.
1. Decide: workspace root or view?
Two questions:
- Is there an existing
IDENTITY.mdupstream that this manifest should adapt? If yes → view; if no → workspace root. - Does the user want the manifest to bind to a specific consumer (operator /
company / persona / skill / locale)? If yes → view (set
appliesTo); if no → workspace root.
Workspace-root mode declares the BASE shape. View mode adapts the base for one
or more consumers. There is no third mode — the schema rejects manifests that
mix workspace-root and view properties (e.g. appliesTo without extends).
If the answer is workspace root, skip step 2 and proceed to step 3.
2. If view: locate parent, set extends:, understand one-way switches
For a view, the extends: field is a RELATIVE path from the new manifest's
directory to the parent IDENTITY.md. The host resolves it bottom-up; recursion
is allowed.
# Per-locale view at /repo/identities/acme/locales/fr/IDENTITY.md
extends: ../../IDENTITY.md
Rules:
- Use POSIX path separators in
extends:even on Windows. - Maximum chain depth is eight.
- If the parent is in another tree, prefer factoring the shared bits into a
small workspace package both can
extends:locally.
One-way switches — read the parent FIRST. Three fields (plus
binding.verifyExistence), once set at any ancestor, MUST NOT be relaxed by
descendants. Trying to relax triggers a HARD refusal — the view fails to load.
Before authoring a view, read the parent (and its parent, if any) and identify
which one-way switches are already on:
| Field | One-way condition | HARD refusal code |
|---|---|---|
defaults.auditMutations |
If any ancestor is true, descendants cannot set false. |
identity_audit_downgrade |
binding.exclusivity |
If any ancestor is set to a stricter value, descendants cannot replace with a more permissive one. | identity_binding_loosen |
binding.verifyExistence |
If any ancestor is true, descendants cannot set false. |
identity_binding_verify_relax |
layers.versioning |
If any ancestor is enabled, descendants cannot set disabled. |
identity_versioning_disable |
If the parent has any of these set, do NOT redeclare them on the view as a
relaxation — inherit silently. Narrowing layers.defaultConfidence (raising the
floor, e.g. 0.3 → 0.5) is fine. Lowering the confidence floor is allowed by the
schema but the host emits identity_confidence_floor_lowered as a warning.
Cycle detection and depth-overflow are runtime warnings, not errors. Do not rely on the warning — write a correct chain.
3. Identity (name, title, description, version)
Every manifest, root or view, MUST declare these four fields:
schema: identity.workspace/v1
name: acme-org-identity # kebab-case, stable
title: Acme Corp organisation identity # human-readable
description: |
One-paragraph statement of purpose: what this identity
captures, who or what it is about.
version: 2.1.0 # semver — bump on shape changes
Guidance:
nameis the stable identifier; never rename once published.versionis the WORKSPACE shape version. Bump on collection / artifact-tier / binding / lint / defaults changes. Independent of any individual layer item's version.
4. Cross-AIP bindings
executor: ws://operators/founder
governance: ../policies/identity-default.yaml
work: ws://workspaces/main-tracker
knowledge: ws://wikis/handbook/KNOWLEDGE.md
| Field | Required | When to set |
|---|---|---|
executor |
optional | The operator the identity is about (or activates against). |
governance |
optional | Set when AIP-7 approval gates apply. Workspace-root manifests usually set this. |
work |
optional | Set to bind the identity to an AIP-20 work tracker. |
knowledge |
optional | Set when items reference an AIP-10 wiki by default. |
appliesTo |
conditional | REQUIRED in view mode (whenever extends is set AND the view binds to a consumer). MUST NOT be set in workspace-root mode. |
The host MUST refuse a view whose appliesTo references a non-existent consumer
(identity_appliesto_unresolvable) — verify the consumer's workspace exists
before declaring the binding. The host also refuses workspaces with unresolvable
executor, governance, or knowledge refs (identity_xref_unresolvable,
HARD).
5. Layer collections — inline vs ref vs aliased
collections: is the bridge to AIP-18. Each layer kind is one
AIP-18 collection. Three forms:
- Inline. Full AIP-18 collection.schema/v1 frontmatter embedded in
IDENTITY.md. Useful for small, single-tenant identities. - File ref (
./collections/<layer>/COLLECTION.md). Useful when the layer schema is shared with peer identities. - Registry import (
ws://collections/<slug>). Useful for third-party or org-shared collections.
Aliasing (any ref form):
collections:
- ref: ws://collections/emotional-bond
alias: bond # workspace-local rename
version: "1.x" # pin schema range
Two collection entries resolving to the same effective name (alias or upstream
name) is a HARD failure (identity_collection_alias_conflict). Pick aliases
deliberately.
Picking which layers are first-class. The decision is domain-specific. Some heuristics from working implementations:
- Companion-style identities (one agent, intimate user relationship): soul, voice, emotional-bond. Skip mind / role-context (the agent doesn't operate in an org).
- Operator-fleet identities (many operators in a company): soul, personality, role-context, mind. Skip emotional-bond at the org level (it's per-relationship; emerges if needed).
- Council / mentor overlays: soul, mind, voice. Skip role-context (the persona's authority comes from the identity, not from organisational role).
- Brand-voice identities: voice, world. Skip the rest (brand voice is communication + worldview).
Starter collections AIP-23 ships in identitykit-compagnon/:
soul— values, mission, energy sources.mind— decision process, principles, mental models.personality— traits, communication style, strengths.emotional-bond— trust level, shared moments. TEMPORAL.role-context— role type, decision domains, delegation.
The merged collections[] array is computed across the extends: chain via
merge-by-effective-name. Inheriting from the parent is the default; only
redeclare collections you want to override.
When extending an AIP-18 starter collection with org-specific
fields, write the extended collection inline OR as a sibling file with its own
extends: — and then ref the extended file from IDENTITY.md. Do NOT mutate
the starter file in place.
6. Compression artifact policy
The artifact policy is AIP-23's first distinctive subsystem. It manages token-budget pressure for prompt assembly.
artifacts:
enabled: true
tiers:
- { id: short, maxTokens: 80, strategy: aaak }
- { id: medium, maxTokens: 300, strategy: bullet-list }
- { id: full, maxTokens: 1024, strategy: markdown }
locales: [en, fr]
refreshPolicy: on-write
Guidance:
- Three tiers (
short / medium / full) is the conventional pick from working implementations. Tiers MUST be monotonic (each tier'smaxTokensstrictly greater than the previous). strategynames the compression algorithm.aaakfor short (compact key-value),bullet-listfor medium (labelled sections),markdownfor full (headers + bodies). Hosts MAY support additional strategies.localesdrives the fan-out: when non-empty, the host generates one artifact per (layer, tier, locale) triple. Leave empty if the identity is single-locale.refreshPolicy: on-writeis the safe default — every layer-item mutation regenerates artifacts immediately. Usescheduledonly when LLM costs matter more than freshness; usemanualonly when the identity is locked.- Disable artifacts (
enabled: false) for tiny identities where the layer fits inline in every prompt.
7. Temporal layers — when to enable, source vocabulary
Temporal entries are AIP-23's second distinctive subsystem.
layers:
temporal:
enabled: true
field: validUntil
sourceVocabulary: [configured, observed, inferred, self-reported]
Guidance:
- Enable temporal entries when the workspace has at least one layer that
evolves over time — typically
emotional-bond(relationship history),shadow(observed contradictions), or domain-specific layers likeattachment-style(clinical). - Mark layer collections themselves as
temporal: truein their AIP-18 COLLECTION.md frontmatter — the workspace declaration is permissive (allows temporal entries) and the per-collection declaration is what actually opts each layer in. sourceVocabularyis APPEND-ONLY across ancestors: a child MAY add (clinical-assessmentfor a therapy domain) but MUST NOT remove the canonical four. Removal triggersidentity_temporal_vocab_removal(HARD).fieldis the expiry-bearing field on temporal-entry items; defaultvalidUntil.
The companion temporal-entry collection is conventionally named temporal-entry
and lives alongside the layer collections it serves. Its schema fields:
| Field | Type | Required |
|---|---|---|
parentLayer |
ref | YES |
content |
object | YES |
observedAt |
datetime | YES |
intensity |
number (0..1) | YES |
validUntil |
datetime | NO |
source |
enum (from sourceVocabulary) |
YES |
8. Confidence semantics — when configured (1.0) vs inferred
Every layer item MUST carry confidence: 0..1. Defaults:
layers:
defaultConfidence: 0.3 # workspace floor
Authoring guidance:
| Authoring mode | confidence value |
|---|---|
| Designer wrote this directly | 1.0 |
| Explicit user statement extracted | 0.9 |
| Clear behavioural signal across exchanges | 0.7 |
| Single-exchange clear signal | 0.5 |
| Weak hint, single mention | 0.3 |
| Speculative | <0.3 (SHOULD NOT promote) |
Workspace-level guidance:
- Companion identities typically set
defaultConfidence: 0.3— they accept inferred entries from conversation. - Brand / regulated identities typically set
defaultConfidence: 0.7or higher — only well-grounded claims land. - Council / mentor identities typically set
defaultConfidence: 1.0— only configured traits, no inference.
The host emits identity_layer_confidence_missing (HARD) for any item that
omits confidence, and identity_confidence_below_floor (HARD) for items below
the declared floor.
9. Junction policy — allowed entities, exclusivity
binding:
allowedEntities: [operator, persona]
exclusivity: per-entity-and-layer
verifyExistence: true
Guidance on allowedEntities:
- Companion-style (one user, intimate agent):
[user, persona]. - Operator-fleet (many operators in an org):
[operator, company]. - Brand voice (single voice across many bearers):
[brand](host-defined) or[company]. - Council overlay:
[persona]— the persona binds to the identity layers the council operator should adopt.
exclusivity: per-entity-and-layer (currently the only defined value) means
at most one item per (bearer, layer) — an operator has one soul, one
personality, etc. This is the safe default; do not override unless the spec adds
permissive forms.
verifyExistence: true (default) requires the host to confirm the bearer exists
before allowing the binding. Set false only in ephemeral / sandbox contexts.
Production identities MUST keep this true.
10. Workspace-spanning lints
AIP-18 lints are per-collection. AIP-23 lints span layers:
lints:
- id: missing-soul
kind: missing-required-layer
severity: error
params:
layers: [soul]
- id: low-confidence-pinned
kind: low-confidence-pinned
severity: warn
params:
threshold: 0.5
collections: [soul, personality]
- id: stale-bond-90d
kind: stale-temporal
severity: warn
params:
collections: [bond]
days: 90
- id: orphan-layer
kind: orphan-layer
severity: error
- id: unbound-personality
kind: unbound-layer
severity: warn
params:
collections: [personality]
Workspace-spanning lint kinds:
| Kind | Purpose | params |
|---|---|---|
missing-required-layer |
Bearer missing a layer the workspace expects every bearer to have. | layers: [...] |
low-confidence-pinned |
Layer item with confidence < threshold but referenced (not just stored). |
threshold: <n>; collections: [...] |
stale-temporal |
Temporal entries older than days without a refresh. |
collections: [...]; days: <n> |
orphan-layer |
Layer item whose bearer no longer exists. | none |
unbound-layer |
Layer item with no bearer set (when bearer is required). | collections: [...] |
custom |
Host-defined, identified by id. |
host-defined |
Severity guidance:
error— block writes that fail the lint.warn— surface in the audit log, do not block.info— surface in tooling only.
11. Body prose — purpose, conventions, what NOT to put in identity
The frontmatter ends; the body is markdown. Conventional sections:
# <title>
## Purpose
What this identity captures and why.
## Layers active
The human-readable rendering of the enabled layers and what each captures.
## Conventions
When to file under `personality` vs `voice`; when to add a temporal entry vs
update the layer directly; how confidence thresholds map to authoring modes.
## What this identity does NOT model
Set boundaries explicitly. Tasks belong on AIP-20. Documents belong on AIP-10.
Ephemeral mood does not belong here.
## When to extend vs replace
Composition guidance for downstream view authors.
Keep the body short. The frontmatter is the contract; the body explains the choices.
12. Validate against IDENTITY.schema.json; if view, dry-run merge — and CHECK no one-way switch is relaxed
Validate the new manifest's frontmatter against AIP-23's schema:
npx ajv validate \
-s apps/agentik/sites/content/docs/agentproto/resources/aip-23/draft/IDENTITY.schema.json \
-d "<workspaceDir>/IDENTITY.md"
Common errors:
appliesToset butextendsmissing → addextends.collections[].aliasproduces a name collision → rename or remove the alias.artifacts.tiers[]not strictly increasing inmaxTokens→ reorder or fix.layers.temporal.sourceVocabularymissing canonical values (configured / observed / inferred / self-reported) → add them; the vocabulary is append-only across ancestors.binding.allowedEntitiesempty → add at least one entity kind.lints[].idcollisions inside one manifest → ids must be unique per manifest.versionnot semver →1.0.0, not1orv1.
If view, run the host's resolution algorithm in dry-run mode and explicitly check that no one-way switch is relaxed:
## Merge diff: uk-research-lead (vs parent acme-uk)
Inherited (no change):
- collections: soul, role-context, personality (parent's set)
- artifacts.tiers: short(80), medium(300), full(1024)
- artifacts.locales: [en, fr]
- artifacts.refreshPolicy: on-write
- layers.versioning: enabled (one-way; descendants cannot disable)
- layers.temporal.enabled: true
- layers.temporal.sourceVocabulary: [configured, observed, inferred,
self-reported]
- binding.exclusivity: per-entity-and-layer (one-way; cannot loosen)
- binding.verifyExistence: true (one-way; cannot disable)
- defaults.auditMutations: true (one-way; cannot disable)
- governance: ../../policies/group-identity-default.yaml
- knowledge: ws://wikis/handbook-uk/KNOWLEDGE.md
Overridden:
- layers.defaultConfidence: 0.3 → 0.5 (RAISING — allowed; stricter floor)
- display.homePage: undefined → SOUL-uk-research-lead
- display.defaultGrouping: undefined → layer
Added:
- lints.research-stale-bond (kind=stale-temporal, severity=warn, days=60)
- appliesTo: [ws://operators/uk-research-lead]
One-way switch check: PASS
- defaults.auditMutations: parent=true, view=undefined → inherits true OK
- binding.exclusivity: parent=per-entity-and-layer, view=undefined → inherits OK
- binding.verifyExistence: parent=true, view=undefined → inherits true OK
- layers.versioning: parent=enabled, view=undefined → inherits enabled OK
Resolution chain: 3 levels (org → acme-uk → uk-research-lead) Warnings: none
If the merge diff shows the view RELAXING any one-way switch (e.g.
auditMutations: true → false, versioning: enabled → disabled,
exclusivity → permissive, verifyExistence: true → false), the view will be
HARD-refused at load — fix it before declaring success.
If the diff includes an unintentional override, edit the view to remove it (deletion of a field reverts to parent's value via the merge).
Final checklist
Before declaring done:
-
schema: identity.workspace/v1is set. -
name,title,description,versionare present. - If view:
extends:is a valid relative path to an existingIDENTITY.md;appliesTo:references existing consumers. - If workspace root:
extends:andappliesTo:are absent. -
collections[]entries have unique effective names (alias orname); refs resolve; inline frontmatters validate against AIP-18's COLLECTION.schema.json. - Per-layer-kind concerns (fields, statuses, ownership rules) are NOT in
IDENTITY.md— they live onCOLLECTION.mdfiles. - Every layer collection's items are expected to carry
confidence: 0..1(the host enforces). -
artifacts.tiers[]is strictly increasing inmaxTokens(monotonic). -
artifacts.localeslists every locale your consumers need; empty for single-locale identities. -
layers.temporal.enabledistruewhen at least one layer collection declarestemporal: true. -
layers.temporal.sourceVocabularyincludes the four canonical values (configured, observed, inferred, self-reported); descendants MAY add but MUST NOT remove. -
binding.allowedEntitiesis set deliberately; never empty. -
binding.exclusivity: per-entity-and-layer(the only defined value). -
binding.verifyExistence: truefor production identities (one-way). -
lints[]have uniqueids within this manifest; severities respect any parent governance constraints. -
defaults.auditMutationsis set deliberately (one-way). -
layers.versioning: enabledfor production identities (one-way). - Cross-AIP refs (
executor,governance,work,knowledge) all resolve. -
metadata.<vendor>.*is namespaced. - Frontmatter validates against
IDENTITY.schema.json. - Body is short and prose-only.
- If view: dry-run merge diff was reviewed; no one-way switch is relaxed.
- If governance binding changed: the change is itself routed through AIP-7 approval before the manifest lands on disk.
Output
Reply to the user with:
- The path of the manifest written.
- Mode (workspace root vs view).
- Resolution chain (for a view): root → … → leaf, one path per level.
- Effective config summary — the merged shape, in particular which layer collections are active, the artifact policy (tiers, locales, refresh policy), the temporal contract, the confidence floor, the binding policy, and which one-way switches are now in effect.
- Bindings —
executor,governance,work,knowledge,appliesTo(if set), each with a one-line note on what it does. - One-way switch report — for a view, an explicit per-switch line:
auditMutations: parent=<x>, view=<y>, status=PASS|FAIL;binding.exclusivity: parent=<x>, view=<y>, status=...;binding.verifyExistence: parent=<x>, view=<y>, status=...;layers.versioning: parent=<x>, view=<y>, status=.... - Validation result — schema clean, dry-run merge clean, warnings (if any).
- Open assumptions — fields you guessed (artifact tier sizes, default confidence floor, lint severities) that the user might want to override.
Do NOT mutate the parent manifest, the workspace root, or any existing view as a side-effect. Authoring a new manifest is a LEAF operation — touch only the file you are creating.
See also
- AIP-23 — agentidentity/v1 spec
- AIP-18 — COLLECTION.md / ITEM.md — substrate this skill composes on
- AIP-3 — SKILL.md
- AIP-7 — governance, approval, audit
- AIP-9 — agentoperators/v1
- AIP-10 — agentknowledge/v1 — sibling Workspace AIP
- AIP-20 — agentwork/v2 — sibling Workspace AIP
- AIP-22 — agentoffice/v1 — sibling Workspace AIP
- AIP-24 — ASSEMBLY.md — assembly / council overlay (forthcoming)
- AIP-25 — agentpersonas/v1 — persona doctype (forthcoming)
../../ADAPTER.md— implementer's guide../../EXAMPLES.md— reference manifests../../IDENTITY.schema.json— frontmatter validator../../starters/identitykit-compagnon/— starter layer collections