@qvac/harness 0.2.8 → 0.2.10

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.
Files changed (76) hide show
  1. package/README.md +15 -7
  2. package/dist/harness.bundle +13 -12
  3. package/dist-lib/index.d.ts +2 -1
  4. package/dist-lib/index.js +2 -1
  5. package/dist-lib/lib/capabilities/completion.d.ts +36 -2
  6. package/dist-lib/lib/capabilities/completion.js +150 -27
  7. package/dist-lib/lib/connect.js +3 -1
  8. package/dist-lib/lib/contract-types.d.ts +2 -0
  9. package/dist-lib/lib/docx/index.js +4 -27
  10. package/dist-lib/lib/hooks/built-in/index.d.ts +1 -0
  11. package/dist-lib/lib/hooks/built-in/index.js +1 -0
  12. package/dist-lib/lib/hooks/built-in/sandbox.d.ts +5 -0
  13. package/dist-lib/lib/hooks/built-in/sandbox.js +43 -0
  14. package/dist-lib/lib/hooks/built-in/skill-catalog.js +7 -2
  15. package/dist-lib/lib/hooks/registry.js +2 -1
  16. package/dist-lib/lib/hooks/types.d.ts +1 -0
  17. package/dist-lib/lib/office/index.d.ts +6 -0
  18. package/dist-lib/lib/office/index.js +38 -0
  19. package/dist-lib/lib/office/pptx.d.ts +1 -0
  20. package/dist-lib/lib/office/pptx.js +26 -0
  21. package/dist-lib/lib/office/python.d.ts +7 -0
  22. package/dist-lib/lib/office/python.js +32 -0
  23. package/dist-lib/lib/office/xlsx.d.ts +1 -0
  24. package/dist-lib/lib/office/xlsx.js +26 -0
  25. package/dist-lib/lib/serve.js +1 -0
  26. package/dist-lib/lib/tools/edit-image.js +1 -1
  27. package/dist-lib/lib/tools/generate-image.js +1 -1
  28. package/dist-lib/lib/tools/generate-music.js +1 -1
  29. package/dist-lib/lib/tools/http-request.js +3 -3
  30. package/dist-lib/lib/tools/mcp-call.js +3 -3
  31. package/dist-lib/lib/tools/skill.js +4 -2
  32. package/dist-lib/lib/tools/skills/auth-resolver.d.ts +1 -0
  33. package/dist-lib/lib/tools/skills/auth-resolver.js +59 -29
  34. package/dist-lib/lib/tools/toolsets.d.ts +1 -0
  35. package/dist-lib/lib/tools/toolsets.js +1 -0
  36. package/dist-lib/lib/tools/weather-lookup.js +9 -3
  37. package/dist-lib/lib/tools/web-fetch.js +6 -3
  38. package/dist-lib/lib/tools/web-search.js +5 -2
  39. package/dist-lib/lib/wire.js +28 -0
  40. package/dist-lib/schema/harness.js +7 -3
  41. package/dist-lib/spec/hrpc/messages.js +28 -10
  42. package/dist-lib/spec/hyperschema/index.js +28 -10
  43. package/dist-lib/spec/hyperschema/schema.json +19 -1
  44. package/dist-lib/spec/hyperschema/types.d.ts +4 -2
  45. package/package.json +10 -6
  46. package/.claude/skills/asana/SKILL.md +0 -86
  47. package/.claude/skills/gh-stack/SKILL.md +0 -181
  48. package/.claude/skills/gh-stack/references/commands.md +0 -177
  49. package/.claude/skills/gh-stack/references/stack-design.md +0 -97
  50. package/.claude/skills/gh-stack/references/troubleshooting.md +0 -159
  51. package/.claude/skills/js-house-style/SKILL.md +0 -671
  52. package/.claude/skills/kvcache/SKILL.md +0 -171
  53. package/.claude/skills/pr-create/SKILL.md +0 -180
  54. package/.claude/skills/pr-review/SKILL.md +0 -414
  55. package/CLAUDE.md +0 -39
  56. package/docs/decisions/2026-05-29-kvcache-tool-turns.md +0 -75
  57. package/docs/decisions/2026-06-02-kvcache-tool-turns-reenabled.md +0 -127
  58. package/docs/decisions/2026-06-23-android-cpu-llm-defaults.md +0 -126
  59. package/docs/decisions/2026-07-29-assistant-owned-storage-layout.md +0 -88
  60. package/docs/decisions/2026-08-04-sidecar-orphan-reaping.md +0 -79
  61. package/docs/decisions/2026-08-11-pyodide-worker-thread.md +0 -68
  62. package/docs/decisions/2026-08-17-harness-hook-pipeline.md +0 -84
  63. package/docs/decisions/2026-08-17-harness-outbound-egress-guard.md +0 -176
  64. package/docs/decisions/2026-08-20-chat-prompt-prefix-is-stable.md +0 -95
  65. package/docs/decisions/2026-08-20-kvcache-static-placement.md +0 -109
  66. package/docs/decisions/2026-08-21-vendored-python-wheels.md +0 -85
  67. package/docs/decisions/2026-08-26-exec-is-not-gated-by-skills.md +0 -64
  68. package/docs/decisions/2026-08-27-writable-skills-overlay.md +0 -56
  69. package/docs/decisions/2026-09-01-skill-operation-manifests.md +0 -53
  70. package/docs/decisions/2026-09-01-usage-breakdown-is-measured.md +0 -78
  71. package/docs/decisions/2026-09-03-usage-breakdown-reported-by-the-harness.md +0 -136
  72. package/docs/decisions/2026-09-10-skill-wording-is-measured-offline.md +0 -63
  73. package/docs/decisions/2026-09-16-a-lookup-reports-the-place-it-resolved.md +0 -70
  74. package/docs/decisions/INDEX.md +0 -40
  75. package/docs/decisions/TEMPLATE.md +0 -32
  76. package/docs/kvcache-reference.md +0 -655
@@ -1,171 +0,0 @@
1
- ---
2
- name: kvcache
3
- description: Reason about the QVAC SDK KV cache, the context window and ContextOverflow in @qvac/harness — including why the sliding window (n_discarded) and dynamic tools (toolsMode) were retired. Use when touching prompt-cache keys, the overflow ladder, TTFT regressions, context-overflow errors, cache invalidation on retry/regenerate, or when debugging "the model forgot the conversation". Read this BEFORE proposing any caller-side history eviction.
4
- ---
5
-
6
- # KV cache + context window
7
-
8
- Read **[docs/kvcache-reference.md](../../../docs/kvcache-reference.md)** — how it
9
- works and the rules for using it. The summary below is just enough to stop the
10
- common wrong turns.
11
-
12
- This repo owns the cache: `lib/capabilities/completion.ts` (the `kvCache` key,
13
- the round loop, the overflow ladder), `lib/context-window.ts`,
14
- `lib/models/kv-cache.ts` and `lib/models/model-pool.ts`. The _prompt_ side —
15
- folding a chat's history so it fits — is `@qvac/core → lib/compaction.ts`;
16
- see its **compaction** skill before proposing anything that shrinks history.
17
-
18
- ## The one thing to internalise
19
-
20
- Three state sets exist and are **never reconciled**:
21
-
22
- | Layer | State | Keyed by |
23
- | --------------------- | ---------------------------------------------------- | --------------- |
24
- | caller (`@qvac/core`) | message **list** (shrinks only on an overflow retry) | chat |
25
- | SDK | message **count** (`savedCount`) | cache file path |
26
- | addon | token **positions** (`nPast`) + the on-disk `.bin` | cache key |
27
-
28
- Almost every bug here is drift between those three, not context pressure.
29
- `savedCount` means "already sent", never "still resident": nothing below the
30
- SDK reports what it evicted. That is what made the retired sliding window and
31
- dynamic-tools trim fatal, and it is the test for any new eviction idea.
32
-
33
- ## The knobs (two)
34
-
35
- - `history`: the full history goes to `completion()`; the SDK computes the delta.
36
- - `cacheKey: 'chat-<chatId>'` — set by the caller, consumed here at
37
- [lib/capabilities/completion.ts](../../../lib/capabilities/completion.ts).
38
- An accepted diagram repair primes under a child of it (`chat-<chatId>/r<n>`),
39
- so deleting the chat key reclaims every generation.
40
-
41
- Nothing else. `n_discarded` and `toolsMode` are gone from the stack, not just
42
- unset.
43
-
44
- **No client-side token math, ever.** Use the SDK's real `cacheTokens` /
45
- `promptTokens` / `generatedTokens` from `completion()` stats, and react to the
46
- typed `ContextOverflowError`. Never estimate chars-per-token, never reserve a
47
- response budget. To A/B the cache, drop `cacheKey` locally.
48
-
49
- If you encounter `RESERVED_FOR_RESPONSE`, a chars-per-token estimator,
50
- `windowHistoryToFit`, or `toolsThatFit` anywhere, that is retired code — not the
51
- shipping path, and none of it should come back.
52
-
53
- ## Retired knobs
54
-
55
- Both were dropped on 2026-08-20 and have since been deleted upstream. Kept here
56
- as bug shapes, not as options.
57
-
58
- - **Sliding window (`n_discarded`).** SDK 0.19 dropped it from the `loadModel`
59
- schema (rejected at validation) and addon 0.48 removed sliding outright. Why
60
- it had to go: a slide was invisible to the SDK — it recorded the turn's full
61
- message count and next sent `history.slice(savedCount)`, so the discarded span
62
- was gone from the KV, never re-sent, and the model silently lost the middle of
63
- the chat. While sliding was possible the tool block also travelled with every
64
- turn (`toolBlockEvictable`, ~331 tokens for a 4-tool agent). A full window is
65
- now a normal end state, recovered by the ladder and by the caller's fold.
66
- - **Dynamic tools (`toolsMode: 'dynamic'`).** SDK 0.18 deleted the key (its
67
- parser rejects it); the addon dropped `tools_compact` after 0.39.4. It was a
68
- no-op on every shipped model — the addon gate is exact-arch `qwen3`, ours are
69
- `qwen35`/`qwen36` — while the SDK still sliced as-if-dynamic: the previous
70
- answer was duplicated into the KV every turn and, on a cold cache, only the
71
- last exchange was sent instead of overflowing. Static placement is the only
72
- mode: `history.slice(savedCount)`, full history on a cold cache at TTFT cost
73
- only, which is also what makes the overflow ladder effective.
74
-
75
- Full history: [2026-08-20-kvcache-static-placement](https://github.com/tetherto/qvac-app/blob/main/docs/decisions/2026-08-20-kvcache-static-placement.md),
76
- still in `tetherto/qvac-app`.
77
-
78
- ## Hard facts that trip people up
79
-
80
- - **A slice at or above `ctx_size` can never be admitted.** Nothing is decoded
81
- yet, so there is nothing to evict — and nothing evicts anyway.
82
- - **Overflow is a normal end state:** every long chat legitimately reaches ctx,
83
- recovered by the ladder and by the caller's fold for any window at or above
84
- 4,096. Also legitimate: the current prompt alone exceeds ctx, a tool result
85
- alone exceeds ctx, or a device that has never run this chat getting the whole
86
- history (the cache is **device-local**). Anything else is a drift bug.
87
- - **A thrown overflow keeps the cache** (SDK 0.19,
88
- [qvac#4163](https://github.com/tetherto/qvac/pull/4163)): a prefill
89
- `ContextOverflowError` or an addon pre-mutation refusal releases the turn and
90
- the committed `.bin` survives. What still destroys it: a turn that hits the
91
- ceiling **mid-generation** (addon `stopReason=contextOverflow`, surfaced as
92
- `length`) is not committed — its file is unlinked and the next turn
93
- cold-primes from history.
94
- - **The tool block is not in the primed prefix** (since 0.17.1): the prime is
95
- the system prompt alone. The block travels with the first turn that renders it
96
- (the SDK requires a user message before believing it landed) and is skipped
97
- once `toolBlockCached` is recorded at commit. It is never trimmed back out, so
98
- `configHash` hashes the system prompt **plus the full tool definitions** — any
99
- tool-set, description, or parameter change re-keys the file and costs a cold
100
- prime. A skill activation and a `disabledToolsets` edit each do that once and
101
- then hold. The block is NOT a fixed size — it is whatever the agent's
102
- `enabledTools` serializes to; read `round start { tools }` and
103
- `round stats { promptTokens }` from the harness log, never assume a figure.
104
- - **`modelId` is identity-by-config**: `sha256(type:src:sortedConfig)[:16]`. ANY
105
- `modelConfig` field change — including `verbosity` from `QVAC_LLM_VERBOSITY` —
106
- is a new `modelId`, a new cache directory, and a cold prime for every chat.
107
- Debug the cache with `QVAC_HARNESS_LOG`/`xxd`, never by raising addon
108
- verbosity. The system prompt is NOT in `modelId` (it is in `configHash`, the
109
- file name): a prompt edit cold-primes one chat, a config edit cold-primes all.
110
- - **The addon compacts reasoning out of the KV itself.**
111
- `remove_thinking_from_context` defaults to `true` (addon 0.33.0); since 0.48 it
112
- rewinds to the block boundary and re-decodes what it keeps. `cacheTokens`
113
- after a turn excludes thinking, so nothing above has to strip it.
114
- - **`ContextOverflowError` carries sizes** (0.19): `requiredTokens`, `ctxSize`,
115
- `cachedTokens` on the warm guard, and `promptTokens` only when the guard
116
- counted tokens. The harness matches the error by `name` (`CONTEXT_OVERFLOW`)
117
- and reads none of them.
118
-
119
- ## Before proposing caller-side eviction
120
-
121
- Ask whether the SDK cache would already handle it. The answer is yes for
122
- ordinary history growth; the cases that genuinely need handling above the SDK
123
- are a message (or tool result) larger than `ctx_size`, plus the cross-device
124
- cold start. Anything else is re-implementing the cache badly.
125
-
126
- **Compaction is the one sanctioned exception, and it already exists in
127
- `@qvac/core` — do not build a second one here.** It evicts from the prompt,
128
- never from the KV; it uses the SDK's own `cacheTokens` rather than token math;
129
- and it invalidates the chat key when a fold lands. The floor is
130
- `COMPACTION_MIN_CTX_SIZE` (4,096): below it a fold cannot pay for the ~420-token
131
- prefix it shares the window with, so those chats stay on the ladder here.
132
-
133
- ## Debugging
134
-
135
- - `round stats { round, wallMs, TTFT, TPS, cacheTokens, promptTokens }` and
136
- `round start { round, turns, tools }` — the per-round `hlog` in
137
- [lib/capabilities/completion.ts](../../../lib/capabilities/completion.ts).
138
- `cacheTokens` dropping back to system-prompt size mid-chat = a re-prime.
139
- `turns` shrinking across an overflow with an identical outcome = an inert
140
- ladder.
141
- - SDK `logMessagesToAddon` tags: `PROMPT_SEND` / `CACHE_INIT` / `NO_CACHE`. A
142
- `CACHE_INIT` mid-chat is a re-prime.
143
- - No addon line reports eviction any more: `discarded N tokens`,
144
- `[ContextSlider]` and `runtimeStats().contextSlides` left with sliding in
145
- addon 0.48. A log that shows them is an old build.
146
- - Cache files: `{kvCacheDir}/{cacheKey}/{modelId}/{configHash}.bin`. Two files
147
- under one chat key means `configHash` changed — a volatile system prompt costs
148
- one cold prime per turn.
149
-
150
- ## Versions — check these first
151
-
152
- **This behaviour is version-specific and has changed before.** Start every task
153
- here:
154
-
155
- ```bash
156
- grep '"@qvac/sdk"' package.json
157
- grep '"version"' node_modules/@qvac/inference/package.json node_modules/@qvac/llm-llamacpp/package.json
158
- ```
159
-
160
- This repo pins **`@qvac/sdk` 0.19.0** → **`@qvac/inference` 0.19.0** →
161
- **`@qvac/llm-llamacpp` 0.49.1**. Since 0.19 `@qvac/sdk` is a thin re-export over
162
- `@qvac/inference`, and the `#sdk` import seam resolves to `@qvac/inference`
163
- in-process on Bare — so the cache code that runs is
164
- `node_modules/@qvac/inference/dist/plugins/builtin/llamacpp-completion/ops/`
165
- (`completion-stream.js`, `kv-cache-session.js`, `context-overflow.js`), not
166
- anything under `@qvac/sdk/dist`. If the shipped numbers move past these, re-read
167
- before acting.
168
-
169
- Whenever you re-verify something, update the Versions section in
170
- [docs/kvcache-reference.md](../../../docs/kvcache-reference.md) in the same
171
- commit.
@@ -1,180 +0,0 @@
1
- ---
2
- name: pr-create
3
- description: Write the title, body and commits for a pull request in @qvac/harness, and decide the conventional-commit type for a change — `fix` is reserved for user-facing defects in a released version. Use when opening a PR here, splitting work into a stack, or choosing between `fix` and `chore`.
4
- ---
5
-
6
- # PR creation
7
-
8
- `@qvac/harness` is a published package. The log is the only durable record of intent,
9
- and for this repo it is also the changelog a consumer reads before upgrading.
10
-
11
- ## Title
12
-
13
- ```
14
- type(scope): subject
15
- ```
16
-
17
- Imperative, lowercase after the colon, no trailing period. Scope optional, and
18
- only when it narrows something real. What and why, not how.
19
-
20
- ```
21
- feat: publish the sidecar entry, so spawning it needs no bundle
22
- fix(publish): repoint dynamic imports at dist-lib too
23
- refactor: own the schema codegen tooling
24
- chore: regenerate the schema
25
- ```
26
-
27
- There is no ticket prefix here, and no title validator in CI. That is not
28
- licence to be loose: nothing will catch a bad title but a reviewer.
29
-
30
- ## Types
31
-
32
- ```
33
- build chore ci docs style refactor perf test fix feat
34
- ```
35
-
36
- **`fix` is reserved for a defect a consumer of a released version could hit.**
37
- It is the line someone reads to decide whether to upgrade, so it claims real
38
- breakage in something already published.
39
-
40
- | The change | Type |
41
- | ----------------------------------------------------------- | -------------------------------------------------- |
42
- | Breaks a consumer on a published version | `fix` |
43
- | Repairs code that has never shipped (same release cycle) | `chore` |
44
- | Repairs the build, the bundle, or what lands in the tarball | `build` or `fix` if a published artifact was wrong |
45
- | Repairs CI, a workflow, an action pin | `ci` |
46
- | Repairs or adds a test | `test` |
47
- | Repairs a lint/type error | `chore` |
48
- | Pure move or rename, no behaviour change | `refactor` |
49
- | New capability, new export, new flag | `feat` |
50
- | README, comments, docs | `docs` |
51
-
52
- When a repair is on unreleased code, check the last release tag: if the bug was
53
- never in a published version, it is `chore`, not `fix`.
54
-
55
- Never relabel a real defect as `chore` to make the log quieter, and never
56
- inflate a cleanup into `fix` to make it look urgent.
57
-
58
- ## Scopes
59
-
60
- Optional, and taken from what the diff actually touches — `lib`, `worker`, `schema`, `spec`, `scripts`, `test`, `ci`, `publish`, or a capability name (`completion`, `tools`, `models`).
61
- A scope that restates the type (`fix(fix)`) or names the whole repo adds
62
- nothing; leave it off.
63
-
64
- ## Decision records
65
-
66
- `docs/decisions/` holds the durable, non-obvious choices — why a boundary is
67
- where it is, why an approach was rejected. They are reference for a future
68
- reader, **not rules, and not to be loaded by default**.
69
-
70
- Add one when this PR settles something a later reader would otherwise have to
71
- reverse-engineer from the diff: a runtime or storage boundary, a wire-format or
72
- schema choice, a build or publish constraint, a rejected alternative that will
73
- look obvious later. Do not add one for a bug fix, a rename, or anything the code
74
- already says plainly.
75
-
76
- Copy `docs/decisions/TEMPLATE.md`, give it a `status`, list it once in
77
- `INDEX.md`, and name it in the PR's Decisions section. `node
78
- scripts/docs-audit.cjs` checks all of that, and CI runs it.
79
-
80
- An **active** record may cite only paths that exist here; a path in another
81
- repository is written `@qvac/harness → lib/x.ts` so the audit treats it as
82
- context rather than a local file. A superseded record is exempt — it describes
83
- how things used to be.
84
-
85
- ### One PR, one type
86
-
87
- The commits carry conventional-commit types, so the mix of types _is_ the scope
88
- statement. `feat:`, `fix:` and `chore:` on one branch is not a well-rounded
89
- change, it is three changes sharing a branch, and a reviewer has to hold all
90
- three while judging any one of them.
91
-
92
- List them before writing the body:
93
-
94
- ```bash
95
- git log --format='%s' main..HEAD | grep -oE '^[a-z]+' | sort -u
96
- ```
97
-
98
- - **One type** is the PR's type.
99
- - **A dominant type plus its own tests or docs** (`feat` + `test`, `fix` +
100
- `docs`) is fine; the dominant one wins.
101
- - **Unrelated types** means split the PR, or stack it.
102
-
103
- ## Small PRs, stacked
104
-
105
- A PR is read by a human, so it has to fit in one head. Default to one concern.
106
- When a change decomposes into dependent steps, do not grow one PR to fit them —
107
- stack them, so each step is reviewable alone and lands in order.
108
-
109
- - A large refactor belongs on a `refactor/...` branch with `refactor:` commits.
110
- - **Refactor commits never live inside a non-refactor branch.** A `feat/` branch
111
- that also reshuffles unrelated code is two PRs. Behaviour changes and pure
112
- moves are reviewed differently, and mixing them hides both.
113
- - A pure rename or move lands _before_ the change that depends on it, as its own
114
- commit or its own PR in the stack.
115
-
116
- Branch names must never contain `codex`, `cursor`, or `claude` (case
117
- insensitive). Use a neutral name before committing or pushing.
118
-
119
- ## Body
120
-
121
- `.github/pull_request_template.md` pre-fills the headings. Keep its shape, fill
122
- what applies, and delete the sections that would be empty — an absent section
123
- says more than one saying "n/a". The template's own order is Why, What, Testing,
124
- Decisions; the expanded form below is what each should contain.
125
-
126
- ```markdown
127
- ## What
128
-
129
- One paragraph. The change, in the reviewer's terms.
130
-
131
- ## Why
132
-
133
- The problem this solves, or the decision it implements. Link the issue,
134
- PR, or upstream change that forced it.
135
-
136
- ## How
137
-
138
- Only the parts a reader would not infer from the diff: the seam that moved,
139
- the invariant being preserved, the thing that looks wrong but isn't.
140
-
141
- ## Testing
142
-
143
- What you ran, and what it proved. ``bun run test` (node, bare and addon lanes)` at minimum.
144
- Say plainly when something could not be tested locally and why.
145
- ```
146
-
147
- For a **breaking change**, say so in the body under its own `## Breaking`
148
- heading, with the migration a consumer has to perform. Published package —
149
- this is the only warning they get.
150
-
151
- ## Before opening
152
-
153
- 1. Check the branch is not `main` and the name is neutral.
154
- 2. `git log --oneline main..HEAD` — every commit is a conventional commit, and
155
- each one is a change you would defend on its own. A trail of `wip`,
156
- `fixup` or `address review` commits is not history; reshape it before
157
- opening.
158
- 3. `bun run lint` and ``bun run test` (node, bare and addon lanes)` pass locally. CI runs the same lanes; a
159
- red PR wastes a reviewer's round trip.
160
- 4. Rebase on `main` rather than merging it in. A merge commit in the branch
161
- makes the diff unreadable.
162
- 5. Open as a **draft** unless you are asking for review right now.
163
-
164
- ```bash
165
- gh pr create --repo tetherto/qvac-harness --draft --title "type(scope): subject" --body-file <path>
166
- ```
167
-
168
- Never push to `main`, never force-push a branch someone else is reviewing, and
169
- never open a PR that carries commits you did not write without saying so.
170
-
171
- ## Version bumps
172
-
173
- Release PRs in this repo are titled by version alone (`0.2.4 (#32)`,
174
- `release/0.1.4 (#40)`). They carry the version bump and nothing else — do not
175
- fold a bump into a feature PR, and do not fold a feature into a bump.
176
-
177
- ## Sibling skills
178
-
179
- - Reviewing a PR or a local diff → **pr-review**.
180
- - How the code itself should be shaped → **js-house-style**.