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 +25 -0
- package/README.md +22 -3
- package/SYSTEM_PROMPT.md +51 -27
- package/package.json +1 -1
- package/rlm_query +93 -9
- package/ypi +11 -3
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
|
|
177
|
-
make test-
|
|
178
|
-
make
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
66
|
+
**Example 4 – Parallel sub-tasks with --async (PREFERRED)**
|
|
63
67
|
```bash
|
|
64
|
-
# Break a complex task into independent pieces
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
echo "
|
|
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. **
|
|
121
|
-
2. **
|
|
122
|
-
3. **
|
|
123
|
-
4. **
|
|
124
|
-
5. **
|
|
125
|
-
6. **
|
|
126
|
-
7. **
|
|
127
|
-
8. **
|
|
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
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
|
|
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
|
|
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
|
|
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
|
|
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 [ -
|
|
215
|
+
if [ -n "${RLM_STDIN:-}" ]; then
|
|
191
216
|
HAS_STDIN=true
|
|
192
|
-
elif [ -
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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[@]}"
|