open-agents-ai 0.106.0 → 0.108.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +198 -12
- package/package.json +1 -1
- package/prompts/agentic/system-large.md +25 -0
package/dist/index.js
CHANGED
|
@@ -7557,10 +7557,54 @@ var init_repl = __esm({
|
|
|
7557
7557
|
setLlmQueryHandler(handler) {
|
|
7558
7558
|
this.llmQueryHandler = handler;
|
|
7559
7559
|
}
|
|
7560
|
+
/** Callback for retrieving externalized handle content (RLM Context OS) */
|
|
7561
|
+
handleResolver = null;
|
|
7562
|
+
/** Set the handle resolver for retrieve() in the REPL (COHERE Layer 2) */
|
|
7563
|
+
setHandleResolver(resolver) {
|
|
7564
|
+
this.handleResolver = resolver;
|
|
7565
|
+
}
|
|
7560
7566
|
/** Get the number of sub-calls made this session */
|
|
7561
7567
|
getSubCallCount() {
|
|
7562
7568
|
return this.subCallCount;
|
|
7563
7569
|
}
|
|
7570
|
+
/**
|
|
7571
|
+
* Pre-load a large context string into the REPL as the `context` variable.
|
|
7572
|
+
* Used by prompt externalization (RLM paper §2, Principle 1: symbolic handle).
|
|
7573
|
+
* The context is stored as a Python string variable — the model can then
|
|
7574
|
+
* slice, search, and process it via repl_exec without it entering the LLM context.
|
|
7575
|
+
*/
|
|
7576
|
+
async loadContext(content) {
|
|
7577
|
+
if (!this.proc || this.proc.killed || this.proc.exitCode !== null) {
|
|
7578
|
+
await this.startProcess();
|
|
7579
|
+
}
|
|
7580
|
+
const tempFile = join18(tmpdir3(), `oa-repl-ctx-${randomBytes2(6).toString("hex")}.txt`);
|
|
7581
|
+
const { writeFileSync: writeFs, unlinkSync: unlinkFs } = await import("node:fs");
|
|
7582
|
+
writeFs(tempFile, content, "utf8");
|
|
7583
|
+
const result = await this.executeCode(`with open(${JSON.stringify(tempFile)}, "r") as _f:
|
|
7584
|
+
context = _f.read()
|
|
7585
|
+
import os; os.unlink(${JSON.stringify(tempFile)})
|
|
7586
|
+
print(f"Context loaded: {len(context)} chars")`);
|
|
7587
|
+
try {
|
|
7588
|
+
unlinkFs(tempFile);
|
|
7589
|
+
} catch {
|
|
7590
|
+
}
|
|
7591
|
+
return result.success;
|
|
7592
|
+
}
|
|
7593
|
+
/**
|
|
7594
|
+
* Read a variable's value from the REPL environment.
|
|
7595
|
+
* Used by FINAL_VAR to extract the task output (RLM paper §2, Principle 2).
|
|
7596
|
+
*/
|
|
7597
|
+
async readVariable(varName) {
|
|
7598
|
+
if (!this.proc || this.proc.killed || this.proc.exitCode !== null)
|
|
7599
|
+
return null;
|
|
7600
|
+
const result = await this.executeCode(`try:
|
|
7601
|
+
print(str(${varName}))
|
|
7602
|
+
except NameError:
|
|
7603
|
+
print("__OA_VAR_NOT_FOUND__")`);
|
|
7604
|
+
if (!result.success || result.output.includes("__OA_VAR_NOT_FOUND__"))
|
|
7605
|
+
return null;
|
|
7606
|
+
return result.output.trim();
|
|
7607
|
+
}
|
|
7564
7608
|
async execute(args) {
|
|
7565
7609
|
const code = String(args.code ?? "");
|
|
7566
7610
|
const reset = Boolean(args.reset);
|
|
@@ -7646,6 +7690,45 @@ def llm_query(prompt, context=""):
|
|
|
7646
7690
|
finally:
|
|
7647
7691
|
sock.close()
|
|
7648
7692
|
|
|
7693
|
+
def retrieve(handle_id):
|
|
7694
|
+
"""Retrieve full content of an externalized tool output by its handle ID.
|
|
7695
|
+
Use this to access large outputs that were stored as handles instead of
|
|
7696
|
+
being shown inline. Returns the full content as a string.
|
|
7697
|
+
Args:
|
|
7698
|
+
handle_id: The handle ID (e.g., 'a1b2c3d4')
|
|
7699
|
+
Returns: Full content string, or error message if not found
|
|
7700
|
+
"""
|
|
7701
|
+
sock_path = os.environ.get("OA_LLM_QUERY_SOCKET", "")
|
|
7702
|
+
if not sock_path:
|
|
7703
|
+
return "[retrieve unavailable: no IPC socket]"
|
|
7704
|
+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
7705
|
+
try:
|
|
7706
|
+
sock.connect(sock_path)
|
|
7707
|
+
request = json.dumps({"op": "retrieve", "handle_id": str(handle_id)})
|
|
7708
|
+
data = request.encode("utf-8")
|
|
7709
|
+
sock.sendall(len(data).to_bytes(4, "big") + data)
|
|
7710
|
+
length_bytes = b""
|
|
7711
|
+
while len(length_bytes) < 4:
|
|
7712
|
+
chunk = sock.recv(4 - len(length_bytes))
|
|
7713
|
+
if not chunk:
|
|
7714
|
+
return "[retrieve: connection closed]"
|
|
7715
|
+
length_bytes += chunk
|
|
7716
|
+
resp_len = int.from_bytes(length_bytes, "big")
|
|
7717
|
+
resp_data = b""
|
|
7718
|
+
while len(resp_data) < resp_len:
|
|
7719
|
+
chunk = sock.recv(min(resp_len - len(resp_data), 65536))
|
|
7720
|
+
if not chunk:
|
|
7721
|
+
break
|
|
7722
|
+
resp_data += chunk
|
|
7723
|
+
result = json.loads(resp_data.decode("utf-8"))
|
|
7724
|
+
if "error" in result:
|
|
7725
|
+
return f"[retrieve error: {result['error']}]"
|
|
7726
|
+
return result.get("content", "")
|
|
7727
|
+
except Exception as e:
|
|
7728
|
+
return f"[retrieve error: {e}]"
|
|
7729
|
+
finally:
|
|
7730
|
+
sock.close()
|
|
7731
|
+
|
|
7649
7732
|
# Signal REPL ready
|
|
7650
7733
|
print("__OA_REPL_READY__")
|
|
7651
7734
|
`.trim();
|
|
@@ -7687,14 +7770,27 @@ print("__OA_REPL_READY__")
|
|
|
7687
7770
|
let response;
|
|
7688
7771
|
try {
|
|
7689
7772
|
const req = JSON.parse(msgData);
|
|
7690
|
-
if (
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7773
|
+
if (req.op === "retrieve") {
|
|
7774
|
+
if (!this.handleResolver) {
|
|
7775
|
+
response = { error: "Handle resolver not configured" };
|
|
7776
|
+
} else {
|
|
7777
|
+
const content = this.handleResolver(req.handle_id ?? "");
|
|
7778
|
+
if (content !== null) {
|
|
7779
|
+
response = { content };
|
|
7780
|
+
} else {
|
|
7781
|
+
response = { error: `Handle '${req.handle_id}' not found` };
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7694
7784
|
} else {
|
|
7695
|
-
this.
|
|
7696
|
-
|
|
7697
|
-
|
|
7785
|
+
if (!this.llmQueryHandler) {
|
|
7786
|
+
response = { error: "llm_query handler not configured" };
|
|
7787
|
+
} else if (this.subCallCount >= this.maxSubCalls) {
|
|
7788
|
+
response = { error: `Sub-call limit reached (${this.maxSubCalls})` };
|
|
7789
|
+
} else {
|
|
7790
|
+
this.subCallCount++;
|
|
7791
|
+
const result = await this.llmQueryHandler(req.prompt ?? "", req.context);
|
|
7792
|
+
response = { response: result };
|
|
7793
|
+
}
|
|
7698
7794
|
}
|
|
7699
7795
|
} catch (err) {
|
|
7700
7796
|
response = { error: err instanceof Error ? err.message : String(err) };
|
|
@@ -19313,7 +19409,8 @@ var init_agenticRunner = __esm({
|
|
|
19313
19409
|
modelTier: options?.modelTier ?? "large",
|
|
19314
19410
|
contextWindowSize: options?.contextWindowSize ?? 0,
|
|
19315
19411
|
personality: options?.personality ?? PERSONALITY_PRESETS.balanced,
|
|
19316
|
-
personalityName: options?.personalityName ?? ""
|
|
19412
|
+
personalityName: options?.personalityName ?? "",
|
|
19413
|
+
finalVarResolver: options?.finalVarResolver ?? void 0
|
|
19317
19414
|
};
|
|
19318
19415
|
}
|
|
19319
19416
|
/** Update context window size (e.g. after querying Ollama /api/show) */
|
|
@@ -19953,8 +20050,29 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
|
|
|
19953
20050
|
}
|
|
19954
20051
|
}
|
|
19955
20052
|
const { toolOutputMaxChars: maxLen } = this.contextLimits();
|
|
19956
|
-
|
|
20053
|
+
let output;
|
|
20054
|
+
if (!result.success) {
|
|
20055
|
+
output = `Error: ${result.error || "unknown error"}
|
|
19957
20056
|
${result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : result.output}`;
|
|
20057
|
+
} else if (result.output.length > maxLen) {
|
|
20058
|
+
const handleId = this.quickHash(tc.name + String(tc.arguments?.["path"] ?? "") + String(turn));
|
|
20059
|
+
const lineCount = (result.output.match(/\n/g) || []).length + 1;
|
|
20060
|
+
const preview = result.output.slice(0, 500).replace(/\n/g, " ");
|
|
20061
|
+
this._memexArchive.set(handleId, {
|
|
20062
|
+
id: handleId,
|
|
20063
|
+
toolName: tc.name,
|
|
20064
|
+
summary: `${tc.name} output (${lineCount} lines, ${result.output.length} chars)`,
|
|
20065
|
+
fullContent: result.output,
|
|
20066
|
+
turn,
|
|
20067
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
20068
|
+
});
|
|
20069
|
+
output = `[Output externalized \u2014 ${result.output.length} chars, ${lineCount} lines]
|
|
20070
|
+
Handle: ${handleId}
|
|
20071
|
+
Preview: ${preview}...
|
|
20072
|
+
Full content available via: repl_exec(code="data = retrieve('${handleId}')") or memex_retrieve(id="${handleId}")`;
|
|
20073
|
+
} else {
|
|
20074
|
+
output = result.output;
|
|
20075
|
+
}
|
|
19958
20076
|
this.emit({
|
|
19959
20077
|
type: "tool_result",
|
|
19960
20078
|
toolName: tc.name,
|
|
@@ -20344,9 +20462,29 @@ Integrate this guidance into your current approach. Continue working on the task
|
|
|
20344
20462
|
}
|
|
20345
20463
|
}
|
|
20346
20464
|
const { toolOutputMaxChars: maxLen2 } = this.contextLimits();
|
|
20347
|
-
|
|
20348
|
-
|
|
20465
|
+
let output;
|
|
20466
|
+
if (!result.success) {
|
|
20467
|
+
output = `Error: ${result.error || "unknown error"}
|
|
20349
20468
|
${result.output}`;
|
|
20469
|
+
} else if (result.output.length > maxLen2) {
|
|
20470
|
+
const handleId = this.quickHash(tc.name + String(tc.arguments?.["path"] ?? "") + String(turn));
|
|
20471
|
+
const lineCount = (result.output.match(/\n/g) || []).length + 1;
|
|
20472
|
+
const preview = result.output.slice(0, 500).replace(/\n/g, " ");
|
|
20473
|
+
this._memexArchive.set(handleId, {
|
|
20474
|
+
id: handleId,
|
|
20475
|
+
toolName: tc.name,
|
|
20476
|
+
summary: `${tc.name} output (${lineCount} lines, ${result.output.length} chars)`,
|
|
20477
|
+
fullContent: result.output,
|
|
20478
|
+
turn,
|
|
20479
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
20480
|
+
});
|
|
20481
|
+
output = `[Output externalized \u2014 ${result.output.length} chars, ${lineCount} lines]
|
|
20482
|
+
Handle: ${handleId}
|
|
20483
|
+
Preview: ${preview}...
|
|
20484
|
+
Full content available via: repl_exec(code="data = retrieve('${handleId}')") or memex_retrieve(id="${handleId}")`;
|
|
20485
|
+
} else {
|
|
20486
|
+
output = result.output;
|
|
20487
|
+
}
|
|
20350
20488
|
this.emit({ type: "tool_result", toolName: tc.name, content: output.slice(0, 200), success: result.success, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
20351
20489
|
const enoentTools2 = /* @__PURE__ */ new Set(["file_read", "list_directory", "find_files"]);
|
|
20352
20490
|
if (!result.success && enoentTools2.has(tc.name) && /ENOENT|no such file|does not exist|not found/i.test(result.error ?? result.output)) {
|
|
@@ -20382,6 +20520,17 @@ ${result.output}`;
|
|
|
20382
20520
|
summary = content;
|
|
20383
20521
|
break;
|
|
20384
20522
|
}
|
|
20523
|
+
const finalVarMatch = content.match(/FINAL_VAR\s*\(\s*["']?(\w+)["']?\s*\)/);
|
|
20524
|
+
if (finalVarMatch && this.options.finalVarResolver) {
|
|
20525
|
+
const varName = finalVarMatch[1];
|
|
20526
|
+
const varValue = await this.options.finalVarResolver(varName);
|
|
20527
|
+
if (varValue !== null) {
|
|
20528
|
+
completed = true;
|
|
20529
|
+
summary = varValue;
|
|
20530
|
+
this.emit({ type: "status", content: `FINAL_VAR(${varName}): resolved ${varValue.length} chars from REPL`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
20531
|
+
break;
|
|
20532
|
+
}
|
|
20533
|
+
}
|
|
20385
20534
|
if (consecutiveTextOnly >= MAX_CONSECUTIVE_TEXT_ONLY) {
|
|
20386
20535
|
this.emit({
|
|
20387
20536
|
type: "status",
|
|
@@ -50240,6 +50389,20 @@ ${context}` : prompt;
|
|
|
50240
50389
|
});
|
|
50241
50390
|
}
|
|
50242
50391
|
}
|
|
50392
|
+
for (const tool of tools) {
|
|
50393
|
+
if ("setHandleResolver" in tool && typeof tool.setHandleResolver === "function") {
|
|
50394
|
+
tool.setHandleResolver((handleId) => {
|
|
50395
|
+
const entry = runner.getMemexEntry(handleId);
|
|
50396
|
+
return entry?.fullContent ?? null;
|
|
50397
|
+
});
|
|
50398
|
+
}
|
|
50399
|
+
}
|
|
50400
|
+
const replToolForFinalVar = tools.find((t) => t.name === "repl_exec");
|
|
50401
|
+
if (replToolForFinalVar && "readVariable" in replToolForFinalVar) {
|
|
50402
|
+
runner.options.finalVarResolver = async (varName) => {
|
|
50403
|
+
return replToolForFinalVar.readVariable(varName);
|
|
50404
|
+
};
|
|
50405
|
+
}
|
|
50243
50406
|
runner.registerTools(tools);
|
|
50244
50407
|
runner.registerTool({
|
|
50245
50408
|
name: "memex_retrieve",
|
|
@@ -50698,7 +50861,30 @@ ${entry.fullContent}`
|
|
|
50698
50861
|
|
|
50699
50862
|
${emotionContext}` : `Working directory: ${repoRoot}`;
|
|
50700
50863
|
resetNarrationContext();
|
|
50701
|
-
|
|
50864
|
+
let effectiveTask = task;
|
|
50865
|
+
const estimatedTaskTokens = Math.ceil(task.length / 4);
|
|
50866
|
+
const externalizeThreshold = contextWindowSize ? Math.floor(contextWindowSize * 0.4) : 3e4;
|
|
50867
|
+
if (estimatedTaskTokens > externalizeThreshold) {
|
|
50868
|
+
const replTool = tools.find((t) => t.name === "repl_exec");
|
|
50869
|
+
if (replTool && "loadContext" in replTool) {
|
|
50870
|
+
replTool.loadContext(task);
|
|
50871
|
+
const prefix = task.slice(0, 500).replace(/\n/g, " ");
|
|
50872
|
+
effectiveTask = `[RLM MODE \u2014 Large input externalized to REPL]
|
|
50873
|
+
Your input has been stored as the variable \`context\` in the persistent Python REPL.
|
|
50874
|
+
Context length: ${task.length} characters (~${estimatedTaskTokens} tokens)
|
|
50875
|
+
Prefix: "${prefix}..."
|
|
50876
|
+
|
|
50877
|
+
Use repl_exec to examine and process the context. For example:
|
|
50878
|
+
repl_exec(code="print(context[:1000])") # see the beginning
|
|
50879
|
+
repl_exec(code="print(len(context))") # check length
|
|
50880
|
+
repl_exec(code="chunks = context.split('\\n\\n')") # split into chunks
|
|
50881
|
+
|
|
50882
|
+
Use llm_query(prompt, chunk) inside repl_exec to analyze chunks.
|
|
50883
|
+
When done, either call task_complete with your answer, or use FINAL_VAR(variable_name) to return a REPL variable as the output.`;
|
|
50884
|
+
contentWrite(() => renderInfo(`RLM: Externalized ${task.length} chars (~${estimatedTaskTokens} tokens) to REPL context variable`));
|
|
50885
|
+
}
|
|
50886
|
+
}
|
|
50887
|
+
const promise = runner.run(effectiveTask, systemContext).then((result) => {
|
|
50702
50888
|
const tokens = { total: result.totalTokens, estimated: result.estimatedTokens };
|
|
50703
50889
|
contentWrite(() => {
|
|
50704
50890
|
if (result.completed) {
|
package/package.json
CHANGED
|
@@ -462,3 +462,28 @@ Entries in a directory listing are RELATIVE to the directory you listed.
|
|
|
462
462
|
- If an entry is marked "d" (directory), use list_directory on it — NOT file_read
|
|
463
463
|
- list_directory output includes full relative paths you can copy directly into your next tool call
|
|
464
464
|
- Prefer list_directory over shell ls — it shows full relative paths you can copy directly into your next tool call
|
|
465
|
+
|
|
466
|
+
## RLM Context Operating System
|
|
467
|
+
|
|
468
|
+
The repl_exec tool provides a persistent Python REPL where variables persist between calls. Use it for:
|
|
469
|
+
|
|
470
|
+
**Data Processing**: When you need to process, transform, or analyze data across multiple steps, use repl_exec. Variables, functions, and imports survive between calls.
|
|
471
|
+
|
|
472
|
+
**Recursive LLM Calls**: Inside the REPL, `llm_query(prompt, context="")` invokes the language model on a sub-prompt. Use it in loops to analyze chunks of large content:
|
|
473
|
+
```python
|
|
474
|
+
# Example: analyze each file in a list
|
|
475
|
+
results = []
|
|
476
|
+
for filename in filenames:
|
|
477
|
+
with open(filename) as f:
|
|
478
|
+
content = f.read()
|
|
479
|
+
summary = llm_query("Summarize the key purpose of this code", content)
|
|
480
|
+
results.append(f"{filename}: {summary}")
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**Externalized Context**: When your input is very large, it may be stored as the `context` variable in the REPL. Use `print(context[:1000])` to examine it, slice it, and process chunks with llm_query().
|
|
484
|
+
|
|
485
|
+
**Handle Retrieval**: When a tool output is too large for context, it's stored as a handle. Access it via `data = retrieve('handle_id')` in the REPL.
|
|
486
|
+
|
|
487
|
+
**Output Construction**: For very long outputs, build the result as a REPL variable and return it with FINAL_VAR(variable_name) instead of autoregressive generation.
|
|
488
|
+
|
|
489
|
+
**Provenance**: Based on "Recursive Language Models" (Zhang, Kraska, Khattab — MIT CSAIL, arxiv:2512.24601) and Project COHERE Layer 2 architecture.
|