@qvac/harness 0.2.8 → 0.2.9
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/harness.bundle +65 -12
- package/dist/prebuilds-manifest.json +8 -0
- package/dist-lib/index.d.ts +2 -1
- package/dist-lib/index.js +2 -1
- package/dist-lib/lib/capabilities/completion.d.ts +35 -2
- package/dist-lib/lib/capabilities/completion.js +84 -18
- package/dist-lib/lib/connect.js +1 -0
- package/dist-lib/lib/contract-types.d.ts +1 -0
- package/dist-lib/lib/serve.js +1 -0
- package/dist-lib/lib/wire.js +28 -0
- package/dist-lib/schema/harness.js +5 -2
- package/dist-lib/spec/hrpc/messages.js +22 -7
- package/dist-lib/spec/hyperschema/index.js +22 -7
- package/dist-lib/spec/hyperschema/schema.json +14 -1
- package/dist-lib/spec/hyperschema/types.d.ts +3 -2
- package/package.json +6 -6
- package/.claude/skills/asana/SKILL.md +0 -86
- package/.claude/skills/gh-stack/SKILL.md +0 -181
- package/.claude/skills/gh-stack/references/commands.md +0 -177
- package/.claude/skills/gh-stack/references/stack-design.md +0 -97
- package/.claude/skills/gh-stack/references/troubleshooting.md +0 -159
- package/.claude/skills/js-house-style/SKILL.md +0 -671
- package/.claude/skills/kvcache/SKILL.md +0 -171
- package/.claude/skills/pr-create/SKILL.md +0 -180
- package/.claude/skills/pr-review/SKILL.md +0 -414
- package/CLAUDE.md +0 -39
- package/docs/decisions/2026-05-29-kvcache-tool-turns.md +0 -75
- package/docs/decisions/2026-06-02-kvcache-tool-turns-reenabled.md +0 -127
- package/docs/decisions/2026-06-23-android-cpu-llm-defaults.md +0 -126
- package/docs/decisions/2026-07-29-assistant-owned-storage-layout.md +0 -88
- package/docs/decisions/2026-08-04-sidecar-orphan-reaping.md +0 -79
- package/docs/decisions/2026-08-11-pyodide-worker-thread.md +0 -68
- package/docs/decisions/2026-08-17-harness-hook-pipeline.md +0 -84
- package/docs/decisions/2026-08-17-harness-outbound-egress-guard.md +0 -176
- package/docs/decisions/2026-08-20-chat-prompt-prefix-is-stable.md +0 -95
- package/docs/decisions/2026-08-20-kvcache-static-placement.md +0 -109
- package/docs/decisions/2026-08-21-vendored-python-wheels.md +0 -85
- package/docs/decisions/2026-08-26-exec-is-not-gated-by-skills.md +0 -64
- package/docs/decisions/2026-08-27-writable-skills-overlay.md +0 -56
- package/docs/decisions/2026-09-01-skill-operation-manifests.md +0 -53
- package/docs/decisions/2026-09-01-usage-breakdown-is-measured.md +0 -78
- package/docs/decisions/2026-09-03-usage-breakdown-reported-by-the-harness.md +0 -136
- package/docs/decisions/2026-09-10-skill-wording-is-measured-offline.md +0 -63
- package/docs/decisions/2026-09-16-a-lookup-reports-the-place-it-resolved.md +0 -70
- package/docs/decisions/INDEX.md +0 -40
- package/docs/decisions/TEMPLATE.md +0 -32
- package/docs/kvcache-reference.md +0 -655
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: ADR-2026-09-03-usage-breakdown-reported-by-the-harness
|
|
3
|
-
status: active
|
|
4
|
-
introduced_by: PR-2067
|
|
5
|
-
supersedes: [ADR-2026-09-01-usage-breakdown-is-measured]
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# The device that ran the turn reports the Usage breakdown; the app only renders it
|
|
9
|
-
|
|
10
|
-
## Decision
|
|
11
|
-
|
|
12
|
-
The Usage modal's rows come from the run. The harness attributes the prompt it
|
|
13
|
-
just assembled — `systemPrompt`, `agentInstructions`, `activeSkills`, `tools` —
|
|
14
|
-
measured as `.length` on the strings it sent, converts each to tokens at the
|
|
15
|
-
measured density **for that kind of text**, and ships the result inside the
|
|
16
|
-
existing `metrics` JSON of the terminal run status
|
|
17
|
-
(`lib/capabilities/usage-breakdown.ts`).
|
|
18
|
-
|
|
19
|
-
A prompt row is never a share of the measured total. It is its own characters at
|
|
20
|
-
its own density, so it holds still across every turn of a chat while the
|
|
21
|
-
conversation grows — the property QA asks for. `conversation` is what is left of
|
|
22
|
-
`cacheTokens` once the prompt rows are subtracted, which is also where the costs
|
|
23
|
-
no segment authors end up: the template's per-message wrappers (~28 tokens a
|
|
24
|
-
message on Qwen3.5) and the reply the run just generated, which the cache holds.
|
|
25
|
-
|
|
26
|
-
`app-runtime` re-derives nothing. `buildUsageSegments` reads the newest reported
|
|
27
|
-
breakdown off the messages and maps it to labels and colors. Conversation is the
|
|
28
|
-
remainder against the live `usedTokens`, never a reported number, so occupancy
|
|
29
|
-
the report cannot know about lands on the turns rather than skewing a prompt row.
|
|
30
|
-
|
|
31
|
-
Three constants per template, keyed off `detectToolDialect`: prose chars/token
|
|
32
|
-
(the prompt body, the bio, the skills list), schema chars/token (the serialized
|
|
33
|
-
SDK function schemas), and the tool block's token count. Measured on
|
|
34
|
-
Qwen3.5-2B-Q4_K_M as `{ prose: 4.38, schema: 4.08, toolBlockTokens: 189 }` by
|
|
35
|
-
least squares over the addon's own `cacheTokens` across 14 tool/skill/bio
|
|
36
|
-
configurations x 3 turns — 42 samples, leave-one-configuration-out worst error
|
|
37
|
-
5.9%. An unmeasured template borrows these rather than reporting zeros.
|
|
38
|
-
|
|
39
|
-
Two things the previous design needed are gone: the per-chat chars-per-token fit
|
|
40
|
-
(`usage-calibration.ts`) and the app's copy of the selection gates
|
|
41
|
-
(`usage-segment-system-prompt.ts`, and with it the drift test that policed the
|
|
42
|
-
copy). `TURN_OVERHEAD_TOKENS` is gone too, and so is the 935-char framing
|
|
43
|
-
constant — the tool block is a fixed string, so it is declared in tokens.
|
|
44
|
-
|
|
45
|
-
The row for attachment filenames is retired. Those labels are composed into the
|
|
46
|
-
user turn by the assistant layer, so they are conversation characters; the app
|
|
47
|
-
was counting raw filenames that never appear in the prompt verbatim.
|
|
48
|
-
|
|
49
|
-
## Context
|
|
50
|
-
|
|
51
|
-
Every row except the total was re-derived app-side by re-running
|
|
52
|
-
`permittedTools`, `enabledTools` and `skillIsActive` against a device tool
|
|
53
|
-
report and a skill catalog. The mirror was only ever as current as the last
|
|
54
|
-
person to remember it existed, and because the fit normalizes the known
|
|
55
|
-
characters onto the measured total, an input that goes missing does not read as
|
|
56
|
-
zero in place — it silently reweights every other row.
|
|
57
|
-
|
|
58
|
-
That failure shipped. After PR-1960 the six provider skills declared tools
|
|
59
|
-
compiled from `operations.json`, so on any host that had not wired
|
|
60
|
-
`compileOperationTools` (the mobile worklet until QVAC-24473; the in-process
|
|
61
|
-
path still) `isToolSupported` marked them unavailable, the estimator dropped
|
|
62
|
-
them, their tool grants went with them, and the System Prompt row absorbed the
|
|
63
|
-
entire window: 2,361 of 2,616 tokens, with Active Skills and Tools at 0.
|
|
64
|
-
|
|
65
|
-
No tokenizer is exposed — `@qvac/sdk` 0.18.2 ships tokenization only for VLA,
|
|
66
|
-
whose docs tell consumers to tokenize their own input — so `cacheTokens` remains
|
|
67
|
-
the only ground truth and the rows remain shares of it. What changed is where
|
|
68
|
-
the characters are counted: at the point the prompt is built, where being wrong
|
|
69
|
-
is not possible without also sending a different prompt.
|
|
70
|
-
|
|
71
|
-
## Alternatives Considered
|
|
72
|
-
|
|
73
|
-
- **Keep the mirror, add a stronger drift test.** Rejected: the drift test runs
|
|
74
|
-
against a stubbed completion, so it cannot see inputs that arrive over the
|
|
75
|
-
wire — exactly the class that broke. It also grows a second maintenance
|
|
76
|
-
surface for every prompt change.
|
|
77
|
-
- **Report chars, fit in the app.** The fit needs the first metrics-bearing turn
|
|
78
|
-
of the chat, whose prompt the harness can no longer reconstruct. Rejected
|
|
79
|
-
anyway: per-turn attribution against that turn's own measurement needs no fit,
|
|
80
|
-
and the prompt rows stay flat regardless (the shares move together).
|
|
81
|
-
- **Report tokens per segment.** Requires a tokenizer we do not have. This is
|
|
82
|
-
the shape to move to when the SDK grows one: the harness swaps its char counts
|
|
83
|
-
for tokenize calls and nothing downstream changes.
|
|
84
|
-
- **One density for the whole prompt** (what shipped first, and what QA caught).
|
|
85
|
-
Rejected on measurement: with rows taken as shares of the total, a chat whose
|
|
86
|
-
tool schemas are dense JSON and whose turns are prose re-weights every row as
|
|
87
|
-
the mix changes. Three "hi" turns moved Tools 3,166 -> 3,252 -> 3,268 with an
|
|
88
|
-
unchanged tool set, and fitting one density across the matrix above is off by
|
|
89
|
-
up to 46% against six per-kind constants' 1.7%.
|
|
90
|
-
- **Keep the attachment-filenames row, reported by the assistant layer.** Held
|
|
91
|
-
back: it would put a third layer in the attribution path for a row that reads
|
|
92
|
-
zero in every chat we have looked at.
|
|
93
|
-
|
|
94
|
-
## Consequences
|
|
95
|
-
|
|
96
|
-
- A prompt-shaping change cannot desync the breakdown. There is nothing to keep
|
|
97
|
-
in sync: the numbers are measured off the prompt that was sent.
|
|
98
|
-
- A delegated chat is attributed by the device that ran it, so the observer no
|
|
99
|
-
longer needs the host's tool catalog or skill names to price the window.
|
|
100
|
-
Everything that fed the estimator therefore loses its only reader — the
|
|
101
|
-
reported tool catalog (`agentToolCatalog`, `selectToolCatalog`,
|
|
102
|
-
`selectActiveAgentHostToolCatalog`, the `skill.toolCatalog` slice and the
|
|
103
|
-
`coreV2.tools({})` call that filled it) and the app-side fs-grant mirror
|
|
104
|
-
(`agent.actor.fs-grants.ts`, its actions, its reducer branches,
|
|
105
|
-
`state.agent.fsGrants`, its selectors and `AgentFsGrants`/`NO_FS_GRANTS`).
|
|
106
|
-
PR-2068 deletes them. The real grant plumbing in `@qvac/core → lib/fs-grants.ts`
|
|
107
|
-
stays either way — that is what gives the harness its roots.
|
|
108
|
-
- The rows still sum exactly to the measured total, and the prompt rows still
|
|
109
|
-
hold still across turns of one chat. Both are asserted by QA (QA-723).
|
|
110
|
-
- The app parses only the four prompt rows out of the report. The run also sends
|
|
111
|
-
its own `conversation` and `totalTokens`, which stay useful in a log or a
|
|
112
|
-
debug view, but requiring them here would turn a harness that trimmed either
|
|
113
|
-
one into no breakdown at all.
|
|
114
|
-
- A turn recorded before this change, or run by an older peer, carries no
|
|
115
|
-
breakdown: the modal shows the window on Conversation and holds the System
|
|
116
|
-
Prompt row back rather than inventing a split. There is no estimator fallback
|
|
117
|
-
on purpose — a stale mirror is the defect this removes.
|
|
118
|
-
- `promptChars` on the reported tool catalog now has no reader, and neither does
|
|
119
|
-
the `toolCatalog` field on a device row (still parsed in
|
|
120
|
-
`device/core-v2-device.ts`, still listed in the Core v2 compatibility matrix).
|
|
121
|
-
Both stay until one schema bump retires them together.
|
|
122
|
-
- Re-measure the densities by driving `Harness.completion` headlessly with the
|
|
123
|
-
real SDK (no TUI or pty needed: it takes `model`, `agent` and `messages`
|
|
124
|
-
directly), reading the char set the harness logs beside the `cacheTokens` the
|
|
125
|
-
addon reports, and fitting the four terms by least squares. **One fresh SDK
|
|
126
|
-
home per configuration, one configuration per process**: the SDK persists cache
|
|
127
|
-
state under its home, and `cacheTokens` counts that whole occupancy, so a reused
|
|
128
|
-
home reports the same prompt as 152, 219 or 469 tokens. That also means a
|
|
129
|
-
device's total can include cache another chat left behind — a separate question
|
|
130
|
-
from this attribution, and the reason `conversation` is the remainder rather
|
|
131
|
-
than a reported number.
|
|
132
|
-
|
|
133
|
-
## When Agents Should Read This
|
|
134
|
-
|
|
135
|
-
Read this when changing the paths, workflows, runtime boundaries, or platform
|
|
136
|
-
behavior listed for this ADR in `docs/decisions/INDEX.md`.
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: ADR-2026-09-10-skill-wording-is-measured-offline
|
|
3
|
-
status: active
|
|
4
|
-
introduced_by: PR-2160
|
|
5
|
-
supersedes: []
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# A skill's wording is measured on the real small model before it ships
|
|
9
|
-
|
|
10
|
-
## Decision
|
|
11
|
-
|
|
12
|
-
A change to a bundled skill's `SKILL.md` that is meant to change model behaviour is measured before
|
|
13
|
-
it lands: the candidate body and the current one run through `examples/skill-eval` on
|
|
14
|
-
the real local 2B model, against a corpus of user-worded prompts, with the skill's tools answered
|
|
15
|
-
from recorded fixtures and every reply judged by deterministic rules. The winning variant records
|
|
16
|
-
its scores in the skill's front matter (`version:` plus a comment the model never sees).
|
|
17
|
-
|
|
18
|
-
The eval drives `Harness.completion` in-process, swaps variants through the skills overlay
|
|
19
|
-
(`writeSkillFile` / `deleteOverlaySkill`), and never uses a second model as a judge. Baseline is
|
|
20
|
-
the bundled `@qvac/skills` tree, or any checkout named by `--skills-dir`.
|
|
21
|
-
|
|
22
|
-
## Context
|
|
23
|
-
|
|
24
|
-
The default mobile model is a 2B. It copies examples it is shown and ignores rules it cannot copy,
|
|
25
|
-
and a wording that reads better to a human routinely scores worse on it. The weather skill's Nassau
|
|
26
|
-
defect (QVAC-24701) was one line of guidance; nine candidate rewrites were tried and seven of them
|
|
27
|
-
fixed the ticket prompt while regressing plain-city prompts through wrong URL suffixes. Only a
|
|
28
|
-
measured comparison separated them, and only categories, not totals, showed the trade.
|
|
29
|
-
|
|
30
|
-
Judging with a larger model was rejected: its verdicts vary run to run, cost a second inference per
|
|
31
|
-
sample, and hide which rule failed. String laws over the call the model made and the reply it
|
|
32
|
-
gave are reproducible and name the failure.
|
|
33
|
-
|
|
34
|
-
Live network was rejected for the loop itself: wttr.in's answers change hourly and the source
|
|
35
|
-
keys its format on the User-Agent. Recorded fixtures keep runs comparable; `--record` refreshes them
|
|
36
|
-
deliberately, with the product's User-Agent.
|
|
37
|
-
|
|
38
|
-
## Alternatives Considered
|
|
39
|
-
|
|
40
|
-
- A Playwright smoke that sends one prompt through the desktop UI. It proves the wiring, not the
|
|
41
|
-
wording: one sample, no categories, no variant swap, and the UI adds minutes per run.
|
|
42
|
-
- Tuning by hand in the TUI. Two repeats of a 130-run corpus already swing by about three runs;
|
|
43
|
-
a handful of manual tries cannot see a five-point change.
|
|
44
|
-
|
|
45
|
-
## Consequences
|
|
46
|
-
|
|
47
|
-
- A skill PR that changes model-facing text states its before/after category table, from this eval
|
|
48
|
-
or an equivalent pack, and keeps the score history in the front matter.
|
|
49
|
-
- A new skill that needs measuring adds a pack (corpus, fake tools, judge) under
|
|
50
|
-
`examples/skill-eval/packs/`; the runner, routes, gap accounting and report are shared.
|
|
51
|
-
- Differences under about five runs per 130 are noise at two repeats. Three repeats decide.
|
|
52
|
-
- The eval is not CI: it needs the model weights and about four seconds per run. It is run by hand
|
|
53
|
-
and its output lives under the gitignored `output/`.
|
|
54
|
-
- It lives here rather than in `@qvac/skills` because a pack is harness code: `world()` builds the
|
|
55
|
-
tools the model is offered. Here it needs no dependency the repo does not already have, and
|
|
56
|
-
`--variant` and `--skills-dir` already reach a skill body in another checkout.
|
|
57
|
-
- Revisit if the SDK exposes a sampler seed (paired repeats would tighten the noise band) or if a
|
|
58
|
-
judge model becomes cheap and stable enough to grade open-ended replies.
|
|
59
|
-
|
|
60
|
-
## When Agents Should Read This
|
|
61
|
-
|
|
62
|
-
Read this when changing the paths, workflows, runtime boundaries, or platform
|
|
63
|
-
behavior listed for this ADR in `docs/decisions/INDEX.md`.
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: ADR-2026-09-16-a-lookup-reports-the-place-it-resolved
|
|
3
|
-
status: active
|
|
4
|
-
introduced_by: PR-52
|
|
5
|
-
supersedes: []
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# A provider that can silently resolve the wrong thing gets a typed tool, not a skill rule
|
|
9
|
-
|
|
10
|
-
## Decision
|
|
11
|
-
|
|
12
|
-
When a third-party lookup can answer `200` for a resource the user did not ask for, the harness owns
|
|
13
|
-
the check in a typed tool rather than leaving it to skill prose. `weather_lookup` is the first: it
|
|
14
|
-
asks wttr.in for the structured `format=j1` body, compares the place the geocoder resolved against
|
|
15
|
-
the country or state the request named, refuses the answer when they contradict, and puts the
|
|
16
|
-
resolved place on the first line of every report it does return.
|
|
17
|
-
|
|
18
|
-
A skill keeps the judgement that is genuinely the model's: whether to call at all, and what location
|
|
19
|
-
to send. The tool keeps the part that is arithmetic.
|
|
20
|
-
|
|
21
|
-
## Context
|
|
22
|
-
|
|
23
|
-
QVAC-24701 shipped a tuned `weather` skill and still failed QA. "What's the weather like in
|
|
24
|
-
Xyzzyville, Atlantis?" produced a correct call, `200`, and the body
|
|
25
|
-
`Xyzzyville, Atlantis: 🌦 +86°F`, which the model relayed. wttr.in echoes the request string back as
|
|
26
|
-
the label and geocodes it fuzzily, so its answer for a place that does not exist is the weather of
|
|
27
|
-
whatever it landed on — Atlantis, Florida. The same mechanism means a bare `Rome` currently answers
|
|
28
|
-
with Lomé, in Togo.
|
|
29
|
-
|
|
30
|
-
No wording can catch that. The skill already tells the model to report a failure on a non-200 or a
|
|
31
|
-
`location not found` body; here the status and the body both look right, and the only evidence to the
|
|
32
|
-
contrary is `nearest_area` inside a 40 KB JSON document the agent's context cannot hold and the
|
|
33
|
-
response truncator would clip. Deciding whether "Atlantis" agrees with "Florida, United States of
|
|
34
|
-
America" is string comparison with an alias table, which is the work a 2B is worst at and code is
|
|
35
|
-
best at. Measured behaviour agrees: "ask before acting" rules top out near 30 % on that model in any
|
|
36
|
-
wording.
|
|
37
|
-
|
|
38
|
-
Only the qualifier is verified, never the city. The geocoder answers with the nearest populated area,
|
|
39
|
-
which for a large city is routinely a neighbourhood that does not carry the city's name — São Paulo
|
|
40
|
-
resolves to Liberdade, Sydney to Kirribilli, Tbilisi to Okrokana. Nine of thirty large cities probed
|
|
41
|
-
would have been rejected by a city check. The region and country are reliable, and they are exactly
|
|
42
|
-
what a wrong resolution gets wrong.
|
|
43
|
-
|
|
44
|
-
## Alternatives Considered
|
|
45
|
-
|
|
46
|
-
- An `operations.json` entry, the existing declarative path for provider calls. Every compiled
|
|
47
|
-
operation is a write: approval-gated, and its only response shaping is `pick` over top-level keys.
|
|
48
|
-
It cannot validate or refuse, and it would put an approval dialog in front of a weather question.
|
|
49
|
-
- Keeping `http_request` and asking the skill for `format=j1`. The body does not fit the context, the
|
|
50
|
-
truncator clips it, and the comparison still lands on the model.
|
|
51
|
-
- A wttr.in-specific hook that issues a second `j1` request and appends the resolved place to the
|
|
52
|
-
`http_request` result. Hidden coupling, two requests per answer, and the model still has to read it.
|
|
53
|
-
|
|
54
|
-
## Consequences
|
|
55
|
-
|
|
56
|
-
- The tool is `skillGated`, so its schema costs nothing until the weather skill body is in context,
|
|
57
|
-
and `autoApprove`, so the SDK does not inject a second `intent` argument into a one-argument read.
|
|
58
|
-
It names the `http-request` toolset, so the per-agent switch still turns it off.
|
|
59
|
-
- A skill that grants `weather_lookup` requires a harness that has it. The bundled `weather` skill
|
|
60
|
-
keeps working on older harnesses only while it still grants `http_request`.
|
|
61
|
-
- The alias table is data and will need names added. A qualifier it does not know falls back to a
|
|
62
|
-
plain substring match against the region and country, which is the behaviour it had before.
|
|
63
|
-
- Adding a tool for every provider is not the intent. The trigger is specific: a provider that can
|
|
64
|
-
answer successfully about the wrong resource, where the evidence exists but not in a form the model
|
|
65
|
-
can be asked to check.
|
|
66
|
-
|
|
67
|
-
## When Agents Should Read This
|
|
68
|
-
|
|
69
|
-
Read this when changing the paths, workflows, runtime boundaries, or platform
|
|
70
|
-
behavior listed for this ADR in `docs/decisions/INDEX.md`.
|
package/docs/decisions/INDEX.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Harness Architecture Decision Records
|
|
2
|
-
|
|
3
|
-
Decision records for `@qvac/harness`: the inference engine and the tool runtime
|
|
4
|
-
skills execute in. They answer HOW we chose to build something, when the reason
|
|
5
|
-
is not obvious from the code or the PR that introduced it.
|
|
6
|
-
|
|
7
|
-
Read this index first. Open a record only when the touched paths, area, or
|
|
8
|
-
keywords match the task at hand.
|
|
9
|
-
|
|
10
|
-
Paths are relative to this repository. A path prefixed `@qvac/core →`,
|
|
11
|
-
`@qvac/sdk →` or `qvac-app →` lives in another repository and is context, not a
|
|
12
|
-
local file. `node scripts/docs-audit.cjs` checks this directory.
|
|
13
|
-
|
|
14
|
-
## Active Decisions
|
|
15
|
-
|
|
16
|
-
| Status | Area | Paths | Decision | Keywords |
|
|
17
|
-
| ------ | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
-
| active | harness, skills, tools, product | `lib/tools/weather-lookup.ts`, `lib/tools/weather-lookup/place.ts`, `lib/tools/weather-lookup/report.ts`, `worker/harness-sidecar.mjs`, `@qvac/skills → skills/weather/SKILL.md` | [A provider that can silently resolve the wrong thing gets a typed tool, not a skill rule](./2026-09-16-a-lookup-reports-the-place-it-resolved.md) | `weather_lookup`, `wttr.in`, `nearest_area`, `geocoder`, `resolved place`, `skillGated`, `autoApprove`, `format=j1`, `QVAC-24701` |
|
|
19
|
-
| active | assistant, harness, skills, security | `lib/tools/operations/manifest.ts`, `lib/tools/operations/compile.ts`, `lib/tools/operations/builders.ts`, `lib/tools/http-request.ts`, `lib/tools/http-request/truncate-response.ts`, `lib/capabilities/tool-selection.ts`, `skills/gmail/operations.json`, `skills/google-calendar/operations.json` | [A provider write is a compiled skill operation, not a shaping hook in `http_request`](./2026-09-01-skill-operation-manifests.md) | `operations.json`, `compileOperationTools`, `parseOperationsManifest`, `OperationSpec`, `BUILDERS`, `gmail_send`, `gmail_draft`, `calendar_create_meet_event`, `calendar_add_meet`, `owningSkill`, `grantedOwners`, `host shaping`, `shapeRequest`, `truncateResponseBody`, `fitJson`, `MAX_RESPONSE_CHARS` |
|
|
20
|
-
| active | assistant, harness, skills, security | `lib/tools/skills/overlay-dir.ts`, `lib/tools/skills/catalog.ts`, `lib/tools/skills/credential-grants.ts`, `lib/tools/skill.ts` | [A user-added skill layers over the bundled base and is trusted only by shadow](./2026-08-27-writable-skills-overlay.md) | `skills overlay`, `writable overlay`, `resolveSkillsOverlayDir`, `loadSkillCatalog`, `mergeOverlay`, `origin overlay`, `shadow`, `credentialGrants`, `mcpReadMethods`, `OVERLAY_DEFAULT_PLATFORMS`, `platform gate`, `untrusted skill`, `user-added skill`, `not available on this device` |
|
|
21
|
-
| active | assistant, harness, security, product | `lib/tools/exec.ts`, `lib/tools/exec/scope.ts`, `lib/capabilities/completion.ts`, `lib/capabilities/tool-selection.ts`, `lib/tools/skill.ts` | [A skill no longer decides what `exec` may run; approval does](./2026-08-26-exec-is-not-gated-by-skills.md) | `exec`, `skillGated`, `scopeRequired`, `grantingSkills`, `grantedScopes`, `toolScopes`, `checkScopedCommand`, `checkPythonScope`, `sanitizeCommand`, `locked until you load the skill`, `binary allowlist`, `RESTRICTED`, `approval is the boundary`, `PERM-7`, `PERM-10` |
|
|
22
|
-
| active | assistant, harness, runtime, inference | `lib/capabilities/prompt-prefix.ts`, `lib/capabilities/completion.ts`, `lib/tools/skills/mention.ts` | [A chat's prompt prefix changes only when the user changes something](./2026-08-20-chat-prompt-prefix-is-stable.md) | `promptPrefixHash`, `prompt prefix`, `round start`, `cacheTokens collapse`, `CACHE_INIT`, `KV cache re-prime`, `skillIsActive`, `SkillActivation`, `activeSkills`, `hasActiveSkills`, `loadedSkillsFrom`, `skillBlocked`, `blockedSkillMessage`, `skills section`, `turn-scoped activation`, `SKILL-3` |
|
|
23
|
-
| active | assistant, harness, runtime | `lib/hooks/**`, `lib/hooks/built-in/arg-repair.ts`, `lib/capabilities/completion.ts` | [Harness hooks fold a whole round before any call runs, and fail closed](./2026-08-17-harness-hook-pipeline.md) | `HarnessHook`, `HookPipeline`, `harnessHooks`, `runHooks`, `preToolCall`, `postToolCall`, `runStart`, `runEnd`, `HookRejection`, `guidance`, `round fold`, `deep clone`, `fail closed`, `tool executed; output withheld`, `abandoned`, `ArgRepairHook`, `arg repair`, `jsonrepair`, `parameters` |
|
|
24
|
-
| active | assistant, storage, desktop, mobile | `@qvac/core → lib/storage.ts`, `@qvac/core → lib/image-cache-dir.ts`, `@qvac/core → lib/sdk-storage.ts`, `qvac-app → desktop/electron/main.ts`, `qvac-app → mobile/worker/assistant-worklet.mjs` | [Assistant-owned storage separates shared models from private KV](./2026-07-29-assistant-owned-storage-layout.md) | `bare-storage`, `models`, `~/.qvac/models`, `SDK shared cache`, `image cache`, `KV cache`, `sdk-home`, `release channel`, `QVAC_MODEL_CACHE_PATH`, `Bare.argv[0]`, `SNAP_USER_COMMON`, `no migration` |
|
|
25
|
-
| active | runtime, inference, harness | `lib/models/model-pool.ts`, `lib/context-window.ts`, `@qvac/core → lib/assistant.ts`, `lib/capabilities/completion.ts` | [Static tool placement, no sliding window](./2026-08-20-kvcache-static-placement.md) | `kvCache`, `prompt cache`, `static placement`, `toolsMode`, `n_discarded`, `sliding window`, `savedCount`, `cold prime`, `modelId re-key`, `cache reset`, `overflow ladder` |
|
|
26
|
-
| active | runtime, inference, mobile, harness | `qvac-app → mobile/lib/android-llm-device.ts`, `qvac-app → mobile/lib/core-v2-harness-config.ts`, `index.ts`, `lib/models/kv-cache.ts`, `lib/models/opencl-cache.ts` | [Android LLM defaults to CPU except Tensor-era Pixels](./2026-06-23-android-cpu-llm-defaults.md) | `Android`, `Pixel 9`, `Pixel 10`, `Pixel Tablet`, `Pixel Fold`, `Adreno`, `Vulkan`, `OpenCL`, `openclCacheDir`, `kernel cache`, `backendDevice`, `Snapdragon`, `CPU inference`, `GPU inference`, `KV cache`, `q4_0`, `cache-type-k`, `cache-type-v`, `gpu_layers`, `transcriptionDevice`, `parakeet`, `useGPU`, `empty transcript` |
|
|
27
|
-
| active | runtime, harness, mobile, desktop | `lib/tools/exec/adapters/python/**`, `lib/tools/exec/scope.ts` | [Pyodide runs in a worker thread with a shared interrupt buffer](./2026-08-11-pyodide-worker-thread.md) | `Pyodide`, `python worker`, `Bare.Thread`, `bare-channel`, `SharedArrayBuffer`, `setInterruptBuffer`, `KeyboardInterrupt`, `terminate`, `join`, `stowed bundle`, `QVAC_PYODIDE_WORKER_ENTRY`, `QVAC_PYODIDE_TEST_LOADER`, `exec`, `in-process fallback` |
|
|
28
|
-
| active | runtime, harness, desktop, build, security | `wheels/manifest.json`, `lib/tools/exec/adapters/python/wheels.ts`, `scripts/vendor-python-wheels.mjs`, `qvac-app → desktop/electron.vite.config.ts` | [Python packages ship with the app, pinned by hash](./2026-08-21-vendored-python-wheels.md) | `python wheels`, `micropip`, `packageCacheDir`, `emfs:`, `deps=False`, `vendored wheels`, `offline`, `PyPI`, `sha256`, `supply chain`, `QVAC_PYTHON_WHEELS_DIR`, `fpdf2`, `pypdf`, `openpyxl`, `python-docx`, `python-pptx`, `cryptography` |
|
|
29
|
-
| active | desktop, harness, runtime | `qvac-app → desktop/electron/assistant.ts`, `qvac-app → desktop/electron/assistant-sidecar-lifecycle.ts`, `qvac-app → desktop/electron/assistant-sidecar-process.ts`, `worker/harness-sidecar.mjs` | [The host reaps the sidecar's process group; the harness cannot reap itself](./2026-08-04-sidecar-orphan-reaping.md) | `orphan`, `process group`, `detached`, `SIGKILL`, `reparent to init`, `taskkill`, `Job Object`, `libuv`, `KILL_ON_JOB_CLOSE`, `orphan watchdog`, `os.ppid`, `model memory leak`, `killStowChildren` |
|
|
30
|
-
| active | harness, security, runtime, mobile, desktop | `lib/hooks/built-in/egress.ts`, `lib/hooks/built-in/skill-catalog.ts`, `lib/hooks/registry.ts`, `lib/tools/url-guard.ts`, `lib/tools/private-ip.ts`, `lib/dns-lookup.ts`, `lib/validating-lookup.ts`, `lib/no-redirect-fetch-bare.ts`, `lib/no-redirect-fetch-node.ts`, `lib/helpers/spawn-path.ts`, `lib/tools/http-request.ts`, `lib/tools/mcp-call.ts`, `lib/tools/exec.ts`, `lib/tools/skill.ts`, `lib/tools/fs/scope.ts` | [Harness tool egress and spawn environments are allowlists, not filters](./2026-08-17-harness-outbound-egress-guard.md) | `SSRF`, `resolveOutboundUrl`, `private-ip`, `isPrivateOrBlocked`, `DNS rebinding`, `validating lookup`, `resolveHost`, `lookupHost`, `bare-dns`, `metadata endpoint`, `redirect hop`, `EXEC_ENV_ALLOWLIST`, `spawn env allowlist`, `workdir`, `resolveKey`, `granted roots`, `deniedRoots`, `canonicalDenyRoots`, `deny outranks grant`, `storage root`, `skill catalog`, `fail closed` |
|
|
31
|
-
| active | harness, app-runtime, desktop, mobile, chat | `lib/capabilities/usage-breakdown.ts`, `lib/capabilities/completion.ts`, `qvac-app → app-runtime/src/chat/usage-segment.ts`, `qvac-app → app-runtime/src/chat/chunk-to-turn.ts`, `qvac-app → app-runtime/src/agent/**`, `qvac-app → app-runtime/src/skill/skill.actor.catalog.ts` | [The device that ran the turn reports the Usage breakdown; the app only renders it](./2026-09-03-usage-breakdown-reported-by-the-harness.md) | `Usage modal`, `usage segments`, `usageBreakdown`, `promptUsageParts`, `buildUsageSegments`, `cacheTokens`, `charsPerToken`, `FRAMING_CHARS_BY_DIALECT`, `toolBlockFramingChars`, `detectToolDialect`, `chat template framing`, `PROMPT_SEND`, `context used`, `no tokenizer`, `metrics json` |
|
|
32
|
-
| active | harness, skills, inference | `examples/skill-eval/run.ts`, `examples/skill-eval/pack.ts`, `examples/skill-eval/packs/weather/index.ts`, `test/skill-eval.ts` | [A skill's wording is measured on the real small model before it ships](./2026-09-10-skill-wording-is-measured-offline.md) | `skill-eval`, `SkillPack`, `PackWorld`, `judge`, `gap`, `fixtures`, `--record`, `mention route`, `prose route`, `self-load`, `deterministic scoring`, `no judge model`, `version:` front matter, `QVAC-24701` |
|
|
33
|
-
|
|
34
|
-
## Superseded Decisions
|
|
35
|
-
|
|
36
|
-
| Status | Area | Paths | Decision | Keywords |
|
|
37
|
-
| ---------- | ---- | ----- | ---------------------------------------------------------------------------------------------------------------------- | -------- |
|
|
38
|
-
| superseded | | | [KV-cache session is disabled for tool-using completions](./2026-05-29-kvcache-tool-turns.md) | |
|
|
39
|
-
| superseded | | | [KV-cache session is re-enabled for tool-using completions](./2026-06-02-kvcache-tool-turns-reenabled.md) | |
|
|
40
|
-
| superseded | | | [The Usage breakdown divides a measured total, it never estimates tokens](./2026-09-01-usage-breakdown-is-measured.md) | |
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: ADR-YYYY-MM-DD-short-name
|
|
3
|
-
status: active
|
|
4
|
-
introduced_by: PR-000
|
|
5
|
-
supersedes: []
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Short Decision Title
|
|
9
|
-
|
|
10
|
-
## Decision
|
|
11
|
-
|
|
12
|
-
State the durable technical choice in one or two short paragraphs.
|
|
13
|
-
|
|
14
|
-
## Context
|
|
15
|
-
|
|
16
|
-
Explain the constraints or trade-offs that made this decision necessary.
|
|
17
|
-
|
|
18
|
-
## Alternatives Considered
|
|
19
|
-
|
|
20
|
-
Optional. Include only rejected approaches that future maintainers or agents
|
|
21
|
-
are likely to rediscover.
|
|
22
|
-
|
|
23
|
-
## Consequences
|
|
24
|
-
|
|
25
|
-
- What future changes must preserve.
|
|
26
|
-
- What this deliberately does not solve.
|
|
27
|
-
- What should trigger revisiting the decision.
|
|
28
|
-
|
|
29
|
-
## When Agents Should Read This
|
|
30
|
-
|
|
31
|
-
Read this when changing the paths, workflows, runtime boundaries, or platform
|
|
32
|
-
behavior listed for this ADR in `docs/decisions/INDEX.md`.
|