feat(talents): add participation talent with structured role/source per span
Introduce a per-span participation talent that produces structured
participation records (role, source, confidence, context, entity_id)
for each activity, consolidating per-segment Sense entity drafts.
Three coordinated changes:
1. Extend talent/sense.md entities schema with role (attendee|mentioned) and source (voice|speaker_label|transcript|screen|other), plus a prompt-level contamination guard for screen-only names and tool/app UI.
2. Add talent/participation.md (quality-judge, activity-scheduled,
tier 3) and talent/participation.py post-hook. The hook parses
the LLM JSON, resolves entities read-only via
think.entities.matching.find_matching_entity, and atomically
merges participation + participation_confidence onto the
activity record.
3. Generalize think/activities.update_record_description into
update_record_fields, preserving the atomic tempfile+rename
write. update_record_description becomes a thin wrapper.
Sense drafts reach participation via think/sense_splitter.py, which
now also writes a sense.md summary alongside sense.json. This was
the only viable load path because think/cluster.py only discovers
talents/**/*.md — sense.json is invisible to the existing load
mechanism. The alternative (widening cluster) was rejected as out
of scope.
active_entities on activity records is preserved unchanged for
backward compatibility. The state-machine flatten in
think/activity_state_machine.py is untouched.
The entity resolver is read-only: it calls find_matching_entity
against already-loaded entities and injects entity_id (or null) per
participation entry. It never writes under facets/{facet}/entities/.
Tests cover: Sense schema, contamination guard prose, participation
frontmatter/prompt, resolver file-count invariance, contamination
regression at the plumbing layer, and atomic activity-record merge
including malformed-JSON early return.
Co-authored-by: Codex <codex@openai.com>