ADR-017: Curriculum schema: attested_involvement, skill derivation, CV export
Date: 2026-05-25 Status: Accepted Author: Rômulo Febasi
Context
ADR-014 §Future direction flagged a “curriculum surface” as the follow-up to the identity bundle: turn the workspace’s accumulated context into something a human can hand to an employer. v0.3 shipped the bundle without the surface. v0.4 closes it.
The surface has to satisfy three constraints simultaneously, and any two of them in isolation produce a bad design:
- Auditable. The output must be reconstructible from the workspace
without an LLM in the loop. A regulator or hiring manager who reruns
syn cv exporton the same inputs gets identical bytes back. - Human-controlled. The user, not Synapse, decides which
involvement counts as part of their CV. There is no auto-promotion
from “you touched this project” to “this is on your CV”. The
decision is an explicit
syn experience add. - Useful as a CV. The output is something a recruiter or collaborator can read end-to-end without first learning the data model. That rules out raw frontmatter dumps.
The line that is easiest to blur in implementation: syn experience add writes a signed claim about the human’s past; capture_fact
(BR-12.3) writes a free-form fact the LLM proposed. Conflating them
would make the bundle’s attested_involvement array unreliable.
Decision
Adopt three coupled artefacts: an explicit attestation array on the self person, a tag-frequency skill ranking, and a deterministic Markdown renderer.
attested_involvement array. Lives on the self: true person
entity’s frontmatter. Each entry has the shape:
attested_involvement:
- type: project # or api, entity, task, note
id: cliente-alfa # slug
role: tech-lead # free-text
since: 2024-01-01 # optional ISO date
until: 2024-12-31 # optional; omitted = present
title: "Tech lead at Cliente Alfa" # display title at the time
The array is the bundle’s source of truth for past involvement; the
v0.3 fallback that derived attestations from memberships still runs
when the explicit array is empty so users who have not migrated do
not lose data.
syn experience add|list|rm. The CLI surface is intentionally
narrow:
add <title> --target {type}/{id} --role R [--since DATE] [--until DATE]appends one entry. Idempotent on(type, id); a duplicate add errors out and points the user atsyn experience rmto replace.listrenders the array (text or JSON).rm {type}/{id}removes every entry whose target matches.
These commands rewrite the markdown of the self person atomically
(tx.write_file + commit). They never touch other entities.
Skill derivation (v0.4 algorithm). syn skills counts the
occurrences of each tag across every entity in the workspace and
ranks count DESC, name ASC. The top N (default 20, capped 200)
ships in the bundle’s skills array and renders in the CV. This is
the simplest defensible signal: a tag that appears on many entities
is a topic the human spends real time on.
The v0.5+ direction (not in scope here, recorded as Future direction):
- Project depth. Weight tags by the lifecycle phase of the
project they appear on (a tag on
status: activecounts more than one onstatus: archived). - Decision count. Boost tags that recur in note
kind: decisionfrontmatter (the person is making calls in this space, not just observing). - Owner-resolved attribution. Filter to entities where the self
person appears in
membershipsorowner. v0.4 counts every tag occurrence regardless of who created it; that is fine for the single-user case but inflates totals for shared workspaces.
CV renderer (syn cv export). A pure function from (workspace, unsigned identity bundle) to Markdown bytes:
# {person.title}heading.- Optional headline (frontmatter field
headline). - Contacts line (
email,github,linkedin,website). ## Summaryfrom the person body, trimmed.## Skills:detailedshowsname (Nx);minimalshows comma list. Top N from the bundle’sskills.## Experience:attested_involvementnewest-first bysince, each entry### {title}: {role}+ date range +ref: {type}/{id}.## Recent decisions(detailed only): last 5 notes withkind: decision, sortedupdated DESC, id ASC. Capped, the list ends with the comment marker<!-- cv: capped at 5 most-recent decisions -->.
--format json emits the structured Cv intermediate so other tools
(Pandoc, a custom Hugo template) can render their own layout without
having to re-extract the data.
Determinism is non-negotiable: same inputs → identical bytes. That is
why the renderer never sorts by created (which fluctuates with
imports), never includes a timestamp in the output, and never touches
the LLM.
PDF export is not in v0.4. Adding it would pull in a heavy
dependency (typst or headless chromium). The user can pipe the
Markdown through pandoc if they need PDF today; a first-party
renderer lands in v0.5+ if the demand justifies the dep.
Consequences
attested_involvementbecomes the canonical place to record what the human wants to claim. The bundle’sattestationsarray serialises from it.- The CV renders without any model invocation. The model is welcome to polish the output afterwards: that path is documented in docs/IDENTITY.md, but the canonical CV is reproducible.
syn skillsexposes a number the user can argue with. A surprising top tag is a hint to clean up the workspace or rename a tag, not a hint to call ranking broken.- The v0.4 skill algorithm is intentionally crude. The followups are flagged as Future direction so a reader six months out knows the team has not forgotten about them.
- The line between
syn experience add(human signs a past) andcapture_fact(LLM enqueues a write proposal) stays sharp: the former runs atomically, the latter goes through the pending queue for review.
Alternatives considered
- LLM-generated CV. Rejected at the start: the value of a reproducible CV is exactly that two different model runs cannot produce two different “you”. The model can polish; it cannot author.
- Auto-derive
attested_involvementfrommemberships. Kept as a fallback for v0.3 bundles but not the default. Explicit is better than implicit for something the user will hand to an employer. - Embed PDF rendering directly. Postponed. The Markdown surface is the contract; a renderer is a layer above and can ship separately.
- Carry skills outside the bundle. Rejected: a bundle is self-contained or it is not portable. The N=20 cap keeps it tiny (<2KB typical).
- First-class “endorsements” from other workspaces. Tempting and also a privacy minefield. Federation (BR-12.4) is opt-in workspace-side; cross-workspace endorsements would need a separate exchange protocol. Reserved for v0.6+.