ypi 0.4.0 → 0.5.1

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/CHANGELOG.md CHANGED
@@ -3,6 +3,31 @@
3
3
  All notable changes to ypi are documented here.
4
4
  Format based on [Keep a Changelog](https://keepachangelog.com/).
5
5
 
6
+ ## [0.5.1] - 2026-03-23
7
+
8
+ ### Fixed
9
+ - **macOS mktemp compatibility**: BSD `mktemp` does not allow characters after the `XXXXXX` template suffix — moved `XXXXXX` to end of all templates and use `${TMPDIR:-/tmp}` for portable temp directory resolution
10
+ - **Bash 3.2 unbound variable crash**: empty array expansion under `set -u` fails on macOS default bash — build argv incrementally with length checks in `ypi` launcher
11
+
12
+ ## [0.5.0] - 2026-02-15
13
+
14
+ ### Added
15
+ - **Notify-done extension** (`contrib/extensions/notify-done.ts`): background task completion notifications via sentinel files — injects messages into conversation when tasks finish, no polling needed
16
+ - **LSP extension** (`contrib/extensions/lsp/`): Language Server Protocol integration for code intelligence (diagnostics, references, definitions, rename, hover, symbols)
17
+ - **Persist-system-prompt extension** (`contrib/extensions/persist-system-prompt.ts`): saves effective system prompt to session files for debugging and reproducibility
18
+ - **Auto-title extension** (`contrib/extensions/auto-title.ts`): automatic session title generation
19
+ - **Cachebro extension** (`contrib/extensions/cachebro.ts`): intelligent file caching with diff-aware invalidation and token estimation
20
+ - **Context window awareness**: SYSTEM_PROMPT.md now teaches agents about finite context budgets and how to manage them
21
+ - Tests for notify-done and persist-system-prompt extensions
22
+
23
+ ### Changed
24
+ - **AGENTS.md**: added sentinel/notify-done workflow pattern, background task instructions
25
+ - **SYSTEM_PROMPT.md**: context window awareness guidance
26
+ - **contrib/README.md**: updated with new extensions documentation
27
+
28
+ ### Fixed
29
+ - Notify-done extension: block broadcast sentinels, use `steer` for busy agents, `display: true` for visibility
30
+
6
31
  ## [0.4.0] - 2026-02-13
7
32
 
8
33
  ### Added
package/README.md CHANGED
@@ -173,13 +173,32 @@ jj git push # Push to GitHub
173
173
  ### Testing
174
174
 
175
175
  ```bash
176
- make test-fast # 54 tests, no LLM calls, seconds
177
- make test-e2e # Real LLM calls, costs ~$0.05
178
- make test # Both
176
+ make test-fast # unit + guardrails
177
+ make test-extensions # extension compatibility with installed pi
178
+ make pre-push-checks # shared local/CI gate (recommended before push)
179
+ make test-e2e # real LLM calls, costs money
180
+ make test # all of the above
181
+ ```
182
+
183
+ Install hooks once per clone to run checks automatically on git push:
184
+ ```bash
185
+ make install-hooks
186
+ ```
187
+
188
+ Release/update helper:
189
+ ```bash
190
+ make release-preflight # same checks + upstream dry-run in one command
191
+ make land # deterministic-ish landing helper
179
192
  ```
180
193
 
181
194
  **Before any change to `rlm_query`:** run `make test-fast`. After: run it again. `rlm_query` is a live dependency of the agent's own execution — breaking it breaks the agent.
182
195
 
196
+ CI helper commands:
197
+ ```bash
198
+ make ci-status N=15 # recent workflow runs
199
+ make ci-last-failure # dump latest failing workflow log
200
+ ```
201
+
183
202
 
184
203
  ### History
185
204
 
package/SYSTEM_PROMPT.md CHANGED
@@ -4,11 +4,13 @@
4
4
  - You are a **recursive LLM** equipped with a Bash shell and the `rlm_query` tool.
5
5
  - The environment variable `RLM_DEPTH` tells you your current recursion depth; respect `RLM_MAX_DEPTH` and be more **conservative** (fewer sub‑calls, more direct actions) the deeper you are.
6
6
  - You can **read files, write files, run commands, and delegate work** to sub‑agents via `rlm_query`.
7
- - Sub‑agents inherit the same capabilities and receive their own isolated context.
7
+ - Sub‑agents inherit the same capabilities and receive their own **fresh context window**.
8
8
  - All actions should aim to be **deterministic and reproducible**.
9
+ - **Your context window is finite and non-renewable.** Every file you read, every tool output you receive, every message in this conversation — it all accumulates. When it fills up, older context gets compressed and you lose information. This is the fundamental constraint that shapes how you work.
9
10
 
10
11
  ## SECTION 2 – Recursive Decomposition
11
12
  You solve problems by **decomposing them**: break big tasks into smaller ones, delegate to sub‑agents, combine results. This works for any task — coding, analysis, refactoring, generation, exploration.
13
+ **Why recurse?** Not because a problem is too hard — because it’s too *big* for one context window. A 10-file refactor doesn’t need more intelligence; it needs more context windows. Each child agent you spawn via `rlm_query` gets a fresh context budget. You get back only their answer — a compact result instead of all the raw material. This is how you stay effective on long tasks.
12
14
 
13
15
  Your original prompt is also available as a file at `$RLM_PROMPT_FILE` — use it when you need to manipulate the question programmatically (e.g., extracting exact strings, counting characters) rather than copying tokens from memory.
14
16
 
@@ -17,13 +19,15 @@ If a `$CONTEXT` file is set, it contains data relevant to your task. Treat it li
17
19
  **Core pattern: size up → search → delegate → combine**
18
20
  1. **Size up the problem** – How big is it? Can you do it directly, or does it need decomposition? For files: `wc -l` / `wc -c`. For code tasks: how many files, how complex?
19
21
  2. **Search & explore** – `grep`, `find`, `ls`, `head` — orient yourself before diving in.
20
- 3. **Delegate** – use `rlm_query` to hand sub‑tasks to child agents. Two patterns:
22
+ 3. **Delegate** – use `rlm_query` to hand sub‑tasks to child agents. Three patterns:
21
23
  ```bash
22
- # Pipe data as the child's context
24
+ # Pipe data as the child's context (synchronous — blocks until done)
23
25
  sed -n '100,200p' bigfile.txt | rlm_query "Summarize this section"
24
-
25
- # Child inherits your environment (files, cwd, $CONTEXT)
26
+ # Child inherits your environment (synchronous)
26
27
  rlm_query "Refactor the error handling in src/api.py"
28
+ # ASYNC — returns immediately, child runs in background (PREFERRED for parallel work)
29
+ rlm_query --async "Write tests for the auth module"
30
+ # Returns: {"job_id": "...", "output": "/tmp/...", "sentinel": "/tmp/...done", "pid": 12345}
27
31
  ```
28
32
  4. **Combine** – aggregate results, deduplicate, resolve conflicts, produce the final output.
29
33
  5. **Do it directly when it's small** – don't delegate what you can do in one step.
@@ -42,11 +46,11 @@ cat src/config.py
42
46
  ```bash
43
47
  # Find all files that need updating
44
48
  grep -rl "old_api_call" src/
45
-
46
- # Delegate each file to a sub-agent (each gets its own jj workspace)
49
+ # Delegate each file to a sub-agent using --async (non-blocking)
47
50
  for f in $(grep -rl "old_api_call" src/); do
48
- rlm_query "In $f, replace all old_api_call() with new_api_call(). Update the imports too."
49
- done
51
+ rlm_query --async "In $f, replace all old_api_call() with new_api_call(). Update the imports. Then jj commit -m 'refactor: $f'"
52
+ done
53
+ # Children run in parallel, each in its own jj workspace. Check sentinels for completion.
50
54
  ```
51
55
 
52
56
  **Example 3 – Large file analysis, chunk and search**
@@ -59,17 +63,27 @@ grep -n "ERROR\|FATAL" data/logs.txt
59
63
  sed -n '480,600p' data/logs.txt | rlm_query "What caused this error? Suggest a fix."
60
64
  ```
61
65
 
62
- **Example 4 – Parallel sub-tasks with different goals**
66
+ **Example 4 – Parallel sub-tasks with --async (PREFERRED)**
63
67
  ```bash
64
- # Break a complex task into independent pieces
65
- SUMMARY=$(rlm_query "Read README.md and summarize what this project does in one paragraph.")
66
- ISSUES=$(rlm_query "Run the test suite and report any failures.")
67
- DEPS=$(rlm_query "Check for outdated dependencies in package.json.")
68
-
69
- # Combine into a report
70
- echo "Summary: $SUMMARY"
71
- echo "Test issues: $ISSUES"
72
- echo "Dependency status: $DEPS"
68
+ # Break a complex task into independent pieces — all run in parallel
69
+ JOB1=$(rlm_query --async "Read README.md and summarize what this project does in one paragraph.")
70
+ JOB2=$(rlm_query --async "Run the test suite and report any failures.")
71
+ JOB3=$(rlm_query --async "Check for outdated dependencies in package.json.")
72
+
73
+ # Each returns immediately with {"job_id", "output", "sentinel", "pid"}
74
+ # Check completion non-blockingly:
75
+ for JOB in "$JOB1" "$JOB2" "$JOB3"; do
76
+ SENTINEL=$(echo "$JOB" | python3 -c "import sys,json; print(json.load(sys.stdin)['sentinel'])")
77
+ OUTPUT=$(echo "$JOB" | python3 -c "import sys,json; print(json.load(sys.stdin)['output'])")
78
+ [ -f "$SENTINEL" ] && echo "Done: $(cat $OUTPUT)" || echo "Still running..."
79
+ done
80
+ ```
81
+
82
+ **Example 5 – Sequential sub-tasks (when order matters)**
83
+ ```bash
84
+ # Use synchronous rlm_query ONLY when each step depends on the previous
85
+ SUMMARY=$(rlm_query "Read README.md and summarize what this project does.")
86
+ ISSUES=$(rlm_query "Given this summary: $SUMMARY — what are the main risks?")
73
87
  ```
74
88
 
75
89
  **Example 5 – Iterative chunking over a huge file**
@@ -95,6 +109,7 @@ done
95
109
  - **Write files directly** with `write` or standard Bash redirection; do **not** merely describe the change.
96
110
  - When you need to create or modify multiple files, perform each action explicitly (e.g., `echo >> file`, `sed -i`, `cat > newfile`).
97
111
  - Any sub‑agents you spawn via `rlm_query` inherit their own jj workspaces, so their edits are also isolated.
112
+ - **Always commit before exiting** — if you're in a jj workspace, run `jj commit -m 'description'` before you finish. Uncommitted work is **lost** when the workspace is forgotten on exit.
98
113
 
99
114
  ## SECTION 4 – Guardrails & Cost Awareness
100
115
  - **RLM_TIMEOUT** – if set, respect the remaining wall‑clock budget; avoid long‑running loops.
@@ -113,15 +128,24 @@ done
113
128
  rlm_sessions grep <pattern> # search across sessions
114
129
  ```
115
130
  Available for debugging and reviewing what other agents in the tree have done.
131
+ - **`rlm_cleanup`** – clean up stale temp files and jj workspaces from previous rlm_query runs:
132
+ ```bash
133
+ rlm_cleanup # dry-run: show what would be cleaned
134
+ rlm_cleanup --force # actually delete stale files and workspace dirs
135
+ rlm_cleanup --age 60 # override age threshold (default: 120 min)
136
+ ```
137
+ Run this when the machine feels slow or /tmp is filling up. The reaper in `rlm_query` runs automatically at depth 0, but this lets you trigger it manually or with a different age threshold.
116
138
  - **Depth awareness** – at deeper `RLM_DEPTH` levels, prefer **direct actions** (e.g., file edits, single‑pass searches) over spawning many sub‑agents.
117
139
  - Always **clean up temporary files** and respect `trap` handlers defined by the infrastructure.
140
+ - **NEVER run `rlm_query` in a foreground for-loop** — this blocks the parent's conversation for the entire duration. Use `rlm_query --async` for parallel work. Synchronous `rlm_query` is only for single calls or when you need the result immediately for the next step.
118
141
 
119
142
  ## SECTION 5 – Rules
120
- 1. **Size up first** – before delegating, check if the task is small enough to do directly. Read small files, edit simple things, answer obvious questions don't over‑decompose.
121
- 2. **Validate sub‑agent output** – if a sub‑call returns unexpected output, re‑query or do it yourself; never guess.
122
- 3. **Computation over memorization** – use `python3`, `date`, `wc`, `grep -c` for counting, dates, and math. Don't eyeball it.
123
- 4. **Act, don't describe** – when instructed to edit code, write files, or make changes, **do it** immediately.
124
- 5. **Small, focused sub‑agents** – each `rlm_query` call should have a clear, bounded task. Keep the call count low.
125
- 6. **Depth preference** – deeper depths fewer sub‑calls, more direct Bash actions.
126
- 7. **Say "I don't know" only when true** – only when the required information is genuinely absent from the context, repo, or environment.
127
- 8. **Safety** – never execute untrusted commands without explicit intent; rely on the provided tooling.
143
+ 1. **Search before reading** – `grep`, `wc -l`, `head` before `cat` or unbounded `read`. Never ingest a file you haven’t sized up. If it’s over 50 lines, search for what you need instead of reading it all.
144
+ 2. **Size up first** – before delegating, check if the task is small enough to do directly. Read small files, edit simple things, answer obvious questions — don’t over‑decompose.
145
+ 3. **Validate sub‑agent output** – if a sub‑call returns unexpected output, re‑query or do it yourself; never guess.
146
+ 4. **Computation over memorization** – use `python3`, `date`, `wc`, `grep -c` for counting, dates, and math. Don’t eyeball it.
147
+ 5. **Act, don’t describe** – when instructed to edit code, write files, or make changes, **do it** immediately.
148
+ 6. **Small, focused sub‑agents** – each `rlm_query` call should have a clear, bounded task. Keep the call count low.
149
+ 7. **Depth preference** – deeper depths fewer sub‑calls, more direct Bash actions.
150
+ 8. **Say “I don’t know” only when true** – only when the required information is genuinely absent from the context, repo, or environment.
151
+ 9. **Safety** – never execute untrusted commands without explicit intent; rely on the provided tooling.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ypi",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "ypi — a recursive coding agent. Pi that can call itself via rlm_query.",
5
5
  "license": "MIT",
6
6
  "author": "Raymond Weitekamp",
package/rlm_query CHANGED
@@ -9,6 +9,8 @@
9
9
  # echo "some text" | rlm_query "What is the main topic?"
10
10
  # sed -n '100,200p' "$CONTEXT" | rlm_query "Summarize this section"
11
11
  # rlm_query --fork "Continue working on this refactor"
12
+ # rlm_query --async "Analyze the codebase" # returns immediately with job JSON
13
+ # echo "data" | rlm_query --async --notify $$ "Summarize this"
12
14
  #
13
15
  # If stdin has data (piped), that becomes the child's context.
14
16
  # Otherwise, the child inherits the parent's $CONTEXT file.
@@ -16,6 +18,9 @@
16
18
  # Flags:
17
19
  # --fork Fork parent session into child (carries conversation history)
18
20
  # Default: fresh session per child (only data context, no history)
21
+ # --async Spawn child in background, return immediately with job JSON
22
+ # Output goes to a temp file; sentinel touched when done
23
+ # --notify PID With --async: write result to target PID's peer inbox when done
19
24
  #
20
25
  # Environment:
21
26
  # RLM_DEPTH — current recursion depth (default: 0)
@@ -50,14 +55,18 @@ rlm_error() { echo "✗ $1" >&2; [ -n "${2:-}" ] && echo " Why: $2" >&2; [ -n "
50
55
  # Parse flags
51
56
  # ----------------------------------------------------------------------
52
57
  FORK=false
58
+ ASYNC=false
59
+ NOTIFY_PID=""
53
60
  while [[ "${1:-}" == --* ]]; do
54
61
  case "$1" in
55
62
  --fork) FORK=true; shift ;;
63
+ --async) ASYNC=true; shift ;;
64
+ --notify) NOTIFY_PID="$2"; shift 2 ;;
56
65
  *) break ;;
57
66
  esac
58
67
  done
59
68
 
60
- PROMPT="${1:?Usage: rlm_query [--fork] \"your prompt here\"}"
69
+ PROMPT="${1:?Usage: rlm_query [--fork] [--async] [--notify PID] \"your prompt here\"}"
61
70
 
62
71
  # ----------------------------------------------------------------------
63
72
  # Depth guard — refuse to go beyond max depth
@@ -72,6 +81,19 @@ if [ "$NEXT_DEPTH" -gt "$MAX_DEPTH" ]; then
72
81
  exit 1
73
82
  fi
74
83
 
84
+ # ----------------------------------------------------------------------
85
+ # Stale temp reaper — runs once per root invocation (depth 0 only)
86
+ # Cleans up leaked files from crashed/killed processes (SIGKILL skips traps)
87
+ # ----------------------------------------------------------------------
88
+ if [ "$DEPTH" -eq 0 ]; then
89
+ (
90
+ # Delete rlm temp files older than 2 hours whose parent process is gone
91
+ find /tmp -maxdepth 1 -name 'rlm_*' -mmin +120 -delete 2>/dev/null
92
+ # Remove stale jj workspace directories older than 2 hours
93
+ find /tmp -maxdepth 1 -name 'rlm_ws_*' -type d -mmin +120 -exec rm -rf {} + 2>/dev/null
94
+ ) &
95
+ fi
96
+
75
97
  PROVIDER="${RLM_PROVIDER:-}"
76
98
  MODEL="${RLM_MODEL:-}"
77
99
  SYSTEM_PROMPT_FILE="${RLM_SYSTEM_PROMPT:-}"
@@ -117,7 +139,7 @@ fi
117
139
 
118
140
  # Initialize cost file if budget is set but no file exists yet
119
141
  if [ -n "${RLM_BUDGET:-}" ] && [ -z "${RLM_COST_FILE:-}" ]; then
120
- export RLM_COST_FILE=$(mktemp /tmp/rlm_cost_XXXXXX.jsonl)
142
+ export RLM_COST_FILE=$(mktemp "${TMPDIR:-/tmp}/rlm_cost.XXXXXX")
121
143
  fi
122
144
 
123
145
  # ----------------------------------------------------------------------
@@ -149,12 +171,12 @@ fi
149
171
  # ----------------------------------------------------------------------
150
172
  # Temporary child context file
151
173
  # ----------------------------------------------------------------------
152
- CHILD_CONTEXT=$(mktemp /tmp/rlm_ctx_d${NEXT_DEPTH}_XXXXXX.txt)
174
+ CHILD_CONTEXT=$(mktemp "${TMPDIR:-/tmp}/rlm_ctx_d${NEXT_DEPTH}.XXXXXX")
153
175
  COMBINED_PROMPT=""
154
176
 
155
177
  # Write prompt to a file for symbolic access — agents can grep/sed the original
156
178
  # question instead of relying on in-context token copying.
157
- PROMPT_FILE=$(mktemp /tmp/rlm_prompt_d${NEXT_DEPTH}_XXXXXX.txt)
179
+ PROMPT_FILE=$(mktemp "${TMPDIR:-/tmp}/rlm_prompt_d${NEXT_DEPTH}.XXXXXX")
158
180
  echo "$PROMPT" > "$PROMPT_FILE"
159
181
 
160
182
  # ----------------------------------------------------------------------
@@ -166,7 +188,7 @@ if [ "${RLM_JJ:-1}" != "0" ] \
166
188
  && command -v jj &>/dev/null \
167
189
  && jj root &>/dev/null 2>&1; then
168
190
  JJ_WS_NAME="rlm-d${NEXT_DEPTH}-$$"
169
- JJ_WORKSPACE=$(mktemp -d /tmp/rlm_ws_d${NEXT_DEPTH}_XXXXXX)
191
+ JJ_WORKSPACE=$(mktemp -d "${TMPDIR:-/tmp}/rlm_ws_d${NEXT_DEPTH}.XXXXXX")
170
192
  if ! jj workspace add --name "$JJ_WS_NAME" "$JJ_WORKSPACE" &>/dev/null; then
171
193
  JJ_WORKSPACE=""
172
194
  JJ_WS_NAME=""
@@ -174,6 +196,8 @@ if [ "${RLM_JJ:-1}" != "0" ] \
174
196
  fi
175
197
 
176
198
  # Cleanup: remove temp context + forget jj workspace (updated in run section below)
199
+ # In async mode, the child needs these files — skip cleanup (child cleans up after itself)
200
+ if [ "$ASYNC" != true ]; then
177
201
  trap '{
178
202
  rm -f "$CHILD_CONTEXT" "$PROMPT_FILE"
179
203
  rm -f "${COMBINED_PROMPT:-}"
@@ -181,20 +205,28 @@ trap '{
181
205
  jj workspace forget "$JJ_WS_NAME" 2>/dev/null || true
182
206
  fi
183
207
  }' EXIT
208
+ fi
184
209
  trap 'rlm_error "Interrupted" "Received signal" "Re-run the command"; exit 130' INT TERM
185
210
 
186
211
  # ----------------------------------------------------------------------
187
212
  # Detect piped stdin
188
213
  # ----------------------------------------------------------------------
189
214
  HAS_STDIN=false
190
- if [ -p /dev/stdin ]; then
215
+ if [ -n "${RLM_STDIN:-}" ]; then
191
216
  HAS_STDIN=true
192
- elif [ -n "${RLM_STDIN:-}" ]; then
217
+ elif [ -p /dev/stdin ]; then
193
218
  HAS_STDIN=true
194
219
  fi
195
220
 
196
221
  if [ "$HAS_STDIN" = true ]; then
197
222
  cat > "$CHILD_CONTEXT"
223
+
224
+ # Some CI runners expose stdin as an empty pipe even when nothing was piped
225
+ # to rlm_query. In that case, preserve inherited CONTEXT unless stdin was
226
+ # explicitly signaled via RLM_STDIN.
227
+ if [ ! -s "$CHILD_CONTEXT" ] && [ -z "${RLM_STDIN:-}" ] && [ -n "${CONTEXT:-}" ] && [ -f "${CONTEXT:-}" ]; then
228
+ cp "$CONTEXT" "$CHILD_CONTEXT"
229
+ fi
198
230
  else
199
231
  if [ -n "${CONTEXT:-}" ] && [ -f "${CONTEXT:-}" ]; then
200
232
  cp "$CONTEXT" "$CHILD_CONTEXT"
@@ -265,7 +297,7 @@ fi
265
297
  # Build combined system prompt with rlm_query source embedded
266
298
  COMBINED_PROMPT=""
267
299
  if [ -n "$SYSTEM_PROMPT_FILE" ] && [ -f "$SYSTEM_PROMPT_FILE" ]; then
268
- COMBINED_PROMPT=$(mktemp /tmp/rlm_system_prompt_XXXXXX.md)
300
+ COMBINED_PROMPT=$(mktemp "${TMPDIR:-/tmp}/rlm_system_prompt.XXXXXX")
269
301
  cat "$SYSTEM_PROMPT_FILE" > "$COMBINED_PROMPT"
270
302
  SELF_SOURCE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)/rlm_query"
271
303
  if [ -f "$SELF_SOURCE" ]; then
@@ -301,11 +333,63 @@ if [ -n "$JJ_WORKSPACE" ]; then
301
333
  cd "$JJ_WORKSPACE"
302
334
  fi
303
335
 
336
+ # ----------------------------------------------------------------------
337
+ # Async mode — spawn child in background and return immediately
338
+ # ----------------------------------------------------------------------
339
+ if [ "$ASYNC" = true ]; then
340
+ ASYNC_ID="rlm_async_${RLM_TRACE_ID}_c${RLM_CALL_COUNT}_$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n')"
341
+ ASYNC_OUTPUT="/tmp/${ASYNC_ID}.txt"
342
+ ASYNC_SENTINEL="/tmp/${ASYNC_ID}.done"
343
+
344
+ # Build the full command
345
+ CHILD_CMD=(pi "${CMD_ARGS[@]}" "$PROMPT")
346
+ if [ -n "$TIMEOUT_CMD" ]; then
347
+ CHILD_CMD=($TIMEOUT_CMD "${CHILD_CMD[@]}")
348
+ fi
349
+
350
+ # Spawn in background: run child, capture output, touch sentinel, optionally notify
351
+ (
352
+ "${CHILD_CMD[@]}" > "$ASYNC_OUTPUT" 2>&1
353
+ touch "$ASYNC_SENTINEL"
354
+
355
+ # If --notify PID was given, write to that peer's inbox
356
+ if [ -n "$NOTIFY_PID" ]; then
357
+ INBOX_DIR=$(find /tmp/pi_peer_* -maxdepth 0 -type d 2>/dev/null | while read d; do
358
+ if [ -f "$d/meta.json" ] && grep -q "\"pid\":$NOTIFY_PID" "$d/meta.json" 2>/dev/null; then
359
+ echo "$d"; break
360
+ fi
361
+ done)
362
+ if [ -n "$INBOX_DIR" ] && [ -d "$INBOX_DIR" ]; then
363
+ RESULT=$(cat "$ASYNC_OUTPUT" | tail -c 50000)
364
+ MSG=$(cat <<MSGJSON
365
+ {"from_pid":$$,"from_project":"rlm_query","message":"[rlm_query --async result]\n\n$RESULT","timestamp":"$(date -Iseconds)","id":"async_${ASYNC_ID}"}
366
+ MSGJSON
367
+ )
368
+ echo "$MSG" >> "$INBOX_DIR/inbox.jsonl"
369
+ fi
370
+ fi
371
+
372
+ # Clean up temp files the parent skipped
373
+ rm -f "$CHILD_CONTEXT" "$PROMPT_FILE" "${COMBINED_PROMPT:-}"
374
+ if [ -n "$JJ_WS_NAME" ]; then
375
+ jj workspace forget "$JJ_WS_NAME" 2>/dev/null || true
376
+ fi
377
+ ) &
378
+ ASYNC_PID=$!
379
+ disown $ASYNC_PID 2>/dev/null || true
380
+
381
+ # Return job metadata immediately
382
+ cat <<EOF
383
+ {"job_id": "$ASYNC_ID", "output": "$ASYNC_OUTPUT", "sentinel": "$ASYNC_SENTINEL", "pid": $ASYNC_PID}
384
+ EOF
385
+ exit 0
386
+ fi
387
+
304
388
  # ----------------------------------------------------------------------
305
389
  # Run child Pi — JSON mode (default) or plain text
306
390
  # JSON mode streams text to stdout and captures cost via fd 3.
307
391
  # ----------------------------------------------------------------------
308
- COST_OUT=$(mktemp /tmp/rlm_cost_out_XXXXXX.json)
392
+ COST_OUT=$(mktemp "${TMPDIR:-/tmp}/rlm_cost_out.XXXXXX")
309
393
  trap '{
310
394
  rm -f "$CHILD_CONTEXT" "$PROMPT_FILE"
311
395
  rm -f "${COMBINED_PROMPT:-}"
package/ypi CHANGED
@@ -66,7 +66,7 @@ mkdir -p "$RLM_SESSION_DIR"
66
66
 
67
67
  # Build combined system prompt: SYSTEM_PROMPT.md + rlm_query source
68
68
  # This way the agent sees the full implementation, not just usage docs.
69
- COMBINED_PROMPT=$(mktemp /tmp/ypi_system_prompt_XXXXXX.md)
69
+ COMBINED_PROMPT=$(mktemp "${TMPDIR:-/tmp}/ypi_system_prompt.XXXXXX")
70
70
  trap 'rm -f "$COMBINED_PROMPT"' EXIT
71
71
 
72
72
  cat "$SCRIPT_DIR/SYSTEM_PROMPT.md" > "$COMBINED_PROMPT"
@@ -94,6 +94,7 @@ fi
94
94
  # We append to the combined prompt file rather than passing through,
95
95
  # since pi already gets --system-prompt from us.
96
96
  PASS_ARGS=()
97
+ QUIET="${YPI_QUIET:-0}"
97
98
  while [[ $# -gt 0 ]]; do
98
99
  case "$1" in
99
100
  --append-system-prompt)
@@ -101,9 +102,13 @@ while [[ $# -gt 0 ]]; do
101
102
  printf '\n%s\n' "$1" >> "$COMBINED_PROMPT"
102
103
  shift
103
104
  ;;
105
+ --quiet|-q)
106
+ QUIET=1
107
+ shift
108
+ ;;
104
109
  --system-prompt)
105
110
  # User overriding ypi's system prompt entirely
106
- echo "⚠️ Overriding ypi's system prompt. Did you mean --append-system-prompt?" >&2
111
+ [ "$QUIET" != "1" ] && echo "⚠️ Overriding ypi's system prompt. Did you mean --append-system-prompt?" >&2
107
112
  shift
108
113
  cat "$1" > "$COMBINED_PROMPT" 2>/dev/null || echo "$1" > "$COMBINED_PROMPT"
109
114
  shift
@@ -116,4 +121,7 @@ while [[ $# -gt 0 ]]; do
116
121
  done
117
122
  # Launch Pi with the combined system prompt, passing all args through
118
123
  # User's own extensions (hashline, etc.) are discovered automatically by Pi.
119
- exec pi --system-prompt "$COMBINED_PROMPT" "${YPI_EXT_ARGS[@]}" "${PASS_ARGS[@]}"
124
+ PI_ARGV=(pi --system-prompt "$COMBINED_PROMPT")
125
+ [ ${#YPI_EXT_ARGS[@]} -gt 0 ] && PI_ARGV+=("${YPI_EXT_ARGS[@]}")
126
+ [ ${#PASS_ARGS[@]} -gt 0 ] && PI_ARGV+=("${PASS_ARGS[@]}")
127
+ exec "${PI_ARGV[@]}"