summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-22 01:22:21 +0900
committerAdam Malczewski <[email protected]>2026-06-22 01:22:21 +0900
commitae8f61cefd383417bc0f80447d7ab1bfdfe0726d (patch)
treeddb6ccb08a4338aa2c6ccb397696074d8c2c22c5 /packages
parent54613fc2bd4869a95ffda34230006da6d9dfc8c3 (diff)
downloaddispatch-ae8f61cefd383417bc0f80447d7ab1bfdfe0726d.tar.gz
dispatch-ae8f61cefd383417bc0f80447d7ab1bfdfe0726d.zip
fix: compaction keeps original ID, forks old history to archive, chains via compactedFrom
Reworked compaction to match the confirmed design: - The compacted conversation KEEPS its original ID (messaging between agents is unaffected — the ID never changes) - The old full history is forked to a new archive conversation (new UUID) - The archive inherits the source's compactedFrom, creating a chain: A → Y → X (walk compactedFrom backward) - A's history is replaced with [summary + recent N] - A.compactedFrom = archive ID forkHistory: inherit compactedFrom from source (not set to sourceId), so archives chain backward to previous archives. FE: no tab switching needed — the ID doesn't change. Just reload history.
Diffstat (limited to 'packages')
-rw-r--r--packages/conversation-store/src/store.ts14
-rw-r--r--packages/session-orchestrator/src/orchestrator.ts13
-rw-r--r--packages/transport-contract/src/index.ts4
-rw-r--r--packages/transport-http/src/app.ts2
-rw-r--r--packages/transport-ws/src/extension.ts4
-rw-r--r--packages/wire/src/index.ts4
6 files changed, 24 insertions, 17 deletions
diff --git a/packages/conversation-store/src/store.ts b/packages/conversation-store/src/store.ts
index d713de3..700be1e 100644
--- a/packages/conversation-store/src/store.ts
+++ b/packages/conversation-store/src/store.ts
@@ -113,7 +113,7 @@ export interface ConversationStore {
* Set the `compactedFrom` field on a conversation's metadata, pointing to
* the archive conversation that holds the pre-compaction history.
*/
- readonly setCompactedFrom: (conversationId: string, archiveId: string) => Promise<void>;
+ readonly setCompactedFrom: (conversationId: string, newConversationId: string) => Promise<void>;
}
export const conversationStoreHandle = defineService<ConversationStore>("conversation-store/store");
@@ -592,7 +592,9 @@ export function createConversationStore(
}
await storage.set(seqKey(targetId), String(Math.max(seq - 1, 0)));
- // Copy metadata with archive title + closed status + compactedFrom.
+ // Copy metadata with archive title + closed status.
+ // Inherit compactedFrom from the source so archives chain:
+ // A → Y → X (each archive points to the previous one).
const metaRaw = await storage.get(metaKey(sourceId));
if (metaRaw !== null) {
const existing = parseMetaRow(metaRaw);
@@ -602,7 +604,9 @@ export function createConversationStore(
lastActivityAt: existing.lastActivityAt,
title: `Archive: ${existing.title}`,
status: "closed",
- compactedFrom: sourceId,
+ ...(existing.compactedFrom !== undefined
+ ? { compactedFrom: existing.compactedFrom }
+ : {}),
};
await storage.set(metaKey(targetId), JSON.stringify(row));
}
@@ -630,7 +634,7 @@ export function createConversationStore(
}
},
- async setCompactedFrom(conversationId, archiveId) {
+ async setCompactedFrom(conversationId, newConversationId) {
const raw = await storage.get(metaKey(conversationId));
const existing = raw !== null ? parseMetaRow(raw) : null;
const ts = now();
@@ -642,7 +646,7 @@ export function createConversationStore(
};
await storage.set(
metaKey(conversationId),
- JSON.stringify({ ...row, compactedFrom: archiveId }),
+ JSON.stringify({ ...row, compactedFrom: newConversationId }),
);
},
};
diff --git a/packages/session-orchestrator/src/orchestrator.ts b/packages/session-orchestrator/src/orchestrator.ts
index 21c068c..b46ecc1 100644
--- a/packages/session-orchestrator/src/orchestrator.ts
+++ b/packages/session-orchestrator/src/orchestrator.ts
@@ -132,7 +132,7 @@ export const conversationStatusChanged: EventHookDescriptor<ConversationStatusCh
/** Payload for the conversationCompacted bus event. */
export interface ConversationCompactedPayload {
readonly conversationId: string;
- readonly archiveId: string;
+ readonly newConversationId: string;
readonly messagesSummarized: number;
readonly messagesKept: number;
}
@@ -802,8 +802,11 @@ export function createCompactionService(
return { error: "model produced empty summary" };
}
- // Non-destructive: fork the full pre-compaction history to an
- // archive conversation before replacing it.
+ // Non-destructive: fork the full pre-compaction history to a new
+ // archive conversation. The original conversation keeps its ID
+ // (so messaging between agents still works) and gets the compacted
+ // content. The archive inherits the original's compactedFrom,
+ // creating a chain: A → Y → X → ...
const archiveId = crypto.randomUUID();
await deps.conversationStore.forkHistory(conversationId, archiveId);
@@ -823,14 +826,14 @@ export function createCompactionService(
const result: CompactionResult = {
summary,
- archiveId,
+ newConversationId: archiveId,
messagesSummarized: toSummarize.length,
messagesKept: toKeep.length,
};
deps.emit(conversationCompacted, {
conversationId,
- archiveId,
+ newConversationId: archiveId,
messagesSummarized: toSummarize.length,
messagesKept: toKeep.length,
});
diff --git a/packages/transport-contract/src/index.ts b/packages/transport-contract/src/index.ts
index 2316c96..5745672 100644
--- a/packages/transport-contract/src/index.ts
+++ b/packages/transport-contract/src/index.ts
@@ -521,7 +521,7 @@ export interface ConversationStatusChangedMessage {
export interface ConversationCompactedMessage {
readonly type: "conversation.compacted";
readonly conversationId: string;
- readonly archiveId: string;
+ readonly newConversationId: string;
readonly messagesSummarized: number;
readonly messagesKept: number;
}
@@ -577,7 +577,7 @@ export interface TitleResponse {
*/
export interface CompactResponse {
readonly conversationId: string;
- readonly archiveId: string;
+ readonly newConversationId: string;
readonly messagesSummarized: number;
readonly messagesKept: number;
}
diff --git a/packages/transport-http/src/app.ts b/packages/transport-http/src/app.ts
index 233e30e..64e46fd 100644
--- a/packages/transport-http/src/app.ts
+++ b/packages/transport-http/src/app.ts
@@ -725,7 +725,7 @@ export function createApp(opts: CreateServerOptions): Hono {
const response: CompactResponse = {
conversationId,
- archiveId: result.archiveId,
+ newConversationId: result.newConversationId,
messagesSummarized: result.messagesSummarized,
messagesKept: result.messagesKept,
};
diff --git a/packages/transport-ws/src/extension.ts b/packages/transport-ws/src/extension.ts
index abc69e7..b42f434 100644
--- a/packages/transport-ws/src/extension.ts
+++ b/packages/transport-ws/src/extension.ts
@@ -153,11 +153,11 @@ export function createTransportWsExtension(): Extension {
disposers.push(
host.on(
conversationCompacted,
- ({ conversationId, archiveId, messagesSummarized, messagesKept }) => {
+ ({ conversationId, newConversationId, messagesSummarized, messagesKept }) => {
broadcast({
type: "conversation.compacted",
conversationId,
- archiveId,
+ newConversationId,
messagesSummarized,
messagesKept,
});
diff --git a/packages/wire/src/index.ts b/packages/wire/src/index.ts
index 8c85f89..4ab8825 100644
--- a/packages/wire/src/index.ts
+++ b/packages/wire/src/index.ts
@@ -535,12 +535,12 @@ export interface ConversationMeta {
* Result of a compaction operation. `summary` is the text the model produced;
* `messagesKept` is how many recent messages were retained after the summary;
* `messagesSummarized` is how many old messages were replaced by the summary.
- * `archiveId` is the ID of the new conversation that holds the full
+ * `newConversationId` is the ID of the new conversation that holds the full
* pre-compaction history (non-destructive — the original history is preserved).
*/
export interface CompactionResult {
readonly summary: string;
- readonly archiveId: string;
+ readonly newConversationId: string;
readonly messagesSummarized: number;
readonly messagesKept: number;
}