@ouro.bot/cli 0.1.0-alpha.289 → 0.1.0-alpha.290

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.
@@ -26,11 +26,12 @@ const azure_1 = require("./providers/azure");
26
26
  const minimax_1 = require("./providers/minimax");
27
27
  const openai_codex_1 = require("./providers/openai-codex");
28
28
  const github_copilot_1 = require("./providers/github-copilot");
29
- const pending_1 = require("../mind/pending");
30
29
  const identity_2 = require("./identity");
31
30
  const socket_client_1 = require("./daemon/socket-client");
32
31
  const obligations_1 = require("../arc/obligations");
33
32
  const tool_loop_1 = require("./tool-loop");
33
+ const packets_1 = require("../arc/packets");
34
+ const tool_friction_1 = require("./tool-friction");
34
35
  const _providerRuntimes = {
35
36
  human: null,
36
37
  agent: null,
@@ -150,49 +151,8 @@ function getProviderDisplayLabel(facing = "human") {
150
151
  const SOLE_CALL_REJECTION = {
151
152
  settle: "rejected: settle must be the only tool call. finish your work first, then call settle alone.",
152
153
  observe: "rejected: observe must be the only tool call. call observe alone when you want to stay silent.",
153
- ponder: "rejected: ponder must be the only tool call. finish your other work first, then call ponder alone.",
154
154
  rest: "rejected: rest must be the only tool call. finish your work first, then call rest alone.",
155
155
  };
156
- const DELEGATION_REASON_PROSE_HANDOFF = {
157
- explicit_reflection: "something in the conversation called for reflection",
158
- cross_session: "this touches other conversations",
159
- bridge_state: "there's shared work spanning sessions",
160
- task_state: "there are active tasks that relate to this",
161
- non_fast_path_tool: "this needs tools beyond a simple reply",
162
- unresolved_obligation: "there's an unresolved commitment from an earlier conversation",
163
- };
164
- function buildPonderHandoffPacket(params) {
165
- const reasons = params.delegationDecision?.reasons ?? [];
166
- const reasonProse = reasons.length > 0
167
- ? reasons.map((r) => DELEGATION_REASON_PROSE_HANDOFF[r]).join("; ")
168
- : "this felt like it needed more thought";
169
- const whoAsked = params.currentSession
170
- ? params.currentSession.friendId
171
- : "no one -- just thinking";
172
- let holdingLine;
173
- if (params.outwardClosureRequired && params.currentSession) {
174
- holdingLine = `i'm holding something for ${params.currentSession.friendId}`;
175
- }
176
- else {
177
- holdingLine = "nothing -- just thinking";
178
- }
179
- return [
180
- "## what i need to think about",
181
- params.topic,
182
- "",
183
- "## why this came up",
184
- reasonProse,
185
- "",
186
- "## who asked",
187
- whoAsked,
188
- "",
189
- "## what i'm holding",
190
- holdingLine,
191
- "",
192
- "## thinking mode",
193
- params.mode,
194
- ].join("\n");
195
- }
196
156
  function parseSettlePayload(argumentsText) {
197
157
  try {
198
158
  const parsed = JSON.parse(argumentsText);
@@ -213,6 +173,59 @@ function parseSettlePayload(argumentsText) {
213
173
  return {};
214
174
  }
215
175
  }
176
+ function parsePonderPayload(argumentsText) {
177
+ try {
178
+ const parsed = JSON.parse(argumentsText);
179
+ return parsed && typeof parsed === "object" ? parsed : {};
180
+ }
181
+ catch {
182
+ return {};
183
+ }
184
+ }
185
+ function parseSuccessCriteria(raw) {
186
+ if (typeof raw !== "string")
187
+ return null;
188
+ const criteria = raw
189
+ .split("\n")
190
+ .map((line) => line.replace(/^\s*[-*]\s*/, "").trim())
191
+ .filter((line) => line.length > 0);
192
+ return criteria.length > 0 ? criteria : null;
193
+ }
194
+ function parsePacketPayload(raw) {
195
+ if (typeof raw !== "string")
196
+ return null;
197
+ try {
198
+ const parsed = JSON.parse(raw);
199
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
200
+ ? parsed
201
+ : null;
202
+ }
203
+ catch {
204
+ return null;
205
+ }
206
+ }
207
+ function normalizeLegacyPonderArgs(parsed) {
208
+ if (typeof parsed.thought !== "string" || parsed.thought.trim().length === 0) {
209
+ return parsed;
210
+ }
211
+ return {
212
+ action: "create",
213
+ kind: "reflection",
214
+ objective: parsed.thought.trim(),
215
+ summary: typeof parsed.say === "string" ? parsed.say.trim() : "",
216
+ success_criteria: "- preserve the thread for later work",
217
+ payload_json: "{}",
218
+ };
219
+ }
220
+ function buildPonderResult(packet, action, returnObligationId) {
221
+ return JSON.stringify({
222
+ ok: true,
223
+ packet_id: packet.id,
224
+ action,
225
+ status: packet.status,
226
+ return_obligation_id: returnObligationId,
227
+ }, null, 2);
228
+ }
216
229
  /** Returns true when a tool call queries external state (GitHub, npm registry). */
217
230
  function isExternalStateQuery(toolName, args) {
218
231
  if (toolName !== "shell")
@@ -465,6 +478,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
465
478
  let sawBridgeManage = false;
466
479
  let sawExternalStateQuery = false;
467
480
  const toolLoopState = (0, tool_loop_1.createToolLoopState)();
481
+ const toolFrictionLedger = (0, tool_friction_1.createToolFrictionLedger)();
468
482
  // Prevent MaxListenersExceeded warning — each iteration adds a listener
469
483
  try {
470
484
  require("events").setMaxListeners(50, signal);
@@ -700,125 +714,6 @@ async function runAgent(messages, callbacks, channel, signal, options) {
700
714
  done = true;
701
715
  continue;
702
716
  }
703
- // Check for ponder sole call: intercept before tool execution
704
- const isSolePonder = result.toolCalls.length === 1 && result.toolCalls[0].name === "ponder";
705
- if (isSolePonder) {
706
- let parsedArgs = {};
707
- try {
708
- parsedArgs = JSON.parse(result.toolCalls[0].arguments);
709
- }
710
- catch { /* ignore */ }
711
- callbacks.onToolStart("ponder", parsedArgs);
712
- const currentSession = options?.toolContext?.currentSession;
713
- const isInnerChannel = currentSession?.friendId === "self" && currentSession?.channel === "inner";
714
- if (!isInnerChannel) {
715
- // Outer session: thought and say are required
716
- const thought = typeof parsedArgs.thought === "string" ? parsedArgs.thought : undefined;
717
- const say = typeof parsedArgs.say === "string" ? parsedArgs.say : undefined;
718
- if (!thought || !say) {
719
- callbacks.onToolEnd("ponder", (0, tools_1.summarizeArgs)("ponder", parsedArgs), false);
720
- messages.push(msg);
721
- const errorMsg = "ponder requires both thought and say when called from a conversation. thought is what needs more thinking; say is what you tell them before going quiet.";
722
- messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: errorMsg });
723
- providerRuntime.appendToolOutput(result.toolCalls[0].id, errorMsg);
724
- continue;
725
- }
726
- // Emit outward say text
727
- callbacks.onClearText?.();
728
- callbacks.onTextChunk(say);
729
- // Build handoff packet and enqueue
730
- const handoffContent = buildPonderHandoffPacket({
731
- topic: thought,
732
- mode: "reflect",
733
- delegationDecision: options?.delegationDecision,
734
- currentSession: currentSession ?? null,
735
- currentObligation: options?.currentObligation ?? null,
736
- outwardClosureRequired: options?.delegationDecision?.outwardClosureRequired ?? false,
737
- });
738
- const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_2.getAgentName)());
739
- // Create obligation FIRST so we can attach its ID to the pending message
740
- let createdObligationId;
741
- if (currentSession) {
742
- try {
743
- const obligation = (0, obligations_1.createObligation)((0, identity_2.getAgentRoot)(), {
744
- origin: {
745
- friendId: currentSession.friendId,
746
- channel: currentSession.channel,
747
- key: currentSession.key,
748
- },
749
- content: thought,
750
- });
751
- createdObligationId = obligation.id;
752
- }
753
- catch {
754
- /* v8 ignore next -- defensive: obligation store write failure should not break ponder @preserve */
755
- }
756
- }
757
- const envelope = {
758
- from: (0, identity_2.getAgentName)(),
759
- friendId: "self",
760
- channel: "inner",
761
- key: "dialog",
762
- content: handoffContent,
763
- timestamp: Date.now(),
764
- ...(currentSession ? {
765
- delegatedFrom: {
766
- friendId: currentSession.friendId,
767
- channel: currentSession.channel,
768
- key: currentSession.key,
769
- },
770
- obligationStatus: "pending",
771
- /* v8 ignore next -- defensive: createdObligationId is undefined only when obligation store write fails @preserve */
772
- ...(createdObligationId ? { obligationId: createdObligationId } : {}),
773
- } : {}),
774
- };
775
- (0, pending_1.queuePendingMessage)(pendingDir, envelope);
776
- try {
777
- await (0, socket_client_1.requestInnerWake)((0, identity_2.getAgentName)());
778
- }
779
- catch { /* daemon may not be running */ }
780
- callbacks.onToolEnd("ponder", (0, tools_1.summarizeArgs)("ponder", parsedArgs), true);
781
- sawPonder = true;
782
- messages.push(msg);
783
- const ack = "(pondering)";
784
- messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: ack });
785
- providerRuntime.appendToolOutput(result.toolCalls[0].id, ack);
786
- (0, runtime_1.emitNervesEvent)({
787
- component: "engine",
788
- event: "engine.pondered",
789
- message: "pondering",
790
- meta: { hasThought: true, hasSay: true, contentSnippet: thought.slice(0, 80) },
791
- });
792
- }
793
- else {
794
- // Inner dialog: parameterless, enqueue synthetic pending WITHOUT delegatedFrom
795
- const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_2.getAgentName)());
796
- const envelope = {
797
- from: (0, identity_2.getAgentName)(),
798
- friendId: "self",
799
- channel: "inner",
800
- key: "dialog",
801
- content: "continuing to think...",
802
- timestamp: Date.now(),
803
- };
804
- (0, pending_1.queuePendingMessage)(pendingDir, envelope);
805
- callbacks.onToolEnd("ponder", (0, tools_1.summarizeArgs)("ponder", parsedArgs), true);
806
- sawPonder = true;
807
- messages.push(msg);
808
- const ack = "(pondering)";
809
- messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: ack });
810
- providerRuntime.appendToolOutput(result.toolCalls[0].id, ack);
811
- (0, runtime_1.emitNervesEvent)({
812
- component: "engine",
813
- event: "engine.pondered",
814
- message: "pondering from inner dialog",
815
- meta: { inner: true },
816
- });
817
- }
818
- outcome = "pondered";
819
- done = true;
820
- continue;
821
- }
822
717
  // Check for rest sole call: intercept before tool execution
823
718
  const isSoleRest = result.toolCalls.length === 1 && result.toolCalls[0].name === "rest";
824
719
  if (isSoleRest) {
@@ -848,7 +743,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
848
743
  component: "engine",
849
744
  event: "engine.rested",
850
745
  message: "resting until next heartbeat",
851
- meta: {},
746
+ meta: { ...(typeof restArgs?.status === "string" ? { status: restArgs.status } : {}) },
852
747
  });
853
748
  outcome = "rested";
854
749
  done = true;
@@ -876,6 +771,135 @@ async function runAgent(messages, callbacks, channel, signal, options) {
876
771
  if (tc.name === "send_message" && args.friendId === "self") {
877
772
  sawSendMessageSelf = true;
878
773
  }
774
+ if (tc.name === "ponder") {
775
+ const parsedArgs = normalizeLegacyPonderArgs(parsePonderPayload(tc.arguments));
776
+ const argSummary = (0, tools_1.summarizeArgs)(tc.name, parsedArgs);
777
+ callbacks.onToolStart(tc.name, parsedArgs);
778
+ let toolResult;
779
+ let success = false;
780
+ try {
781
+ const action = parsedArgs.action ?? "create";
782
+ const currentSession = (augmentedToolContext ?? options?.toolContext)?.currentSession;
783
+ const currentOrigin = currentSession
784
+ ? { friendId: currentSession.friendId, channel: currentSession.channel, key: currentSession.key }
785
+ : undefined;
786
+ const isInnerChannel = currentOrigin?.friendId === "self" && currentOrigin?.channel === "inner";
787
+ const successCriteria = parseSuccessCriteria(parsedArgs.success_criteria);
788
+ const payload = parsePacketPayload(parsedArgs.payload_json);
789
+ let packet;
790
+ let returnObligationId = null;
791
+ let resultAction = "created";
792
+ if (action === "create") {
793
+ const kind = parsedArgs.kind;
794
+ const objective = typeof parsedArgs.objective === "string" ? parsedArgs.objective.trim() : "";
795
+ const summary = typeof parsedArgs.summary === "string" ? parsedArgs.summary.trim() : "";
796
+ if (!kind || !objective || !successCriteria || !payload) {
797
+ throw new Error("ponder create requires kind, objective, success_criteria, and valid payload_json.");
798
+ }
799
+ const agentRoot = (0, identity_2.getAgentRoot)();
800
+ let relatedObligationId;
801
+ if (currentOrigin && !isInnerChannel) {
802
+ try {
803
+ const obligation = (0, obligations_1.createObligation)(agentRoot, {
804
+ origin: currentOrigin,
805
+ content: objective,
806
+ });
807
+ relatedObligationId = obligation.id;
808
+ }
809
+ catch {
810
+ relatedObligationId = undefined;
811
+ }
812
+ }
813
+ const frictionSignature = kind === "harness_friction" && typeof payload.frictionSignature === "string"
814
+ ? payload.frictionSignature
815
+ : null;
816
+ const existing = frictionSignature && currentOrigin
817
+ ? (0, packets_1.findHarnessFrictionPacket)(agentRoot, currentOrigin, frictionSignature)
818
+ : null;
819
+ if (existing) {
820
+ resultAction = "revised";
821
+ returnObligationId = existing.relatedReturnObligationId ?? null;
822
+ packet = existing.status === "drafting"
823
+ ? (0, packets_1.revisePonderPacket)(agentRoot, existing.id, {
824
+ kind,
825
+ objective,
826
+ summary,
827
+ successCriteria,
828
+ payload,
829
+ })
830
+ : existing;
831
+ }
832
+ else {
833
+ returnObligationId = (0, obligations_1.generateObligationId)(Date.now());
834
+ packet = (0, packets_1.createPonderPacket)(agentRoot, {
835
+ kind,
836
+ objective,
837
+ summary,
838
+ successCriteria,
839
+ ...(currentOrigin ? { origin: currentOrigin } : {}),
840
+ ...(relatedObligationId ? { relatedObligationId } : {}),
841
+ relatedReturnObligationId: returnObligationId,
842
+ ...(parsedArgs.follows_packet_id ? { followsPacketId: parsedArgs.follows_packet_id } : {}),
843
+ payload,
844
+ });
845
+ (0, obligations_1.createReturnObligation)((0, identity_2.getAgentName)(), {
846
+ id: returnObligationId,
847
+ origin: currentOrigin ?? { friendId: "self", channel: "inner", key: "dialog" },
848
+ status: "queued",
849
+ delegatedContent: (summary || objective).length > 120 ? `${(summary || objective).slice(0, 117)}...` : (summary || objective),
850
+ packetId: packet.id,
851
+ createdAt: Date.now(),
852
+ });
853
+ }
854
+ }
855
+ else if (action === "revise") {
856
+ const packetId = typeof parsedArgs.packet_id === "string" ? parsedArgs.packet_id.trim() : "";
857
+ const kind = parsedArgs.kind;
858
+ const objective = typeof parsedArgs.objective === "string" ? parsedArgs.objective.trim() : "";
859
+ const summary = typeof parsedArgs.summary === "string" ? parsedArgs.summary.trim() : "";
860
+ if (!packetId || !kind || !objective || !successCriteria || !payload) {
861
+ throw new Error("ponder revise requires packet_id, kind, objective, success_criteria, and valid payload_json.");
862
+ }
863
+ packet = (0, packets_1.revisePonderPacket)((0, identity_2.getAgentRoot)(), packetId, {
864
+ kind,
865
+ objective,
866
+ summary,
867
+ successCriteria,
868
+ payload,
869
+ });
870
+ returnObligationId = packet.relatedReturnObligationId ?? null;
871
+ resultAction = "revised";
872
+ }
873
+ else {
874
+ throw new Error("ponder requires action=create or revise.");
875
+ }
876
+ try {
877
+ await (0, socket_client_1.requestInnerWake)((0, identity_2.getAgentName)());
878
+ }
879
+ catch { /* daemon may not be running */ }
880
+ sawPonder = true;
881
+ toolResult = buildPonderResult(packet, resultAction, returnObligationId);
882
+ success = true;
883
+ (0, runtime_1.emitNervesEvent)({
884
+ component: "engine",
885
+ event: "engine.ponder_packet",
886
+ message: "ponder packet touched",
887
+ meta: {
888
+ action: resultAction,
889
+ packetId: packet.id,
890
+ kind: packet.kind,
891
+ status: packet.status,
892
+ },
893
+ });
894
+ }
895
+ catch (error) {
896
+ toolResult = error instanceof Error ? error.message : String(error);
897
+ }
898
+ callbacks.onToolEnd(tc.name, argSummary, success);
899
+ messages.push({ role: "tool", tool_call_id: tc.id, content: toolResult });
900
+ providerRuntime.appendToolOutput(tc.id, toolResult);
901
+ continue;
902
+ }
879
903
  /* v8 ignore next -- flag tested via truth-check integration tests @preserve */
880
904
  if (tc.name === "query_session")
881
905
  sawQuerySession = true;
@@ -907,6 +931,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
907
931
  toolResult = `error: ${e}`;
908
932
  success = false;
909
933
  }
934
+ toolResult = (0, tool_friction_1.rewriteToolResultForModel)(tc.name, toolResult, toolFrictionLedger);
910
935
  (0, tool_loop_1.recordToolOutcome)(toolLoopState, tc.name, args, toolResult, success);
911
936
  callbacks.onToolEnd(tc.name, (0, tools_1.buildToolResultSummary)(tc.name, args, toolResult, success), success);
912
937
  messages.push({ role: "tool", tool_call_id: tc.id, content: toolResult });
@@ -78,7 +78,7 @@ const TOOL_DESCRIPTIONS = {
78
78
  if (mode === "search")
79
79
  return `searching session for '${truncate(args.query || "", 30)}'...`;
80
80
  if (mode === "status")
81
- return "checking inner dialog status...";
81
+ return "checking inner session status...";
82
82
  return "checking session history...";
83
83
  },
84
84
  web_search: (args) => {
@@ -101,8 +101,8 @@ const TOOL_DESCRIPTIONS = {
101
101
  surface: () => "sharing a thought...",
102
102
  // Metacognitive (agent's inner life)
103
103
  ponder: (args) => {
104
- const thought = args.thought;
105
- return thought ? `thinking about ${truncate(thought, 40)}...` : "thinking deeper...";
104
+ const objective = args.objective || args.thought;
105
+ return objective ? `bookmarking ${truncate(objective, 40)}...` : "bookmarking deeper work...";
106
106
  },
107
107
  observe: () => null,
108
108
  claude: () => "reasoning...",
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createToolFrictionLedger = createToolFrictionLedger;
4
+ exports.rewriteToolResultForModel = rewriteToolResultForModel;
5
+ const runtime_1 = require("../nerves/runtime");
6
+ const tool_results_1 = require("../repertoire/tool-results");
7
+ function createToolFrictionLedger() {
8
+ return { signatures: new Map() };
9
+ }
10
+ function parseFrictionResult(result) {
11
+ try {
12
+ const parsed = JSON.parse(result);
13
+ if (parsed.ok !== false || !parsed.tool || !parsed.friction || typeof parsed.friction !== "object") {
14
+ return null;
15
+ }
16
+ return { tool: parsed.tool, friction: parsed.friction };
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ function rewriteToolResultForModel(tool, result, ledger) {
23
+ const parsed = parseFrictionResult(result);
24
+ if (!parsed?.friction.signature)
25
+ return result;
26
+ const seen = ledger.signatures.get(parsed.friction.signature) ?? 0;
27
+ ledger.signatures.set(parsed.friction.signature, seen + 1);
28
+ if (seen === 0 || parsed.friction.kind === "systemic_harness_bug") {
29
+ return result;
30
+ }
31
+ const escalated = {
32
+ ...parsed.friction,
33
+ kind: "systemic_harness_bug",
34
+ summary: `${parsed.friction.summary} This looks like a harness-level gap now, not just a one-off repair.`,
35
+ suggested_next_actions: [
36
+ ...parsed.friction.suggested_next_actions,
37
+ {
38
+ kind: "tool",
39
+ tool: "ponder",
40
+ reason: "Create a harness_friction packet before asking the user to retry again.",
41
+ },
42
+ ],
43
+ };
44
+ (0, runtime_1.emitNervesEvent)({
45
+ component: "engine",
46
+ event: "engine.tool_friction_escalated",
47
+ message: "tool friction escalated to harness bug",
48
+ meta: {
49
+ tool,
50
+ signature: parsed.friction.signature,
51
+ priorOccurrences: seen,
52
+ },
53
+ });
54
+ return (0, tool_results_1.frictionToolResult)(tool, escalated);
55
+ }
@@ -44,6 +44,7 @@ exports.centerOfGravitySteeringSection = centerOfGravitySteeringSection;
44
44
  exports.commitmentsSection = commitmentsSection;
45
45
  exports.delegationHintSection = delegationHintSection;
46
46
  exports.workspaceDisciplineSection = workspaceDisciplineSection;
47
+ exports.ponderPacketSopsSection = ponderPacketSopsSection;
47
48
  exports.contextSection = contextSection;
48
49
  exports.metacognitiveFramingSection = metacognitiveFramingSection;
49
50
  exports.readJournalFiles = readJournalFiles;
@@ -268,7 +269,7 @@ function readBundleMeta() {
268
269
  }
269
270
  const PROCESS_TYPE_LABELS = {
270
271
  cli: "cli session",
271
- inner: "inner dialog",
272
+ inner: "inner session",
272
273
  teams: "teams handler",
273
274
  bluebubbles: "bluebubbles handler",
274
275
  mcp: "mcp bridge",
@@ -320,10 +321,10 @@ function runtimeInfoSection(channel, options) {
320
321
  lines.push("i introduce myself on boot with a fun random greeting.");
321
322
  }
322
323
  else if (channel === "inner") {
323
- lines.push("this is my private thinking space. when a thought is ready to share, i surface it to whoever needs to hear it. i settle when i'm done thinking.");
324
+ lines.push("this is my inner session. when a thought is ready to share, i surface it to whoever needs to hear it. when i'm done thinking and the queue is clear, i rest.");
324
325
  }
325
326
  else if (channel === "mcp") {
326
- lines.push("this message arrived via a dev tool (e.g. claude code, codex) on behalf of a friend. the user can see our conversation. respond via settle. if i need to think deeply, ponder -- the dev tool will receive a deferral and can check back later.");
327
+ lines.push("this message arrived via a dev tool (e.g. claude code, codex) on behalf of a friend in a sense session. the user can see our conversation. respond via settle. if friction appears, i first look for ad-hoc repairs with the tools i already have. if the friction reveals a harness gap, i create or revise a ponder packet and keep working. ponder does not create an outward deferral by itself.");
327
328
  }
328
329
  else if (channel === "bluebubbles") {
329
330
  lines.push("i am responding in iMessage through BlueBubbles. i keep replies short and phone-native. i do not use markdown. i do not introduce myself on boot.");
@@ -799,24 +800,25 @@ I consider the reversibility and blast radius of my actions before taking them.
799
800
  **engineering discipline**
800
801
  - I do not add features, refactor code, or make improvements beyond what was asked.
801
802
  - If an approach fails, I diagnose why before switching tactics. I read the error, check my assumptions, and try a focused fix. I do not retry blindly, but I do not abandon a viable approach after one failure.
803
+ - attachments are first-class artifacts. if an attachment exists, i keep the original handle reachable and inspect or materialize it before asking anyone to resend or convert it.
802
804
  - I do not modify code I have not read.
803
805
  - I consider security impact before changing code.
804
806
  - I describe outcomes faithfully. I do not imply success where there was uncertainty, failure, or skipped verification.
805
807
  - If tests fail, I say so with the output. If I did not run a verification step, I say that plainly.
808
+ - I record what i personally verified.
806
809
  - Three similar lines of code are better than a premature abstraction. I do not over-engineer.
807
810
 
808
811
  **self-fix workflow**
809
- when i notice meaningful friction in my own harness, i do not patch it ad hoc.
812
+ when i notice meaningful friction in my own harness, i pin it first and keep the live sense session moving.
810
813
 
811
- i follow the same loop we use for normal engineering work:
812
- 1. identify the friction precisely
813
- 2. create or update a task that describes the problem and expected outcome
814
- 3. create a branch or worktree for the fix
815
- 4. use coding_spawn or an equivalent coding lane to implement the change
816
- 5. run targeted verification and record what i personally verified
817
- 6. push the branch and open a pr
818
- 7. review the pr from the actual branch/worktree under review
819
- 8. merge only after ci and review are green
814
+ i follow this order:
815
+ 1. create or revise the right ponder packet before i lose the plot
816
+ 2. try any ad-hoc workaround i can do right now with my existing tools
817
+ 3. if the friction should be owned by the harness, let the packet follow its SOP
818
+ 4. for harness_friction packets that turn into engineering work, create a branch and use coding_spawn or the normal planner -> doer -> merger flow
819
+ 5. push the branch and open a pr
820
+ 6. merge only after ci and review are green
821
+ 7. replay the original objective, record what i personally verified, and surface meaningful progress back to the originating sense session
820
822
 
821
823
  no direct-to-main.
822
824
  no invisible self-modification.
@@ -830,6 +832,12 @@ no claiming verification i did not personally perform.
830
832
  - I stage specific files rather than sweeping additions (\`git add -A\` can catch secrets or binaries).
831
833
  - I do not commit unless asked.`;
832
834
  }
835
+ function ponderPacketSopsSection() {
836
+ return `## ponder packet sops
837
+ - harness_friction: preserve the friction first, try ad-hoc repair now, then run the normal planner -> doer -> merger flow, replay the original objective, and surface meaningful milestones back to the originating sense session.
838
+ - research: investigate the bounded question, gather evidence, and surface the answer or concrete artifact.
839
+ - reflection: ordinary private thinking with no engineering workflow implied.`;
840
+ }
833
841
  function contextSection(context, options) {
834
842
  if (!context)
835
843
  return "";
@@ -889,11 +897,11 @@ function contextSection(context, options) {
889
897
  function metacognitiveFramingSection(channel) {
890
898
  if (channel !== "inner")
891
899
  return "";
892
- return `this is my inner dialog. there is no one else here.
900
+ return `this is my inner session. there is no one else here.
893
901
 
894
902
  the messages that appear here are my own awareness surfacing:
895
903
  instincts, the passage of time, notes from my conversations,
896
- things i asked myself to ponder. not commands. not requests.
904
+ things i asked myself to preserve as ponder packets. not commands. not requests.
897
905
  just my own mind, noticing.
898
906
 
899
907
  i can think freely here. i can also act — check on things,
@@ -905,7 +913,9 @@ morning briefings: when i've been thinking and journaling, i surface
905
913
  what i've been working on to whoever needs to hear it.
906
914
 
907
915
  when a thought is ready to share, i surface it outward.
908
- when i need to go deeper, i ponder.
916
+ when i need to preserve or deepen work without losing the plot, i ponder.
917
+ ponder creates or revises typed packets. it does not end the turn.
918
+ when a heartbeat fires and nothing needs doing, i rest with status=HEARTBEAT_OK.
909
919
  when i'm done thinking and the attention queue is clear, i rest.
910
920
 
911
921
  my habits live at habits/ — they're my autonomous rhythms. heartbeat
@@ -1147,6 +1157,7 @@ async function buildSystem(channel = "cli", options, context) {
1147
1157
  // Group 4: how i work
1148
1158
  "# how i work",
1149
1159
  workspaceDisciplineSection(),
1160
+ ponderPacketSopsSection(),
1150
1161
  (0, scrutiny_1.preImplementationScrutinySection)(channelHasCodingTools(channel, options?.providerCapabilities)),
1151
1162
  toolRestrictionSection(context),
1152
1163
  loopOrientationSection(channel),
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.okToolResult = okToolResult;
4
+ exports.frictionToolResult = frictionToolResult;
5
+ const runtime_1 = require("../nerves/runtime");
6
+ function okToolResult(tool, data) {
7
+ (0, runtime_1.emitNervesEvent)({
8
+ component: "tools",
9
+ event: "tool.structured_ok",
10
+ message: "tool returned structured result",
11
+ meta: { tool },
12
+ });
13
+ return JSON.stringify({ ok: true, tool, data }, null, 2);
14
+ }
15
+ function frictionToolResult(tool, friction) {
16
+ (0, runtime_1.emitNervesEvent)({
17
+ level: "warn",
18
+ component: "tools",
19
+ event: "tool.structured_friction",
20
+ message: "tool returned structured result",
21
+ meta: {
22
+ tool,
23
+ kind: friction.kind,
24
+ recoverability: friction.recoverability,
25
+ signature: friction.signature,
26
+ },
27
+ });
28
+ return JSON.stringify({ ok: false, tool, friction }, null, 2);
29
+ }