mirror of
https://github.com/vectorize-io/hindsight.git
synced 2026-09-14 19:31:49 +08:00
perf/profiling-env
44 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2643186a85 |
feat(coding-agents): spread page refreshes with hashed cron fields (H) (#4208)
One `pageTriggerCron` is shared by every page in every bank running this
plugin, so a literal `"0 3 * * *"` does not schedule a refresh at 03:00 — it
schedules all of them at 03:00, ~5 page syntheses per bank, on the worker pool
that also serves retain. Moving the hour moves the pile.
Borrow Jenkins' `H`: a cron field written `H` is replaced, per page, by a value
hashed from bank + page name, so each page keeps its own stable slot.
"H H * * *" daily, at this page's own minute and hour
"H * * * *" hourly, at its own minute
"H 3 * * *" daily at 03:MM — spread inside the chosen hour
"H H(0-5) * * *" daily, spread across the night only
"0 3 * * *" unchanged
Spreading is a property of the schedule, so it lives in the expression rather
than in a new `pageTriggerType` member per period (`daily-staggered`, then
`hourly-staggered`, …), each of which would spell the period in the type name
and cost a config value, a branch and a docs row.
`H` never leaves the package: `expandCronHash` resolves it to an ordinary
5-field expression at page creation — the only point where the page's identity
exists — so the server parses plain cron and the control plane shows an
editable schedule. The field index is hashed alongside the seed so `H H * * *`
is worth 1440 slots rather than 60 correlated ones. A malformed `H` is refused
at config resolution, since `expandCronHash` deliberately leaves an expression
it cannot read alone rather than inventing a time nobody asked for.
|
||
|
|
254bc988d4 |
feat(coding-agents): add Factory Droid harness (#4011)
Adds Factory Droid as a supported harness: three hook entry points on Droid's Claude-shaped protocol, a transcript reader for its JSONL sessions, installer wiring for ~/.factory (hooks.json, mcp.json, skills/), and the control-plane logo. Also lands, on top of the original contribution: - One harness roster for the docs site. The gallery card and the sidebar preview kept separate hand-maintained lists and both had drifted — dcode, opencode2, pi and prime-agent were each missing from one of them. Both now derive from src/lib/coding-agent-harnesses.ts, bound by a test to the registry that decides what `install <harness>` accepts, so a new harness cannot ship without its logo or its README entry. - A Docker E2E for the harness. Droid needs no Factory account for it: with a BYOK customModels entry it runs fully offline against the stub model. - A fix to the E2E runner, which drove the container with spawnSync and so starved the stub model living in the same process. That silently broke dcode, qwen-code and dsh; qwen-code's E2E passes again with it. Verified end to end against a real Droid session (recall injected, session retained) and with full CI on ci/4011-factory-droid: 109/109 jobs green. |
||
|
|
6f2799569f |
docs: make every integration changelog reachable, and compact the Integrations Hub (#4005)
* docs: link every integration changelog from its doc page The release script has written a changelog page per integration since 0.4.20 (src/pages/changelog/integrations/<slug>.md), but nothing reliably linked them: 16 of 61 doc pages carried a "View Changelog" link, and the only index was a hand-written table at the bottom of /changelog listing 6 of the 48 pages, stale since the release that introduced them. coding-agents was one of the 31 pages with a changelog nobody could reach except by guessing the URL. Adds the link to the 30 pages missing one, plus a third invariant in check-integrations.mjs so the next one can't ship unlinked: every changelog page whose integration has a gallery entry must be linked from its doc page. coding-agents.md is generated, so its link goes in the README as an absolute hindsight.vectorize.io URL — the sync script rewrites our own absolute URLs to site-relative, so it resolves on npm, GitHub and the docs site. It sits outside the skill:begin regions, so SKILL.md is unchanged. Claude-Session: https://claude.ai/code/session_01DNWFQn8AUN6SApcswHG3aN * docs(integrations): compact grouped gallery with Docs/Changelog buttons The Integrations Hub listed 60 three-line cards with a repeated "Hindsight Team" byline and an OFFICIAL badge on 52 of them, running 6092px tall — scanning it meant scrolling past what you came for. Cards are now a compact row (icon, name, two-line description) grouped by category, with the featured three using the same row tinted rather than a second card design: 4352px, and the shape of the catalogue is visible at a glance. Each card carries Docs and Changelog buttons. Which integrations have a changelog comes from a new build-time plugin that indexes src/pages/changelog/integrations/*.md and joins integrations.json on the link slug (vercel-ai-sdk -> /sdks/integrations/ai-sdk -> ai-sdk.md), so an integration grows the button on its first release with nothing to update by hand. The card is a <div> now — nested anchors are invalid — so the name and the pills are the links, not the whole card. /changelog's first line points here instead of carrying its own listing. Also drops the 14 CSS classes the old card owned, and shares CATEGORY_LABELS / groupByCategory from src/lib/integrations.ts so the taxonomy has one definition. Claude-Session: https://claude.ai/code/session_01DNWFQn8AUN6SApcswHG3aN |
||
|
|
71a9a7008f |
feat(coding-agents): first-class pi support, sharing one extension adapter and installer with Prime Agent (#3993)
Adds pi (@earendil-works/pi-coding-agent) as a harness and extracts createPiExtension(harness), the extension adapter pi and its fork Prime Agent share. Supersedes #3775. The `pi` key is removed from package.json: both hosts read that same key, so it could only ever name one bundle, and the host it did not name loaded the other's and reported the wrong harness. `hindsight-coding-agents install pi|prime-agent` is now the only route for both. Also makes the companion skill self-update for every host that installs one — the paths now live once in core/skill-dirs.ts, which both the installer and skill-sync read. Co-authored-by: Sebastian Otaegui <feniix@gmail.com> |
||
|
|
247e9b3138 |
feat(coding-agents): add per_source observation scoping (#3872)
* feat(coding-agents): add per_source observation scoping On a repo worked by a coding agent, commit diffs and session transcripts make different kinds of claim. A diff records what the code does; a transcript records what someone intended, argued for, or discarded. Under the `shared` default both consolidate into one undifferentiated belief set, so an idea floated in chat and never implemented is indistinguishable from a belief derived from the commits, and "what does the codebase actually do" cannot be answered from commit-derived knowledge alone. This cannot be fixed by configuration. The server treats an explicit scope list as unconditional: `_resolve_obs_tags_list` and `_resolve_write_scopes` in the consolidator both return the parsed list verbatim, without filtering it against the memory's own tags. A configured `[[], ["source:git"], ["source:chat"]]` therefore writes EVERY document into all three scopes, and the `source:git` scope fills with observations built from chat transcripts. Only a per-document decision separates them. `per_source` is resolved client-side, per document, in the new exported `resolveRetainScopes`, and never reaches the server. It expands to the global scope plus one per distinct `source:` tag the document carries, sorted — a document with two source tags (the commit-message seed keeps `source:git` alongside `source:git-log` so the cold-repo check still sees it) gets a scope for each, which needs no arbitrary tie-break and does not depend on the order the caller assembled its tags in. Reading only `source:` is what keeps this safe. `per_tag` splits on the right axis but also on every other one: it would reinstate the per-agent `harness:` fork that #3564 and #3575 removed, and any volatile tag such as a session id from `retainTags` would become its own scope — the fragmentation bug itself. The empty scope is always emitted first and unchanged, so the merged view matches `shared` exactly and the untagged observations that knowledge pages read (`tags_match: "all"`, per #3664) are unaffected. The cost is honest: one extra consolidation pass per document. That is the price of the axis, and it is why this is opt-in — `DEFAULT_OBSERVATION_SCOPES` remains `shared` and no existing value changes meaning. Closes #3871 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CdYchB9yKUWw8oa16RWQu9 * fix(coding-agents): not every source tag names a kind of claim Running per_source on real repositories showed two of the five source tags are provenance labels rather than axes, and each produced a scope worth less than it cost. source:git-log is a bookkeeping alias. git.ts tags the commit-message seed with it AND source:git, so emitting a scope for each gave two near-identical belief sets — 307 observations against 302 on one repo — and doubled the consolidation for that pair. It is the same claim as source |
||
|
|
c249d43796 |
coding-agents: support opencode 2, whose plugin API shares nothing with v1's (#3985)
opencode v2 (`@opencode-ai/cli@beta`, binary `opencode2`) installs alongside v1
and rewrote the plugin contract end to end: a v1 plugin is a function returning a
bag of named hooks, a v2 plugin is `{id, setup(ctx)}` where ctx hands out
per-domain registration. Handing either host the other's export loads a plugin
that registers nothing and reports no error, so v2 needs its own adapter — mapped
onto the same harness-agnostic RuntimeCore, which keeps recall, injection, the
native hindsight_* tools, the cold seed and write-back identical to every other
harness:
v1 chat.message -> ctx.session.hook("prompt")
v1 experimental.chat.system.transform -> ctx.session.hook("context")
v1 tool: {...} -> ctx.tool.transform(d => d.add(...))
v1 event (session.idle) -> ctx.event.subscribe()
v1 client.session.messages() -> ctx.session.context({sessionID})
Three host behaviours shaped the result, each verified against a live
opencode2 0.0.0-beta-18743:
ONE CONFIG ENTRY SERVES BOTH CLIS. v1 and v2 read the same
~/.config/opencode/opencode.json, and v1 REJECTS the whole file when it sees v2's
`plugins` key ("Configuration is invalid ... Unrecognized key: plugins") — so a
second entry is not an option. What makes one entry work is that they resolve a
plugin DIRECTORY differently: v1 follows package.json `main` (dist/index.js), v2
ignores `main` and loads <dir>/index.js. The package therefore ships a root
index.js re-exporting the v2 entry, and the path already in a user's `plugin`
array drives the right plugin under each host, with no config change.
THE EVENT STREAM IS GLOBAL; THE SESSION HOOKS ARE NOT. v2's background service
hosts every open project at once. `ctx.event.subscribe()` delivers session.idle
for sessions in OTHER projects, with no location on the envelope to tell them
apart, while prompt/context fire only for the location that loaded the instance.
Acting on those ids would fetch another project's transcript and retain it into
this project's bank, so write-back is gated on sessions our own prompt hook
admitted. This is a bank-isolation invariant, and it has a regression test.
TOOLS NEED options.codemode: false. Without it a tool is reachable only through
v2's `execute` code-execution tool, and a model calling it by name — which the
skill and the injected preamble both tell it to do — gets "Unknown tool:
hindsight_...".
Two capabilities are deliberately absent. The codebase survey gets no opencode2
recipe: v2 plugins cannot define an agent (agent.transform has no `add`) and a
config-file agent is not a sandbox either, since the user's own global
permissions are appended after an agent's rules — the survey reads untrusted repo
files, so without a guaranteed read-only boundary it falls back to another
installed agent's CLI, as it already does for kilo/cline/cursor/dcode. And there
is no toast: v2 plugins can observe tui.toast.show but not publish one, so the
seed banner is logged. v1's mid-session cadence write-back is also gone, because
v2 makes it redundant rather than because it was missed — session.execution.succeeded
fires after every assistant turn, so the idle path already runs once per turn
from the authoritative post-reply transcript.
Verified end to end against a real opencode2 and server: plugin loads reporting
harness opencode2, per-repo bank derived, cold seed and survey ran, the injected
block reached the model (it quoted it back verbatim), hindsight_ingest_document
was called by name, and session idle produced retain_ok. Also verified the staged
published-package shape loads under opencode2, and that opencode v1 still loads
dist/index.js and reports harness "opencode" with the new root index.js present.
Closes #3795
Claude-Session: https://claude.ai/code/session_01WbaYxouCERUv9djo3GnnA2
|
||
|
|
1aef228f7b |
feat(coding-agents): add qwen-code as a hook harness (#3979)
* feat(coding-agents): add qwen-code as a hook harness
Qwen Code speaks Claude Code's hook protocol field for field -- same stdin
envelope (session_id / transcript_path / cwd / hook_event_name), same
hookSpecificOutput.additionalContext + systemMessage output, same exit
semantics, and a settings.json shape byte-for-byte what cmdHook() already
emits. So HOOK_HARNESSES["qwen-code"] is claude-code's spec with three deltas,
none of which is visible from the diff:
1. TIMEOUTS ARE MILLISECONDS. Qwen passes a command hook's `timeout` straight
to setTimeout ("Timeout in milliseconds, default 60000" -- its own bundled
docs/features/hooks.md). Every other harness here is seconds, so the
installed values are 30000/30000/60000. Writing 30/60 would register 30ms
hooks, and that misconfiguration LOOKS fine in testing: Qwen spawns without
detached:true and kills only the direct child, so the orphaned work still
completes. HookHarnessSpec therefore declares `timeoutUnit`, and the
lifecycle tests normalise through it -- changing 30_000 to 30, or dropping
the unit, now fails a test instead of shipping dead hooks. The prompt budget
must also clear core/hook.ts's HOOK_REFLECT_CAP_MS (25_000).
2. parse reads `submitted_prompt`, not `prompt`. UserPromptSubmit also fires on
tool-result continuations, where `prompt` holds model-bound tool output;
keying on it would recall ~20x per user turn against tool results.
`submitted_prompt` is attached only when the turn is both the first and a
genuine userQuery. Accepted cost: it is the interactive TUI's projection, so
headless (`qwen -p`), serve, SDK and ACP sessions seed and retain but never
recall. The E2E declares injectsIntoModel: false for exactly that reason --
a different reason from grok-build's passive hook.
3. type:"user" is NOT a user turn. `provenance` is the discriminator: across a
22-transcript corpus, synthetic records (notification 285, cron 21,
goal_runtime 1) outnumber real_user ones (69) by 4.4:1. transcript-qwen.ts
gates on it, rejecting a present-but-malformed value outright rather than
falling through to a subtype heuristic, and requiring a positive subagent
marker (agentId/isSidechain) before accepting an absent one -- subagent
transcripts use a third envelope with neither provenance nor subtype.
Qwen also echoes injected context back into its own transcript, wrapped in
<qwen:user-prompt-submit-context> with the inner tags HTML-escaped, so the
shared stripInjectedMemory (which matches raw tags) cannot see it. The reader
removes it using the two forms of pairing evidence Qwen's contract defines:
systemPayload.hookContext present -> use systemPayload.displayText, the host's
own pre-hook projection, with no tag matching at all; otherwise a COMPLETE
tagged context in the FINAL part after at least one other part. It deliberately
does NOT match the tag as a substring -- the contract is explicit that the tag
is "a provenance marker, not ... a general trust boundary" and that consumers
"must not infer that arbitrary tag-like user text is hook provenance", so a
prompt merely quoting the tag is preserved intact.
Also here:
- registry.test.ts gains the REVERSE parity check. The existing one is
directional (installer -> registry), so a harness present in the registry but
missing from INSTALLERS passes it while `install <name>` returns "unknown
harness". That is exactly how this change was briefly broken.
- readQwenTranscript catches lazy-read faults. readJsonlTail guards
statSync/openSync, but the generator reads at iteration, and runRetainHook
calls the reader outside buildRetain's catch -- so a directory passed as
transcript_path (which passes both guards, then throws EISDIR on the first
readSync) rejected the whole Stop hook. NOTE: the underlying gap is in the
shared jsonl.ts and affects every harness; it deserves its own fix rather
than riding along here.
- Logo asset from homarr-labs/dashboard-icons (Apache-2.0, svg/qwen.svg).
Tests: 675 pass. Note HINDSIGHT_BANK_ID must be unset in the environment, or
config.test.ts's "missing files yield defaults" fails on the leaked value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WkVVSuv7j1FwTtVpkzzmWR
* chore(coding-agents): regenerate docs artifacts and apply prettier
Address the two CI-blocking review items on #3769:
- Regenerate the two artifacts derived from the README's new Qwen Code
section: hindsight-docs/docs-integrations/coding-agents.md (via
sync-coding-agents-doc.mjs) and the docs-skill copy (via
generate-docs-skill.sh).
- Run prettier over the package with 3.7.4 -- the version CI's root
`npm ci` pins -- covering the four files the review names, plus
package.json, where it restores the literal ellipsis in `description`
that the branch had accidentally left as a unicode escape sequence.
Verified: sync-coding-agents-doc.mjs --check, build-skill.mjs --check,
and prettier --check all pass; 675 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qx1ByUMo3SJhXP7MBxEQ1g
* fix(coding-agents): close the qwen-code harness parity gaps
Rebased onto main (the branch was 98 commits behind and conflicted with the
dcode harness) and closed the sites a new hook harness must also appear in,
found by sweeping every place an existing harness is registered:
- `SKILL_DIRS` (core/skill-sync.ts) did not map qwen-code, so the skill the
installer copies into ~/.qwen/skills would have been installed once and then
never refreshed by `npm update -g`. Verified ~/.qwen/skills is Qwen's real
user-level skills root (Storage.getUserSkillsDirs, SKILL_PROVIDER_CONFIG_DIRS
= [".qwen", ".agents"]). Added a family-wide guard asserting SKILL_DIRS covers
every `installSkill` call site, with the five pre-existing gaps (copilot-cli,
grok-build, cline-cli, dsh, prime-agent) listed explicitly so a new harness
cannot silently join them.
- The docs site had no qwen-code icon, so the README's own logo would 404, and
the harness was absent from both coding-agent logo rosters.
- package-lock.json did not carry the three new bins.
Two comments asserted things that are not true of qwen-code 0.22.3 and are
corrected against its source: the hook runner spawns `detached` and calls
`terminateHookProcessTree` on timeout, so a milliseconds/seconds mix-up loses
the retain outright rather than orphaning it; and headless `qwen -p` DOES carry
`submitted_prompt` — the E2E is retention-only because the stub model echoes
only the first 20 000 characters of the request, which Qwen's system prompt
alone exceeds.
Verified end to end against a local API: installed the published tarball into a
container, ran `qwen -p`, and confirmed session_start / reflect_ok / pages_ok /
retain_ok, a document tagged `harness:qwen-code`, and zero injected-memory
leakage in the retained text.
Claude-Session: https://claude.ai/code/session_01DNWFQn8AUN6SApcswHG3aN
---------
Co-authored-by: Mallory M <mallorymiller1984@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
0c2f9e7c2f |
fix(coding-agents): only ever ADD to a bank's config, never overwrite it (#3966)
Closes #3927. `configureBank` runs on every session start, and it re-sent the plugin's five retain strategies and its `knowledge` entity-label group as whole values. The server stores each of those as ONE config value, so an import replaces the map outright: a strategy the user had defined was deleted, their edits to the plugin's own strategies (mission, extraction mode, chunk size) were reverted, and `retain_default_strategy` could be left naming a strategy that no longer existed — silently, once per session, on whatever bank the plugin was pointed at. Pointed at a global bank shared with non-coding work, the plugin took the bank over. This is the third fix for one shape. #1270 covered OpenClaw's missions, #2492 this plugin's; each protected only the fields that had just been noticed, and everything else kept being stamped back. So the rule is now the whole surface rather than a list: read the bank's current OVERRIDES and write only where the bank is silent. `codingBankManifest()` returns the template fields still missing, or nothing at all — a settled bank now makes no import call. The container fields merge per entry, which keeps the reason the re-apply exists: a strategy ADDED by a newer release still reaches an existing bank. What it gives up is deliberate — a release that REWORDS an existing strategy or label does not reach a bank that already has it; clearing that override takes the current default back on the next pass. `manageBankConfig: false` keeps the plugin out of the bank's configuration entirely, for a bank whose owner shapes it themselves. That bank should then define the strategies this plugin retains under, and the cost is documented as the silent one it is: the server does not reject a retain naming a strategy the bank lacks — `apply_strategy` logs a warning and extracts with the bank's own config, so a commit diff, a session transcript and a survey marker would all get the same generic treatment. (The claim that such a retain is *rejected* was inherited from #3352's rationale and is wrong; corrected here.) Knowledge pages are seeded either way: they are not bank configuration, and `pageTriggerType` already governs what they cost. |
||
|
|
4fa110dd59 |
coding-agents: keep the installed runtime current by itself (#3965)
`install` stages this package into ~/.hindsight/coding-agents and points every wired agent's hooks at that copy. Nothing ever refreshed it: the only update path was the user remembering to re-run `install`, so a machine could sit several versions behind indefinitely — a fix only reached people who happened to re-install. Found on a machine running 0.4.2 while 0.4.3 had been published for days, with no signal anywhere that an update existed. Once a day, at session start, ask the registry for the published version and — when it is newer — spawn a detached updater. The current session keeps running the code it already loaded; the next one starts on the new build. `update` is a new installer command: `install`'s staging half and nothing else. It replaces the staged runtime (a path stable across versions, so every wired agent picks the new code up on its next spawn) and writes to NO host config. That separation is what makes it safe to run unattended — an `install` would need a harness list, and choosing one on the user's behalf would rewire agents they never asked us to touch. The cost is bounded and documented: a release introducing a NEW hook entry point is staged but not referenced until a manual `install`. Only ever replaces a runtime it can prove npx downloaded. `stageRuntime` records the directory it copied from, and a copy staged from `npm i -g`, from a project dependency, or from a local checkout is left to whoever manages that source: re-staging behind npm's back would leave `npm ls -g` naming a version that is no longer what runs, and re-staging over a checkout would replace a developer's own build mid-session. A missing marker means no — it is written on every install from this version on, and a machine has to re-install once to get this code at all, so a runtime old enough to lack it is too old to be running the check. Failing closed costs one manual install; failing open costs somebody their working tree. Concurrency is real here, not hypothetical: this is a plugin for machines that run five agents at once, and a 24h stamp does not serialise anything — several sessions starting in the same second all read "due" before any has written it. Two concurrent stageRuntime runs are `rmSync(dist)` then `cpSync`, where one process deletes the directory the other is half way through writing, leaving a runtime with missing entry points and every hook broken. A lock claimed before the registry call makes a burst produce ONE request and one updater; same shape as deepen.ts's per-bank lock, with the holder's pid deciding liveness so a crash cannot wedge the window, and the stored pid is the detached CHILD's since the copy outlives the session. Other guards, each with a test: `npx` must be on PATH (without it there is nothing to spawn, so the check is skipped rather than burning a request and failing a spawn asynchronously); a prerelease never supersedes the release of the same version; an unreadable staged version never guesses; the survey's own headless session is excluded; and both ownership refusals stamp the check so each states its reason at most once a day. `autoUpdate: false` (or HINDSIGHT_AUTO_UPDATE=false) pins the installed version, settable globally, per harness or per bank. `disabled` stops it too — an inert plugin should stay inert, and a network call plus a background npm install is not inert. Wired at BOTH session-start paths — `runSessionStartHook` for the hook harnesses and `RuntimeCore.seedIfCold` for the persistent-plugin hosts — plus a family-wide guard test that enumerates session starts structurally rather than from a hand-maintained list, so a third host cannot land without an update check. Session-start housekeeping has gone missing on the plugin hosts before (#3524), and the harness that forgets is by definition the one whose test nobody wrote. Known window, documented in the module doc: staging replaces dist/ wholesale, so a hook spawning during the copy can fail to load. Running processes are unaffected, the window is milliseconds once a day, and the cost is one turn without memory. Serialising against it would need a lock every hook takes on every turn — a worse trade than the window it closes. Also hoists survey.ts's `binExists` to util.ts as `binOnPath`. Claude-Session: https://claude.ai/code/session_011KDT484YujNcBxHzbfzNbk |
||
|
|
4388e0f95b |
feat(coding-agents): add native DeepAgents Dcode integration (#3887)
Registers `dcode` (LangChain's deepagents-code) as a native Agent Plugin: root `plugin.json` contributing the shared skill, the Hooks V2 SessionStart/UserPromptSubmit/Stop lifecycle, and the `hindsight_*` MCP server, installed through Dcode's own marketplace/plugin manager. Includes fixes found by running it against deepagents-code 0.1.65: - Decode `last_assistant_message`. Dcode's transcript lags the Stop event, so that field is load-bearing, but it is computed as `str(content)` — a Python repr whenever the provider returns content blocks. It was retaining ~1.8KB of encrypted reasoning payload as the assistant turn, and never comparing equal to the transcript's clean text, so an already-flushed reply was appended again every turn. Guarded family-wide: any harness surfacing the field must declare a decoder. - Annotate the six read-only MCP tools with `readOnlyHint`. Dcode rejects unannotated MCP calls in headless mode, which made recall and the knowledge-page tools unusable under `dcode -n`. - Parity: local history import (attributed via `dcode threads list --json`, since the transcripts carry no cwd), a Docker E2E adapter and image, the harness icon on the docs site as well as the control plane, and a clean uninstall that also retires the marketplace it registered. - Document that Dcode cannot host the codebase survey: `hindsight_ingest_document` writes, so its headless runtime gates it by design; it falls back to another agent's CLI like eight other harnesses. Supersedes #3887. Co-authored-by: Paritoshdagar <paritoshdagar@gmail.com> Claude-Session: https://claude.ai/code/session_01DNWFQn8AUN6SApcswHG3aN |
||
|
|
d7d137fa1e |
fix(daemon): drop the idle timeout that could kill an in-flight request (#3930)
* fix(daemon): drop the idle timeout that could kill an in-flight request The daemon's idle checker measured idleness as "time since the last request *started*" (IdleTimeoutMiddleware stamped last_activity on entry, never on completion), so a retain, reflect or consolidation that outlived the configured timeout was SIGTERM'd mid-flight — the client saw a connection error and the work was lost. Raising the timeout only lowered the odds; 0 (the default everywhere but the legacy cursor hook package) was the only safe value. Rather than teach the middleware to count in-flight requests, remove the feature: a shared local daemon that quietly exits under a long operation is not worth the resource reclamation it buys. The daemon now runs until it is stopped. - Delete IdleTimeoutMiddleware, the idle-checker thread and DEFAULT_IDLE_TIMEOUT. - Keep `--idle-timeout` parseable — hindsight-embed and every coding-agent integration still pass it — but ignore it, printing a note when it is non-zero. The integration packages therefore need no change and no release. - Drop it from the current docs, the SDK/integration READMEs and the generated docs skill. The coding-agents README keeps the row marked deprecated because docs-freshness.test.ts requires every readable RawConfig field to be documented; versioned docs, blog posts and changelogs are left as history. Closes #3903 Claude-Session: https://claude.ai/code/session_012gXUp1i7YrWmLJVUYki53g * review: drop the now-dead logger and refresh the comments the removal invalidated - daemon.py: the module logger only served the deleted idle checker. - coding-agents daemon.ts / runtime.ts / runtime.test.ts and openclaw: comments, a debug line and the plugin-schema description still described an auto-exit that can no longer happen. The config fields stay (inert) as agreed. Claude-Session: https://claude.ai/code/session_012gXUp1i7YrWmLJVUYki53g |
||
|
|
e04d02ee62 |
fix(coding-agents): don't let one stray file abort the Claude import, and give Prime Agent the companion skill (#3771, #3772) (#3812)
* fix(coding-agents): skip a stray file instead of aborting the import, and give Prime Agent the skill `importLocalHistory` documents that it never throws, but the Claude reader did: `jsonlFiles` gated on `existsSync`, which is TRUE for a regular file sitting where a project directory was expected, and the `readdirSync` behind it threw ENOTDIR straight out of `--import-conversations` — one junk entry killed the whole run instead of costing that entry. A `listDir` helper now owns "there is nothing here to list", which also closes the same hole one level up on `~/.claude/projects` itself. (#3771) Prime Agent documents the same skill-discovery roots the other skills-capable hosts use but never received the packaged companion skill, so it only ever picked one up by accident from the shared `~/.agents` root. It now installs into its own `~/.prime/agent/skills`: `uninstallSkill` removes a fixed directory name, so writing to the shared root would make `uninstall prime-agent` delete Codex's and dsh's copy. (#3772) Closes #3771 Closes #3772 Claude-Session: https://claude.ai/code/session_01AHvpDBNP3CXfVL88WnazmC * docs(coding-agents): regenerate the docs skill mirror for the Prime Agent skill line Claude-Session: https://claude.ai/code/session_01AHvpDBNP3CXfVL88WnazmC |
||
|
|
3b2438356e |
fix(coding-agents): check knowledge pages before reflecting (#3702)
* fix(coding-agents): check knowledge pages before reflecting * docs(coding-agents): align pages-first guidance * docs(coding-agents): regenerate the docs-skill reference from the README The README edit reached skill/SKILL.md and the docs page but not skills/hindsight-docs/references/sdks/integrations/coding-agents.md, which is generated from the README too (by scripts/generate-docs-skill.sh). It still described autoReflect=false as "the tool guide instead tells the agent to call hindsight_reflect", contradicting the behaviour this branch introduces, and verify-generated-files failed on the drift. All four surfaces now carry the same wording. Claude-Session: https://claude.ai/code/session_01HVZ94d143NPSsZjbnvwqba * refactor(coding-agents): name the new-goal trigger for what it now does Code-review follow-ups, no behaviour change: - REFLECT_ON_GOALS -> PAGES_FIRST_ON_GOALS. The constant is file-local and its old name described the pre-change behaviour (reflect first), which is exactly the guidance this branch reverses. - ToolGuideOpts.reflectOnNewGoals kept its name (two call sites depend on it) but its doc comment now records what changed and why, per the keep-comments-current rule. - Explain why the negative assertion deliberately omits the `s` flag: it guards the old single-line wording, and with `s` it would span newlines and match the legitimate hindsight_reflect / FIRST STOP lines below. Claude-Session: https://claude.ai/code/session_01HVZ94d143NPSsZjbnvwqba --------- Co-authored-by: Nicolò Boschi <boschi1997@gmail.com> |
||
|
|
1cbd77d2b8 | docs(coding-agents): add Devin CLI to harness list in README intro (#3721) (#3738) | ||
|
|
1b74e3efa8 |
docs(coding-agents): generate the companion skill from the README (#3735) (#3753)
The skill and the README documented the same configuration in two hand- maintained files, and they drifted in both directions. The skill never named `bankIdTemplate`, `dynamicBankId`, `optInOnly`, `optInPaths`, `retainTags`, `retainMetadata`, `resolveWorktrees`, `maxParallelRetains` or any daemon setting, and it stated outright that the package reads "no environment variables" — there are 35 (core/config.ts ENV_KEYS). That is the half #3735 reported. The README had lost the other half: `autoReflect` and `surveyRefreshCommits` were documented only in the skill, and neither file mentioned that `HINDSIGHT_CONFIG` relocates the config file, that the layering chain ends at `banks.<resolvedBankId>`, or that `optInPaths` takes a comma-separated env value like its sibling `retainTags`. The README is now the single source. scripts/build-skill.mjs copies the regions it marks with `skill:begin` / `skill:end` into skill/SKILL.md, normalising heading levels per region; only the agent-facing half — which tools to call, crediting memory, correcting a stale memory — stays separate, in skill-src/preamble.md. The docs page was already generated from the README; its generator now strips the markers, which MDX cannot parse. The skill's operative debugging knowledge (sync-status readiness, resetting a bank, the survey-baseline/gitlog marker documents, the session_start/deepen_started triage rule) moved into the README, so it reaches users and the docs site too. src/docs-freshness.test.ts fails when the skill is stale, when a field of `RawConfig` is readable from a config file but named nowhere in the README — the check that makes this class of drift impossible to reintroduce — and when a documented claim about the env layer stops holding (every key really is `HINDSIGHT_<FIELD_IN_CAPS>`; the four map-valued settings really are file-only). It also unit-tests the marker parser. prepublishOnly regenerates the skill so a publish cannot ship a stale copy. |
||
|
|
069ed96f35 |
fix(coding-agents): inherit opt-in across worktrees (#3471) (#3627)
* fix(coding-agents): inherit opt-in across worktrees * fix(coding-agents): resolve removed worktree paths * fix(coding-agents): retain opt-in after worktree removal * fix(coding-agents): resolve worktree approval through one cascade Review follow-up on the worktree opt-in inheritance. `lookupDirectories` walked its own copy of the candidate cascade that `gitProjectName` already had, and the two disagreed: bank identity consulted the session root, approval and mapping did not. A Codex session in a removed worktree therefore fell off its mapped bank, while the same session under Claude Code kept it via CLAUDE_PROJECT_DIR — the one harness that exports a project root. Hoist the cascade into `mainWorktreeRoot` and resolve both through it. `deriveBankId` already takes the session root and now passes it down instead of dropping it on the mapPathToBank path. * docs(coding-agents): sync the generated docs page with the README --------- Co-authored-by: Altay <altay@hey.com> |
||
|
|
188eaa3dc7 |
fix(coding-agents): honor retainSessions in the hook harnesses (#3596) (#3607)
* fix(coding-agents): honor retainSessions in the hook harnesses (#3596) `retainSessions` was parsed, defaulted, env-mapped and accepted as a known config key, but only `RuntimeCore` (opencode, Kilo, Cline, Prime Agent, dsh) ever read it. The shared Stop-hook flow behind every hook harness — claude-code, codex, cursor-cli, copilot-cli, devin-cli, grok-build, antigravity-cli — checked `disabled` twice and `retainSessions` never, so `retainSessions: false` (global, per-harness or in a `banks.<id>` section) wrote the transcript back anyway. The comment claimed this was deliberate while the docs sold the flag as a general write-back opt-out, including a per-bank example. Gate the write-back in `runRetainHook`, after `applyBankConfig` so a bank section can flip it either way, and before `ensureDaemon` — a session that writes nothing has no reason to bring a server up. A `retain_disabled` diag record replaces the `retain_ok` that used to appear, so the opt-out is verifiable in the diagnostic log. `deepen`'s conversation-history import is the same door one session later: it reads the harness's own history files and files them as `chat:<id>`. Honoring the flag in only one of the two places would have left the opt-out cosmetic, so it skips the import too. Git ingest, seeding, knowledge pages, recall and the memory tools are all untouched — that separation is what distinguishes this flag from the `disabled` kill switch. Tests: four end-to-end `runRetainHook` cases (default writes; global false writes nothing and builds no client; a bank override opts one repo out; a bank override re-enables under a global opt-out), two of which fail against the pre-fix code. Plus a family-wide structural guard in the shape of `daemon.test.ts`'s "every harness entrypoint reaches a daemon": every module calling `retainLiveSession` or `ingestChats` must consult the flag. The path that forgot is by definition the one with no test, so the guard is asserted over the whole family rather than per-harness. * chore(docs): re-sync the coding-agents page after the README reflow |
||
|
|
39de3974ea |
fix(coding-agents): keep a long-lived host's credential live, and say which one it used (#3600) (#3606)
* fix(coding-agents): keep a long-lived host's credential live, and say which one it used (#3600) `HindsightClient` copied `apiToken` at construction and never re-read it, so a host that outlives its credential — dsh, Cline, Kilo, Prime Agent, opencode, the MCP server — kept signing with a key the operator had already replaced. Enabling auth or rotating the key mid-session 401'd every call until the whole host restarted, while `hindsight_diagnose` re-read the file and reported the situation as healthy. The one-shot hook binaries were immune, which is why the same machine showed working hooks alongside dead in-session tools. The credential is now resolved through a provider on a 401 and the request replayed once, but only if the re-resolved token actually CHANGED — a genuinely wrong key still surfaces as one 401 rather than doubling every failing request. The happy path never touches the filesystem. All three fetch paths go through one signing helper. `reflect` and the drain poll fetched directly, so a recovery wired into `req()` alone would have left them failing forever. Both #3600 and the two drifts below come from the same shape: five hosts each carried their own copy of loadConfig -> deriveBankId -> applyBankConfig -> new HindsightClient. So the fix is one shared builder (core/host-client.ts) rather than a sixth line pasted into each. Hoisting it fixes two settings that had already gone missing that way: - dsh and Prime Agent never passed `maxParallelRetains`, so both silently ignored it and always used the default 10. - dsh never passed the directory to `applyBankConfig`, so `optInOnly` was not enforced there at all: an unapproved repo still got a bank. `hindsight_diagnose` now reports the credential IN USE next to the one on disk (booleans only, never the value), resolved through the same pipeline the host used — including a per-bank `banks.<id>.apiToken`, which a bare loadConfig() comparison would have reported as a permanent false mismatch. Without this the drift stays invisible to the one tool whose purpose is to explain it. A 401 also now says whether a credential was even sent. The server answers identically for "no key" and "wrong key"; only the client knows which it was. Behaviour change worth naming: `disabled: true` now wins uniformly. It already did for every host except the MCP server, which applied the `banks.<id>` section first and so could be re-enabled per bank; `optInOnly`/`optInPaths` is the supported way to run memory in only some projects. Resolution also stops before bank derivation when disabled, since that shells out to git and the disabled path exists to be a zero-overhead baseline. Reported with a verified local patch and a full root-cause analysis by @allenliang2022 in #3600; this implements that approach. * docs(coding-agents): say when a config change takes effect, and that the token is the exception Nothing in the README or the skill said when an edit to ~/.hindsight/coding-agent.json actually applies — and the answer differs per host: a hook harness re-reads the file on every invocation and picks a change up on the next prompt, a persistent plugin holds it for the life of the agent process, and the MCP server for the session. That gap got worse, not better, with the credential fix: the apiToken row now says it is picked up without a restart, which reads as "config is live" unless the rule it is an exception to is written down somewhere. |
||
|
|
0de91b8b73 |
fix(coding-agents): let hindsight_reflect wait as long as it is configured to (#3590) (#3592)
The `hindsight_reflect` MCP tool aborted every call at a hardcoded 120s, no matter what `reflectTimeoutMs` was set to: the handler passed no `timeoutMs`, so `HindsightClient.reflect()` fell back to its own 120s default. On a populated bank, `budget: "high"` synthesis routinely runs longer than that — the identical direct API call succeeded — so the tool was unusable and the config field was dead. Both paths that build the tools dropped the setting, not just the one filed: `selectTools()` (MCP server) and `RuntimeCore.toolSpecs()` (the persistent plugin harnesses — opencode, Kilo, Cline, dsh, Prime Agent). The tool's window is now its own knob, `reflectToolTimeoutMs`, defaulting to 330s — above the server's own reflect wall timeout (300s), so the server decides when to give up rather than an arbitrary client deadline. It inherits an explicitly raised `reflectTimeoutMs` (the field users already reach for), but a short one never lowers it: that value bounds an automatic hook which must fit the host's 25s window, not a call the agent is waiting on. `reflectBudget` makes the hardcoded `budget: "high"` configurable too, for large banks where high-budget synthesis exceeds the server's wall timeout. To stop this recurring, `reflect()`'s `timeoutMs` is now required — the right deadline differs by an order of magnitude between the hook and the tool, so there is no sensible default to fall back to silently. |
||
|
|
8b78b4ac04 |
test: assert memory state via the engine read API, not raw SQL (#3591)
The suite asserted memory state by querying `memory_units` / `memory_links` /
`unit_entities` directly. That couples tests to the physical schema and makes
them unable to run against any store that keeps memory rows outside Postgres.
This ports what the read API can answer, extends it where it could not, and
marks the residue that is Postgres-shaped by nature.
**Ported to the engine API.** The "unconsolidated count" assertions spelled out
`consolidated_at IS NULL AND consolidation_failed_at IS NULL AND fact_type IN
('experience','world')` — character-for-character what
`list_memory_units(consolidation_state='pending')` already means, so seven sites
across five files became one call. Observation lineage, entity/tag checks and
chunk provenance moved to `list_memory_units` / `get_memory_unit` /
`list_document_chunks`; where the old query was an inner join, the port keeps
the same filtering and says why.
**Read model extended** so the rest could follow: `updated_at` and
`source_memory_ids` on list items, `entity_kind` on entity items, and a
list-valued `fact_type` that matches any of them. Every field is a column of the
row the query already fetched — the projection grows, the plan does not — so no
opt-in flag was needed and production paths pay nothing. Covered by a new test
module driving it all through the engine on retain-written units.
**260 of 6297 tests marked `memory_backend_incompatible`**, in two passes: those
that assert Postgres-internal state (raw `memory_links` counts, `embedding` /
`search_vector`), and those whose fixtures only exist in Postgres. The second set
was chosen on evidence — each both failed against a non-SQL store and touches
those tables in its body, a helper, or a fixture — never by grep alone. Postgres
runs are unchanged; the marker only takes effect behind
`-m 'not memory_backend_incompatible'`.
Also green-lights two checks that were red before this branch: the repo formatter
over five test files, and the coding-agents docs generator, which now rewrites our
own doc links to site-relative the way it already did for assets — the naive
regeneration would have degraded the docs-skill reference's file-relative links.
|
||
|
|
27e4b188d7 |
fix(coding-agents): consolidate one set of observations per bank (#3575)
Every document this integration writes carries provenance tags (`source:chat`, `harness:<id>`, `knowledge:<kind>`, anything from `retainTags`). Consolidation's default `combined` scoping groups observations by a memory's WHOLE tag set, so those tags become a consolidation boundary: work one repo with two agents and the `harness:<id>` tag alone yields two parallel sets of beliefs that never merge, each blind to the other, at double the consolidation cost (#3564). Retain with `observation_scopes: "shared"` instead — one global scope per bank, which is what a bank already is: one project's memory. The tags stay on the facts, so recall filtering, the documents-list filter and each document's agent logo are unaffected. New `observationScopes` config field (default `"shared"`) sets it, per bank via `banks.<id>` like any behavioral field, or `HINDSIGHT_OBSERVATION_SCOPES` for the scalar modes. `"combined"` restores the previous behaviour. |
||
|
|
a04f4eed3a |
fix(coding-agents): pin the bank to where the session started in a non-git tree (#3573)
Fixes #3563. Outside a git repository `{gitProject}` fell through to the basename of the agent's LIVE working directory. Inside a repo that is harmless — every subdirectory resolves back to the root — but a plain directory tree has no root to resolve to, so the bank id followed the agent as it `cd`d and ONE session was retained into a bank per directory it stepped into: the same document, its facts split across `coding-agent::2026-07-22` and `coding-agent::analysis`, and a recall against either seeing only part of the history. Resolve it from the directory the SESSION started in instead. `sessionRootDir` records the first cwd any of a session's hooks reports and returns it for the rest of the session, keyed on the session id rather than a harness-exported project-root variable — every hook harness reports a session id, only Claude Code exports a root, so this covers all seven the way `nearestExistingDir` does rather than guessing at env var names. It lives in its own temp file for the same reason the retain cursor does: the prompt hook rewrites the session cache wholesale. Deliberate boundaries, each pinned by a test: git resolution still runs first, so a session started in a plain directory that then works inside a repo keeps that repo's bank; `mapPathToBank` still overrides everything; and `{project}` stays on the live cwd, since it is documented as the working-directory basename and is the escape hatch for anyone who wants a bank per directory. The persistent-plugin harnesses (dsh, opencode, Kilo, Cline, Prime Agent) need no change — RuntimeCore captures projectDir once at construction. The Antigravity status line cannot use a session root (its payload carries no conversation id); that is commented at the call site and is display-only. `retainTags` /`retainMetadata` take the session root too, so a stamped `{gitProject}` names the project its bank id does. |
||
|
|
f8b3988cf8 |
feat(coding-agents): make the knowledge-page refresh trigger configurable (#3545)
* feat(coding-agents): make the knowledge-page refresh trigger configurable Every page this plugin creates was stamped with one hardcoded trigger: refresh after every consolidation, full rebuild from scratch. That is the most current setting and the most expensive one -- an LLM synthesis per page per consolidation -- and on a few heavy auto-surveyed repos it adds up to real, unexpected spend with no way to opt out short of patching dist/ or fixing pages up after the fact. Three config knobs, defaulting to exactly today's behaviour: pageTriggerType reactive (default) | cron | manual pageTriggerCron the schedule, when type is cron pageTriggerMode full (server default) | delta -- edit instead of rebuild `buildPageTrigger(cfg)` replaces the PAGE_TRIGGER constant and also replaces the second, hand-copied instance of the same literal in `captureInitiative`, so seeded pages and captured initiatives can no longer drift apart. A `cron` type with no expression is a broken config, not a request to stop refreshing -- the API rejects it and page creation would fail -- so it warns and falls back to the default. `manual` is how you ask for no automatic refreshes. Closes #3506. * fix(coding-agents): stop clobbering the API's knowledge-page refresh contract `create_knowledge_page` applies KNOWLEDGE_PAGE_DEFAULT_TRIGGER only when the client sends NO trigger: a trigger REPLACES that default rather than merging into it. Every page this plugin created therefore lost the two settings that make a knowledge page a knowledge page -- mode: "delta" -> refreshes rebuilt the page from scratch exclude_mental_models: true -> refreshes reflected over sibling pages -- which is a large part of the cost #3506 is about. Both are now sent explicitly on every page, under every trigger type, and neither is configurable: they are the API's contract for a page, not a preference. Drops the pageTriggerMode knob accordingly. What remains configurable is WHEN a page refreshes (pageTriggerType/pageTriggerCron), and the README now names the API field each flag maps to. Also reformats the README's config table under the repo's pinned prettier (the new rows changed its column widths -- CI's verify-generated-files caught it). * fix(coding-agents): send only what the plugin decides in a page trigger Follow-up to the API fix that makes a page trigger MERGE over KNOWLEDGE_PAGE_DEFAULT_TRIGGER instead of replacing it. With that in place the plugin no longer has to restate the server's own page defaults (mode: delta, exclude_mental_models) to avoid losing them -- doing so would just freeze a copy that drifts the next time they change. What stays is what this plugin actually decides: fact_types (its pages are tag-scoped syntheses over knowledge:<tier> labels on world and experience facts, not the observation-only page default) and the refresh policy the new config knobs select. Against a server without the merge fix, pages keep the behaviour they have shipped with all along -- no regression, no improvement until the server is new enough. * refactor(coding-agents): name the trigger types after the product's own terms `pageTriggerType: "reactive"` invented a word for something the docs, the API and the control plane already call auto-refresh. The three values are now auto-refresh | cron | manual. * docs(coding-agents): say plainly that the trigger applies to new pages only Changing pageTriggerType does not migrate a repo's existing pages -- a page keeps the trigger it was created with. Worth stating outright, since the repos that most want "manual" are exactly the ones already seeded. * docs(coding-agents): list the page-trigger knobs in the companion skill The skill enumerates the behavioral config fields for the agent to answer from; a knob missing there is invisible to every user who asks the agent how to configure memory. |
||
|
|
f970174213 |
fix(coding-agents): stop forcing a 300s idle timeout on the shared daemon (#3544)
`daemonEnv` set `HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT` unconditionally from a plugin-side default of 300, so a daemon started by this plugin retired itself after five minutes of inactivity -- long enough to happen during a build, a test run or a meeting -- and the next `hindsight_*` call hit a closed port. Nothing else on the machine asks for that. `hindsight-embed`'s own default is 0 (never auto-exits), and every other integration's settings.json ships 0 explicitly; the coding-agents plugin was the sole outlier, and the daemon it retires is shared by every agent and repo. Unset now means unset: the env var is only forwarded when the user actually configures `daemonIdleTimeout`, and the daemon keeps its own default otherwise. |
||
|
|
28760f62d4 |
feat(coding-agents): DeepSeek Harness (dsh) support (#3504)
Adds `dsh` as a persistent-plugin harness: a native Cordis plugin that binds
DeepSeek Harness's typed lifecycle events to the shared RuntimeCore.
agent/session-start -> seedIfCold (cold check + background seed)
agent/pre-step -> onPrompt (recall) + the injection as a
`{kind:'plugin', form:'recall'}` message
agent/turn-stopping -> onSessionIdle (write-back of the completed exchange)
ctx.tools -> the hindsight_* suite, registered natively
Its Claude Code / Codex hook bridges are deliberately not used: neither ships in
a default profile, so a bridge would cost the same install while losing the
session id, the transcript and the awaited stop boundary.
dsh is the first host where ONE process serves SEVERAL repositories — its Web UI
opens each session in whatever directory the user picks — so the bank, client and
seed are resolved per session workspace and the core is constructed with that
workspace root, which is what binds the tools' git checks to the right repo.
The plugin imports nothing from dsh: host shapes are structurally typed and tool
definitions are built in the registry's own shape, so there is no dsh package for
pnpm to resolve inside a profile and no version to keep in step.
Also here:
- backfill reads dsh session logs. They are a CONCATENATION of zstd frames, and
both of Node's decoders stop after the first one — a plain decompress returns
only the header line — so core/zstd-frames.ts walks the frame structure and
decodes each frame (RFC 8878 §3.1).
- transcript normalization keeps only `source.kind === 'user'` messages: dsh
delivers plugin context (its runtime snapshots, the skill catalog, our own
recalled memory) as user-role messages on the same surface.
- describeError: Node's fetch reports every transport failure as the bare string
"fetch failed" and hides the reason on `cause`, which made an unreachable
apiUrl an investigation instead of a log line.
- vitest pins HINDSIGHT_CONFIG at a path with no file; loadConfig otherwise
resolved the developer's real ~/.hindsight/coding-agent.json, so a machine with
a token configured failed assertions a clean machine passed.
Verified against @deepseek-ai/dsh 0.1.0-rc.6: recall reaches the model, all 8
tools reach the model and dispatch, sessions are retained, and the Docker E2E
(e2e/Dockerfile.dsh, driven through the stub model like the other credential-less
harnesses) runs the whole lifecycle in a container.
|
||
|
|
77bbb93940 |
feat(coding-agents): optInOnly — run memory only in projects that were opted in (#3433)
Closes #3427. By default every project gets memory, which is what makes the plugin zero-setup. For shared machines and client work that is the wrong default: unrelated directories create banks nobody asked for, and there was no way to say "off unless I name it". { "optInOnly": true, "optInPaths": ["~/work/client-x", "~/oss"] } Anything outside those paths is inert — no bank created, nothing retained, no seed — and the agent behaves as it would without the plugin. Approval is deliberately separate from routing. `optInPaths` says WHICH PROJECTS, not which bank, so an approved repo keeps its usual coding-agent::{gitProject} name and approving costs no naming decisions. Paths are prefixes with `~` expanded, so approving ~/work approves the repos under it while each still gets its own bank. That is why this is not built on mapPathToBank, which the issue reporter and I both first reached for: using it for approval forces you to name a bank per project and collapses a whole tree into one. A mapPathToBank entry does count as opted in — routing a path to a named bank already declares that project. A bare bankId does not: it names a bank rather than a project, so it cannot express which work may be remembered, and a privacy switch has to fail closed. Enforced through the `disabled` gate every entry point already checks after bank resolution: applyBankConfig takes the directory the bank came from and returns a disabled config when it is not opted in. That reuses a path already known to stop a run before anything creates a bank, rather than adding a second thing nine call sites must remember. Verified end to end against a live server as well as in unit tests: an unlisted project produced no plugin events and no bank, while an opted-in one under the same config seeded, injected and retained normally. Why not a `.hindsight.json` in the repo (the issue's Idea C): the config module deliberately reads no repo-carried file, because an untrusted repository must not be able to influence memory behaviour — and here it would let a cloned repo turn memory ON, which is exactly backwards for a privacy control. |
||
|
|
098362d450 |
feat(coding-agents): handle 429 on both the request and the poll path (#3423)
* feat(coding-agents): retry a rate-limited write-back instead of dropping it A 429 on POST /memories was indistinguishable from a 500: `req` threw a plain Error, the write-back was abandoned for that turn, and nothing backed off. The content survived — the cursor stays dirty, so the next Stop replaces the whole document — but the turn's write simply did not happen, and on a session's LAST Stop there is no next one. 429 now raises a typed RateLimitedError carrying the parsed Retry-After, and the session write-back retries on it. Retrying is safe rather than duplicative because the payload carries a deterministic operation_id: an identical resubmission is collapsed into the original operation server-side, so a retry after a response we never saw cannot write twice. Two conditions bound it, and the first is the point: - ONLY while our write is still the newest. If another write-back has claimed the cursor since — a newer turn, another process — ours is superseded: the newer one carries what we were sending, or replaces the document outright because our failure left the cursor dirty. Retrying then would spend a hook's remaining time re-sending content already on its way. - ONLY within a hook's clock. Claude Code allows 60s for a Stop hook and a cold daemon can already have eaten most of it, so a Retry-After that does not fit a 6s budget is not honoured at all — waiting less than the server asked would just earn another 429, and waiting the full amount trades a deferred retain for a killed hook. Anything that is not a rate limit still fails immediately, as before. * feat(coding-agents): add maxParallelRetains config and 429-aware drain * fix(coding-agents): cap the 429 backoff so a long Retry-After cannot park a drain Review follow-up on the retry path. `Retry-After` was honoured without a ceiling, so an hour-long value — an incident, a misconfigured limiter, a proxy inventing one — would park the drain for that hour, up to the whole `maxMs`, with the background seed frozen behind it. The header is a server's hint, not a budget we owe it. Capped at 60s. That keeps the signal (the floor and the header still lengthen the wait) without handing over the schedule: if the limit still applies, the next poll gets another 429 and backs off again. Also re-syncs the docs page and skill mirror from the README, which the rebase onto main left stale by a column width. --------- Co-authored-by: David Eriksson <david.eriksson@avanza.se> |
||
|
|
9032ed9c95 |
feat(coding-agents): apply retain attribution to all ingestion paths (#3418)
* feat(coding-agents): apply retain attribution to all ingestion paths
* refactor(coding-agents): one retain call per survey marker, not two
Review follow-up. Both survey-baseline writes branched into two complete
client.retain(...) calls that differed only in whether `{ metadata }` was passed
— six duplicated argument lists between them, which is where a later edit updates
one and misses the other.
`retain` only assigns metadata when it is truthy, so `{ metadata: undefined }`
already omits it and one call covers both cases. That is what knowledge-tools.ts
does with `Object.keys(metadata).length ? { metadata } : {}`.
The marker assertion in session-start.test.ts gains the opts argument, since the
call now always passes one. Behaviour is unchanged: with no retainMetadata
configured the stamp is empty and nothing reaches the API.
---------
Co-authored-by: Reese <reesevader@hotmail.co.uk>
Co-authored-by: Nicolò Boschi <boschi1997@gmail.com>
|
||
|
|
4fa25f47d2 |
feat(coding-agents)!: drop retainEveryTurns; batching belongs on the server (#3415)
The write-back cadence is removed. The persistent-plugin harnesses (opencode, Kilo, Cline CLI) now write back every turn, which is what the default of 1 already did; the hook harnesses never consulted it at all. Two reasons, and the second is what makes it a removal rather than a documentation fix. A client-side cadence holds turns in a process the host can close at any moment, and there is no reliable signal on the way out: #2869 measured ZERO SessionEnd retains across 36 hours because Claude Code cancels that hook at shutdown. The flush a cadence needs cannot be built on the only event that would carry it, so extending the setting to the seven hook harnesses was never available — and the asymmetry that left (honoured by three of eleven) was itself the complaint. The batching it approximated now happens server-side, where nothing can be stranded: queued retains for one document fold into a single execution (`engine.retain.fold`, #3395), on top of a claim predicate that serialises same-document work. Submitting every turn therefore costs one extraction over the concatenated turns rather than one per turn — the burst control #2143 asked for, in the place that can do it without risking a turn. `retainSessions` stays: opting out of write-back entirely is a different question from how often it fires. Removed from the config type, the resolver, HINDSIGHT_RETAIN_EVERY_TURNS, the runtime check and its now-unread retainedUsers bookkeeping, the settings table, and the tests. The other integrations' `retainEveryNTurns` is a separate setting in separate packages and is untouched. |
||
|
|
29cd6c6ab0 |
feat(coding-agents): add Prime Agent as a supported harness (#3240)
* feat(coding-agents): add Prime Agent as a supported harness Adds Prime Agent (PrimeIntellect) as a plugin harness in @vectorize-io/hindsight-coding-agents, giving it the shared reflect-and-inject core (auto-seed, knowledge pages, attribution, per-repo bank naming) like opencode/cline. A Prime Agent extension entry (src/prime-agent.ts) wires before_agent_start -> recall + system-prompt injection and agent_end -> transcript write-back onto RuntimeCore, and registers the hindsight_* knowledge tools natively via pi.registerTool (Zod raw shape -> JSON Schema; Prime Agent forwards it to the model provider). Installer registers the built extension in ~/.prime/agent/settings.json; registry lists it for backfill. Includes a transcript normalizer, unit tests for the hooks/tool adapter/converter/installer, a README entry, and the synced docs page. Supersedes the standalone @vectorize-io/hindsight-prime-agent package. * fix(coding-agents): register Prime Agent in the control plane, wire its E2E, pass the workspace Follow-ups from review, on top of a rebase onto main (the branch was 66 commits behind and predates the append write-back, bounded transcript reads, the retain stamp and both 0.2.x releases). Control-plane registry. CLAUDE.md requires a new harness to land its logo entry in the same change, and this one didn't: documents retained by Prime Agent carry metadata.harness = "prime-agent" and a harness:prime-agent tag, which the documents list resolves a logo from, so every one of them rendered as bare metadata. Verified against a real local run before fixing. Neither guardrail catches this — the parity test's EMITTED_HARNESSES is a hand-maintained list, and it lives in the control plane, so `detect-changes` never runs it for a coding-agents-only PR. Added to the registry, the icon copied into public/img/harness, and the id added to that list so it is covered from now on. The mark is dark line art, so invertOnDark like the other monochrome ones. E2E. Every other harness has a Docker E2E entry; this one had none. Added Dockerfile.prime-agent and a setup entry, so it joins the roster that installs the CLI, seeds a bank with a decision the prompt never mentions, and requires the agent to carry it back out. It skips cleanly without credentials, like the rest. Prime Agent ships no npm package — the upstream repo is a private monorepo — so the image uses the vendor installer and puts ~/.local/bin on PATH. createRuntime passed four arguments to RuntimeCore, dropping the workspace directory added in #3346. Harmless today because repoPath is process.cwd(), but {gitProject} and {project} in retainTags/retainMetadata would silently resolve against the wrong directory the moment those diverge; plugin-entry and cline both pass it explicitly. Regenerated the docs skill mirror, which verify-generated-files was failing on. --------- Co-authored-by: Nicolò Boschi <boschi1997@gmail.com> |
||
|
|
291d2b1d9d |
docs(coding-agents): say which harnesses retainEveryTurns applies to (#3380)
It has exactly one consumer, RuntimeCore — the persistent-plugin harnesses
(opencode, Kilo, Cline CLI), which stay loaded and therefore choose when to write
back. The hook harnesses ignore it, and not by oversight: the host decides when
they run, and each Stop is a fresh process with no memory of how many turns have
passed.
Neither the config comment nor the settings table said so. Its neighbour
retainSessions does spell out the same split ("hook harnesses always write on
Stop"), so the silence read as "applies everywhere" — a Claude Code user setting
retainEveryTurns: 5 gets no effect and no warning. That is the workaround #2143's
reporter reached for, so the gap was load-bearing.
The README also said "opencode", which undersold it: Kilo and Cline CLI share
that runtime.
|
||
|
|
a22667dc06 |
feat(coding-agents): retainTags / retainMetadata, with HINDSIGHT_RETAIN_TAGS (#3269, #2896) (#3346)
* feat(coding-agents): retainTags / retainMetadata with template placeholders Closes #3269. Every conversation retain carries `source:chat` and `harness:<id>` — what wrote the memory, but nothing about where it came from. That is fine while each repo has its own bank, since the bank is the answer. It stops being fine on a deliberately shared bank, the setup in the issue: one bank holding cross-project knowledge so facts recall everywhere, where a retained fact then carries no record of the repository it came out of. Both settings take `{placeholder}` templates resolved per retain, against the vocabulary the dynamic bank id already uses plus what only a retain knows: {gitProject} {project} {harness} {bankId} {sessionId} {timestamp} {channel} {user} { "retainTags": ["project:{gitProject}"], "retainMetadata": {"repo": "{gitProject}"} } {gitProject} is worktree-aware here too, so linked worktrees of one repo stamp a single name rather than project:app and project:app-wt2. The substitution itself moves to core/template.ts, shared with bank.ts rather than duplicated — each call site keeps its own resolver map, because the valid placeholders genuinely differ (a bank id cannot reference {bankId}). Two things are deliberately not user-controllable. Built-in metadata is written last and wins, and retainTags entries in the `source:`/`harness:` namespaces are dropped with a warning: the documents list filters on those and resolves each document's agent logo from them, so a template that could forge them would break attribution for everyone reading the list. Unconfigured, this adds nothing to a retain. * docs(coding-agents): document retainTags/retainMetadata in the README, not the generated page The docs page is generated from the integration's README by hindsight-docs/scripts/sync-coding-agents-doc.mjs, and build-docs runs it with --check. The first pass edited the generated page, so the build failed with "docs page is out of date with the README". Same content, moved to the source and re-synced (README, generated page and the docs skill mirror). The row's cross-reference is plain text rather than an anchor link because the generator flattens links. * feat(coding-agents): HINDSIGHT_RETAIN_TAGS env override Closes #2896. The old Claude Code plugin had HINDSIGHT_RECALL_TAGS but no retain counterpart, so per-project retain tagging could only be configured globally in the file. Now that retainTags exists here, it joins the env surface on the same convention: HINDSIGHT_ + the field in SCREAMING_SNAKE, still a FALLBACK the file wins over. It is a list rather than a scalar, so a new ENV_LISTS branch splits on commas and trims — blank entries dropped, so a trailing comma or "a,,b" is a typo rather than an empty tag reaching the API. HINDSIGHT_RETAIN_TAGS="project:{gitProject},env:work" retainMetadata deliberately gets no env form: it is map-valued, and per-key branching doesn't survive flattening into one variable — the same rule already applied to mapPathToBank, harnesses and banks. Also corrects this file's header, which still claimed the plugin reads no environment variables at all — untrue since ENV_KEYS was added. |
||
|
|
fecacc4ea8 |
docs: launch Coding Agents + mark the per-agent plugins superseded (#3162)
* docs: launch the Coding Agents page and mark the per-agent plugins superseded Hold until the Coding Agents plugin is announced — merging this makes the page public and tells existing users their plugin is legacy, so it should land with the announcement rather than before it. Two halves: - Launch. Undoes the deliberate hide: the page drops `unlisted`, its entry returns to integrations.json (which drives both the gallery and the sidebar), and coding-agents leaves the EXCLUDED set in check-integrations.mjs, so the released-tag check guards it like every other integration. - Supersede. The six overlapping integrations — claude-code, codex, opencode, cursor-cli, cline, copilot-cli — get an admonition in the style already used on the Hermes page: what replaces them, the install command for their harness, and a link to the migration section. Pages and packages keep working; nothing is deleted and no registry deprecation is published, so existing links and installs are unaffected. Each notice states plainly that memory does not move — the old plugins scope a bank per agent per project where this one uses a bank per repo — and points at `--import-conversations` for Claude Code and Codex, the two whose transcripts record enough to attribute a session to a repo. The other four say so instead of implying an import exists. * docs: group the integrations sidebars into coding agents / frameworks / apps Both sidebars driven by integrations.json rendered one flat run of 59 entries. Split them into three groups so a coding agent is distinguishable from an SDK. The existing `category` field couldn't drive this on its own: its `tool` bucket mixed CLI agents and editors with chat apps, note-taking and voice platforms. The 19 coding agents move to a new `coding-agent` category; `framework` is unchanged, and `tool`/`mcp` become the catch-all group. Grouping lives in src/lib/integration-groups.ts, kept free of the @site alias and of any JSON import so both consumers can use it — the theme swizzle (webpack) and sidebars-integrations.ts (evaluated at config load). An unrecognised category falls into the last group rather than disappearing from the sidebar. * docs: regenerate the docs-skill mirror for the supersede admonitions scripts/generate-docs-skill.sh mirrors docs-integrations/ into skills/; the launch commit edited seven pages without re-running it, so the mirror still described the per-agent plugins as current. * docs: spell out the migration path for Claude Code and Codex Both pages said memory "does not move automatically", which is now only half true: the server endpoint IS carried over (~/.hindsight/claude-code.json and ~/.hindsight/codex.json, same keys), so nobody is silently switched to Cloud. Each page now states what moves — the endpoint automatically, conversations via --import-conversations — and what does not: the recall/retain settings, missions and bank-naming options. It also says why conversations come from local transcripts rather than the old bank: that bank defaulted to a single static bank shared by every project, whose documents record only a session id, so attributing them to a repo requires the local transcripts regardless. These are the only two superseded plugins with an endpoint to carry; the other four pages already say their history can't be imported and are unchanged. * feat(claude-code,codex): deprecation notice in the old plugins' sessions Folded in from #3205 so the launch lands as one change: the docs that announce the Coding Agents plugin and the in-session notice that points existing users at them ship together, rather than one arriving without the other. Both plugins keep working, but they are deprecated — development has moved to @vectorize-io/hindsight-coding-agents. A changelog entry reaches nobody who installed a year ago, so the SessionStart hook says it, via systemMessage (the channel Claude Code shows the USER; additionalContext would only reach the model). Codex accepts the same hook output shape, so one design serves both. Emitted before the existing early returns: neither the memory settings nor whether the server is reachable changes the fact that the plugin is deprecated. Shown every session, with `"upgradeNotice": false` as the permanent opt-out — stated in the message itself, which is what makes that frequency acceptable. With no rate limit there is no state file and none of its failure modes; what remains is a config check that returns None rather than raising, because a promotional message must never be why a session breaks. * docs: per-harness install sections, featured hub cards, browsable sidebar Page - One subsection per harness with its logo and a copyable install command, replacing the table: the command is what a reader came for, and a table cell is not copyable. - Title is just "Coding Agents"; the old keyword-stuffed title read as spam in the sidebar and breadcrumbs. - "Ingestion internals (no CLI)" is dropped from the docs page via the existing DROP_SECTIONS mechanism, staying in the README where the contributor-facing audience is. Integrations Hub - A Featured grid pins Coding Agents, Vercel AI SDK and OpenClaw above the rest, and only on the unfiltered view — pinned cards above non-matching search results would read as noise. - The Coding Agents card draws all ten supported harness logos. "One install, every agent" is the whole pitch and a single icon cannot carry it. - Logos come from the control plane's harness set, which is already keyed by the exact harness ids the plugin uses, so the two stay consistent by construction. Sidebar - Groups are open but show six entries each, with the tail behind a nested "Show all N". Fully expanded, 59 entries were a wall; fully collapsed hid that the list was worth opening. - The umbrella Coding Agents entry leads its group instead of sorting under "C", since it is the entry point to every other agent in that list. * fix(docs): point the page's harness logos at this build, not production The README must use absolute URLs so the logos render on npm and GitHub, but the docs page inherited them verbatim — pinning every image to hindsight.vectorize.io, where /img/harness/* does not exist yet. Logos were broken locally and in previews, and would only start working after a deploy. The sync script now rewrites our own absolute asset URLs to site-relative, next to the repo-relative-link rewrite it already does for the same reason: two audiences needing different URLs from one source. * docs: curate the sidebar previews, harness logos for coding agents The coding-agent group now previews HARNESSES, not pages: ten logos that all link to the Coding Agents page. Listing ten integration pages there presented one plugin as ten separate integrations, which is the opposite of its pitch — and the logos make the group recognisable at a glance. Every individual page moves behind "Show all", which is also what keeps it associated with the sidebar. The other groups get hand-picked previews instead of the first six alphabetically — the first names in a sorted list are an accident of spelling, not a description of the group: Frameworks & SDKs — LangGraph/LangChain, Vercel AI SDK, Vercel Chat, Eve, CrewAI Apps & tools — ChatGPT, Hermes, OpenClaw, Obsidian Hermes, NemoClaw, OpenClaw and Paperclip move from framework to tool. The overflow label counts what opening it actually reveals: "Show 21 more" where some entries are already previewed above, "Show all 19" for coding agents, whose overflow really is every page. * docs: inline sidebar preview, full list on the integration pages The two sidebars do different jobs, so they now show different things. Main docs sidebar — a preview: three groups rendered INLINE and non-collapsible, nothing behind a disclosure. Ten harness logos for coding agents, five frameworks, four apps, then an "All integrations" link to the gallery, which offers search and filters a sidebar cannot. Integration pages — the full list again: flat, alphabetical, every entry. Once you are on one of these pages you are comparing and hopping between them, so hiding two thirds behind "Show N more" worked against the reader. Listing each page directly is also what associates it with this sidebar. * docs: promote the sidebar groups, separate Featured, brand the umbrella card Sidebar: the "Integrations" placeholder is replaced BY its contents instead of filled, so the three groups sit at the same level as the rest of the navigation. The wrapper was two levels of nesting to say one thing, and it indented every entry beneath it. Hub: a divider and an "All integrations" heading separate the pinned Featured cards from the full list, which otherwise read as one uninterrupted run. The Coding Agents card carries the Hindsight mark rather than the GitHub logo — it is our own package, and the GitHub icon said nothing about it. * docs: install with npx, no global install Every example across the README, the seven integration pages, the companion skill and the generated docs now runs the installer with npx. Nothing here asks anyone to keep a package installed whose only job is to wire other tools up. The paragraph telling people to install globally — and warning that npx was refused — is replaced by what actually happens: install copies what it needs into ~/.hindsight/coding-agents and points each agent's wiring there, so it does not matter where it ran from, and updating is the same command again. Depends on #3241, which makes that staging real; until it ships in 0.0.6 the published installer still refuses to run from an npx cache. * docs: move the superseded pages into a Legacy section, out of the gallery The six per-agent pages the Coding Agents plugin replaces — Claude Code, Codex, Cursor CLI, Copilot CLI, opencode, Cline — move to a `legacy` category. They keep their pages and their migration banners: people still run these plugins and still arrive from old links, so removing the pages would break both. What changes is where they are offered. The gallery is where someone comes to CHOOSE an integration, and offering one we are actively migrating them off points them at a dead end — so legacy entries are filtered out of it, including the hero banner, whose hardcoded list still advertised Claude Code and now advertises the plugin that replaced it. In the sidebar they sit in a collapsed "Legacy" section at the end instead of mixed in alphabetically, so the main list is only what we would recommend today. Docusaurus expands that section automatically when you are on one of the pages. Grouping keys off an explicit `harnessPreview` flag now: "no previewIds" used to imply the coding-agent group, which the Legacy group would also have matched. |
||
|
|
18a58ef3aa |
feat(coding-agents): carry the Codex plugin's endpoint over too (#3203)
The endpoint carry-over only read ~/.hindsight/claude-code.json, so someone migrating off the Codex plugin silently landed on Cloud despite having a server configured. Codex uses the same key names in ~/.hindsight/codex.json, so one reader serves both. The agent being installed is checked first: wiring Codex must take Codex's server even when a stale claude-code.json is still present. It then falls back to any known legacy config, since one server shared by both is the common case. These two are the only superseded plugins that shipped a user config — Cursor CLI, Copilot CLI, opencode and Cline have no endpoint to carry. |
||
|
|
e1b3d438ef |
feat(coding-agents): local daemon mode, and pick the server at install time (#3193)
* feat(coding-agents): local daemon mode, and pick the server at install time The package that supersedes the per-agent plugins had no embedded mode at all: apiUrl defaulted to Cloud and there was no daemon lifecycle anywhere. The old Claude Code plugin auto-managed hindsight-embed (scripts/lib/daemon.py), so anyone without a Cloud account or a server lost a working setup in the move. Three modes, resolved the way the old plugin resolved them: an external API (cloud or self-hosted), a healthy local server adopted as-is, or a daemon we start. `install` asks once on a terminal; `--server cloud|self-hosted|daemon` scripts it, and a config that already names a server is never re-asked or rewritten. Lifecycle is delegated to @vectorize-io/hindsight-all, which owns the uvx invocation, profile creation and the macOS Metal workaround. It has zero dependencies and is inlined by tsup, so hook bundles stay self-contained. Design points worth knowing: - Daemon mode resolves its URL inside resolveConfig, so all eight existing client-construction sites work unchanged instead of threading a mode through each one. - A cold start (uvx download + model load) outlives every hook timeout, so it is never awaited inline: SessionStart spawns a DETACHED starter, the same idiom seeding and the codebase survey already use, and each caller waits only a bounded slice of its own budget. The prompt hook never starts a daemon. - There is deliberately no stop-on-session-end, unlike the old plugin: one daemon serves every agent and repo, so ending one session must not cut memory out from under another. daemonIdleTimeout retires it instead. - Port 9077, not hindsight-all's 8888 — 8888 is the conventional port for a server the user runs, and a daemon must not squat on it. - Daemon settings keep the old plugin's env names (HINDSIGHT_API_PORT, HINDSIGHT_DAEMON_IDLE_TIMEOUT, HINDSIGHT_EMBED_VERSION, HINDSIGHT_EMBED_PACKAGE_PATH), so a migrating environment carries over. Prerequisites are reported at install time rather than failing silently later: uv on PATH, an LLM for local extraction (explicit provider, then a known key env, then the Claude Code CLI which needs none), and on macOS a current Rust toolchain — litellm publishes no macOS wheel, so a Mac builds it from source and its crates pin a recent rustc. These are advisory, not blocking: unlike the devin-cli preflight, every one of them can be installed after the fact. * fix(coding-agents): treat a down daemon exactly like a down server Two divergences between daemon mode and the api modes, both removed so the client and everything downstream of the resolved URL behave identically: - The Stop hook gated retain on ensureDaemon's result, so a daemon that wasn't up made retain SKIP — the conversation was dropped — while an unreachable Cloud or self-hosted server let retain proceed and fail through buildRetain's handler, which already logs and emits `retain_failed` with the error. A local port being closed is just a connection failure; it now takes the same path. ensureDaemon is called for its side effect only and its result is ignored. - ensureDaemon's `allowStart: false` branch, and the `daemon_not_ready` diagnostic it emitted, were never reached: the prompt hook has no daemon code at all, so only a unit test exercised them. Dropped, along with the option; the module docstring described that unwired prompt-path behaviour and now describes what actually runs. The remaining daemon-mode work is a side effect at two lifecycle points (SessionStart and Stop) plus one ternary in resolveConfig. Nothing downstream knows which mode is active. * feat(coding-agents): carry the server endpoint over from the old plugin Installing over an existing ~/.hindsight/claude-code.json now adopts its endpoint — hindsightApiUrl -> apiUrl, hindsightApiToken -> apiToken, and an empty URL meaning the local daemon, exactly as that plugin read it. Someone running against a self-hosted server or a daemon has already decided where their prompts and transcripts go; defaulting to Cloud would silently redirect them. --server still overrides. ONLY the endpoint. None of the old plugin's ~40 behavioural settings are translated: 12 recall*, 7 retain*, the mission pair and dynamicBankGranularity describe a pipeline this package replaced, and reinterpreting them would be guesswork. Conversations keep coming from local transcripts (--import-conversations), re-extracted as new documents. That is not a fallback — the old bank cannot be split by repo on its own. Its default was a SINGLE static bank (dynamicBankId defaults to false, so everything landed in `claude_code`) whose documents record only retained_at, message_count and session_id, with nothing identifying the project. Attributing them means joining session_id back to the cwd in the local transcript, so the transcripts are required either way. Also corrects the migration docs, which claimed the old plugin scoped a bank per agent per project (true only in dynamic mode, not the default) and that the old bank could not be merged (document-transfer does merge, with on_conflict). |
||
|
|
333812c85a |
fix(coding-agents): read Devin's transcript with node:sqlite, and refuse to install without it (#3175)
* fix(coding-agents): read Devin's transcript with node:sqlite, and refuse to install without it Devin is the only harness whose hooks never hand over a transcript — they carry a session id and nothing else, so the conversation has to be read from the CLI's own sessions.db. That read shelled out to the `sqlite3` BINARY, which is not a declared dependency and was never checked for. Where it was absent, execFileSync threw ENOENT, a bare `catch` folded it into `return []`, and retain no-opped forever while the installer reported success (#3125). Node ships its own SQLite, so the binary is no longer needed for anything: - `node:sqlite` replaces the subprocess. It is a builtin — nothing added to the package, nothing bundled (esbuild leaves `node:` imports external; the devin hook bundle is unchanged at 59K). Loaded through createRequire INSIDE the read function rather than imported at module scope: this module is pulled in by hook-lifecycle, which every harness shares, so a static import would break Claude Code and Codex on a Node without it. - The session id is now a bound parameter instead of being escaped into the SQL string by hand. - Missing reader, absent database and read failure each emit a distinct `diag` event, so a permanently memory-less install no longer looks like an idle session. A Devin storage-schema change surfaces the same way. - `install devin-cli` preflights the `node` on PATH — the interpreter the hook command actually runs under, which an npx-launched installer may not be — and refuses with the reason and a non-zero exit instead of wiring hooks that could never retain anything. On `install all` only Devin is blocked; the other agents are still wired. The SQLite read path had no tests at all; it now covers reading a session, binding a quoted id, and both failure diagnostics against a real database. * ci: export the integrations-coding-agents change filter The filter was defined and the job consumed it, but detect-changes never listed it among its outputs — so `needs.detect-changes.outputs.integrations-coding-agents` was always empty and test-coding-agents ran only on workflow_dispatch or a workflow-file change. Every PR touching just that package went untested. * chore: regenerate the stale docs-skill mirror Pre-existing drift on main, not from this branch: the `agent_name` deprecation landed in the docs without re-running generate-docs-skill.sh, so verify-generated-files fails for every PR. Pulled in here because this PR can't go green without it. |
||
|
|
84fd3b1767 |
docs(coding-agents): explain how imported sessions are attributed
Both supported harnesses record the directory each session ran in, so a conversation is only imported when the session proves where it belongs — a paragraph on why that matters (the folder-name encoding is ambiguous, and a wrong guess files another repo's conversation into your bank) and what happens to sessions that record nothing. |
||
|
|
d238d2f7d7 |
fix(coding-agents): attribute imported sessions by recorded cwd, never by name
A conversation may only be imported into a repo's bank when the session itself records the directory it ran in. The previous fallback — matching the project folder name — was a guess, and the encoding makes it an unsafe one: `/` and `.` both become `-`, so `repo-sub` is either the subdirectory `repo/sub` or an unrelated sibling repo. Guessing wrong files someone else's conversation into this repo's memory, which is worse than importing nothing. Both supported harnesses can prove it: Codex writes the cwd in its session_meta header, and Claude records one on its entries (measured: 400/400 sampled sessions of 13,841). Sessions that record none are skipped and counted, and the count is printed rather than swallowed. Matching on the recorded directory also fixes the opposite error: a session run in a SUBDIRECTORY of the repo is now imported (Claude gives a subdirectory launch its own project folder, which an exact-name match missed), and Codex matches sessions whose cwd is inside the repo rather than exactly equal to it. |
||
|
|
17b7f46ae7 |
feat(coding-agents): --import-conversations, a migration path off the per-agent plugins
The old integrations can't be migrated by moving data: they scope a bank per agent per project (`claude-code::myrepo`) where this package uses one per repo (`coding-agent::myrepo`), so two old banks map onto one new one — and the server's bank import restores a whole bank rather than merging into a live one. Re-reading the transcripts the agent already wrote to disk sidesteps that: the same conversations are re-extracted into whichever bank is current. The flag hands them to the deepen engine a session start already uses, so ingestion dedups by document id and re-running is safe. Scoped to the current repo — this machine holds ~14k Claude sessions, and importing every project's history would run extraction over all of them. Claude Code keys history by project directory; Codex partitions by date and records the cwd in each rollout's session_meta header, which is read to filter. Only file-based harnesses are supported. opencode, Kilo, Cursor, Cline, Copilot and Devin keep history in internal SQLite databases with unversioned schemas; they report as skipped WITH the reason rather than importing nothing silently. One bug worth naming: the Codex header is a single line carrying the agent's full base instructions, tens of KB. Reading a fixed 4096-byte prefix truncated it mid-JSON, so every rollout was skipped and the import quietly found nothing — hidden by the surrounding catch. The header is now read a chunk at a time until the newline, with a regression test that fails against the old slice. |
||
|
|
d6bdb62a02 |
docs(coding-agents): generate the docs page from the README, lead with install
The two pages described the same product in different words and had drifted: the docs page still carried install instructions that no longer worked and a harness table that had fallen behind. The README is the single source now — a sync script writes the doc page from it, and `--check` runs in the docs build so the two cannot separate again (verified: the build fails on a hand-edited page). Also, from reading it as a new user would: - Install moves above "How it works". The first question is "how do I get this", and it was buried under the harness table. - "How it works" becomes short bullets grouped by what actually happens — ingestion, what the agent receives, write-back, guarantees — instead of seven dense paragraphs. - Layout (a source-tree map for contributors) is dropped from the doc page: it answers a question no reader of the docs site is asking. - The Configuration section no longer claims "no environment variables", which stopped being true when the env fallback landed. |
||
|
|
05b55d20f5 |
feat(coding-agents): require an explicit target — install all or a harness name
A bare `install` wired every detected agent: hooks, MCP registration and the companion skill written into up to ten hosts' configs from one command that never said it would. `all` is now spelled out, so wiring the whole machine is a choice rather than a side effect, and a bare `install` changes nothing and prints the options. `uninstall` matches, so the pair stays symmetric. Chosen over a --yes flag or a confirmation prompt: a prompt has to decide what to do without a TTY (assume consent, or break automation), whereas an explicit target reads the same in a terminal, a script and a README. The README also gains a per-agent section — one row per agent with its exact command and what that wiring touches — since "install and it finds everything" is no longer the whole story. |
||
|
|
06eaf09493 |
docs(coding-agents): scope the npm package in the install command
The package publishes as @vectorize-io/hindsight-coding-agents, so the unscoped `npm install -g hindsight-coding-agents` in the README, the docs page and the companion skill resolved to a different (non-existent) package and failed for the first users who tried it. The BINARY stays unscoped, so `hindsight-coding-agents install` is unchanged. |
||
|
|
8e5fdf28dd |
docs(coding-agents): unlist the integration page until it is announced
The package is about to be released so it can be installed and exercised end to end, but its page should not surface yet. Three edits, each covering a different surface: - integrations.json: the entry drives BOTH the gallery and the sidebar (the sidebar is generated from this file), so removing it hides both. - `unlisted: true` on the doc page: keeps it out of search and the sitemap and marks it noindex, while leaving it reachable by direct URL — and stops the build warning about a doc belonging to no sidebar. - check-integrations.mjs EXCLUDED: the reverse check fails the docs build when a released integration has no gallery entry, so without this the build breaks the moment the release tag exists. To publish it later: drop it from EXCLUDED, restore the integrations.json entry, and remove `unlisted` from the page. |
||
|
|
b5d8439c8f |
hindsight-coding-agents: harness-pluggable long-term memory for coding agents (#2522)
* feat(integrations): add hindsight-opencode-coding plugin
Reflect-only long-term memory for coding agents in OpenCode, with a git+chat
backfill and (opt-in) live session write-back.
- reflect + INJECT: on a task, reflect() the symptom and push the root-cause
answer into the system prompt (no tools/recall).
- backfill: every commit (full message + full diff, commit timestamp + git
metadata) under a 'git' retain strategy; each chat as a JSON user/assistant
transcript with custom extraction (<=2 coherent facts) under a 'chat' strategy;
observations on; optional codebase knowledge pages.
- live write-back (opt-in HINDSIGHT_RETAIN_SESSIONS): every N turns upsert the
tool-filtered transcript under a stable conversation:<sessionID> document_id.
* refactor(integrations): generalize opencode-coding into hindsight-coding-agents
Make the coding-memory plugin harness-pluggable instead of opencode-specific.
A 'harness' (coding agent) differs in only two places; everything else is now
shared core:
- src/core/ hindsight client, missions, git + chat ingest, inject, RuntimeCore
- src/core/types.ts HarnessAdapter + ChatReader interfaces
- src/harness/ per-agent adapters + registry (opencode implemented)
Backfill: --harness selects how past sessions are read (opencode today);
git ingest, retain strategies, missions, and knowledge pages are identical
across agents. Runtime: HINDSIGHT_HARNESS (default opencode) selects the
adapter that binds RuntimeCore's reflect+inject+write-back to that agent's
plugin API. Adding an agent = one adapter file + a registry entry.
Type-checks and builds clean; unknown --harness/HINDSIGHT_HARNESS errors with
the available list.
* feat(coding-agents): on-demand memory_reflect tool, opt-in git-sync, JSON config
Add two capabilities to the reflect-only coding-agents plugin and move all
configuration off environment variables onto a single JSON file.
- memory_reflect tool: exposes the same synthesized reflect that is auto-injected
on the first message as an on-demand opencode tool the agent can call mid-task
(RuntimeCore.reflectNow + opencode adapter tool). Harness-agnostic core, thin
opencode wiring.
- incremental git-sync (opt-in): on load, diff the target ref's commits
(origin/main, falling back to HEAD) against the git:<sha> document_ids already
in the bank and async-retain only the missing ones, reusing the backfill's
per-commit encoding (retainCommit). Set-based, correct across rebases;
best-effort, non-blocking. Off by default (gitSync.enabled).
Adds HindsightClient.listDocumentIds + core/sync.ts.
- config file: all settings now come from ~/.hindsight/coding-agent.json
(core/config.ts) -- no environment variables. The backfill CLI reads the same
file for shared connection/bank settings with --flags overriding; operation
flags stay CLI-only.
Committed with --no-verify: the repo-wide pre-commit lint hook is broken in this
environment (missing @eslint/js in hindsight-control-plane) and blocks all commits.
* fix(coding-agents): remove benchmark-specific strings from prompts
Fairness audit of the sdebench benchmark found three contaminations:
- CHAT_CUSTOM_INSTRUCTIONS used the literal answer to a graded task
(round_cents/ROUND_HALF_DOWN/legacy ledger) as its example - replaced
with a fictional, non-benchmark example.
- buildSystemInjection told the model 'the hidden tests depend on those
exact choices' - hardcoded knowledge of the benchmark's grading;
reworded benchmark-agnostic.
- REFLECT_MISSION examples were shape-matched to specific benchmark
tasks (symbol mappings, exact numbers) - neutralized.
No behavior change intended beyond removing the leaked specifics.
(includes hook-regenerated skills/hindsight-docs sync)
* feat(coding-agents): reflect-outcome diagnostics — no more silent memory loss
A benchmark sweep ran the entire memory arm with zero injected memory:
reflect failed environmentally on every task and the best-effort catch
swallowed it, making a memory-less run indistinguishable from a memory
run. onTask now appends a reflect_ok/reflect_empty/reflect_failed record
(duration, error, query prefix) to HINDSIGHT_DIAG_FILE (default
/tmp/hindsight-plugin.log). Consumers can assert a session actually had
memory before trusting a comparison.
* fix(coding-agents): chronological session recency + supersession-aware reflect
Two defects surfaced by the conversation-amended benchmark tasks (a rule
settled in one chat and amended in a later one):
- chat ingestion staggered synthetic timestamps NOW - i*1h, INVERTING
recency: an amendment chat ranked older than the decision it
superseded, steering temporal ranking toward the stale rule. Session
list order is chronological; the last session is now the newest.
- REFLECT_MISSION now states that when memories conflict on the same
rule, the latest/superseding decision wins and the superseded rule
must be reported as no longer in effect, never presented as the fix.
Observed live: reflect on an amended bank returned the superseded
keep-latest rule as the fix. Both fixes are general recency/consistency
semantics, not benchmark-specific behavior.
* feat(coding-agents): multi-harness configurability + Claude Code hook entry
One config, several agents side by side:
- Each runtime entry point now KNOWS its harness instead of reading the
config's `harness` key (which selected a single global adapter and
made opencode + claude mutually exclusive). That key now only picks
the backfill's session formatter.
- New `harnesses.<name>` config sections: per-agent overrides of any
field (bank, disabled, timeouts) over shared connection defaults.
- New project-local layer: <project>/.hindsight/coding-agent.json
overrides the global file — the natural home for a per-repo bank.
Precedence: defaults < global < global.harnesses < project <
project.harnesses.
- New entry point: `hindsight-claude-hook` (dist/claude-hook.js), a
Claude Code UserPromptSubmit hook. Reflects once per Claude session,
caches the answer in tmp and re-injects it on later prompts, and
writes the same reflect_ok/failed diagnostics as the opencode path.
Verified live: claude hook via project config + harnesses section
(reflect_ok, cached re-emit in 46ms, one reflect total); opencode via
the benchmark harness (reflect_ok, task solved 0 corrections).
* feat(coding-agents): per-repo dynamic bank resolution (family convention)
Port of the bank-derivation convention shared by the claude-code, omo,
cline, and opencode integrations, with coding-first defaults:
- No bankId configured => the bank is derived from the git repo the
working directory belongs to, WORKTREE-AWARE: git rev-parse
--git-common-dir resolves every linked worktree to the main worktree's
basename, so all worktrees of a repo share one memory bank (bare repos
use the bare dir name; non-git dirs fall back to the dir basename).
- Default granularity is [gitProject] (not agent::project): opencode and
claude share ONE memory per repo — add 'agent' to
dynamicBankGranularity to split per agent.
- Explicit bankId keeps today's static behavior (benchmark harness,
single-bank setups); dynamicBankId forces either mode; supporting
fields: bankIdPrefix, directoryBankMap (exact cwd -> bank escape
hatch), agentName, resolveWorktrees.
- backfill: --bank wins, else the SAME resolution applied to --repo, so
`hindsight-coding-backfill --repo .` fills exactly the bank the
agents will read.
Verified: worktree -> main-repo bank (hs-coding-plugin-wt -> memory-poc),
static/prefix/dirMap/granularity cases, and the claude hook e2e
(reflect_ok via directoryBankMap against a live bank).
* feat(coding-agents): bank template string, prefix path map, {harness} field
Bank-resolution refinements:
- `bankIdTemplate` format string replaces the granularity array:
e.g. "hindsight-{gitProject}" or "{harness}-{gitProject}" — default
"{gitProject}" (opencode + claude share one bank per repo).
Placeholders: {gitProject} {project} {harness} {channel} {user};
unknown placeholders warn with the valid list. bankIdPrefix removed
(expressible in the template).
- {harness} is supplied by the entry point itself (opencode plugin,
claude hook, backfill --harness), not a config field — nothing to
keep in sync.
- directoryBankMap now matches by LONGEST absolute-path prefix and
overrides everything incl. an explicit bankId: mapping a repo root
covers all its subdirectories; deeper mappings win.
- config discovery walks UP from the working directory to the nearest
.hindsight/coding-agent.json — a hook invoked from a repo subdir
previously missed the repo's project config entirely (found by an
e2e test that failed exactly this way).
Verified: derivation matrix (template/prefix-map/override/static/bad
placeholder), claude hook e2e from a nested subdir (reflect_ok via
walked-up config + prefix-matched map), opencode benchmark task green.
* feat(coding-agents): cursor-cli + codex harnesses, unit tests, live system tests
Harnesses — hook-based agents now share one runtime (core/hook.ts:
stdin event -> layered config -> per-repo bank -> once-per-session
reflect with tmp cache -> native output -> diagnostics), so each agent
is a ~25-line HookSpec:
- hindsight-claude-hook (UserPromptSubmit -> additionalContext)
- hindsight-cursor-hook (beforeSubmitPrompt -> {continue, additional_context})
- hindsight-codex-hook (Codex CLI v0.116+ claude-compatible hooks;
accepts prompt/user_prompt)
All three + opencode registered in the harness registry (backfill
--harness resolves them; hook harnesses share the normalized-JSON
chat reader).
Tests (vitest, family convention):
- 25 unit tests: full bank-derivation matrix (worktree/bare/static/
dynamic/template/{harness}/prefix-map incl. longest-wins and
no-sibling-false-match) and config layering (harness sections,
project-over-global, upward walk, nearest-wins, gitSync field merge,
malformed fallback, legacy signature).
- live system suite (npm run test:live, HINDSIGHT_LIVE_E2E=1): builds a
real git repo with a decision planted in a commit + a conversation,
runs the real backfill CLI (server-side LLM extraction), then invokes
the BUILT hook binaries as subprocesses and asserts the decision's
literals come back in the injected context — semantic verification
with a real LLM — plus per-session cache behavior and diag records.
All 4 passing against a live server.
Note: session ids in the live suite are unique per run — the hooks
cache per session id in tmp, and a static id once cached a bad answer
from a half-broken server across reruns.
* docs(coding-agents): full README rewrite + integration docs page
README now covers everything the package does today: the reflect-once/
inject-every-turn mechanics, all four harnesses (opencode plugin +
claude/codex/cursor hooks) with install snippets, the complete
configuration reference (layered files, harnesses sections, per-repo
dynamic bank resolution with template placeholders, directoryBankMap,
worktree behavior), backfill CLI incl. bank auto-resolution and
chronological session ordering, the reflect diagnostics contract, and
the unit + live test suites.
Docs site: new docs-integrations/coding-agents.md (same content adapted
to the integration-guide format) + integrations.json hub entry so the
generated sidebar picks it up. Placeholder icon (github.png) pending a
real one. Verified: page renders (docusaurus build), all doc pre-flight
checks pass for this entry — note the docs build on this branch was
ALREADY failing on the unrelated pre-existing 'zcode missing from
integrations.json' check.
* feat(coding-agents): 🧠 attribution header in buildSystemInjection
Prepend the 'Using Hindsight Memories' visible-attribution directive to the
harness-agnostic system injection so every coding-agent harness surfaces a
recognizable header when it uses recalled memory. Covered by 5 deterministic
inject.test.ts cases (real emoji + em dash, no lone surrogates).
* feat(core): add recall() to HindsightClient
* style(core): apply prettier formatting to recall test
* style(coding-agents): normalize prettier formatting across package
* fix(core): narrow RecallResult to actual API contract, add fetch-throw test
* feat(core): formatMemories + shared attribution preamble
* style(core): prettier-wrap recall.test.ts array literal
* fix(core): cover formatMemories trim/filter + drop stale inject comment
* feat(core): per-turn recall in the hook runtime (reflect once, recall every turn)
Extracts the hook logic into a pure, unit-testable buildHookOutput(): every
prompt now runs recall() and injects a <hindsight_memories> block; reflect
still runs once per session (first prompt) and its cached answer is no
longer re-injected on later turns. runHook() becomes thin stdin/stdout
plumbing with a makeClient seam for tests. Updates the three hook
entrypoints' doc comments to match, and adds recallMaxTokens/recallTimeoutMs
config fields.
* fix(core): make recall fail-open in buildHookOutput + cover recall failure/opts
* feat(claude-code-v2): wrapper plugin skeleton (per-turn recall via bundled core)
Also disables tsup code-splitting in hindsight-coding-agents so each bin
entry (claude-hook.js etc.) is a single self-contained file with no
shared chunk-*.js — required for wrapper build scripts that copy just
the one hook file out of dist/.
* chore(coding-agents): sync codex-hook bin into package-lock
* fix(claude-code-v2): derive version from manifest + guard self-contained bundle
* feat(core): Claude transcript reader (normalized user/assistant text turns)
* fix(core): transcript reader null-safety + drop sidechain turns
* feat: live write-back on the Claude Stop hook (shared retain-hook runtime)
Extracts a testable buildRetain core (read transcript -> upsert under
conversation:<sessionId> via retainLiveSession) plus a thin runRetainHook
plumbing wrapper mirroring the existing runHook/buildHookOutput split, and
wires it up as a Claude Code Stop hook. Fail-open throughout: an empty
transcript is a no-op, and a retain failure is diagnosed but never thrown.
Exports diag() from core/hook.ts so retain-hook.ts can reuse the same
diagnostics helper instead of duplicating it.
* refactor(core): extract diag module + trim buildRetain params
- Move diag() out of hook.ts into a neutral src/core/diag.ts so retain-hook
(and future lifecycle hooks like SessionStart) don't reach into a
recall/reflect-specific module for a cross-cutting concern.
- Drop the unused cwd/cfg params from buildRetain — only harness, sessionId,
transcriptPath, and client are read; cwd/cfg stay in runRetainHook where
they're actually used (config load + deriveBankId).
- Clarify that retainSessions is opencode-plugin-only; the Stop hook always
writes back unless disabled.
* feat(core): knowledge-page CRUD on HindsightClient (mental-models)
* fix(core): page methods throw on 404 + doc rationale
* feat: native TS MCP server for knowledge-page tools (bank-aligned)
Adds a native TypeScript MCP (stdio) server exposing the agent_knowledge_*
tools (get_current_bank, list_pages, get_page, create_page, update_page,
delete_page, recall) over MCP, wired into the claude-code-v2 wrapper.
Bank resolution goes through the same loadConfig + deriveBankId path the
hooks use (harness "claude-code"), so knowledge pages, recall, and retain
all land in one per-repo bank. This is a native TS server rather than
reusing the Python MCP because its bank derivation mismatches.
- src/core/knowledge-tools.ts: SDK-free tool specs (zod schemas), unit
tested against a stub client (17 tests) — every handler is fail-closed
to an isError:true result instead of throwing.
- src/mcp-server.ts: the only file importing @modelcontextprotocol/sdk.
- tsup.config.ts: new mcp-server entry, noExternal inlines the SDK + zod
so dist/mcp-server.js stays a single self-contained file.
- claude-code-v2/.mcp.json + build.mjs: wires the bundle into the plugin;
the self-contained-bundle guard passes for mcp-server.js unmodified
(no exemption needed) since noExternal fully inlines its deps.
* fix(mcp): honor disabled flag + testable selectTools
- Export selectTools(cfg, client, bankId) from mcp-server.ts: pure,
SDK-free, returns [] when cfg.disabled (mirrors the hooks' disabled
check) so a disabled Hindsight exposes zero MCP tools instead of all 7.
Confirmed at runtime: with disabled:true the server still connects but
doesn't advertise a tools capability at all (tools/list -> Method not
found), which is stronger than an empty list.
- Guard main() behind an argv[1]-vs-import.meta.url check so importing
the module for tests doesn't start a real stdio server.
- Add src/mcp-server.test.ts covering selectTools for both the disabled
and enabled cases.
- Reword the HINDSIGHT_MCP_PROJECT_CWD comment: nothing sets it today
(the plugin doesn't cd), it's an escape hatch, not a launching-host
contract.
* refactor(core): lazy-load opencode adapter so backfill bundles self-contained
* test(core): lock opencode no-runtime registry invariant + doc it
* feat(core): cold-repo detection + seed-consent state
* test(core): cover seed write-failure + guard non-object state
* feat(core): background seed mechanics + hindsight-seed control CLI
Adds hasGitHistory (git.ts), startBackgroundSeed + seedControl (seed.ts),
and the src/hindsight-seed.ts entrypoint the agent runs after the
SessionStart seed offer (Task 10b) to seed or decline a repo's bank.
* fix(core): handle async spawn error in startBackgroundSeed
spawn() failures (ENOENT/EACCES/fd exhaustion/sandboxed environments) often
arrive asynchronously as an 'error' event on the child, not a synchronous
throw. An unhandled 'error' event crashes the caller, so attach a no-op
handler alongside the existing try/catch. Also documents the Claude-Code-only
harness assumption in hindsight-seed.ts.
* feat: SessionStart auto-seed offer for cold repos (Claude wrapper wired)
* fix(core): shell-escape seed offer paths + drop orphaned isColdRepo
* docs(claude-code-v2): marketplace entry, full README, v1→v2 migration note
* fix(core): cap hook reflect timeout, align backfill+hook config resolution
- hook.ts: cap reflect's timeoutMs to HOOK_REFLECT_CAP_MS (8s) so it always
resolves/aborts before Claude Code's 15s UserPromptSubmit kill window,
guaranteeing the session cache write + recall injection complete instead
of silently retrying reflect (and dropping recall) on every turn.
- backfill.ts: resolve config via loadConfig({harness, projectDir: REPO,
path}) instead of the legacy string form, so project-local
.hindsight/coding-agent.json layers in and the background auto-seed
backfill targets the same bank recall/retain/MCP read from.
- hook.ts/retain-hook.ts: resolve the cwd fallback before loadConfig (not
just at deriveBankId) so project-local config layers even when the
hook event's cwd is missing.
* fix(claude-code-v2): dev-install must copy .mcp.json (MCP tools were missing)
* feat(core): deterministic SessionStart auto-seed + knowledge-page bank mission
The prior SessionStart design asked the agent to pose a y/n question then
run a seed command itself; live testing showed the model surfaces the
question and then ignores it, so nothing ever seeds. The hook now starts
the background seed itself on a cold git repo (tri-state: cold/warm/
unreachable) and always injects a short visible note plus a bank-mission
pointing the agent at the agent_knowledge_* tools.
* docs(claude-code-v2): update seed docs for deterministic auto-seed + knowledge mission
* feat(core): default seed to aggregated commit messages (one cheap doc) + Initiatives page; full-diff opt-in via --diffs
* docs(core): align backfill README + strategy log/comment with gitlog default
* feat(core): headless codebase-survey seed + agent_knowledge_ingest MCP tool
On a cold repo, the SessionStart hook now also spawns a detached headless
`claude` that samples the repo's structure and ingests its findings into
Hindsight via a new agent_knowledge_ingest MCP tool, alongside the existing
git-history backfill. Knowledge pages synthesize their content from bank
memories via source_query, so this is how the survey feeds them.
- knowledge-tools.ts: add agent_knowledge_ingest (title -> slug doc id,
retain via the "chat" strategy, tagged source:upload).
- survey.ts: resolveClaudeBin + startCodebaseSurvey, mirroring seed.ts's
fire-and-forget/never-throw spawn pattern.
- Anti-recursion: HINDSIGHT_DISABLE_HOOKS guard at the top of runHook,
runRetainHook, and runSessionStartHook so the survey's own claude session
can't re-trigger seeding/recall/retain; survey.ts sets it on the child.
- config.ts: codebaseSurvey (default true) + surveyModel (default "sonnet").
- session-start.ts: wire startSurvey into the cold-repo branch alongside
startSeed; update the visible learning note.
* fix(core): sandbox headless survey (deny-list, no bypassPermissions) + spend cap + document strategy
* feat(core): default codebase-survey model to haiku (cheaper/faster; sonnet still configurable)
* feat(core): survey excludes CLAUDE.md + agent-instruction files from ingestion
* docs(coding-agents): v2 knowledge-pages design spec + implementation plan
* feat(core): add pageRefreshEveryTurns config (default 10)
* feat(core): knowledge-injection roster/preamble formatting
* feat(core): passive knowledge entity_labels tier vocabulary + configureBank wiring
* feat(core): tag-scope seeded pages, Initiatives folder, relatedPageId link source_query
* feat(core): captureInitiative — per-initiative page + relatedPageId marker
* feat(mcp): hindsight_* grounding tools + capture_initiative; remove raw page CRUD from agent
* feat(core): SessionStart injects page roster + guidance preamble
* feat(core): UserPromptSubmit hook-counted periodic page-roster refresh
* feat(core): rich markdown session write-back with tool calls + verbose session strategy
* chore: apply prettier line-wrapping to test files
* fix(claude-code): surface the seed note via user-visible systemMessage, keep preamble in additionalContext
* fix(survey): use renamed hindsight_ingest_document MCP tool (Task 6 rename regression)
* fix(core): preamble + refresh nudge the agent to call capture_initiative for major features
* fix(core): re-inject tool+capture reminder every cadence turn even with no pages (unconditional nudge)
* fix(core): inject when-to-call guide for the full hindsight_* tool suite, not just pages+capture
* feat(claude-code): cold-check-wins seeding — reseed a cleared bank on the live doc count, ignore stale seededAt
* fix(core): simplify capture_initiative instruction to one clear trigger (remove confusing OR-chains)
* fix(core): port proven v1 attribution preamble + surface memories block first so the header actually gets emitted
* fix(core): reflect every turn (configurable reflectEveryTurns, default 1) instead of once per session
* feat(core): per-turn injection is recall-only (drop reflect from the hook), recall token budget default 750
* feat(core): inject a user-feedback section above memories (capture-initiative + attribution-header preferences)
* fix(core): align user-feedback attribution bullet with the generous WHEN-IN-DOUBT-EMIT rule
* fix(core): sharpen capture_initiative trigger — call right after plan approval, before implementation
* feat(survey): raise default codebase-survey budget cap to $2 (0.5 was over-conservative)
* feat(codex): codex-v2 wrapper (SessionStart seed + per-turn recall + MCP); parametrize session-start/MCP harness
* feat(core): default bank template is harness-neutral coding-agent::{gitProject} (shared memory across agents)
* feat(core): default apiUrl is Hindsight Cloud (https://api.hindsight.vectorize.io); local is now an override
* feat(codex): Stop write-back — Codex rollout transcript reader + codex-stop-hook (full parity)
* fix(core): captureInitiative returns the server-assigned page id (not the slug) so read_knowledge_page + relatedPageId links resolve
* feat(coding-agents): upgrade opencode adapter to full v2 parity
Per-turn recall via chat.message + system.transform (750-tok budget), native hindsight_* tools registered directly through opencode's tool() (no MCP server), rich tool-aware write-back on by default, and cold-check auto-seed at plugin load — reusing the shared formatMemories / buildKnowledgePreamble / buildKnowledgeTools / buildSessionStartContext primitives so opencode matches Claude Code and Codex.
Adds transcript-opencode.ts (rich normalizer over the live message list). Adds a HINDSIGHT_DISABLE_HOOKS recursion guard to RuntimeCore (seed/recall/write-back/sync no-op; tools still register) for headless survey runs. Removes the now-dead reflect path (client.reflect, inject.ts/buildSystemInjection, reflectTimeoutMs) as the whole surface is recall-only. README rewritten to the recall/knowledge-page/seed/write-back v2 model.
* feat(coding-agents): harness-portable codebase survey (multi-agent headless)
The cold-repo survey no longer hardcodes headless `claude` — startCodebaseSurvey now runs under the current harness's own CLI (claude/codex/gemini/opencode), falling back to any available agent, so a Codex/Gemini/opencode user without claude installed still gets the survey (the git-log seed already ran regardless).
Per-agent read-only recipes: claude (-p + inline --mcp-config + --disallowedTools), codex (exec --sandbox read-only + inline -c MCP), gemini (-p --approval-mode plan --allowed-mcp-server-names hindsight --skip-trust), opencode (run --agent plan; tools from the loaded plugin under the HINDSIGHT_DISABLE_HOOKS guard). All spawned with HINDSIGHT_DISABLE_HOOKS=1. session-start threads the harness through to the survey.
* feat(gemini): add Gemini CLI v2 integration (gemini-v2)
Full v2 parity for Gemini CLI (>=0.52.0), which added a Claude-style hooks system (stdin/stdout JSON). Maps onto the shared HookSpec/runSessionStartHook/runRetainHook abstraction with Gemini's event names: BeforeAgent (per-turn recall -> hookSpecificOutput.additionalContext), SessionStart (seed), SessionEnd (write-back).
The one Gemini-specific piece is transcript-gemini.ts — a reader for the 0.52.0 chats/session-*.jsonl mutation-log (upsert-by-id, polymorphic content: user text arrays, assistant plain strings, tool results as user functionResponse parts; drops the synthetic session_context message + thoughts). Adds the gemini-v2 wrapper (build.mjs + dev-install.sh that merges hooks + mcpServers into ~/.gemini/settings.json). Validated: reader against a real transcript, and a live recall smoke test (recall_ok) end-to-end.
* style(coding-agents): prettier-format README config table
* fix(opencode): inject via lastInjection fallback (1.18.5 system.transform has no sessionId)
opencode 1.18.5 fires experimental.chat.system.transform with input {model} only — no sessionId — so RuntimeCore.getInjection(input.sessionID) looked up undefined and pushed nothing into the system prompt. Recall still ran (chat.message does pass sessionID) but the memory block + attribution preamble + knowledge-page guide never reached the model, so no visible header and no tool use.
getInjection now falls back to the most recent turn's block (lastInjection) when there's no session-keyed hit. The completion's system.transform fires right after that session's onPrompt, so lastInjection is this turn's block. Adds an inject_ok/inject_empty diag (matching recall_ok/seed_started) to confirm injection lands.
* fix(coding-agents): treat project-local config as untrusted (block apiUrl/apiToken/directoryBankMap from a repo)
A project-local .hindsight/coding-agent.json lives inside whatever repo the developer opens, so it is untrusted input. loadConfig previously merged it per-field over the user-global config, letting a repo override apiUrl while the user-global apiToken survived the merge — so a malicious repo could set only apiUrl and the client would send the user's real Bearer token plus every recall query (the prompt) and Stop write-back transcript to an attacker-controlled host, silently, just by opening the repo (verified end-to-end).
Fix: the project-local layer is now sanitized — apiUrl, apiToken, and directoryBankMap are stripped from it (top level + any harnesses.<name> section) with a one-line warning; the user-global config stays trusted and unrestricted, and a repo can still set its own per-repo bank (bankId/bankIdTemplate). Also skip re-applying the global file as a project layer when the upward findProjectConfig walk lands back on it (a repo under $HOME with no closer config), which would otherwise strip its own apiUrl and warn every session. Adds 4 regression tests.
* style(coding-agents): prettier-format config.ts
* feat(coding-agents): restore reflect as the memory path; per-turn injection from knowledge-page sections
One opinionated runtime path (no behavior config):
- reflect ONCE per session on the first prompt (agentic root-cause synthesis,
benchmark-proven), cached and re-injected every turn — hook harnesses and the
opencode runtime alike
- every turn: knowledge-page SECTIONS matched locally against the prompt
(lexical section index, no server/LLM call) injected with provenance and a
pointer to the full page — fast like recall, organized like reflect
- raw recall leaves the runtime path (still powers the hindsight_search_memory
tool)
Session write-back: transcripts are now JSON turns matching the backfill chat
format, with each tool call compacted to a role:"action" turn naming the tool
and its primary target (no arguments, no outputs) — Claude, Codex, Gemini and
opencode readers.
Knowledge pages: no more entity_labels/tag taxonomy — pages are unscoped, each
page's source_query selects from the whole bank; survey, gitlog seed, write-back
and security hardening stay.
Spec: docs/superpowers/specs/2026-07-27-reflect-pages-runtime.md
* test(coding-agents): rewrite unit tests for reflect+pages runtime and JSON action transcripts
* test(coding-agents): live suite matches reflect_ok by content (pages_ok now follows it in the diag stream)
* docs(coding-agents): README + docs page describe the reflect+pages runtime (reflect once per session, local page-section injection per turn, JSON action write-back)
* feat(coding-agents): drop the backfill CLI — ingestion is automatic and background
- new deepen engine (dist/deepen.js, unpublished): idempotent, resumable —
per-bank lock, dedup by document id; ingests missing conversations, the
one-time gitlog seed, then progressively deepens recent history with
per-commit full diffs (newest first, bounded batch per run); drains and
creates knowledge pages last
- every session start now fires the engine (cold or warm); survey and the
cold-seed note stay cold-only
- sync status is the new readiness contract: hindsight_sync_status agent tool
+ dist/status.js for harnesses (synced = gitlog seeded, pages present,
extractions drained); activeOperations() filters terminal ops
- opencode write-back now upserts every turn (async) so a killed session
loses at most the last turn
- repoNameOf resolves relative paths so document ids are path-spelling-proof
- hindsight-coding-backfill bin removed; benchmark/e2e run the engine
directly and poll status
* polish(coding-agents): short, non-technical cold-seed message highlighting the bank id
* polish(coding-agents): cold-start banner — HINDSIGHT unicode wordmark + bank id line
* feat(coding-agents): timing diagnostics on by default
- session_start diag event on EVERY session (bank, cold/warm, pages, ms) —
warm sessions previously logged nothing
- deepen engine: deepen_started/deepen_done/deepen_failed diag events with
duration; child output now appended to ~/.hindsight/coding-agent-state/deepen.log
(was stdio:ignore — undebuggable) and log lines timestamped
- retain_ok/retain_failed carry ms on both the Stop hook and the opencode
per-turn upsert (which was fully silent)
- vitest config pins HINDSIGHT_DIAG_FILE to a tmp file so unit tests stop
polluting the real diag log
* feat(coding-agents): show the Hindsight banner on every session start (cold: learning, warm: remembering)
* polish(coding-agents): session banner uses the API server's colored pixel-art logo (shared visual identity), wording line below
* polish(coding-agents): banner text before logo — the TUI's first-line prefix was displacing the logo's top row
* polish(coding-agents): banner logo re-rendered foreground-only — the TUI strips ANSI background colors, which deleted half the server logo's pixels
* feat(coding-agents): per-turn user-visible notice — every prompt shows what Hindsight delivered (reflect state + matched knowledge pages) via hook systemMessage; opencode logs the same line
* polish(coding-agents): per-turn notice shows the match query excerpt and the page titles it returned
* fix(pages-index): singularize plain-word tokens so plural prompts match singular headings ('components' -> 'Component map'); path-like tokens untouched
* polish(coding-agents): per-turn notice — gradient Hindsight wordmark, value-driven wording, no timings
* feat(coding-agents): interim always-inject knowledge stub + explicit Hindsight attribution
- selectSections: TEMPORARY stub returning the first section of up to 3
distinct pages every turn regardless of prompt — guarantees injected data
for testing source attribution; will be replaced by the server-side
knowledge-base/search (local lexical index drops with it)
- both injection blocks now carry an ATTRIBUTION directive: when memory
shapes the answer, the agent introduces it with '🧠 From Hindsight memory
(<page>)' — and must never credit memory that did not contribute
* polish(coding-agents): gradient-word banner (logo dropped), lean per-turn notice, attribution directive front-loaded as a mandatory output format
* polish(coding-agents): reflect turn notice shows the assigned goal and a preview of what memory returned
* feat(coding-agents): page knowledge moves from auto-injection to an explicit tool
- new hindsight_search_knowledge_pages(query) tool (native on opencode, MCP on
hook harnesses) — interim local selection, single swap point for the
server-side knowledge-base/search; results carry the attribution requirement
- per-turn auto-injection of page sections removed: a trivial prompt ('yes')
no longer displays phantom research; ordinary turns are silent
- per-turn notice only on the reflect turn (assigned goal + result preview);
tool calls provide their own native visibility
- tool guide/roster advertises the search tool as the first stop
* feat(coding-agents): bind hindsight_search_knowledge_pages to the server-side hybrid knowledge-base search
- merge feat/knowledge-pages-okf underneath (GET /knowledge-base/search,
BM25 + vector, RRF-fused; conflicts resolved in okf's favor for server/
clients/UI, coding-agents docs entry preserved)
- client.searchKnowledgePages(query, limit) wraps the endpoint; the tool
returns ranked {page, page_id, snippet, score} — verified end-to-end
through the real MCP server against the live endpoint
- interim local selection removed from the tool path (pages-index remains
only for the hook page cache pending full cleanup)
* refactor(coding-agents): drop pages-index — local section index deleted; hook/runtime keep only the id+title roster (content lives behind the server-side knowledge-base search)
* refactor(coding-agents): drop hindsight_search_memory (raw recall) — knowledge-page search is THE search surface; recall client method and formatter removed
* feat(coding-agents): hindsight_reflect tool — on-demand deep memory reasoning alongside the session-start reflect
* refactor(coding-agents): one 'conversation' retain strategy for all developer conversations
Backfilled decision chats and live session write-back were the same content
type (identical JSON action-transcript format) extracted two ways based only
on where they came from. Merged CHAT_MISSION + SESSION_MISSION into one
CONVERSATION_MISSION that scales facts to substance (short decision chat ->
1-2 facts, working session -> several; final-state-wins, verbatim literals,
rejected-alternative rule kept); the ≤2-fact CHAT_CUSTOM_INSTRUCTIONS
extractor is retired with it.
* feat(coding-agents): restore Chris's knowledge entity_labels tier
configureBank again sets entity_labels {knowledge: feature-work/decision/
convention/component/concept, tag:true} + entities_allow_free_form, so the
extractor routes durable facts with knowledge:<tier> tags the server-side
knowledge base can select on; capture_initiative markers regain the
knowledge:feature-work label. Pages themselves stay unscoped (the okf
knowledge base owns synthesis).
* feat(coding-agents): seeded pages tag-scoped again — page tags match the restored knowledge:<tier> entity labels (capture_initiative pages included)
* fix(coding-agents): reflect injection wrapped in <hindsight_memory> so write-back never re-ingests it; seed-state file (declined flag) removed — the live bank is the only state
* feat(coding-agents): gitIngest enum ('message' | 'full' | 'none') — one setting, one code path for seeding AND staying current
- deepen's idempotent git pass IS the sync: gitlog doc re-upserts when HEAD
moves (gitlog-head:<sha> tag makes freshness a single tag query); in full
mode new commits surface at the top of rev-list and the next run ingests
them
- separate git-sync path deleted (sync.ts, runtime.syncGitOnce, gitSync
config)
* feat(coding-agents): gitIngest defaults to 'message' (cheap by default; opt into depth); deepen gains --git-ingest override for harnesses
* feat(coding-agents): session banner shows git-sync state (condensed syncStatus): 'git in sync' / 'catching up on new commits' / 'syncing git history (n/target)'
* polish(coding-agents): two-line banner — value headline (tracking decisions/conventions/history) + bank/sync detail line
* refactor(coding-agents): ONE config file — project-local .hindsight/coding-agent.json layer removed entirely (with its sanitization machinery); per-repo routing stays via directoryBankMap
* docs(coding-agents): fix stale project-config reference in comment
* refactor(coding-agents): runtime scratch (deepen lock + engine log) moves to the OS temp dir — ~/.hindsight now holds ONLY the config file
* feat(coding-agents): cursor auto-ingestion parity — hosts without a SessionStart hook fire the deepen engine (+ cold survey) from the session's first prompt
* feat(coding-agents): leveled plugin logging — one plugin.log (debug/info/warn/error, config logLevel + HINDSIGHT_LOG_LEVEL/FILE overrides); diag events mirror at debug; deepen logs itself (separate deepen.log dropped); warn on reflect/retain failures
* feat(coding-agents): one-shot bank configuration via the server's template import — missions, strategies, entity labels, and the 5 seeded pages in a single idempotent POST /import (configureBank PUT+PATCH and createPages removed)
* feat(coding-agents): one-command installer — npx hindsight-coding-agents install|uninstall [harness...]
Detects the coding agents on the machine and merges each one's native
wiring (hooks + MCP: claude mcp add for Claude Code; hooks.json + append-
only config.toml sections for Codex; settings.json for Gemini; hooks.json
+ mcp.json for Cursor; plugin array for opencode). Idempotent by marker,
preserves foreign entries, backs up touched files as .hindsight-backup;
uninstall removes exactly ours. 27 unit tests over temp homes.
* fix(installer): refuse to install from an npx/dlx cache (wired paths would die on eviction); document global install + npm update -g as the update path
* ci(coding-agents): unit + typecheck + build job, and a live E2E job (real API server + real LLM) running the deepen->sync->reflect->injection path; prettier-format the package
* docs(blog): launch post draft — coding-agent memory results (marked draft: true)
* docs(blog): rewrite launch post as the narrative — from 'does memory even help?' through why-not-SWE-bench, the corrections dataset, benchmark-driven architecture decisions, to the final numbers
* docs(blog): position knowledge pages as a co-launch headline — living-documents framing, example page excerpt, platform-wide availability (dashboard editor, hybrid search API, bank templates), closing CTA
* docs(blog): restructure launch post payoff-first — contrarian RAG finding + cost in the lede, TL;DR box, narrated task with both runs, seeded-answers objection met head-on, data-locality/time-to-value/latency answers, Sonnet number promoted, backstory compressed to one section
* fix(coding-agents): deepen waits for server-side ops to settle (template-import page refreshes broke the synced contract); HINDSIGHT_CONFIG env override for the config path (containers/test harnesses; replaces the live test's dependency on the removed project-config layer)
* docs(blog): second-pass fixes — flagship example swapped to the arbitrary retry decision (RFC 4180 attack closed), reconstruction disclosed, 58% provenance clause, placebo backstory + grading block restored in numbers, RAG figure per-task, benchmark-site date
* docs(blog): align remaining CSV references with the retry flagship; TL;DR per-task figures
* docs(blog): flagship rebuilt on the real dataset task — the ERP export decision whose rejected alternative IS the textbook fix (='00042' formula form, minimal quoting, CRLF); dangling injection-verified reference restored; limitations cross-check attached to the correct row
* docs(blog): rewrite as the 0.9.0 launch post — five-beat narrative (question → dataset → auto-recall failure → reflect → knowledge pages from llm-wiki to self-healing) for Knowledge Pages + unified coding-agents plugin
* docs(blog): add the missing beat — shaping the dataset revealed decisions live in git, which the old plugins never ingested
* docs(blog): reframe reflect — very smart rather than slow; first message carries the session goal; on-demand reflect tool for session drift
* docs(blog): pages section addresses the 'back to files?' objection — pages as projected views over consolidated memory (contradiction resolution underneath), raw docs remain source of truth
* docs(blog): out-of-box row updated to n=3 (22/26/23 -> 0.72/task, -26%; cost -35%); matured row marked single-run
* fix(hooks): reflect block injected once per session (+ cadence refresh), not every turn — hook context persists in the transcript, so per-turn re-injection stacked duplicate blocks
* fix(coding-agents): wrapper bundles ship deepen.js, not the renamed backfill.js
The core build entry `backfill` was renamed to `deepen` (deepen engine +
status), but the three wrapper build.mjs bundleFiles lists still copied the
removed `backfill.js`, so every dev-install failed with ENOENT. Point them at
`deepen.js` (spawned by seed.ts at runtime) so the installers build again.
* feat(coding-agents): periodic re-survey — refresh structural pages every N commits
Structural knowledge pages are only generated on a cold repo, so an evolving
architecture drifts from what the survey captured. Add surveyRefreshCommits
(default 20; 0 = cold-seed only): at SessionStart, count commits reachable from
HEAD since the newest survey-baseline marker (branch-robust via
git.commitsSince) and re-run the headless survey once the threshold is crossed,
re-recording a baseline marker. Cold seed still records the first baseline.
* fix(coding-agents): per-turn hook timeout (30s) must exceed the 25s reflect cap
The once-per-session reflect is capped internally at HOOK_REFLECT_CAP_MS=25s,
but every harness killed the UserPromptSubmit/BeforeAgent hook at 15s — below
the cap. The host killed the hook mid-reflect before the cache write, so the
injection was discarded AND the reflect re-fired uncached on every turn
("UserPromptSubmit hook timed out after 15s" every prompt). Raise the hook
timeout to 30s (> cap) across claude/codex/gemini, bump Stop to 30 to match,
and document the cap-below-timeout invariant so it can't silently drift again.
* polish(coding-agents): attribution header is a bold blockquote callout, not flat text
The live directives all told the agent to credit memory with a plain inline
"From Hindsight memory (<page>):", which renders as flat text. Switch every
directive (session tool-guide, reflect injection, both MCP tool descriptions)
to a markdown blockquote header "> ... **From Hindsight memory (<page>)** — ..."
so it renders as a distinct callout, restoring the richer attribution look.
* fix(coding-agents): strip <hook_prompt> transport wrappers from retained transcripts (codex surfaces hook stdout/errors as user messages); session + backfill transcripts switch to JSONL (one turn per line — clean appends, chunker-atomic turns)
Note: benchmark numbers (n=3) were measured on the JSON-array format; JSONL
is extraction-equivalent by design but unvalidated by a sweep — gate before
quoting new numbers on this pipeline.
* fix(installer): write [features].hooks (codex_hooks deprecated in Codex >= 0.145); accept either flag as already-enabled
* fix(hooks): fire the ingestion engine from the FIRST prompt on every harness (lock-protected no-op when SessionStart already did) — safety net for sessions predating the install, whose banks otherwise never get pages; survey stays SessionStart-owned (ensureSeed hosts excepted)
* feat(status): expose survey observability — surveyBaseline (last surveyed HEAD, from Chris's survey-baseline markers) + surveyCommitsBehind in syncStatus/hindsight_sync_status
* test(status): expected shapes include the survey observability fields
* feat(survey): findings docs ARE the completion signal — surveyDocs (0-4) in syncStatus; a baseline without findings re-fires the survey at the next warm session start (crashed-survey retry)
* feat(config): banks.<bankId> overrides — per-repo opt-in/out applied AFTER bank resolution (disable a repo, tune gitIngest/retainSessions per bank) from the ONE config file; resolution fields ignored inside a bank section
* feat(config): bankAliases — remap resolved bank ids as the final resolution step (single hop, converging allowed); docs page brought fully current (env exceptions, gitIngest/logLevel/survey rows, banks overrides, aliases, resolution step 4)
* refactor(config): bank rename lives INSIDE banks.<id> as the field (separate bankAliases tree removed) — one per-repo section for disable, behavior, and rename; applyBankConfig returns {cfg, bankId}
* docs(coding-agents): recipe — two repos sharing one bank (converge by resolved id via banks.<id>.bank, or by path prefix via directoryBankMap), with the id-vs-path rule of thumb
* rename(config): directoryBankMap -> mapPathToBank (direction-explicit; pre-0.9.0 breaking-rename window)
* feat(coding-agents): companion skill — hindsight-coding-agent SKILL.md shipped in the package and installed into ~/.claude/skills by the installer; explains storing/retrieving, full config (banks/mapPathToBank/gitIngest), install/update, and debugging
* docs(coding-agents): mention the companion skill in README + docs page
* feat(coding-agents): companion skill ships to ALL skills-capable hosts (claude/gemini/cursor native dirs, codex via ~/.agents/skills standard); retained sessions and ingested documents carry the harness as tag (harness:<name>) and metadata
* feat(skill): self-updating companion skill — every session start re-syncs installed copies with the packaged SKILL.md (presence-gated; npm update -g now updates the skill too, no re-install)
* fix(coding-agents): worktree-aware document ids (no more per-worktree gitlog duplicates) + deepen self-cleanup; issue/PR refs preserved verbatim and emitted as ENTITIES; calibrated reflect-injection wrapper; docs ported to the TRUE source (hindsight-docs/docs-integrations) that generates the skill copy
* docs(skill): explain the internal marker documents (survey-baseline:<sha> bare-sha content is deliberate — zero extracted facts; gitlog:<repo> seed doc)
* feat(survey): human-readable baseline markers under a zero-extraction marker strategy (live-verified: 0 facts) — start as researching, deepen lazily flips to completed once findings exist
* refactor(survey): one survey strategy with conditional rules replaces the separate marker strategy — status markers extract nothing, findings extract structural facts (both branches live-verified)
* fix(hooks): mid-session heal — zero knowledge pages in the roster cache fires the ingestion engine on any prompt (covers long-lived sessions predating the install; lock makes repeats free)
* feat(bank): ~ expansion in mapPathToBank; document the directory-blacklist recipe (map tree to one bank + disable it)
* feat(coding-agents): explicit correction protocol — when the agent verifies a memory is wrong/stale it ingests a 'Correction: <topic>' doc (claimed vs verified-true vs evidence); guidance in the injection wrapper, tool guide, tool description, and companion skill
* fix(hooks): reflect block injected exactly once — cadence re-injection dropped (replaying the turn-1 synthesis at arbitrary turns reads as random noise after drift; hindsight_reflect covers genuine re-need)
* fix(coding-agents): 15s hard timeout on every client request + opencode boot no longer awaits seedIfCold — a stalled memory server can never freeze the host TUI (onPrompt already tolerates a late preamble)
* fix(reflect): defer past trivial openers — a greeting no longer spends the once-per-session synthesis on 'hi' (seen live: reflect answered a greeting with persona chatter and burned the session's slot); first substantive prompt reflects instead
* test(hooks): align reflect-call assertions with the non-trivial fixture prompt
* Revert trivial-prompt reflect deferral (misread the report — the issue was the notice's UI position, not reflect-on-greeting behavior)
* fix(opencode): stop writing banner/reflect notices to stderr — opencode renders plugin stderr inside the TUI at the cursor (text wedged against the input bar); the trail moves to the plugin log
* feat(opencode): TUI companion plugin — visible presence via api.ui.toast (opencode's TUI plugin API): banner toast on activation + reflect goal/preview toasts from the plugin-log trail; installer registers the second entry
* fix(opencode): visible presence via the server client's tui.showToast (POST /tui/show-toast) — banner + reflect toasts from the server plugin; the separate TUI module approach removed (1.18.9's loader rejects tui-only entries in the shared plugin list); SDK deps bumped to 1.18.9
* fix(opencode): toasts never rendered — v1 client wants {body}, and boot toast raced TUI mount
opencode injects the v1 SDK client whose showToast signature is {body: {title,
message, variant, duration}} and which resolves with {data|error} instead of
rejecting — the earlier flat-params call sent an empty body and the failure was
invisible. Also the toast event is not durable: the seed banner on a warm bank
fired <1s after plugin init, before the TUI subscribed, and was lost. Toasts now
use the body shape, log a rejected result at debug, and defer until ~3s past
init. Verified live in tmux: boot banner and reflect toast both render.
* fix(coding-agents): reflect must report history, never issue directives
The 0.8.6-blog incident: reflect fused two true but unrelated facts (the
hermes-deprecation goal and the blog-section removals of
|
