Skip to main content

ADR-058: Machine-Specific Session Logs with Multi-Device Sync

Status

Accepted - January 6, 2026

Context

Problem Statement

Session logs (SESSION-LOG-YYYY-MM-DD.md) capture daily Claude Code session activity. Current issues:

  1. Git Pollution: Session logs in git repos sync back to GitHub, mixing personal machine activity with shared code
  2. Multi-Machine Conflict: Contributors using multiple machines get merge conflicts on session logs
  3. No Machine Identity: Cannot distinguish which machine generated which session log
  4. Lost Context: Session logs are machine-specific context that should persist per-machine
  5. Backup Gap: No cloud backup of machine-specific session history

Current State

coditect-rollout-master/
└── docs/
└── session-logs/ # ❌ IN GIT - syncs to GitHub
├── SESSION-LOG-2026-01-04.md
├── SESSION-LOG-2026-01-05.md
└── SESSION-LOG-2026-01-06.md # Which machine?

Requirements

  1. Session logs must be machine-specific with unique hardware identification
  2. Session logs must be stored outside git-tracked directories
  3. Each machine maintains its own session log history
  4. Support cloud backup for disaster recovery
  5. Support cross-machine querying (view logs from other machines)
  6. No merge conflicts when syncing coditect-core

Decision

Implement a machine-identified session log system with:

  1. Hardware-based Machine UUID generated from hardware characteristics
  2. Local storage outside git in ~/.coditect-data/session-logs/
  3. Cloud sync to GCS bucket with machine-namespaced paths
  4. Conflict-free replication using machine UUID as partition key

Architecture

┌─────────────────────────────────────────────────────────────────────────────────┐
│ MACHINE-SPECIFIC SESSION LOG ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ MACHINE A (MacBook Pro - hal-mbp-2024) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ ~/.coditect-data/ │ │
│ │ ├── machine-id.json # Hardware UUID + metadata │ │
│ │ │ { │ │
│ │ │ "machine_uuid": "a1b2c3d4-...", │ │
│ │ │ "hardware_hash": "sha256:abc123...", │ │
│ │ │ "hostname": "hal-mbp-2024", │ │
│ │ │ "created": "2026-01-06T10:30:00Z" │ │
│ │ │ } │ │
│ │ │ │ │
│ │ └── session-logs/ │ │
│ │ ├── SESSION-LOG-2026-01-04.md │ │
│ │ ├── SESSION-LOG-2026-01-05.md │ │
│ │ └── SESSION-LOG-2026-01-06.md │ │
│ │ │ │
│ │ session-log-sync.py ──▶ GCS Upload │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ MACHINE B (iMac - hal-imac-studio) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ ~/.coditect-data/ │ │
│ │ ├── machine-id.json # Different UUID │ │
│ │ │ { │ │
│ │ │ "machine_uuid": "e5f6g7h8-...", │ │
│ │ │ "hardware_hash": "sha256:def456...", │ │
│ │ │ "hostname": "hal-imac-studio", │ │
│ │ │ "created": "2026-01-01T08:00:00Z" │ │
│ │ │ } │ │
│ │ │ │ │
│ │ └── session-logs/ │ │
│ │ ├── SESSION-LOG-2026-01-03.md │ │
│ │ └── SESSION-LOG-2026-01-06.md # Same date, different machine │ │
│ │ │ │
│ │ session-log-sync.py ──▶ GCS Upload │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────────────────────┤
│ CLOUD STORAGE (GCS) │
│ gs://coditect-session-logs/ │
│ ├── tenant-abc123/ # Tenant isolation │
│ │ ├── machines/ │
│ │ │ ├── a1b2c3d4-.../ # Machine A UUID │
│ │ │ │ ├── machine-id.json │
│ │ │ │ ├── SESSION-LOG-2026-01-04.md │
│ │ │ │ ├── SESSION-LOG-2026-01-05.md │
│ │ │ │ └── SESSION-LOG-2026-01-06.md │
│ │ │ │ │
│ │ │ └── e5f6g7h8-.../ # Machine B UUID │
│ │ │ ├── machine-id.json │
│ │ │ ├── SESSION-LOG-2026-01-03.md │
│ │ │ └── SESSION-LOG-2026-01-06.md │
│ │ │ │
│ │ └── index/ # Cross-machine index │
│ │ └── session-index.json # Aggregated metadata │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘

Machine UUID Generation

The machine UUID is generated from hardware characteristics to ensure:

  • Persistence: Same UUID across reboots and reinstalls
  • Uniqueness: No collision between machines
  • Privacy: Hash-based, doesn't expose raw serial numbers
import hashlib
import uuid
import subprocess
import platform

def generate_machine_uuid() -> dict:
"""Generate a persistent, unique machine identifier."""

# Collect hardware identifiers (macOS)
hardware_data = []

# 1. Hardware UUID (from IOKit)
try:
result = subprocess.run(
['ioreg', '-rd1', '-c', 'IOPlatformExpertDevice'],
capture_output=True, text=True
)
for line in result.stdout.split('\n'):
if 'IOPlatformUUID' in line:
hw_uuid = line.split('"')[-2]
hardware_data.append(f"hw_uuid:{hw_uuid}")
break
except Exception:
pass

# 2. Serial number (hashed for privacy)
try:
result = subprocess.run(
['ioreg', '-l', '-c', 'IOPlatformExpertDevice'],
capture_output=True, text=True
)
for line in result.stdout.split('\n'):
if 'IOPlatformSerialNumber' in line:
serial = line.split('"')[-2]
# Hash the serial for privacy
serial_hash = hashlib.sha256(serial.encode()).hexdigest()[:16]
hardware_data.append(f"serial_hash:{serial_hash}")
break
except Exception:
pass

# 3. MAC address of primary interface (hashed)
try:
result = subprocess.run(['ifconfig', 'en0'], capture_output=True, text=True)
for line in result.stdout.split('\n'):
if 'ether' in line:
mac = line.split()[1]
mac_hash = hashlib.sha256(mac.encode()).hexdigest()[:16]
hardware_data.append(f"mac_hash:{mac_hash}")
break
except Exception:
pass

# 4. CPU identifier
hardware_data.append(f"cpu:{platform.processor()}")

# 5. Machine model
try:
result = subprocess.run(
['sysctl', '-n', 'hw.model'],
capture_output=True, text=True
)
hardware_data.append(f"model:{result.stdout.strip()}")
except Exception:
pass

# Create composite hash
composite = '|'.join(sorted(hardware_data))
hardware_hash = hashlib.sha256(composite.encode()).hexdigest()

# Generate UUID from hash (deterministic)
machine_uuid = str(uuid.UUID(hardware_hash[:32]))

return {
'machine_uuid': machine_uuid,
'hardware_hash': f"sha256:{hardware_hash}",
'hostname': platform.node(),
'platform': platform.system(),
'platform_version': platform.mac_ver()[0] if platform.system() == 'Darwin' else platform.release(),
'model': hardware_data[-1].split(':')[1] if hardware_data else 'unknown',
'created': datetime.now(timezone.utc).isoformat()
}

Synchronization Strategy

Conflict-Free by Design

Key Insight: By using machine UUID as the partition key, we eliminate conflicts entirely.

┌─────────────────────────────────────────────────────────────────┐
│ CONFLICT-FREE REPLICATION (CRF) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Traditional Approach (CONFLICTS): │
│ ───────────────────────────────── │
│ Machine A writes: SESSION-LOG-2026-01-06.md │
│ Machine B writes: SESSION-LOG-2026-01-06.md ← CONFLICT! │
│ │
│ Our Approach (NO CONFLICTS): │
│ ──────────────────────────── │
│ Machine A writes: machines/uuid-A/SESSION-LOG-2026-01-06.md │
│ Machine B writes: machines/uuid-B/SESSION-LOG-2026-01-06.md │
│ │
│ Same date, different paths → No conflict possible! │
│ │
└─────────────────────────────────────────────────────────────────┘

Sync Modes

ModeDescriptionWhen Used
PushLocal → CloudAfter session end, daily backup
Pull IndexCloud index → LocalStart of session, show available
Pull LogsSpecific logs → Local cacheOn-demand viewing
Full SyncBidirectional reconciliationWeekly or manual

Directory Structure

~/.coditect-data/
├── machine-id.json # Machine identification (generated once)
├── session-logs/ # LOCAL session logs (this machine only)
│ ├── SESSION-LOG-2026-01-04.md
│ ├── SESSION-LOG-2026-01-05.md
│ └── SESSION-LOG-2026-01-06.md

├── session-logs-cache/ # CACHED logs from other machines (read-only)
│ ├── e5f6g7h8-.../ # Other machine's logs
│ │ └── SESSION-LOG-2026-01-03.md
│ └── index.json # Cross-machine index

└── context-storage/ # ADR-118 Four-Tier databases
├── org.db # Tier 2: IRREPLACEABLE
├── sessions.db # Tier 3: REGENERABLE
└── platform.db # Tier 1: REGENERABLE

Consequences

Positive

  1. No Git Conflicts: Session logs never sync to GitHub, eliminating merge conflicts
  2. Machine Traceability: Every log linked to specific hardware via UUID
  3. Cloud Backup: All session history preserved in GCS
  4. Cross-Machine Visibility: Can view logs from any machine (on-demand)
  5. Privacy: Hardware identifiers hashed, not exposed
  6. Offline First: Works without internet, syncs when available

Negative

  1. Complexity: More moving parts than simple git-tracked files
  2. Storage Cost: GCS storage costs (~$0.02/GB/month)
  3. Setup Required: Initial setup generates machine ID

Risks

RiskMitigation
Machine ID collisionUUID from SHA-256 hash has negligible collision probability
GCS access failureLocal logs always authoritative; sync is best-effort
Hardware changeRegenerate machine ID; link old UUID in metadata
Lost machine IDCan regenerate (deterministic); cloud has backup

Implementation

Required Changes

  1. ADR-057 Update: Add session-log setup to initial setup script
  2. New Script: session-log-sync.py for cloud sync
  3. Hook: Post-session hook to trigger sync
  4. Migration: One-time migration of existing logs

Commands

# Initialize machine ID (first time)
python3 scripts/session-log-init.py

# Sync to cloud
python3 scripts/session-log-sync.py --push

# View available logs (all machines)
python3 scripts/session-log-sync.py --index

# Fetch specific log from another machine
python3 scripts/session-log-sync.py --pull --machine e5f6g7h8 --date 2026-01-03

# Migrate from git-tracked location
python3 scripts/session-log-migrate.py

Author: AI Assistant Reviewed By: Hal Casteel Approved: January 6, 2026