mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 21:03:19 +00:00
Add session version field and migrate-on-load strategy
This commit is contained in:
parent
013b6814e4
commit
3239a4a11a
1 changed files with 63 additions and 17 deletions
|
|
@ -16,16 +16,20 @@ Context is built by scanning linearly, applying compaction ranges.
|
|||
|
||||
## Proposed Format (Tree)
|
||||
|
||||
Each entry has a `uuid` and `parentUuid` field (null for root):
|
||||
Each entry has a `uuid` and `parentUuid` field (null for root). Session header includes `version` for future migrations:
|
||||
|
||||
```jsonl
|
||||
{"type":"session","uuid":"a1b2c3","parentUuid":null,"id":"...","cwd":"..."}
|
||||
{"type":"session","version":2,"uuid":"a1b2c3","parentUuid":null,"id":"...","cwd":"..."}
|
||||
{"type":"message","uuid":"d4e5f6","parentUuid":"a1b2c3","message":{"role":"user",...}}
|
||||
{"type":"message","uuid":"g7h8i9","parentUuid":"d4e5f6","message":{"role":"assistant",...}}
|
||||
{"type":"message","uuid":"j0k1l2","parentUuid":"g7h8i9","message":{"role":"user",...}}
|
||||
{"type":"message","uuid":"m3n4o5","parentUuid":"j0k1l2","message":{"role":"assistant",...}}
|
||||
```
|
||||
|
||||
Version history:
|
||||
- **v1** (implicit): Linear format, no uuid/parentUuid
|
||||
- **v2**: Tree format with uuid/parentUuid
|
||||
|
||||
The **last entry** is always the current leaf. Context = walk from leaf to root via `parentUuid`.
|
||||
|
||||
Using UUIDs (like Claude Code does) instead of indices because:
|
||||
|
|
@ -312,33 +316,75 @@ With tree structure, could support:
|
|||
|
||||
## Migration
|
||||
|
||||
### File Format
|
||||
### Strategy: Migrate on Load + Rewrite
|
||||
|
||||
Add `uuid` and `parentUuid` fields to all entries. Existing sessions get generated UUIDs with linear parentage:
|
||||
When loading a session, check if migration is needed. If so, migrate in memory and rewrite the file. This is transparent to users and only happens once per session file.
|
||||
|
||||
```typescript
|
||||
function migrateSession(content: string): string {
|
||||
const lines = content.trim().split('\n');
|
||||
const uuids: string[] = [];
|
||||
const CURRENT_VERSION = 2;
|
||||
|
||||
function loadSession(path: string): SessionEntry[] {
|
||||
const content = readFileSync(path, 'utf8');
|
||||
const entries = parseEntries(content);
|
||||
|
||||
return lines.map((line, i) => {
|
||||
const entry = JSON.parse(line);
|
||||
const uuid = generateUuid();
|
||||
uuids.push(uuid);
|
||||
entry.uuid = uuid;
|
||||
entry.parentUuid = i === 0 ? null : uuids[i - 1];
|
||||
return JSON.stringify(entry);
|
||||
}).join('\n');
|
||||
const header = entries.find(e => e.type === 'session');
|
||||
const version = header?.version ?? 1;
|
||||
|
||||
if (version < CURRENT_VERSION) {
|
||||
migrateEntries(entries, version);
|
||||
writeFileSync(path, entries.map(e => JSON.stringify(e)).join('\n') + '\n');
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
function migrateEntries(entries: SessionEntry[], fromVersion: number): void {
|
||||
if (fromVersion < 2) {
|
||||
// v1 → v2: Add uuid/parentUuid, convert firstKeptEntryIndex
|
||||
const uuids: string[] = [];
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
const uuid = generateUuid();
|
||||
uuids.push(uuid);
|
||||
|
||||
entry.uuid = uuid;
|
||||
entry.parentUuid = i === 0 ? null : uuids[i - 1];
|
||||
|
||||
// Update session header version
|
||||
if (entry.type === 'session') {
|
||||
entry.version = CURRENT_VERSION;
|
||||
}
|
||||
|
||||
// Convert compaction index to UUID
|
||||
if (entry.type === 'compaction' && 'firstKeptEntryIndex' in entry) {
|
||||
entry.firstKeptEntryUuid = uuids[entry.firstKeptEntryIndex];
|
||||
delete entry.firstKeptEntryIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Future migrations: if (fromVersion < 3) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Migrated sessions work exactly as before (linear path).
|
||||
### What Gets Migrated
|
||||
|
||||
| v1 Field | v2 Field |
|
||||
|----------|----------|
|
||||
| (none) | `uuid` (generated) |
|
||||
| (none) | `parentUuid` (previous entry's uuid, null for root) |
|
||||
| (none on session) | `version: 2` |
|
||||
| `firstKeptEntryIndex` | `firstKeptEntryUuid` |
|
||||
|
||||
Migrated sessions work exactly as before (linear path). Tree features become available.
|
||||
|
||||
### API Compatibility
|
||||
|
||||
- `buildSessionContext()` returns same structure
|
||||
- `branch()` still works, just uses UUIDs
|
||||
- Existing hooks continue to work
|
||||
- Old sessions auto-migrate on first load
|
||||
|
||||
## Complexity Analysis
|
||||
|
||||
|
|
@ -361,7 +407,7 @@ Abandoned branches remain in file but don't affect context building performance.
|
|||
## Example: Full Session with Branching
|
||||
|
||||
```jsonl
|
||||
{"type":"session","uuid":"ses1","parentUuid":null,"id":"abc","cwd":"/project"}
|
||||
{"type":"session","version":2,"uuid":"ses1","parentUuid":null,"id":"abc","cwd":"/project"}
|
||||
{"type":"message","uuid":"m1","parentUuid":"ses1","message":{"role":"user","content":"Build a CLI"}}
|
||||
{"type":"message","uuid":"m2","parentUuid":"m1","message":{"role":"assistant","content":"I'll create..."}}
|
||||
{"type":"message","uuid":"m3","parentUuid":"m2","message":{"role":"user","content":"Add --verbose flag"}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue