SmolForge can store and display the AI agent conversations that produced your code, linked directly to the commits they generated.
SmolForge normalizes agent-specific transcript files into this common shape:
{
"session_id": "stable-agent-session-id",
"agent_type": "codex",
"commit_sha": "optional 40-character git sha",
"session_started_at": "optional ISO timestamp",
"session_ended_at": "optional ISO timestamp",
"messages": [
{
"role": "user|assistant|system|developer",
"content": "plain text, markdown, or compact tool summary",
"timestamp": "optional ISO timestamp",
"image_keys": []
}
]
}Server-side conformance rules:
session_id must be stable enough to deduplicate a real agent session in the UI.agent_type should be one of claude-code, claude-cowork, codex, cursor, factory, devin, opencode, aider, continue, or custom.message_count is the count of normalized messages stored for the session.session_started_at and session_ended_at come from agent timestamps when present; otherwise SmolForge derives them from message timestamps.linked_commits contains only commit SHAs that still resolve in the repository object store.start_ordering, end_ordering, and a positive segment_message_count.Expected UI behavior:
linked_commits shows commit chips and a View affordance.AI-Session: <session-id>Git trailers are standard commit message metadata (like Co-authored-by or Signed-off-by). They survive rebases, cherry-picks, and format-patch. They're just text in the commit message.
Use the SmolForge CLI instead of hand-writing hooks:
sf hooks install alice/demo --agent all --git post-commit,pre-push
sf hooks status alice/demoCurrent support:
| Agent | Capture strategy |
|---|---|
| Claude Code | repo-local PostToolUse hook plus Git backstop |
| Codex | Git hook backstop plus ~/.codex/sessions JSONL discovery |
| Cursor CLI | repo-local stop hook plus ~/.cursor/projects/.../agent-transcripts JSONL discovery |
| Factory Droid | repo-local SessionEnd hook using hook transcript_path plus ~/.factory/sessions discovery |
| Devin for Terminal | repo-local SessionEnd hook plus best-effort SQLite extraction from ~/.local/share/devin/cli/sessions.db or ~/.config/devin/cli/sessions.db |
| OpenCode | Git hook backstop plus best-effort SQLite extraction from ~/.local/share/opencode/opencode*.db |
| Aider, Continue, custom JSONL agents | Git hook backstop and explicit sf transcript upload --file ... |
When you commit after a Claude Code session, add the session trailer:
# Find your current Claude Code session ID
SESSION_ID=$(ls -t ~/.claude/projects/*/sessions/*.jsonl 2>/dev/null | head -1 | xargs -I{} basename {} .jsonl)
# Commit with trailer
git commit -m "Add landing page
AI-Session: $SESSION_ID
Co-Authored-By: Claude <noreply@anthropic.com>"Or add it manually:
git commit -m "Fix rate limiting bug
Increased all rate limits 10x to accommodate hover prefetching.
AI-Session: d9b29d45-f833-4e98-ba0a-510931675178"git push origin main# Set your SmolForge credentials
SF_TOKEN="your-smolforge-jwt-token"
SF_URL="https://forge.smol.ai"
OWNER="your-username"
REPO="your-repo"
# Extract and upload the Claude Code transcript
SESSION_FILE="$HOME/.claude/projects/$(pwd | sed 's|/|-|g')/$SESSION_ID.jsonl"
# Parse JSONL into the upload format
python3 -c "
import json, sys
messages = []
for line in open('$SESSION_FILE'):
entry = json.loads(line)
if entry.get('type') == 'human':
messages.append({'role': 'user', 'content': entry.get('message', {}).get('content', ''), 'timestamp': entry.get('timestamp', '')})
elif entry.get('type') == 'assistant':
content = entry.get('message', {}).get('content', '')
if isinstance(content, list):
content = ' '.join(c.get('text', '') for c in content if c.get('type') == 'text')
messages.append({'role': 'assistant', 'content': content, 'timestamp': entry.get('timestamp', '')})
payload = {'session_id': '$SESSION_ID', 'agent_type': 'claude-code', 'commit_sha': '$(git rev-parse HEAD)', 'messages': messages}
print(json.dumps(payload))
" | curl -s -X POST "$SF_URL/api/repos/$OWNER/$REPO/transcripts" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-d @-Go to the commit page on SmolForge. The transcript appears as an expandable "AI Transcript" section showing your conversation with the agent.
Same approach — just change the agent_type:
curl -X POST "$CF_URL/api/repos/$OWNER/$REPO/transcripts" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"session_id": "codex-session-abc",
"agent_type": "codex",
"commit_sha": "abc123...",
"messages": [
{"role": "user", "content": "Fix the login bug", "timestamp": "2026-03-29T08:00:00Z"},
{"role": "assistant", "content": "I found the issue...", "timestamp": "2026-03-29T08:01:00Z"}
]
}'If your transcript includes screenshots or images you shared with the AI:
# Upload an image
curl -X POST "$CF_URL/api/repos/$OWNER/$REPO/transcripts/$SESSION_ID/images" \
-H "Authorization: Bearer $CF_TOKEN" \
-F "image=@screenshot.png"
# Returns: {"key": "repo-id/transcripts/session-id/img-abc.png"}
# Include this key in the message's image_keys array| Method | Endpoint | Description |
|---|---|---|
| POST | /api/repos/:owner/:repo/transcripts |
Upload a transcript |
| GET | /api/repos/:owner/:repo/transcripts |
List transcripts (paginated) |
| GET | /api/repos/:owner/:repo/transcripts/:sessionId |
Get transcript + messages |
| GET | /api/repos/:owner/:repo/commits/:sha/transcript |
Get transcript for a commit |
| POST | /api/repos/:owner/:repo/transcripts/:sessionId/images |
Upload image |
| GET | /api/repos/:owner/:repo/transcripts/images/:key |
Get image |
SmolForge automatically masks sensitive patterns in transcript content:
| Pattern | What it masks |
|---|---|
cfut_* |
Cloudflare API tokens |
sk-* |
OpenAI API keys |
ghp_* |
GitHub personal access tokens |
AKIA* |
AWS access keys |
| contextual password phrases | password is ..., password ..., JSON password fields |
| credentialed URLs | https://user:password@host/... |
| secret-like shell variables | SECRET_VALUE=..., TOKEN=..., API_KEY=... |
| piped login passwords | `printf ... |
You can add repo-specific mask patterns via the API:
curl -X POST "$CF_URL/api/repos/$OWNER/$REPO/secret-masks" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pattern": "my_secret_prefix_[A-Za-z0-9]+", "label": "Internal API Key"}'Masking is applied both when storing and when retrieving transcript content. If a specific value has already leaked into a transcript in a non-contextual form, add a repo-specific mask and run the remask endpoint:
curl -X POST "$SF_URL/api/repos/$OWNER/$REPO/transcripts/remask" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json"Add this as .git/hooks/post-commit to auto-upload transcripts:
#!/bin/bash
# Auto-upload Claude Code transcript on commit
COMMIT_MSG=$(git log -1 --format=%B)
SESSION_ID=$(echo "$COMMIT_MSG" | grep "^AI-Session:" | awk '{print $2}')
if [ -z "$SESSION_ID" ]; then
exit 0 # No AI-Session trailer, skip
fi
SHA=$(git rev-parse HEAD)
CF_TOKEN=$(git config --get cloudforge.token)
CF_URL=$(git config --get cloudforge.url || echo "https://forge.smol.ai")
REMOTE_URL=$(git remote get-url origin)
OWNER=$(echo "$REMOTE_URL" | sed -E 's|.*/([^/]+)/([^/.]+)(\.git)?$|\1|')
REPO=$(echo "$REMOTE_URL" | sed -E 's|.*/([^/]+)/([^/.]+)(\.git)?$|\2|')
# Find session file
SESSION_FILE=$(find ~/.claude/projects/ -name "$SESSION_ID.jsonl" 2>/dev/null | head -1)
if [ -z "$SESSION_FILE" ]; then
echo "CloudForge: Session file not found for $SESSION_ID"
exit 0
fi
echo "CloudForge: Uploading transcript for session $SESSION_ID..."
python3 -c "
import json
messages = []
for line in open('$SESSION_FILE'):
entry = json.loads(line)
if entry.get('type') == 'human':
messages.append({'role': 'user', 'content': entry.get('message',{}).get('content',''), 'timestamp': entry.get('timestamp','')})
elif entry.get('type') == 'assistant':
c = entry.get('message',{}).get('content','')
if isinstance(c, list): c = ' '.join(x.get('text','') for x in c if x.get('type')=='text')
messages.append({'role': 'assistant', 'content': c, 'timestamp': entry.get('timestamp','')})
print(json.dumps({'session_id':'$SESSION_ID','agent_type':'claude-code','commit_sha':'$SHA','messages':messages}))
" | curl -s -X POST "$CF_URL/api/repos/$OWNER/$REPO/transcripts" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
-d @- > /dev/null
echo "CloudForge: Transcript uploaded."Configure your token once:
git config cloudforge.token "your-jwt-token"
git config cloudforge.url "https://forge.smol.ai"