switchroom 0.19.43 → 0.19.44
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/agent-scheduler/index.js +4 -1
- package/dist/auth-broker/index.js +254 -119
- package/dist/cli/notion-write-pretool.mjs +4 -1
- package/dist/cli/switchroom.js +3281 -2416
- package/dist/host-control/main.js +270 -130
- package/dist/vault/approvals/kernel-server.js +201 -66
- package/dist/vault/broker/server.js +263 -128
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +11 -4
- package/telegram-plugin/dist/gateway/gateway.js +463 -328
- package/vendor/hindsight-memory/scripts/lib/config.py +15 -0
- package/vendor/hindsight-memory/scripts/tests/test_retain_provenance_tag.py +135 -0
- package/vendor/hindsight-memory/settings.json +1 -1
|
@@ -25,9 +25,24 @@ OBSERVATION_SCOPE_STRATEGIES = ("curated", "shared", "combined", "off")
|
|
|
25
25
|
#: value that, if left in scope, defeats cross-session dedup on the dominant
|
|
26
26
|
#: (sidechain) observation path. A tag matching ANY of these is dropped from the
|
|
27
27
|
#: consolidation scope but LEFT on the source fact.
|
|
28
|
+
#:
|
|
29
|
+
#: ``^source:`` (switchroom provenance tagging) is in the list for a different
|
|
30
|
+
#: reason than the other two, and the name of the constant undersells it: what
|
|
31
|
+
#: this list really means is "tags excluded from the CONSOLIDATION SCOPE, kept
|
|
32
|
+
#: on the source fact". ``source:transcript`` is the opposite of volatile — it
|
|
33
|
+
#: is stamped identically on every auto-retain, forever. That is exactly why it
|
|
34
|
+
#: must not reach the scope: a stable tag flips ``curated`` from the bank-wide
|
|
35
|
+
#: ``"shared"`` scope to an explicit ``[["source:transcript"]]`` partition, so
|
|
36
|
+
#: every observation consolidated after the tag ships would be isolated from
|
|
37
|
+
#: every observation consolidated before it. The tag is a RECALL-side filter
|
|
38
|
+
#: handle (``metadata`` is not filterable, tags are), not a consolidation
|
|
39
|
+
#: partition, so scope computation must stay byte-identical to before it
|
|
40
|
+
#: existed. Pinned against ``RETAIN_PROVENANCE_TAG_SCOPE_PATTERN`` in
|
|
41
|
+
#: ``src/memory/hindsight-retain-provenance.ts``.
|
|
28
42
|
DEFAULT_VOLATILE_SCOPE_PATTERNS = (
|
|
29
43
|
r"^parent_session:",
|
|
30
44
|
r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(-sub-.+)?$",
|
|
45
|
+
r"^source:",
|
|
31
46
|
)
|
|
32
47
|
|
|
33
48
|
DEFAULTS = {
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Provenance on auto-retained transcript content (switchroom fix/retain-provenance).
|
|
2
|
+
|
|
3
|
+
The defect: the Stop-hook auto-retain posts the recent transcript window to
|
|
4
|
+
Hindsight, which extracts facts from it — including from the ASSISTANT's own
|
|
5
|
+
synthesis. Those fabrications land as ordinary memory units and recall back next
|
|
6
|
+
session as though a human had asserted them. Nothing on the stored unit said
|
|
7
|
+
"this came out of a transcript": ``tags`` held only the raw session UUID and the
|
|
8
|
+
only other signal, ``session_id``, lived in ``metadata`` — which Hindsight's
|
|
9
|
+
best-practices page states is NOT filterable, while tags ARE.
|
|
10
|
+
|
|
11
|
+
The fix stamps a stable, semantic ``source:transcript`` tag via switchroom's
|
|
12
|
+
``retainTags`` default, so recall/reflect can address it (tag filters, tag
|
|
13
|
+
weights). This module asserts the two OUTCOMES that matter at the retain seam:
|
|
14
|
+
|
|
15
|
+
1. The tag actually reaches the wire payload, alongside the session tag and
|
|
16
|
+
any detected lesson tags (i.e. it composes, it does not clobber).
|
|
17
|
+
2. It does NOT change the observation consolidation scope. This is the
|
|
18
|
+
non-obvious half: ``observationScopeStrategy: "curated"`` builds the scope
|
|
19
|
+
from a retain's STABLE tags, so a permanently-stamped tag would flip every
|
|
20
|
+
bank from the bank-wide ``"shared"`` scope to a ``[["source:transcript"]]``
|
|
21
|
+
partition, isolating every new observation from every observation
|
|
22
|
+
consolidated before the tag shipped. ``^source:`` is therefore excluded
|
|
23
|
+
from the scope in ``DEFAULT_VOLATILE_SCOPE_PATTERNS`` — the tag is a
|
|
24
|
+
recall-side filter handle, not a consolidation partition.
|
|
25
|
+
|
|
26
|
+
Stdlib-only; runs under ``python3 -m unittest discover tests/``.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import os
|
|
30
|
+
import sys
|
|
31
|
+
import unittest
|
|
32
|
+
|
|
33
|
+
SCRIPTS_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
34
|
+
if SCRIPTS_DIR not in sys.path:
|
|
35
|
+
sys.path.insert(0, SCRIPTS_DIR)
|
|
36
|
+
|
|
37
|
+
from lib.config import ( # noqa: E402
|
|
38
|
+
DEFAULTS,
|
|
39
|
+
DEFAULT_VOLATILE_SCOPE_PATTERNS,
|
|
40
|
+
compute_observation_scopes,
|
|
41
|
+
)
|
|
42
|
+
from retain import build_retain_payload # noqa: E402
|
|
43
|
+
|
|
44
|
+
#: What switchroom's ``renderHindsightSettingsOverrides`` stamps into every
|
|
45
|
+
#: agent's deployed settings.json (src/memory/hindsight-retain-provenance.ts).
|
|
46
|
+
PROVENANCE_TAG = "source:transcript"
|
|
47
|
+
SESSION_ID = "4c386b32-ddfd-40d1-b557-da8135b294af"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _cfg(**over):
|
|
51
|
+
c = {
|
|
52
|
+
"retainRoles": ["user", "assistant"],
|
|
53
|
+
"retainToolCalls": True,
|
|
54
|
+
"retainContext": "claude-code",
|
|
55
|
+
"retainMetadata": {},
|
|
56
|
+
"retainTags": ["{session_id}", PROVENANCE_TAG],
|
|
57
|
+
"lessonTagging": DEFAULTS["lessonTagging"],
|
|
58
|
+
"lessonTagMarkers": DEFAULTS["lessonTagMarkers"],
|
|
59
|
+
"observationScopeStrategy": "curated",
|
|
60
|
+
}
|
|
61
|
+
c.update(over)
|
|
62
|
+
return c
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _build(transcript_text="Edited the login handler and ran tests.", **cfg_over):
|
|
66
|
+
msgs = [
|
|
67
|
+
{"role": "user", "content": "what did we change?", "uuid": "u1"},
|
|
68
|
+
{"role": "assistant", "content": transcript_text, "uuid": "a1"},
|
|
69
|
+
]
|
|
70
|
+
built = build_retain_payload(
|
|
71
|
+
_cfg(**cfg_over),
|
|
72
|
+
SESSION_ID,
|
|
73
|
+
msgs,
|
|
74
|
+
msgs,
|
|
75
|
+
bank_id="bank",
|
|
76
|
+
api_url="http://x",
|
|
77
|
+
api_token=None,
|
|
78
|
+
)
|
|
79
|
+
assert built is not None
|
|
80
|
+
return built["payload"]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ProvenanceTagReachesTheWire(unittest.TestCase):
|
|
84
|
+
def test_tag_is_on_the_retain_payload(self):
|
|
85
|
+
self.assertIn(PROVENANCE_TAG, _build()["tags"])
|
|
86
|
+
|
|
87
|
+
def test_session_tag_still_templated_and_kept(self):
|
|
88
|
+
tags = _build()["tags"]
|
|
89
|
+
self.assertIn(SESSION_ID, tags)
|
|
90
|
+
self.assertNotIn("{session_id}", tags)
|
|
91
|
+
|
|
92
|
+
def test_composes_with_detected_lesson_tags(self):
|
|
93
|
+
tags = _build("anti-pattern: narrate without executing")["tags"]
|
|
94
|
+
self.assertIn(PROVENANCE_TAG, tags)
|
|
95
|
+
self.assertIn("anti-pattern", tags)
|
|
96
|
+
self.assertIn(SESSION_ID, tags)
|
|
97
|
+
|
|
98
|
+
def test_absent_when_retain_tags_config_omits_it(self):
|
|
99
|
+
# The tag is data, not hardcoded behaviour: retain.py has no knowledge
|
|
100
|
+
# of it beyond honouring the configured retainTags. That keeps the
|
|
101
|
+
# vendored plugin generic and the switchroom stamp the single source.
|
|
102
|
+
tags = _build(retainTags=["{session_id}"])["tags"]
|
|
103
|
+
self.assertNotIn(PROVENANCE_TAG, tags)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ProvenanceTagIsScopeNeutral(unittest.TestCase):
|
|
107
|
+
"""The tag must not silently re-partition observation consolidation."""
|
|
108
|
+
|
|
109
|
+
def test_curated_scope_stays_shared_with_the_provenance_tag(self):
|
|
110
|
+
payload = _build()
|
|
111
|
+
self.assertEqual(payload["observation_scopes"], "shared")
|
|
112
|
+
|
|
113
|
+
def test_scope_is_byte_identical_to_a_bank_without_the_tag(self):
|
|
114
|
+
with_tag = _build()["observation_scopes"]
|
|
115
|
+
without = _build(retainTags=["{session_id}"])["observation_scopes"]
|
|
116
|
+
self.assertEqual(with_tag, without)
|
|
117
|
+
|
|
118
|
+
def test_a_stable_tag_that_is_not_excluded_would_have_changed_the_scope(self):
|
|
119
|
+
# Guards the guard: proves the "shared" result above is caused by the
|
|
120
|
+
# exclusion pattern and not by curated ignoring stable tags generally.
|
|
121
|
+
scope, err = compute_observation_scopes(
|
|
122
|
+
[SESSION_ID, "project:switchroom"], _cfg()
|
|
123
|
+
)
|
|
124
|
+
self.assertIsNone(err)
|
|
125
|
+
self.assertEqual(scope, [["project:switchroom"]])
|
|
126
|
+
|
|
127
|
+
def test_exclusion_pattern_is_present_and_matches_the_tag(self):
|
|
128
|
+
self.assertIn(r"^source:", DEFAULT_VOLATILE_SCOPE_PATTERNS)
|
|
129
|
+
scope, err = compute_observation_scopes([SESSION_ID, PROVENANCE_TAG], _cfg())
|
|
130
|
+
self.assertIsNone(err)
|
|
131
|
+
self.assertEqual(scope, "shared")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__": # pragma: no cover
|
|
135
|
+
unittest.main()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"hindsightApiUrl": "",
|
|
3
3
|
"bankId": "claude_code",
|
|
4
4
|
"bankMission": "You are a Claude Code AI assistant. Focus on technical discussions, decisions, and context relevant to the user's projects.",
|
|
5
|
-
"retainMission": "Extract durable facts that will still be true and useful weeks from now, once this session is forgotten.\n\nDURABILITY GATE. Emit a candidate ONLY if it is one of these five classes:\n- PREFERENCE — what the user likes, wants, or always does; a standing rule or correction.\n- DECISION — a settled choice that changes how future work is done, including a choice NOT to do something. A decision about the mechanics of the CURRENT task (which worker to dispatch, which branch to rebase, which PR to merge now, what to do next) is process narration, not a durable decision — drop it unless it establishes a standing rule or permanently changes a system.\n- FINDING — a root cause, a measurement, or verified behaviour of a system. Include the number.\n- OUTCOME — a completed result that changed the world: what shipped, what a thing turned out to be. Not the act of shipping it.\n- RELATIONSHIP — who a person is, what a project or tool is, and how they connect.\nIf a candidate fits none of the five, it is not a memory. Drop it; do not reword it into one.\n\nA preference revealed by a request is durable — record the preference (what the user likes, wants, or always does), not the request itself.\n\nA TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this\ncandidate a file path, a command/process/agent/session id, a temp directory, or\nthe location where some output was written? If yes, drop it — it is transcript\nexhaust, not memory.\n\nNEVER extract:\n- Tool results verbatim or paraphrased. Concretely, never produce a fact whose\n text resembles any of these: \"File created successfully at /path/to/file\",\n \"A background command with ID bctz4yskm is running, and its output will be\n written to /tmp/...\", \"Async agent a745598ba84e71df1 was launched successfully\n and is running in the background\", \"User executed a Bash command to sleep for\n 200 seconds\", \"The assistant used grep to locate 'truncateSync' in src/foo.ts\".\n- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.\n- Agent tool-use traces or narration of what the assistant did (e.g. \"the\n assistant used X to query Y\", \"ran a search\", \"sent the message\").\n- The act of delegating, dispatching, spawning, launching, steering or merging\n work — including when it succeeded. \"X was dispatched and completed\" is the\n session describing itself. Record only what the work LEARNED or CHANGED.\n- In-flight workflow/process narration (a sub-task started, paused, or is still\n running) — retain the outcome only once the task completes or a decision is made.\n- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.\n- Slash commands the user typed and their effects (e.g. \"User issued /clear to\n reset assistant state\").\n- Hindsight's own errors, retries, backlogs, or internal state — the memory\n system's self-reports are not memories.\n- Restatements of the user's current request or the task in progress.\n- Volatile state written as a timeless assertion. A version, count, size,\n backlog, status, or any \"X is running Y\" / \"X is at Y\" / \"X is currently Y\"\n claim is true only at the instant it was said. Concretely, never produce a\n fact whose text resembles any of these: \"Switchroom fleet is running image\n version v0.18.19\", \"The switchroom repo is at /path/to/fleet, version\n v0.19.5\", \"Bank overlord has 43155 pending consolidations\", \"The build is\n currently green\". If the claim is worth keeping, put the date INSIDE the\n fact text (\"As of 2026-07-19 the fleet was running v0.18.19\"); if you\n cannot date it, drop it. An undated one is recalled forever as though it\n were still true, which is worse than not remembering it at all.\n- Transient state (unread counts, build status, what is running right now) unless\n the fact is explicitly dated, in which case record it as a dated observation.\n- Greetings, acknowledgements, and routine operational chatter.\n\nWrite each fact so it stands alone: name the thing, the number, and the date. A\nsentence that only makes sense while reading this transcript is not durable.\n\nIf a candidate fact matches an exclusion, drop it rather than rewording it. If\nnothing durable remains, return an empty facts list.\n",
|
|
5
|
+
"retainMission": "Extract durable facts that will still be true and useful weeks from now, once this session is forgotten.\n\nDURABILITY GATE. Emit a candidate ONLY if it is one of these five classes:\n- PREFERENCE — what the user likes, wants, or always does; a standing rule or correction.\n- DECISION — a settled choice that changes how future work is done, including a choice NOT to do something. A decision about the mechanics of the CURRENT task (which worker to dispatch, which branch to rebase, which PR to merge now, what to do next) is process narration, not a durable decision — drop it unless it establishes a standing rule or permanently changes a system.\n- FINDING — a root cause, a measurement, or verified behaviour of a system. Include the number.\n- OUTCOME — a completed result that changed the world: what shipped, what a thing turned out to be. Not the act of shipping it.\n- RELATIONSHIP — who a person is, what a project or tool is, and how they connect.\nIf a candidate fits none of the five, it is not a memory. Drop it; do not reword it into one.\n\nA preference revealed by a request is durable — record the preference (what the user likes, wants, or always does), not the request itself.\n\nA TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this\ncandidate a file path, a command/process/agent/session id, a temp directory, or\nthe location where some output was written? If yes, drop it — it is transcript\nexhaust, not memory.\n\nNEVER extract:\n- Tool results verbatim or paraphrased. Concretely, never produce a fact whose\n text resembles any of these: \"File created successfully at /path/to/file\",\n \"A background command with ID bctz4yskm is running, and its output will be\n written to /tmp/...\", \"Async agent a745598ba84e71df1 was launched successfully\n and is running in the background\", \"User executed a Bash command to sleep for\n 200 seconds\", \"The assistant used grep to locate 'truncateSync' in src/foo.ts\".\n- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.\n- Agent tool-use traces or narration of what the assistant did (e.g. \"the\n assistant used X to query Y\", \"ran a search\", \"sent the message\").\n- The act of delegating, dispatching, spawning, launching, steering or merging\n work — including when it succeeded. \"X was dispatched and completed\" is the\n session describing itself. Record only what the work LEARNED or CHANGED.\n- In-flight workflow/process narration (a sub-task started, paused, or is still\n running) — retain the outcome only once the task completes or a decision is made.\n- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.\n- Slash commands the user typed and their effects (e.g. \"User issued /clear to\n reset assistant state\").\n- Hindsight's own errors, retries, backlogs, or internal state — the memory\n system's self-reports are not memories.\n- Restatements of the user's current request or the task in progress.\n- The assistant's own answers, summaries, recaps, or reflect output. Before\n extracting, ask: does anything in this transcript support this claim OTHER\n than the assistant having asserted it? If not, drop it — a model's own\n synthesis re-extracted as a fact is how a guess becomes permanent.\n Concretely, never produce a fact whose only support is the assistant\n stating a date, a version, an attribution, a total, or a decision that the\n user never confirmed and no tool output shows. An unverified claim recalls\n later as though it had been established, which is worse than not\n remembering it at all.\n- Volatile state written as a timeless assertion. A version, count, size,\n backlog, status, or any \"X is running Y\" / \"X is at Y\" / \"X is currently Y\"\n claim is true only at the instant it was said. Concretely, never produce a\n fact whose text resembles any of these: \"Switchroom fleet is running image\n version v0.18.19\", \"The switchroom repo is at /path/to/fleet, version\n v0.19.5\", \"Bank overlord has 43155 pending consolidations\", \"The build is\n currently green\". If the claim is worth keeping, put the date INSIDE the\n fact text (\"As of 2026-07-19 the fleet was running v0.18.19\"); if you\n cannot date it, drop it. An undated one is recalled forever as though it\n were still true, which is worse than not remembering it at all.\n- Transient state (unread counts, build status, what is running right now) unless\n the fact is explicitly dated, in which case record it as a dated observation.\n- Greetings, acknowledgements, and routine operational chatter.\n\nWrite each fact so it stands alone: name the thing, the number, and the date. A\nsentence that only makes sense while reading this transcript is not durable.\n\nIf a candidate fact matches an exclusion, drop it rather than rewording it. If\nnothing durable remains, return an empty facts list.\n",
|
|
6
6
|
"autoRecall": true,
|
|
7
7
|
"autoRetain": true,
|
|
8
8
|
"retainMode": "full-session",
|