create-egregore 0.21.0-runtime-mvp.14 → 0.21.0-runtime-mvp.16
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/assets/egregore-launcher.js +2 -2
- package/package.json +1 -1
- package/runtime/claude/CLAUDE.md +19 -19
- package/runtime/claude/bin/codex-render-spec.mjs +2 -3
- package/runtime/claude/bin/search.sh +37 -10
- package/runtime/claude/egregore_runtime/adapters/qmd.py +42 -6
- package/runtime/claude/egregore_runtime/harness_cli.py +94 -3
- package/runtime/codex/.codex/spec-manifest.json +1 -1
- package/runtime/codex/AGENTS.md +17 -16
- package/runtime/codex/bin/codex-render-spec.mjs +2 -3
- package/runtime/codex/bin/search.sh +37 -10
- package/runtime/codex/egregore_runtime/adapters/qmd.py +42 -6
- package/runtime/codex/egregore_runtime/harness_cli.py +94 -3
- package/runtime/pi/.codex/spec-manifest.json +1 -1
- package/runtime/pi/.pi/APPEND_SYSTEM.md +17 -16
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +17 -16
- package/runtime/pi/bin/codex-render-spec.mjs +2 -3
- package/runtime/pi/bin/search.sh +37 -10
- package/runtime/pi/egregore_runtime/adapters/qmd.py +42 -6
- package/runtime/pi/egregore_runtime/harness_cli.py +94 -3
- package/runtime/prime/.codex/spec-manifest.json +1 -1
- package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +17 -16
- package/runtime/prime/.prime/agent/spec-manifest.json +2 -2
- package/runtime/prime/AGENTS.md +17 -16
- package/runtime/prime/bin/codex-render-spec.mjs +2 -3
- package/runtime/prime/bin/search.sh +37 -10
- package/runtime/prime/egregore_runtime/adapters/qmd.py +42 -6
- package/runtime/prime/egregore_runtime/harness_cli.py +94 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// egregore-launcher-version: 0.21.0-runtime-mvp.
|
|
2
|
+
// egregore-launcher-version: 0.21.0-runtime-mvp.16
|
|
3
3
|
//
|
|
4
4
|
// egregore — launch and browse Egregore instances from your terminal.
|
|
5
5
|
// Installed by create-egregore. Reads ~/.egregore/instances.json.
|
|
@@ -35,7 +35,7 @@ const crypto = require("node:crypto");
|
|
|
35
35
|
const { spawnSync, spawn } = require("node:child_process");
|
|
36
36
|
const readline = require("node:readline");
|
|
37
37
|
|
|
38
|
-
const VERSION = "0.21.0-runtime-mvp.
|
|
38
|
+
const VERSION = "0.21.0-runtime-mvp.16";
|
|
39
39
|
const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
|
|
40
40
|
const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
|
|
41
41
|
const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-egregore",
|
|
3
|
-
"version": "0.21.0-runtime-mvp.
|
|
3
|
+
"version": "0.21.0-runtime-mvp.16",
|
|
4
4
|
"description": "Set up Egregore for your team in one command — installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
package/runtime/claude/CLAUDE.md
CHANGED
|
@@ -228,19 +228,21 @@ For each user-directed organizational lookup, show exactly one visible line:
|
|
|
228
228
|
- retrieval that actually queries the connected graph:
|
|
229
229
|
`⌕ Egregore Connect · searching your organization’s memory and relationships`
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
is only the schema label. When attached, authorized retrieval is complete:
|
|
231
|
+
When `EGREGORE_ORG_CONTEXT_V1` is attached, authorized retrieval is complete:
|
|
233
232
|
never query again that turn, and state any evidence gap. `OPENED_BY_OBSERVE`
|
|
234
233
|
means the evidence is open. A later explicit search starts a new turn.
|
|
235
234
|
|
|
236
|
-
**
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
Shell/tool output does not satisfy this requirement. Do not paraphrase the line.
|
|
235
|
+
**Scope.** Egregore Runtime is for organizational recall only: team memory,
|
|
236
|
+
decisions, handoffs, prior work. Code, files, Git, and tests use the harness's
|
|
237
|
+
normal tools, with no beat and no Runtime call.
|
|
240
238
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
239
|
+
**Visibility is the contract.** Emit it verbatim as a standalone assistant
|
|
240
|
+
message before the first tool call that performs the organizational recall
|
|
241
|
+
(the `bin/search.sh` query or a graph read). Tool output does not satisfy it.
|
|
242
|
+
Do not paraphrase the line.
|
|
243
|
+
|
|
244
|
+
Use Connect only for a graph traversal that actually runs. Emit once per
|
|
245
|
+
episode.
|
|
244
246
|
|
|
245
247
|
**Routing:** Organizational recall always enters Egregore Runtime. The model is
|
|
246
248
|
the semantic intent authority, deciding from meaning rather than fixed phrases.
|
|
@@ -251,20 +253,18 @@ Without sufficient attached context, make exactly one Runtime/QMD call:
|
|
|
251
253
|
bash bin/search.sh query "<concept>" -n 6 --compact --context-packet
|
|
252
254
|
```
|
|
253
255
|
|
|
254
|
-
`--context-packet`
|
|
255
|
-
|
|
256
|
-
Never re-print ranked results, file/rank/excerpt metadata, or raw JSON.
|
|
256
|
+
`--context-packet` attaches the evidence privately; the visible output is the
|
|
257
|
+
attribution line alone. Never re-print ranked results or raw JSON.
|
|
257
258
|
|
|
258
259
|
Do not resolve `memory/` to its sibling repo or start with raw `grep`/`ls`.
|
|
259
|
-
Search
|
|
260
|
-
|
|
260
|
+
Search attaches no graph by default. Open a source only when its excerpt
|
|
261
|
+
cannot support a necessary claim. For current
|
|
261
262
|
team/person synthesis, retain known organization/member names and choose
|
|
262
|
-
`--recent-days` plus `--min-evidence`.
|
|
263
|
-
Grep/Glob for code and exact errors. `/activity`, `/dashboard`, and `/project`
|
|
263
|
+
`--recent-days` plus `--min-evidence`. `/activity`, `/dashboard`, and `/project`
|
|
264
264
|
display bounded status surfaces only when named or directly requested as a
|
|
265
|
-
card.
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
card. Explained syntheses of current organizational work and prior-work
|
|
266
|
+
discovery are Runtime/QMD recall. Do not run retrieval on unrelated prompts or
|
|
267
|
+
infer graph from recall intent.
|
|
268
268
|
|
|
269
269
|
Do not emit the beat for code search, Git inspection, startup hydration, graph
|
|
270
270
|
writes, ingestion, background sync, maintenance, or internal queries.
|
|
@@ -157,9 +157,8 @@ const NON_SKILL_TOKENS = new Set(['loop', 'fast', 'config', 'clear', 'help']);
|
|
|
157
157
|
// hook delivers there. Rewrite the fallback command to the stdout form and
|
|
158
158
|
// state the same hygiene contract in shell terms: search output is
|
|
159
159
|
// model-internal; the visible reply carries only the retrieval line.
|
|
160
|
-
const CLAUDE_PACKET_NOTE = `\`--context-packet\`
|
|
161
|
-
|
|
162
|
-
Never re-print ranked results, file/rank/excerpt metadata, or raw JSON.`;
|
|
160
|
+
const CLAUDE_PACKET_NOTE = `\`--context-packet\` attaches the evidence privately; the visible output is the
|
|
161
|
+
attribution line alone. Never re-print ranked results or raw JSON.`;
|
|
163
162
|
const SHELL_PACKET_NOTE = `Search output is model-internal: show only the retrieval line and receipts —
|
|
164
163
|
never ranked blocks, file/rank/excerpt metadata, or raw JSON.`;
|
|
165
164
|
|
|
@@ -32,6 +32,19 @@ _maintenance_runtime() {
|
|
|
32
32
|
python3 -m egregore_runtime.adapters.qmd_cli "$@"
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
_now_ms() {
|
|
36
|
+
if command -v perl >/dev/null 2>&1; then
|
|
37
|
+
perl -MTime::HiRes=time -e 'printf("%d\n", time() * 1000)'
|
|
38
|
+
else
|
|
39
|
+
echo $(( $(date +%s) * 1000 ))
|
|
40
|
+
fi
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_elapsed_s() {
|
|
44
|
+
# One-decimal seconds since a _now_ms() timestamp.
|
|
45
|
+
awk -v ms="$(( $(_now_ms) - $1 ))" 'BEGIN { printf "%.1f", ms / 1000 }'
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
_result_paths() {
|
|
36
49
|
local line path canonical_prefix
|
|
37
50
|
canonical_prefix="**file:** \`memory/"
|
|
@@ -125,14 +138,14 @@ _banner_and_emit() {
|
|
|
125
138
|
actual="${actual%% *}"
|
|
126
139
|
required="${gap#*required=}"
|
|
127
140
|
required="${required%% *}"
|
|
128
|
-
dur=$(
|
|
141
|
+
dur=$(_elapsed_s "$t0")
|
|
129
142
|
printf '⌕ Egregore · searching your organization’s memory · %s · evidence gap · %s/%s sources · %ss\n\n' \
|
|
130
143
|
"$label" "$actual" "$required" "$dur"
|
|
131
144
|
[ "$hide_results" = 1 ] || printf '%s\n' "$out" | tail -n +2
|
|
132
145
|
return 0
|
|
133
146
|
fi
|
|
134
147
|
hits=$(printf '%s\n' "$out" | _result_paths | wc -l | tr -d ' ')
|
|
135
|
-
dur=$(
|
|
148
|
+
dur=$(_elapsed_s "$t0")
|
|
136
149
|
product="Egregore"
|
|
137
150
|
surface="searching your organization’s memory"
|
|
138
151
|
if [ "$emit" = "_enrich" ] && [ "$hits" -gt 0 ]; then
|
|
@@ -223,7 +236,7 @@ cmd_query() {
|
|
|
223
236
|
esac
|
|
224
237
|
|
|
225
238
|
local t0 out emit="cat" query_rc
|
|
226
|
-
t0=$(
|
|
239
|
+
t0=$(_now_ms)
|
|
227
240
|
out=$(_runtime "${args[@]}" 2>&1)
|
|
228
241
|
query_rc=$?
|
|
229
242
|
if [ "$query_rc" -ne 0 ]; then
|
|
@@ -235,20 +248,31 @@ cmd_query() {
|
|
|
235
248
|
} >&2
|
|
236
249
|
return "$query_rc"
|
|
237
250
|
fi
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
251
|
+
# The runtime marks a query that ran without its persistent worker (the
|
|
252
|
+
# pinned CLI started cold). Strip the marker from the evidence and carry
|
|
253
|
+
# it into the banner, the telemetry, and a one-line repair hint.
|
|
254
|
+
local cold=0 cold_json=false
|
|
255
|
+
if printf '%s\n' "$out" | grep -qx 'EGREGORE_RETRIEVAL_COLD_START'; then
|
|
256
|
+
cold=1
|
|
257
|
+
cold_json=true
|
|
258
|
+
out=$(printf '%s\n' "$out" | grep -vx 'EGREGORE_RETRIEVAL_COLD_START')
|
|
259
|
+
fi
|
|
260
|
+
# Content-free query telemetry: mode, duration, result count, degraded
|
|
261
|
+
# path. Never the query text. This is what makes Runtime/QMD retrieval
|
|
262
|
+
# visible in the telemetry event list instead of only legacy graph_query rows.
|
|
241
263
|
local _tele_count _tele_ms
|
|
242
|
-
_tele_ms=$((
|
|
264
|
+
_tele_ms=$(( $(_now_ms) - t0 ))
|
|
243
265
|
_tele_count=$(printf '%s' "$out" | jq 'if type == "array" then length elif type == "object" then (.results // [] | length) else empty end' 2>/dev/null || true)
|
|
244
266
|
if [[ "$_tele_count" =~ ^[0-9]+$ ]]; then
|
|
245
267
|
bash "$SCRIPT_DIR/bin/telemetry.sh" emit "query" \
|
|
246
|
-
"$(jq -n --arg mode "$label" --argjson d "$_tele_ms" --argjson c "$_tele_count" \
|
|
247
|
-
'{mode:$mode, duration_ms:$d, count:$c}')" 2>/dev/null &
|
|
268
|
+
"$(jq -n --arg mode "$label" --argjson d "$_tele_ms" --argjson c "$_tele_count" --argjson cold "$cold_json" \
|
|
269
|
+
'{mode:$mode, duration_ms:$d, count:$c, degraded:$cold}')" 2>/dev/null &
|
|
248
270
|
else
|
|
249
271
|
bash "$SCRIPT_DIR/bin/telemetry.sh" emit "query" \
|
|
250
|
-
"$(jq -n --arg mode "$label" --argjson d "$_tele_ms"
|
|
272
|
+
"$(jq -n --arg mode "$label" --argjson d "$_tele_ms" --argjson cold "$cold_json" \
|
|
273
|
+
'{mode:$mode, duration_ms:$d, degraded:$cold}')" 2>/dev/null &
|
|
251
274
|
fi
|
|
275
|
+
[ "$cold" = 1 ] && label="${label} · cold start"
|
|
252
276
|
|
|
253
277
|
[ "$enrich" = 1 ] && emit="_enrich"
|
|
254
278
|
if [ "$context_packet" = 1 ]; then
|
|
@@ -263,6 +287,9 @@ cmd_query() {
|
|
|
263
287
|
else
|
|
264
288
|
_banner_and_emit "$label" "$t0" "$emit" "$out"
|
|
265
289
|
fi
|
|
290
|
+
if [ "$cold" = 1 ]; then
|
|
291
|
+
echo " ↳ the retrieval server was not running, so this query started QMD cold. Repair: bash bin/search.sh start" >&2
|
|
292
|
+
fi
|
|
266
293
|
}
|
|
267
294
|
|
|
268
295
|
cmd_reindex() {
|
|
@@ -157,6 +157,11 @@ class QmdLocalRetriever:
|
|
|
157
157
|
self._resolved_command: tuple[str, ...] | None = None
|
|
158
158
|
self._version_checked = False
|
|
159
159
|
self._daemon_available = False
|
|
160
|
+
# Outcome of the most recent typed query: True when the owned worker
|
|
161
|
+
# answered, False when the pinned CLI ran cold, None when no query
|
|
162
|
+
# ran or persistence is disabled. Read by the harness so a slow
|
|
163
|
+
# retrieval is never silent.
|
|
164
|
+
self._last_query_used_daemon: bool | None = None
|
|
160
165
|
self._source_revision_cache: str | None = None
|
|
161
166
|
self._configure_isolated_environment()
|
|
162
167
|
self._prefer_usable_node_runtime()
|
|
@@ -726,7 +731,10 @@ class QmdLocalRetriever:
|
|
|
726
731
|
limit: int,
|
|
727
732
|
intent: str,
|
|
728
733
|
) -> list[dict[str, Any]]:
|
|
729
|
-
|
|
734
|
+
self._last_query_used_daemon = None
|
|
735
|
+
if not self._persistent_enabled():
|
|
736
|
+
daemon_error = "daemon unavailable"
|
|
737
|
+
elif self._ensure_daemon():
|
|
730
738
|
payload = {
|
|
731
739
|
"searches": searches,
|
|
732
740
|
"collections": [self.collection],
|
|
@@ -746,6 +754,7 @@ class QmdLocalRetriever:
|
|
|
746
754
|
body = json.loads(response.read().decode("utf-8"))
|
|
747
755
|
results = body.get("results") if isinstance(body, dict) else None
|
|
748
756
|
if isinstance(results, list):
|
|
757
|
+
self._last_query_used_daemon = True
|
|
749
758
|
return [item for item in results if isinstance(item, dict)]
|
|
750
759
|
except (OSError, ValueError, urllib.error.URLError) as exc:
|
|
751
760
|
self._daemon_available = False
|
|
@@ -754,8 +763,13 @@ class QmdLocalRetriever:
|
|
|
754
763
|
daemon_error = str(exc)
|
|
755
764
|
else:
|
|
756
765
|
daemon_error = "daemon returned an invalid result payload"
|
|
766
|
+
self._last_query_used_daemon = False
|
|
757
767
|
else:
|
|
768
|
+
# The owned worker could not be started or reused. The pinned CLI
|
|
769
|
+
# answers cold; the harness surfaces that instead of a silent
|
|
770
|
+
# multi-second query.
|
|
758
771
|
daemon_error = "daemon unavailable"
|
|
772
|
+
self._last_query_used_daemon = False
|
|
759
773
|
|
|
760
774
|
query_document = "\n".join(
|
|
761
775
|
f"{item['type']}: {self._single_line(item['query'])}" for item in searches
|
|
@@ -1009,6 +1023,13 @@ class QmdLocalRetriever:
|
|
|
1009
1023
|
def _persistent_enabled(self) -> bool:
|
|
1010
1024
|
return self.environment.get("EGREGORE_QMD_PERSISTENT", "1") != "0"
|
|
1011
1025
|
|
|
1026
|
+
@property
|
|
1027
|
+
def last_query_used_daemon(self) -> bool | None:
|
|
1028
|
+
"""True when the last typed query answered through the owned worker,
|
|
1029
|
+
False when it fell back to a cold CLI run, None when no query ran or
|
|
1030
|
+
the persistent worker is disabled for this instance."""
|
|
1031
|
+
return self._last_query_used_daemon
|
|
1032
|
+
|
|
1012
1033
|
def _ensure_daemon(self) -> bool:
|
|
1013
1034
|
if not self._persistent_enabled():
|
|
1014
1035
|
return False
|
|
@@ -1153,15 +1174,30 @@ class QmdLocalRetriever:
|
|
|
1153
1174
|
|
|
1154
1175
|
@staticmethod
|
|
1155
1176
|
def _port_available(port: int) -> bool:
|
|
1177
|
+
"""A port is free only when both loopback families can bind it.
|
|
1178
|
+
|
|
1179
|
+
The worker binds "localhost", which resolves to ::1 as well as
|
|
1180
|
+
127.0.0.1. Probing IPv4 alone reported a port free while a foreign
|
|
1181
|
+
server held [::1] on it; the owned worker then died on bind and every
|
|
1182
|
+
query ran the CLI cold.
|
|
1183
|
+
"""
|
|
1156
1184
|
if port == 8181:
|
|
1157
1185
|
return False
|
|
1158
|
-
|
|
1159
|
-
|
|
1186
|
+
for family, address in (
|
|
1187
|
+
(socket.AF_INET, "127.0.0.1"),
|
|
1188
|
+
(socket.AF_INET6, "::1"),
|
|
1189
|
+
):
|
|
1160
1190
|
try:
|
|
1161
|
-
probe.
|
|
1162
|
-
return True
|
|
1191
|
+
probe = socket.socket(family, socket.SOCK_STREAM)
|
|
1163
1192
|
except OSError:
|
|
1164
|
-
|
|
1193
|
+
continue # family not available on this host
|
|
1194
|
+
with probe:
|
|
1195
|
+
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
1196
|
+
try:
|
|
1197
|
+
probe.bind((address, port))
|
|
1198
|
+
except OSError:
|
|
1199
|
+
return False
|
|
1200
|
+
return True
|
|
1165
1201
|
|
|
1166
1202
|
def _allocate_port(self) -> int:
|
|
1167
1203
|
for _ in range(20):
|
|
@@ -38,7 +38,7 @@ from .runtime import EgregoreRuntime, local_runtime
|
|
|
38
38
|
_ORG_CONTEXT_PREFETCH_PATTERN = re.compile(
|
|
39
39
|
"|".join(
|
|
40
40
|
(
|
|
41
|
-
r"\b(find|locate|dig up|look up|pull up)\b
|
|
41
|
+
r"\b(find|locate|dig up|look up|pull up)\b[^\n]*\b"
|
|
42
42
|
r"(handoff|decision|notes?|docs?|meeting|harvest|quest|reflection)s?\b",
|
|
43
43
|
r"\bdid we (decide|discuss|agree|say|talk about|cover)\b",
|
|
44
44
|
r"\bdo we have (any )?(notes?|docs?|anything|something)( on| about)?\b",
|
|
@@ -161,6 +161,19 @@ def _clear_observe_receipt(root: Path, harness: str) -> None:
|
|
|
161
161
|
pass
|
|
162
162
|
|
|
163
163
|
|
|
164
|
+
def _actor_contract_marker_path(root: Path, harness: str) -> Path:
|
|
165
|
+
"""Gitignored per-session marker: the full actor/retrieval contract was
|
|
166
|
+
already attached once. Later prompts receive a short reminder instead of
|
|
167
|
+
the ~4 KB block. bin/pre-compact.sh removes the marker so the first prompt
|
|
168
|
+
after a compaction re-attaches the full contract."""
|
|
169
|
+
|
|
170
|
+
session = re.sub(r"[^A-Za-z0-9_.-]", "-", _session_id(root))[:120]
|
|
171
|
+
return (
|
|
172
|
+
root / ".egregore" / "runtime" / "observe"
|
|
173
|
+
/ f"actor-contract-{harness}-{session}.sent"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
164
177
|
def _write_observe_receipt(root: Path, harness: str, prompt: str, context) -> None:
|
|
165
178
|
"""Record that this prompt already received useful authorized evidence.
|
|
166
179
|
|
|
@@ -369,9 +382,46 @@ def _query(args: argparse.Namespace) -> int:
|
|
|
369
382
|
)
|
|
370
383
|
else:
|
|
371
384
|
print("No results found.")
|
|
385
|
+
retriever = getattr(runtime, "retriever", None)
|
|
386
|
+
if not args.json and getattr(retriever, "last_query_used_daemon", None) is False:
|
|
387
|
+
# The owned QMD worker was unavailable and the pinned CLI answered
|
|
388
|
+
# cold. bin/search.sh turns this marker into a visible "cold start"
|
|
389
|
+
# note so a slow query is never silent.
|
|
390
|
+
print("EGREGORE_RETRIEVAL_COLD_START")
|
|
372
391
|
return 0
|
|
373
392
|
|
|
374
393
|
|
|
394
|
+
_PASTED_OUTPUT_LINE = re.compile(
|
|
395
|
+
r"^(?:"
|
|
396
|
+
r"[ MADRCU?!]{2}\s+\S" # git status --short / porcelain rows
|
|
397
|
+
r"|[▶❯»]\s" # terminal prompt markers
|
|
398
|
+
r"|\$\s\S" # shell prompt
|
|
399
|
+
r"|(?:\+\+\+|---|@@)\s" # unified diff
|
|
400
|
+
r"|diff --git\b"
|
|
401
|
+
r"|Traceback \(most recent call last\)"
|
|
402
|
+
r"|\s+File \"[^\"]+\", line \d+"
|
|
403
|
+
r"|\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}" # log timestamps
|
|
404
|
+
r"|[A-Za-z0-9_./-]+/[A-Za-z0-9_.-]+\.[a-z]{1,6}$" # a bare path
|
|
405
|
+
r")"
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _looks_like_pasted_output(prompt: str) -> bool:
|
|
410
|
+
"""True for terminal output, diffs, logs, and code blocks pasted into a prompt.
|
|
411
|
+
|
|
412
|
+
Those carry words like "handoff", "notes", or "docs" by accident; running a
|
|
413
|
+
retrieval on them attaches unrelated evidence and costs seconds. The actor
|
|
414
|
+
context still tells the model to recall once if the prompt truly needs it.
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
if "```" in prompt:
|
|
418
|
+
return True
|
|
419
|
+
lines = [line for line in prompt.splitlines() if line.strip()]
|
|
420
|
+
if len(lines) >= 8:
|
|
421
|
+
return True
|
|
422
|
+
return sum(1 for line in lines if _PASTED_OUTPUT_LINE.match(line)) >= 2
|
|
423
|
+
|
|
424
|
+
|
|
375
425
|
def prompt_prefetches_org_context(prompt: str) -> bool:
|
|
376
426
|
"""Return whether Observe should prefetch likely organizational context.
|
|
377
427
|
|
|
@@ -384,6 +434,7 @@ def prompt_prefetches_org_context(prompt: str) -> bool:
|
|
|
384
434
|
return bool(
|
|
385
435
|
prompt
|
|
386
436
|
and not _EXPLICIT_SEARCH.search(prompt)
|
|
437
|
+
and not _looks_like_pasted_output(prompt)
|
|
387
438
|
and (
|
|
388
439
|
_ORG_CONTEXT_PREFETCH_PATTERN.search(prompt)
|
|
389
440
|
or _ORG_RITUAL_CONTEXT_PREFETCH_PATTERN.search(prompt)
|
|
@@ -408,8 +459,22 @@ def prompt_requests_actor_context(prompt: str) -> bool:
|
|
|
408
459
|
return bool(prompt and prompt.strip())
|
|
409
460
|
|
|
410
461
|
|
|
411
|
-
|
|
412
|
-
"
|
|
462
|
+
_RECALL_ONLY_SCOPE = (
|
|
463
|
+
"Egregore Runtime is for organizational recall and memory only: team memory, "
|
|
464
|
+
"decisions, handoffs, meetings, prior work. Code search, file reads, Git, tests, "
|
|
465
|
+
"and every other task use the harness's normal tools (Grep, Glob, Read, Bash) "
|
|
466
|
+
"with no retrieval ritual and no Runtime call."
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _actor_context_block(
|
|
471
|
+
runtime: EgregoreRuntime, root: Path, harness: str, *, full: bool = True
|
|
472
|
+
) -> str:
|
|
473
|
+
"""Render the stable identity and the recall contract for harness rituals.
|
|
474
|
+
|
|
475
|
+
``full`` renders the complete contract (once per session); otherwise a
|
|
476
|
+
short reminder that keeps identity and the recall-only scope in view.
|
|
477
|
+
"""
|
|
413
478
|
|
|
414
479
|
actor_context = _actor(runtime, root, harness)
|
|
415
480
|
actor = actor_context.actor
|
|
@@ -429,6 +494,22 @@ def _actor_context_block(runtime: EgregoreRuntime, root: Path, harness: str) ->
|
|
|
429
494
|
search_command = 'bash bin/search.sh query "<concept>" -n 6 --compact'
|
|
430
495
|
if harness == "claude":
|
|
431
496
|
search_command += " --context-packet"
|
|
497
|
+
if not full:
|
|
498
|
+
return "\n".join(
|
|
499
|
+
(
|
|
500
|
+
"EGREGORE_ACTOR_CONTEXT_V1",
|
|
501
|
+
"Reminder — the full contract was attached earlier in this session.",
|
|
502
|
+
f"Actor: {actor.display_name} · Actor ID: {actor.actor_id} · "
|
|
503
|
+
f"Organization ID: {actor_context.profile.org_id} · Runtime root: {root}",
|
|
504
|
+
_RECALL_ONLY_SCOPE,
|
|
505
|
+
"When a prompt needs that recall and no EGREGORE_ORG_CONTEXT_V1 block is "
|
|
506
|
+
f"attached, run exactly one `{search_command}`; exact latest/oldest/open "
|
|
507
|
+
"handoff questions use `bash bin/search.sh handoffs …` instead.",
|
|
508
|
+
"`me` and `myself` mean this Actor ID and display name; never infer identity "
|
|
509
|
+
"from the OS user, Git, or GitHub names.",
|
|
510
|
+
"END_EGREGORE_ACTOR_CONTEXT_V1",
|
|
511
|
+
)
|
|
512
|
+
)
|
|
432
513
|
return "\n".join(
|
|
433
514
|
(
|
|
434
515
|
"EGREGORE_ACTOR_CONTEXT_V1",
|
|
@@ -445,6 +526,7 @@ def _actor_context_block(runtime: EgregoreRuntime, root: Path, harness: str) ->
|
|
|
445
526
|
f"Runtime root: {root}",
|
|
446
527
|
"Run Egregore retrieval and workflow commands from this Runtime root. "
|
|
447
528
|
"Do not fall back to a launch checkout named elsewhere in the session.",
|
|
529
|
+
_RECALL_ONLY_SCOPE,
|
|
448
530
|
"Organizational retrieval status: NOT_PERFORMED. This actor-only block "
|
|
449
531
|
"does not mean organizational recall is unnecessary.",
|
|
450
532
|
"Interpret the user's intent semantically, without requiring trigger words. "
|
|
@@ -681,11 +763,20 @@ def _prompt_hook(args: argparse.Namespace) -> int:
|
|
|
681
763
|
args.limit,
|
|
682
764
|
)
|
|
683
765
|
else:
|
|
766
|
+
marker = _actor_contract_marker_path(root, args.harness)
|
|
767
|
+
full = not marker.exists()
|
|
684
768
|
additional_context = _actor_context_block(
|
|
685
769
|
runtime,
|
|
686
770
|
root,
|
|
687
771
|
args.harness,
|
|
772
|
+
full=full,
|
|
688
773
|
)
|
|
774
|
+
if full:
|
|
775
|
+
try:
|
|
776
|
+
marker.parent.mkdir(parents=True, exist_ok=True)
|
|
777
|
+
marker.write_text(datetime.now(UTC).isoformat(), encoding="utf-8")
|
|
778
|
+
except OSError:
|
|
779
|
+
pass
|
|
689
780
|
except Exception as exc: # Hooks are advisory and must never block a prompt.
|
|
690
781
|
additional_context = (
|
|
691
782
|
"Egregore Runtime context was requested but the Local Runtime is unavailable "
|
package/runtime/codex/AGENTS.md
CHANGED
|
@@ -258,19 +258,21 @@ For each user-directed organizational lookup, show exactly one visible line:
|
|
|
258
258
|
- retrieval that actually queries the connected graph:
|
|
259
259
|
`⌕ Egregore Connect · searching your organization’s memory and relationships`
|
|
260
260
|
|
|
261
|
-
|
|
262
|
-
is only the schema label. When attached, authorized retrieval is complete:
|
|
261
|
+
When `EGREGORE_ORG_CONTEXT_V1` is attached, authorized retrieval is complete:
|
|
263
262
|
never query again that turn, and state any evidence gap. `OPENED_BY_OBSERVE`
|
|
264
263
|
means the evidence is open. A later explicit search starts a new turn.
|
|
265
264
|
|
|
266
|
-
**
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
Shell/tool output does not satisfy this requirement. Do not paraphrase the line.
|
|
265
|
+
**Scope.** Egregore Runtime is for organizational recall only: team memory,
|
|
266
|
+
decisions, handoffs, prior work. Code, files, Git, and tests use the harness's
|
|
267
|
+
normal tools, with no beat and no Runtime call.
|
|
270
268
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
269
|
+
**Visibility is the contract.** Emit it verbatim as a standalone assistant
|
|
270
|
+
message before the first tool call that performs the organizational recall
|
|
271
|
+
(the `bin/search.sh` query or a graph read). Tool output does not satisfy it.
|
|
272
|
+
Do not paraphrase the line.
|
|
273
|
+
|
|
274
|
+
Use Connect only for a graph traversal that actually runs. Emit once per
|
|
275
|
+
episode.
|
|
274
276
|
|
|
275
277
|
**Routing:** Organizational recall always enters Egregore Runtime. The model is
|
|
276
278
|
the semantic intent authority, deciding from meaning rather than fixed phrases.
|
|
@@ -285,15 +287,14 @@ Search output is model-internal: show only the retrieval line and receipts —
|
|
|
285
287
|
never ranked blocks, file/rank/excerpt metadata, or raw JSON.
|
|
286
288
|
|
|
287
289
|
Do not resolve `memory/` to its sibling repo or start with raw `grep`/`ls`.
|
|
288
|
-
Search
|
|
289
|
-
|
|
290
|
+
Search attaches no graph by default. Open a source only when its excerpt
|
|
291
|
+
cannot support a necessary claim. For current
|
|
290
292
|
team/person synthesis, retain known organization/member names and choose
|
|
291
|
-
`--recent-days` plus `--min-evidence`.
|
|
292
|
-
Grep/Glob for code and exact errors. `$activity`, `$dashboard`, and `$project`
|
|
293
|
+
`--recent-days` plus `--min-evidence`. `$activity`, `$dashboard`, and `$project`
|
|
293
294
|
display bounded status surfaces only when named or directly requested as a
|
|
294
|
-
card.
|
|
295
|
-
|
|
296
|
-
|
|
295
|
+
card. Explained syntheses of current organizational work and prior-work
|
|
296
|
+
discovery are Runtime/QMD recall. Do not run retrieval on unrelated prompts or
|
|
297
|
+
infer graph from recall intent.
|
|
297
298
|
|
|
298
299
|
Do not emit the beat for code search, Git inspection, startup hydration, graph
|
|
299
300
|
writes, ingestion, background sync, maintenance, or internal queries.
|
|
@@ -157,9 +157,8 @@ const NON_SKILL_TOKENS = new Set(['loop', 'fast', 'config', 'clear', 'help']);
|
|
|
157
157
|
// hook delivers there. Rewrite the fallback command to the stdout form and
|
|
158
158
|
// state the same hygiene contract in shell terms: search output is
|
|
159
159
|
// model-internal; the visible reply carries only the retrieval line.
|
|
160
|
-
const CLAUDE_PACKET_NOTE = `\`--context-packet\`
|
|
161
|
-
|
|
162
|
-
Never re-print ranked results, file/rank/excerpt metadata, or raw JSON.`;
|
|
160
|
+
const CLAUDE_PACKET_NOTE = `\`--context-packet\` attaches the evidence privately; the visible output is the
|
|
161
|
+
attribution line alone. Never re-print ranked results or raw JSON.`;
|
|
163
162
|
const SHELL_PACKET_NOTE = `Search output is model-internal: show only the retrieval line and receipts —
|
|
164
163
|
never ranked blocks, file/rank/excerpt metadata, or raw JSON.`;
|
|
165
164
|
|
|
@@ -32,6 +32,19 @@ _maintenance_runtime() {
|
|
|
32
32
|
python3 -m egregore_runtime.adapters.qmd_cli "$@"
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
_now_ms() {
|
|
36
|
+
if command -v perl >/dev/null 2>&1; then
|
|
37
|
+
perl -MTime::HiRes=time -e 'printf("%d\n", time() * 1000)'
|
|
38
|
+
else
|
|
39
|
+
echo $(( $(date +%s) * 1000 ))
|
|
40
|
+
fi
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_elapsed_s() {
|
|
44
|
+
# One-decimal seconds since a _now_ms() timestamp.
|
|
45
|
+
awk -v ms="$(( $(_now_ms) - $1 ))" 'BEGIN { printf "%.1f", ms / 1000 }'
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
_result_paths() {
|
|
36
49
|
local line path canonical_prefix
|
|
37
50
|
canonical_prefix="**file:** \`memory/"
|
|
@@ -125,14 +138,14 @@ _banner_and_emit() {
|
|
|
125
138
|
actual="${actual%% *}"
|
|
126
139
|
required="${gap#*required=}"
|
|
127
140
|
required="${required%% *}"
|
|
128
|
-
dur=$(
|
|
141
|
+
dur=$(_elapsed_s "$t0")
|
|
129
142
|
printf '⌕ Egregore · searching your organization’s memory · %s · evidence gap · %s/%s sources · %ss\n\n' \
|
|
130
143
|
"$label" "$actual" "$required" "$dur"
|
|
131
144
|
[ "$hide_results" = 1 ] || printf '%s\n' "$out" | tail -n +2
|
|
132
145
|
return 0
|
|
133
146
|
fi
|
|
134
147
|
hits=$(printf '%s\n' "$out" | _result_paths | wc -l | tr -d ' ')
|
|
135
|
-
dur=$(
|
|
148
|
+
dur=$(_elapsed_s "$t0")
|
|
136
149
|
product="Egregore"
|
|
137
150
|
surface="searching your organization’s memory"
|
|
138
151
|
if [ "$emit" = "_enrich" ] && [ "$hits" -gt 0 ]; then
|
|
@@ -223,7 +236,7 @@ cmd_query() {
|
|
|
223
236
|
esac
|
|
224
237
|
|
|
225
238
|
local t0 out emit="cat" query_rc
|
|
226
|
-
t0=$(
|
|
239
|
+
t0=$(_now_ms)
|
|
227
240
|
out=$(_runtime "${args[@]}" 2>&1)
|
|
228
241
|
query_rc=$?
|
|
229
242
|
if [ "$query_rc" -ne 0 ]; then
|
|
@@ -235,20 +248,31 @@ cmd_query() {
|
|
|
235
248
|
} >&2
|
|
236
249
|
return "$query_rc"
|
|
237
250
|
fi
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
251
|
+
# The runtime marks a query that ran without its persistent worker (the
|
|
252
|
+
# pinned CLI started cold). Strip the marker from the evidence and carry
|
|
253
|
+
# it into the banner, the telemetry, and a one-line repair hint.
|
|
254
|
+
local cold=0 cold_json=false
|
|
255
|
+
if printf '%s\n' "$out" | grep -qx 'EGREGORE_RETRIEVAL_COLD_START'; then
|
|
256
|
+
cold=1
|
|
257
|
+
cold_json=true
|
|
258
|
+
out=$(printf '%s\n' "$out" | grep -vx 'EGREGORE_RETRIEVAL_COLD_START')
|
|
259
|
+
fi
|
|
260
|
+
# Content-free query telemetry: mode, duration, result count, degraded
|
|
261
|
+
# path. Never the query text. This is what makes Runtime/QMD retrieval
|
|
262
|
+
# visible in the telemetry event list instead of only legacy graph_query rows.
|
|
241
263
|
local _tele_count _tele_ms
|
|
242
|
-
_tele_ms=$((
|
|
264
|
+
_tele_ms=$(( $(_now_ms) - t0 ))
|
|
243
265
|
_tele_count=$(printf '%s' "$out" | jq 'if type == "array" then length elif type == "object" then (.results // [] | length) else empty end' 2>/dev/null || true)
|
|
244
266
|
if [[ "$_tele_count" =~ ^[0-9]+$ ]]; then
|
|
245
267
|
bash "$SCRIPT_DIR/bin/telemetry.sh" emit "query" \
|
|
246
|
-
"$(jq -n --arg mode "$label" --argjson d "$_tele_ms" --argjson c "$_tele_count" \
|
|
247
|
-
'{mode:$mode, duration_ms:$d, count:$c}')" 2>/dev/null &
|
|
268
|
+
"$(jq -n --arg mode "$label" --argjson d "$_tele_ms" --argjson c "$_tele_count" --argjson cold "$cold_json" \
|
|
269
|
+
'{mode:$mode, duration_ms:$d, count:$c, degraded:$cold}')" 2>/dev/null &
|
|
248
270
|
else
|
|
249
271
|
bash "$SCRIPT_DIR/bin/telemetry.sh" emit "query" \
|
|
250
|
-
"$(jq -n --arg mode "$label" --argjson d "$_tele_ms"
|
|
272
|
+
"$(jq -n --arg mode "$label" --argjson d "$_tele_ms" --argjson cold "$cold_json" \
|
|
273
|
+
'{mode:$mode, duration_ms:$d, degraded:$cold}')" 2>/dev/null &
|
|
251
274
|
fi
|
|
275
|
+
[ "$cold" = 1 ] && label="${label} · cold start"
|
|
252
276
|
|
|
253
277
|
[ "$enrich" = 1 ] && emit="_enrich"
|
|
254
278
|
if [ "$context_packet" = 1 ]; then
|
|
@@ -263,6 +287,9 @@ cmd_query() {
|
|
|
263
287
|
else
|
|
264
288
|
_banner_and_emit "$label" "$t0" "$emit" "$out"
|
|
265
289
|
fi
|
|
290
|
+
if [ "$cold" = 1 ]; then
|
|
291
|
+
echo " ↳ the retrieval server was not running, so this query started QMD cold. Repair: bash bin/search.sh start" >&2
|
|
292
|
+
fi
|
|
266
293
|
}
|
|
267
294
|
|
|
268
295
|
cmd_reindex() {
|