feat: back up omp config (agent settings, commands, extensions, themes, loops, plugins)

This commit is contained in:
Prad Nukala committed 2026-10-16 10:51:26 -04:00
commit 31c9602d0c
22 files changed
+1154

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
# This repo is checked out directly at ~/.omp (chezmoi external), which is
# mostly runtime state: dbs, sessions, blobs, caches, logs, oauth, natives,
# browser profiles, device ids. Ignore everything; whitelist config only.
/*
!/.gitignore
!/marketplaces.json
!/agent/
/agent/*
!/agent/config.yml
!/agent/mcp.json
!/agent/keybindings.yml
!/agent/commands/
!/agent/extensions/
!/agent/themes/
!/loops/
/loops/*/state.md
/loops/*/runs.log
/loops/escalations.log
!/plugins/
/plugins/*
!/plugins/package.json
!/plugins/bun.lock
!/plugins/omp-plugins.lock.json
!/plugins/installed_plugins.json
+84
View File
@@ -0,0 +1,84 @@
---
description: Convert a LeetCode solution file into a Fumadocs MDX docs page
---
Convert the LeetCode solution file at `$1` into a Fumadocs-formatted MDX page.
Extra instructions (optional, may include output dir override): $@
## Input format
The source file follows the standard on-disk format:
- A `/* ... */` header block: problem number, title, difficulty, LeetCode URL, problem description, examples, constraints
- A JSDoc `@param`/`@return` block
- One or more solution functions (possibly with line-by-line beginner comments and `// ====` banners)
## Output location
Default: write next to the content root at `content/docs/leetcode/<difficulty>/<category>/<number>-<slug>.mdx`, mirroring the source tree (e.g. `Easy/Array/1365.how-many-numbers.js` → `content/docs/leetcode/easy/array/1365-how-many-numbers.mdx`). Lowercase all path segments. If the extra instructions specify a different output path or content root, use that instead. Create directories as needed.
## Page structure
Produce exactly this shape:
```mdx
---
title: 1365. How Many Numbers Are Smaller Than the Current Number
description: <one-sentence summary of the technique, e.g. "Frequency map + prefix count — the counting hammer.">
---
<Callout title="Easy · Array · Hash Table">
[View on LeetCode](https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/)
</Callout>
## Problem
<problem description from the header block, reflowed into clean Markdown prose — no hard-wrapped 70-char lines>
## Examples
<each example as a fenced code block:>
```txt title="Example 1"
Input: nums = [8,1,2,2,3]
Output: [4,0,1,1,3]
```
<if an example has an Explanation, put it as plain prose under its code block>
## Constraints
<bulleted list from the header block, with values in inline code: `2 <= nums.length <= 500`>
## Solution
<one-paragraph intuition: name the pattern (frequency map, prefix sum, two pointers, etc.) and the key insight in 2–3 sentences. Derive this from the code itself.>
```js title="solution.js" lineNumbers
<the solution function, verbatim — including any existing beginner comments>
```
<Callout type="idea" title="Complexity">
**Time:** O(n log n) — **Space:** O(n)
</Callout>
```
## Rules
1. **Frontmatter `title` carries the h1** — Fumadocs renders it as the page title, so never emit a `# heading` in the body.
2. **Escape MDX hazards.** The header text may contain `<`, `>`, `{`, `}` (e.g. `j != i and nums[j] < nums[i]`, `2 <= nums.length <= 500`). Anything with these characters must be wrapped in inline code or fenced blocks, or the MDX compile will fail. When in doubt, inline-code it.
3. **Code is verbatim.** Copy the solution function(s) exactly — do not rewrite, reformat, or strip existing comments. The docs page documents the code as it exists.
4. **Multiple approaches → tab group:**
````mdx
```js tab="Frequency Map" tab-group="approach"
...
```
```js tab="Sort + Map"
...
```
````
5. **Complexity callout is derived, not guessed blindly.** Read the code: nested loop → O(n²), sort → O(n log n), single pass + map → O(n). If genuinely ambiguous, state the dominant term and why in one clause.
6. **Callout header line** = `<Difficulty> · <topic tags>`. Difficulty comes from the header block; infer 1–2 topic tags from the directory path and the technique used.
7. **No invented content.** Problem statement, examples, and constraints come only from the header block. The intuition paragraph and complexity are the only sections you author.
8. **After writing**, print one line: the output path. Nothing else.
+74
View File
@@ -0,0 +1,74 @@
---
description: Add super-simple line-by-line comments to a LeetCode solution file
---
Add beginner-level line-by-line comments to the LeetCode solution file at: $1
## Rules
1. **Read the file first.** It follows the standard on-disk format:
- A `/* ... */` header block containing problem number, title, difficulty, URL, description, examples, and constraints
- A JSDoc `@param`/`@return` block
- The solution function(s)
2. **Never touch the header block or JSDoc block.** Preserve them byte-for-byte.
3. **Never change the code.** Do not rename variables, reformat logic, alter whitespace inside expressions, or "improve" anything. Comments only. The file must produce an identical AST after your edit.
4. **Comment style — explain as if to a complete beginner who has never programmed:**
- One comment line (or short block) ABOVE each meaningful line/statement
- Use plain everyday language and physical metaphors:
- object/map → "box" ("Create an empty box to count how many times each number appears")
- increment → "add 1 to its count"
- `(freq[x] || 0) + 1` → "If we already saw this number, add 1 to its count. If we never saw it before, start counting from 1"
- sort comparator → spell out the rule in words ("put the letter that appears MORE times first; if tied, alphabetical order")
- return → say WHAT is being returned and WHY in plain words
- Explain the WHY, not just the WHAT ("so the next bigger numbers know there are more smaller ones")
- Inline comments (`// ...` at end of line) are fine for short trailing clarifications like `.join('')` → "glue the sorted letters back into a string"
- No jargon without immediate translation: if you must say "index", follow with "(its position)"
5. **Add a section banner** above the solution function in this exact style:
```js
// ============================================
// <problem number>. <problem title>
// ============================================
```
Pull the number and title from the header block.
6. **Multiple solutions in one file:** comment every one of them, each with its own banner (append "– Approach 2" etc. to the banner title).
7. **Extra instructions** (if provided after the file path): $@
8. **After editing**, print a one-line confirmation: file path + number of comment lines added. Nothing else.
## Example transformation
Input body:
```js
var smallerNumbersThanCurrent = function (nums) {
const freq = {};
for (let x of nums) freq[x] = (freq[x] || 0) + 1;
return nums.map((x) => smaller[x]);
};
```
Output body:
```js
// ============================================
// 1365. How Many Numbers Are Smaller Than the Current Number
// ============================================
var smallerNumbersThanCurrent = function (nums) {
// Create an empty box to store how many times each number appears
const freq = {};
// Go through every number in the list one by one
// If we already saw this number, add 1 to its count
// If we never saw it before, start counting from 1
for (let x of nums) freq[x] = (freq[x] || 0) + 1;
// Go through the original list and replace each number
// with "how many numbers are smaller than it"
return nums.map((x) => smaller[x]);
};
```
+31
View File
@@ -0,0 +1,31 @@
theme:
dark: titanium
light: light
symbolPreset: nerd
setupVersion: 2
hideThinkingBlock: false
modelRoles:
plan: anthropic/claude-fable-5-1:high
default: anthropic/claude-opus-5-5:high
smol: anthropic/claude-haiku-5-5
task: anthropic/claude-sonnet-5-5
web: web/exa
dictation: local/parakeet-tdt-0.6b-v3
speech: local/kokoro
image: openai/gpt-image-2
advisor: anthropic/claude-mythos-5-1:xhigh
slow: anthropic/claude-fable-5-1:high
vision: openai/gpt-6-astra:high
commit: cerebras/qwen-3-coder-480b
tiny: opencode-go/step-5-preview:auto
memory: opencode-go/gemini-4-argon-contributor:medium
judge: anthropic/claude-fable-5-1:max
composer:
shape: rule
modelPresets:
Primary:
modelRoles:
plan: anthropic/claude-fable-5-1:high
default: anthropic/claude-opus-5-5:high
smol: anthropic/claude-haiku-5-5
defaultThinkingLevel: high
+471
View File
@@ -0,0 +1,471 @@
// installed by herdr
// managed by herdr; reinstalling or updating the integration overwrites this file.
// add custom hooks/plugins beside this file instead of editing it.
// HERDR_INTEGRATION_ID=omp
// HERDR_INTEGRATION_VERSION=10
// @ts-nocheck
import net from "node:net";
import path from "node:path";
const HERDR_ENV = process.env.HERDR_ENV;
const socketPath = process.env.HERDR_SOCKET_PATH;
const socketEndpoint =
process.platform === "win32" && socketPath ? `\\\\.\\pipe\\${socketPath}` : socketPath;
const paneId = process.env.HERDR_PANE_ID;
const source = "herdr:omp";
// OMP marks every shell it spawns with OMPCODE=1. A nested `omp` launched from
// a parent session's shell inherits it, so that process is not the pane's root
// agent and must not report its short-lived session over the parent's.
const nestedOmpSession = process.env.OMPCODE === "1";
function enabled() {
return HERDR_ENV === "1" && !!socketPath && !!paneId && !nestedOmpSession;
}
let requestQueue = Promise.resolve();
function sendRequestAttempt(request: unknown, timeoutMs: number): Promise<boolean> {
if (!enabled()) {
return Promise.resolve(true);
}
return new Promise((resolve) => {
let done = false;
let timeout: ReturnType<typeof setTimeout> | undefined;
const finish = (delivered: boolean) => {
if (done) return;
done = true;
if (timeout) {
clearTimeout(timeout);
}
socket.destroy();
resolve(delivered);
};
const socket = net.createConnection(socketEndpoint!);
socket.on("error", () => finish(false));
socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`));
socket.on("data", () => finish(true));
socket.on("end", () => finish(false));
timeout = setTimeout(() => finish(false), timeoutMs);
timeout.unref?.();
});
}
async function sendRequestNow(request: unknown): Promise<void> {
if (await sendRequestAttempt(request, 500)) {
return;
}
await sendRequestAttempt(request, 1500);
}
function sendRequest(request: unknown): Promise<void> {
requestQueue = requestQueue.then(
() => sendRequestNow(request),
() => sendRequestNow(request),
);
return requestQueue;
}
type AgentState = "working" | "blocked" | "idle";
type QueuedState = {
state: AgentState;
message?: string;
seq: number;
};
const idleDebounceMs = parseDurationEnv("HERDR_OMP_IDLE_DEBOUNCE_MS", 250);
const retryGraceMs = parseDurationEnv("HERDR_OMP_RETRY_GRACE_MS", 2500);
const retryableErrorPattern =
/overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i;
let reportSeq = Date.now() * 1000;
let currentAgentSessionId: string | undefined;
let currentAgentSessionPath: string | undefined;
function nextReportSeq(): number {
reportSeq += 1;
return reportSeq;
}
export function isAbsoluteSessionPath(file: unknown): file is string {
return (
typeof file === "string" &&
(path.posix.isAbsolute(file) || path.win32.isAbsolute(file))
);
}
function updateSessionRef(ctx: any): void {
try {
const file = ctx?.sessionManager?.getSessionFile?.();
currentAgentSessionPath = isAbsoluteSessionPath(file) ? file : undefined;
} catch {
currentAgentSessionPath = undefined;
}
try {
const id = ctx?.sessionManager?.getSessionId?.();
currentAgentSessionId = typeof id === "string" && id.length > 0 ? id : undefined;
} catch {
currentAgentSessionId = undefined;
}
}
function withSessionRef(params: Record<string, unknown>): Record<string, unknown> {
if (currentAgentSessionPath) {
return { ...params, agent_session_path: currentAgentSessionPath };
}
if (currentAgentSessionId) {
return { ...params, agent_session_id: currentAgentSessionId };
}
return params;
}
function parseDurationEnv(name: string, fallback: number): number {
const raw = process.env[name];
if (!raw) {
return fallback;
}
const parsed = Number.parseInt(raw, 10);
if (!Number.isFinite(parsed) || parsed < 0) {
return fallback;
}
return parsed;
}
function currentSessionRef(): Record<string, unknown> | undefined {
if (currentAgentSessionPath) {
return { agent_session_path: currentAgentSessionPath };
}
if (currentAgentSessionId) {
return { agent_session_id: currentAgentSessionId };
}
return undefined;
}
function reportSession(sessionStartSource = "startup"): Promise<void> {
const sessionRef = currentSessionRef();
if (!sessionRef) {
return Promise.resolve();
}
return sendRequest({
id: `${source}:session:${Date.now()}:${Math.random().toString(36).slice(2)}`,
method: "pane.report_agent_session",
params: {
pane_id: paneId,
source,
agent: "omp",
seq: nextReportSeq(),
session_start_source: sessionStartSource,
...sessionRef,
},
});
}
function sendState(state: AgentState, message?: string, seq = nextReportSeq()): Promise<void> {
return sendRequest({
id: `${source}:${Date.now()}:${Math.random().toString(36).slice(2)}`,
method: "pane.report_agent",
params: withSessionRef({
pane_id: paneId,
source,
agent: "omp",
state,
message,
seq,
}),
});
}
let sendInFlight = false;
let queuedState: QueuedState | undefined;
function queueState(state: AgentState, message?: string): void {
queuedState = { state, message, seq: nextReportSeq() };
if (!sendInFlight) {
void drainStateQueue();
}
}
async function drainStateQueue(): Promise<void> {
if (sendInFlight) {
return;
}
sendInFlight = true;
try {
while (queuedState) {
const next = queuedState;
queuedState = undefined;
await sendState(next.state, next.message, next.seq);
}
} finally {
sendInFlight = false;
if (queuedState) {
void drainStateQueue();
}
}
}
function lastAssistantMessage(messages: unknown[]): any | undefined {
for (let i = messages.length - 1; i >= 0; i -= 1) {
const message = messages[i] as any;
if (message?.role === "assistant") {
return message;
}
}
return undefined;
}
function retryableErrorMessage(event: any): string | undefined {
const messages = Array.isArray(event?.messages) ? event.messages : [];
const assistant = lastAssistantMessage(messages);
if (assistant?.stopReason !== "error") {
return undefined;
}
const errorMessage = String(assistant.errorMessage ?? "");
if (!retryableErrorPattern.test(errorMessage)) {
return undefined;
}
return errorMessage || "retryable provider error";
}
function askBlockedMessage(args: any): string {
const questions = Array.isArray(args?.questions) ? args.questions : [];
const firstQuestion = questions.find((question: any) => typeof question?.question === "string");
if (firstQuestion?.question) {
return firstQuestion.question;
}
return "waiting for user input";
}
export default function (pi) {
if (!enabled()) {
return;
}
let agentActive = false;
let retryHoldActive = false;
let failureBlocked = false;
let failureMessage: string | undefined;
let blockedCount = 0;
let blockedMessage: string | undefined;
let lastState: AgentState | undefined;
let lastMessage: string | undefined;
let idleTimer: ReturnType<typeof setTimeout> | undefined;
let retryTimer: ReturnType<typeof setTimeout> | undefined;
let rootSession = false;
function clearTimer(timer: ReturnType<typeof setTimeout> | undefined) {
if (timer) {
clearTimeout(timer);
}
}
function clearPendingTimers() {
clearTimer(idleTimer);
clearTimer(retryTimer);
idleTimer = undefined;
retryTimer = undefined;
}
function clearFailureState() {
retryHoldActive = false;
failureBlocked = false;
failureMessage = undefined;
}
function desiredState() {
if (blockedCount > 0) {
return { state: "blocked" as const, message: blockedMessage };
}
if (failureBlocked) {
return { state: "blocked" as const, message: failureMessage };
}
if (agentActive || retryHoldActive) {
return { state: "working" as const, message: undefined };
}
return { state: "idle" as const, message: undefined };
}
function publishState(force = false) {
const next = desiredState();
if (!force && next.state === lastState && next.message === lastMessage) {
return;
}
lastState = next.state;
lastMessage = next.message;
queueState(next.state, next.message);
}
function scheduleIdle() {
clearPendingTimers();
clearFailureState();
idleTimer = setTimeout(() => {
idleTimer = undefined;
publishState();
}, idleDebounceMs);
idleTimer.unref?.();
}
function holdForRetry(message: string) {
clearPendingTimers();
retryHoldActive = true;
failureBlocked = false;
failureMessage = message;
publishState();
retryTimer = setTimeout(() => {
retryTimer = undefined;
retryHoldActive = false;
failureBlocked = true;
publishState();
}, retryGraceMs);
retryTimer.unref?.();
}
function activateRootSession(ctx: any, sessionStartSource = "startup"): boolean {
if (ctx?.hasUI !== true) {
return false;
}
rootSession = true;
updateSessionRef(ctx);
void reportSession(sessionStartSource);
return true;
}
function resetSessionState() {
clearPendingTimers();
clearFailureState();
agentActive = false;
blockedCount = 0;
blockedMessage = undefined;
}
function activateBlocked(message: string | undefined) {
clearPendingTimers();
blockedCount += 1;
blockedMessage = message;
publishState();
}
function deactivateBlocked() {
blockedCount = Math.max(0, blockedCount - 1);
if (blockedCount === 0) {
blockedMessage = undefined;
}
publishState();
}
pi.events.on("herdr:blocked", (data) => {
if (!rootSession) {
return;
}
if (!data?.active) {
deactivateBlocked();
return;
}
activateBlocked(data.label);
});
pi.on("session_start", (_event, ctx) => {
if (!activateRootSession(ctx)) {
return;
}
// A reload can replace this extension mid-run without emitting another agent_start.
agentActive = ctx?.isIdle?.() === false;
publishState(true);
});
pi.on("session_switch", (event, ctx) => {
if (!activateRootSession(ctx, event?.reason || "resume")) {
return;
}
resetSessionState();
publishState(true);
});
pi.on("agent_start", (_event, ctx) => {
if (!rootSession && !activateRootSession(ctx)) {
return;
}
updateSessionRef(ctx);
void reportSession();
clearPendingTimers();
clearFailureState();
agentActive = true;
publishState();
});
pi.on("tool_approval_requested", (event, ctx) => {
if (!rootSession && !activateRootSession(ctx)) {
return;
}
const label = event?.reason || `${event?.toolName || "Tool"} approval`;
activateBlocked(label);
});
pi.on("tool_approval_resolved", (_event, ctx) => {
if (!rootSession && !activateRootSession(ctx)) {
return;
}
deactivateBlocked();
});
pi.on("tool_execution_start", (event, ctx) => {
if (event?.toolName !== "ask") {
return;
}
if (!rootSession && !activateRootSession(ctx)) {
return;
}
activateBlocked(askBlockedMessage(event.args));
});
pi.on("tool_execution_end", (event, ctx) => {
if (event?.toolName !== "ask") {
return;
}
if (!rootSession && !activateRootSession(ctx)) {
return;
}
deactivateBlocked();
});
pi.on("agent_end", (event) => {
if (!rootSession) {
return;
}
if (!agentActive) {
// OMP can emit duplicate/late end events while auto-retry is already
// holding the pane in Working. Do not let an unqualified duplicate end
// cancel the retry hold and publish a false Idle.
return;
}
if (event?.willContinue === true) {
// A continuation is already scheduled, so this end is not a settle.
// Older builds omit the field and fall through as before.
return;
}
agentActive = false;
const retryableMessage = retryableErrorMessage(event);
if (retryableMessage) {
holdForRetry(retryableMessage);
return;
}
scheduleIdle();
});
pi.on("session_shutdown", () => {
if (rootSession) {
clearPendingTimers();
}
});
}
+2
View File
@@ -0,0 +1,2 @@
app.editor.external: Ctrl+E
app.exit: Ctrl+Q
+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json",
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "!printf 'Bearer %s' \"$(gh auth token)\""
}
},
"obscura": {
"type": "stdio",
"command": "obscura",
"args": [
"mcp"
]
}
}
}
+96
View File
@@ -0,0 +1,96 @@
{
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
"name": "cyberdream",
"vars": {
"bg": "#16181a",
"bgAlt": "#1e2124",
"bgHighlight": "#3c4048",
"fg": "#ffffff",
"grey": "#7b8496",
"blue": "#5ea1ff",
"green": "#5eff6c",
"cyan": "#5ef1ff",
"red": "#ff6e5e",
"yellow": "#f1ff5e",
"magenta": "#ff5ef1",
"pink": "#ff5ea0",
"orange": "#ffbd5e",
"purple": "#bd5eff"
},
"colors": {
"accent": "blue",
"border": "blue",
"borderAccent": "cyan",
"borderMuted": "grey",
"success": "green",
"error": "red",
"warning": "yellow",
"muted": "grey",
"dim": "grey",
"text": "",
"thinkingText": "grey",
"selectedBg": "bgHighlight",
"userMessageBg": "bgAlt",
"userMessageText": "fg",
"customMessageBg": "bgAlt",
"customMessageText": "fg",
"customMessageLabel": "cyan",
"toolPendingBg": "bgAlt",
"toolSuccessBg": "bgAlt",
"toolErrorBg": "bgAlt",
"toolTitle": "cyan",
"toolOutput": "fg",
"mdHeading": "orange",
"mdLink": "cyan",
"mdLinkUrl": "blue",
"mdCode": "cyan",
"mdCodeBlock": "fg",
"mdCodeBlockBorder": "grey",
"mdQuote": "grey",
"mdQuoteBorder": "grey",
"mdHr": "grey",
"mdListBullet": "cyan",
"toolDiffAdded": "green",
"toolDiffRemoved": "red",
"toolDiffContext": "grey",
"syntaxComment": "grey",
"syntaxKeyword": "magenta",
"syntaxFunction": "blue",
"syntaxVariable": "yellow",
"syntaxString": "green",
"syntaxNumber": "purple",
"syntaxType": "cyan",
"syntaxOperator": "pink",
"syntaxPunctuation": "grey",
"thinkingOff": "grey",
"thinkingMinimal": "blue",
"thinkingLow": "cyan",
"thinkingMedium": "green",
"thinkingHigh": "yellow",
"thinkingXhigh": "orange",
"thinkingMax": "red",
"bashMode": "yellow",
"pythonMode": "purple",
"statusLineBg": "bgAlt",
"statusLineSep": "grey",
"statusLineModel": "purple",
"statusLinePath": "blue",
"statusLineGitClean": "green",
"statusLineGitDirty": "yellow",
"statusLineContext": "cyan",
"statusLineSpend": "pink",
"statusLineStaged": "green",
"statusLineDirty": "yellow",
"statusLineUntracked": "red",
"statusLineOutput": "fg",
"statusLineCost": "orange",
"statusLineSubagents": "magenta"
}
}
+28
View File
@@ -0,0 +1,28 @@
# Loop 1 — answer-engine gap (green)
# Cadence: Thu 07:00 weekly · Tool: Exa
You are one round of a weekly answer-gap loop: questions developers ask that
WalletBase should be the answer to, but isn't.
## Question universe (rotate through, same check per question)
Seed: "how do I give my AI agent a wallet", "how to sponsor gas for users",
"passkey signing for smart accounts", "how to limit what an AI agent can spend",
"ERC-4337 vs EIP-7702 for agents", "revoke an AI agent's permissions onchain".
Plus: new questions harvested from previous rounds (see state file ## queue).
## Round procedure
1. READ the state file. Find the first question in ## queue not yet processed
(or the next seed question on first runs).
2. For that ONE question: search what currently answers it (docs, blogs, SO,
competitor pages). Log who owns the answer today.
3. Write ONE full content brief under ## queue: exact target question, current
best answer's weakness, outline, code-example requirements, internal
docs/GUIDE.md sections to link. Brief only — a human writes and ships it.
4. Harvest up to 3 NEW adjacent questions discovered during research; append
to ## queue unprocessed.
## Escalation contract
Never escalate. Gap analysis is routine work.
## Stop
ONE question per round, ONE brief. Max 15 fetches. Two failures → ## blocked.
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# run-loop.sh <loop-name> — one round of a loop, cheap-model-first with Fable escalation.
# Green loops only: prompts are read-only + write-to-own-state-file by design.
set -euo pipefail
LOOP="${1:?usage: run-loop.sh <loop-name>}"
DIR="$HOME/.omp/loops/$LOOP"
PROMPT="$DIR/loop.md"
STATE="$DIR/state.md"
LOG="$DIR/runs.log"
[[ -f "$PROMPT" ]] || { echo "no such loop: $LOOP" >&2; exit 1; }
[[ -f "$STATE" ]] || printf '# %s — state\n\n## queue\n\n## done\n\n## blocked\n' "$LOOP" > "$STATE"
# Hard caps (loop #21 escalation policy + the article's stop rules):
# - smol model runs the routine round, 10 min wall clock
# - Fable (default model) only re-runs on an explicit ESCALATE marker, 20 min cap
# - one escalation max per round; never more than one round per invocation
SMOL_MAX=600
FABLE_MAX=1200
ts() { date -Is; }
run() { # run <model-flag...> — returns the model's stdout
omp -p --no-session --max-time "$2" ${1:+--model "$1"} \
"$(cat "$PROMPT")
Loop directory: $DIR
State file (read it first, append to it, never rewrite history): $STATE
Round timestamp: $(ts)"
}
echo "[$(ts)] round start (smol)" >> "$LOG"
OUT="$(run "kimi-for-coding" "$SMOL_MAX" 2>>"$LOG" || true)"
printf '%s\n' "$OUT" >> "$LOG"
if grep -q '^ESCALATE' <<<"$OUT"; then
echo "[$(ts)] escalating to default model" >> "$LOG"
OUT="$(run "" "$FABLE_MAX" 2>>"$LOG" || true)"
printf '%s\n' "$OUT" >> "$LOG"
# Escalation ledger (loop #21): every Fable call must be visible and countable
echo "[$(ts)] $LOOP escalated" >> "$HOME/.omp/loops/escalations.log"
fi
echo "[$(ts)] round end" >> "$LOG"
+31
View File
@@ -0,0 +1,31 @@
# Loop 10 — brand-mention feature radar (green)
# Cadence: Wed 07:00 weekly · Tools: HN Algolia API (no key), Exa
You are one round of a weekly mention-sweep for WalletBase and its problem
space. WalletBase itself is early — so sweep the PROBLEM space, not just the
brand.
## Queries (same check every round)
- HN Algolia (https://hn.algolia.com/api/v1/search_by_date?query=...):
"agent wallet", "agent payments", "account abstraction", "ERC-4337",
"passkey wallet", "embedded wallet"
- Exa: complaints/wishes about Privy, Dynamic, Turnkey, Web3Auth pricing or
agent support ("privy pricing", "alternatives to privy", etc.)
- Exa: "walletbase" brand mentions (expect ~0; log the count anyway — trend matters)
## Round procedure
1. READ the state file. Skip item IDs/URLs already logged.
2. Sweep last 7 days. Cluster new items into: feature asks · pricing pain ·
security concerns · integration friction.
3. Log clusters with counts and 1–2 representative links each.
4. ONE change per round: take the loudest cluster and write ONE drafted
response under ## queue — either a feature-issue draft (formatted so it can
be pasted into `bd create` in the right wltbs repo) or a positioning note.
Draft only. Never create the issue yourself.
## Escalation contract
`ESCALATE:` only for a security-relevant discussion of a dependency WalletBase
uses (ERC-4337 EntryPoint, passkey/WebAuthn, UCAN libs).
## Stop
One sweep, one draft, stop. Max 30 API calls. Two failures → ## blocked, stop.
+26
View File
@@ -0,0 +1,26 @@
# Loop 4 — competitor-content watch (green)
# Cadence: Fri 07:00 weekly · Tool: Exa web search
You are one round of a weekly competitor-content loop for WalletBase.
## Watched competitors (same check every round)
- Privy (privy.io/blog), Dynamic (dynamic.xyz/blog), Turnkey (turnkey.com/blog)
- Crossmint (blog.crossmint.com), Para (getpara.com/blog), Pimlico (pimlico.io/blog)
## Round procedure
1. READ the state file. Skip URLs already logged.
2. Find posts published in the last 7 days from each competitor.
3. For each new post log: URL, title, the keyword/theme it chases, and which
audience (human-wallet devs vs agent devs).
4. ONE change per round: identify the single biggest content gap — a theme
competitors are pushing that WalletBase has no answer to — and write ONE
content brief for it under ## queue (title, angle, target query, outline,
why-us). One brief, not a list.
## Escalation contract
`ESCALATE:` only if a competitor announces an agent-economy product (agent
wallets, delegation, agent payments) — that reprioritizes the whole queue.
## Stop
Six competitors, one pass, one brief, stop. Max 20 page fetches. Two
consecutive tool failures → log under ## blocked, stop.
+36
View File
@@ -0,0 +1,36 @@
# /goal templates — kill-criteria (#22) and pre-mortem (#23)
Not scheduled. Paste into an interactive omp session when facing a decision.
Both end with pasted proof per the goal contract: the judge only sees the
conversation, so "done" = the artifact is IN the chat.
---
## kill-criteria (#22)
/goal For the decision "[DECISION]", produce a kill-criteria table for every
live option. For each option: (1) state the option in one line, (2) write the
SINGLE measurable condition that would disqualify it — a number, a date, or a
named event, not vibes, (3) hunt for evidence for/against that condition using
web search and the repos under ~/Developer/github.com/wltbs, citing every
claim, (4) verdict: ALIVE / KILLED / NEEDS-DATA with what data and where to
get it. Done when the full table is pasted in this chat with a citation per
verdict, and every KILLED verdict quotes the evidence that tripped its
condition. If evidence can't be found for an option, paste what you searched
and mark it NEEDS-DATA — do not guess. Hard cap: 40 tool calls.
---
## pre-mortem (#23)
/goal It is 12 months from now and the decision "[DECISION]" failed badly.
Write the post-mortem as if it already happened: (1) the headline of the
failure, (2) the 3–5 causal chains that led there, each traced back to a
specific assumption we hold TODAY (quote the assumption from our notes/repos
where possible), (3) for each chain, the earliest observable warning sign and
where it would show up first (a metric, a log, a competitor move, a regulator
page), (4) the one cheapest mitigation per chain that we could start this
week. Done when the full pre-mortem is pasted in this chat and every causal
chain names its present-day assumption explicitly. No generic risks ("market
turns") — every chain must be specific to WalletBase's architecture, GTM, or
regulatory surface. Hard cap: 30 tool calls.
+31
View File
@@ -0,0 +1,31 @@
# Loop 19 — overnight intel refresh (green)
# Cadence: Mon 06:00 weekly · Tool: Exa web search
You are one round of a weekly intel loop for WalletBase — embedded wallet
infrastructure for the agent economy (ERC-4337 smart accounts, passkeys,
UCAN delegation, in-house bundler/paymaster on Base).
## Tracked beats (same check every round)
1. ERC-4337 / account-abstraction ecosystem (EntryPoint, bundlers: Rundler, Alchemy, Pimlico pricing/changes)
2. EIP-7702 adoption and wallet implementations
3. Embedded-wallet competitors: Privy (Stripe), Dynamic (Fireblocks), Turnkey, Crossmint, Para
4. Agent-payments protocols: x402, UCAN ecosystem, agent wallet SDKs
5. Base chain: fees, precompiles (RIP-7212), infra changes
## Round procedure
1. READ the state file first. Skip anything already logged in ## done.
2. Search each beat for items from the last 7 days ONLY.
3. Write at most 5 bullets per beat to the state file under a `## week of <date>`
heading: link, one-line summary, one-line "so what for WalletBase".
4. ONE change per round: if a beat has produced nothing for 3 consecutive weeks,
propose swapping it (write the proposal under ## queue — do NOT swap it yourself).
## Escalation contract
If you find a development that plausibly invalidates a core assumption
(bundler economics, a competitor shipping agent-native delegation, an
EntryPoint breaking change), output a line starting with `ESCALATE:` and the
reason. Otherwise never escalate.
## Stop
One pass over the 5 beats, then stop. If a search tool fails twice, log it
under ## blocked and stop. Never fetch more than 25 pages total.
+31
View File
@@ -0,0 +1,31 @@
# Loop 20 — regulatory / source digest (green)
# Cadence: Tue 07:00 weekly · Tool: Exa
# Maps to the WalletBase risk register: MSB/FinCEN, OFAC, MiCA/CASP, MEV policy.
You are one round of a weekly regulatory watch for WalletBase (non-custodial
paymaster, bundler relay, USDC agent payments, US-based, EU geo-blocked).
## Watched sources (same check every round)
1. FinCEN — guidance/rulings touching money transmission, self-hosted wallets, MSB scope
2. OFAC — SDN designations or guidance touching crypto relays/mixers/bundler-like infra
3. SEC/CFTC — enforcement touching wallet infra or AA providers
4. EU — MiCA/CASP implementation news relevant to non-custodial infra
5. Ethereum protocol governance — ERC-4337/EIP-7702 spec changes, EntryPoint audits
## Round procedure
1. READ the state file. Skip items already logged.
2. Search last 7 days per source. ONLY log items that MATERIALLY change
obligations or assumptions — silence is the correct output on a quiet week.
3. Per material item: link, what changed, plain-language "so what" for the
paymaster/bundler architecture, which risk note it touches.
4. ONE change per round: if an item invalidates a standing assumption, write
ONE proposed edit to the relevant risk position under ## queue.
## Escalation contract
`ESCALATE:` if anything suggests non-custodial paymasters/bundler relays fall
under MSB registration, or an OFAC action names infra architecturally similar
to ours. These are existential — worth Fable's read.
## Stop
Five sources, one pass, stop. A quiet week produces a one-line "no material
changes <date>" entry — that is a SUCCESS, not a failure. Max 20 fetches.
+32
View File
@@ -0,0 +1,32 @@
# Loop 24 — repeat-offender digest (green)
# Cadence: Sun 18:00 weekly · Tools: beads (bd) in ~/Developer/github.com/wltbs/*, omp logs
You are one round of a weekly failure-pattern hunt across the WalletBase
workspace. The same failure in two workflows is one system problem wearing
two costumes.
## Sources (same check every round)
1. `bd list --all --json` in each of: ~/Developer/github.com/wltbs/{mono,backend,contracts,core,website}
— look at issues created or closed in the last 7 days, especially bugs/chores.
2. ~/.omp/loops/*/runs.log and ~/.omp/loops/*/state.md ## blocked sections
— the loop harness's own failures.
3. ~/.omp/loops/escalations.log — is any loop escalating too often?
## Round procedure
1. READ the state file. Note previously identified patterns.
2. Sweep the sources. Cluster failures by ROOT shape, not surface symptom
(e.g. "workspace dep not resolvable" appearing as three different bd issues
across repos = one pattern).
3. Log new/recurring patterns: name, occurrences (issue IDs / log lines), trend.
4. ONE change per round: for the most repeated pattern, draft ONE systemic fix
proposal under ## queue — formatted as a `bd create` command the human can
paste, targeting the repo where the root cause lives.
NEVER run bd create yourself — draft only; a human files it.
## Escalation contract
`ESCALATE:` only if a pattern implicates data loss or a security boundary
(key handling, paymaster balance accounting, sync corruption).
## Stop
One sweep, one proposal, stop. Read-only on beads (list/show only). Two
failures → ## blocked, stop.
+28
View File
@@ -0,0 +1,28 @@
# Loop 3 — share-of-model watch (green)
# Cadence: Mon 07:00 weekly · Tool: Exa (answer mode)
You are one round of a weekly share-of-model tracker: when buyers ask AI
engines about our category, who is in the answer?
## Fixed question set (NEVER reword — comparability is the point)
1. "best embedded wallet SDK for developers"
2. "best wallet infrastructure for AI agents"
3. "how to give an AI agent a crypto wallet"
4. "Privy alternatives"
5. "ERC-4337 bundler and paymaster providers"
6. "passkey wallet SDK"
## Round procedure
1. READ the state file for last week's table.
2. Ask each question via Exa answer/search. Record: which vendors are named,
in what order, and whether WalletBase appears.
3. Append this week's table to the state file:
`| # | vendors in answer (ordered) | walletbase? |`
4. ONE change per round: note the single biggest rank shift vs last week under
## queue with a one-line hypothesis why.
## Escalation contract
Never escalate. This loop only measures.
## Stop
Six questions, one table, stop. If Exa fails twice → ## blocked, stop.
+13
View File
@@ -0,0 +1,13 @@
{
"version": 1,
"marketplaces": [
{
"name": "ponytail",
"sourceType": "github",
"sourceUri": "DietrichGebert/ponytail",
"catalogPath": "/home/prad/.omp/plugins/cache/marketplaces/ponytail/marketplace.json",
"addedAt": "2026-10-06T16:12:52.335Z",
"updatedAt": "2026-10-16T14:51:17.380Z"
}
]
}
+15
View File
@@ -0,0 +1,15 @@
{
"lockfileVersion": 2,
"configVersion": 1,
"workspaces": {
"": {
"name": "omp-plugins",
"dependencies": {
"omp.nvim": "^2.0.0",
},
},
},
"packages": {
"omp.nvim": ["omp.nvim@2.0.0", "", {}, "sha512-7ARJF2Md0VU3uyk9SNwHcO6n/J8Qsl1Gi8yfH9eLtPTKMZQA5KdZbUMP2/RN8Ab6FZyxucWQrxOdh+G8Y2JLQA=="],
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"version": 2,
"plugins": {
"ponytail@ponytail": [
{
"scope": "user",
"installPath": "/home/prad/.omp/plugins/cache/plugins/ponytail___ponytail___4.13.0",
"version": "4.13.0",
"installedAt": "2026-10-06T16:12:52.700Z",
"lastUpdated": "2026-10-06T16:12:52.700Z"
}
]
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"plugins": {
"omp.nvim": {
"version": "2.0.0",
"enabledFeatures": null,
"enabled": true
},
"@dietrichgebert/ponytail": {
"version": "4.13.0",
"enabledFeatures": null,
"enabled": true
}
},
"settings": {}
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "omp-plugins",
"private": true,
"dependencies": {
"omp.nvim": "^2.0.0"
}
}