Skip to content

Instantly share code, notes, and snippets.

@T-Gro
Last active April 21, 2026 13:45
Show Gist options
  • Select an option

  • Save T-Gro/e53fdc21c79ab70ab183514b8d979a57 to your computer and use it in GitHub Desktop.

Select an option

Save T-Gro/e53fdc21c79ab70ab183514b8d979a57 to your computer and use it in GitHub Desktop.
Employee Profile Extraction Pipeline — for Copilot CLI Opus sessions
name employee-profile-extraction
description Extracts a generalized employee profile from the user's full Copilot CLI session history. Invoke to understand who someone is, what they do, and how they work — not what they did last week.

Employee Profile Extraction Pipeline

Build a generalized employee profile from Copilot CLI local session data. Produces a structural description of role, work inboxes, competencies, and work patterns — like an employee description, not a sprint report.

Pipeline

Wave 1: Collect       Wave 1.5: Engagement    Wave 2: Classify        Wave 3: Synthesize
────────────────      ──────────────────      ────────────────        ─────────────────
Session store SQL →   Turn-gap analysis   →   Parallel sub-agents →   Single Opus agent
  sessions              per-session:            ~15 sessions each       reads classified
  turns (turn 0)        - steering turns        classify each by:       + engagement data
  checkpoints           - reviewing turns       - category              produces profile
  files touched         - autonomous turns      - domain                JSON
  refs (PRs/issues)     → engagement_mode       - complexity
  FTS5 search             per session           - role signal        Wave 4: Present
                                                - derived pattern    ─────────────────
                                                - engagement_mode     Format tables
                                              Anti-overfitting:       Publish gist
                                                1 branch = 1 vote
                                                normalize by category
                                                drop sprint-specific

Wave 1: Collect

Data source: session_store database (read-only, database: "session_store").

-- Schema you need:
-- sessions: id, cwd, repository, branch, summary, created_at
-- turns: session_id, turn_index, user_message, assistant_response
-- checkpoints: session_id, checkpoint_number, title, overview, work_done, next_steps
-- session_files: session_id, file_path, tool_name
-- session_refs: session_id, ref_type (commit/pr/issue), ref_value
-- search_index: FTS5 (content, session_id, source_type) — use MATCH for full-text

-- Start here: all sessions, no time filter
SELECT id, repository, branch, summary, created_at FROM sessions ORDER BY created_at DESC;

For each real session (filter out automated/verifier noise — short summaries, repetitive bot patterns), collect:

  • First user message (turns where turn_index = 0) — this is the intent signal
  • Checkpoint overviews — these are work summaries
  • Files touched — these reveal technical domains
  • Refs — these reveal collaboration surface (PRs, issues)
  • Repository + branch — these reveal project scope

Export to JSON backup files under /tmp/profile-extraction/. SQLite is the working copy; JSON is the safety net.

Scale: Expect hundreds to thousands of sessions. Batch into sub-agents of ~15 sessions each. Do not try to fit all session content into one context window.

Wave 1.5: Engagement Mode Analysis

Compute turn-to-turn time gaps per session. The gap between a user's message and their next message reveals whether the human was present, reviewing, or gone.

-- Per-session: compute gap between consecutive user turns
SELECT t1.session_id, t1.turn_index,
  ROUND((julianday(t2.timestamp) - julianday(t1.timestamp)) * 24 * 60, 1) as gap_minutes,
  substr(t1.user_message, 1, 100) as msg
FROM turns t1
JOIN turns t2 ON t2.session_id = t1.session_id AND t2.turn_index = t1.turn_index + 1
ORDER BY t1.session_id, t1.turn_index

Three engagement modes

Mode Gap Keyword signals Human posture
Hands-on <5 min Corrections, follow-ups, frustration markers ("srsly?", "wtf") Sitting at the problem. Active co-pilot usage. Rapid steering, real-time course correction.
Reviewing 5–60 min Questions, "what about...", "is this right?" Reading AI output, thinking, then giving next direction. Side-by-side but not real-time.
Orchestrating >60 min "autopilot", "GO", "trust you", "fire and forget", TODO lists Human left. Agent runs autonomously. Human returns later to inspect results and steer next phase.

Per-session engagement profile

Classify each session by its dominant mode (plurality of turns):

-- Aggregate per session
SELECT session_id,
  COUNT(*) as total_turns,
  SUM(CASE WHEN gap_minutes < 5 THEN 1 ELSE 0 END) as hands_on,
  SUM(CASE WHEN gap_minutes BETWEEN 5 AND 60 THEN 1 ELSE 0 END) as reviewing,
  SUM(CASE WHEN gap_minutes > 60 THEN 1 ELSE 0 END) as orchestrating
FROM turn_gaps
GROUP BY session_id

Assign engagement_mode per session: hands-on, reviewing, orchestrating, or mixed (no mode >50%).

Also compute per-session:

  • session_span_hours: time from first to last turn — reveals multi-day vs single-sitting work
  • overnight_gaps: count of gaps >8h — reveals fire-and-forget patterns

Pass engagement_mode, session_span_hours, and mode distribution to Wave 2 classifiers as metadata alongside the session content. This lets classifiers answer: "Does this person orchestrate perf work but hands-on steer public-facing artifacts?"

What this feeds into the profile

The synthesis agent (Wave 3) receives aggregated engagement data:

  • Distribution of engagement modes across all sessions
  • Engagement mode × task category cross-tabulation (e.g., "orchestrates CI triage, hands-on for review feedback")
  • This populates the ai_integration_profile and collaboration_profile.delegation_style sections

Wave 2: Classify

Launch parallel sub-agents (~15 sessions per agent). Each agent receives full context for its batch (summary, first user message, checkpoints, files, refs) and classifies each session:

{
  "session_id": "...",
  "task_category": "...",     // derive from data, don't hardcode list
  "domain": "...",            // technical domain
  "complexity": "low|medium|high|extreme",
  "role_signal": "...",       // what this says about who the person IS
  "derived_pattern": "..."    // generalizable work pattern (1 sentence)
}

Bootstrap the taxonomy. Do NOT use a hardcoded category list. First agent gets a stratified sample and proposes categories. Remaining agents use that taxonomy. Refine after: merge categories with <5 sessions, split categories with >30% of total.

Store all classifications in the session SQLite database. Aggregate:

  • Category distribution (count + percentage)
  • Complexity distribution
  • Role signal frequency
  • Derived patterns grouped by category

Anti-Overfitting Rules (critical — do not skip)

  1. Normalize by category, not volume. One branch = one vote. 150 sessions on one branch ≠ 150× weight. It's one instance of one category.
  2. Distinguish sprint work from role pattern. If it wouldn't appear in a different 3-week window 6 months from now, it's sprint work — collapse it into its category, don't feature it.
  3. Automated sessions are metadata, not content. If the user ran 200 automated verification loops, that's evidence of one pattern ("uses autonomous verification fleets"), not 200 data points.
  4. Generalize, don't transcribe. "Root-cause debugging across compiler subsystems" is a pattern. "Fixed NRE in layoutTyparRef on line 732" is a sprint artifact.

Wave 3: Synthesize

Single Opus agent. Receives ONLY:

  • Aggregated category/complexity distributions (SQL output)
  • Deduplicated role signals
  • Derived patterns grouped by category
  • Repository distribution
  • Communication style signals (from user messages — tone, instruction patterns, quality standards)

Does NOT receive: raw turns, full checkpoint text, file paths, PR numbers.

Produces profile JSON with these sections:

Section Content Rules
profile_title Role title 5 words max
one_liner Who they are 1 sentence
work_inboxes Structural input channels Name the channel + source + shape + processing pattern. NO specific items.
work_dimensions What they do, with time % Must sum to 100%. Cross-reference with any self-reported allocation if found in session data.
core_competencies What they're good at evidence_strength: strong (3+ sessions) / moderate (2) / signal (1)
engineering_philosophy How they think Universal principles, not project-specific rules
ai_integration_profile How they use AI Structural patterns, not specific tool invocations
collaboration_profile How they work with others Mentoring, review style, delegation, communication
tools_and_systems Repos, platforms, frameworks Structural names only
distinguishing_traits What makes them stand out Would survive a job change
growth_vectors Where they're investing Forward-looking

Output format rules

  • Name inboxes structurally: "GitHub review-requested PRs in {repo}" — not "PR #19505"
  • Name repos, labels, queue mechanisms, flow shapes. Not current contents.
  • NO PR numbers, branch names, issue numbers, commit SHAs in the profile body.
  • NO function names or file paths unless they represent stable subsystems.
  • Evidence appendix (max 10 entries): short grounding statements to prove claims. Only place where specifics are allowed.

Wave 4: Present

Render two markdown tables:

  1. Work Inboxes — channel / source / shape / processing
  2. Work Dimensions — dimension / % / description

Publish to a GitHub gist via gh gist create.

Regularization Checklist (run before publishing)

  • No single feature/branch dominates >1 table row
  • Every work dimension would appear in a different 3-week sample
  • Inboxes describe channels, not current items
  • Profile reads as employee description, not data analysis output
  • Zero PR numbers, branch names, or commit SHAs outside evidence appendix
  • Time percentages sum to 100% and cross-check against self-reported allocation if available
  • Automated/bot sessions collapsed into pattern evidence, not inflated as volume
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment