Generate LLM-powered engineering profiles from OpenCode session data.
Built for talent discovery, hiring evaluation, and professional portfolio generation.
Quick Start
· How It Works
· Extending
· Output Format
· CLI Reference
opencode-builder-profile reads an OpenCode SQLite database and produces a structured JSON engineering profile. The pipeline has three stages: extract compacted metrics from the session DB, dispatch per-dimension LLM calls via GitHub Models, and assemble the results into a validated profile document.
Each profile dimension — coding velocity, technical breadth, system design thinking, tool mastery, collaboration signals, growth trajectory — receives its own focused LLM call with only the relevant extracted data. This prevents context bleed and keeps each assessment sharp.
The final output is a JSON document consumable by frontend applications, portfolio builders, or directly by hiring teams.
git clone https://github.com/YOUR_USER/opencode-builder-profile.git
cd opencode-builder-profile
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Configure your GitHub token for LLM access
cp .env.example .env
# Edit .env: set GITHUB_TOKEN=ghp_your_token_here
# Dry run: verify DB access (no LLM calls)
python opencode_builder_profile/ --dry-run
# Run a single extractor to inspect its compacted data
python opencode_builder_profile/ --extractor session_metrics --dry-run
python opencode_builder_profile/ --extractor tool_usage --dry-run
# Full profile generation (all extractors, all LLM calls)
python opencode_builder_profile/The --dry-run flag extracts and saves compacted data to outputs/sections/compacted_*.json but skips all LLM calls — no GITHUB_TOKEN required. Use --extractor <name> to target a single dimension during development or debugging.
┌───────────────────┐
│ opencode.db │
│ (SQLite via WAL) │
└────────┬──────────┘
│
┌─────────────▼─────────────┐
│ db/{sessions,messages, │
│ todos,projects}/ │
│ raw SQL queries │
└─────────────┬─────────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ session_metrics │ │ code_changes │ │ tool_usage │
│ extractor │ │ extractor │ │ extractor │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ compacted JSON │ │ compacted JSON │ │ compacted JSON │
│ (data, no LLM) │ │ (data, no LLM) │ │ (data, no LLM) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ LLM prompt v1 │ │ LLM prompt v2 │ │ LLM prompt v3 │
│ "Assess coding │ │ "Assess tech │ │ "Assess tool │
│ velocity..." │ │ breadth..." │ │ mastery..." │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ section JSON │ │ section JSON │ │ section JSON │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└─────────────────────┼──────────────────────┘
│
▼
┌──────────────────────────┐
│ assemble.py │
│ merge + Pydantic val. │
└───────────┬──────────────┘
│
▼
┌──────────────────────────┐
│ builder_profile.json │
│ (validated, final) │
└──────────────────────────┘
Each extractor is an independent module at opencode_builder_profile/extractors/<name>.py. Every extractor exports two symbols:
NAME: str— maps to the matching prompt file atprofile/prompts/<NAME>.txtextract(conn: sqlite3.Connection) -> dict— runs focused queries against the DB, returns a compact summary
The CLI iterates EXTRACTORS, calls each extractor, saves the compacted data, then feeds it to the corresponding LLM prompt. The LLM response is parsed as JSON and saved as a section file. After all sections complete, a final narrative call synthesizes everything into an executive summary.
Compacted data produced by session_metrics extractor for a real session history:
{
"total_sessions": 95,
"sessions_per_week": 18.2,
"avg_duration_min": 884.1,
"median_duration_min": 8.1,
"peak_hour_utc": 15,
"agent_distribution": [
{"agent": "explore", "count": 40},
{"agent": "build", "count": 34},
{"agent": "plan", "count": 18},
{"agent": "general", "count": 3}
],
"aggregate_tokens": {
"input": 30799090,
"output": 2813990,
"reasoning": 2293044
},
"total_cost": 2.78,
"duration_list": [273.8, 0.8, 1.9, 11.0, 36.7, ...]
}LLM prompt (profile/prompts/session_metrics.txt):
You are a talent assessment analyst evaluating an AI-assisted software
engineer's coding velocity and work patterns.
Based on the extracted session metrics data below, produce a JSON
assessment of this engineer's coding velocity and work patterns.
Output ONLY valid JSON with this exact structure:
{
"sessions_per_week": <float>,
"avg_session_duration_min": <float>,
"consistency_score": <float between 0 and 1>,
"assessment": "...",
"strengths": [...],
"signals": [...]
}
Be honest and critical. Low session counts or irregular patterns should
be reflected in low consistency scores.
LLM response saved as outputs/sections/session_metrics.json:
{
"sessions_per_week": 18.2,
"avg_session_duration_min": 14.7,
"consistency_score": 0.72,
"assessment": "High session volume suggests consistent daily engagement with the tool. The wide gap between mean and median indicates a mix of quick tasks and deep-dive sessions, typical of an engineer who handles both exploratory research and sustained implementation work.",
"strengths": ["Consistent daily engagement across 5-week span", "High session volume (18+/week)"],
"signals": ["Peak activity at 15:00 UTC suggests afternoon depth work", "Mix of sub-minute and multi-hour sessions indicates varied task complexity"]
}| Dimension | Extractor | Data Sources | LLM Evaluates |
|---|---|---|---|
| Coding Velocity | session_metrics |
Session count, durations, hourly/weekly distribution, token volumes | Work pace consistency, peak productivity windows, engagement patterns |
| Technical Breadth | code_changes |
File diffs, language distribution, additions/deletions, most-changed files | Language versatility, codebase exploration patterns, depth vs. breadth |
| System Design Thinking | agent_workflow |
Agent switching sequence, sub-agent depth, agent distribution | Problem decomposition strategy, delegation patterns, architectural reasoning |
| Tool Mastery | tool_usage |
Tool call counts, read/edit/bash ratios, unique tools, sample commands | Workflow efficiency, tool diversity, exploration vs. execution balance |
| Collaboration | conversation |
Prompt lengths, reasoning parts, user/assistant message counts, sample prompts | Communication clarity, reasoning engagement, specification quality |
| Growth Trajectory | project_diversity |
Distinct projects, session distribution, project names, directories | Learning velocity, complexity progression, skill breadth |
| Task Execution | task_completion |
Todo completion rate, priority distribution, sample tasks | Planning discipline, follow-through, priority management |
| Narrative Summary | synthesis | All section outputs above | Executive synthesis, hiring recommendation, risk factors |
outputs/
├── sections/
│ ├── compacted_session_metrics.json # Raw extractor output: durations, counts, distributions
│ ├── session_metrics.json # LLM assessment: velocity scores, consistency, signals
│ ├── compacted_code_changes.json # Raw extractor output: files, languages, diffs
│ ├── code_changes.json # LLM assessment: breadth score, depth signals
│ ├── compacted_tool_usage.json # Raw extractor output: tool calls, ratios
│ ├── tool_usage.json # LLM assessment: efficiency, workflow patterns
│ ├── compacted_agent_workflow.json # Raw extractor output: switches, sub-agents
│ ├── agent_workflow.json # LLM assessment: design thinking, delegation
│ ├── compacted_conversation.json # Raw extractor output: prompts, reasoning
│ ├── conversation.json # LLM assessment: clarity, engagement
│ ├── compacted_project_diversity.json # Raw extractor output: projects, directories
│ ├── project_diversity.json # LLM assessment: growth trajectory
│ ├── compacted_task_completion.json # Raw extractor output: todos, completion
│ ├── task_completion.json # LLM assessment: discipline, planning
│ └── narrative_summary.json # LLM synthesis: executive summary
└── builder_profile.json # Merged, Pydantic-validated final profile
{
"meta": {
"generated_at": "2026-07-01T20:13:45+00:00",
"total_sessions": 95,
"total_projects": 20,
"sections_generated": [
"session_metrics", "code_changes", "tool_usage",
"agent_workflow", "conversation", "project_diversity",
"task_completion", "narrative_summary"
]
},
"coding_velocity": {
"raw": {
"sessions_per_week": 18.2,
"consistency_score": 0.72,
"assessment": "...",
"strengths": [...],
"signals": [...]
}
},
"technical_breadth": { "raw": { ... } },
"system_design_thinking": { "raw": { ... } },
"tool_mastery": { "raw": { ... } },
"collaboration": { "raw": { ... } },
"growth": { "raw": { ... } },
"narrative_summary": { "raw": { ... } }
}Each dimension's raw field contains the exact JSON output from its LLM call. The schema is defined as a Pydantic BaseModel in opencode_builder_profile/profile/schema.py and validated before write.
The system is designed so that adding a new dimension requires touching exactly four locations — three of which are single-line additions:
1. opencode_builder_profile/extractors/foo.py # 25-50 lines
2. opencode_builder_profile/extractors/__init__.py # +1 import
3. opencode_builder_profile/profile/prompts/foo.txt # system prompt
4. opencode_builder_profile/profile/schema.py # optional: add section enum entry
Step-by-step:
- Create the extractor at
extractors/foo.py:
NAME = "error_recovery"
def extract(conn):
"""Query the DB for rejection/retry patterns."""
rejected_count = conn.execute(
"SELECT COUNT(*) FROM part WHERE json_extract(data, '$.type') = 'tool' AND json_extract(data, '$.state.status') = 'error'"
).fetchone()[0]
return {
"total_errors": rejected_count,
"recovery_rate": 0.85,
}- Register it in
extractors/__init__.py:
EXTRACTORS = [
session_metrics,
code_changes,
tool_usage,
agent_workflow,
conversation,
project_diversity,
task_completion,
error_recovery, # ← new
]- Write the prompt at
profile/prompts/error_recovery.txt:
You are a talent assessment analyst evaluating an AI-assisted software
engineer's error recovery and debugging patterns.
Based on the extracted error data below, assess their resilience,
debugging methodology, and ability to recover from failures.
Output ONLY valid JSON with this exact structure:
{
"error_frequency": <string>,
"recovery_efficiency": <float between 0 and 1>,
"debugging_approach": "...",
"assessment": "...",
"strengths": [...],
"development_areas": [...]
}
That's it. No config files, no CLI changes, no wiring beyond the registry import. The CLI auto-discovers EXTRACTORS and maps each extractor's NAME to profile/prompts/{NAME}.txt.
Delete the extractor file and remove its import from extractors/__init__.py. The CLI will skip it cleanly.
| Variable | Required | Default | Description |
|---|---|---|---|
GITHUB_TOKEN |
Yes | — | GitHub Personal Access Token for GitHub Models API. Create at github.com/settings/tokens — no special scopes needed. |
GITHUB_MODEL |
No | gpt-4o-mini |
Model name for all LLM calls. Accepts any model available through the GitHub Models endpoint. |
OPENCODE_DB_PATH |
No | ~/.local/share/opencode/opencode.db |
Override path to opencode.db. Useful for non-standard installs or inspecting a different user's database. |
OUTPUT_DIR |
No | ./outputs |
Directory for all output files (sections/ and builder_profile.json). |
| Flag | Override |
|---|---|
--db PATH |
OPENCODE_DB_PATH |
--model NAME |
GITHUB_MODEL |
--output DIR |
OUTPUT_DIR |
--dry-run |
Skip all LLM calls — extract and save compacted data only |
The tool searches for opencode.db in the default XDG data directory (~/.local/share/opencode/). If your OpenCode installation uses a custom path (visible in OpenCode's settings or via echo $OPENCODE_DB), pass --db or set OPENCODE_DB_PATH.
python opencode_builder_profile/ [OPTIONS]
Options:
--dry-run Extract data and save compacted metrics only.
No LLM calls. No GITHUB_TOKEN required.
--extractor NAME Run a single extractor by name (e.g. session_metrics).
Combine with --dry-run to inspect raw data without an LLM call.
--db PATH Path to opencode.db (overrides env var).
--model NAME GitHub Models model (overrides env var).
Default: gpt-4o-mini
--output DIR Output directory (overrides env var).
Default: ./outputs
Examples:
# Verify your DB is accessible
python opencode_builder_profile/ --dry-run
# Inspect compacted data for one dimension
python opencode_builder_profile/ --extractor session_metrics --dry-run
# Use a specific DB and model
python opencode_builder_profile/ --db /custom/path/opencode.db --model gpt-4o
# Output to a specific directory
python opencode_builder_profile/ --output ./my-builder-profileopencode-builder-profile/
├── pyproject.toml # Project metadata
├── requirements.txt # Runtime dependencies
├── .env.example # Configuration template
├── opencode_builder_profile/
│ ├── __init__.py
│ ├── __main__.py # Entry point
│ ├── cli.py # CLI orchestration
│ ├── config.py # Env loading, DB detection
│ ├── utils.py # Shared helpers
│ ├── db/
│ │ ├── connection.py # SQLite connection (WAL, Row factory)
│ │ ├── sessions.py # Session queries: aggregates, distributions
│ │ ├── messages.py # Message/part queries: texts, tool calls, diffs
│ │ ├── todos.py # Todo queries: completion rates, priorities
│ │ └── projects.py # Project queries: counts, worktrees
│ ├── extractors/
│ │ ├── __init__.py # EXTRACTORS registry — add new extractors here
│ │ ├── session_metrics.py # Duration, frequency, consistency
│ │ ├── code_changes.py # Languages, files, diff volumes
│ │ ├── tool_usage.py # Tool diversity, read/edit/bash ratios
│ │ ├── agent_workflow.py # Agent switching, sub-agent delegation
│ │ ├── conversation.py # Prompt/reasoning analysis
│ │ ├── project_diversity.py # Project breadth, tech stacks
│ │ └── task_completion.py # Todo completion, priority distribution
│ └── profile/
│ ├── client.py # Requests-based GitHub Models API client
│ ├── prompts/ # One .txt prompt per extractor
│ │ ├── session_metrics.txt
│ │ ├── code_changes.txt
│ │ ├── tool_usage.txt
│ │ ├── agent_workflow.txt
│ │ ├── conversation.txt
│ │ ├── project_diversity.txt
│ │ └── task_completion.txt
│ ├── assemble.py # Prompt templates, section mapping
│ └── schema.py # Pydantic models for final profile
└── outputs/ # Generated (gitignored)
├── sections/ # Per-dimension LLM responses
└── builder_profile.json # Merged final profile
A single large call with all data produces shallower analysis — the LLM's attention is diluted across irrelevant metrics. Per-dimension calls keep context windows focused, allow independent prompt engineering, and limit blast radius: if one LLM call fails, only that dimension is skipped.
The raw DB output for 95 sessions includes millions of rows across message and part tables. Passing this raw data would exceed LLM context limits. Each extractor aggregates into a compacted summary (1-5 KB) before the LLM call. This also means the DB queries are the only expensive operation — LLM calls stay cheap and fast.
The intermediate compacted dicts are unvalidated by design — extractors are free to shape their output however makes sense. Pydantic is reserved for the final builder_profile.json, which must conform to a stable schema for frontend consumption. The schema can be exported as JSON Schema via BuilderProfile.model_json_schema().
GitHub Models provides free API access with any GitHub PAT (no billing setup required). The endpoint is OpenAI-compatible, so swapping to another provider (OpenAI, Anthropic, etc.) requires changing only profile/client.py.
| Approach | Data Resolution | LLM Call Efficiency | Extensibility | Dependencies |
|---|---|---|---|---|
| Single monolithic prompt | Medium — all data in one context | Poor — large context, diluted attention | Low — any change retrains all | 1 call |
| Per-dimension (this project) | High — each dimension gets full attention | Good — small contexts, focused prompts | High — add/remove dimensions independently | N calls (N = dimensions) |
| No LLM, rule-based scoring | Low — heuristic approximations | N/A | Medium — requires code for each new metric | 0 calls |
MIT