Skip to content

feat: quota-aware Hermes specialist model router - #1

Closed
djpapzin wants to merge 2161 commits into
mainfrom
agent/0-quota-specialist-router-codex
Closed

feat: quota-aware Hermes specialist model router#1
djpapzin wants to merge 2161 commits into
mainfrom
agent/0-quota-specialist-router-codex

Conversation

@djpapzin

Copy link
Copy Markdown
Owner

Summary

  • keep ordinary gateway conversation on a cheap GPT-5.6 coordinator
  • route bounded coding work to GPT-5.3-Codex-Spark and high-risk/escalated work to GPT-5.6-sol
  • derive separate 5-hour/weekly quota pools from Codex telemetry with a 20% sol reserve
  • add automatic one-attempt Spark escalation, compact handoff, Spark validation, and /model-route-status

Tests

  • focused router and plugin-manager suites: 123 passed
  • live model probes: openai-api/gpt-5.6, gpt-5.6-sol

Do not merge without owner approval.

mahdiwafy and others added 30 commits July 9, 2026 01:36
The session messages endpoint returned ALL messages in a single
response with no limit/offset. Sessions with 500+ messages produced
1.2-1.6 MB JSON payloads, causing GIL starvation and WebSocket
timeouts on the Desktop client (NousResearch#60155).

Add optional limit/offset query params to both the API endpoint and
SessionDB.get_messages(). Limit clamped to 500 max per page. Response
now includes a pagination object with limit/offset/returned count.

Backward compatible: callers that omit limit get the old behavior
(all messages).

Closes NousResearch#60155

(cherry picked from commit d58396b)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 1e70eaa)
…ist queries

list_sessions_rich and _get_session_rich_row previously used SELECT s.*,
pulling the system_prompt TEXT blob on every row even for dashboard and
picker callers that never display it. On large databases this blob routinely
runs to tens of kilobytes per session, causing unnecessary B-tree I/O.

Add compact_rows=False param to both functions. When True, an explicit
column list omitting system_prompt is substituted for s.* in both the
simple and the recursive-CTE (order_by_last_active) query paths.
Default is False so all existing callers are unaffected.

Update dashboard and session-picker callers in web_server.py and
tui_gateway/server.py to pass compact_rows=True.

Add seven regression tests covering: omission of system_prompt, presence
of all metadata fields, both query paths, _get_session_rich_row, and
backward-compat default.

(cherry picked from commit c470cbd)
…sResearch#47437

- derive the compact_rows projection from SCHEMA_SQL (parse once, cache)
  instead of a hardcoded column list: the original NousResearch#47437 list was cut
  against a June schema and silently dropped session_key/chat_id/chat_type/
  thread_id/display_name/origin_json/expiry_finalized/git_branch/
  git_repo_root/compression_failure_* — including desktop sidebar fields.
  Schema-derived means declaratively reconciled new columns are included
  automatically; only system_prompt is excluded.
- guard test pinning the schema<->projection contract (mutation-verified:
  dropping a column from the projection fails it)
- wire compact_rows=(not full) into /api/sessions and /api/profiles/sessions
  so the SQL projection pairs with the API-level field strip (?full=1 still
  returns complete rows end-to-end)
- pass compact_rows at the remaining hot list callers: /api/status active
  count, _session_latest_descendant fallback, /api/sessions/stats by-source
- thread compact_rows through the compression-tip projection
  (_get_session_rich_row) so projected tips can't reintroduce the blob
- add pagination tests for get_messages (NousResearch#60347 shipped none): paging order,
  offset-past-end, active-flag interaction; add tip-projection compact test
- AUTHOR_MAP entries for mahdiwafy + CodeForgeNet (plain emails)
Review finding: get_messages(offset=N) with no limit dropped the OFFSET
entirely. SQLite requires a LIMIT clause for OFFSET, so emit LIMIT -1
(unbounded) when only offset is given. Regression test added.
tui_gateway session.list/most_recent now pass compact_rows=True
(NousResearch#47437 salvage); the keyword-only fake signatures in
test_tui_gateway_server.py rejected the new kwarg and CI slice 6/8
failed with TypeError. Other list_sessions_rich fakes use **kwargs and
are unaffected.
Rebase reconciliation with NousResearch#60884: _count_status_active_sessions (from
NousResearch#58238) now passes compact_rows=True (this branch's NousResearch#47437 projection),
so the fake asserts both.
…t httpx.ResponseNotRead

When an API error carries an httpx.Response whose body was consumed via
iter_bytes() during streaming error handling (e.g. GeminiAPIError from
agent/gemini_native_adapter.py), accessing .text raises
httpx.ResponseNotRead. The secondary exception replaced the real,
already-computed provider error (429 free-tier quota guidance) with the
generic 'Attempted to access streaming response content' message on
every turn.

Guard the .text access so it degrades to an empty snippet and falls
through to the str(error) fallback, which carries the full original
message. Mirrors the existing guards in
agent/error_classifier.py::_extract_error_body() and
agent/gemini_native_adapter.py::gemini_http_error().

Fixes NousResearch#59769

Salvaged from PR NousResearch#59868 (guard + regression test); the unrelated
desktop Ctrl-C fix bundled in that PR was intentionally dropped and is
triaged separately.
Gateway froze the fallback chain at process start while cron reloads it
per job, so a chain configured after hermes gateway was running never
reached messaging sessions. Refresh from disk on agent create and when
reusing a cached agent.

Fixes NousResearch#60955.

(cherry picked from commit b64e715)
Pin reload + cached-agent apply helpers for NousResearch#60955 so a mid-uptime
fallback chain change reaches messaging sessions without a restart.

(cherry picked from commit fafb341)
Follow-ups on the NousResearch#60987 salvage (review pass):
- _refresh_fallback_model: keep last known-good chain on transient
  config.yaml read/parse failure (user mid-edit, torn write) — only a
  successful read that lacks the key clears the chain. Previously a
  refresh error wiped a cached agent's working fallback for the turn.
- Move the cached-agent refresh+apply OUTSIDE the agent-cache lock:
  config.yaml read is disk I/O and the idle-sweep watcher contends on
  that lock (same reasoning as NousResearch#52197). Per-session turn serialization
  keeps the post-lock apply safe.
- _apply_fallback_chain_to_agent: clear _unavailable_fallback_keys when
  chain content actually changes, so an entry re-configured mid-uptime
  (e.g. credentials added) is retried instead of staying suppressed for
  the cached agent's lifetime; no-op refreshes keep the memo.
- Tests: cwd-independent source pin (Path(__file__) anchor), pin the
  reuse-path apply call, + regression tests for last-known-good, memo
  clear-on-change, memo keep-on-unchanged (mutation-verified).
Fixes NousResearch#3356

Build the skills snapshot manifest in one directory walk, avoid importing gateway session context during CLI prompt startup, and reuse direct platform-list matching for snapshot entries.

(cherry picked from commit 1a64c2e)
Deep review of the cherry-picked NousResearch#16454 found the ad-hoc flush thread
raced new_session()'s inline on_session_switch(reset=True): memory
providers key off internal _session_id state (MemoryManager.on_session_end
takes no session id), so a late off-thread extraction ran against
post-rotation bindings — misattributing the old transcript to the new
session id, double-ingesting the old turn buffer (supermemory), or
double-committing (openviking already async-finalizes in
on_session_switch).

Redesign: new MemoryManager.commit_session_boundary_async queues
on_session_end + on_session_switch as ONE task on the manager's existing
single-worker background executor (the same worker sync_all already
uses). This preserves the strict end→switch ordering providers depend on,
serializes against per-turn syncs FIFO, keeps /new non-blocking, and
degrades to inline (pre-NousResearch#16454 behavior) when the executor is
unavailable. No ad-hoc threads; no per-provider changes needed.

The context-engine on_session_end half stays synchronous in
_launch_session_boundary_memory_flush (cheap, must land before
reset_session_state rebinds the engine).

Exit durability: _run_cleanup calls the manager's existing
flush_pending(timeout=10) barrier before shutdown, so '/new then quit'
doesn't drop the queued extraction (shutdown_all's own drain is ~5s and
cancels queued tasks). Bounded well inside the 30s exit watchdog.

Tests: ordering invariant with slow (LLM-like) extraction, FIFO
serialization vs sync_all, switch-fires-even-if-end-raises, no-provider
no-op, CLI snapshot handoff + inline-switch fallback, sync engine
boundary, cleanup flush_pending.
- Return the boundary snapshot from
  _launch_session_boundary_memory_flush as a local value instead of
  staging it on self._session_boundary_snapshot. The instance-attr
  handoff could leak (no memory manager configured) or mis-fire a
  stale snapshot on a later /new if an exception hit between staging
  and consumption. A local variable eliminates the class; the helper
  also returns None when no memory manager is configured so
  new_session takes the inline-switch path.
- Drop the now-dead session_id kwarg from commit_memory_session:
  after the redesign no production caller passes it (gateway, TUI,
  compression all use the default), and speculative params are
  rejected per AGENTS.md. The explicit-old-session need is served by
  cli.py's direct engine call + commit_session_boundary_async.
- Drop the dead providers snapshot in commit_session_boundary_async
  (only the emptiness check used it).
- Tests updated accordingly (dead-kwarg test removed, snapshot
  assertion now covered by return-value contract).

Phase-2 gates: 2a tests/cli 1048 passed + 6 memory files 137 passed;
2b programmatic live smoke 0.38ms non-blocking caller, end→switch→sync
ordering verified; 2c structured 4-angle review — no Criticals, these
warnings fixed.
The model often emits a follow-up batch of tool calls as its own
assistant message with no prose or reasoning. On screen those rows look
like one continuous run, but assistant-ui only groups tool calls within a
single message, so the auto-scrolling tool window never triggered on them
(e.g. two batches of two searches read as 2 + 2, never reaching the
threshold).

Coalesce each settled tool-only assistant message into the preceding
assistant message in the render pipeline so its calls join that message's
tool group. Render-only (never touches the $messages store) and
settle-only (pending messages are skipped) so a live turn is never
merged/un-merged mid-stream; merged results are cached by source identity
so a stable turn yields stable objects with no re-render churn.
…ol-window-merge

feat(desktop): group tool calls across text-less assistant messages
we should run this as part of merges at some point :)
…in favor of grok-4.5 (NousResearch#61097)

grok-4.5 is GA and is now the single curated Grok entry on the
aggregator lists. grok-4.3 is NOT retired upstream — it remains fully
usable by typing the model name (validated against the live catalogs);
this only removes it from the short curated picker snapshots. The
xAI-direct list is models.dev-cache-driven and unaffected.
Electron 40 ships Node 24.15, where tsx's ESM load hook returns null and
crashes with ERR_INVALID_RETURN_PROPERTY_VALUE. Bundle main+preload via
esbuild for `npm run dev` and always load the JS preload from dist/.
…p-tsx-electron40

fix(desktop): stop using tsx to boot Electron main in dev
The new oauth.copyCode/copyFailed keys existed only in en.ts, with
optional types and English literal fallbacks in OAuthLoginModal — so
non-English users got English strings on the device-code copy button.

Backfill translations in all 16 non-English locales, refresh the
updated oauth.description/notConnected copy (dashboard Login flow
mention) to match en.ts, make the two keys required in the
Translations interface, and drop the English fallbacks from the modal.
Verified with web tsc --noEmit (required keys enforce locale
completeness), vitest, and a web build.
…okie

PR NousResearch#61281 removed the client-side X-Hermes-Session-Token requirement from the
dashboard OAuth mutation calls so cookie-authenticated hosted/mobile sessions
can start provider logins. That change is safe only because the server still
gates those endpoints (gated_auth_middleware cookie check + _require_token).
The PR's api.test.ts suite mocks fetch and only asserts client behavior, so a
re-break of the gated-mode cookie gate would pass CI unnoticed.

Add gated-mode TestClient tests asserting POST /api/env/reveal and the OAuth
mutation endpoints (disconnect/start/submit/cancel) return 401 with no session
cookie. Mutation-verified: neutering both the middleware gate and _require_token
flips all five to 200.
Estimate tool-schema size without repeatedly stringifying full tool lists, and cache the result per tool snapshot to reduce GIL-heavy work during preflight and compaction.
solyanviktor-star and others added 18 commits July 11, 2026 12:28
…g credentials

Review feedback: a same-host redirect to a different port can land on a
different service, which must not inherit the provider API key. Compare
(scheme, hostname, effective port) — with 80/443 defaults — instead of
hostname alone, and add a two-server regression test for the
same-host/different-port case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`hermes -t web chat` silently dropped the toolset filter (and the same
hold true for `-m`, `--provider`, `--tui`, `--dev` placed before
`chat`). Reported in NousResearch#28780 for `-t/--toolsets`; the others are sibling
failures with the same root cause.

Root cause: the chat subparser re-declared these flags with `default=None`
(or `default=False` for store_true) on top of the matching top-level
parser flags. When argparse dispatches into the subparser it shares the
namespace via `dest`, so the subparser's default overwrites whatever the
top-level parser parsed before the subcommand. `-s/--skills`, `-r/-c/-w`,
`--yolo`, and `--pass-session-id` already use `default=argparse.SUPPRESS`
for exactly this reason — the chat-subparser action becomes a no-op
unless the user explicitly passes the flag after `chat`, and the parent
value survives.

Reproduction (origin/main, before fix):

  >>> parser.parse_known_args(["-t", "web", "chat"]).toolsets
  None
  >>> parser.parse_known_args(["chat", "-t", "web"]).toolsets
  'web'

After fix:

  >>> parser.parse_known_args(["-t", "web", "chat"]).toolsets
  'web'
  >>> parser.parse_known_args(["chat", "-t", "web"]).toolsets
  'web'

Sibling flags fixed in the same commit because they share the exact same
argparse pattern bug — verified via a new contract test that scans every
chat-subparser action whose `dest` is also on the top-level parser and
asserts `default is argparse.SUPPRESS`. The test fails on origin/main
listing all five offenders and passes after this fix.

Test additions in tests/hermes_cli/test_argparse_flag_propagation.py:
- TestChatSubparserInheritedValueFlags exercising real `_parser` build
  (not the hand-rolled replica) so it catches future drift.
- Parametrized before-chat / after-chat cases for `-t`, `--toolsets`,
  `-m`, `--model`, `--provider`.
- Negative case: passing none of the flags leaves attrs at the top-level
  parser's `None` default (SUPPRESS does not remove existing attrs).
- Combined case: all three value flags before `chat` simultaneously.
- store_true cases for `--tui` / `--dev`.
- Contract test asserting every shared-`dest` flag on chat uses SUPPRESS.

Fixes NousResearch#28780.
@djpapzin

Copy link
Copy Markdown
Owner Author

AGENT_STATUS_UPDATE
status: needs_review
agent: codex
repo: djpapzin/hermes-agent
thread_or_topic: Telegram home channel
branch: agent/0-quota-specialist-router-codex
pr_url: #1
issue_url: none (GitHub auth was unavailable when the required worktree was created)
commit: c6e4a2e
tests: 124 passed (router + existing plugin manager); 5 policy scenarios passed
checks: live probes passed for openai-api/gpt-5.6, gpt-5.3-codex-spark, and gpt-5.6-sol; gateway active; Telegram polling connected; /model-route-status handler verified
privacy_check: passed; status and logs contain no private user data
secret_check: passed; no secrets committed or copied into project files
integrations_touched: Hermes config, user plugin directory, Codex quota telemetry read-only, Telegram report
services_restarted: hermes-gateway.service (two graceful restarts; active jobs were allowed the configured drain window)
files_changed: docs/specialist-model-router.md; plugins/specialist_router/{plugin.yaml,init.py,router.py}; tests/plugins/specialist_router/test_router.py
summary: Deployed a deterministic quota-aware router that keeps normal conversation on GPT-5.6, delegates bounded coding to Spark, escalates risky/failed work to GPT-5.6-sol with compact handoff, validates sol work with Spark, caches separate quota telemetry, preserves a 20% sol reserve, and exposes /model-route-status.
blockers: none for live use; PR requires owner review and must not be merged automatically
next_recommended_action: review PR #1 and exercise /model-route-status from Telegram
next_goal_suggestion: add a low-volume scheduled quota snapshot only if status freshness proves insufficient
safe_to_merge: yes; focused and existing plugin tests pass, live model/provider IDs were probed, rollback is documented, and no core files or secrets changed

@djpapzin

Copy link
Copy Markdown
Owner Author

AGENT_STATUS_UPDATE
status: needs_review
agent: codex
repo: djpapzin/hermes-agent
thread_or_topic: Telegram home channel
branch: agent/0-quota-specialist-router-codex
pr_url: #1
issue_url: none
commit: f8d3124
tests: 125 passed (router + existing plugin manager); 5 policy scenarios passed
checks: coordinator, Spark, and sol live probes passed; automatic live Telegram routing produced specialist state; gateway active; Telegram connected; command and tool registered; specialist resume regression passed
privacy_check: passed
secret_check: passed; no secrets committed or copied into project files
integrations_touched: Hermes config and user plugin, Codex CLI/telemetry, Telegram report
services_restarted: hermes-gateway.service; graceful drain honored
files_changed: docs/specialist-model-router.md; plugins/specialist_router/{plugin.yaml,init.py,router.py}; tests/plugins/specialist_router/test_router.py
summary: Deployed quota-aware automatic routing with GPT-5.6 coordinator, separate Spark/sol specialists, 20% sol reserve, one-attempt escalation, compact handoff, Spark validation, Telegram status, and resumable specialist sessions.
blockers: none; PR remains unmerged pending owner review
next_recommended_action: review PR #1 and use /model-route-status in Telegram
next_goal_suggestion: monitor first production coding routes for classification tuning
safe_to_merge: yes; 125 tests pass, live routing and rollback were verified, and no core files or secrets changed

@djpapzin

Copy link
Copy Markdown
Owner Author

AGENT_STATUS_UPDATE
status: needs_review
agent: codex
repo: djpapzin/hermes-agent
thread_or_topic: Telegram home channel
branch: agent/0-quota-specialist-router-codex
pr_url: #1
issue_url: none
commit: f32707c
tests: 125 passed (router + existing plugin manager); 5 policy scenarios passed; direct plugin executor smoke passed
checks: verified exact CLI syntax and IDs; coordinator/Spark/sol live probes passed; automatic Telegram route observed; executor pinned to verified Codex binary; gateway active; Telegram connected; deployed file matches commit
privacy_check: passed
secret_check: passed; no secrets committed or copied into project files
integrations_touched: Hermes config and user plugin, Codex CLI/telemetry, Telegram report
services_restarted: hermes-gateway.service; configured graceful drain honored
files_changed: docs/specialist-model-router.md; plugins/specialist_router/{plugin.yaml,init.py,router.py}; tests/plugins/specialist_router/test_router.py
summary: Deployed automatic GPT-5.6 coordinator routing to separate Spark/sol specialists with 20% sol reserve, one-attempt escalation, compact handoff, Spark validation, Telegram status, resumable sessions, and a verified systemd-safe Codex executor.
blockers: none; PR remains unmerged pending owner review
next_recommended_action: review PR #1 and use /model-route-status in Telegram
next_goal_suggestion: monitor first completed production coding route for classifier tuning
safe_to_merge: yes; tests and live executor pass, rollback is documented, and no core files or secrets changed

@djpapzin

Copy link
Copy Markdown
Owner Author

Superseded because this PR was created while the fork main branch was 2,153 commits behind upstream; GitHub pinned the stale base snapshot, causing unrelated contributor-attribution failures. The feature branch itself remains unchanged and will be reopened as a clean PR against the synchronized main base.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26e2270cc9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"goal": {"type": "string"},
"repository": {"type": "string"},
"risk": {"type": "string", "enum": ["auto", "low", "high", "critical"]},
"simulate_spark_failure": {"type": "boolean"},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove test-only escalation flag from the tool schema

This optional field is part of the model-visible tool schema, and route_tool passes it through to Router.execute, where a true value makes the Spark attempt return a simulated failure and escalates the task to Sol. A user can ask the coordinator to set this flag on any low-risk coding request, bypassing the quota-saving Spark-first policy and burning Sol quota; keep this as a test-only/internal path instead of advertising it.

Useful? React with 👍 / 👎.


def reserve_active(self, quotas: dict[str, PoolQuota] | None = None) -> bool:
sol = (quotas or self.quotas())["sol"]
return sol.weekly_remaining is not None and sol.weekly_remaining <= self.config.reserve_percent

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat exhausted five-hour Sol quota as unavailable

When Sol's primary/five-hour bucket is empty but weekly remaining is above the reserve, reserve_active returns false and execute leaves high-risk or failed-Spark work on Sol, so the router immediately calls an exhausted model instead of staying on Spark or reporting no Sol capacity. The quota parser already records five_hour_remaining, so include that window in the availability check before allowing Sol.

Useful? React with 👍 / 👎.

Comment thread run_agent.py
def _is_copilot_url(self) -> bool:
"""Return True when the base URL targets GitHub Copilot or GitHub Models."""
return (
"api.githubcopilot.com" in self._base_url_lower

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recognize enterprise Copilot hosts in _is_copilot_url

For enterprise Copilot accounts, the token exchange can store base URLs such as api.enterprise.githubcopilot.com, but this helper only recognizes the literal public host. Those sessions skip the GitHub Responses preflight and first-turn x-initiator header in conversation_loop, so resumed or rotated enterprise sessions can replay connection-bound response item IDs and lose Copilot's user-initiated routing. Use the suffix-aware base_url_host_matches(..., "githubcopilot.com") check instead of the literal substring.

Useful? React with 👍 / 👎.

attempts.append(spark)
if not spark["ok"] or FAILURE.search(spark.get("message", "")):
handoff = self._handoff(goal, repo, spark)
route = "sol"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Respect Sol reserve when escalating Spark failures

When Sol's weekly reserve is active, direct Sol routes are downgraded above, but a failed Spark attempt still unconditionally flips route to sol. For a low-risk or otherwise non-critical request while Sol is at or below the reserve, any Spark error now burns the reserved Sol pool, defeating the reserve policy; gate this escalation on not reserve or critical risk before invoking Sol.

Useful? React with 👍 / 👎.

Comment on lines +202 to +203
executor = concurrent.futures.ThreadPoolExecutor(
max_workers=1, thread_name_prefix=f"secret-src-{source.name}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce secret-source timeouts with cancellable workers

If an enabled plugin secret source hangs beyond its timeout, this uses a normal ThreadPoolExecutor worker; shutdown(wait=False) does not stop that non-daemon thread, so the CLI can still leak the blocked fetch and wait on it at process exit despite returning a TIMEOUT result. Use a cancellable process/subprocess or an explicitly daemonized worker if the registry promises a wall-clock startup budget.

Useful? React with 👍 / 👎.

Comment thread agent/codex_runtime.py
# skips its own append_to_transcript DB write — writing again there
# would re-INSERT the already-flushed user turn (append_message has no
# dedup), reintroducing the #860 / #42039 duplicate-write bug.
"agent_persisted": True,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Only skip gateway DB writes after a successful flush

If _flush_messages_to_session_db raises here, the exception is swallowed but the result still reports agent_persisted=True, causing the gateway to call append_to_transcript(..., skip_db=True). In a transient SQLite/DB failure on the codex app-server path, the assistant/tool rows are then never written to state.db, so session search, resume, and memory distillation miss the completed turn; return false unless the agent-side flush actually succeeded.

Useful? React with 👍 / 👎.

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.