@poncho-ai/harness 0.55.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2789,6 +2789,8 @@ var InMemoryEngine = class {
2789
2789
  agentId;
2790
2790
  // Conversation data
2791
2791
  convs = /* @__PURE__ */ new Map();
2792
+ // Append-only conversation entries (Phase 3 substrate)
2793
+ entries = /* @__PURE__ */ new Map();
2792
2794
  // Memory data
2793
2795
  mem = /* @__PURE__ */ new Map();
2794
2796
  // Todos data
@@ -2918,6 +2920,30 @@ var InMemoryEngine = class {
2918
2920
  }
2919
2921
  results.sort((a, b) => b.updatedAt - a.updatedAt);
2920
2922
  return results;
2923
+ },
2924
+ appendEntries: async (conversationId, _agentId, _tenantId, entries) => {
2925
+ const list = this.entries.get(conversationId) ?? [];
2926
+ let nextSeq = list.reduce((max, e) => e.seq > max ? e.seq : max, 0) + 1;
2927
+ const now2 = Date.now();
2928
+ const stored = entries.map(
2929
+ (e) => ({ ...e, seq: nextSeq++, createdAt: now2 })
2930
+ );
2931
+ this.entries.set(conversationId, [...list, ...stored]);
2932
+ return stored;
2933
+ },
2934
+ readEntries: async (conversationId, opts) => {
2935
+ let list = (this.entries.get(conversationId) ?? []).slice().sort((a, b) => a.seq - b.seq);
2936
+ if (opts?.types && opts.types.length > 0) {
2937
+ const allowed = new Set(opts.types);
2938
+ list = list.filter((e) => allowed.has(e.type));
2939
+ }
2940
+ if (typeof opts?.afterSeq === "number") {
2941
+ list = list.filter((e) => e.seq > opts.afterSeq);
2942
+ }
2943
+ if (typeof opts?.limit === "number") {
2944
+ list = list.slice(0, opts.limit);
2945
+ }
2946
+ return list;
2921
2947
  }
2922
2948
  };
2923
2949
  // -----------------------------------------------------------------------
@@ -3457,6 +3483,36 @@ var migrations = [
3457
3483
  `ALTER TABLE reminders ALTER COLUMN scheduled_at TYPE BIGINT USING scheduled_at::bigint`
3458
3484
  ];
3459
3485
  }
3486
+ },
3487
+ {
3488
+ version: 8,
3489
+ name: "conversation_entries",
3490
+ // Append-only conversation log (Phase 3 substrate). Additive: no
3491
+ // existing table or behavior changes. `seq` is a per-conversation
3492
+ // monotonic order assigned by the application (NOT an autoincrement
3493
+ // serial), so the same seq space restarts at 1 for every conversation.
3494
+ // The UNIQUE (conversation_id, seq) constraint backstops the
3495
+ // app-assigned ordering against concurrent writers.
3496
+ up: (d) => {
3497
+ const jsonType = d === "sqlite" ? "TEXT" : "JSONB";
3498
+ const tsDefault = d === "sqlite" ? "datetime('now')" : "NOW()";
3499
+ const autoTs = `DEFAULT (${tsDefault})`;
3500
+ return [
3501
+ `CREATE TABLE IF NOT EXISTS conversation_entries (
3502
+ seq INTEGER NOT NULL,
3503
+ id TEXT NOT NULL UNIQUE,
3504
+ agent_id TEXT NOT NULL,
3505
+ tenant_id TEXT NOT NULL DEFAULT '__default__',
3506
+ conversation_id TEXT NOT NULL,
3507
+ type TEXT NOT NULL,
3508
+ payload ${jsonType} NOT NULL,
3509
+ created_at TIMESTAMP ${autoTs},
3510
+ UNIQUE (conversation_id, seq)
3511
+ )`,
3512
+ `CREATE INDEX IF NOT EXISTS idx_conversation_entries_seq
3513
+ ON conversation_entries (conversation_id, seq)`
3514
+ ];
3515
+ }
3460
3516
  }
3461
3517
  ];
3462
3518
 
@@ -3880,6 +3936,87 @@ var SqlStorageEngine = class {
3880
3936
  parentConversationId
3881
3937
  ]);
3882
3938
  return rows.map((r) => this.rowToSummary(r));
3939
+ },
3940
+ appendEntries: async (conversationId, agentId, tenantId, entries) => {
3941
+ if (entries.length === 0) return [];
3942
+ const tid = normalizeTenant2(tenantId);
3943
+ const maxAttempts = 3;
3944
+ let lastErr;
3945
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
3946
+ const stored = [];
3947
+ try {
3948
+ await this.executor.transaction(async () => {
3949
+ stored.length = 0;
3950
+ const row = await this.executor.get(
3951
+ rewrite(
3952
+ "SELECT MAX(seq) AS max_seq FROM conversation_entries WHERE conversation_id = $1",
3953
+ this.dialect
3954
+ ),
3955
+ [conversationId]
3956
+ );
3957
+ let nextSeq = Number(row?.max_seq ?? 0) + 1;
3958
+ const now2 = Date.now();
3959
+ for (const entry of entries) {
3960
+ const seq = nextSeq++;
3961
+ const createdAt = now2;
3962
+ const payload = JSON.stringify(entry);
3963
+ await this.executor.run(
3964
+ rewrite(
3965
+ `INSERT INTO conversation_entries
3966
+ (seq, id, agent_id, tenant_id, conversation_id, type, payload, created_at)
3967
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
3968
+ this.dialect
3969
+ ),
3970
+ [
3971
+ seq,
3972
+ entry.id,
3973
+ agentId,
3974
+ tid,
3975
+ conversationId,
3976
+ entry.type,
3977
+ payload,
3978
+ new Date(createdAt).toISOString()
3979
+ ]
3980
+ );
3981
+ stored.push({ ...entry, seq, createdAt });
3982
+ }
3983
+ });
3984
+ return stored;
3985
+ } catch (err) {
3986
+ lastErr = err;
3987
+ }
3988
+ }
3989
+ throw lastErr;
3990
+ },
3991
+ readEntries: async (conversationId, opts) => {
3992
+ const params = [conversationId];
3993
+ let sql = "SELECT seq, id, payload, created_at FROM conversation_entries WHERE conversation_id = $1";
3994
+ if (opts?.types && opts.types.length > 0) {
3995
+ const placeholders = opts.types.map(
3996
+ (_t, i) => `$${params.length + 1 + i}`
3997
+ );
3998
+ sql += ` AND type IN (${placeholders.join(", ")})`;
3999
+ params.push(...opts.types);
4000
+ }
4001
+ if (typeof opts?.afterSeq === "number") {
4002
+ sql += ` AND seq > $${params.length + 1}`;
4003
+ params.push(opts.afterSeq);
4004
+ }
4005
+ sql += " ORDER BY seq ASC";
4006
+ if (typeof opts?.limit === "number") {
4007
+ sql += ` LIMIT $${params.length + 1}`;
4008
+ params.push(opts.limit);
4009
+ }
4010
+ const rows = await this.executor.all(rewrite(sql, this.dialect), params);
4011
+ return rows.map((r) => {
4012
+ const parsed = typeof r.payload === "string" ? JSON.parse(r.payload) : r.payload;
4013
+ return {
4014
+ ...parsed,
4015
+ id: r.id,
4016
+ seq: Number(r.seq),
4017
+ createdAt: new Date(r.created_at).getTime()
4018
+ };
4019
+ });
3883
4020
  }
3884
4021
  };
3885
4022
  // -----------------------------------------------------------------------
@@ -4712,7 +4849,9 @@ function createConversationStoreFromEngine(engine) {
4712
4849
  delete: (conversationId) => engine.conversations.delete(conversationId),
4713
4850
  appendSubagentResult: (conversationId, result) => engine.conversations.appendSubagentResult(conversationId, result),
4714
4851
  clearCallbackLock: (conversationId) => engine.conversations.clearCallbackLock(conversationId),
4715
- listThreads: (parentConversationId) => engine.conversations.listThreads(parentConversationId)
4852
+ listThreads: (parentConversationId) => engine.conversations.listThreads(parentConversationId),
4853
+ appendEntries: (conversationId, agentId, tenantId, entries) => engine.conversations.appendEntries(conversationId, agentId, tenantId, entries),
4854
+ readEntries: (conversationId, opts) => engine.conversations.readEntries(conversationId, opts)
4716
4855
  };
4717
4856
  }
4718
4857
  function createMemoryStoreFromEngine(engine, tenantId) {
@@ -11973,6 +12112,7 @@ var InMemoryStateStore = class {
11973
12112
  };
11974
12113
  var InMemoryConversationStore = class {
11975
12114
  conversations = /* @__PURE__ */ new Map();
12115
+ entries = /* @__PURE__ */ new Map();
11976
12116
  ttlMs;
11977
12117
  constructor(ttlSeconds) {
11978
12118
  this.ttlMs = typeof ttlSeconds === "number" ? ttlSeconds * 1e3 : void 0;
@@ -12102,6 +12242,30 @@ var InMemoryConversationStore = class {
12102
12242
  channelMeta: c.channelMeta
12103
12243
  }));
12104
12244
  }
12245
+ async appendEntries(conversationId, _agentId, _tenantId, entries) {
12246
+ const list = this.entries.get(conversationId) ?? [];
12247
+ let nextSeq = list.reduce((max, e) => e.seq > max ? e.seq : max, 0) + 1;
12248
+ const now2 = Date.now();
12249
+ const stored = entries.map(
12250
+ (e) => ({ ...e, seq: nextSeq++, createdAt: now2 })
12251
+ );
12252
+ this.entries.set(conversationId, [...list, ...stored]);
12253
+ return stored;
12254
+ }
12255
+ async readEntries(conversationId, opts) {
12256
+ let list = (this.entries.get(conversationId) ?? []).slice().sort((a, b) => a.seq - b.seq);
12257
+ if (opts?.types && opts.types.length > 0) {
12258
+ const allowed = new Set(opts.types);
12259
+ list = list.filter((e) => allowed.has(e.type));
12260
+ }
12261
+ if (typeof opts?.afterSeq === "number") {
12262
+ list = list.filter((e) => e.seq > opts.afterSeq);
12263
+ }
12264
+ if (typeof opts?.limit === "number") {
12265
+ list = list.slice(0, opts.limit);
12266
+ }
12267
+ return list;
12268
+ }
12105
12269
  };
12106
12270
  var createStateStore = (config, _options) => {
12107
12271
  const ttl = config?.ttl;
@@ -12112,6 +12276,66 @@ var createConversationStore = (config, _options) => {
12112
12276
  return new InMemoryConversationStore(ttl);
12113
12277
  };
12114
12278
 
12279
+ // src/storage/entries.ts
12280
+ function buildLlmContext(entries) {
12281
+ let latestCompaction;
12282
+ for (const e of entries) {
12283
+ if (e.type === "compaction" && (!latestCompaction || e.seq > latestCompaction.seq)) {
12284
+ latestCompaction = e;
12285
+ }
12286
+ }
12287
+ const harnessMsgs = entries.filter(
12288
+ (e) => e.type === "harness_message"
12289
+ );
12290
+ if (latestCompaction) {
12291
+ const kept = harnessMsgs.filter((e) => e.seq >= latestCompaction.firstKeptSeq).map((e) => e.message);
12292
+ return [latestCompaction.summaryMessage, ...kept];
12293
+ }
12294
+ return harnessMsgs.map((e) => e.message);
12295
+ }
12296
+ function buildDisplaySnapshot(entries, tailN) {
12297
+ const amendmentsByTarget = /* @__PURE__ */ new Map();
12298
+ for (const e of entries) {
12299
+ if (e.type === "assistant_amendment") {
12300
+ const list = amendmentsByTarget.get(e.targetEntryId) ?? [];
12301
+ list.push(e);
12302
+ amendmentsByTarget.set(e.targetEntryId, list);
12303
+ }
12304
+ }
12305
+ const built = [];
12306
+ for (const e of entries) {
12307
+ if (e.type === "user_message") {
12308
+ if (e.hidden) continue;
12309
+ built.push({ seq: e.seq, message: e.message });
12310
+ } else if (e.type === "assistant_message") {
12311
+ let content = typeof e.message.content === "string" ? e.message.content : "";
12312
+ const amendments = amendmentsByTarget.get(e.id);
12313
+ if (amendments) {
12314
+ for (const a of amendments.sort((x, y) => x.seq - y.seq)) {
12315
+ if (a.appendText) content += a.appendText;
12316
+ }
12317
+ }
12318
+ built.push({ seq: e.seq, message: { ...e.message, content } });
12319
+ }
12320
+ }
12321
+ const total = built.length;
12322
+ const tail = tailN >= total ? built : built.slice(total - tailN);
12323
+ return {
12324
+ messages: tail.map((b) => b.message),
12325
+ totalMessages: total,
12326
+ headSeq: tail.length > 0 ? tail[0].seq : null
12327
+ };
12328
+ }
12329
+ function getPendingSubagentResults(entries) {
12330
+ const consumed = /* @__PURE__ */ new Set();
12331
+ for (const e of entries) {
12332
+ if (e.type === "callback_started") {
12333
+ for (const s of e.consumedSeqs) consumed.add(s);
12334
+ }
12335
+ }
12336
+ return entries.filter((e) => e.type === "subagent_result").filter((e) => !consumed.has(e.seq)).map((e) => e.result);
12337
+ }
12338
+
12115
12339
  // src/tenant-token.ts
12116
12340
  import { jwtVerify } from "jose";
12117
12341
  async function verifyTenantToken(signingKey, token) {
@@ -12444,9 +12668,143 @@ var CALLBACK_LOCK_STALE_MS = 5 * 60 * 1e3;
12444
12668
  var STALE_SUBAGENT_THRESHOLD_MS = 5 * 60 * 1e3;
12445
12669
 
12446
12670
  // src/orchestrator/orchestrator.ts
12671
+ import { createLogger as createLogger7, getTextContent as getTextContent4 } from "@poncho-ai/sdk";
12672
+
12673
+ // src/orchestrator/entries-dual-write.ts
12674
+ import { randomUUID as randomUUID6 } from "crypto";
12447
12675
  import { getTextContent as getTextContent3 } from "@poncho-ai/sdk";
12676
+ var entriesParityEnabled = () => process.env.PONCHO_VERIFY_ENTRIES === "1";
12677
+ var appendEntriesSafe = async (store, conversation, entries, log2) => {
12678
+ if (entries.length === 0) return [];
12679
+ try {
12680
+ const withIds = entries.map(
12681
+ (e) => ({ id: randomUUID6(), ...e })
12682
+ );
12683
+ return await store.appendEntries(
12684
+ conversation.conversationId,
12685
+ conversation.ownerId,
12686
+ conversation.tenantId ?? null,
12687
+ withIds
12688
+ );
12689
+ } catch (err) {
12690
+ log2.error(
12691
+ `[entries-dual-write] append failed for ${conversation.conversationId}: ${err instanceof Error ? err.message : String(err)}`
12692
+ );
12693
+ return [];
12694
+ }
12695
+ };
12696
+ var userMessageEntry = (message, turnId, opts) => ({
12697
+ type: "user_message",
12698
+ message,
12699
+ turnId,
12700
+ ...opts?.hidden ? { hidden: true } : {}
12701
+ });
12702
+ var assistantMessageEntry = (message, turnId, runId) => ({
12703
+ type: "assistant_message",
12704
+ message,
12705
+ turnId,
12706
+ runId
12707
+ });
12708
+ var harnessMessageEntries = (messages, turnId) => messages.map((message) => ({ type: "harness_message", message, turnId }));
12709
+ var compactionEntry = (summaryMessage, firstKeptSeq, opts) => ({
12710
+ type: "compaction",
12711
+ summaryMessage,
12712
+ firstKeptSeq,
12713
+ ...opts?.tokensBefore !== void 0 ? { tokensBefore: opts.tokensBefore } : {},
12714
+ ...opts?.tokensAfter !== void 0 ? { tokensAfter: opts.tokensAfter } : {}
12715
+ });
12716
+ var subagentResultEntry = (result) => ({ type: "subagent_result", result });
12717
+ var callbackStartedEntry = (consumedSeqs) => ({
12718
+ type: "callback_started",
12719
+ consumedSeqs
12720
+ });
12721
+ var assistantAmendmentEntry = (targetEntryId, appendText) => ({
12722
+ type: "assistant_amendment",
12723
+ targetEntryId,
12724
+ ...appendText ? { appendText } : {}
12725
+ });
12726
+ var newHarnessMessagesThisTurn = (prev, next) => {
12727
+ const prevArr = prev ?? [];
12728
+ const nextArr = next ?? [];
12729
+ if (nextArr.length === 0) return { messages: [], approximate: false };
12730
+ if (prevArr.length === 0) return { messages: nextArr, approximate: false };
12731
+ if (nextArr.length >= prevArr.length) {
12732
+ return { messages: nextArr.slice(prevArr.length), approximate: false };
12733
+ }
12734
+ return { messages: nextArr, approximate: true };
12735
+ };
12736
+ var projectText = (m) => {
12737
+ const role = m.role;
12738
+ const text = getTextContent3(m).replace(/\s+/g, " ").trim();
12739
+ return `${role}:${text}`;
12740
+ };
12741
+ var projectAll = (msgs) => msgs.map(projectText);
12742
+ var countMismatch = (label, a, b) => a === b ? null : `${label} length ${a} (entries) vs ${b} (blob)`;
12743
+ var verifyEntriesParity = async (store, conversationId, blob, log2) => {
12744
+ if (!entriesParityEnabled()) return;
12745
+ try {
12746
+ const entries = await store.readEntries(conversationId);
12747
+ const mismatches = [];
12748
+ if (blob.harnessMessages) {
12749
+ const llm = buildLlmContext(entries);
12750
+ const lenMismatch = countMismatch(
12751
+ "llmContext",
12752
+ llm.length,
12753
+ blob.harnessMessages.length
12754
+ );
12755
+ if (lenMismatch) mismatches.push(lenMismatch);
12756
+ const entriesProj = projectAll(llm);
12757
+ const blobProj = projectAll(blob.harnessMessages);
12758
+ const tail = Math.min(entriesProj.length, blobProj.length, 5);
12759
+ for (let i = 1; i <= tail; i++) {
12760
+ const ep = entriesProj[entriesProj.length - i];
12761
+ const bp = blobProj[blobProj.length - i];
12762
+ if (ep !== bp) {
12763
+ mismatches.push(
12764
+ `llmContext tail[-${i}] differs: entries=${JSON.stringify(ep).slice(0, 120)} blob=${JSON.stringify(bp).slice(0, 120)}`
12765
+ );
12766
+ }
12767
+ }
12768
+ }
12769
+ if (blob.displayMessages) {
12770
+ const snap = buildDisplaySnapshot(entries, Number.MAX_SAFE_INTEGER);
12771
+ const lenMismatch = countMismatch(
12772
+ "display",
12773
+ snap.totalMessages,
12774
+ blob.displayMessages.length
12775
+ );
12776
+ if (lenMismatch) mismatches.push(lenMismatch);
12777
+ const entriesProj = projectAll(snap.messages);
12778
+ const blobProj = projectAll(blob.displayMessages);
12779
+ const tail = Math.min(entriesProj.length, blobProj.length, 5);
12780
+ for (let i = 1; i <= tail; i++) {
12781
+ const ep = entriesProj[entriesProj.length - i];
12782
+ const bp = blobProj[blobProj.length - i];
12783
+ if (ep !== bp) {
12784
+ mismatches.push(
12785
+ `display tail[-${i}] differs: entries=${JSON.stringify(ep).slice(0, 120)} blob=${JSON.stringify(bp).slice(0, 120)}`
12786
+ );
12787
+ }
12788
+ }
12789
+ }
12790
+ if (mismatches.length > 0) {
12791
+ log2.warn(
12792
+ `[entries-parity] ${conversationId} MISMATCH (${mismatches.length}): ${mismatches.join(" | ")}`
12793
+ );
12794
+ } else {
12795
+ log2.info(`[entries-parity] ${conversationId} OK`);
12796
+ }
12797
+ } catch (err) {
12798
+ log2.error(
12799
+ `[entries-parity] ${conversationId} checker threw (ignored): ${err instanceof Error ? err.message : String(err)}`
12800
+ );
12801
+ }
12802
+ };
12803
+
12804
+ // src/orchestrator/orchestrator.ts
12805
+ var dualWriteLog = createLogger7("orchestrator:entries");
12448
12806
  var assistantMessageText = (message) => {
12449
- const raw = getTextContent3(message).trim();
12807
+ const raw = getTextContent4(message).trim();
12450
12808
  if (raw.startsWith("{") && raw.includes('"tool_calls"')) {
12451
12809
  try {
12452
12810
  const parsed = JSON.parse(raw);
@@ -12755,6 +13113,8 @@ var AgentOrchestrator = class {
12755
13113
  if (!checkpointedRun) {
12756
13114
  const conv = await this.conversationStore.get(conversationId);
12757
13115
  if (conv) {
13116
+ let amendmentText;
13117
+ let pushedAssistant;
12758
13118
  const hasAssistantContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0;
12759
13119
  if (hasAssistantContent) {
12760
13120
  const prevMessages = conv.messages;
@@ -12782,15 +13142,14 @@ var AgentOrchestrator = class {
12782
13142
  }
12783
13143
  }
12784
13144
  ];
13145
+ amendmentText = draft.assistantResponse;
12785
13146
  } else {
12786
- conv.messages = [
12787
- ...prevMessages,
12788
- {
12789
- role: "assistant",
12790
- content: draft.assistantResponse,
12791
- metadata: buildAssistantMetadata(draft)
12792
- }
12793
- ];
13147
+ pushedAssistant = {
13148
+ role: "assistant",
13149
+ content: draft.assistantResponse,
13150
+ metadata: buildAssistantMetadata(draft)
13151
+ };
13152
+ conv.messages = [...prevMessages, pushedAssistant];
12794
13153
  }
12795
13154
  }
12796
13155
  applyTurnMetadata(conv, {
@@ -12800,6 +13159,54 @@ var AgentOrchestrator = class {
12800
13159
  harnessMessages: execution?.runHarnessMessages
12801
13160
  }, { shouldRebuildCanonical: true });
12802
13161
  await this.conversationStore.update(conv);
13162
+ if (amendmentText !== void 0 || pushedAssistant) {
13163
+ const finalConv = conv;
13164
+ const amendText = amendmentText;
13165
+ const pushed = pushedAssistant;
13166
+ void (async () => {
13167
+ try {
13168
+ if (pushed) {
13169
+ await appendEntriesSafe(
13170
+ this.conversationStore,
13171
+ finalConv,
13172
+ [assistantMessageEntry(pushed, `resume-${conversationId}`, latestRunId)],
13173
+ dualWriteLog
13174
+ );
13175
+ } else if (amendText !== void 0) {
13176
+ const existing = await this.conversationStore.readEntries(
13177
+ conversationId,
13178
+ { types: ["assistant_message"] }
13179
+ );
13180
+ const target = existing[existing.length - 1];
13181
+ if (target) {
13182
+ await appendEntriesSafe(
13183
+ this.conversationStore,
13184
+ finalConv,
13185
+ [assistantAmendmentEntry(target.id, amendText)],
13186
+ dualWriteLog
13187
+ );
13188
+ } else {
13189
+ dualWriteLog.warn(
13190
+ `[entries-dual-write] resume amendment for ${conversationId}: no assistant_message entry to target; skipped`
13191
+ );
13192
+ }
13193
+ }
13194
+ await verifyEntriesParity(
13195
+ this.conversationStore,
13196
+ conversationId,
13197
+ {
13198
+ harnessMessages: finalConv._harnessMessages,
13199
+ displayMessages: finalConv.messages
13200
+ },
13201
+ dualWriteLog
13202
+ );
13203
+ } catch (err) {
13204
+ dualWriteLog.error(
13205
+ `[entries-dual-write] resume finalize append failed for ${conversationId}: ${err instanceof Error ? err.message : String(err)}`
13206
+ );
13207
+ }
13208
+ })();
13209
+ }
12803
13210
  }
12804
13211
  } else {
12805
13212
  const conv = await this.conversationStore.get(conversationId);
@@ -13310,19 +13717,22 @@ var AgentOrchestrator = class {
13310
13717
  conversation.runStatus = "running";
13311
13718
  const callbackCount = (conversation.subagentCallbackCount ?? 0) + 1;
13312
13719
  conversation.subagentCallbackCount = callbackCount;
13720
+ const injectedCallbackMessages = [];
13313
13721
  for (const pr of pendingResults) {
13314
13722
  const responseText = (pr.result?.response ?? "").trim();
13315
13723
  const responseLine = responseText || `(subagent produced no final summary after ${pr.result?.steps ?? 0} step(s); its work may be incomplete. Call read_subagent with subagent_id "${pr.subagentId}" and mode "assistant" to retrieve what it did.)`;
13316
13724
  const resultBody = pr.result ? `Status: ${pr.result.status}
13317
13725
  Response: ${responseLine}
13318
13726
  Steps: ${pr.result.steps}, Duration: ${pr.result.duration}ms` : pr.error ? `Error: ${pr.error.message}` : "(no result)";
13319
- conversation.messages.push({
13727
+ const injected = {
13320
13728
  role: "user",
13321
13729
  content: `[Subagent Result] Subagent "${pr.task}" (${pr.subagentId}) ${pr.status}:
13322
13730
 
13323
13731
  ${resultBody}`,
13324
13732
  metadata: { _subagentCallback: true, subagentId: pr.subagentId, task: pr.task, timestamp: pr.timestamp }
13325
- });
13733
+ };
13734
+ injectedCallbackMessages.push(injected);
13735
+ conversation.messages.push(injected);
13326
13736
  }
13327
13737
  const processedIds = new Set(pendingResults.map((pr) => pr.subagentId));
13328
13738
  const freshForPending = await this.conversationStore.get(conversationId);
@@ -13331,6 +13741,36 @@ ${resultBody}`,
13331
13741
  conversation._harnessMessages = [...conversation.messages];
13332
13742
  conversation.updatedAt = Date.now();
13333
13743
  await this.conversationStore.update(conversation);
13744
+ if (pendingResults.length > 0) {
13745
+ const turnId = `callback-${callbackCount}-${conversation.conversationId}`;
13746
+ void (async () => {
13747
+ try {
13748
+ const resultEntries = await this.conversationStore.readEntries(
13749
+ conversation.conversationId,
13750
+ { types: ["subagent_result"] }
13751
+ );
13752
+ const consumedIds = new Set(pendingResults.map((pr) => pr.subagentId));
13753
+ const consumedSeqs = resultEntries.filter(
13754
+ (e) => e.type === "subagent_result" && consumedIds.has(e.result.subagentId)
13755
+ ).map((e) => e.seq);
13756
+ await appendEntriesSafe(
13757
+ this.conversationStore,
13758
+ conversation,
13759
+ [
13760
+ callbackStartedEntry(consumedSeqs),
13761
+ ...injectedCallbackMessages.map(
13762
+ (m) => userMessageEntry(m, turnId, { hidden: true })
13763
+ )
13764
+ ],
13765
+ dualWriteLog
13766
+ );
13767
+ } catch (err) {
13768
+ dualWriteLog.error(
13769
+ `[entries-dual-write] callback_started append failed for ${conversation.conversationId}: ${err instanceof Error ? err.message : String(err)}`
13770
+ );
13771
+ }
13772
+ })();
13773
+ }
13334
13774
  if (callbackCount > MAX_SUBAGENT_CALLBACK_COUNT) {
13335
13775
  console.warn(`[poncho][subagent-callback] Circuit breaker: ${callbackCount} callbacks for ${conversationId}, skipping re-run`);
13336
13776
  conversation.runningCallbackSince = void 0;
@@ -13392,12 +13832,14 @@ ${resultBody}`,
13392
13832
  if (callbackNeedsContinuation || execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0) {
13393
13833
  const freshConv = await this.conversationStore.get(conversationId);
13394
13834
  if (freshConv) {
13835
+ let callbackAssistantMsg;
13395
13836
  if (!callbackNeedsContinuation) {
13396
- freshConv.messages.push({
13837
+ callbackAssistantMsg = {
13397
13838
  role: "assistant",
13398
13839
  content: execution.draft.assistantResponse,
13399
13840
  metadata: buildAssistantMetadata(execution.draft)
13400
- });
13841
+ };
13842
+ freshConv.messages.push(callbackAssistantMsg);
13401
13843
  }
13402
13844
  applyTurnMetadata(freshConv, {
13403
13845
  latestRunId: execution.latestRunId,
@@ -13410,6 +13852,25 @@ ${resultBody}`,
13410
13852
  }, { shouldRebuildCanonical: true, clearApprovals: false });
13411
13853
  freshConv.runningCallbackSince = void 0;
13412
13854
  await this.conversationStore.update(freshConv);
13855
+ if (callbackAssistantMsg) {
13856
+ const finalMsg = callbackAssistantMsg;
13857
+ void appendEntriesSafe(
13858
+ this.conversationStore,
13859
+ freshConv,
13860
+ [assistantMessageEntry(finalMsg, `callback-${conversationId}`, execution.latestRunId)],
13861
+ dualWriteLog
13862
+ ).then(
13863
+ () => verifyEntriesParity(
13864
+ this.conversationStore,
13865
+ conversationId,
13866
+ {
13867
+ harnessMessages: freshConv._harnessMessages,
13868
+ displayMessages: freshConv.messages
13869
+ },
13870
+ dualWriteLog
13871
+ )
13872
+ );
13873
+ }
13413
13874
  if (freshConv.channelMeta && execution.draft.assistantResponse.length > 0) {
13414
13875
  this.hooks?.onMessagingNotify?.(conversationId, execution.draft.assistantResponse);
13415
13876
  }
@@ -13814,6 +14275,22 @@ ${resultBody}`,
13814
14275
  * old `.catch(() => {})` call sites did. Returns whether the result landed.
13815
14276
  */
13816
14277
  async appendSubagentResultReliable(parentConversationId, result) {
14278
+ void (async () => {
14279
+ try {
14280
+ const parent = await this.conversationStore.get(parentConversationId);
14281
+ if (!parent) return;
14282
+ await appendEntriesSafe(
14283
+ this.conversationStore,
14284
+ parent,
14285
+ [subagentResultEntry(result)],
14286
+ dualWriteLog
14287
+ );
14288
+ } catch (err) {
14289
+ dualWriteLog.error(
14290
+ `[entries-dual-write] subagent_result append failed for ${parentConversationId}: ${err instanceof Error ? err.message : String(err)}`
14291
+ );
14292
+ }
14293
+ })();
13817
14294
  try {
13818
14295
  await this.conversationStore.appendSubagentResult(parentConversationId, result);
13819
14296
  return true;
@@ -13915,9 +14392,9 @@ ${resultBody}`,
13915
14392
  };
13916
14393
 
13917
14394
  // src/orchestrator/run-conversation-turn.ts
13918
- import { randomUUID as randomUUID6 } from "crypto";
13919
- import { createLogger as createLogger7 } from "@poncho-ai/sdk";
13920
- var log = createLogger7("orchestrator");
14395
+ import { randomUUID as randomUUID7 } from "crypto";
14396
+ import { createLogger as createLogger8 } from "@poncho-ai/sdk";
14397
+ var log = createLogger8("orchestrator");
13921
14398
  var runConversationTurn = async (opts) => {
13922
14399
  const conversation = await opts.conversationStore.getWithArchive(opts.conversationId);
13923
14400
  if (!conversation) {
@@ -13955,9 +14432,9 @@ var runConversationTurn = async (opts) => {
13955
14432
  const userMessage = {
13956
14433
  role: "user",
13957
14434
  content: userContent,
13958
- metadata: { id: randomUUID6(), timestamp: turnTimestamp }
14435
+ metadata: { id: randomUUID7(), timestamp: turnTimestamp }
13959
14436
  };
13960
- const assistantId = randomUUID6();
14437
+ const assistantId = randomUUID7();
13961
14438
  const draft = createTurnDraftState();
13962
14439
  let latestRunId = conversation.runtimeRunId ?? "";
13963
14440
  let runCancelled = false;
@@ -13996,6 +14473,8 @@ var runConversationTurn = async (opts) => {
13996
14473
  conversation.updatedAt = Date.now();
13997
14474
  await opts.conversationStore.update(conversation);
13998
14475
  };
14476
+ const preTurnHarnessMessages = conversation._harnessMessages ? [...conversation._harnessMessages] : void 0;
14477
+ const turnId = assistantId;
13999
14478
  conversation.messages = [...historyMessages, userMessage];
14000
14479
  conversation.subagentCallbackCount = 0;
14001
14480
  conversation._continuationCount = void 0;
@@ -14005,6 +14484,12 @@ var runConversationTurn = async (opts) => {
14005
14484
  `failed to persist user turn: ${err instanceof Error ? err.message : String(err)}`
14006
14485
  );
14007
14486
  });
14487
+ void appendEntriesSafe(
14488
+ opts.conversationStore,
14489
+ conversation,
14490
+ [userMessageEntry(userMessage, turnId)],
14491
+ log
14492
+ );
14008
14493
  try {
14009
14494
  const execution = await executeConversationTurn({
14010
14495
  harness: opts.harness,
@@ -14052,6 +14537,34 @@ var runConversationTurn = async (opts) => {
14052
14537
  ...existingHistory,
14053
14538
  ...preRunMessages.slice(0, removedCount)
14054
14539
  ];
14540
+ const summaryMessage = event.compactedMessages[0];
14541
+ const keptCount = Math.max(0, event.compactedMessages.length - 1);
14542
+ if (summaryMessage) {
14543
+ void (async () => {
14544
+ try {
14545
+ const existing = await opts.conversationStore.readEntries(
14546
+ opts.conversationId,
14547
+ { types: ["harness_message"] }
14548
+ );
14549
+ const harnessSeqs = existing.map((e) => e.seq);
14550
+ const firstKeptSeq = harnessSeqs.length >= keptCount && keptCount > 0 ? harnessSeqs[harnessSeqs.length - keptCount] : (harnessSeqs[harnessSeqs.length - 1] ?? 0) + 1;
14551
+ await appendEntriesSafe(
14552
+ opts.conversationStore,
14553
+ conversation,
14554
+ [
14555
+ compactionEntry(summaryMessage, firstKeptSeq, {
14556
+ tokensBefore: conversation.contextTokens
14557
+ })
14558
+ ],
14559
+ log
14560
+ );
14561
+ } catch (err) {
14562
+ log.error(
14563
+ `[entries-dual-write] compaction append failed: ${err instanceof Error ? err.message : String(err)}`
14564
+ );
14565
+ }
14566
+ })();
14567
+ }
14055
14568
  }
14056
14569
  }
14057
14570
  if (event.type === "step:completed") {
@@ -14185,6 +14698,36 @@ var runConversationTurn = async (opts) => {
14185
14698
  { shouldRebuildCanonical }
14186
14699
  );
14187
14700
  await opts.conversationStore.update(conversation);
14701
+ const finalAssistant = conversation.messages[conversation.messages.length - 1];
14702
+ const { messages: newHarness, approximate } = newHarnessMessagesThisTurn(
14703
+ preTurnHarnessMessages,
14704
+ conversation._harnessMessages
14705
+ );
14706
+ if (approximate) {
14707
+ log.warn(
14708
+ `[entries-dual-write] ${opts.conversationId} harness-message diff approximate (blob array shrank this turn \u2014 likely compaction); appended full context`
14709
+ );
14710
+ }
14711
+ const finalizeEntries = [
14712
+ ...harnessMessageEntries(newHarness, turnId),
14713
+ ...finalAssistant && finalAssistant.role === "assistant" ? [assistantMessageEntry(finalAssistant, turnId, latestRunId)] : []
14714
+ ];
14715
+ void appendEntriesSafe(
14716
+ opts.conversationStore,
14717
+ conversation,
14718
+ finalizeEntries,
14719
+ log
14720
+ ).then(
14721
+ () => verifyEntriesParity(
14722
+ opts.conversationStore,
14723
+ opts.conversationId,
14724
+ {
14725
+ harnessMessages: conversation._harnessMessages,
14726
+ displayMessages: conversation.messages
14727
+ },
14728
+ log
14729
+ )
14730
+ );
14188
14731
  }
14189
14732
  return {
14190
14733
  latestRunId,
@@ -14302,10 +14845,13 @@ export {
14302
14845
  VFS_SCHEME,
14303
14846
  VercelBlobUploadStore,
14304
14847
  abnormalEndResponse,
14848
+ appendEntriesSafe,
14305
14849
  applyTurnMetadata,
14306
14850
  buildAgentDirectoryName,
14307
14851
  buildApprovalCheckpoints,
14308
14852
  buildAssistantMetadata,
14853
+ buildDisplaySnapshot,
14854
+ buildLlmContext,
14309
14855
  buildSkillContextWindow,
14310
14856
  buildToolCompletedText,
14311
14857
  cloneSections,
@@ -14342,6 +14888,7 @@ export {
14342
14888
  deleteOpenAICodexSession,
14343
14889
  deriveUploadKey,
14344
14890
  ensureAgentIdentity,
14891
+ entriesParityEnabled,
14345
14892
  estimateTokens,
14346
14893
  estimateTotalTokens,
14347
14894
  executeConversationTurn,
@@ -14353,6 +14900,7 @@ export {
14353
14900
  getOpenAICodexAccessToken,
14354
14901
  getOpenAICodexAuthFilePath,
14355
14902
  getOpenAICodexRequiredScopes,
14903
+ getPendingSubagentResults,
14356
14904
  getPonchoStoreRoot,
14357
14905
  isMessageArray,
14358
14906
  jsonSchemaToZod,
@@ -14366,6 +14914,7 @@ export {
14366
14914
  loadSkillMetadataFromDirs,
14367
14915
  loadVfsSkillMetadata,
14368
14916
  mergeSkills,
14917
+ newHarnessMessagesThisTurn,
14369
14918
  normalizeApprovalCheckpoint,
14370
14919
  normalizeOtlp,
14371
14920
  normalizeScriptPolicyPath,
@@ -14389,6 +14938,7 @@ export {
14389
14938
  runConversationTurn,
14390
14939
  slugifyStorageComponent,
14391
14940
  startOpenAICodexDeviceAuth,
14941
+ verifyEntriesParity,
14392
14942
  verifyTenantToken,
14393
14943
  withToolResultArchiveParam,
14394
14944
  writeOpenAICodexSession