Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 export on 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 at syn experience rm to replace.
  • list renders 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: active counts more than one on status: archived).
  • Decision count. Boost tags that recur in note kind: decision frontmatter (the person is making calls in this space, not just observing).
  • Owner-resolved attribution. Filter to entities where the self person appears in memberships or owner. 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:

  1. # {person.title} heading.
  2. Optional headline (frontmatter field headline).
  3. Contacts line (email, github, linkedin, website).
  4. ## Summary from the person body, trimmed.
  5. ## Skills: detailed shows name (Nx); minimal shows comma list. Top N from the bundle’s skills.
  6. ## Experience: attested_involvement newest-first by since, each entry ### {title}: {role} + date range + ref: {type}/{id}.
  7. ## Recent decisions (detailed only): last 5 notes with kind: decision, sorted updated 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_involvement becomes the canonical place to record what the human wants to claim. The bundle’s attestations array 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 skills exposes 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) and capture_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_involvement from memberships. 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+.