Skip to content

feat: host-neutral defaults, poolside adapter, categories migration#77

Merged
Patel230 merged 6 commits into
mainfrom
feature/host-neutral-updates
Jul 20, 2026
Merged

feat: host-neutral defaults, poolside adapter, categories migration#77
Patel230 merged 6 commits into
mainfrom
feature/host-neutral-updates

Conversation

@Patel230

Copy link
Copy Markdown
Contributor

All updates from neutral-host-defaults branch:

  • Host-neutral ServiceName and config-dir (was hardcoded 'hawk')
  • Poolside adapter with connection handling
  • Legacy provider.json migration from old 'hawk' dir
  • Legacy categories.json migration with fix for override dir
  • Remove GitNexus references from AGENTS.md; delete CLAUDE.md
  • Catalog improvements: provider discovery, credential handling, isolation tests

Closes #76

Patel230 added 6 commits July 20, 2026 10:08
ServiceName defaults to 'eyrie' (host-neutral); embedders call
SetServiceName before any credential access. Config-dir default
renamed from 'hawk' to 'eyrie'. Engine API unchanged.
When the default config dir was renamed from 'hawk' to 'eyrie', an existing
provider.json in the old dir was silently ignored — upgrading users lost
their provider/model selection, deployments, and routing.

On first engine start, if the eyrie-dir provider.json does not yet exist,
copy it from <UserConfigDir>/hawk/provider.json. One-time, idempotent,
never overwrites newer state.
loadOverrides now reads categories.json from the host-neutral eyrie dir
(EYRIE_CONFIG_DIR → HAWK_CONFIG_DIR compat → <userdir>/eyrie) to match
GetProviderConfigDir, instead of hardcoding the old hawk dir.

The one-time legacy_config migration now also copies categories.json from
<userdir>/hawk to <userdir>/eyrie on upgrade, alongside provider.json.
GitNexus docs superseded by Graphify integration (PR #72).
@Patel230
Patel230 merged commit b2cab57 into main Jul 20, 2026
16 checks passed
@Patel230
Patel230 deleted the feature/host-neutral-updates branch July 20, 2026 04:54
Patel230 added a commit that referenced this pull request Jul 21, 2026
* fix(eyrie): make legacy config migration correct and atomic

The migration I added in b2cab57 (PR #77) had three real bugs:

1. Read source + dest directly from os.UserConfigDir(), ignoring
   EYRIE_CONFIG_DIR / HAWK_CONFIG_DIR. A host using a custom
   EYRIE_CONFIG_DIR ended up with the legacy file copied to a path
   eyrie never reads — the migration was effectively a no-op for
   that user, and silently wrote to an invisible location.

2. Swallowed MkdirAll / WriteFile errors with '_ ='. On a partial
   write or read-only filesystem the destination provider.json could
   be left corrupt / empty, and the next start looks like a fresh
   install. Now slog.Warn on each failure (best-effort, non-fatal).

3. TOCTOU between os.Stat and os.WriteFile. Two engines starting in
   parallel could both miss the file, both write, and race on
   contents. Now uses .tmp + os.Rename for an atomic swap, so a
   concurrent engine cannot observe a half-written file.

Source + destination now both derived from config.GetProviderConfigDir(),
so the migration honors the same env-var resolution as the rest of
eyrie. Added tests covering EYRIE_CONFIG_DIR redirection and the
atomic-write + idempotency invariants.

* fix(eyrie): return error from GetProviderConfigDir; fix goroutine leak in adaptive rate limiter

Two real bugs:

1. GetProviderConfigDir panicked with 'user config directory unavailable'
   on platforms where os.UserConfigDir fails (sandboxed runtimes,
   restricted $HOME). It now returns (string, error) so callers can
   recover; a MustGetProviderConfigDir shim is provided for startup
   paths that genuinely cannot proceed. Updated all 6 production
   call sites in runtime/, setup/, engine/ and the 2 test files.

2. AdaptiveRateLimitProvider.StreamChat wrapped the inner stream's
   events channel in a goroutine that blocked on
   'wrappedCh <- evt' forever if the caller's ctx was cancelled.
   The goroutine never observed ctx.Done, never released the
   inner stream's body, and the caller waited indefinitely. Now
   selects on ctx.Done and calls result.Close() to release the
   inner stream, then drains remaining events so the inner parser
   can complete cleanly.

* fix(eyrie): M1-M6 MEDIUM hardening

- retry: return a clear error if a request body is set but GetBody is
  nil, instead of silently retrying a drained body.
- conversation: emit EventError before EventDone when a continuation
  request fails, so truncated responses aren't indistinguishable from
  clean completions.
- coalesce: replace time.Sleep with a cancellable timer that exits on
  ctx.Done, so the cleanup goroutine doesn't linger for the full TTL
  on shutdown.
- config: delete the exported-but-unused ValidateBaseURL (only test
  caller removed).
- adaptive_ratelimit: audit confirmed recordTokens + shared-window
  mutations already take a.mu — no code change; documented lock
  invariant.

* chore(eyrie): centralize magic numbers in client/core/constants.go

Retry defaults, transport timeouts and cooldown windows now reference
named constants. DefaultRetryConfig uses them. Deferred: full split of
the 215-line providerSpecs() literal and the batch.go inline retry
(LOW effort, mechanical).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant