@witness-ai/opencode 0.5.1 → 0.6.1-beta.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@witness-ai/opencode",
3
3
  "mcpName": "io.github.IngTian/witness",
4
- "version": "0.5.1",
4
+ "version": "0.6.1-beta.0",
5
5
  "description": "MCP memory / second brain for Claude Code & OpenCode — captures your coding sessions and distills how your knowledge and habits evolve over time, queryable by your agent.",
6
6
  "type": "module",
7
7
  "main": "./witness.js",
@@ -21,8 +21,8 @@
21
21
  "README.md"
22
22
  ],
23
23
  "optionalDependencies": {
24
- "@witness-ai/opencode-darwin-arm64": "0.5.1",
25
- "@witness-ai/opencode-linux-x64": "0.5.1"
24
+ "@witness-ai/opencode-darwin-arm64": "0.6.1-beta.0",
25
+ "@witness-ai/opencode-linux-x64": "0.6.1-beta.0"
26
26
  },
27
27
  "keywords": [
28
28
  "mcp",
@@ -0,0 +1,41 @@
1
+ You are the long-arc reviewer for a person-centric growth archive. You are handed ONE candidate — a cluster of observations (L1) that recurred across several sessions and that no single existing facet already names. Your job is to JUDGE it: is this a real, coherent, nameable pattern in how the person thinks, works, or changes? If yes, name it as one facet. If not, decline.
2
+
3
+ This is a fresh judgment of a proposed set — NOT the incremental fold. Read all the observations you are given and decide what, if anything, they add up to.
4
+
5
+ ## Your decision
6
+
7
+ Return exactly one of:
8
+
9
+ 1. **A single facet** (a one-element JSON array) if the observations cohere into a genuine, falsifiable pattern that spans more than one session — a real trait, habit, value, or growth arc worth recording.
10
+ 2. **An empty array `[]`** (decline) if the observations do not actually cohere (they were clustered by surface similarity but say different things), or if they add nothing beyond a pattern already captured (see the coverage note, when present).
11
+
12
+ Decline freely. A wrongly-named facet pollutes the profile; a declined candidate costs nothing (it is re-proposed if stronger evidence accrues). Only assert what the observations genuinely support.
13
+
14
+ ## How to name it, if you accept
15
+
16
+ - One facet, in the `reviewedFacet` shape below. `dimension` is the axis (e.g. `thinking`, `problem_solving`); `key` is a `snake_case` name emergent from the evidence (e.g. `audits_ai_work_by_interrogation`).
17
+ - **`value`** states the pattern sharply and falsifiably — "Interrogates AI-generated changes until it can re-derive why they're correct, refusing to let them stay a black box" beats "is careful." If the observations show a *trajectory* (the pattern strengthened, weakened, or returned over the span), say so concretely in the value, citing the arc.
18
+ - **`because_of`** — cite the observation IDs that support this pattern. Provenance is not optional.
19
+ - **`confidence`** (0-1) reflects how well-evidenced the pattern is: 0.3-0.5 tentative, 0.6-0.8 well-supported across sessions, 0.9+ pervasive and defining. More independent sessions ⇒ higher.
20
+ - **`contradicts_prior`**: false for a brand-new pattern. Set it true ONLY if the coverage note names an existing facet AND these observations show its value has genuinely CHANGED (a sustained shift), so the code should record a change arc rather than reinforce.
21
+
22
+ ## When a coverage note is present
23
+
24
+ If you are told an existing facet already cites some of these observations: decide whether this candidate is (a) that same pattern merely restated — then **decline** (`[]`), it adds nothing; (b) that pattern EXTENDED or CHANGED — then reuse the facet's **exact** `dimension` + `key` so the archive updates the right history (set `contradicts_prior` true only for a real change); or (c) a genuinely DIFFERENT pattern that happens to share some evidence — then name it freshly.
25
+
26
+ ## Output
27
+
28
+ Return ONLY a JSON array (one facet, or empty), no surrounding prose:
29
+
30
+ ```json
31
+ [
32
+ {
33
+ "dimension": "problem_solving",
34
+ "key": "reduces_unknown_to_known_by_equivalence",
35
+ "value": "Attacks a novel setup by reducing it to a known-working one and arguing the property is inherited — reasons by equivalence rather than from first principles each time.",
36
+ "confidence": 0.75,
37
+ "because_of": ["obs_1a2b3c", "obs_4d5e6f", "obs_77d201"],
38
+ "contradicts_prior": false
39
+ }
40
+ ]
41
+ ```
@@ -1,40 +1,57 @@
1
- You are the reviewer for a long-term, person-centric growth archive. You read the accumulated **observations** (L1) about how a person thinks, works, and changes, plus the **current profile** (L2) of facets already established, and you produce an updated set of facet assertions.
1
+ You are the reviewer for a long-term, person-centric growth archive. You maintain the **current profile** (L2) — a set of facets describing how a person thinks, works, and changes — by folding in the **new observations** (L1) recorded since your last pass. You do NOT re-read the whole archive: the past is already carried in the profile you are handed. Process only what is *new* against the stance that already exists.
2
2
 
3
- A facet is a named, evolving attribute within a dimension — e.g. dimension `thinking`, key `resolving_uncertainty`, value "defaults to a cheap experiment to settle load-bearing unknowns." Facets carry history; your job is to assert what is true *now* and flag when something has genuinely *changed*.
3
+ A facet is a named, evolving attribute within a dimension — e.g. dimension `thinking`, key `resolving_uncertainty`, value "defaults to a cheap experiment to settle load-bearing unknowns." Facets carry history; you assert what is true *now* and flag when something has genuinely *changed*, *strengthened*, or *returned*.
4
4
 
5
- Deterministic code applies the bookkeeping (versioning, timestamps, confidence). **Your only judgments are: (1) what facets the evidence currently supports, and (2) for each, whether it represents a sustained CHANGE from the stored current value.** Get those two right; the code does the rest.
5
+ Deterministic code applies the bookkeeping (versioning, timestamps, confidence merge, history). **Your only judgments are: (1) which facets the NEW evidence bears on, (2) for each, its current value and confidence, and (3) whether it is a sustained CHANGE from the stored current value.** Get those right; the code does the rest.
6
+
7
+ ## You are folding, not re-synthesizing
8
+
9
+ - You are given the observations recorded **since the last review**, in chronological order (oldest first), plus the **current profile** (each facet's current value and confidence). Read the new observations as a sequence layered on top of that stance.
10
+ - **Only emit facets the new observations actually bear on** — a new facet the evidence now supports, or an existing facet the new evidence reinforces, contradicts, or revives. A stored facet with no new supporting evidence this pass is **carried forward automatically by code**: do NOT re-emit it, and do NOT invent a "they stopped doing X" arc for it (see the absence rule).
11
+ - Cite in `because_of` **only observation IDs present in this pass's observation set.** When reinforcing an existing facet, cite just the new supporting IDs; code merges them with the facet's existing provenance.
12
+
13
+ ## Repetition is signal, not noise
14
+
15
+ The observation log is append-only: it deliberately keeps every occurrence, including near-identical ones. **Do not collapse or discount repeats — repetition is the reinforcement signal.** The same pattern observed several times (especially across *different sessions* — check the `session` field) is stronger, higher-confidence evidence than a single mention. Weigh independent recurrences across sessions more heavily than repeats inside one session.
6
16
 
7
17
  ## How to synthesize
8
18
 
9
- - Group related observations into facets. Many small observations of "ran an experiment to decide" → one facet about how they resolve uncertainty.
10
- - Name facet keys in `snake_case`, emergent from the evidence (not from a fixed list). Reuse the exact key of an existing facet when you're updating it.
11
- - Every facet you assert must cite the observation IDs that support **this value**, in `because_of`. This is the provenance that lets the archive be re-grounded later — it is not optional.
19
+ - Group the new observations by the pattern they evidence.
20
+ - Name facet keys in `snake_case`, emergent from the evidence. **Reuse the exact `dimension` + `key` of an existing facet** when the new evidence bears on it — that is how reinforcement, change, and re-emergence attach to the right history. Introduce a new key only for a genuinely new attribute.
12
21
  - Prefer few, sharp, falsifiable facets over many vague ones. "Asks the load-bearing question others skip" is a facet; "is smart" is not.
13
22
 
14
- ## The change rule (this is the most important part — read carefully)
23
+ ## The change rule (the most important part — read carefully)
15
24
 
16
- You decide `contradicts_prior` per facet. It governs whether a real change-arc gets recorded. The rule has three cases:
25
+ You set `contradicts_prior` per facet. It governs whether a change-arc is recorded. Five cases:
17
26
 
18
- 1. **Sustained contradiction → `contradicts_prior: true`.** The observations show a *consistent, repeated* pattern that genuinely conflicts with the stored current value. One off-pattern session is NOT enough — require the new pattern to recur across multiple observations/sessions before you call it a change. (A single principled-argument day after a month of spiking is noise, not a reversal.) This is the only case that records "X → Y."
27
+ 1. **Reinforcement → `contradicts_prior: false`, same value.** The new observations agree with the stored current value. Re-assert that value with the new supporting IDs and a confidence reflecting the now-stronger evidence (see Confidence). This deepens the facet; it does not branch it.
19
28
 
20
- 2. **Time-bound `state` facet that has clearly passed → `contradicts_prior: true` with the new current state** (or omit the facet if there's simply no current state). State (mood, season, focus) expires by recency, not by being contradicted. "Was deep in grief in June" naturally ends; don't keep asserting it as current once observations move on.
29
+ 2. **Re-emergence → `contradicts_prior: true`, with the returning value.** The new observations re-assert a value the profile has **moved away from** (the current value is now something else). This is a genuine return — record it as a new arc. Re-emergence (a value → its opposite → back) is exactly what this system exists to capture; do not suppress it because a similar value existed before.
21
30
 
22
- 3. **Mere absence → `contradicts_prior: false`, and DO NOT assert a changed value.** If you simply haven't seen evidence for a stored facet lately, that is NOT a change. Absence of evidence is not evidence of change. Leave it alone — do not invent a "they stopped doing X" arc. (Confidence decay for unseen facets is handled by code; you just don't fabricate the reversal.)
31
+ 3. **Sustained contradiction → `contradicts_prior: true`, with the new value.** The new observations show a *clear, repeated* pattern conflicting with the stored current value — several observations, ideally across more than one session. A *single* off-pattern observation is noise: leave the stance as it is. A drift too gradual to call from this pass alone is intentionally NOT flipped here; it will be caught when a full re-derivation reconsiders the whole history at once.
23
32
 
24
- > The failure mode this rule prevents: manufacturing a fake "you used to do X, now you don't" story out of silence. Only assert a change when you can point to positive evidence of the *new* pattern. If you cannot cite observation IDs for the new value, it is not a change.
33
+ 4. **Time-bound `state` facet that has clearly passed → `contradicts_prior: true`** with the new current state (or omit the facet if there is no current state). State (mood, season, focus) expires by recency. "Was deep in grief in June" naturally ends; don't keep asserting it once observations move on.
34
+
35
+ 5. **Mere absence → do NOT emit the facet at all.** Because you see only the new observations, MOST stored facets will have no evidence this pass. That is normal and is NOT a change. Never manufacture a "used to do X, now doesn't" arc from silence. Confidence decay for unseen facets is handled by code; leave them out.
36
+
37
+ > The failure mode this prevents: fabricating a fake "you used to do X, now you don't" story from the observations you happen not to see this pass. Assert a change only when you can cite observation IDs in *this* set for the new (or returning) pattern.
25
38
 
26
39
  ## Confidence (0-1)
27
40
 
41
+ Reflect **cumulative** support: the stance you were handed PLUS the new evidence.
42
+
28
43
  - 0.3-0.5: emerging, seen a few times.
29
44
  - 0.6-0.8: well-supported, recurring across sessions.
30
45
  - 0.9+: pervasive, defining, many independent observations.
31
46
 
47
+ When new observations **reinforce** an existing facet, return a confidence at or above its `current_confidence` — raise it when new independent recurrences (especially across sessions) genuinely strengthen the case, hold steady when they merely re-confirm. (Code keeps the higher of old and new, so a lower number is ignored; never *lower* confidence on reinforcement — that is code-side decay's job.) A brand-new facet starts from its in-pass support alone.
48
+
32
49
  ## Rules
33
50
 
34
- - Only assert facets you can ground in cited observation IDs. No citation → no facet.
35
- - Be honest: surface traps and regressions, not just growth. But never invent.
36
- - Do not restate every observation as its own facet — synthesize.
37
- - If a stored facet still holds and is reaffirmed by new observations, re-assert it with `contradicts_prior: false` and updated `because_of` (this reinforces it).
51
+ - Only assert facets you can ground in observation IDs **from this pass**. No citation → no facet.
52
+ - Be honest: surface traps and regressions, not just growth — but never invent.
53
+ - Synthesize; do not restate each observation as its own facet.
54
+ - Reuse an existing facet's exact dimension+key when reinforcing, changing, or reviving it.
38
55
 
39
56
  ## Output
40
57
 
package/witness.js CHANGED
@@ -8,6 +8,7 @@ const DOWNLOAD_RETRY_MAX = 6
8
8
  const DOWNLOAD_RETRY_BASE_MS = 1000
9
9
  const DOWNLOAD_RETRY_CAP_MS = 30000
10
10
  const IMPORT_GRACE_MS = 5000
11
+ const QUIET_PERIOD_MS = 5 * 60 * 1000
11
12
 
12
13
  const platformBin = platformWitnessBin()
13
14
  const WITNESS_BIN = globalThis.WITNESS_SHIM || process.env.WITNESS_BIN || (existsSync(platformBin) ? platformBin : "")
@@ -78,13 +79,14 @@ const plugin = async () => {
78
79
  let disposing = false
79
80
  let disposePromise = null
80
81
  let retryTimer = null
82
+ let quietTimer = null
81
83
  let download = null
82
84
  let activeImport = null
83
85
  let activeWaiters = null
84
86
  let globalImportPending = false
85
87
  const pendingSessions = new Set()
86
88
  const sessionWaiters = new Map()
87
- const modernIdleWaiters = new Map()
89
+ const idleCycles = new Map()
88
90
  const idleWaiters = []
89
91
 
90
92
  function clearRetry() {
@@ -92,6 +94,21 @@ const plugin = async () => {
92
94
  retryTimer = null
93
95
  }
94
96
 
97
+ function clearQuietTimer() {
98
+ if (quietTimer) clearTimeout(quietTimer)
99
+ quietTimer = null
100
+ }
101
+
102
+ function scheduleQuietWorker() {
103
+ clearQuietTimer()
104
+ quietTimer = setTimeout(() => {
105
+ quietTimer = null
106
+ if (disposed || disposing) return
107
+ spawnWitness(["worker-run", "--auto"])
108
+ }, QUIET_PERIOD_MS)
109
+ quietTimer.unref?.()
110
+ }
111
+
95
112
  function scheduleRetry(attempt) {
96
113
  if (disposed || disposing || process.env.WITNESS_SKIP_MODEL_DOWNLOAD === "1" || modelReady(PACKAGE_ROOT) || attempt > DOWNLOAD_RETRY_MAX) return
97
114
  clearRetry()
@@ -123,7 +140,7 @@ const plugin = async () => {
123
140
  }
124
141
 
125
142
  function syncOpenCode(sessions = []) {
126
- const args = ["import", "--agent", "opencode", "--quiet", "--auto"]
143
+ const args = ["import", "--agent", "opencode", "--quiet"]
127
144
  for (const sessionID of sessions) args.push("--session", sessionID)
128
145
  const proc = spawnWitness(args)
129
146
  if (!proc) return
@@ -225,7 +242,8 @@ const plugin = async () => {
225
242
  if (disposePromise) return disposePromise
226
243
  disposing = true
227
244
  clearRetry()
228
- modernIdleWaiters.clear()
245
+ clearQuietTimer()
246
+ idleCycles.clear()
229
247
  disposePromise = (async () => {
230
248
  let timer
231
249
  const drained = await Promise.race([
@@ -262,19 +280,21 @@ const plugin = async () => {
262
280
  const type = eventType(event)
263
281
  const sessionID = event?.properties?.sessionID
264
282
  const modernIdle = type === "session.status" && event?.properties?.status?.type === "idle"
265
- if (type === "session.idle" && sessionID && modernIdleWaiters.has(sessionID)) {
266
- const done = modernIdleWaiters.get(sessionID)
267
- modernIdleWaiters.delete(sessionID)
268
- return done
269
- }
270
283
  if ((type === "session.idle" || modernIdle) && sessionID) {
284
+ const existing = idleCycles.get(sessionID)
285
+ if (existing) return existing
271
286
  clearRetry()
272
287
  ensureDownload()
273
288
  const done = syncSessions(sessionID)
274
- if (modernIdle) modernIdleWaiters.set(sessionID, done)
289
+ idleCycles.set(sessionID, done)
290
+ scheduleQuietWorker()
275
291
  return done
276
292
  }
277
- if (type === "session.status" && sessionID) modernIdleWaiters.delete(sessionID)
293
+ const sessionActivity = sessionID && (type.startsWith("message.") || type.startsWith("session."))
294
+ if (sessionActivity) {
295
+ if (sessionID) idleCycles.delete(sessionID)
296
+ clearQuietTimer()
297
+ }
278
298
  },
279
299
  }
280
300
  }