Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,18 @@ narrative text. Constraints:
- **Frontmatter accepts allowed keys only** (`additionalProperties: false`
in the schema):
- experiences: `id, title, company, start_date, end_date,
accomplishments, skill_ids, skill_links, scope, source, created_at,
updated_at` — `scope` is the optional quantified role-scope block
(reporting line, direct/indirect headcount, functions owned,
budget/hiring/decision authority, platform scale, org context); set
only the fields you know — an absent scope is omitted entirely,
never written as `null` or `{}`
accomplishments, skill_ids, skill_links, scope, artifact_links,
source, created_at, updated_at` — `scope` is the optional quantified
role-scope block (reporting line, direct/indirect headcount,
functions owned, budget/hiring/decision authority, platform scale,
org context); set only the fields you know — an absent scope is
omitted entirely, never written as `null` or `{}`
- stories: `id, title, skill_ids, experience_id, outcome, theme_tags,
source, created_at, updated_at`
artifact_links, source, created_at, updated_at`
- `artifact_links` (stories AND experiences) is an optional list of
evidence links `{url, label?}` — https-only URLs (≤ 500 chars),
optional label (≤ 120 chars), at most 8 per entity; an empty list is
omitted entirely, and an unset label is never written as `null`
- philosophies: `id, title, category, evidence_story_ids, source,
created_at, updated_at`
- **Story bodies use the STAR heading convention** — `## Situation`,
Expand All @@ -75,7 +79,7 @@ narrative text. Constraints:
skills the story evidences; an experience's `skill_ids` are the skills
exercised in that role, and its optional `skill_links` entries annotate
per-role emphasis on top of that list. (Both are additive, optional
keys; the current vault contract revision is 1.5 — `traitprint
keys; the current vault contract revision is 1.6 — `traitprint
proposals contract --json` prints the live contract.) A dangling
UUID does not break parsing — it surfaces as an audit finding. Never
fabricate UUIDs; copy them from `traitprint vault list` output.
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Artifact links (vault contract revision 1.6, additive) — provenance
ladder rung 1.** `stories/*.md` AND `experiences/*.md` frontmatter gain
an optional `artifact_links` list of evidence URLs pointing at public
artifacts (repo, PR, talk, press, published work). Each entry is
`{url, label?}`: `url` is https-only (plain `http` and every other
scheme are rejected) and capped at 500 characters, `label` is optional
and capped at 120, and an entity holds at most 8 links. Only set fields
are written (an unset `label` is absent, never `null`) and an empty
list never reaches frontmatter, so pre-1.6 vaults round-trip
byte-identically. `add/update_experience` and `add/update_story`
proposal payloads accept the key (cloud `vault_propose` lock-step from
day one); `get_profile_summary(depth="detailed")` and `find_story`
include an entity's links when present. Cloud mirrors the field 1:1 per
the provenance-ladder coordination spec.
- **Experience scope block (vault contract revision 1.5, additive).**
`experiences/*.md` frontmatter gains an optional `scope` object — the
quantified role-scope block recruiters need for calibration:
Expand Down
31 changes: 29 additions & 2 deletions docs/schema/vault-v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Status:** Stable contract (Phase 0 of the
[agent-native architecture](../../agent-native-architecture.md))
**Contract revision:** 1.5 (additive over 1.4 — see [Versioning](#versioning))
**Contract revision:** 1.6 (additive over 1.5 — see [Versioning](#versioning))
**Consumers:** the `traitprint` CLI/MCP server (read+write) and the
traitprint-cloud ingest pipeline (validate+project).

Expand Down Expand Up @@ -112,7 +112,21 @@ meaning — no contract revision tracks it.
attestations ("verified by former manager: `direct_reports=8`") can
attach without a schema migration — field-level attestation itself is
out of scope for this revision. Additive — `schema_version` stays `1`.
10. **Readers accept v0 (`vault.json`) and v1; writers emit v1 only.**
10. **`artifact_links[]` (optional, revision 1.6) carry evidence URLs on
stories AND experiences** (`$defs/artifactLink`) — provenance ladder
rung 1 ("artifact-supported"): links to public artifacts that evidence
the parent entity (repo, PR, talk, press, published work). Each entry
is `{ url, label? }`: `url` is required, **https-only** (plain `http`
and every other scheme are rejected) and capped at 500 characters;
`label` is optional short text capped at 120 characters — an unset
label is absent, never `label: null`. At most **8 links per entity**.
On experiences the links are top-level (role-level evidence; NOT
inside `scope` — they evidence the role broadly). The list defaults to
empty and an empty list is **never written** to frontmatter, so
pre-1.6 vaults round-trip byte-identically. Links ride their parent
entity through lenses (salience/suppression unaffected). Additive —
`schema_version` stays `1`.
11. **Readers accept v0 (`vault.json`) and v1; writers emit v1 only.**
`traitprint vault migrate` converts v0→v1 in place as a single git
commit. The lossless single-document JSON export remains available for
v0 consumers.
Expand All @@ -128,6 +142,19 @@ they implement.

### Revision history

- **1.6 (2026-07-17)** — additive: optional `artifact_links[]` on the
story AND experience entities (`$defs/storyFrontmatter`,
`$defs/experienceFrontmatter`, `$defs/artifactLink`) — provenance
ladder rung 1 ("artifact-supported"): evidence URLs pointing at public
artifacts (repo, PR, talk, press, published work; see rule 10). Each
entry is `{ url, label? }` with `url` https-only and ≤ 500 characters,
`label` optional and ≤ 120 characters, at most 8 links per entity.
Only set fields are written (an unset `label` is absent, never `null`)
and an empty list never reaches frontmatter, so pre-1.6 vaults
round-trip byte-identically. Experience links are top-level (role-level
evidence, deliberately outside `scope`). Cloud mirrors the field 1:1
per the provenance-ladder coordination spec. Older vaults without the
key remain valid; `schema_version` stays `1`.
- **1.5 (2026-07-15)** — additive: optional `scope` on the experience
entity (`$defs/experienceFrontmatter`, `$defs/experienceScope`) — the
quantified role-scope block for recruiter-grade calibration
Expand Down
14 changes: 13 additions & 1 deletion docs/schema/vault-v1/vault-v1.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"$id": "https://traitprint.com/schema/vault-v1.schema.json",
"title": "Traitprint Vault v1",
"description": "Contract for the vault v1 file tree. Each $defs entry validates one file type; see README.md for layout and markdown-body conventions.",
"$comment": "Contract revision 1.1 — adds optional experienceFrontmatter.skill_ids (additive; vaults written against 1.0 remain valid, schema_version stays 1). Revision 1.2 — adds optional experienceFrontmatter.skill_links (additive; vaults written against 1.0/1.1 remain valid). Revision 1.3 — adds optional profile basics.phone, basics.url and basics.profiles (additive; vaults written against 1.0-1.2 remain valid). Revision 1.4 — adds the optional top-level lenses.json array ($defs/lens): positioning lenses, an emphasis-only projection over the vault (canonical semantics in lens-v1); unique kebab-case slugs with 'none' reserved, at most one is_default, at most 20 lenses; also adds the add_lens/update_lens proposal kinds to $defs/proposal (the kinds shipped with 1.4; the enum here caught up in a later correction); additive, schema_version stays 1. Revision 1.5 — adds optional experienceFrontmatter.scope ($defs/experienceScope): quantified role scope (reporting line, headcount, budget/hiring/decision authority, platform scale, org context); all fields optional, only set fields are written (absent scope is omitted, never null/empty), attestation-ready — each field addressable as (experience_id, field_name); additive, vaults written against 1.0-1.4 remain valid, schema_version stays 1. Revision history in README.md.",
"$comment": "Contract revision 1.1 — adds optional experienceFrontmatter.skill_ids (additive; vaults written against 1.0 remain valid, schema_version stays 1). Revision 1.2 — adds optional experienceFrontmatter.skill_links (additive; vaults written against 1.0/1.1 remain valid). Revision 1.3 — adds optional profile basics.phone, basics.url and basics.profiles (additive; vaults written against 1.0-1.2 remain valid). Revision 1.4 — adds the optional top-level lenses.json array ($defs/lens): positioning lenses, an emphasis-only projection over the vault (canonical semantics in lens-v1); unique kebab-case slugs with 'none' reserved, at most one is_default, at most 20 lenses; also adds the add_lens/update_lens proposal kinds to $defs/proposal (the kinds shipped with 1.4; the enum here caught up in a later correction); additive, schema_version stays 1. Revision 1.5 — adds optional experienceFrontmatter.scope ($defs/experienceScope): quantified role scope (reporting line, headcount, budget/hiring/decision authority, platform scale, org context); all fields optional, only set fields are written (absent scope is omitted, never null/empty), attestation-ready — each field addressable as (experience_id, field_name); additive, vaults written against 1.0-1.4 remain valid, schema_version stays 1. Revision 1.6 — adds optional artifact_links on storyFrontmatter AND experienceFrontmatter ($defs/artifactLink): evidence URLs, provenance ladder rung 1 ('artifact-supported'); each entry is {url, label?} with url https-only and <= 500 chars, label optional <= 120 chars, at most 8 links per entity; only set fields are written (unset label is absent, never null) and an empty list never reaches frontmatter, so vaults written against 1.0-1.5 remain valid and round-trip byte-identically, schema_version stays 1. Revision history in README.md.",
"$defs": {
"uuid": {
"type": "string",
Expand Down Expand Up @@ -72,6 +72,16 @@
},
"additionalProperties": false
},
"artifactLink": {
"type": "object",
"description": "Revision 1.6: one artifact_links[] entry on a story or experience — an evidence URL (provenance ladder rung 1, 'artifact-supported'): a public artifact evidencing the parent entity (repo, PR, talk, press, published work). url is https-only; an unset label is omitted, never null. At most 8 entries per entity; an empty list is never written.",
"required": ["url"],
"properties": {
"url": { "type": "string", "pattern": "^https://", "minLength": 9, "maxLength": 500, "description": "https-only evidence URL with content after the scheme (plain http and every other scheme are rejected)." },
"label": { "type": "string", "maxLength": 120, "description": "Optional short display label, e.g. 'conference talk' or 'launch post'." }
},
"additionalProperties": false
},
"profileLink": {
"type": "object",
"description": "Revision 1.3: one basics.profiles[] entry — a JSON Resume profile item (social/professional link).",
Expand Down Expand Up @@ -162,6 +172,7 @@
"skill_ids": { "type": "array", "items": { "$ref": "#/$defs/uuid" } },
"skill_links": { "type": "array", "items": { "$ref": "#/$defs/skillLink" } },
"scope": { "$ref": "#/$defs/experienceScope" },
"artifact_links": { "type": "array", "items": { "$ref": "#/$defs/artifactLink" }, "maxItems": 8 },
"source": { "$ref": "#/$defs/source" },
"created_at": { "$ref": "#/$defs/timestamp" },
"updated_at": { "$ref": "#/$defs/timestamp" }
Expand All @@ -179,6 +190,7 @@
"experience_id": { "oneOf": [{ "$ref": "#/$defs/uuid" }, { "type": "null" }] },
"outcome": { "enum": ["", "win", "failure", "learning"] },
"theme_tags": { "type": "array", "items": { "type": "string" } },
"artifact_links": { "type": "array", "items": { "$ref": "#/$defs/artifactLink" }, "maxItems": 8 },
"source": { "$ref": "#/$defs/source" },
"created_at": { "$ref": "#/$defs/timestamp" },
"updated_at": { "$ref": "#/$defs/timestamp" }
Expand Down
30 changes: 28 additions & 2 deletions src/traitprint/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,8 @@ def _skill_key(s: Any) -> tuple[int, int, float]:
# refs skipped; experience skill_ids are contract revision 1.1+).
# scope (contract revision 1.5) is included only when the role has one;
# the dump carries only its set fields, so consumers never see nulls.
# artifact_links (contract revision 1.6) likewise appears only when the
# role carries evidence links.
skill_name_by_id = {s.id: s.name for s in vault.skills}
result["signature_experiences"] = [
{
Expand All @@ -852,6 +854,15 @@ def _skill_key(s: Any) -> tuple[int, int, float]:
if e.scope is not None
else {}
),
**(
{
"artifact_links": [
link.model_dump(mode="json") for link in e.artifact_links
]
}
if e.artifact_links
else {}
),
"evidence": None,
"disputed": e.id in disputes,
"dispute": disputes.get(e.id),
Expand Down Expand Up @@ -1134,6 +1145,19 @@ def _handle_find_story(
"related_experience_id": (
str(story.experience_id) if story.experience_id else None
),
# artifact_links (contract revision 1.6): evidence URLs ride
# the story only when present — same key-absent-when-empty
# discipline as the experience scope block.
**(
{
"artifact_links": [
link.model_dump(mode="json")
for link in story.artifact_links
]
}
if story.artifact_links
else {}
),
"match_score": _round3(score),
"evidence": None,
"disputed": story.id in disputes,
Expand Down Expand Up @@ -1357,7 +1381,8 @@ def create_server(store: VaultStore) -> FastMCP:
"+ top 5 skills; 'detailed' = + top 10 skills, signature "
"experiences (each with its scope block — reporting line, "
"headcount, budget/hiring/decision authority, platform scale, "
"org context — when the role has one), and core philosophies. "
"org context — and its artifact_links evidence URLs, when the "
"role has them), and core philosophies. "
"lens: optional positioning lens (slug or id) to project the "
"profile through — applies headline/bio overrides, the lens's "
"signature experiences, and skill salience (core skills lead, "
Expand Down Expand Up @@ -1426,7 +1451,8 @@ def search_skills(
"STAR-pattern narrative retrieval. 'Tell me about a time "
"when…' Provide at least one filter: query (free-text, "
"searches across all STAR fields), situation, theme, or "
"outcome. Structured filters take precedence over query."
"outcome. Structured filters take precedence over query. "
"Stories carrying artifact_links (evidence URLs) include them."
)
)
def find_story(
Expand Down
11 changes: 10 additions & 1 deletion src/traitprint/proposals.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@
#: ``ExperienceSchema.model_validate``), matching how every other
#: experience field is checked; ``scope: null`` (or ``{}``) clears the
#: block on update — same semantics as the cloud applier.
#: ``artifact_links`` is contract revision 1.6 (additive, experiences AND
#: stories: evidence URLs, provenance ladder rung 1 — see
#: ``$defs/artifactLink``). Shape validation (https-only, caps, max 8)
#: happens at apply time via :class:`ArtifactLink` through the entity
#: models; ``artifact_links: []`` clears the list on update. Kept in
#: lock-step with the cloud ``vault_propose`` applier from day one.
_EXPERIENCE_KEYS = (
"id",
"title",
Expand All @@ -98,6 +104,7 @@
"skill_ids",
"skill_links",
"scope",
"artifact_links",
"source",
"created_at",
"updated_at",
Expand All @@ -110,6 +117,7 @@
"experience_id",
"outcome",
"theme_tags",
"artifact_links",
"source",
"created_at",
"updated_at",
Expand Down Expand Up @@ -928,7 +936,8 @@ def proposal_diff(
# Dump the current entity to plain JSON values so every ``current``
# cell is renderable and ``proposals show --json`` stays serializable —
# model-typed fields (UUID lists, skill_links, the revision-1.5 scope
# block) would otherwise leak pydantic objects into the rows.
# block, revision-1.6 artifact_links) would otherwise leak pydantic
# objects into the rows.
current_dump: dict[str, Any] | None = (
current.model_dump(mode="json") if current is not None else None
)
Expand Down
Loading
Loading